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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLudjwx2GWw8CFdYaBzVzK
This commit is contained in:
jared 2026-07-13 11:00:26 -04:00
parent f9a81a17c5
commit 3708c0099d
6 changed files with 87 additions and 1 deletions

View File

@ -8,6 +8,11 @@
"type": "command", "type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session_start.py", "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/session_start.py",
"timeout": 5 "timeout": 5
},
{
"type": "command",
"command": "ruby ${CLAUDE_PLUGIN_ROOT}/hooks/triage_check.rb",
"timeout": 8
} }
] ]
} }

View File

@ -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 pull beats push board state is fetched on demand via /os-backlog:list, never
pushed into a session). 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) git project -> additionalContext note (rules only, near-zero tokens)
not a git repo -> silent (process rules only apply inside real projects) not a git repo -> silent (process rules only apply inside real projects)

View File

@ -42,7 +42,8 @@ module Backlog
# @param name [String] board name (never an archived---prefixed name; # @param name [String] board name (never an archived---prefixed name;
# this method only ever targets the active board) # 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] # @return [Result]
def ensure(name, project_name:) def ensure(name, project_name:)
raise ArgumentError, "board name must not use the archived-- prefix" if BoardSpec.archived?(name) raise ArgumentError, "board name must not use the archived-- prefix" if BoardSpec.archived?(name)

View File

@ -120,6 +120,18 @@ class BoardEnsurerTest < Minitest::Test
refute_nil archived refute_nil archived
end 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 def test_ensure_rejects_archived_prefixed_name
client = FakePlankaClient.new client = FakePlankaClient.new
ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1") ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1")

View File

@ -90,4 +90,22 @@ class ResolverTest < Minitest::Test
assert_equal "use llf-schema", resolver.resolve_string assert_equal "use llf-schema", resolver.resolve_string
end 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 end

View File

@ -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