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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/_data/ai.yml +10 -0
- data/_data/backlog.yml +65 -37
- data/_data/navigation/docs.yml +4 -0
- data/_data/theme_backgrounds.yml +24 -0
- data/_includes/components/component-showcase.html +77 -16
- data/_includes/components/search-modal.html +28 -4
- data/_includes/core/color-mode-init.html +35 -0
- data/_includes/core/head.html +9 -0
- data/_includes/core/tokens-inline.html +5 -0
- data/_layouts/author.html +22 -1
- data/_layouts/root.html +15 -1
- data/scripts/bin/validate +1 -0
- data/scripts/ci/classify_changes.py +129 -0
- data/scripts/issues/dispatch.py +478 -0
- data/scripts/issues/test_verify_close.py +118 -0
- data/scripts/issues/triage.py +1046 -0
- data/scripts/issues/verify_close.py +179 -0
- metadata +9 -2
|
@@ -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.
|
|
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-
|
|
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
|