Merge branch 'main' into client

This commit is contained in:
2026-02-11 23:03:32 +01:00
4 changed files with 45 additions and 18 deletions
+8 -2
View File
@@ -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 <type> <title> [--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]
+3 -3
View File
@@ -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)
+32 -13
View File
@@ -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":
+2
View File
@@ -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);
-- ---------------------------------------------------------------------------