{"record":{"id":"26fb69daeffc1f45","repo":"nextlevelbuilder/ui-ux-pro-max-skill","slug":"catalog-summary-json-is-stale-regenerate-it","errorCode":null,"errorMessage":"catalog-summary.json is stale; regenerate it","messagePattern":"catalog-summary\\.json is stale; regenerate it","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/generate-catalog-summary.py","lineNumber":131,"sourceCode":"\ndef main():\n    parser = argparse.ArgumentParser(description=__doc__)\n    parser.add_argument(\"--verified-at\")\n    parser.add_argument(\"--check\", action=\"store_true\")\n    args = parser.parse_args()\n    try:\n        verified_at = args.verified_at\n        if args.check and not verified_at:\n            if not OUTPUT.exists():\n                raise ValueError(\"catalog-summary.json is missing\")\n            verified_at = json.loads(OUTPUT.read_text(encoding=\"utf-8\")).get(\"verifiedAt\")\n        if not verified_at:\n            raise ValueError(\"--verified-at is required when generating the summary\")\n        summary = build(verified_at)\n        content = json.dumps(summary, ensure_ascii=False, indent=2) + \"\\n\"\n        if args.check:\n            if not OUTPUT.exists() or OUTPUT.read_text(encoding=\"utf-8\") != content:\n                raise ValueError(\"catalog-summary.json is stale; regenerate it\")\n            check_readme_counts(summary)\n        else:\n            OUTPUT.write_text(content, encoding=\"utf-8\")\n    except (KeyError, OSError, ValueError, json.JSONDecodeError) as exc:\n        print(f\"generate-catalog-summary: {exc}\", file=sys.stderr)\n        return 2\n    print(\"Catalog summary is current.\" if args.check else \"Generated catalog summary.\")\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n","sourceCodeStart":113,"sourceCodeEnd":144,"githubUrl":"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5/scripts/generate-catalog-summary.py#L113-L144","documentation":"Raised in --check mode when the on-disk catalog-summary.json does not byte-for-byte match the content regenerated from current data plus the resolved verifiedAt timestamp. The check is a deterministic reproducibility guard: same data + same timestamp must produce identical JSON (including indent=2, ensure_ascii=False, and the trailing newline). Any drift means the committed summary is stale.","triggerScenarios":"`--check` runs after CSV data files changed (counts/rows differ), or catalog-summary.json was regenerated with a different --verified-at than the one stored in its 'verifiedAt' field, or the file was reformatted (different indentation, key order, missing trailing newline, CRLF line endings from a Windows checkout).","commonSituations":"CI staleness check fails after a data PR touched src CSVs without regenerating the summary; a developer regenerated locally with today's date but the committed file carries an older timestamp; an editor or .gitattributes eol setting rewrote line endings.","solutions":["Regenerate with the exact timestamp stored in the existing file: `python3 scripts/generate-catalog-summary.py --verified-at $(python3 -c \"import json;print(json.load(open('catalog-summary.json'))['verifiedAt'])\")`, then commit.","If the data change is intentional and the verification date moved, regenerate with the new --verified-at and commit both files together.","If only whitespace/line endings differ, normalize the file (LF endings, 2-space indent, trailing newline) by regenerating rather than hand-editing.","Check for stray local CSV edits with `git status` before regenerating, so you do not commit a summary built from uncommitted data."],"exampleFix":"# before (CI: catalog-summary.json is stale; regenerate it)\ngit pull  # CSV counts changed upstream\n\n# after\npython3 scripts/generate-catalog-summary.py --verified-at \"$(python3 -c \"import json;print(json.load(open('catalog-summary.json'))['verifiedAt'])\")\"\ngit add catalog-summary.json && git commit -m \"chore: refresh catalog summary\"","handlingStrategy":"validation","validationCode":"# Regenerate into a temp string and diff before overwriting, in a wrapper script:\nimport json, pathlib, subprocess, sys\nstored = pathlib.Path(\"catalog-summary.json\")\nexpected = subprocess.run(\n    [sys.executable, \"scripts/generate-catalog-summary.py\"], capture_output=True\n)  # will fail loudly if flags missing\n# safer: call build() directly and compare, so you see a diff instead of a boolean failure\nimport importlib.util\nspec = importlib.util.spec_from_file_location(\"gcs\", \"scripts/generate-catalog-summary.py\")\nmod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)\ncontent = json.dumps(mod.build(stored_verified_at), ensure_ascii=False, indent=2) + \"\\n\"\nif stored.exists() and stored.read_text(encoding=\"utf-8\") != content:\n    print(\"diff:\", [l for l in zip(stored.read_text().splitlines(), content.splitlines()) if l[0] != l[1]][:5])","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run `generate-catalog-summary.py --check` locally before every commit that touches data CSVs.","Add the --check run to CI so stale summaries block merge instead of surprising you later.","Never hand-format catalog-summary.json; always regenerate so indentation and trailing newline match exactly.","Use LF line endings (.gitattributes: *.json text eol=lf) to avoid false staleness on Windows checkouts."],"tags":["ci","staleness","json","reproducibility"],"backgroundTag":null,"analyzedSha":"a38d04c3d5c298c851dbe5e6ee1965ee3de42cb5","analyzedAt":"2026-08-14T18:51:02.321Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}