Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/generated-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Generated Docs
# docs/language_status/oracle_assumptions.md (register entries, tests/tools/register.py) and
# docs/language_status/cics_spec_status.md (the CICS spec, translator, stub and cics_spec_status.json) and
# docs/language_status/cobolrt_rules.md (the cobolrt rule catalogue, tests/tools/cobolrt_rules.py, #4711) and
# docs/language_status/progress.md / progress.json (the burned-estate ladder and burn-down, tests/tools/progress_report.py, #5280) -- are no longer
# docs/language_status/progress.md / progress.json / progress.svg (the burned-estate ladder and burn-down, tests/tools/progress_report.py, #5280; the status slide, tests/tools/progress_slide.py, #5286) -- are no longer
# committed. Every PR re-rendered and committed them, and parallel PRs conflicted on them for hours. The list of pages
# lives in tests/tools/generated_docs.py (PAGES).
#
Expand Down Expand Up @@ -33,6 +33,8 @@ on:
- "tests/tools/cobolrt_rules.py"
- "tests/tools/readiness_report.py"
- "tests/tools/progress_report.py"
- "tests/tools/progress_slide.py"
- "gitgalaxy/tools/readiness/era_tags.py"
- "docs/language_status/mutation_scores.json"
- "tests/cics_crucible/coverage.json"
- "tests/estate_crucible/baseline.json"
Expand All @@ -42,6 +44,8 @@ on:
- ".github/workflows/generated-docs.yml"
push:
branches: [main]
schedule:
- cron: "17 5 * * *" # #5286: nightly, so the GitHub-fed numbers of the status slide (milestones, issue counts) stay fresh
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -164,5 +168,6 @@ jobs:
docs/language_status/cobolrt_rules.md \
docs/language_status/readiness_report.md \
docs/language_status/progress.md \
docs/language_status/progress.json
docs/language_status/progress.json \
docs/language_status/progress.svg
echo "published the generated pages from main $GITHUB_SHA: https://github.com/${GITHUB_REPOSITORY}/tree/generated/docs/language_status" >> "$GITHUB_STEP_SUMMARY"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,10 @@ docs/language_status/oracle_assumptions.md
docs/language_status/cics_spec_status.md
docs/language_status/cobolrt_rules.md
docs/language_status/readiness_report.md
# #5280: the progress page and its issues cache (tests/tools/progress_report.py)
# #5280, #5286: the progress page, its slide and its issues cache (tests/tools/progress_report.py)
docs/language_status/progress.md
docs/language_status/progress.json
docs/language_status/progress.svg
docs/language_status/progress_issues_cache.json

# #4980: fetched by equivalence_db2.fetch_odbc_cli() into the Docker build context; never committed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ own always-current self-scan brief is at

## CICS to Java: ported, then proven

[![CICS to Java: where we stand](https://raw.githubusercontent.com/squid-protocol/gitgalaxy/generated/docs/language_status/progress.svg)](https://github.com/squid-protocol/gitgalaxy/blob/generated/docs/language_status/progress.md)

For mainframe estates the structural graph is the start of a migration. `cobol-to-java`
turns a CICS estate (its online transactions, plus the batch COBOL and JCL around them) into
a compiling Spring Boot project, and an equivalence harness proves each ported program
Expand Down
2 changes: 2 additions & 0 deletions gitgalaxy/tools/cobol_to_java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ byte against the original COBOL under GnuCOBOL. Anything the translator cannot h
named `Hole` or an explicit refusal citing the IBM manual—never a guess. Every port ships with
its machine-checked evidence record, and a person approves every port.

[![CICS to Java: where we stand](https://raw.githubusercontent.com/squid-protocol/gitgalaxy/generated/docs/language_status/progress.svg)](https://github.com/squid-protocol/gitgalaxy/blob/generated/docs/language_status/progress.md)

**Current numbers** (the evidence ladder, the CICS commands modelled, the mutation kill rate, the crucibles and the
era table) are generated from the repo's own artifacts, each with its source and the commit it was rendered from, on
the [progress page](https://github.com/squid-protocol/gitgalaxy/blob/generated/docs/language_status/progress.md) (#5280). This README keeps prose only, so it cannot drift. Levels are evidence levels,
Expand Down
7 changes: 7 additions & 0 deletions tests/tools/generated_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def _progress_json() -> str:
return progress_report.render_json_page()


def _progress_svg() -> str:
import progress_slide # tests/tools/progress_slide.py (#5286)

return progress_slide.render_page()


# repo-relative path -> its renderer. A page added here must also be git-ignored and published by generated-docs.yml
# (test_generated_docs.py checks both).
PAGES: dict[str, Callable[[], str]] = {
Expand All @@ -76,6 +82,7 @@ def _progress_json() -> str:
"docs/language_status/readiness_report.md": _readiness_report,
"docs/language_status/progress.md": _progress_md,
"docs/language_status/progress.json": _progress_json,
"docs/language_status/progress.svg": _progress_svg,
}


Expand Down
76 changes: 68 additions & 8 deletions tests/tools/progress_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
LEVELS = ("L0", "L1", "L2", "L3", "L4", "L5")
ERAS = ("0", "1", "2", "3", "4", "none")
SCOPES = ("core", "sweep", "frontier", "infra")
OPEN_WORLD_EPICS = (4890, 4891, 4892, 4893, 4894) # the slide's runtime rows read their states (#5286)
TITLE_TERMS = ("CICS", "COBOL") # issues with the term in the title, for the slide's resolved-issue boxes (#5286)
NOT_CONVERGING = "not converging"
SOURCES = {
"ladder": "docs/language_status/evidence_report/<estate>/report.json (evidence_report.py, generated branch)",
Expand Down Expand Up @@ -167,11 +169,18 @@ def rules_component(doc: dict[str, Any]) -> dict[str, Any]:
rules = doc["rules"]
status = Counter(r["status"] for r in rules)
measured = sum(1 for r in rules if r.get("calibration") not in (None, "uncalibrated"))
return {
families: dict[str, dict[str, int]] = {f["id"]: {} for f in doc.get("families", [])}
for r in rules:
fam = families.setdefault(r.get("family", "?"), {})
fam[r["status"]] = fam.get(r["status"], 0) + 1
out = {
"rules": len(rules),
"status": {s: status.get(s, 0) for s in ("MATCHED", "DIFFERS", "ASSUMED", "REFUSED")},
"measured_on_zos": measured,
}
if families: # the slide's per-family bars (#5286); added only when non-empty
out["families"] = {k: dict(sorted(v.items())) for k, v in families.items()} # the catalogue's own order
return out


def mutation_component(doc: dict[str, Any]) -> dict[str, Any]:
Expand Down Expand Up @@ -252,7 +261,9 @@ def counts(group: list[dict[str, Any]]) -> dict[str, int]:
m28 = [i for i in core if i.get("milestone") == CORE_MILESTONE]
frontier = scoped("frontier")
f_opened, _ = window(frontier)
extra = _slide_inputs(cache)
return {
**extra,
"measured": True,
"as_of": cache["fetched_at"],
"window_days": WINDOW_DAYS,
Expand All @@ -270,6 +281,37 @@ def counts(group: list[dict[str, Any]]) -> dict[str, int]:
}


def _slide_inputs(cache: dict[str, Any]) -> dict[str, Any]:
"""The status slide's GitHub inputs (#5286): milestones, title-search counts and the Open World epic states. Each
key is added only when the cache carries it, so an older cache still loads and the slide says "not measured"."""
out: dict[str, Any] = {}
if cache.get("milestones"):
out["milestones"] = [
{
"number": m["number"],
"title": m["title"],
"open": m["open_issues"],
"closed": m["closed_issues"],
"total": m["open_issues"] + m["closed_issues"],
}
for m in sorted(cache["milestones"], key=lambda m: m["number"])
]
if cache.get("title_counts"):
out["title_counts"] = {k: dict(v) for k, v in sorted(cache["title_counts"].items())}
if cache.get("epics"):
out["epics"] = {str(k): v for k, v in sorted(cache["epics"].items(), key=lambda kv: int(kv[0]))}
return out


def _api(path: str, token: str) -> Any:
req = urllib.request.Request( # noqa: S310 -- a fixed https URL
f"https://api.github.com/{path}",
headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"},
)
with urllib.request.urlopen(req, timeout=60) as resp: # noqa: S310
return json.load(resp)


def fetch_issues(token: str | None = None) -> dict[str, Any]:
"""Every issue (no PRs) carrying a scope label, from the GitHub REST API (stdlib only)."""
token = token or os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
Expand All @@ -278,12 +320,7 @@ def fetch_issues(token: str | None = None) -> dict[str, Any]:
seen: dict[int, dict[str, Any]] = {}
for scope in SCOPES:
for page in range(1, 41):
url = f"https://api.github.com/repos/{REPO_SLUG}/issues?labels=scope:{scope}&state=all&per_page=100&page={page}"
req = urllib.request.Request( # noqa: S310 -- a fixed https URL
url, headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json"}
)
with urllib.request.urlopen(req, timeout=60) as resp: # noqa: S310
batch = json.load(resp)
batch = _api(f"repos/{REPO_SLUG}/issues?labels=scope:{scope}&state=all&per_page=100&page={page}", token)
for i in batch:
if "pull_request" in i:
continue
Expand All @@ -297,8 +334,31 @@ def fetch_issues(token: str | None = None) -> dict[str, Any]:
}
if len(batch) < 100:
break
milestones = [
{
"number": m["number"],
"title": m["title"],
"open_issues": m["open_issues"],
"closed_issues": m["closed_issues"],
}
for m in _api(f"repos/{REPO_SLUG}/milestones?state=all&per_page=100", token)
]
title_counts = {}
for term in TITLE_TERMS:
tot = _api(f"search/issues?q=repo:{REPO_SLUG}+is:issue+{term}+in:title&per_page=1", token)["total_count"]
closed = _api(f"search/issues?q=repo:{REPO_SLUG}+is:issue+is:closed+{term}+in:title&per_page=1", token)[
"total_count"
]
title_counts[term.lower()] = {"total": tot, "closed": closed}
epics = {n: _api(f"repos/{REPO_SLUG}/issues/{n}", token)["state"] for n in OPEN_WORLD_EPICS}
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return {"fetched_at": now, "issues": [seen[k] for k in sorted(seen)]}
return {
"fetched_at": now,
"issues": [seen[k] for k in sorted(seen)],
"milestones": milestones,
"title_counts": title_counts,
"epics": epics,
}


def load_issues() -> dict[str, Any] | None:
Expand Down
Loading
Loading