diff --git a/.claude/skills/ticket/SKILL.md b/.claude/skills/ticket/SKILL.md index b4fd087f3..00feba4d3 100644 --- a/.claude/skills/ticket/SKILL.md +++ b/.claude/skills/ticket/SKILL.md @@ -34,7 +34,7 @@ For raw SQL access (rare), use the wrapper scripts: ### List tickets ```bash -db/connectors/ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] +db/connectors/ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] [--team T] ``` ### Show ticket detail @@ -45,7 +45,7 @@ Returns full ticket with children, blockers, and dependents. ### Create ticket ```bash -db/connectors/ticket create [--parent N] [--priority P] [--decision D] +db/connectors/ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T] ``` Types: `initiative`, `epic`, `story`, `task`, `bug` Priorities: `critical`, `high`, `medium`, `low` @@ -63,6 +63,12 @@ db/connectors/ticket assign <id> <agent> db/connectors/ticket unassign <id> ``` +### Team assignment +```bash +db/connectors/ticket team <id> <teams> +``` +Teams are comma-separated, e.g. `server`, `client`, `server,client`. + ### Sprint management ```bash db/connectors/ticket sprint [--active] diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc15aef9..98f1e39b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Format based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Added +- `ticket team` command and `--team` filter — comma-separated team assignment for tickets (server, client, joint, content) + ### Fixed - `start-sprint` skill uses `git rev-parse --show-toplevel` for worktree-safe absolute paths — fixes "No such file or directory" errors on team branches @@ -126,12 +129,9 @@ Format based on [Keep a Changelog](https://keepachangelog.com/). - Rejected alternatives R-004 through R-010 documented (pure Bevy, pure Godot, GDExtension, C++ GDExtension, Fyrox, custom framework, protobuf) ### Fixed -<<<<<<< HEAD - create-skill references to nonexistent init_skill.py and package_skill.py scripts -======= - SimBridge wire format: inputs now batch-encoded as Vec\<PlayerInput\> array per server protocol (was sending individual inputs per frame) - SimBridge server args: positional address format ("127.0.0.1:9876") matching server CLI, port default corrected to 9876 ->>>>>>> origin/client - Wire format mismatch: LocalBridge now uses rmp_serde::to_vec_named() (named maps) matching client Protocol.gd expectations - EOF on bridge read now returns BridgeError::Transport for disconnect detection instead of empty Vec - WalkabilityMap rewritten from flat Vec to chunk-based HashMap per D-012 architecture (Tyre review) diff --git a/db/connectors/ticket b/db/connectors/ticket index c70852b01..e8cc80d13 100755 --- a/db/connectors/ticket +++ b/db/connectors/ticket @@ -3,17 +3,18 @@ Ticket CLI — ergonomic interface to the project ticketing database. Usage: - ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] + ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] [--team T] ticket show <id> ticket done <id> [<id> ...] ticket status <id> <new_status> ticket assign <id> <agent> ticket unassign <id> + ticket team <id> <teams> ticket sprint [--active] ticket sprint assign <id> <sprint_id> ticket deps <id> ticket search <keyword> - ticket create <type> <title> [--parent N] [--priority P] [--decision D] + ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T] ticket epics [--status S] ticket children <id> ticket count [--status S] @@ -112,7 +113,7 @@ def parse_flags(args, known_flags): # --------------------------------------------------------------------------- def cmd_list(conn, args): - flags, _ = parse_flags(args, ["status", "priority", "epic", "sprint", "assigned"]) + flags, _ = parse_flags(args, ["status", "priority", "epic", "sprint", "assigned", "team"]) conditions = [] params = [] if "status" in flags: @@ -130,9 +131,13 @@ def cmd_list(conn, args): if "assigned" in flags: conditions.append("t.assigned_to = ?") params.append(flags["assigned"]) + if "team" in flags: + # Match exact team name within comma-separated list + conditions.append("(',' || t.team || ',' LIKE '%,' || ? || ',%')") + params.append(flags["team"]) where = " AND ".join(conditions) if conditions else "1=1" sql = f"""SELECT t.id, t.type, t.title, t.status, t.priority, t.assigned_to, - t.parent_id, t.sprint_id + t.team, t.parent_id, t.sprint_id FROM tickets t WHERE {where} ORDER BY CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 @@ -184,6 +189,8 @@ def _print_brief(tickets): parts = [f"{t['type']}", f"P:{t['priority']}", f"S:{t['status']}"] if t.get("assigned_to"): parts.append(f"@{t['assigned_to']}") + if t.get("team"): + parts.append(f"Team:{t['team']}") if t.get("parent_id"): parts.append(f"Epic:#{t['parent_id']} ({t.get('parent_title', '?')})") if t.get("sprint_id"): @@ -265,25 +272,26 @@ def cmd_deps(conn, ticket_id): def cmd_search(conn, keyword): - rows = query(conn, """SELECT id, type, title, status, priority, assigned_to + rows = query(conn, """SELECT id, type, title, status, priority, assigned_to, team FROM tickets WHERE title LIKE ? OR description LIKE ? ORDER BY id""", (f"%{keyword}%", f"%{keyword}%")) out({"ok": True, "count": len(rows), "rows": rows}) def cmd_create(conn, args): - flags, positional = parse_flags(args, ["parent", "priority", "decision"]) + flags, positional = parse_flags(args, ["parent", "priority", "decision", "team"]) if len(positional) < 2: - out({"ok": False, "error": "Usage: ticket create <type> <title> [--parent N] [--priority P] [--decision D]"}) + out({"ok": False, "error": "Usage: ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T]"}) return ticket_type = positional[0] title = " ".join(positional[1:]) parent_id = int(flags["parent"]) if "parent" in flags else None priority = flags.get("priority", "medium") decision_ref = flags.get("decision") + team = flags.get("team") conn.execute( - "INSERT INTO tickets (type, title, parent_id, priority, decision_ref) VALUES (?, ?, ?, ?, ?)", - (ticket_type, title, parent_id, priority, decision_ref)) + "INSERT INTO tickets (type, title, parent_id, priority, decision_ref, team) VALUES (?, ?, ?, ?, ?, ?)", + (ticket_type, title, parent_id, priority, decision_ref, team)) conn.commit() last_id = query(conn, "SELECT last_insert_rowid() as id")[0]["id"] out({"ok": True, "id": last_id, "title": title}) @@ -297,7 +305,7 @@ def cmd_epics(conn, args): conditions.append("t.status = ?") params.append(flags["status"]) where = " AND ".join(conditions) - rows = query(conn, f"""SELECT t.id, t.title, t.status, t.priority, t.assigned_to, + rows = query(conn, f"""SELECT t.id, t.title, t.status, t.priority, t.assigned_to, t.team, COUNT(c.id) as child_count, SUM(CASE WHEN c.status='done' THEN 1 ELSE 0 END) as done_count FROM tickets t LEFT JOIN tickets c ON c.parent_id = t.id @@ -308,11 +316,16 @@ def cmd_epics(conn, args): def cmd_children(conn, ticket_id): - rows = query(conn, """SELECT id, type, title, status, priority, assigned_to + rows = query(conn, """SELECT id, type, title, status, priority, assigned_to, team FROM tickets WHERE parent_id = ? ORDER BY id""", (int(ticket_id),)) out({"ok": True, "count": len(rows), "parent_id": int(ticket_id), "rows": rows}) +def cmd_team(conn, ticket_id, teams): + updated = execute(conn, "UPDATE tickets SET team=?, updated_at=datetime('now') WHERE id=?", (teams, int(ticket_id))) + out({"ok": True, "updated": updated, "id": int(ticket_id), "team": teams}) + + def cmd_count(conn, args): flags, _ = parse_flags(args, ["status"]) if "status" in flags: @@ -329,17 +342,18 @@ def cmd_count(conn, args): HELP = """ticket — project ticket CLI Usage: - ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] + ticket list [--status S] [--priority P] [--epic N] [--sprint N] [--assigned A] [--team T] ticket show [--brief] <id> [<id>...] Full ticket detail (--brief for summary) ticket done <id> [<id> ...] Mark tickets as done ticket status <id> <new_status> Change ticket status ticket assign <id> <agent> Assign ticket to agent/branch ticket unassign <id> Remove assignment + ticket team <id> <teams> Set team(s) (comma-separated, e.g. server,client) ticket sprint [--active] List sprints ticket sprint assign <id> <sprint> Assign ticket to sprint ticket deps <id> Show ticket dependencies ticket search <keyword> Search tickets by title/description - ticket create <type> <title> [--parent N] [--priority P] [--decision D] + ticket create <type> <title> [--parent N] [--priority P] [--decision D] [--team T] ticket epics [--status S] List epics with child counts ticket children <id> List children of a ticket ticket count [--status S] Count tickets by status""" @@ -390,6 +404,11 @@ def main(): out({"ok": False, "error": "Usage: ticket unassign <id>"}) else: cmd_unassign(conn, args[0]) + elif cmd == "team": + if len(args) < 2: + out({"ok": False, "error": "Usage: ticket team <id> <teams>"}) + else: + cmd_team(conn, args[0], args[1]) elif cmd == "sprint": cmd_sprint(conn, args) elif cmd == "deps": diff --git a/db/schema.sql b/db/schema.sql index 18fa6485d..d202f85d2 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS tickets ( status TEXT NOT NULL DEFAULT 'backlog' CHECK(status IN ('backlog', 'ready', 'in_progress', 'review', 'done', 'cancelled')), priority TEXT DEFAULT 'medium' CHECK(priority IN ('critical', 'high', 'medium', 'low')), assigned_to TEXT, + team TEXT, -- comma-separated team names, e.g. 'server', 'client,server' decision_ref TEXT, -- e.g. 'D-010' or 'Q-001' sprint_id INTEGER REFERENCES sprints(id), created_at TEXT NOT NULL DEFAULT (datetime('now')), @@ -58,6 +59,7 @@ CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_id); CREATE INDEX IF NOT EXISTS idx_tickets_sprint ON tickets(sprint_id); CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to); CREATE INDEX IF NOT EXISTS idx_tickets_decision ON tickets(decision_ref); +CREATE INDEX IF NOT EXISTS idx_tickets_team ON tickets(team); CREATE INDEX IF NOT EXISTS idx_history_ticket ON ticket_history(ticket_id); -- ---------------------------------------------------------------------------