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:
parent
3e42aecc13
commit
3c5a17c288
|
|
@ -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)
|
||||
class Check:
|
||||
name: str
|
||||
|
|
@ -207,4 +241,5 @@ REGISTRY = [
|
|||
Check("adr-system-present", adr_system_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("tracker-configured", tracker_configured, project_scoped=True),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,11 +22,13 @@ from checks import ( # noqa: E402
|
|||
ABSENT_NOTE,
|
||||
ENV_VAR,
|
||||
PRESENT_NOTE,
|
||||
TRACKER_ABSENT_NOTE,
|
||||
Check,
|
||||
CheckResult,
|
||||
Ctx,
|
||||
adr_system_present,
|
||||
subagent_model_env_override,
|
||||
tracker_configured,
|
||||
vault_hub_note_present,
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""run() contract: routing, aggregation, snooze/suppress, isolation."""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue