code-coordinator 0.5.46__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_coordinator-0.5.46.dist-info/METADATA +625 -0
- code_coordinator-0.5.46.dist-info/RECORD +295 -0
- code_coordinator-0.5.46.dist-info/WHEEL +5 -0
- code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
- code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
- code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
- coord/__init__.py +176 -0
- coord/_board_mapping.py +229 -0
- coord/acceptance.py +468 -0
- coord/acceptance_drivers.py +632 -0
- coord/agent.py +7517 -0
- coord/agent_app.py +1555 -0
- coord/agent_update.py +417 -0
- coord/agents/opencode/.gitignore +13 -0
- coord/agents/opencode/agents/work.md +129 -0
- coord/agents/opencode/routing.jsonc +49 -0
- coord/audit.py +301 -0
- coord/auto_loop.py +1440 -0
- coord/board_bool_guard.py +72 -0
- coord/board_service.py +141 -0
- coord/board_wire.py +309 -0
- coord/brain.py +581 -0
- coord/branch_model.py +214 -0
- coord/cargo_cache.py +258 -0
- coord/ci_github.py +386 -0
- coord/ci_store.py +560 -0
- coord/claim.py +353 -0
- coord/cli.py +454 -0
- coord/client.py +610 -0
- coord/commands/__init__.py +1 -0
- coord/commands/_common.py +329 -0
- coord/commands/acceptance.py +916 -0
- coord/commands/agent_ops.py +1339 -0
- coord/commands/audit.py +131 -0
- coord/commands/chat.py +320 -0
- coord/commands/dispatch.py +1780 -0
- coord/commands/dispatch_workers.py +4894 -0
- coord/commands/drive.py +616 -0
- coord/commands/drive_queue.py +1203 -0
- coord/commands/gate_a.py +217 -0
- coord/commands/gates.py +89 -0
- coord/commands/issues.py +681 -0
- coord/commands/lifecycle.py +513 -0
- coord/commands/merge.py +1900 -0
- coord/commands/milestone.py +2081 -0
- coord/commands/plan_followup.py +1243 -0
- coord/commands/plans.py +156 -0
- coord/commands/release.py +2232 -0
- coord/commands/report.py +341 -0
- coord/commands/review.py +1523 -0
- coord/commands/scorecard.py +252 -0
- coord/commands/sessions.py +1930 -0
- coord/commands/setup.py +576 -0
- coord/commands/status.py +2089 -0
- coord/commands/terminal.py +385 -0
- coord/commands/test_gate.py +775 -0
- coord/commands/tui.py +288 -0
- coord/comments.py +718 -0
- coord/config.py +3032 -0
- coord/conflict_fix.py +633 -0
- coord/dao.py +483 -0
- coord/dashboard/__init__.py +0 -0
- coord/dashboard/fixture.py +376 -0
- coord/dashboard/index.html +658 -0
- coord/dashboard/server.py +1894 -0
- coord/dashboard/terminal.py +382 -0
- coord/dashboard/webapp/.gitignore +9 -0
- coord/dashboard/webapp/components.json +17 -0
- coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
- coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
- coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
- coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
- coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
- coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
- coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
- coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
- coord/dashboard/webapp/dist/icons/icon.svg +5 -0
- coord/dashboard/webapp/dist/index.html +38 -0
- coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
- coord/dashboard/webapp/dist/sw.js +1 -0
- coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
- coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
- coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
- coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
- coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
- coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
- coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
- coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
- coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
- coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
- coord/dashboard/webapp/eslint.config.js +20 -0
- coord/dashboard/webapp/index.html +37 -0
- coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
- coord/dashboard/webapp/package-lock.json +10584 -0
- coord/dashboard/webapp/package.json +63 -0
- coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
- coord/dashboard/webapp/playwright.config.ts +93 -0
- coord/dashboard/webapp/postcss.config.js +6 -0
- coord/dashboard/webapp/public/icons/icon-192.png +0 -0
- coord/dashboard/webapp/public/icons/icon-512.png +0 -0
- coord/dashboard/webapp/public/icons/icon.svg +5 -0
- coord/dashboard/webapp/src/App.tsx +140 -0
- coord/dashboard/webapp/src/api/client.ts +199 -0
- coord/dashboard/webapp/src/api/generated.ts +176 -0
- coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
- coord/dashboard/webapp/src/components/Detail.tsx +800 -0
- coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
- coord/dashboard/webapp/src/components/Home.tsx +435 -0
- coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
- coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
- coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
- coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
- coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
- coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
- coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
- coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
- coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
- coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
- coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
- coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
- coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
- coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
- coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
- coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
- coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
- coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
- coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
- coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
- coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
- coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
- coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
- coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
- coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
- coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
- coord/dashboard/webapp/src/index.css +210 -0
- coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
- coord/dashboard/webapp/src/lib/utils.ts +6 -0
- coord/dashboard/webapp/src/main.tsx +46 -0
- coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
- coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
- coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
- coord/dashboard/webapp/src/realtime/connection.ts +227 -0
- coord/dashboard/webapp/src/realtime/events.ts +100 -0
- coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
- coord/dashboard/webapp/src/routes/paths.ts +92 -0
- coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
- coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
- coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
- coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
- coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
- coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
- coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
- coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
- coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
- coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
- coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
- coord/dashboard/webapp/src/shell/railItems.ts +105 -0
- coord/dashboard/webapp/src/shell/shellState.ts +174 -0
- coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
- coord/dashboard/webapp/src/test-setup.ts +41 -0
- coord/dashboard/webapp/src/vite-env.d.ts +2 -0
- coord/dashboard/webapp/tailwind.config.js +140 -0
- coord/dashboard/webapp/tsconfig.json +25 -0
- coord/dashboard/webapp/tsconfig.node.json +11 -0
- coord/dashboard/webapp/vite.config.ts +71 -0
- coord/db.py +1076 -0
- coord/dead_end.py +332 -0
- coord/deploy/README.md +33 -0
- coord/deploy/coord-agent.service +89 -0
- coord/deploy/coord-db-backup.service +60 -0
- coord/deploy/coord-db-backup.sh +74 -0
- coord/deploy/coord-db-backup.timer +18 -0
- coord/deploy/coord-drive-queue.service +117 -0
- coord/deploy/coord-drive-queue.timer +39 -0
- coord/deploy/coord-notify.service +48 -0
- coord/deploy/coord-notify.timer +24 -0
- coord/deploy/coord-release-propagate.service +83 -0
- coord/deploy/coord-release-propagate.timer +38 -0
- coord/deploy/coord-release-window.service +119 -0
- coord/deploy/coord-release-window.timer +36 -0
- coord/deploy/coord-serve.service +82 -0
- coord/deploy/coord-web-dist-build.service +43 -0
- coord/deploy/coord-web-dist-build.timer +36 -0
- coord/deploy/coord-web.service +125 -0
- coord/deploy_manifest.py +80 -0
- coord/deploy_units.py +384 -0
- coord/deps.py +115 -0
- coord/diagnose.py +1623 -0
- coord/dispatch.py +1009 -0
- coord/dist_name.py +123 -0
- coord/drive.py +3101 -0
- coord/drive_queue.py +2298 -0
- coord/drive_state.py +870 -0
- coord/events.py +381 -0
- coord/failure_class.py +914 -0
- coord/filelock.py +168 -0
- coord/fleet_config_health.py +300 -0
- coord/freshness.py +206 -0
- coord/gate_a.py +469 -0
- coord/gate_b.py +411 -0
- coord/gate_snapshot.py +385 -0
- coord/gates.py +582 -0
- coord/github_ops.py +1954 -0
- coord/goal.py +125 -0
- coord/graph_health.py +348 -0
- coord/health/__init__.py +69 -0
- coord/health/aggregate.py +129 -0
- coord/health/checks/__init__.py +13 -0
- coord/health/checks/agent_install.py +280 -0
- coord/health/checks/cargo_targets.py +171 -0
- coord/health/checks/claude_binary.py +65 -0
- coord/health/checks/deploy_lane_facts.py +458 -0
- coord/health/checks/disk.py +99 -0
- coord/health/checks/fleet_board.py +89 -0
- coord/health/checks/fleet_deploy_lanes.py +469 -0
- coord/health/checks/fleet_phantom.py +69 -0
- coord/health/checks/fleet_unit_drift.py +151 -0
- coord/health/checks/graph.py +192 -0
- coord/health/checks/plan_usage.py +88 -0
- coord/health/checks/repo_state.py +161 -0
- coord/health/checks/spawned_coord.py +465 -0
- coord/health/checks/timer_active.py +254 -0
- coord/health/checks/toolchain.py +547 -0
- coord/health/checks/unit_drift.py +648 -0
- coord/health/checks/unit_enablement.py +171 -0
- coord/health/checks/worktrees.py +96 -0
- coord/health/cli.py +121 -0
- coord/health/context.py +106 -0
- coord/health/fleet_snapshot.py +477 -0
- coord/health/models.py +250 -0
- coord/health/pypi.py +231 -0
- coord/health/registry.py +240 -0
- coord/health/render.py +82 -0
- coord/health/units.py +60 -0
- coord/hooks.py +106 -0
- coord/housekeeping.py +204 -0
- coord/interactive.py +4286 -0
- coord/issue_store.py +1496 -0
- coord/liveness_auditor.py +293 -0
- coord/machine_pause.py +755 -0
- coord/merge_queue.py +4681 -0
- coord/milestone_chat.py +600 -0
- coord/milestone_dispatch.py +943 -0
- coord/milestone_gate.py +709 -0
- coord/milestone_order.py +840 -0
- coord/mock_author.py +334 -0
- coord/models.py +891 -0
- coord/network.py +269 -0
- coord/new_issue_chat.py +229 -0
- coord/notify.py +3226 -0
- coord/openapi.py +404 -0
- coord/overlap_fence.py +133 -0
- coord/parentage.py +200 -0
- coord/parentage_github.py +58 -0
- coord/pipeline.py +481 -0
- coord/plan_parser.py +266 -0
- coord/plans.py +543 -0
- coord/platform_paths.py +43 -0
- coord/pr_body_lint.py +67 -0
- coord/prereqs.py +533 -0
- coord/progress.py +425 -0
- coord/providers/__init__.py +683 -0
- coord/providers/base.py +218 -0
- coord/providers/claude.py +284 -0
- coord/providers/claude_pty.py +610 -0
- coord/providers/opencode.py +896 -0
- coord/reconcile.py +2233 -0
- coord/refine_chat.py +485 -0
- coord/release_cordon.py +525 -0
- coord/release_propagate.py +1176 -0
- coord/release_verify.py +777 -0
- coord/release_window.py +322 -0
- coord/reports.py +1643 -0
- coord/revalidate.py +1101 -0
- coord/review.py +3317 -0
- coord/scorecard.py +484 -0
- coord/serve_app.py +7192 -0
- coord/skills/update-issue/SKILL.md +93 -0
- coord/smoke.py +1030 -0
- coord/split_work.py +210 -0
- coord/stage_projection.py +650 -0
- coord/state.py +5720 -0
- coord/test_author.py +1064 -0
- coord/test_chat.py +352 -0
- coord/test_orchestrator.py +494 -0
- coord/test_report.py +178 -0
- coord/tui_release.py +271 -0
- coord/usage.py +753 -0
- coord/usage_limits.py +358 -0
- coord/usage_rollup.py +709 -0
- coord/worker_events.py +954 -0
coord/commands/audit.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""`coord audit` — CLI query surface over the append-only audit trail (#1037).
|
|
2
|
+
|
|
3
|
+
Routes through the ``coord.state.list_audit_log`` seam (daemon when
|
|
4
|
+
``board_service`` is configured, local DB otherwise) — same pattern as
|
|
5
|
+
``coord context show`` — so a thin client never opens ``~/.coord/coord.db``
|
|
6
|
+
directly. Human table by default (relative timestamps); ``--json`` for
|
|
7
|
+
scripting/tests. Also doubles as the manual + CI verification path for
|
|
8
|
+
#1036 (capture) / #1038 (operational tier, once it lands).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json as _json
|
|
14
|
+
import time
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
|
|
20
|
+
from coord.commands._common import _CONFIG_OPTION
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _parse_timestamp(raw: str | None) -> float | None:
|
|
24
|
+
"""Accept an epoch number (``1720000000`` / ``1720000000.5``) or an
|
|
25
|
+
ISO-8601 string (``2026-07-10`` / ``2026-07-10T12:00:00``)."""
|
|
26
|
+
if not raw:
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
return float(raw)
|
|
30
|
+
except ValueError:
|
|
31
|
+
pass
|
|
32
|
+
try:
|
|
33
|
+
return datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp()
|
|
34
|
+
except ValueError as e:
|
|
35
|
+
raise click.BadParameter(f"not an epoch number or ISO-8601 timestamp: {raw!r}") from e
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _relative_time(ts: float | None) -> str:
|
|
39
|
+
if ts is None:
|
|
40
|
+
return "-"
|
|
41
|
+
delta = time.time() - ts
|
|
42
|
+
if delta < 0:
|
|
43
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
44
|
+
for seconds, unit in (
|
|
45
|
+
(60, "s"), (3600, "m"), (86400, "h"), (86400 * 30, "d"),
|
|
46
|
+
):
|
|
47
|
+
if delta < seconds:
|
|
48
|
+
prior = {60: 1, 3600: 60, 86400: 3600, 86400 * 30: 86400}[seconds]
|
|
49
|
+
return f"{int(delta // prior)}{unit} ago"
|
|
50
|
+
return f"{int(delta // (86400 * 30))}mo ago"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _truncate(s: str, width: int) -> str:
|
|
54
|
+
return s if len(s) <= width else s[: width - 1] + "…"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@click.command(
|
|
58
|
+
"audit",
|
|
59
|
+
help="Query the audit trail (#1036/#1037) — dispatch, verdicts, merges, notifications.",
|
|
60
|
+
)
|
|
61
|
+
@click.option("--since", "since_raw", default=None, help="Epoch seconds or ISO-8601 — only rows at/after this time.")
|
|
62
|
+
@click.option("--until", "until_raw", default=None, help="Epoch seconds or ISO-8601 — only rows at/before this time.")
|
|
63
|
+
@click.option("--type", "event_type", default=None, help="Filter by event_type (e.g. test_passed, dispatched).")
|
|
64
|
+
@click.option("--category", default=None, help="Filter by category (e.g. test, merge, dispatch, review).")
|
|
65
|
+
@click.option("--repo", default=None, help="Filter by repo name.")
|
|
66
|
+
@click.option("--issue", default=None, type=int, help="Filter by issue number.")
|
|
67
|
+
@click.option("--assignment", "assignment_id", default=None, help="Filter by assignment_id.")
|
|
68
|
+
@click.option("--tier", default=None, help="Filter by tier (business|operational).")
|
|
69
|
+
@click.option("--limit", default=200, show_default=True, type=int, help="Max rows to return (hard-capped at 500).")
|
|
70
|
+
@click.option("--cursor", default=None, help="Keyset cursor from a previous run's next_cursor (for manual pagination).")
|
|
71
|
+
@click.option("--json", "output_json", is_flag=True, default=False, help="Output the raw {entries, next_cursor, has_more} JSON.")
|
|
72
|
+
@_CONFIG_OPTION
|
|
73
|
+
def audit(
|
|
74
|
+
since_raw: str | None,
|
|
75
|
+
until_raw: str | None,
|
|
76
|
+
event_type: str | None,
|
|
77
|
+
category: str | None,
|
|
78
|
+
repo: str | None,
|
|
79
|
+
issue: int | None,
|
|
80
|
+
assignment_id: str | None,
|
|
81
|
+
tier: str | None,
|
|
82
|
+
limit: int,
|
|
83
|
+
cursor: str | None,
|
|
84
|
+
output_json: bool,
|
|
85
|
+
config_path: Path, # noqa: ARG001 — accepted for --config-flag consistency; audit reads need no coordinator.yml
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Print (or dump as JSON) a page of the audit trail, newest-first."""
|
|
88
|
+
from coord.state import list_audit_log # noqa: PLC0415
|
|
89
|
+
|
|
90
|
+
since = _parse_timestamp(since_raw)
|
|
91
|
+
until = _parse_timestamp(until_raw)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
result = list_audit_log(
|
|
95
|
+
since=since, until=until, event_type=event_type, category=category,
|
|
96
|
+
repo=repo, issue=issue, assignment_id=assignment_id, tier=tier,
|
|
97
|
+
limit=limit, cursor=cursor,
|
|
98
|
+
)
|
|
99
|
+
except Exception as e: # noqa: BLE001 — surface a clean CLI error, not a traceback
|
|
100
|
+
click.echo(f"error: audit read failed: {e}", err=True)
|
|
101
|
+
raise SystemExit(1) from e
|
|
102
|
+
|
|
103
|
+
if output_json:
|
|
104
|
+
click.echo(_json.dumps(result, indent=2, default=str))
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
entries = result.get("entries") or []
|
|
108
|
+
if not entries:
|
|
109
|
+
click.echo("(no audit entries match)")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
click.echo(
|
|
113
|
+
f"{'WHEN':<10} {'TIER':<8} {'CATEGORY':<10} {'EVENT':<20} {'ACTOR':<11} "
|
|
114
|
+
f"{'REPO':<12} {'ISSUE':<7} {'SUMMARY'}"
|
|
115
|
+
)
|
|
116
|
+
for e in entries:
|
|
117
|
+
click.echo(
|
|
118
|
+
f"{_relative_time(e.get('ts')):<10} "
|
|
119
|
+
f"{_truncate(e.get('tier') or '-', 8):<8} "
|
|
120
|
+
f"{_truncate(e.get('category') or '-', 10):<10} "
|
|
121
|
+
f"{_truncate(e.get('event_type') or '-', 20):<20} "
|
|
122
|
+
f"{_truncate(e.get('actor') or '-', 11):<11} "
|
|
123
|
+
f"{_truncate(e.get('repo') or '-', 12):<12} "
|
|
124
|
+
f"{str(e.get('issue') if e.get('issue') is not None else '-'):<7} "
|
|
125
|
+
f"{_truncate(e.get('summary') or '', 60)}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if result.get("has_more"):
|
|
129
|
+
click.echo(
|
|
130
|
+
f"\n… more rows available — rerun with --cursor {result.get('next_cursor')!r}"
|
|
131
|
+
)
|
coord/commands/chat.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Refinement-chat commands: `refine-chat`, `test-chat`, `new-issue-chat`,
|
|
2
|
+
`refine-board`, `ready`, `refine`. Extracted from coord/cli.py (#747)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from coord.commands._common import _apply_label_change, _CONFIG_OPTION, _load_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command(
|
|
15
|
+
"refine-chat",
|
|
16
|
+
help=(
|
|
17
|
+
"#264: dispatch a refinement-chat session for an issue.\n\n"
|
|
18
|
+
"Seeds a `type=\"refinement\"` `claude -p` worker with the issue "
|
|
19
|
+
"body + recent comments + the repo's CLAUDE.md + a bounded file-tree "
|
|
20
|
+
"snapshot, then prints the new assignment id to stdout. The TUI "
|
|
21
|
+
"captures the id and opens a ChatController overlay bound to it; "
|
|
22
|
+
"developer-typed turns flow via `POST /inject/{id}` and assistant "
|
|
23
|
+
"replies stream back via the existing SSE watch.\n\n"
|
|
24
|
+
"Read-only — refinement workers have only the `Read` tool; they "
|
|
25
|
+
"cannot mutate the repo or talk to GitHub. The Done button in the "
|
|
26
|
+
"TUI calls `coord ready` to flip `status:refining` → `status:ready` "
|
|
27
|
+
"on session end.\n\n"
|
|
28
|
+
"REPO is the local repo name from coordinator.yml; ISSUE is the GH "
|
|
29
|
+
"issue number."
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.argument("repo")
|
|
35
|
+
@click.argument("issue", type=int)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--machine",
|
|
38
|
+
default=None,
|
|
39
|
+
help="Override machine selection (default: first reachable machine that lists the repo).",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@_CONFIG_OPTION
|
|
44
|
+
def refine_chat(repo: str, issue: int, machine: str | None, config_path: Path) -> None:
|
|
45
|
+
cfg = _load_config(config_path)
|
|
46
|
+
repo_cfg = cfg.repo(repo)
|
|
47
|
+
if repo_cfg is None:
|
|
48
|
+
click.echo(
|
|
49
|
+
f"error: repo {repo!r} not in coordinator.yml "
|
|
50
|
+
f"(have: {[r.name for r in cfg.repos]})",
|
|
51
|
+
err=True,
|
|
52
|
+
)
|
|
53
|
+
sys.exit(2)
|
|
54
|
+
|
|
55
|
+
from coord.refine_chat import dispatch_refinement
|
|
56
|
+
try:
|
|
57
|
+
assignment_id, _picked_machine = dispatch_refinement(
|
|
58
|
+
cfg=cfg,
|
|
59
|
+
repo_cfg=repo_cfg,
|
|
60
|
+
repo=repo,
|
|
61
|
+
issue_number=issue,
|
|
62
|
+
machine_override=machine,
|
|
63
|
+
)
|
|
64
|
+
except RuntimeError as exc:
|
|
65
|
+
click.echo(f"error: {exc}", err=True)
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
# Also flip status:backlog → status:refining so the lifecycle view
|
|
69
|
+
# shows the issue is being actively refined. Best-effort; the chat
|
|
70
|
+
# session itself is the actual refinement work.
|
|
71
|
+
_apply_label_change(
|
|
72
|
+
repo, issue, config_path,
|
|
73
|
+
add={"status:refining"},
|
|
74
|
+
remove_if_present={"status:ready", "status:backlog"},
|
|
75
|
+
success_message="", # no echo — keep stdout clean for the TUI
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Print the assignment_id as the LAST line on stdout so callers (the
|
|
79
|
+
# TUI) can capture it with a simple "last non-empty line" parse. Any
|
|
80
|
+
# warnings or progress lines must be written to stderr.
|
|
81
|
+
click.echo(assignment_id)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@click.command(
|
|
85
|
+
"test-chat",
|
|
86
|
+
help=(
|
|
87
|
+
"#314 Phase B: dispatch a test-chat session for a completed work assignment.\n\n"
|
|
88
|
+
"Seeds a `type=\"test-chat\"` `claude -p` worker with the PR diff, "
|
|
89
|
+
"most recent build log, the worker's SMOKE_TESTS block, the repo's "
|
|
90
|
+
"run command, and the repo's CLAUDE.md. Prints the new assignment id "
|
|
91
|
+
"to stdout. The TUI captures the id and opens a ChatController overlay "
|
|
92
|
+
"bound to it; developer-typed turns flow via `POST /inject/{id}`.\n\n"
|
|
93
|
+
"Read-plus-Bash — test-chat workers have `Read` and `Bash` tools but "
|
|
94
|
+
"write-side Bash commands (gh, git push, etc.) are blocked by the deny "
|
|
95
|
+
"list in the system prompt.\n\n"
|
|
96
|
+
"WORK_ASSIGNMENT_ID is the id of the work assignment to test (visible "
|
|
97
|
+
"in `coord status` or the TUI Pipeline > Stages tab)."
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@click.argument("work_assignment_id")
|
|
103
|
+
@click.option(
|
|
104
|
+
"--machine",
|
|
105
|
+
default=None,
|
|
106
|
+
help="Override machine selection (default: first reachable machine that lists the repo).",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@_CONFIG_OPTION
|
|
111
|
+
def test_chat(work_assignment_id: str, machine: str | None, config_path: Path) -> None:
|
|
112
|
+
"""Dispatch a test-chat session for a completed work assignment."""
|
|
113
|
+
from coord.db import get_connection # noqa: PLC0415
|
|
114
|
+
|
|
115
|
+
cfg = _load_config(config_path)
|
|
116
|
+
|
|
117
|
+
# Look up the work assignment to resolve the repo name.
|
|
118
|
+
conn = get_connection()
|
|
119
|
+
row = conn.execute(
|
|
120
|
+
"SELECT repo_name FROM assignments WHERE assignment_id=?",
|
|
121
|
+
(work_assignment_id,),
|
|
122
|
+
).fetchone()
|
|
123
|
+
if row is None:
|
|
124
|
+
click.echo(
|
|
125
|
+
f"error: assignment {work_assignment_id!r} not found in DB",
|
|
126
|
+
err=True,
|
|
127
|
+
)
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
repo = row["repo_name"]
|
|
131
|
+
repo_cfg = cfg.repo(repo)
|
|
132
|
+
if repo_cfg is None:
|
|
133
|
+
click.echo(
|
|
134
|
+
f"error: repo {repo!r} not in coordinator.yml "
|
|
135
|
+
f"(have: {[r.name for r in cfg.repos]})",
|
|
136
|
+
err=True,
|
|
137
|
+
)
|
|
138
|
+
sys.exit(2)
|
|
139
|
+
|
|
140
|
+
from coord.test_chat import dispatch_test_chat # noqa: PLC0415
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
assignment_id, _picked_machine = dispatch_test_chat(
|
|
144
|
+
cfg=cfg,
|
|
145
|
+
repo_cfg=repo_cfg,
|
|
146
|
+
repo=repo,
|
|
147
|
+
work_assignment_id=work_assignment_id,
|
|
148
|
+
machine_override=machine,
|
|
149
|
+
)
|
|
150
|
+
except RuntimeError as exc:
|
|
151
|
+
click.echo(f"error: {exc}", err=True)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
# Print the assignment_id as the LAST line on stdout so callers (the TUI)
|
|
155
|
+
# can capture it with a simple "last non-empty line" parse.
|
|
156
|
+
click.echo(assignment_id)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@click.command(
|
|
160
|
+
"new-issue-chat",
|
|
161
|
+
help=(
|
|
162
|
+
"#316: dispatch a new-issue-chat session for drafting a GitHub issue.\n\n"
|
|
163
|
+
"Seeds a `type=\"new-issue-chat\"` `claude -p` worker with the repo's "
|
|
164
|
+
"CLAUDE.md, the per-repo issue guidance from coordinator.yml, and a "
|
|
165
|
+
"list of recently open issues for near-duplicate detection. Prints "
|
|
166
|
+
"the new assignment id to stdout — the TUI shells this out and binds "
|
|
167
|
+
"a ChatController overlay to the returned id.\n\n"
|
|
168
|
+
"The worker helps the developer draft a well-structured issue body in "
|
|
169
|
+
"the TITLE: / --- / body format. It does NOT call `gh issue create`; "
|
|
170
|
+
"submission is handled by the TUI.\n\n"
|
|
171
|
+
"REPO is the local repo name from coordinator.yml."
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@click.argument("repo")
|
|
177
|
+
@click.option(
|
|
178
|
+
"--machine",
|
|
179
|
+
default=None,
|
|
180
|
+
help="Override machine selection (default: first unpaused machine that lists the repo).",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@_CONFIG_OPTION
|
|
185
|
+
def new_issue_chat(repo: str, machine: str | None, config_path: Path) -> None:
|
|
186
|
+
cfg = _load_config(config_path)
|
|
187
|
+
repo_cfg = cfg.repo(repo)
|
|
188
|
+
if repo_cfg is None:
|
|
189
|
+
click.echo(
|
|
190
|
+
f"error: repo {repo!r} not in coordinator.yml "
|
|
191
|
+
f"(have: {[r.name for r in cfg.repos]})",
|
|
192
|
+
err=True,
|
|
193
|
+
)
|
|
194
|
+
sys.exit(2)
|
|
195
|
+
|
|
196
|
+
from coord.new_issue_chat import dispatch_new_issue_chat
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
assignment_id, _picked_machine = dispatch_new_issue_chat(
|
|
200
|
+
repo,
|
|
201
|
+
cfg,
|
|
202
|
+
machine_override=machine,
|
|
203
|
+
)
|
|
204
|
+
except RuntimeError as exc:
|
|
205
|
+
click.echo(f"error: {exc}", err=True)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
# Print the assignment id as the LAST stdout line so the TUI can capture
|
|
209
|
+
# it with a simple "last non-empty line" parse.
|
|
210
|
+
click.echo(assignment_id)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@click.command(
|
|
214
|
+
"refine-board",
|
|
215
|
+
help=(
|
|
216
|
+
"#316 Phase C: dispatch a board-level refinement chat for a repo.\n\n"
|
|
217
|
+
"Unlike `refine-chat` (which targets a specific issue), this starts an "
|
|
218
|
+
"open-ended `type=\"refinement\"` session for brainstorming new work, "
|
|
219
|
+
"exploring the codebase, or discussing ideas without being tied to any "
|
|
220
|
+
"particular issue.\n\n"
|
|
221
|
+
"Uses ``issue_number=0`` as the sentinel so the TUI routes the chat to "
|
|
222
|
+
"the Board Chat tab rather than a pipeline issue's Refinement tab. "
|
|
223
|
+
"Prints the new assignment id to stdout — the TUI shells this out and "
|
|
224
|
+
"binds a ChatController overlay to the returned id.\n\n"
|
|
225
|
+
"REPO is the local repo name from coordinator.yml."
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@click.argument("repo")
|
|
231
|
+
@click.option(
|
|
232
|
+
"--machine",
|
|
233
|
+
default=None,
|
|
234
|
+
help="Override machine selection (default: first unpaused machine that lists the repo).",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@_CONFIG_OPTION
|
|
239
|
+
def refine_board(repo: str, machine: str | None, config_path: Path) -> None:
|
|
240
|
+
cfg = _load_config(config_path)
|
|
241
|
+
repo_cfg = cfg.repo(repo)
|
|
242
|
+
if repo_cfg is None:
|
|
243
|
+
click.echo(
|
|
244
|
+
f"error: repo {repo!r} not in coordinator.yml "
|
|
245
|
+
f"(have: {[r.name for r in cfg.repos]})",
|
|
246
|
+
err=True,
|
|
247
|
+
)
|
|
248
|
+
sys.exit(2)
|
|
249
|
+
|
|
250
|
+
from coord.refine_chat import dispatch_board_refinement
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
assignment_id, _picked_machine = dispatch_board_refinement(
|
|
254
|
+
cfg=cfg,
|
|
255
|
+
repo=repo,
|
|
256
|
+
machine_override=machine,
|
|
257
|
+
)
|
|
258
|
+
except RuntimeError as exc:
|
|
259
|
+
click.echo(f"error: {exc}", err=True)
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
|
|
262
|
+
# Print the assignment id as the LAST stdout line so the TUI can capture
|
|
263
|
+
# it with a simple "last non-empty line" parse.
|
|
264
|
+
click.echo(assignment_id)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@click.command(
|
|
268
|
+
help=(
|
|
269
|
+
"Mark a refined issue as ready for dispatch.\n\n"
|
|
270
|
+
"Sets the GitHub `status:ready` and `coord` labels and removes "
|
|
271
|
+
"`status:refining` / `status:backlog` if present. After this the "
|
|
272
|
+
"issue appears in the Pipeline panel as Pending with a [Go] button.\n\n"
|
|
273
|
+
"REPO is the local repo name from coordinator.yml; ISSUE is the GH "
|
|
274
|
+
"issue number."
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@click.argument("repo")
|
|
280
|
+
@click.argument("issue", type=int)
|
|
281
|
+
@_CONFIG_OPTION
|
|
282
|
+
def ready(repo: str, issue: int, config_path: Path) -> None:
|
|
283
|
+
cfg = _load_config(config_path)
|
|
284
|
+
repo_entry = cfg.repo(repo)
|
|
285
|
+
slug = repo_entry.github if repo_entry else repo
|
|
286
|
+
_apply_label_change(
|
|
287
|
+
repo, issue, config_path,
|
|
288
|
+
add={"status:ready", "coord"},
|
|
289
|
+
remove_if_present={"status:refining", "status:backlog"},
|
|
290
|
+
success_message=f"#{issue} ({slug}) marked ready for dispatch",
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@click.command(
|
|
295
|
+
help=(
|
|
296
|
+
"Mark an issue as in-refinement on GitHub.\n\n"
|
|
297
|
+
"Sets the `status:refining` label and removes `status:ready` if "
|
|
298
|
+
"present so the issue moves out of Refined and back into the "
|
|
299
|
+
"scoping flow. Symmetric with `coord ready`.\n\n"
|
|
300
|
+
"REPO is the local repo name from coordinator.yml; ISSUE is the "
|
|
301
|
+
"GH issue number."
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@click.argument("repo")
|
|
307
|
+
@click.argument("issue", type=int)
|
|
308
|
+
@_CONFIG_OPTION
|
|
309
|
+
def refine(repo: str, issue: int, config_path: Path) -> None:
|
|
310
|
+
"""#260: TUI right-click 'Refine' fires this command to move a
|
|
311
|
+
Backlog row into the Refining lifecycle section."""
|
|
312
|
+
cfg = _load_config(config_path)
|
|
313
|
+
repo_entry = cfg.repo(repo)
|
|
314
|
+
slug = repo_entry.github if repo_entry else repo
|
|
315
|
+
_apply_label_change(
|
|
316
|
+
repo, issue, config_path,
|
|
317
|
+
add={"status:refining"},
|
|
318
|
+
remove_if_present={"status:ready"},
|
|
319
|
+
success_message=f"#{issue} ({slug}) marked status:refining",
|
|
320
|
+
)
|