)]}'
{
  "commit": "e13eff8d4e757ebe7709a58fce44017b8be5a84d",
  "tree": "32073e11437e057d92ff843290d3a5594c86d528",
  "parents": [
    "f96ddbaa1e11a98f8076df48ba73026191c28399"
  ],
  "author": {
    "name": "hathach",
    "email": "thach@tinyusb.org",
    "time": "Fri Aug 21 12:41:47 2026 +0700"
  },
  "committer": {
    "name": "hathach",
    "email": "thach@tinyusb.org",
    "time": "Fri Aug 21 12:41:47 2026 +0700"
  },
  "message": "ci: fix nine ways the selection under-selected or mismatched\n\nEvery one of these dropped coverage silently - the worst failure mode here,\nbecause the PR still goes green. Found by review, each reproduced first.\n\nSelection rules:\n\n* class_macros derived the config macro from the class DIRECTORY, so a change to\n  src/class/midi/midi2_device.c selected the midi_test examples (which do not\n  compile it) and never examples/device/midi2_device (the only one that enables\n  CFG_TUD_MIDI2, and the only one that does). The file\u0027s own macro is unioned in\n  where it differs - union, never replace: over-selecting costs a build,\n  under-selecting merges a break.\n* the ${FAMILY_MCUS} fallback added for espressif fired on any family whose\n  _family_mcus came back empty, and _cmake_sets is if()-blind and keeps the FIRST\n  definition - so mcx/frdm_mcxn947 answered MCXA15, a token six examples\u0027 skip.txt\n  names, dropping 12 firmware images CMake builds. Limited now to families that\n  never spell set(FAMILY_MCUS ...) at all.\n* lib_examples read only an example\u0027s top-level CMakeLists.txt/Makefile;\n  host/msc_file_explorer_freertos names lib/embedded-cli in src/CMakeLists.txt and\n  survived by luck. The whole example tree is scanned. (SEGGER_RTT and rt-thread\n  still resolve to nothing: all three references sit inside a LOGGER\u003drtt guard no\n  CI build sets - the documented ruling, not a miss.)\n* get_family_boards applied ci_skip_boards/ci_preferred_boards only under\n  GITHUB_ACTIONS/CIRCLECI, so the selector answered differently on a laptop than\n  on a runner; _prune_buildable forces CI semantics. Its one-board pick also\n  abandoned the whole preferred list when entry one could not build the -e set,\n  and asked skip_example without the build\u0027s -D tokens.\n* _config_enables and lib_examples still read with the locale encoding - under\n  LC_ALL\u003dC the selector tracebacked on three tracked tusb_config.h files. The\n  whole selector and its suite run clean there now.\n\nWorkflows:\n\n* the Membrowse Upload step omitted $EX_ARGS, but --one-first now picks the board\n  from the -e set, so it configured a different, empty build dir and uploaded\n  --identical for a board never compiled. It takes $EX_ARGS for the BOARD; the\n  target stays the aggregate, which has no DEPENDS and still records every example.\n* blanking FAM_REGEX reset only build_filtered, leaving the build scoped while\n  code-metrics took the UNSCOPED branch and diffed a 1-family run against the full\n  averaged baseline. All three drop together now, as CircleCI\u0027s fall-open does.\n* CircleCI\u0027s EX_ARGS had no character screen and is used unquoted, and its\n  code-metrics job still exit 1\u0027d on an empty metrics set - which a scoped build\n  makes a legitimate outcome.\n* a `ci-full` PR label now turns the scoping off for one PR. A selector bug\n  under-selects silently, and without a label the only ways back to a full matrix\n  are accidental.\n\nPerformance, since the selector gates every other job: family.cmake texts are read\nonce rather than per changed directory (a 6,000-file dep bump re-read 84 files\n99,892 times) and _scrape_mcu is cached: 2.2s -\u003e 0.29s there, 0.8s -\u003e 0.33s on a\nclass diff.\n\nTests: a drift guard for hw/bsp families absent from ci_set_matrix.family_list\n(they select zero legs now, where they used to ride the full matrix); the rule-4\nport test asserted a SUBSET, which set() satisfies, so it could not fail on the\nempty selection it exists to catch; the GITHUB_ENV guard test counted a SUM of two\nguards. Drops metrics.py\u0027s --only-examples, which nothing called, and applies the\nTOTAL scrub to the by-example branch that skipped it.\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "899cbe24adedf22bb079c3ff366b2ad4b4333c9d",
      "old_mode": 33188,
      "old_path": ".circleci/config2.yml",
      "new_id": "2e69588ae7ae95c6eba056ffe23b8be3930a5fe2",
      "new_mode": 33188,
      "new_path": ".circleci/config2.yml"
    },
    {
      "type": "modify",
      "old_id": "2ee124cb3051f9366c888cf094b7a6cbd939fc88",
      "old_mode": 33188,
      "old_path": ".github/workflows/build.yml",
      "new_id": "39a4e7afddc2c474334e410cd7209230078aba21",
      "new_mode": 33188,
      "new_path": ".github/workflows/build.yml"
    },
    {
      "type": "modify",
      "old_id": "dfbd83ee2c62a2e91a6bd5767c6df6680fdd86dc",
      "old_mode": 33188,
      "old_path": ".github/workflows/build_util.yml",
      "new_id": "52999616d4aefdbdacf2733085a696a5f6aea335",
      "new_mode": 33188,
      "new_path": ".github/workflows/build_util.yml"
    },
    {
      "type": "modify",
      "old_id": "9fa358bee14899c4e97df0e822408fe35d6f67b5",
      "old_mode": 33188,
      "old_path": "docs/superpowers/specs/2026-08-19-ci-build-family-filter-design.md",
      "new_id": "8f77dc50a68ecb9cb154d64534e727f19d4f2bf7",
      "new_mode": 33188,
      "new_path": "docs/superpowers/specs/2026-08-19-ci-build-family-filter-design.md"
    },
    {
      "type": "modify",
      "old_id": "89d03aaaecc7b52715ee8b9ff71ae6bf9dcec351",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_ci_metrics.py",
      "new_id": "6c236e8271bdb3e5920c35d382d6a0852303eea0",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_ci_metrics.py"
    },
    {
      "type": "modify",
      "old_id": "74e5f48e6b36a9f6e4ac913ea3ab567501755a58",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_ci_select.py",
      "new_id": "031e8e28799a30165ca75aefe01a22d09627f1b5",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_ci_select.py"
    },
    {
      "type": "modify",
      "old_id": "e7ca1c8392c38639fef6d16e716ea44afaa743ab",
      "old_mode": 33261,
      "old_path": "tools/build.py",
      "new_id": "eeefca22d8387ed3bc72045bdb00bc9ea9c09c29",
      "new_mode": 33261,
      "new_path": "tools/build.py"
    },
    {
      "type": "modify",
      "old_id": "2af8fd624fee3600e86e059ca2cf5014035cc7a5",
      "old_mode": 33261,
      "old_path": "tools/build_utils.py",
      "new_id": "1eeef026968fda77129be06a0ab21a7400b16b12",
      "new_mode": 33261,
      "new_path": "tools/build_utils.py"
    },
    {
      "type": "modify",
      "old_id": "d253f8c010adacbf94cf6ba2b0edc56a158dff2a",
      "old_mode": 33261,
      "old_path": "tools/ci_select.py",
      "new_id": "cd63899c1469122dc42fe62e845e45ac599aa8ed",
      "new_mode": 33261,
      "new_path": "tools/ci_select.py"
    },
    {
      "type": "modify",
      "old_id": "b97b2b20652a4fdf63af50c618f7d6702d214a53",
      "old_mode": 33188,
      "old_path": "tools/metrics.py",
      "new_id": "27c9959547c3363dc006b03d0e459c9c0764e0c1",
      "new_mode": 33188,
      "new_path": "tools/metrics.py"
    }
  ]
}
