jekyll-theme-zer0 1.22.0 → 1.23.0

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.
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ verify_close.py — the deterministic gate of the Issue Autopilot verify-and-close lane.
4
+
5
+ The issue-verifier (an LLM, read-only) inspects each `verify_candidate` issue and
6
+ writes its verdicts to `.issues/verify.json`. This script is the DETERMINISTIC half:
7
+ it selects which of those verdicts may actually close an issue, and it does so ONLY
8
+ when `main`'s full CI/CD gate suite is green. The autopilot never closes a human
9
+ issue on an LLM's say-so alone — the green-CI gate here is the hard backstop.
10
+
11
+ A verdict closes its issue iff ALL of:
12
+ 1. verdict.resolved is true,
13
+ 2. verdict.confidence == "high" and verdict.evidence is non-empty,
14
+ 3. the issue is a `verify_candidate` in plan.json (defense in depth — never a
15
+ protected/backlog-managed/bot/epic issue, even if the LLM names one),
16
+ 4. `main`'s combined commit status + every check-run on its HEAD are green.
17
+
18
+ `select` prints `<number>\t<evidence>` TSV for closable issues (the workflow does
19
+ the actual `gh issue close`, keeping the irreversible mutation in an auditable
20
+ step). If the CI/CD gate is not green, it prints NOTHING and the whole batch is
21
+ held — "close only if they pass all CI/CD gates" is all-or-nothing per run.
22
+
23
+ READ-ONLY against GitHub: this script only ever calls `gh api` GET endpoints. It
24
+ never closes, comments, or labels. Verdict text is treated strictly as DATA.
25
+
26
+ Subcommands:
27
+ select print closable `<number>\t<evidence>` lines (gated on green CI)
28
+ gate print "green" / "not-green: <why>" for main's CI suite, then exit 0/1
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import json
34
+ import subprocess
35
+ import sys
36
+ from pathlib import Path
37
+ from typing import Any, Optional
38
+
39
+ ISSUES_DIR = Path(".issues")
40
+ PLAN_PATH = ISSUES_DIR / "plan.json"
41
+ VERIFY_PATH = ISSUES_DIR / "verify.json"
42
+
43
+ # check-run conclusions that do NOT block a close (everything else does, and any
44
+ # check-run that has not `completed` blocks too — something is still running).
45
+ OK_CONCLUSIONS = {"success", "neutral", "skipped"}
46
+
47
+
48
+ def warn(msg: str) -> None:
49
+ print(f"::warning::verify_close: {msg}", file=sys.stderr)
50
+
51
+
52
+ def _gh_api(path: str) -> Optional[Any]:
53
+ """GET a gh api endpoint, returning parsed JSON or None on any failure."""
54
+ try:
55
+ out = subprocess.run(
56
+ ["gh", "api", "-H", "Accept: application/vnd.github+json", path],
57
+ capture_output=True, text=True, timeout=60,
58
+ )
59
+ except (OSError, subprocess.SubprocessError) as exc:
60
+ warn(f"gh api {path} failed to run: {exc}")
61
+ return None
62
+ if out.returncode != 0:
63
+ warn(f"gh api {path} exited {out.returncode}: {out.stderr.strip()[:200]}")
64
+ return None
65
+ try:
66
+ return json.loads(out.stdout or "null")
67
+ except json.JSONDecodeError as exc:
68
+ warn(f"gh api {path} returned non-JSON: {exc}")
69
+ return None
70
+
71
+
72
+ def ci_gate(repo: str, ref: str = "main") -> tuple[bool, str]:
73
+ """
74
+ True iff `main`'s combined commit status AND every check-run on its HEAD are
75
+ green. Fails CLOSED: any API error, pending run, or failure -> (False, why).
76
+ """
77
+ if not repo:
78
+ return False, "no repo configured"
79
+
80
+ status = _gh_api(f"repos/{repo}/commits/{ref}/status")
81
+ if status is None:
82
+ return False, "could not read commit status (failing closed)"
83
+ state = str(status.get("state") or "")
84
+ total = int(status.get("total_count") or 0)
85
+ # A commit with zero classic statuses reports state=pending; that's only a
86
+ # blocker if there are actually statuses. With statuses, require success.
87
+ if total > 0 and state != "success":
88
+ return False, f"combined commit status is '{state}' ({total} status(es))"
89
+
90
+ runs = _gh_api(f"repos/{repo}/commits/{ref}/check-runs?per_page=100")
91
+ if runs is None:
92
+ return False, "could not read check-runs (failing closed)"
93
+ for run in runs.get("check_runs") or []:
94
+ name = str(run.get("name") or "?")
95
+ if str(run.get("status")) != "completed":
96
+ return False, f"check-run '{name}' is not completed (status={run.get('status')})"
97
+ if str(run.get("conclusion")) not in OK_CONCLUSIONS:
98
+ return False, f"check-run '{name}' concluded '{run.get('conclusion')}'"
99
+ return True, f"green (state={state or 'none'}, {total} status(es), all check-runs passed)"
100
+
101
+
102
+ def _load(path: Path) -> Optional[Any]:
103
+ if not path.exists():
104
+ warn(f"{path} not found")
105
+ return None
106
+ try:
107
+ return json.loads(path.read_text(encoding="utf-8"))
108
+ except (OSError, json.JSONDecodeError) as exc:
109
+ warn(f"{path} unreadable: {exc}")
110
+ return None
111
+
112
+
113
+ def closable(plan: dict[str, Any], verify: dict[str, Any]) -> list[dict[str, Any]]:
114
+ """Verdicts that pass the policy filter (steps 1-3), before the CI gate."""
115
+ candidates = {
116
+ int(r["number"]): r
117
+ for r in (plan.get("issues") or [])
118
+ if r.get("verify_candidate") and r.get("number") is not None
119
+ }
120
+ out: list[dict[str, Any]] = []
121
+ for v in (verify.get("verdicts") or []):
122
+ try:
123
+ num = int(v.get("number"))
124
+ except (TypeError, ValueError):
125
+ continue
126
+ if not v.get("resolved"):
127
+ continue
128
+ if str(v.get("confidence") or "").lower() != "high":
129
+ continue
130
+ evidence = str(v.get("evidence") or "").strip()
131
+ if not evidence:
132
+ continue
133
+ if num not in candidates: # defense in depth — never close a non-candidate
134
+ warn(f"#{num} verdict resolved but not a verify_candidate — skipping")
135
+ continue
136
+ out.append({"number": num, "evidence": evidence})
137
+ return out
138
+
139
+
140
+ def main(argv: Optional[list[str]] = None) -> int:
141
+ ap = argparse.ArgumentParser(description=__doc__)
142
+ ap.add_argument("command", choices=["select", "gate"])
143
+ ap.add_argument("--repo", default=None, help="owner/name (else from plan.json)")
144
+ ap.add_argument("--ref", default="main", help="git ref to gate on (default: main)")
145
+ args = ap.parse_args(argv)
146
+
147
+ plan = _load(PLAN_PATH) or {}
148
+ repo = args.repo or str(plan.get("repo") or "")
149
+
150
+ if args.command == "gate":
151
+ green, why = ci_gate(repo, args.ref)
152
+ print("green" if green else f"not-green: {why}")
153
+ return 0 if green else 1
154
+
155
+ # select
156
+ verify = _load(VERIFY_PATH)
157
+ if not isinstance(verify, dict):
158
+ print("no verdicts to act on.", file=sys.stderr)
159
+ return 0
160
+ picks = closable(plan, verify)
161
+ if not picks:
162
+ print("no resolved+high-confidence verdicts for a verify_candidate.", file=sys.stderr)
163
+ return 0
164
+
165
+ green, why = ci_gate(repo, args.ref)
166
+ if not green:
167
+ warn(f"CI/CD gate not green — holding {len(picks)} close(s) this run: {why}")
168
+ return 0
169
+
170
+ print(f"CI/CD gate {why}", file=sys.stderr)
171
+ for p in picks:
172
+ # TSV: the workflow closes these; evidence goes into the close comment.
173
+ evidence = p["evidence"].replace("\t", " ").replace("\n", " ")
174
+ print(f"{p['number']}\t{evidence}")
175
+ return 0
176
+
177
+
178
+ if __name__ == "__main__":
179
+ raise SystemExit(main())
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-zer0
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.0
4
+ version: 1.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Abdel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-29 00:00:00.000000000 Z
11
+ date: 2026-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -78,6 +78,7 @@ files:
78
78
  - LICENSE
79
79
  - README.md
80
80
  - _data/README.md
81
+ - _data/ai.yml
81
82
  - _data/authors.yml
82
83
  - _data/backlog.yml
83
84
  - _data/content_statistics.yml
@@ -171,6 +172,7 @@ files:
171
172
  - _includes/content/toc.html
172
173
  - _includes/content/transclude.html
173
174
  - _includes/core/branding.html
175
+ - _includes/core/color-mode-init.html
174
176
  - _includes/core/footer-fabs.html
175
177
  - _includes/core/footer.html
176
178
  - _includes/core/head.html
@@ -384,6 +386,7 @@ files:
384
386
  - scripts/bin/test
385
387
  - scripts/bin/validate
386
388
  - scripts/build
389
+ - scripts/ci/classify_changes.py
387
390
  - scripts/content-review.rb
388
391
  - scripts/convert-notebooks.sh
389
392
  - scripts/dev/css-diff.sh
@@ -449,6 +452,10 @@ files:
449
452
  - scripts/install/template.sh
450
453
  - scripts/install/tui.sh
451
454
  - scripts/install/upgrade.sh
455
+ - scripts/issues/dispatch.py
456
+ - scripts/issues/test_verify_close.py
457
+ - scripts/issues/triage.py
458
+ - scripts/issues/verify_close.py
452
459
  - scripts/lib/README.md
453
460
  - scripts/lib/audit.sh
454
461
  - scripts/lib/changelog.sh