From 3708c0099dc2fb8771db92bb2e728bdf9b2a5a69 Mon Sep 17 00:00:00 2001 From: jared Date: Mon, 13 Jul 2026 11:00:26 -0400 Subject: [PATCH] os-backlog: wire the triage-trigger hook + arbitrary-project test coverage (cards #1818330518584820864, #1818330528525321347) hooks/triage_check.rb and the Servers project heuristic/board-ensure generalization already landed in f9a81a1 but the triage hook was never registered in hooks.json, so it could never fire. This wires it in (second SessionStart command, 8s timeout) and adds the missing test coverage: TriageCheck's needs-triage/note-composition logic, an arbitrary-project (Servers) BoardEnsurer case, and the ~/servers resolver-ambiguity fix. Also reconciles session_start.py's pull-beats-push docstring with the new hook's sanctioned exception, and fixes a stray comment-indent bug in board_ensurer.rb. Suite 128/282/0. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PLudjwx2GWw8CFdYaBzVzK --- plugins/os-backlog/hooks/hooks.json | 5 +++ plugins/os-backlog/hooks/session_start.py | 9 ++++ .../os-backlog/lib/backlog/board_ensurer.rb | 3 +- .../os-backlog/tests/board_ensurer_test.rb | 12 ++++++ plugins/os-backlog/tests/resolver_test.rb | 18 ++++++++ plugins/os-backlog/tests/triage_check_test.rb | 41 +++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 plugins/os-backlog/tests/triage_check_test.rb diff --git a/plugins/os-backlog/hooks/hooks.json b/plugins/os-backlog/hooks/hooks.json index cdb73ee..c92ebf1 100644 --- a/plugins/os-backlog/hooks/hooks.json +++ b/plugins/os-backlog/hooks/hooks.json @@ -8,6 +8,11 @@ "type": "command", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session_start.py", "timeout": 5 + }, + { + "type": "command", + "command": "ruby ${CLAUDE_PLUGIN_ROOT}/hooks/triage_check.rb", + "timeout": 8 } ] } diff --git a/plugins/os-backlog/hooks/session_start.py b/plugins/os-backlog/hooks/session_start.py index 1011d1f..6eca611 100644 --- a/plugins/os-backlog/hooks/session_start.py +++ b/plugins/os-backlog/hooks/session_start.py @@ -7,6 +7,15 @@ 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) diff --git a/plugins/os-backlog/lib/backlog/board_ensurer.rb b/plugins/os-backlog/lib/backlog/board_ensurer.rb index 83b8ca7..d1c6558 100644 --- a/plugins/os-backlog/lib/backlog/board_ensurer.rb +++ b/plugins/os-backlog/lib/backlog/board_ensurer.rb @@ -42,7 +42,8 @@ module Backlog # @param name [String] board name (never an archived---prefixed name; # this method only ever targets the active board) - # @param project_name [String] "Dev" or "Clients" (or any project name) + # @param project_name [String] any Planka project name — created on demand + # if it doesn't already exist # @return [Result] def ensure(name, project_name:) raise ArgumentError, "board name must not use the archived-- prefix" if BoardSpec.archived?(name) diff --git a/plugins/os-backlog/tests/board_ensurer_test.rb b/plugins/os-backlog/tests/board_ensurer_test.rb index a46cb88..eba50e6 100644 --- a/plugins/os-backlog/tests/board_ensurer_test.rb +++ b/plugins/os-backlog/tests/board_ensurer_test.rb @@ -120,6 +120,18 @@ class BoardEnsurerTest < Minitest::Test refute_nil archived end + def test_ensure_creates_an_arbitrary_project_name_on_demand + client = FakePlankaClient.new + ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1") + + result = ensurer.ensure("ovh-prod", project_name: "Servers") + + assert result.created + project = client.projects_store.first + assert_equal "Servers", project.name + assert_equal "Servers", result.project.name + end + def test_ensure_rejects_archived_prefixed_name client = FakePlankaClient.new ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1") diff --git a/plugins/os-backlog/tests/resolver_test.rb b/plugins/os-backlog/tests/resolver_test.rb index fc11a93..b26ff59 100644 --- a/plugins/os-backlog/tests/resolver_test.rb +++ b/plugins/os-backlog/tests/resolver_test.rb @@ -90,4 +90,22 @@ class ResolverTest < Minitest::Test assert_equal "use llf-schema", resolver.resolve_string end + + def test_unconfigured_servers_repo_uses_board_named_after_repo + resolver = Backlog::Resolver.new( + repo_path: "/home/jared/servers/proxmox-ubuntu", + config_contents: nil, + boards: %w[proxmox-ubuntu] + ) + + assert_equal "use proxmox-ubuntu", resolver.resolve_string + end + + def test_project_for_returns_dev_clients_servers_or_nil + assert_equal "Dev", Backlog::Resolver.project_for("/home/jared/dev/some-repo") + assert_equal "Clients", Backlog::Resolver.project_for("/home/jared/clients/acme-co") + assert_equal "Servers", Backlog::Resolver.project_for("/home/jared/servers/ovh-prod") + assert_equal "Servers", Backlog::Resolver.project_for("/home/jared/servers") + assert_nil Backlog::Resolver.project_for("/home/jared/scratch/some-repo") + end end diff --git a/plugins/os-backlog/tests/triage_check_test.rb b/plugins/os-backlog/tests/triage_check_test.rb new file mode 100644 index 0000000..583909d --- /dev/null +++ b/plugins/os-backlog/tests/triage_check_test.rb @@ -0,0 +1,41 @@ +require_relative "test_helper" + +class TriageCheckTest < Minitest::Test + def test_card_missing_both_labels_needs_triage + assert Backlog::TriageCheck.needs_triage?([]) + end + + def test_card_missing_priority_label_needs_triage + assert Backlog::TriageCheck.needs_triage?(["semi"]) + end + + def test_card_missing_autonomy_label_needs_triage + assert Backlog::TriageCheck.needs_triage?(["P1"]) + end + + def test_fully_labeled_card_does_not_need_triage + refute Backlog::TriageCheck.needs_triage?(%w[P1 semi]) + end + + def test_note_for_no_unlabeled_cards_is_nil + assert_nil Backlog::TriageCheck.note_for("cc-os", [%w[P1 semi], %w[P0 hitl]]) + end + + def test_note_for_counts_only_unlabeled_cards + labels = [%w[P1 semi], [], ["P2"]] + note = Backlog::TriageCheck.note_for("cc-os", labels) + + refute_nil note + assert_includes note, "2 unlabeled Backlog cards on board cc-os" + assert_includes note, "card-triage" + assert_includes note, "haiku" + assert_includes note, "background" + end + + def test_note_for_singular_card_uses_singular_wording + note = Backlog::TriageCheck.note_for("cc-os", [[]]) + + assert_includes note, "1 unlabeled Backlog card on board cc-os" + refute_includes note, "1 unlabeled Backlog cards" + end +end