From 90a8bcd9d538424ae2e53fd585ede312172887e1 Mon Sep 17 00:00:00 2001 From: Shane Kidd <33380501+StarshipSuperjam@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:25:14 -0700 Subject: [PATCH 1/2] De-duplicate 7 more GitHub _http clients onto github_client (follow-up to #295) Follow-up to PR #357: the same authenticated _http shape lived in seven more tools. Re-point each to github_client.request (the shared builder), keeping each one's own urlopen + status/error model + injectable transport seam: bootstrap, issue_conformance_ci, disposition_issue_resolution_check, spec_referent, milestone_emit, projects_sync, dependency_discipline/review. Behavior-preserving: each transport seam is unchanged, so the per-tool tests pass untouched. bootstrap keeps its 3-tuple (status, json, headers) return; disposition stays 2-arg (method, path) with no Content-Type; projects_sync keeps its GraphQL POST. API_ROOT removed from each (now unused). The inert GET Content-Type the write-capable callers previously set is dropped (GitHub ignores it on a bodyless request); disposition was already Content-Type-free. Co-Authored-By: Claude Opus 4.8 --- .engine/knowledge/graph.json | 14 +++++++------- .engine/tools/bootstrap.py | 13 ++----------- .engine/tools/dependency_discipline/review.py | 13 ++----------- .../tools/disposition_issue_resolution_check.py | 12 ++---------- .engine/tools/issue_conformance_ci.py | 13 ++----------- .engine/tools/milestone_emit.py | 15 ++++----------- .engine/tools/projects_sync/projects_sync.py | 13 ++----------- .engine/tools/spec_referent.py | 15 ++++----------- 8 files changed, 25 insertions(+), 83 deletions(-) diff --git a/.engine/knowledge/graph.json b/.engine/knowledge/graph.json index 8a5b530..2d06a45 100644 --- a/.engine/knowledge/graph.json +++ b/.engine/knowledge/graph.json @@ -4053,7 +4053,7 @@ }, "slug": "bootstrap", "source": { - "fingerprint": "sha256:8ed699ea3a47457c7d930b80bd03f8502756f0b0b2bf26ed43d5c02b83cdf591", + "fingerprint": "sha256:d1cde224ea91db72daebf5f2ea97ddc6624001c224e10256a8ec577328f76676", "path": ".engine/tools/bootstrap.py" }, "status": "active", @@ -4529,7 +4529,7 @@ }, "slug": "disposition_issue_resolution_check", "source": { - "fingerprint": "sha256:bcf048e4b39cb22dd4628be313dbb00f9318edc0eecaa4b06ecca0d58b40c312", + "fingerprint": "sha256:92063029a9dc55cfd29652079effbaaf9b0079539cfbe76b9dd0eeac0569a065", "path": ".engine/tools/disposition_issue_resolution_check.py" }, "status": "active", @@ -4801,7 +4801,7 @@ }, "slug": "issue_conformance_ci", "source": { - "fingerprint": "sha256:34dc46cb824356e75ef386cc3ecb38c30ac51da794b311eef41f177777d14037", + "fingerprint": "sha256:9c4752d5fb8f74d830edd436ef80243683f6b7564df1deac7f22e3f32ccc9416", "path": ".engine/tools/issue_conformance_ci.py" }, "status": "active", @@ -5022,7 +5022,7 @@ }, "slug": "milestone_emit", "source": { - "fingerprint": "sha256:9d3717c2831dbc9087a9c7a43188bea6dfeb4a0b9a10868f8e5c2079b931fe8f", + "fingerprint": "sha256:f43a1ed496916e777f7fd1cfc7732c7fbbb7749ec8406a5f06f6f91b6364a50e", "path": ".engine/tools/milestone_emit.py" }, "status": "active", @@ -5192,7 +5192,7 @@ }, "slug": "projects_sync", "source": { - "fingerprint": "sha256:0fb7fc0b5256efa42a90f715ef5f5bba9ec1122ed9daf8b048582b45f10ce312", + "fingerprint": "sha256:bef0404e623dd4339b369669c5424dd3f42f0c9af8f97fc9b4088706a9203be9", "path": ".engine/tools/projects_sync/projects_sync.py" }, "status": "active", @@ -5277,7 +5277,7 @@ }, "slug": "review", "source": { - "fingerprint": "sha256:a613a3030cc3a1b3c845d924fd8483e5eb13dc3c208563d7b479016d4255f87a", + "fingerprint": "sha256:1a586c2b2493e76023df23e8e652afe2db9ef01ca0148cc1882e51c2d9076ac3", "path": ".engine/tools/dependency_discipline/review.py" }, "status": "active", @@ -5447,7 +5447,7 @@ }, "slug": "spec_referent", "source": { - "fingerprint": "sha256:c2e06b463c577c4701ea40c7aa75fda9803202d6beb67bd324735c8859bdf95a", + "fingerprint": "sha256:cacd5631adb59089cabc3d7b0ffb74b8b5044381b07fbe682ceb2faec94ef7b4", "path": ".engine/tools/spec_referent.py" }, "status": "active", diff --git a/.engine/tools/bootstrap.py b/.engine/tools/bootstrap.py index 264bbc2..6596a0c 100644 --- a/.engine/tools/bootstrap.py +++ b/.engine/tools/bootstrap.py @@ -65,11 +65,11 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import validate # noqa: E402 +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) import boot # noqa: E402 (repo_slug, gh_token, PROTECTED_BRANCH — the shared GitHub-context helpers) import protection_guard # noqa: E402 (REQUIRED_CHECKS + missing_floor — the SINGLE home of the floor) import telemetry # noqa: E402 (GitHubIssues.ensure_label — the slice-18 minimal ensure this inherits) -API_ROOT = "https://api.github.com" USER_AGENT = "engine-control-plane-bootstrap" TEMPLATE_PATH = os.path.join(validate.ENGINE_DIR, "templates", "control-plane-bootstrap.md") @@ -523,16 +523,7 @@ def __init__(self, repo: str, token: str, transport=None, refresh_fn=None, issue def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/dependency_discipline/review.py b/.engine/tools/dependency_discipline/review.py index f1eaeea..1b31629 100644 --- a/.engine/tools/dependency_discipline/review.py +++ b/.engine/tools/dependency_discipline/review.py @@ -59,8 +59,8 @@ sys.path.insert(0, _PARENT) import validate # noqa: E402 — the finding.v1 helper + ROOT (test-redirectable) +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) -API_ROOT = "https://api.github.com" USER_AGENT = "engine-dependency-review" _PRICING_URL = "https://github.com/pricing" _ENGINE_PREFIX = ".engine/" # the §13 wall: manifests here are the engine's own tooling, never product deps @@ -126,16 +126,7 @@ def __init__(self, repo: str, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/disposition_issue_resolution_check.py b/.engine/tools/disposition_issue_resolution_check.py index 6a34e9e..d07a69a 100644 --- a/.engine/tools/disposition_issue_resolution_check.py +++ b/.engine/tools/disposition_issue_resolution_check.py @@ -38,10 +38,10 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import validate # noqa: E402 (finding, section_blocks, get_pr_body, env_override_path, read) +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) ENGINE_LABEL = "engine" _CITATION_RE = re.compile(r"#(\d+)") -_API_ROOT = "https://api.github.com" _USER_AGENT = "engine-disposition-issue-resolution" @@ -61,15 +61,7 @@ def __init__(self, repo: str, token: str, *, transport=None): self._transport = transport or self._http def _http(self, method: str, path: str): - req = urllib.request.Request( - _API_ROOT + path, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": _USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=_USER_AGENT, method=method) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/issue_conformance_ci.py b/.engine/tools/issue_conformance_ci.py index 30c6574..fe1c6a7 100644 --- a/.engine/tools/issue_conformance_ci.py +++ b/.engine/tools/issue_conformance_ci.py @@ -51,9 +51,9 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import issue_author # noqa: E402 +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) import issue_gate # noqa: E402 -API_ROOT = "https://api.github.com" USER_AGENT = "engine-issue-conformance" # The label applied to a non-conforming engine Issue. The design names the signal `needs-reauthoring` @@ -89,16 +89,7 @@ def __init__(self, repo: str, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/milestone_emit.py b/.engine/tools/milestone_emit.py index 857ab66..5c89959 100644 --- a/.engine/tools/milestone_emit.py +++ b/.engine/tools/milestone_emit.py @@ -35,9 +35,11 @@ import urllib.error import urllib.request +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # the sibling tools dir, for github_client +import github_client # noqa: E402 — the shared authenticated GitHub API client; request-build + # ---- constants ------------------------------------------------------------- -API_ROOT = "https://api.github.com" USER_AGENT = "engine-milestone-emit" # Where the committed build order lives (the product-design module's artifact), relative to the repo root. @@ -119,16 +121,7 @@ def __init__(self, repo: str, token: str, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/projects_sync/projects_sync.py b/.engine/tools/projects_sync/projects_sync.py index 490bc63..ce313d4 100644 --- a/.engine/tools/projects_sync/projects_sync.py +++ b/.engine/tools/projects_sync/projects_sync.py @@ -52,10 +52,10 @@ import boot # noqa: E402 — the already-resolved, leak-guard-clean signal assembler + gh token/slug import validate # noqa: E402 — ROOT (test-redirectable) for the gitignored config path import telemetry # noqa: E402 — ENGINE_DOMAIN_LABEL + the open-engine-item lister +import github_client # noqa: E402 (the shared authenticated GitHub API client; request-build) # ---- constants --------------------------------------------------------------------------------- -API_ROOT = "https://api.github.com" GRAPHQL_PATH = "/graphql" USER_AGENT = "engine-github-projects-sync" @@ -162,16 +162,7 @@ def __init__(self, token: str, *, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") diff --git a/.engine/tools/spec_referent.py b/.engine/tools/spec_referent.py index 5b1df02..3834e02 100644 --- a/.engine/tools/spec_referent.py +++ b/.engine/tools/spec_referent.py @@ -54,9 +54,11 @@ import urllib.error import urllib.request +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # the sibling tools dir, for github_client +import github_client # noqa: E402 — the shared authenticated GitHub API client; request-build + # ---- constants ------------------------------------------------------------- -API_ROOT = "https://api.github.com" USER_AGENT = "engine-spec-referent" # The committed product spec tree, relative to the repository root. @@ -387,16 +389,7 @@ def __init__(self, repo: str, token: str, transport=None): def _http(self, method: str, path: str, body=None): data = json.dumps(body).encode("utf-8") if body is not None else None - req = urllib.request.Request( - API_ROOT + path, data=data, method=method, - headers={ - "Authorization": f"Bearer {self.token}", - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Content-Type": "application/json", - "User-Agent": USER_AGENT, - }, - ) + req = github_client.request(path, self.token, user_agent=USER_AGENT, method=method, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8") From 306b5afa2df8a814c771c714e5a81984639f13bf Mon Sep 17 00:00:00 2001 From: Shane Kidd <33380501+StarshipSuperjam@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:43:06 -0700 Subject: [PATCH 2/2] Fold deliverable-gate nits: refresh now-stale gh-client docstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usability lens caught that spec_referent / milestone_emit / issue_conformance still described their _http as a "knowing duplicate" of the gh client tracked for consolidation in #295 — language #357 stripped from its files but this follow-up missed. Update those docstrings to say the request-build now goes through the shared github_client while each keeps its own transport/error handling. The markdown-parser and pipe-table-parser "knowing duplicate" notes are left intact — those duplications are real and untouched. Co-Authored-By: Claude Opus 4.8 --- .engine/knowledge/graph.json | 6 +++--- .engine/tools/issue_conformance_ci.py | 9 +++++---- .engine/tools/milestone_emit.py | 11 ++++------- .engine/tools/spec_referent.py | 8 ++++---- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.engine/knowledge/graph.json b/.engine/knowledge/graph.json index 2d06a45..ff1a381 100644 --- a/.engine/knowledge/graph.json +++ b/.engine/knowledge/graph.json @@ -4801,7 +4801,7 @@ }, "slug": "issue_conformance_ci", "source": { - "fingerprint": "sha256:9c4752d5fb8f74d830edd436ef80243683f6b7564df1deac7f22e3f32ccc9416", + "fingerprint": "sha256:1175a7e114bf86167d422d8bdcdc8830c6c703525d80fca3d2244bb97aba0730", "path": ".engine/tools/issue_conformance_ci.py" }, "status": "active", @@ -5022,7 +5022,7 @@ }, "slug": "milestone_emit", "source": { - "fingerprint": "sha256:f43a1ed496916e777f7fd1cfc7732c7fbbb7749ec8406a5f06f6f91b6364a50e", + "fingerprint": "sha256:2a03cc45baecc55a08088ee2af54b680f502b220f95fbbc6f24fe2249b40a9ce", "path": ".engine/tools/milestone_emit.py" }, "status": "active", @@ -5447,7 +5447,7 @@ }, "slug": "spec_referent", "source": { - "fingerprint": "sha256:cacd5631adb59089cabc3d7b0ffb74b8b5044381b07fbe682ceb2faec94ef7b4", + "fingerprint": "sha256:a1a142f4a56a6c8c599c5ce931cab0d2cca53034396ec6e038adfe06c8ccd888", "path": ".engine/tools/spec_referent.py" }, "status": "active", diff --git a/.engine/tools/issue_conformance_ci.py b/.engine/tools/issue_conformance_ci.py index fe1c6a7..648a714 100644 --- a/.engine/tools/issue_conformance_ci.py +++ b/.engine/tools/issue_conformance_ci.py @@ -32,10 +32,11 @@ and the in-session gate is the first line — widening the trigger would diverge from the locked design. SELF-CONTAINED TRANSPORT. telemetry.GitHubIssues has no per-Issue label/comment operations (its label is baked -in at construction and it only opens/updates engine-health Issues), so this tool carries its own small client -mirroring the audit_digest._http / telemetry._http idiom (same headers, 30s timeout, (status, json) return, -injectable `_transport` seam) over the per-Issue label and comment operations it needs (label ensure/add/remove, -comment list/post). telemetry.py is left untouched; the markers are single-sourced from issue_gate. +in at construction and it only opens/updates engine-health Issues), so this tool carries its own transport — its +own urlopen + (status, json) return + injectable `_transport` seam (30s timeout), building requests through the +shared `github_client` (the audit_digest / telemetry idiom) — over the per-Issue label and comment operations it +needs (label ensure/add/remove, comment list/post). telemetry.py is left untouched; the markers are +single-sourced from issue_gate. CLI (operator-runnable, falsifiable — the live net is what the workflow invokes): uv run --directory .engine -- python tools/issue_conformance_ci.py demo # scripted, fake GitHub, self-checks diff --git a/.engine/tools/milestone_emit.py b/.engine/tools/milestone_emit.py index 5c89959..3e6e40e 100644 --- a/.engine/tools/milestone_emit.py +++ b/.engine/tools/milestone_emit.py @@ -12,11 +12,9 @@ SELF-CONTAINED ON PURPOSE. This is a CORE tool, but the build order is authored by the OPTIONAL product-design module. So it imports NO product-design code — a required tool must not depend on an optional module, or it would crash the "absent a build order, plan the phase yourself" path on every repo that never installed product-design. -It therefore carries its own minimal pipe-table parser (a knowing duplicate of a trivial parse) and its own -GitHub boundary, mirroring the engine's injectable-transport seam (`telemetry.GitHubIssues` / -`standing_situation`). The gh-client duplication across telemetry (issues) / weakening_guard + audit_digest + -lock_integrity (contents) / here (milestones) is the shared-client consolidation tracked in engine-template -#295 — folded there, not solved here. +It therefore carries its own minimal pipe-table parser (a knowing duplicate of a trivial parse); its GitHub +boundary builds requests through the shared `github_client` (the gh-client consolidation engine-template #295 +began) while keeping its own injectable-transport seam (`telemetry.GitHubIssues` / `standing_situation`). Idempotency (the `gh api` Milestones surface has no upsert): list every existing milestone (`state=all`, so a CLOSED phase is not recreated) paginated to exhaustion, match by trimmed title, and POST only the missing ones. @@ -111,8 +109,7 @@ class GitHubMilestones: """The Milestone boundary. Mirrors the engine's injectable-transport seam (`telemetry.GitHubIssues` / `standing_situation`): `transport(method, path, body) -> (status, json)` is injectable, so the demo and tests fake ONLY the network and run the real list-then-create logic. A read/write failure RAISES (never a - partial "done"). Carries its own minimal `_http` — a knowing duplicate of the engine's gh client, tracked - for consolidation in #295.""" + partial "done"). Its `_http` builds requests through the shared `github_client`, keeping its own status/error handling.""" def __init__(self, repo: str, token: str, transport=None): self.repo = repo diff --git a/.engine/tools/spec_referent.py b/.engine/tools/spec_referent.py index 3834e02..d8c2a08 100644 --- a/.engine/tools/spec_referent.py +++ b/.engine/tools/spec_referent.py @@ -23,9 +23,9 @@ SELF-CONTAINED ON PURPOSE. This is a CORE tool, but the spec corpus is authored by the OPTIONAL product-design module. So it imports NO product-design code — a required tool must not depend on an optional module, or it would crash on every repo that never installed product-design. It carries its own minimal markdown parser (a knowing -duplicate of the trivial bits of `product_design/spec_form.py`) and its own GitHub boundary, mirroring the -engine's injectable-transport seam (`milestone_emit` / `standing_situation`). The gh-client duplication is the -shared-client consolidation tracked in engine-template #295 — folded there, not solved here. +duplicate of the trivial bits of `product_design/spec_form.py`); its GitHub boundary builds requests through the +shared `github_client` (the gh-client consolidation engine-template #295 began) while keeping its own +injectable-transport seam (`milestone_emit` / `standing_situation`). Fail-closed (the load-bearing safety): the work-item body is the one REMOTE read. A read FAILURE (HTTP >= 400, a null body, an unreachable host, an unexpected shape) RAISES `SpecReferentError` — it is NEVER read as "no spec -> @@ -380,7 +380,7 @@ class GitHubIssues: """The Issue-read boundary. Mirrors the engine's injectable-transport seam (`milestone_emit.GitHubMilestones` / `standing_situation`): `transport(method, path, body) -> (status, json)` is injectable, so the demo and tests fake ONLY the network and run the real resolution. A read failure RAISES `SpecReferentError` (never a - silent no-op). Carries its own minimal `_http` — a knowing duplicate tracked for consolidation in #295.""" + silent no-op). Its `_http` builds the request through the shared `github_client`, keeping its own status/error handling.""" def __init__(self, repo: str, token: str, transport=None): self.repo = repo