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/cli.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""Click CLI entry point for the `coord` command.
|
|
2
|
+
|
|
3
|
+
#747: this module just builds the `main`/`agent`/`issue`/`context` groups and
|
|
4
|
+
registers the commands implemented in coord/commands/*.py — the actual
|
|
5
|
+
command bodies (~70 commands across ~12k lines pre-#747) now live in those
|
|
6
|
+
focused modules. Keep this file thin: new commands belong in
|
|
7
|
+
coord/commands/<area>.py, imported and attached here in one place.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
# Compatibility shims, not used directly by this file: a number of existing
|
|
13
|
+
# tests patch e.g. "coord.cli.subprocess.run" / "coord.cli.socket.gethostname"
|
|
14
|
+
# / "coord.cli.httpx.get" / "coord.cli.Path.home". Those patches work by
|
|
15
|
+
# replacing an attribute on the *shared* stdlib/third-party module or class
|
|
16
|
+
# object (the same object every coord.commands.* module below imports), so
|
|
17
|
+
# keeping these imports here — even though cli.py itself no longer calls
|
|
18
|
+
# them — keeps those existing test-patch targets resolving. noqa: F401
|
|
19
|
+
import os # noqa: F401
|
|
20
|
+
import shutil # noqa: F401
|
|
21
|
+
import socket # noqa: F401
|
|
22
|
+
import subprocess # noqa: F401
|
|
23
|
+
import time # noqa: F401
|
|
24
|
+
from pathlib import Path # noqa: F401
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
import httpx # noqa: F401
|
|
28
|
+
|
|
29
|
+
from coord import __version__
|
|
30
|
+
from coord.dist_name import pkg_spec as _dist_pkg_spec
|
|
31
|
+
|
|
32
|
+
from coord.commands.acceptance import acceptance_group
|
|
33
|
+
from coord.commands.audit import audit
|
|
34
|
+
from coord.commands.gate_a import gate_a
|
|
35
|
+
from coord.commands.report import report_group
|
|
36
|
+
from coord.commands.scorecard import scorecard
|
|
37
|
+
|
|
38
|
+
# Re-exported for back-compat: some tests do `from coord.cli import
|
|
39
|
+
# _save_config_snapshot` / `_load_config` / etc. directly.
|
|
40
|
+
from coord.commands._common import ( # noqa: F401
|
|
41
|
+
AGENT_PORT,
|
|
42
|
+
SERVE_PORT,
|
|
43
|
+
_apply_label_change,
|
|
44
|
+
_CONFIG_OPTION,
|
|
45
|
+
_load_config,
|
|
46
|
+
_not_implemented,
|
|
47
|
+
_save_config_snapshot,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from coord.commands.setup import (
|
|
51
|
+
_ensure_coord_permissions, # noqa: F401 — re-exported for tests
|
|
52
|
+
_parse_github_remote, # noqa: F401 — re-exported for tests
|
|
53
|
+
config_cmd,
|
|
54
|
+
init,
|
|
55
|
+
install_skills,
|
|
56
|
+
version,
|
|
57
|
+
)
|
|
58
|
+
from coord.commands.agent_ops import agent, pause, unpause
|
|
59
|
+
from coord.commands.gates import gates
|
|
60
|
+
from coord.commands.status import diagnose, doctor, show_plan, status, usage
|
|
61
|
+
from coord.commands.dispatch import (
|
|
62
|
+
approve,
|
|
63
|
+
assign,
|
|
64
|
+
chat_continue,
|
|
65
|
+
inject,
|
|
66
|
+
plan,
|
|
67
|
+
retry,
|
|
68
|
+
stop,
|
|
69
|
+
)
|
|
70
|
+
from coord.commands.sessions import (
|
|
71
|
+
_prune_dead_sessions, # noqa: F401 — re-exported for tests
|
|
72
|
+
log,
|
|
73
|
+
pull_artifact,
|
|
74
|
+
reattach,
|
|
75
|
+
session,
|
|
76
|
+
sessions_cmd,
|
|
77
|
+
wait,
|
|
78
|
+
watch,
|
|
79
|
+
)
|
|
80
|
+
from coord.commands.terminal import terminal_group
|
|
81
|
+
from coord.commands.tui import tui_group
|
|
82
|
+
|
|
83
|
+
# #1628: the health check engine ships its own CLI surface inside
|
|
84
|
+
# coord/health/ so the whole feature — registry, probes, renderer, command —
|
|
85
|
+
# stays in one package. Registered here like any other command.
|
|
86
|
+
from coord.health.cli import health
|
|
87
|
+
from coord.commands.merge import (
|
|
88
|
+
bounce,
|
|
89
|
+
merge,
|
|
90
|
+
post_pending_reviews,
|
|
91
|
+
reconcile_merges,
|
|
92
|
+
verify_merge,
|
|
93
|
+
)
|
|
94
|
+
from coord.commands.review import (
|
|
95
|
+
_prompt_and_relay_review_verdict, # noqa: F401 — re-exported for tests
|
|
96
|
+
_prompt_and_relay_test_verdict, # noqa: F401 — re-exported for tests
|
|
97
|
+
fix_briefing_cmd,
|
|
98
|
+
report_result,
|
|
99
|
+
review_reaffirm,
|
|
100
|
+
set_review_findings,
|
|
101
|
+
)
|
|
102
|
+
from coord.commands.test_gate import (
|
|
103
|
+
_get_assignment_branch_head, # noqa: F401 — re-exported for tests
|
|
104
|
+
set_test_mode,
|
|
105
|
+
test,
|
|
106
|
+
test_plan_cmd,
|
|
107
|
+
)
|
|
108
|
+
from coord.commands.chat import (
|
|
109
|
+
new_issue_chat,
|
|
110
|
+
ready,
|
|
111
|
+
refine,
|
|
112
|
+
refine_board,
|
|
113
|
+
refine_chat,
|
|
114
|
+
test_chat,
|
|
115
|
+
)
|
|
116
|
+
from coord.commands.issues import (
|
|
117
|
+
backlog,
|
|
118
|
+
context_group,
|
|
119
|
+
issue_group,
|
|
120
|
+
queue,
|
|
121
|
+
sync,
|
|
122
|
+
track,
|
|
123
|
+
unqueue,
|
|
124
|
+
untrack,
|
|
125
|
+
)
|
|
126
|
+
from coord.commands.drive import (
|
|
127
|
+
drive,
|
|
128
|
+
drive_attach,
|
|
129
|
+
drive_sessions,
|
|
130
|
+
drive_stop,
|
|
131
|
+
escalate_group,
|
|
132
|
+
)
|
|
133
|
+
from coord.commands.drive_queue import drive_queue_group
|
|
134
|
+
from coord.commands.lifecycle import done, housekeeping, notify, resume, serve, web
|
|
135
|
+
from coord.commands.milestone import milestone_group
|
|
136
|
+
from coord.commands.plans import plans_cmd
|
|
137
|
+
from coord.commands.release import release_group, release_preflight
|
|
138
|
+
from coord.commands.plan_followup import (
|
|
139
|
+
_dispatch_followup, # noqa: F401 — re-exported for tests
|
|
140
|
+
approve_plan,
|
|
141
|
+
fix,
|
|
142
|
+
pr,
|
|
143
|
+
reject_plan,
|
|
144
|
+
resume_stuck,
|
|
145
|
+
review,
|
|
146
|
+
split,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# #1182: thresholds past which a stale non-editable install escalates from the
|
|
151
|
+
# mild "edits won't reach the CLI" note to a loud STALE INSTALL banner.
|
|
152
|
+
_STALE_COMMITS_THRESHOLD = 3
|
|
153
|
+
_STALE_DAYS_THRESHOLD = 2.0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _compute_install_staleness(repo_root: Path, installed_version: str) -> dict | None:
|
|
157
|
+
"""Best-effort: how far *installed_version* trails ``repo_root``'s HEAD.
|
|
158
|
+
|
|
159
|
+
Returns ``{"commits_behind": int, "days_behind": float | None, "tag": str}``
|
|
160
|
+
when a ``v{installed_version}`` tag exists in the checkout and HEAD has
|
|
161
|
+
moved past it, or ``None`` when there's no matching tag, the checkout
|
|
162
|
+
isn't a git repo, HEAD hasn't moved, or anything else goes wrong (never
|
|
163
|
+
raises — this is a nice-to-have signal, not something that should ever
|
|
164
|
+
break the CLI, per #1182).
|
|
165
|
+
"""
|
|
166
|
+
import subprocess # noqa: PLC0415
|
|
167
|
+
import time # noqa: PLC0415
|
|
168
|
+
|
|
169
|
+
if not installed_version:
|
|
170
|
+
return None
|
|
171
|
+
try:
|
|
172
|
+
if not (repo_root / ".git").exists():
|
|
173
|
+
return None
|
|
174
|
+
tag = f"v{installed_version}"
|
|
175
|
+
|
|
176
|
+
def _git(*args: str) -> str | None:
|
|
177
|
+
result = subprocess.run(
|
|
178
|
+
["git", "-C", str(repo_root), *args],
|
|
179
|
+
capture_output=True, text=True, timeout=5,
|
|
180
|
+
)
|
|
181
|
+
if result.returncode != 0:
|
|
182
|
+
return None
|
|
183
|
+
return result.stdout.strip()
|
|
184
|
+
|
|
185
|
+
if _git("rev-parse", "-q", "--verify", f"refs/tags/{tag}") is None:
|
|
186
|
+
return None # no matching tag — can't quantify drift
|
|
187
|
+
count_raw = _git("rev-list", "--count", f"{tag}..HEAD")
|
|
188
|
+
if count_raw is None or not count_raw.isdigit():
|
|
189
|
+
return None
|
|
190
|
+
commits_behind = int(count_raw)
|
|
191
|
+
if commits_behind <= 0:
|
|
192
|
+
return None # installed version is at least as new as HEAD
|
|
193
|
+
|
|
194
|
+
days_behind = None
|
|
195
|
+
ts_raw = _git("log", "-1", "--format=%ct", tag)
|
|
196
|
+
if ts_raw and ts_raw.isdigit():
|
|
197
|
+
days_behind = (time.time() - int(ts_raw)) / 86400.0
|
|
198
|
+
return {"commits_behind": commits_behind, "days_behind": days_behind, "tag": tag}
|
|
199
|
+
except Exception: # noqa: BLE001 — best-effort, never break the CLI
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _warn_if_source_install_drift() -> None:
|
|
204
|
+
"""Warn when the CLI is running from a non-editable install of a package
|
|
205
|
+
whose source checkout is the current working directory.
|
|
206
|
+
|
|
207
|
+
Root cause of #222: ``pip install .`` (without ``-e``) copies a snapshot
|
|
208
|
+
into site-packages. Subsequent edits in the source tree don't reach the
|
|
209
|
+
CLI, while ``python -c "from coord.... import ..."`` from the source dir
|
|
210
|
+
DOES pick them up (cwd shadows site-packages on import). Result: the same
|
|
211
|
+
workflow gives different answers depending on entry path.
|
|
212
|
+
|
|
213
|
+
Heuristic: ``coord.__file__`` lives in ``site-packages`` AND the cwd has
|
|
214
|
+
a sibling ``coord/`` package — that's exactly the drift case.
|
|
215
|
+
|
|
216
|
+
#1182: that plain warning shipped in #222 turned out to read as
|
|
217
|
+
boilerplate noise — it fired identically whether the install was a day
|
|
218
|
+
stale or, as happened on elitebook, many releases behind `main` (silently
|
|
219
|
+
evaluating retired logic and causing a false merge-gate block). When the
|
|
220
|
+
checkout has a tag matching the installed version, we now also quantify
|
|
221
|
+
how many commits/days HEAD has moved past it and escalate to a much
|
|
222
|
+
louder banner past ``_STALE_COMMITS_THRESHOLD`` / ``_STALE_DAYS_THRESHOLD``.
|
|
223
|
+
"""
|
|
224
|
+
import os # noqa: PLC0415
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
import coord as _coord # noqa: PLC0415
|
|
228
|
+
|
|
229
|
+
coord_file = _coord.__file__ or ""
|
|
230
|
+
if "site-packages" not in coord_file:
|
|
231
|
+
return # Editable install — source IS the import path, no drift.
|
|
232
|
+
local_init = Path(os.getcwd()) / "coord" / "__init__.py"
|
|
233
|
+
if not local_init.exists():
|
|
234
|
+
return # Not running from a source checkout.
|
|
235
|
+
|
|
236
|
+
staleness = _compute_install_staleness(Path(os.getcwd()), _coord.__version__ or "")
|
|
237
|
+
if staleness is not None and (
|
|
238
|
+
staleness["commits_behind"] >= _STALE_COMMITS_THRESHOLD
|
|
239
|
+
or (
|
|
240
|
+
staleness["days_behind"] is not None
|
|
241
|
+
and staleness["days_behind"] >= _STALE_DAYS_THRESHOLD
|
|
242
|
+
)
|
|
243
|
+
):
|
|
244
|
+
days_note = (
|
|
245
|
+
f", ~{staleness['days_behind']:.1f} day(s)"
|
|
246
|
+
if staleness["days_behind"] is not None
|
|
247
|
+
else ""
|
|
248
|
+
)
|
|
249
|
+
# #2103: suggest an upgrade of whichever distribution name is
|
|
250
|
+
# actually installed — `code-coordinator` since the #2104 rename,
|
|
251
|
+
# `claude-coordinator` on an agent that has not been updated past
|
|
252
|
+
# it yet. `_coord.__file__` is already confirmed above to be a
|
|
253
|
+
# real site-packages install, so one of the two names is
|
|
254
|
+
# guaranteed to resolve here; the literal fallback only guards a
|
|
255
|
+
# resolution race between that check and this one. It names the
|
|
256
|
+
# NEW distribution (#2104): the only way to reach it is "neither
|
|
257
|
+
# name resolved", and the one name guaranteed to still be
|
|
258
|
+
# publishable is the one every future release ships under —
|
|
259
|
+
# `claude-coordinator` is a tombstone that will never gain
|
|
260
|
+
# another version. `_dist_pkg_spec` is the module-level
|
|
261
|
+
# `coord.dist_name` import above (real module, independent of
|
|
262
|
+
# `_coord` here potentially being a test stand-in), so this
|
|
263
|
+
# doesn't depend on `coord.dist_name` already being cached.
|
|
264
|
+
try:
|
|
265
|
+
install_target = _dist_pkg_spec(extra="server")
|
|
266
|
+
except Exception: # noqa: BLE001 — best-effort, never break the CLI
|
|
267
|
+
install_target = "code-coordinator[server]"
|
|
268
|
+
banner = "⚠" * 24
|
|
269
|
+
click.echo(
|
|
270
|
+
f"{banner}\n"
|
|
271
|
+
f"⚠⚠⚠ STALE INSTALL: coord CLI is {staleness['commits_behind']} "
|
|
272
|
+
f"commit(s){days_note} behind the checkout's HEAD ⚠⚠⚠\n"
|
|
273
|
+
f"Installed: {_coord.__version__} (tag {staleness['tag']}) — the source "
|
|
274
|
+
f"checkout at {local_init.parent} has moved past that release.\n"
|
|
275
|
+
"This install may be silently evaluating RETIRED logic (#1182 — this is "
|
|
276
|
+
"how a false merge-gate block slipped through). Fix:\n"
|
|
277
|
+
f" pip install --upgrade '{install_target}' "
|
|
278
|
+
"(if a release covers those commits)\n"
|
|
279
|
+
" pip install -e . (to run live source instead)\n"
|
|
280
|
+
f"{banner}",
|
|
281
|
+
err=True,
|
|
282
|
+
)
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
# Inside a source checkout but CLI uses snapshot copy → drift possible.
|
|
286
|
+
click.echo(
|
|
287
|
+
"warning: coord CLI is running from a non-editable install "
|
|
288
|
+
"(site-packages snapshot) but a source checkout exists at "
|
|
289
|
+
f"{local_init.parent}.\n"
|
|
290
|
+
" Edits to the source tree will NOT reach the CLI. "
|
|
291
|
+
"Fix: pip install -e .",
|
|
292
|
+
err=True,
|
|
293
|
+
)
|
|
294
|
+
except Exception: # noqa: BLE001 — best-effort, never break the CLI
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _warn_if_editable_checkout_moved() -> None:
|
|
299
|
+
"""#561/#601 backstop: when running from an EDITABLE checkout, warn loudly if
|
|
300
|
+
its branch was moved off the default.
|
|
301
|
+
|
|
302
|
+
A Build/`coord test`/smoke that git-checkout'd the base — or an interactive
|
|
303
|
+
agent inspecting a branch in the live checkout — silently puts the running
|
|
304
|
+
coordinator on that branch's code until restored (#561 incident: disabled
|
|
305
|
+
guards; #601 incident: old code + retired local DB). This makes that state
|
|
306
|
+
visible on every command instead of waiting for a verdict or manual restore.
|
|
307
|
+
"""
|
|
308
|
+
import subprocess # noqa: PLC0415
|
|
309
|
+
import sys as _sys # noqa: PLC0415
|
|
310
|
+
|
|
311
|
+
if "pytest" in _sys.modules:
|
|
312
|
+
return # don't add startup noise to the test suite
|
|
313
|
+
try:
|
|
314
|
+
import coord as _coord # noqa: PLC0415
|
|
315
|
+
|
|
316
|
+
coord_file = _coord.__file__ or ""
|
|
317
|
+
if "site-packages" in coord_file:
|
|
318
|
+
return # PyPI/snapshot install — moving a checkout can't affect it.
|
|
319
|
+
repo_root = Path(coord_file).resolve().parents[1]
|
|
320
|
+
if not (repo_root / ".git").exists():
|
|
321
|
+
return
|
|
322
|
+
head = subprocess.run(
|
|
323
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
324
|
+
cwd=str(repo_root), capture_output=True, text=True, timeout=3,
|
|
325
|
+
)
|
|
326
|
+
if head.returncode != 0:
|
|
327
|
+
return
|
|
328
|
+
branch = head.stdout.strip()
|
|
329
|
+
if branch in ("main", "master"):
|
|
330
|
+
return
|
|
331
|
+
shown = "(detached HEAD)" if branch == "HEAD" else f"'{branch}'"
|
|
332
|
+
click.echo(
|
|
333
|
+
f"⚠ coord: editable checkout {repo_root} is on {shown}, not the "
|
|
334
|
+
"default branch — the running coordinator is on THAT code. A "
|
|
335
|
+
"Build/smoke/test may have checked it out. Restore with: "
|
|
336
|
+
f"git -C {repo_root} checkout main",
|
|
337
|
+
err=True,
|
|
338
|
+
)
|
|
339
|
+
except Exception: # noqa: BLE001 — best-effort, never break the CLI
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@click.group(help="Multi-agent coordinator for Claude Code workers.")
|
|
344
|
+
@click.version_option(__version__, prog_name="coord")
|
|
345
|
+
def main() -> None:
|
|
346
|
+
"""coord — coordinate Claude Code workers across machines and repos."""
|
|
347
|
+
_warn_if_source_install_drift()
|
|
348
|
+
_warn_if_editable_checkout_moved()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# Registration order below matches the historical decoration order in the
|
|
352
|
+
# pre-#747 cli.py exactly.
|
|
353
|
+
main.add_command(version)
|
|
354
|
+
main.add_command(config_cmd)
|
|
355
|
+
main.add_command(init)
|
|
356
|
+
main.add_command(agent)
|
|
357
|
+
main.add_command(status)
|
|
358
|
+
main.add_command(plan)
|
|
359
|
+
main.add_command(approve)
|
|
360
|
+
main.add_command(assign)
|
|
361
|
+
main.add_command(log)
|
|
362
|
+
main.add_command(show_plan)
|
|
363
|
+
main.add_command(inject)
|
|
364
|
+
main.add_command(chat_continue)
|
|
365
|
+
main.add_command(stop)
|
|
366
|
+
main.add_command(report_result)
|
|
367
|
+
main.add_command(verify_merge)
|
|
368
|
+
main.add_command(set_review_findings)
|
|
369
|
+
main.add_command(review_reaffirm)
|
|
370
|
+
main.add_command(retry)
|
|
371
|
+
main.add_command(pull_artifact)
|
|
372
|
+
main.add_command(bounce)
|
|
373
|
+
main.add_command(sync)
|
|
374
|
+
main.add_command(pause)
|
|
375
|
+
main.add_command(unpause)
|
|
376
|
+
main.add_command(refine_chat)
|
|
377
|
+
main.add_command(test_chat)
|
|
378
|
+
main.add_command(new_issue_chat)
|
|
379
|
+
main.add_command(refine_board)
|
|
380
|
+
main.add_command(ready)
|
|
381
|
+
main.add_command(refine)
|
|
382
|
+
main.add_command(reconcile_merges)
|
|
383
|
+
main.add_command(housekeeping)
|
|
384
|
+
main.add_command(diagnose)
|
|
385
|
+
main.add_command(gates)
|
|
386
|
+
main.add_command(doctor)
|
|
387
|
+
main.add_command(health)
|
|
388
|
+
main.add_command(issue_group)
|
|
389
|
+
main.add_command(context_group)
|
|
390
|
+
main.add_command(audit)
|
|
391
|
+
main.add_command(report_group)
|
|
392
|
+
main.add_command(scorecard)
|
|
393
|
+
main.add_command(milestone_group)
|
|
394
|
+
main.add_command(plans_cmd)
|
|
395
|
+
main.add_command(fix_briefing_cmd)
|
|
396
|
+
main.add_command(track)
|
|
397
|
+
main.add_command(untrack)
|
|
398
|
+
main.add_command(backlog)
|
|
399
|
+
main.add_command(queue)
|
|
400
|
+
main.add_command(unqueue)
|
|
401
|
+
main.add_command(set_test_mode)
|
|
402
|
+
main.add_command(notify)
|
|
403
|
+
main.add_command(post_pending_reviews)
|
|
404
|
+
main.add_command(merge)
|
|
405
|
+
main.add_command(resume)
|
|
406
|
+
main.add_command(test)
|
|
407
|
+
main.add_command(test_plan_cmd)
|
|
408
|
+
main.add_command(split)
|
|
409
|
+
main.add_command(done)
|
|
410
|
+
main.add_command(session)
|
|
411
|
+
main.add_command(sessions_cmd)
|
|
412
|
+
main.add_command(reattach)
|
|
413
|
+
main.add_command(terminal_group)
|
|
414
|
+
main.add_command(usage)
|
|
415
|
+
main.add_command(web)
|
|
416
|
+
main.add_command(serve)
|
|
417
|
+
main.add_command(wait)
|
|
418
|
+
main.add_command(watch)
|
|
419
|
+
main.add_command(drive)
|
|
420
|
+
main.add_command(drive_sessions)
|
|
421
|
+
main.add_command(drive_attach)
|
|
422
|
+
main.add_command(drive_stop)
|
|
423
|
+
main.add_command(drive_queue_group)
|
|
424
|
+
main.add_command(escalate_group)
|
|
425
|
+
main.add_command(pr)
|
|
426
|
+
main.add_command(fix)
|
|
427
|
+
main.add_command(review)
|
|
428
|
+
main.add_command(approve_plan)
|
|
429
|
+
main.add_command(reject_plan)
|
|
430
|
+
main.add_command(resume_stuck)
|
|
431
|
+
main.add_command(install_skills)
|
|
432
|
+
main.add_command(acceptance_group)
|
|
433
|
+
# #2063: the Gate-A human sign-off verdict, sibling to `coord test
|
|
434
|
+
# --passed|--fail`. Flat (not under `acceptance`) because it is an operator
|
|
435
|
+
# gesture on a milestone, not part of the acceptance runner.
|
|
436
|
+
main.add_command(gate_a)
|
|
437
|
+
main.add_command(release_preflight)
|
|
438
|
+
# #1834: `coord release verify` (and `coord release preflight` as an alias of
|
|
439
|
+
# the flat command above), grouped so the pre-tag and post-release halves of
|
|
440
|
+
# the release story are discoverable together.
|
|
441
|
+
main.add_command(release_group)
|
|
442
|
+
main.add_command(tui_group)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# #1809: without this guard, `python -m coord.cli <args>` just IMPORTS the
|
|
446
|
+
# module — building every `click.group`/`add_command` above, then returning —
|
|
447
|
+
# and exits 0 having run nothing and printed nothing. That import-only path is
|
|
448
|
+
# exactly what `coord.drive.coord_argv()` falls back to whenever `coord` isn't
|
|
449
|
+
# on PATH (a bare venv, a systemd user unit, a non-interactive ssh session —
|
|
450
|
+
# see #402), so every subprocess the driver or the drive queue spawned on such
|
|
451
|
+
# a host was a silent no-op that reported success. `python -m coord.cli
|
|
452
|
+
# --version` and the fallback argv both need `main()` to actually run.
|
|
453
|
+
if __name__ == "__main__": # pragma: no cover — exercised via subprocess in tests
|
|
454
|
+
main()
|