Skip to content

Commit e12d41c

Browse files
fix: stop discarding the health report
The engine runs health checks on every analysis, full or incremental, and writes health_report.json beside the graph it produced. Nothing kept it: sync deleted the file instead of installing the fresh one, and review never uploaded it. The only surviving copy was inside the Actions cache, which has no download API, so neither the extension nor the webview could read health information at all. It has been that way since the v2 rewrite in 474d40a, which removed the file as a v1 leftover without noticing that the engine still produced it. Sync now installs the report it just generated, and removes a stale one when a run produced none, matching how the other optional artifacts are handled. Review ships it in the artifact next to the analyses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6bf27f0 commit e12d41c

6 files changed

Lines changed: 100 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ The uploaded artifact holds both sides of the comparison, so a reader can reprod
6060
|---|---|
6161
| `analysis.json` | the head analysis, at `head_sha` |
6262
| `base_analysis.json` | the analysis it was compared against, at `merge_base_sha` |
63-
| `metadata.json` | both SHAs, `merge_base_resolved`, `seed_source`, `chain_depth`, PR number |
63+
| `health_report.json` | the head's health findings, when the engine produced any |
64+
| `metadata.json` | which commits those graphs describe — see [the field list](docs/COMMIT_STRATEGY.md#names) |
6465

65-
Artifacts are kept 30 days. Reading the default branch's committed baseline instead of `base_analysis.json` would drift from the merge base in exactly the way described above.
66+
The action requests 30-day retention; a repository or organisation policy can shorten it, so treat an artifact's own `expired` flag as the truth rather than any fixed window. Reading the default branch's committed baseline instead of `base_analysis.json` would drift from the merge base in exactly the way described above.
6667

6768
### Reused analysis
6869

@@ -127,8 +128,9 @@ Sync mode commits only Core's persisted incremental-analysis state under `.codeb
127128
- `static_analysis.pkl`
128129
- `static_analysis.sha`
129130
- `codeboarding_version.json` when emitted by Core
131+
- `health/health_report.json`
130132

131-
It does **not** render or commit architecture Markdown. Existing v1-generated `.codeboarding/*.md` and `docs/development/architecture.md` files carrying CodeBoarding's generated badge, plus `.codeboarding/health/health_report.json`, are removed on the first v2 sync. Hand-written Markdown and user-authored CodeBoarding configuration are preserved.
133+
It does **not** render or commit architecture Markdown. Existing v1-generated `.codeboarding/*.md` and `docs/development/architecture.md` files carrying CodeBoarding's generated badge are removed on the first v2 sync. Hand-written Markdown and user-authored CodeBoarding configuration, including `health/health_config.json` and `health/.healthignore`, are preserved.
132134

133135
Create `.github/workflows/codeboarding-sync.yml`:
134136

‎docs/COMMIT_STRATEGY.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ branch, so generated files cannot conflict during a merge.
2727
| `static_analysis.pkl` | LSP/CFG cache and the cluster baseline incremental needs |
2828
| `static_analysis.sha` | the warm-start gate for the pickle |
2929
| `codeboarding_version.json` | when Core emits it |
30+
| `health/health_report.json` | the warnings, read without regenerating them |
3031

31-
Everything else generated is removed on sync: v1 architecture Markdown and
32-
`health/health_report.json`. Hand-written Markdown and user configuration
33-
(`.codeboardingignore`, health config) are preserved.
32+
The engine writes a health report on every run, full or incremental, so sync
33+
installs the fresh one and drops a stale one when a run produced none. v1
34+
architecture Markdown is removed. Hand-written Markdown and user configuration
35+
(`.codeboardingignore`, `health/health_config.json`, `health/.healthignore`) are
36+
preserved.
3437

3538
**Delivery (`sync_strategy`).** The committed set is identical either way; only
3639
how it reaches the branch differs. `push` fast-forwards it directly.
@@ -100,6 +103,7 @@ lists a pull request's artifacts and takes the newest.
100103
codeboarding-review-<run_id>-<attempt>/
101104
analysis.json the head analysis, at metadata.head_sha
102105
base_analysis.json what it was compared against, at metadata.merge_base_sha
106+
health_report.json the head's health findings, when the engine wrote one
103107
metadata.json which commits those graphs describe
104108

105109
| `metadata.json` field | Meaning |

‎scripts/action/build-review-artifact.sh‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ cp "$ANALYSIS_PATH" "${RUNNER_TEMP}/cb-review-artifact/analysis.json"
88
# branch's committed baseline instead would drift from the merge base exactly as
99
# the review itself used to.
1010
cp "$BASE_ANALYSIS_PATH" "${RUNNER_TEMP}/cb-review-artifact/base_analysis.json"
11+
# The engine writes this next to the analysis it came from. Ship it when present
12+
# so a pull request's warnings are readable without rerunning the analysis.
13+
HEALTH_REPORT="$(dirname "$ANALYSIS_PATH")/health/health_report.json"
14+
[ ! -f "$HEALTH_REPORT" ] || cp "$HEALTH_REPORT" "${RUNNER_TEMP}/cb-review-artifact/health_report.json"
15+
1116
# base_sha stays the event's base branch tip for consumers that key on it;
1217
# merge_base_sha records the commit the diagram actually compared against.
1318
# pr_base_sha carries the same value under the name the webview already reads:

‎scripts/action/install-sync.sh‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,25 @@ for name in "${artifacts[@]}"; do
4040
printf '%s\n' "$target"
4141
done
4242

43-
# Remove identifiable v1 Markdown and its generated health report.
43+
# The engine writes a health report on every run, full or incremental. Install it
44+
# beside the analysis so the extension and webview can read warnings without
45+
# regenerating, and remove a stale one when a run produced none.
46+
health_source="$ANALYSIS_DIR/health/health_report.json"
47+
health_target="$output/health/health_report.json"
48+
if [ -f "$health_source" ]; then
49+
mkdir -p "$output/health"
50+
cp "$health_source" "$health_target"
51+
elif [ -e "$health_target" ]; then
52+
rm -f "$health_target"
53+
fi
54+
printf '%s\n' "$health_target"
55+
56+
# Remove identifiable v1 Markdown.
4457
marker='https://img.shields.io/badge/Generated%20by-CodeBoarding'
4558
for legacy in "$output"/*.md "$CHECKOUT_DIR/docs/development/architecture.md"; do
4659
[ -f "$legacy" ] || continue
4760
grep -Fq "$marker" "$legacy" || continue
4861
rm -f "$legacy"
4962
printf '%s\n' "$legacy"
5063
done
51-
legacy="$output/health/health_report.json"
52-
if [ -e "$legacy" ]; then
53-
rm -f "$legacy"
54-
printf '%s\n' "$legacy"
55-
fi
56-
5764
echo "installed=$installed" >> "$GITHUB_OUTPUT"

‎tests/test_action_cache.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def setUp(self) -> None:
337337
def tearDown(self) -> None:
338338
self.temp_dir.cleanup()
339339

340-
def test_it_ships_both_graphs_and_the_commit_they_describe(self) -> None:
340+
def _build(self) -> subprocess.CompletedProcess:
341341
output = self.root / "github-output"
342342
result = subprocess.run(
343343
[str(ROOT / "scripts" / "action" / "build-review-artifact.sh")],
@@ -361,6 +361,10 @@ def test_it_ships_both_graphs_and_the_commit_they_describe(self) -> None:
361361
check=False,
362362
)
363363
self.assertEqual(result.returncode, 0, result.stderr or result.stdout)
364+
return result
365+
366+
def test_it_ships_both_graphs_and_the_commit_they_describe(self) -> None:
367+
self._build()
364368

365369
artifact = self.root / "cb-review-artifact"
366370
self.assertEqual(json.loads((artifact / "analysis.json").read_text())["components"], ["head"])
@@ -379,6 +383,26 @@ def test_it_ships_both_graphs_and_the_commit_they_describe(self) -> None:
379383
self.assertEqual(metadata["seed_source"], "pr-chain")
380384

381385

386+
class ReviewHealthArtifactTests(ReviewArtifactTests):
387+
"""The engine writes a health report beside every analysis it produces."""
388+
389+
def test_it_ships_the_health_report_when_the_engine_wrote_one(self) -> None:
390+
(self.root / "health").mkdir()
391+
(self.root / "health" / "health_report.json").write_text('{"overall_score": 0.9}', encoding="utf-8")
392+
393+
self._build()
394+
395+
report = self.root / "cb-review-artifact" / "health_report.json"
396+
self.assertEqual(report.read_text(encoding="utf-8"), '{"overall_score": 0.9}')
397+
398+
def test_it_still_builds_when_no_health_report_was_written(self) -> None:
399+
self._build()
400+
401+
artifact = self.root / "cb-review-artifact"
402+
self.assertTrue((artifact / "analysis.json").is_file())
403+
self.assertFalse((artifact / "health_report.json").exists())
404+
405+
382406
class CachePathParityTests(unittest.TestCase):
383407
"""actions/cache derives its lookup version from the path strings, so a save
384408
under a different path than the restore can never be found again."""

‎tests/test_action_sync.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,50 @@ def test_installs_core_manifest_and_preserves_user_configuration(self) -> None:
244244
self.assertEqual(result.returncode, 0, result.stderr or result.stdout)
245245
self.assertEqual(architecture.read_text(encoding="utf-8"), "hand-written architecture\n")
246246

247+
def test_installs_the_health_report_the_engine_produced(self) -> None:
248+
with tempfile.TemporaryDirectory() as tmp:
249+
root = Path(tmp)
250+
checkout = root / "checkout"
251+
analysis = root / "analysis"
252+
fake_core = root / "core"
253+
static_analyzer = fake_core / "static_analyzer"
254+
for directory in (checkout / ".codeboarding", analysis / "health", static_analyzer):
255+
directory.mkdir(parents=True)
256+
(fake_core / "utils.py").write_text(
257+
"ANALYSIS_FILENAME = 'analysis.json'\nFINGERPRINT_FILENAME = 'fingerprint.json'\n",
258+
encoding="utf-8",
259+
)
260+
(static_analyzer / "__init__.py").touch()
261+
(static_analyzer / "analysis_cache.py").write_text(
262+
"STATIC_ANALYSIS_PKL = 'static_analysis.pkl'\nSTATIC_ANALYSIS_SHA = 'static_analysis.sha'\n",
263+
encoding="utf-8",
264+
)
265+
(analysis / "analysis.json").write_text("{}\n", encoding="utf-8")
266+
(analysis / "health" / "health_report.json").write_text('{"overall_score": 1.0}', encoding="utf-8")
267+
268+
result = subprocess.run(
269+
[str(INSTALL_SYNC)],
270+
cwd=checkout,
271+
env={
272+
"PATH": os.environ["PATH"],
273+
"PYTHONPATH": str(fake_core),
274+
"ACTION_PATH": str(ROOT),
275+
"ANALYSIS_DIR": str(analysis),
276+
"CHECKOUT_DIR": str(checkout),
277+
"GITHUB_OUTPUT": str(root / "github-output"),
278+
},
279+
capture_output=True,
280+
text=True,
281+
check=False,
282+
)
283+
284+
self.assertEqual(result.returncode, 0, result.stderr or result.stdout)
285+
installed = checkout / ".codeboarding" / "health" / "health_report.json"
286+
self.assertEqual(installed.read_text(encoding="utf-8"), '{"overall_score": 1.0}')
287+
# Printed paths are what the delivery step stages, so an installed
288+
# file that is never printed is silently left out of the commit.
289+
self.assertIn(str(installed), result.stdout.splitlines())
290+
247291
def test_empty_review_is_successful(self) -> None:
248292
with tempfile.TemporaryDirectory() as tmp:
249293
root = Path(tmp)

0 commit comments

Comments
 (0)