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/parentage.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Parentage seam (#1195) — backend-agnostic parent/child API over the
|
|
2
|
+
issue-store seam.
|
|
3
|
+
|
|
4
|
+
Every gap in the "Epic as a first-class parent" milestone is downstream of
|
|
5
|
+
parentage being **plain text** today: the epic->child relation is the ``##
|
|
6
|
+
Work order`` / ``## Sub-issues`` markdown checklist
|
|
7
|
+
:mod:`coord.milestone_order` parses (:func:`coord.milestone_order.
|
|
8
|
+
parse_sub_issues`). GitHub's REST sub-issues API is live on this repo but
|
|
9
|
+
used nowhere — there is no structural parent link for anything to reconcile
|
|
10
|
+
against.
|
|
11
|
+
|
|
12
|
+
This module is the narrow contract the future :issue:`183` pluggable
|
|
13
|
+
``IssueStore`` refactor will slot a real backend-selection policy into. Until
|
|
14
|
+
then it ships two concrete adapters behind the identical
|
|
15
|
+
:class:`ParentageStore` shape:
|
|
16
|
+
|
|
17
|
+
* :class:`coord.parentage_github.GitHubParentage` — the live GitHub
|
|
18
|
+
sub-issues REST API (``GET``/``POST``/``DELETE .../sub_issues``).
|
|
19
|
+
* :class:`MarkdownParentage` — the ``## Sub-issues`` checklist fallback for
|
|
20
|
+
GitLab / SQLite / no-API backends (and, today, the cheap path the
|
|
21
|
+
board-payload publish step uses — see ``coord/serve_app.py``'s ``board()``
|
|
22
|
+
handler — since a live API call per pipeline row per poll isn't
|
|
23
|
+
affordable).
|
|
24
|
+
|
|
25
|
+
Callers reach either backend through the same method names/signatures so
|
|
26
|
+
neither the board-payload builder nor a future ``coord`` command has to
|
|
27
|
+
branch on which one is live — that's precisely what keeps #183 (pluggable
|
|
28
|
+
issue store) viable.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from dataclasses import dataclass
|
|
34
|
+
from typing import Protocol, runtime_checkable
|
|
35
|
+
|
|
36
|
+
from coord.milestone_order import parse_sub_issues, parse_work_order
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"Child",
|
|
41
|
+
"ParentRef",
|
|
42
|
+
"ParentageStore",
|
|
43
|
+
"MarkdownParentage",
|
|
44
|
+
"build_parentage_store",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class Child:
|
|
50
|
+
"""One child of an issue: its number and open/closed state."""
|
|
51
|
+
|
|
52
|
+
number: int
|
|
53
|
+
state: str # "open" | "closed"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class ParentRef:
|
|
58
|
+
"""The parent of an issue: its number and open/closed state."""
|
|
59
|
+
|
|
60
|
+
number: int
|
|
61
|
+
state: str # "open" | "closed"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@runtime_checkable
|
|
65
|
+
class ParentageStore(Protocol):
|
|
66
|
+
"""Read/write parent-child relations for one issue-tracker backend.
|
|
67
|
+
|
|
68
|
+
``body``/``epics`` are backend-specific extra context a caller may
|
|
69
|
+
already have on hand (e.g. from the same cached ``issues`` row the
|
|
70
|
+
board-payload builder already fetched) — a backend that doesn't need
|
|
71
|
+
them (GitHub, which asks the live API) simply ignores them. Every
|
|
72
|
+
adapter accepts the same keyword names so a caller can pass everything
|
|
73
|
+
it might need without knowing which backend answers.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def children(
|
|
77
|
+
self, repo_github: str, issue_number: int, *, body: str = "",
|
|
78
|
+
) -> list[Child]: ...
|
|
79
|
+
|
|
80
|
+
def parent(
|
|
81
|
+
self,
|
|
82
|
+
repo_github: str,
|
|
83
|
+
issue_number: int,
|
|
84
|
+
*,
|
|
85
|
+
epics: list[dict] | None = None,
|
|
86
|
+
) -> ParentRef | None: ...
|
|
87
|
+
|
|
88
|
+
def add_child(self, repo_github: str, parent_number: int, child_number: int) -> None: ...
|
|
89
|
+
|
|
90
|
+
def remove_child(self, repo_github: str, parent_number: int, child_number: int) -> None: ...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MarkdownParentage:
|
|
94
|
+
"""Fallback adapter (#1195): the ``## Sub-issues`` checklist convention
|
|
95
|
+
#1008 already established for backends with no native sub-issues API
|
|
96
|
+
(GitLab / SQLite / no-API).
|
|
97
|
+
|
|
98
|
+
``children()`` parses *body* — the tracking issue's OWN body — with
|
|
99
|
+
:func:`coord.milestone_order.parse_sub_issues`. No network call, so this
|
|
100
|
+
is also what the board-payload publish step uses even on a GitHub-backed
|
|
101
|
+
repo: a live sub-issues API call per epic per poll isn't affordable, and
|
|
102
|
+
(until EP-2 backfills existing epics) the markdown checklist is the only
|
|
103
|
+
populated source of truth anyway.
|
|
104
|
+
|
|
105
|
+
#1197 fix-iteration finding: an "epic" tracking issue's ``## Work
|
|
106
|
+
order`` block (#767/#645 — the milestone's DAG of work items, written by
|
|
107
|
+
``coord milestone write-order``) already names the exact same
|
|
108
|
+
parent->children relationship the newer ``## Sub-issues`` checklist
|
|
109
|
+
(#1008, written only by the separate, opt-in ``coord milestone
|
|
110
|
+
add-child``) is meant to encode — but plenty of existing epics (this
|
|
111
|
+
milestone's own #1200 tracking issue included) predate #1008 and were
|
|
112
|
+
never additionally seeded with a ``## Sub-issues`` block, so
|
|
113
|
+
``parse_sub_issues`` legitimately finds nothing even though the epic
|
|
114
|
+
unambiguously has children. Pass ``fallback_to_work_order=True`` to
|
|
115
|
+
treat an empty/absent ``## Sub-issues`` block as "fall back to ``##
|
|
116
|
+
Work order``" instead of "no children" — this is what the board-payload
|
|
117
|
+
children projection (the Pipeline nesting feature) opts into; it
|
|
118
|
+
defaults to ``False`` so the #1196 close-guard (:func:`coord.
|
|
119
|
+
github_ops.get_open_children`) keeps its narrower, deliberately
|
|
120
|
+
conservative behavior unchanged.
|
|
121
|
+
|
|
122
|
+
``parent()`` has no reverse pointer to walk — the checklist lives on the
|
|
123
|
+
PARENT's body, not the child's — so callers must supply *epics*: every
|
|
124
|
+
candidate tracking issue as ``{"number": int, "state": str, "body":
|
|
125
|
+
str}``. The first whose ``## Sub-issues`` checklist names *issue_number*
|
|
126
|
+
is the parent; ``None`` when no candidate does (or none were supplied).
|
|
127
|
+
|
|
128
|
+
The checklist's ``- [x]`` / ``- [ ]`` box is the only completion signal
|
|
129
|
+
this convention carries, so it doubles as the child's reported ``state``
|
|
130
|
+
here (``"closed"`` for checked, ``"open"`` otherwise) — an approximation
|
|
131
|
+
of the real GitHub issue state, good enough for nesting/display, not a
|
|
132
|
+
live lookup.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def children(
|
|
136
|
+
self,
|
|
137
|
+
repo_github: str,
|
|
138
|
+
issue_number: int,
|
|
139
|
+
*,
|
|
140
|
+
body: str = "",
|
|
141
|
+
fallback_to_work_order: bool = False,
|
|
142
|
+
) -> list[Child]:
|
|
143
|
+
work_order = parse_sub_issues(body)
|
|
144
|
+
if not work_order.nodes and fallback_to_work_order:
|
|
145
|
+
work_order = parse_work_order(body)
|
|
146
|
+
return [
|
|
147
|
+
Child(number=n.issue_number, state="closed" if n.checked else "open")
|
|
148
|
+
for n in work_order.nodes
|
|
149
|
+
if n.issue_number != issue_number # defensive: never self-nest
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
def parent(
|
|
153
|
+
self,
|
|
154
|
+
repo_github: str,
|
|
155
|
+
issue_number: int,
|
|
156
|
+
*,
|
|
157
|
+
epics: list[dict] | None = None,
|
|
158
|
+
) -> ParentRef | None:
|
|
159
|
+
for epic in epics or []:
|
|
160
|
+
try:
|
|
161
|
+
work_order = parse_sub_issues(epic.get("body") or "")
|
|
162
|
+
except Exception: # noqa: BLE001 — a malformed epic body: skip it
|
|
163
|
+
continue
|
|
164
|
+
if work_order.node(issue_number) is not None:
|
|
165
|
+
number = epic.get("number")
|
|
166
|
+
if number is None:
|
|
167
|
+
continue
|
|
168
|
+
return ParentRef(number=int(number), state=str(epic.get("state") or "open"))
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def add_child(self, repo_github: str, parent_number: int, child_number: int) -> None:
|
|
172
|
+
raise NotImplementedError(
|
|
173
|
+
"MarkdownParentage is read-only from this seam — the `## "
|
|
174
|
+
"Sub-issues` checklist is mutated via `coord milestone "
|
|
175
|
+
"add-child` (#1008), the existing write path for this "
|
|
176
|
+
"convention (splices the section idempotently, unlike a plain "
|
|
177
|
+
"body overwrite)."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def remove_child(self, repo_github: str, parent_number: int, child_number: int) -> None:
|
|
181
|
+
raise NotImplementedError(
|
|
182
|
+
"MarkdownParentage is read-only from this seam — remove a "
|
|
183
|
+
"sub-issue via `coord milestone add-child ... --remove` (#1008)."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def build_parentage_store(backend: str) -> ParentageStore:
|
|
188
|
+
"""Construct the :class:`ParentageStore` named by *backend*.
|
|
189
|
+
|
|
190
|
+
Centralised here (mirrors :func:`coord.ci_store.build_ci_store`) so
|
|
191
|
+
callers don't branch on the config value themselves. Unknown values fall
|
|
192
|
+
back to :class:`MarkdownParentage` — the always-available, no-network
|
|
193
|
+
adapter — so a typo in config can't crash a caller the way a KeyError
|
|
194
|
+
would.
|
|
195
|
+
"""
|
|
196
|
+
if backend == "github":
|
|
197
|
+
from coord.parentage_github import GitHubParentage # noqa: PLC0415
|
|
198
|
+
|
|
199
|
+
return GitHubParentage()
|
|
200
|
+
return MarkdownParentage()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""GitHub sub-issues backend for :mod:`coord.parentage` (#1195).
|
|
2
|
+
|
|
3
|
+
Shells out to ``gh api`` via :mod:`coord.github_ops`'s sub-issues wrappers.
|
|
4
|
+
Mirrors the :class:`coord.ci_store.CiStore` / :class:`coord.ci_github.GitHubCi`
|
|
5
|
+
split: the Protocol + pure fallback adapter live in ``coord.parentage``, and
|
|
6
|
+
this module is the live-API adapter, kept separate so a test can import the
|
|
7
|
+
Protocol/dataclasses without dragging in ``github_ops``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from coord import github_ops
|
|
13
|
+
from coord.parentage import Child, ParentRef
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitHubParentage:
|
|
17
|
+
"""Live adapter over the GitHub sub-issues REST API.
|
|
18
|
+
|
|
19
|
+
``body``/``epics`` are accepted for :class:`coord.parentage.
|
|
20
|
+
ParentageStore` shape parity with :class:`coord.parentage.
|
|
21
|
+
MarkdownParentage` but ignored here — every read is a live ``gh api``
|
|
22
|
+
call, so there's nothing cached to fall back to.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def children(
|
|
26
|
+
self, repo_github: str, issue_number: int, *, body: str = "",
|
|
27
|
+
) -> list[Child]:
|
|
28
|
+
raw = github_ops.get_sub_issues(repo_github, issue_number)
|
|
29
|
+
children: list[Child] = []
|
|
30
|
+
for entry in raw:
|
|
31
|
+
if not isinstance(entry, dict):
|
|
32
|
+
continue
|
|
33
|
+
number = entry.get("number")
|
|
34
|
+
if number is None:
|
|
35
|
+
continue
|
|
36
|
+
children.append(Child(number=int(number), state=str(entry.get("state") or "open")))
|
|
37
|
+
return children
|
|
38
|
+
|
|
39
|
+
def parent(
|
|
40
|
+
self,
|
|
41
|
+
repo_github: str,
|
|
42
|
+
issue_number: int,
|
|
43
|
+
*,
|
|
44
|
+
epics: list[dict] | None = None,
|
|
45
|
+
) -> ParentRef | None:
|
|
46
|
+
raw = github_ops.get_issue_parent(repo_github, issue_number)
|
|
47
|
+
if not raw:
|
|
48
|
+
return None
|
|
49
|
+
number = raw.get("number")
|
|
50
|
+
if number is None:
|
|
51
|
+
return None
|
|
52
|
+
return ParentRef(number=int(number), state=str(raw.get("state") or "open"))
|
|
53
|
+
|
|
54
|
+
def add_child(self, repo_github: str, parent_number: int, child_number: int) -> None:
|
|
55
|
+
github_ops.add_sub_issue(repo_github, parent_number, child_number)
|
|
56
|
+
|
|
57
|
+
def remove_child(self, repo_github: str, parent_number: int, child_number: int) -> None:
|
|
58
|
+
github_ops.remove_sub_issue(repo_github, parent_number, child_number)
|