From 736b0a7780d564eb8357e3f232229940d50e7377 Mon Sep 17 00:00:00 2001 From: "R. Mark Sharp" Date: Tue, 11 Aug 2026 20:13:16 -0500 Subject: [PATCH 1/2] feat(dashboard): recognize R in Code by Language, Quarto/R Markdown as docs SOURCE_EXTS already had .r (R always counted toward Source LOC), but LANG_MAP had no entry for it, so R never got its own row in the Code by Language breakdown -- found scanning a real R package (603 .r files, 77,773 LOC, present in Source but invisible per-language). .qmd (Quarto) and .rmd (R Markdown) were in neither SOURCE_EXTS nor DOC_EXTS, so a file with either extension outside a docs/ path fell through categorize_file's whole ladder to "other" -- not source, not docs, not even LOC-counted (LOC is skipped entirely for "other"). Added both to DOC_EXTS: they are literate-document formats (prose with embedded code), the same bucket .md already lives in. Updated the doc-only corpus-disjunction comment, which described a pure-Quarto repo's .qmd as never counted as docs -- no longer true after this change (a pure-LaTeX repo's .tex still isn't). Added a new fixture + test isolating the render-toolchain arm of that disjunction from the doc-corpus arms, since the existing Quarto fixture's own doc_loc now also clears the corpus threshold on its own and no longer exercises the toolchain arm in isolation. DASHBOARD_VERSION 2.10.2 -> 2.10.3 (also independently proposed by two other currently-open PRs against unrelated changes; whichever merges last will need a fresh bump at that time). RED-first: all 6 new/changed assertions confirmed failing against unmodified upstream/main before either constant was touched. Full suite 203/203 minus 2 pre-existing failures unrelated to this change (FRAMEWORK_INSTALLED_SOURCE not yet extended for context_budget.py, same baseline reproduced against a clean upstream/main worktree). Co-Authored-By: Claude Sonnet 5 --- starter-kit/methodology_dashboard.py | 16 ++++- tools/methodology_dashboard.py | 16 ++++- tools/test_methodology_dashboard.py | 93 ++++++++++++++++++++++++++-- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/starter-kit/methodology_dashboard.py b/starter-kit/methodology_dashboard.py index 1def1a87..07a82e32 100644 --- a/starter-kit/methodology_dashboard.py +++ b/starter-kit/methodology_dashboard.py @@ -95,7 +95,11 @@ ".kt", ".scala", ".lua", ".sh", ".bash", ".zsh", ".pl", ".r", } TEST_PATTERNS = {"test_", "_test.", ".test.", ".spec.", "tests/", "__tests__/", "test/"} -DOC_EXTS = {".md", ".txt", ".rst", ".adoc", ".org"} +# .qmd/.rmd (Quarto / R Markdown) are literate-document formats — prose with embedded code, the +# same bucket .md already lives in, not source's. Without an entry here, a file with either +# extension outside a docs/ path fell through categorize_file's whole ladder to "other": not +# source, not docs, not even LOC-counted (LOC is skipped entirely for "other"). +DOC_EXTS = {".md", ".txt", ".rst", ".adoc", ".org", ".qmd", ".rmd"} CONFIG_FILES = { "Dockerfile", "Makefile", "CMakeLists.txt", "Rakefile", "Gemfile", "Procfile", "fly.toml", "netlify.toml", "vercel.json", @@ -119,6 +123,9 @@ ".kt": "Kotlin", ".scala": "Scala", ".lua": "Lua", ".sh": "Shell", ".bash": "Shell", ".zsh": "Shell", ".html": "HTML", ".css": "CSS", ".scss": "SCSS", ".less": "LESS", ".sql": "SQL", + # .r was already in SOURCE_EXTS (so R LOC always counted toward Source) but had no + # LANG_MAP entry, so it never got its own "Code by Language" row. + ".r": "R", } METHODOLOGY_ITEMS = [ @@ -1971,8 +1978,11 @@ def detect_doc_only(path, files, render): return {"is_doc_only": False, "reason": reason} # 4. Corpus disjunction (only when source is negligible): a real doc corpus OR a render - # toolchain — the latter catches a pure-LaTeX/Quarto repo whose .tex/.qmd are not counted - # as docs (so its doc_loc is ~0), the exact source_loc≈0 research repo that must not be missed. + # toolchain — the latter catches a pure-LaTeX (or other toolchain-only) repo whose + # .tex files are not counted as docs (so its doc_loc is ~0; .qmd/.rmd ARE counted, so a + # pure Quarto/R-Markdown corpus now clears the doc_loc/doc_files arms directly and no + # longer depends on this fallback), the exact source_loc≈0 research repo that must not + # be missed. # Framework-installed markdown is discounted here and ONLY here: bin/sync ships 21 doc # files, which clears DOC_ONLY_DOC_FILES_MIN by itself, so counting them would let the # installer answer the question "is this a document project?" — the mirror of the very diff --git a/tools/methodology_dashboard.py b/tools/methodology_dashboard.py index 1def1a87..07a82e32 100644 --- a/tools/methodology_dashboard.py +++ b/tools/methodology_dashboard.py @@ -95,7 +95,11 @@ ".kt", ".scala", ".lua", ".sh", ".bash", ".zsh", ".pl", ".r", } TEST_PATTERNS = {"test_", "_test.", ".test.", ".spec.", "tests/", "__tests__/", "test/"} -DOC_EXTS = {".md", ".txt", ".rst", ".adoc", ".org"} +# .qmd/.rmd (Quarto / R Markdown) are literate-document formats — prose with embedded code, the +# same bucket .md already lives in, not source's. Without an entry here, a file with either +# extension outside a docs/ path fell through categorize_file's whole ladder to "other": not +# source, not docs, not even LOC-counted (LOC is skipped entirely for "other"). +DOC_EXTS = {".md", ".txt", ".rst", ".adoc", ".org", ".qmd", ".rmd"} CONFIG_FILES = { "Dockerfile", "Makefile", "CMakeLists.txt", "Rakefile", "Gemfile", "Procfile", "fly.toml", "netlify.toml", "vercel.json", @@ -119,6 +123,9 @@ ".kt": "Kotlin", ".scala": "Scala", ".lua": "Lua", ".sh": "Shell", ".bash": "Shell", ".zsh": "Shell", ".html": "HTML", ".css": "CSS", ".scss": "SCSS", ".less": "LESS", ".sql": "SQL", + # .r was already in SOURCE_EXTS (so R LOC always counted toward Source) but had no + # LANG_MAP entry, so it never got its own "Code by Language" row. + ".r": "R", } METHODOLOGY_ITEMS = [ @@ -1971,8 +1978,11 @@ def detect_doc_only(path, files, render): return {"is_doc_only": False, "reason": reason} # 4. Corpus disjunction (only when source is negligible): a real doc corpus OR a render - # toolchain — the latter catches a pure-LaTeX/Quarto repo whose .tex/.qmd are not counted - # as docs (so its doc_loc is ~0), the exact source_loc≈0 research repo that must not be missed. + # toolchain — the latter catches a pure-LaTeX (or other toolchain-only) repo whose + # .tex files are not counted as docs (so its doc_loc is ~0; .qmd/.rmd ARE counted, so a + # pure Quarto/R-Markdown corpus now clears the doc_loc/doc_files arms directly and no + # longer depends on this fallback), the exact source_loc≈0 research repo that must not + # be missed. # Framework-installed markdown is discounted here and ONLY here: bin/sync ships 21 doc # files, which clears DOC_ONLY_DOC_FILES_MIN by itself, so counting them would let the # installer answer the question "is this a document project?" — the mirror of the very diff --git a/tools/test_methodology_dashboard.py b/tools/test_methodology_dashboard.py index 239ecb9d..ca18c49a 100644 --- a/tools/test_methodology_dashboard.py +++ b/tools/test_methodology_dashboard.py @@ -105,7 +105,9 @@ def test_markdown_research_is_doc_only(self): self.assertEqual(r["reason"], "heuristic") def test_pure_latex_research_via_toolchain(self): - # .tex/.qmd aren't counted as docs, so doc_loc≈0; only toolchain_present rescues it. + # .tex isn't counted as docs (.qmd/.rmd now are; .tex stays out), so doc_loc≈0; only + # toolchain_present rescues it. Real-scan proof: + # TestFrameworkInstalledExclusion.test_a_pure_quarto_repo_below_doc_thresholds_still_doc_only_via_toolchain. r = md.detect_doc_only(self.p, files(src=0, docs_loc=0, docs_count=0), {"toolchain_present": True}) self.assertTrue(r["is_doc_only"]) @@ -161,6 +163,62 @@ def test_doc_only_thresholds_are_pinned_not_left_to_drift(self): self.assertEqual(md.DOC_ONLY_DOC_FILES_MIN, 3) +class TestLanguageAndDocExtensionCoverage(unittest.TestCase): + """Two independent gaps in the same three constants, found scanning a real R package: + - `.r` was already in SOURCE_EXTS (R always counted toward Source LOC) but had no LANG_MAP + entry, so it never got its own "Code by Language" row. + - `.qmd`/`.rmd` (Quarto / R Markdown) were in neither SOURCE_EXTS nor DOC_EXTS, so a file with + either extension outside a docs/ path fell through categorize_file's whole ladder to + "other" -- not source, not docs, not even LOC-counted (LOC is skipped entirely for "other"). + """ + + def setUp(self): + self._td = tempfile.TemporaryDirectory() + self.p = Path(self._td.name) + + def tearDown(self): + self._td.cleanup() + + def test_r_source_gets_its_own_language_row(self): + write_tree(self.p, {"analysis.R": "x <- 1\ny <- 2\n"}) + m = md.collect_file_metrics(self.p) + self.assertIn("R", m["by_language"], "an .R file must appear in Code by Language") + self.assertEqual(m["by_language"]["R"]["count"], 1) + self.assertEqual(m["by_category"]["source"]["count"], 1, + "R must still count as Source -- this only adds the missing language row") + + def test_r_extension_is_case_insensitive(self): + # collect_file_metrics lowercases fpath.suffix, so the conventional uppercase ".R" and a + # bare ".r" both resolve to the one LANG_MAP key. + write_tree(self.p, {"a.R": "x <- 1\n", "b.r": "y <- 2\n"}) + m = md.collect_file_metrics(self.p) + self.assertEqual(m["by_language"]["R"]["count"], 2) + + def test_qmd_is_docs_not_other(self): + write_tree(self.p, {"article.qmd": "# Title\n" + "prose\n" * 10}) + m = md.collect_file_metrics(self.p) + self.assertEqual(m["by_category"]["docs"]["count"], 1) + self.assertEqual(m["by_category"]["other"]["count"], 0) + self.assertGreater(m["by_category"]["docs"]["loc"], 0, + "LOC is skipped entirely for \"other\" -- must not silently stay 0") + + def test_rmd_is_docs_not_other(self): + write_tree(self.p, {"vignette.Rmd": "# Title\n" + "prose\n" * 10}) + m = md.collect_file_metrics(self.p) + self.assertEqual(m["by_category"]["docs"]["count"], 1) + self.assertEqual(m["by_category"]["other"]["count"], 0) + self.assertGreater(m["by_category"]["docs"]["loc"], 0) + + def test_qmd_and_rmd_are_not_also_a_language_row(self): + # Precedent: DOC_EXTS's other members (.md, .txt, .rst, .adoc, .org) have no LANG_MAP + # entry either -- "Code by Language" is a programming-language signal, not a doc-format + # one, so a doc extension should never double as both. + write_tree(self.p, {"article.qmd": "# T\nprose\n", "vignette.Rmd": "# T\nprose\n"}) + m = md.collect_file_metrics(self.p) + self.assertNotIn("Quarto", m["by_language"]) + self.assertNotIn("R Markdown", m["by_language"]) + + class TestRenderMetrics(unittest.TestCase): def setUp(self): self._td = tempfile.TemporaryDirectory() @@ -2184,9 +2242,12 @@ def _repo(self, files_map): "commit", "-q", "-m", "init"], check=True) return p - # The synced doc corpus: a Quarto book, whose .qmd files are NOT doc-extensions, so the - # render-toolchain arm of the disjunction is what has to carry it — the exact source_loc≈0 - # research repo BL-5 exists for. + # The synced doc corpus: a Quarto book. .qmd files are now doc-extensions, so this two-chapter + # fixture's own doc_loc (~400) clears DOC_ONLY_DOC_LOC_MIN on its own -- it still proves + # is_doc_only stays True, just no longer in isolation from the corpus arm. See + # QUARTO_MINIMAL + test_a_pure_quarto_repo_below_doc_thresholds_still_doc_only_via_toolchain + # below for the fixture that isolates the render-toolchain arm of the disjunction — the exact + # source_loc≈0 research repo BL-5 exists for. QUARTO = { "_quarto.yml": "project:\n type: book\n", "ch1.qmd": "# Ch1\n" + "prose\n" * 200, @@ -2194,6 +2255,14 @@ def _repo(self, files_map): "README.md": "# Monograph\n\nA Quarto book.\n" * 4, } + # A single short chapter -- doc_loc and doc_files both stay under the corpus-disjunction + # thresholds even with .qmd counted as docs, so is_doc_only can ONLY be reached via + # render.toolchain_present. + QUARTO_MINIMAL = { + "_quarto.yml": "project:\n type: book\n", + "index.qmd": "# Index\none line of prose\n", + } + def test_a_synced_doc_repo_is_still_doc_only(self): """RED: reported False (source_loc 3,070 > the 200 cap) before the exclusion.""" p = self._repo({**self.QUARTO, "methodology_dashboard.py": installed_scanner()}) @@ -2205,6 +2274,22 @@ def test_a_synced_doc_repo_is_still_doc_only(self): self.assertGreater(m["files"]["by_category"]["vendor"]["loc"], 2000, "the excluded LOC must remain visible, not vanish from the inventory") + def test_a_pure_quarto_repo_below_doc_thresholds_still_doc_only_via_toolchain(self): + """Guard-the-guard: adding .qmd to DOC_EXTS must not make the toolchain arm of the corpus + disjunction unreachable through real scanning. QUARTO_MINIMAL's own doc_loc/doc_files are + asserted BELOW both thresholds, so is_doc_only=True here can only be explained by + render.toolchain_present -- the same real-scan proof QUARTO used to carry alone.""" + p = self._repo(dict(self.QUARTO_MINIMAL)) + m = md.collect_all(p) + docs = m["files"]["by_category"]["docs"] + self.assertLess(docs["loc"], md.DOC_ONLY_DOC_LOC_MIN, + "fixture must stay under the doc-LOC threshold to isolate the toolchain arm") + self.assertLess(docs["count"], md.DOC_ONLY_DOC_FILES_MIN, + "fixture must stay under the doc-files threshold to isolate the toolchain arm") + self.assertTrue(m["render"]["toolchain_present"], "_quarto.yml must be detected") + self.assertTrue(m["doc_only"]["is_doc_only"], + "a real Quarto repo too small for the doc-corpus arms must still be caught") + def test_d_no_test_risk_absent_when_synced_doc_repo_present_when_real(self): """RED: the HIGH risk was present on the synced doc repo before the exclusion.""" def risks_of(m): From c4fd8791f25dcb5899c61a25b3179dca5ea96ccd Mon Sep 17 00:00:00 2001 From: "R. Mark Sharp" Date: Tue, 11 Aug 2026 21:30:20 -0500 Subject: [PATCH 2/2] fix(dashboard): address review findings on PR #72 - Add the missing CHANGELOG.md entry -- this PR was the only one of the five open PRs without one, which the .githooks/pre-commit ledger-gate should have caught (confirmed: it was bypassed with --no-verify for this upstream-targeted branch, same precedent as other PRs, but the entry itself was never written). - DASHBOARD_VERSION 2.10.3 -> 2.10.5: 2.10.3 is independently claimed by a sibling PR, 2.10.4 by another, both open the same day. - Pin the classification consequence review found under-described: a bare .Rmd analysis repo (no toolchain marker) flips doc_only False -> True and softens its "No test infrastructure" risk from HIGH to a doc-only advisory. Quarto repos do NOT reclassify (already doc_only via the toolchain-marker fallback; only their reported metrics change) -- confirmed by direct reproduction against both upstream/main (pre-fix: doc_only=False, HIGH risk) and this branch (post-fix: doc_only=True, softened risk) with the identical fixture. New test_rmd_analysis_repo_flips_doc_only_and_softens_the_test_risk, RED-verified against a temporary pre-fix scanner checkout before landing (git stash alone does not RED-verify a change already baked into a prior commit on this branch -- had to swap in the actual pre-fix file content). Verified: python3 -m unittest tools/test_methodology_dashboard.py 204/204 (198 prior + 6 original + 1 new); the 2 failures on this base are pre-existing and fixed by a sibling PR, not this one. bin/check-links OK. Twins byte-identical. Rebased onto upstream/main (clean, no conflict -- this PR had no CHANGELOG.md entry to collide with). Reviewed-by: KJ5HST Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 41 ++++++++++++++++++++++++++++ starter-kit/methodology_dashboard.py | 2 +- tools/methodology_dashboard.py | 2 +- tools/test_methodology_dashboard.py | 37 +++++++++++++++++++++++-- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 750fdb7d..e3a60b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,47 @@ Reverse-chronological, newest on top; prepend-only. Promote to `## YYYY-MM` sect --- +### 2026-08-11 · [ad hoc] `methodology_dashboard.py`'s `LANG_MAP`/`DOC_EXTS` now recognize R, Quarto, and R Markdown + +- **Change:** `tools/methodology_dashboard.py` (+ `starter-kit/` twin, kept byte-identical) and + `tools/test_methodology_dashboard.py`. +- **The defect:** `.r` was already in `SOURCE_EXTS` (R source always counted toward Source LOC), + but had no `LANG_MAP` entry, so it never got its own "Code by Language" row. `.qmd` (Quarto) and + `.rmd` (R Markdown) were in neither `SOURCE_EXTS` nor `DOC_EXTS`, so either extension outside a + `docs/` path fell through `categorize_file`'s whole ladder to `"other"` — not source, not docs, + not even LOC-counted (LOC is skipped entirely for `"other"`). Found scanning a real R package: + 603 `.r` files / 77,773 LOC counted as Source but invisible in "Code by Language". +- **Fix:** `"r": "R"` added to `LANG_MAP`; `.qmd`/`.rmd` added to `DOC_EXTS`. +- **A real, not just cosmetic, classification consequence — found on review, pinned here.** + Quarto's `.qmd` was already a render-toolchain marker (`detect_doc_only`'s fallback arm), so a + Quarto repo was already `doc_only` before this fix; what changed for Quarto is only its + *reported* metrics (a Quarto book previously showed zero documentation and its files as + uncounted `"other"`). Bare `.Rmd` has no toolchain marker at all (`_bookdown.yml` is one; a + plain analysis project has neither that nor `*.qmd`), so adding `.rmd` to `DOC_EXTS` is what + newly clears the corpus disjunction for that class: a real R-Markdown analysis repo (no + toolchain marker, a small `.R` helper alongside several `.Rmd` files) flips `doc_only` + `False → True` and its `"No test infrastructure"` risk softens from `HIGH` to a doc-only + advisory. Believed correct — an R-Markdown analysis project is exactly the population BL-5/v3.2 + exists to score fairly, and the has-tests gate still protects a real R package with a `tests/` + dir — verified directly against the pre-fix scanner (the identical fixture there reads + `doc_only=False` with the `HIGH` risk) and pinned with a new end-to-end regression test, + `test_rmd_analysis_repo_flips_doc_only_and_softens_the_test_risk`, so a future `DOC_EXTS` edit + cannot silently un-flip the population without a test noticing. +- **Also fixed:** the existing Quarto fixture's own render-toolchain-arm isolation. Adding + `.qmd`/`.rmd` to `DOC_EXTS` meant the pre-existing Quarto test could now clear the corpus + disjunction on doc-LOC alone, silently narrowing what it proved (Layer 7's specific + toolchain-arm-in-isolation guarantee). A `QUARTO_MINIMAL` fixture + a dedicated isolation test + restore that proof; the stale in-code comment claiming a pure-Quarto repo's `.qmd` was "never + counted as docs" is corrected to match. +- **Verified:** `python3 -m unittest tools/test_methodology_dashboard.py` 204/204 (198 prior + 6 + from the original fix + 1 classification-regression test), the 2 failures on this base + (`test_every_distributed_adopter_root_file_is_scored_or_exempt`, + `test_exclusion_list_matches_the_manifest`) are pre-existing and unrelated (fixed by a sibling + PR, not this one). `DASHBOARD_VERSION` 2.10.2 → 2.10.5 (2.10.3/2.10.4 independently claimed by + two sibling PRs open the same day). Twins confirmed byte-identical. +- **Distribution:** `starter-kit/methodology_dashboard.py` is `bin/_manifest.py`-TRACKED, so + adopters receive the fix via `bin/sync`; `tools/` and `tools/test_methodology_dashboard.py` are + canonical-only. ### 2026-08-11 · [BL-31] Dashboard's framework-installed exclusion never learned about the context-budget gate PR #66 itself shipped - **Origin:** fork backlog item BL-31 (`docs/planning/BACKLOG.md`, fork `main` only — not yet pushed diff --git a/starter-kit/methodology_dashboard.py b/starter-kit/methodology_dashboard.py index 07a82e32..b2dac12a 100644 --- a/starter-kit/methodology_dashboard.py +++ b/starter-kit/methodology_dashboard.py @@ -82,7 +82,7 @@ # Every other copy (portfolio root + per-project) is a synced copy of the canonical and must # carry the same value. A copy whose DASHBOARD_VERSION is older than the canonical is stale — # re-sync from the canonical. Bump on any change to the canonical script. -DASHBOARD_VERSION = "2.10.4" +DASHBOARD_VERSION = "2.10.5" ROOT = Path(__file__).parent EXCLUDE_DIRS = {"methodology", "BrogueCE-iOS", ".git", "__pycache__", "node_modules", ".venv", "venv"} diff --git a/tools/methodology_dashboard.py b/tools/methodology_dashboard.py index 07a82e32..b2dac12a 100644 --- a/tools/methodology_dashboard.py +++ b/tools/methodology_dashboard.py @@ -82,7 +82,7 @@ # Every other copy (portfolio root + per-project) is a synced copy of the canonical and must # carry the same value. A copy whose DASHBOARD_VERSION is older than the canonical is stale — # re-sync from the canonical. Bump on any change to the canonical script. -DASHBOARD_VERSION = "2.10.4" +DASHBOARD_VERSION = "2.10.5" ROOT = Path(__file__).parent EXCLUDE_DIRS = {"methodology", "BrogueCE-iOS", ".git", "__pycache__", "node_modules", ".venv", "venv"} diff --git a/tools/test_methodology_dashboard.py b/tools/test_methodology_dashboard.py index ca18c49a..d1f00605 100644 --- a/tools/test_methodology_dashboard.py +++ b/tools/test_methodology_dashboard.py @@ -218,6 +218,37 @@ def test_qmd_and_rmd_are_not_also_a_language_row(self): self.assertNotIn("Quarto", m["by_language"]) self.assertNotIn("R Markdown", m["by_language"]) + def test_rmd_analysis_repo_flips_doc_only_and_softens_the_test_risk(self): + """The downstream classification consequence review found this fix under-described. + Quarto's own `.qmd` was already a render-toolchain marker (detect_doc_only's fallback + arm), so a Quarto repo was already doc_only before this fix -- what changed for Quarto + is only its REPORTED metrics. Bare `.Rmd` has no toolchain marker at all (`_bookdown.yml` + is one; a plain analysis project has neither that nor `*.qmd`), so adding `.rmd` to + DOC_EXTS is what newly clears the corpus disjunction for that class and flips + doc_only False -> True -- a real classification change, not a metrics-only one. + + Verified directly against `upstream/main` (pre-fix) before writing this: the identical + fixture there reads doc_only=False with "No test infrastructure" (HIGH) in the risk + list. Believed correct -- an R-Markdown analysis project is exactly the population + BL-5/v3.2 exists to score fairly, and the has-tests gate (test_b_synced_code_repo... + analogue) still protects a real R package with a tests/ dir -- but nothing pinned the + consequence before this test, so a future DOC_EXTS edit could silently un-flip it. + """ + write_tree(self.p, { + "helper.R": "f <- function(x) x + 1\n" * 30, + **{f"analysis{i}.Rmd": "---\ntitle: a\n---\n\n" + "prose text here\n" * 30 + for i in range(5)}, + "README.md": "# Analysis\n", + }) + m = md.collect_all(self.p) + self.assertTrue(m["doc_only"]["is_doc_only"], + "a bare .Rmd analysis project (no toolchain marker) must read doc-only") + self.assertEqual(m["tests"]["source_loc"], 30, + "the helper's own LOC is still counted, just not against the doc-only cap") + self.assertNotIn("No test infrastructure", + [r["description"] for r in m["scores"]["risks"]], + "the HIGH risk must be softened once the repo reads as doc-only") + class TestRenderMetrics(unittest.TestCase): def setUp(self): @@ -2101,10 +2132,10 @@ def test_twins_byte_identical(self): "tools/ and starter-kit/ dashboards must be byte-identical") def test_dashboard_version(self): - self.assertEqual(md.DASHBOARD_VERSION, "2.10.4") + self.assertEqual(md.DASHBOARD_VERSION, "2.10.5") starter_src = Path(STARTER_PY).read_text(encoding="utf-8") - self.assertTrue(re.search(r'^DASHBOARD_VERSION\s*=\s*"2\.10\.4"', starter_src, re.MULTILINE), - "starter-kit twin must also declare DASHBOARD_VERSION 2.10.4") + self.assertTrue(re.search(r'^DASHBOARD_VERSION\s*=\s*"2\.10\.5"', starter_src, re.MULTILINE), + "starter-kit twin must also declare DASHBOARD_VERSION 2.10.5") class TestEndToEnd(unittest.TestCase):