diff --git a/.github/workflows/file-freshness.yml b/.github/workflows/file-freshness.yml index 2859a60..84f9159 100644 --- a/.github/workflows/file-freshness.yml +++ b/.github/workflows/file-freshness.yml @@ -38,7 +38,11 @@ jobs: run: uv sync --frozen --extra dev - name: Generate freshness artifacts - run: uv run --active python scripts/repo_file_freshness.py + run: > + uv run --active python scripts/repo_file_freshness.py + --metric commits + --green-max-commits 5 + --yellow-max-commits 20 - name: Configure git author run: | @@ -47,7 +51,7 @@ jobs: - name: Commit and push (if changed) run: | - git add docs/repo_file_status_report.md file_freshness.json freshness_summary.json freshness_ignore.json + git add docs/repo_file_status_report.md assets/file_freshness.json assets/freshness_summary.json assets/freshness_ignore.json if git diff --staged --quiet; then echo "No freshness changes to commit." exit 0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 92f33e0..c905a52 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,8 @@ repos: hooks: - id: detect-secrets args: ["--baseline", ".secrets.baseline"] - exclude: ^template/.*\.jinja$ + # Jinja templates contain secret-like placeholders; freshness JSON embeds git SHAs. + exclude: ^(template/.*\.jinja|assets/file_freshness\.json)$ - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 diff --git a/assets/file_freshness.json b/assets/file_freshness.json new file mode 100644 index 0000000..165652d --- /dev/null +++ b/assets/file_freshness.json @@ -0,0 +1,1466 @@ +[ + { + "age_days": 0, + "commits_since": 3, + "file": "justfile", + "last_commit": "2026-04-08T19:58:15+02:00", + "last_commit_sha": "1dcdb2f58e93ffa196f3cf8c997a6ddf045afa76", + "status": "green" + }, + { + "age_days": 0, + "commits_since": 3, + "file": "tests/test_template.py", + "last_commit": "2026-04-08T19:58:15+02:00", + "last_commit_sha": "1dcdb2f58e93ffa196f3cf8c997a6ddf045afa76", + "status": "green" + }, + { + "age_days": 0, + "commits_since": 0, + "file": ".gitignore", + "last_commit": "2026-04-08T20:08:20+02:00", + "last_commit_sha": "dc1352d64a38a52246a02cde774bbdf4188cb217", + "status": "green" + }, + { + "age_days": 0, + "commits_since": 19, + "file": ".claude/rules/copier/template-conventions.md", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "scripts/sync_skip_if_exists.py", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/.github/workflows/release.yml.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/docs/{% if include_docs %}ci.md{% endif %}.jinja", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/docs/{% if include_docs %}index.md{% endif %}.jinja", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/justfile.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/src/{{ package_name }}/common/bump_version.py.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/src/{{ package_name }}/common/logging_manager.py.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/src/{{ package_name }}/{% if include_cli %}cli.py{% endif %}.jinja", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/tests/conftest.py.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/tests/{{ package_name }}/test_support.py.jinja", + "last_commit": "2026-04-08T18:35:21+02:00", + "last_commit_sha": "b08135a3b1e6e6266d746902a50d946e5c2abca9", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/{% if include_docs %}mkdocs.yml{% endif %}.jinja", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 19, + "file": "template/{% if include_git_cliff %}cliff.toml{% endif %}.jinja", + "last_commit": "2026-04-08T16:34:44Z", + "last_commit_sha": "375611bbdd1c405ebbcc1bd1dfcc7583e6cb93ca", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 17, + "file": ".github/workflows/file-freshness.yml", + "last_commit": "2026-04-08T17:46:02Z", + "last_commit_sha": "5ee7ed7de2e5d30ba3f71b64fd53f708ea2d47c4", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 16, + "file": "docs/repo_file_status_report.md", + "last_commit": "2026-04-08T17:52:45Z", + "last_commit_sha": "c650881274ee46685f97814934dabe2e04d28558", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 16, + "file": "scripts/repo_file_freshness.py", + "last_commit": "2026-04-08T17:52:45Z", + "last_commit_sha": "c650881274ee46685f97814934dabe2e04d28558", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 16, + "file": "tests/test_repo_file_freshness.py", + "last_commit": "2026-04-08T17:52:45Z", + "last_commit_sha": "c650881274ee46685f97814934dabe2e04d28558", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 12, + "file": "copier.yml", + "last_commit": "2026-04-08T16:55:38Z", + "last_commit_sha": "134674da19840f482d60ebe507c50958a19d3500", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 10, + "file": ".github/labeler.yml", + "last_commit": "2026-04-08T19:10:47+02:00", + "last_commit_sha": "d0f268c5bdab2179b014c99ca7de2bdc7b151606", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": ".claude/hooks/README.md", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": ".claude/hooks/pre-config-protection.sh", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": ".claude/hooks/pre-write-src-test-reminder.sh", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": ".claude/settings.json", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "pyproject.toml", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/.claude/hooks/README.md", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/.claude/hooks/pre-config-protection.sh", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/.claude/hooks/pre-write-src-test-reminder.sh", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/.claude/rules/python/hooks.md", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/.claude/settings.json", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 9, + "file": "template/pyproject.toml.jinja", + "last_commit": "2026-04-08T19:49:04+02:00", + "last_commit_sha": "83cd2e2f6a272636f16bd57f1546a62f7347f9f3", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "temp/justfile", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "temp/pyproject.toml", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/SKILL.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/anti-patterns.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/assertions.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/ci-and-plugins.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/fixtures.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/mocking.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/parametrize-and-markers.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/test-organization.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 0, + "commits_since": 7, + "file": "template/.claude/skills/pytest/references/test-types.md", + "last_commit": "2026-04-08T20:05:38+02:00", + "last_commit_sha": "6d0f7f2e91fbafc7a549a0b3daafaf4b0c3e3417", + "status": "yellow" + }, + { + "age_days": 7, + "commits_since": 103, + "file": ".vscode/extensions.json", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": ".vscode/launch.json", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": ".vscode/settings.json", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "LICENSE", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/.github/CODE_OF_CONDUCT.md.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/.github/PULL_REQUEST_TEMPLATE.md.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/.vscode/extensions.json.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/.vscode/launch.json.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/.vscode/settings.json.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 103, + "file": "template/LICENSE.jinja", + "last_commit": "2026-04-01T16:16:11+02:00", + "last_commit_sha": "f94e6af3e7f3cdc97388c16477d8a311779445b8", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 101, + "file": "template/.github/ISSUE_TEMPLATE/bug_report.md.jinja", + "last_commit": "2026-04-01T18:12:59+02:00", + "last_commit_sha": "5c17c5dab50090fef6a209cd68d661d29fb8da42", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 101, + "file": "template/.github/ISSUE_TEMPLATE/config.yml.jinja", + "last_commit": "2026-04-01T18:12:59+02:00", + "last_commit_sha": "5c17c5dab50090fef6a209cd68d661d29fb8da42", + "status": "red" + }, + { + "age_days": 7, + "commits_since": 101, + "file": "template/.github/ISSUE_TEMPLATE/feature_request.md.jinja", + "last_commit": "2026-04-01T18:12:59+02:00", + "last_commit_sha": "5c17c5dab50090fef6a209cd68d661d29fb8da42", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 100, + "file": ".claude/commands/generate.md", + "last_commit": "2026-04-01T23:49:24+02:00", + "last_commit_sha": "f7ff87d01252787cb3db727287da65d38b761406", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 100, + "file": ".claude/commands/test.md", + "last_commit": "2026-04-01T23:49:24+02:00", + "last_commit_sha": "f7ff87d01252787cb3db727287da65d38b761406", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 99, + "file": "template/tests/{{ package_name }}/test_core.py.jinja", + "last_commit": "2026-04-02T00:35:48+02:00", + "last_commit_sha": "ce0a8e7783e70700b8edbcd4c4cad79caac7c31e", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 98, + "file": ".claude/commands/ci.md", + "last_commit": "2026-04-01T23:54:33Z", + "last_commit_sha": "6efdf417f100941b9c6e488b82acb6d25203ebff", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 77, + "file": "template/.claude/commands/ci.md", + "last_commit": "2026-04-02T20:14:16+02:00", + "last_commit_sha": "1e2f6a6ea22f4e72d9d7104c7cebe804740f363f", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 77, + "file": "template/.claude/commands/generate.md", + "last_commit": "2026-04-02T20:14:16+02:00", + "last_commit_sha": "1e2f6a6ea22f4e72d9d7104c7cebe804740f363f", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 77, + "file": "template/.claude/commands/test.md", + "last_commit": "2026-04-02T20:14:16+02:00", + "last_commit_sha": "1e2f6a6ea22f4e72d9d7104c7cebe804740f363f", + "status": "red" + }, + { + "age_days": 6, + "commits_since": 77, + "file": "template/{{_copier_conf.answers_file}}.jinja", + "last_commit": "2026-04-02T20:14:16+02:00", + "last_commit_sha": "1e2f6a6ea22f4e72d9d7104c7cebe804740f363f", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 71, + "file": "scripts/update_files.sh", + "last_commit": "2026-04-02T22:06:25+02:00", + "last_commit_sha": "a2183edf5e9022f26d410ee148b64da0b973e250", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 71, + "file": "template/.github/CODEOWNERS.jinja", + "last_commit": "2026-04-02T22:06:25+02:00", + "last_commit_sha": "a2183edf5e9022f26d410ee148b64da0b973e250", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 71, + "file": "template/tests/__init__.py.jinja", + "last_commit": "2026-04-02T22:06:25+02:00", + "last_commit_sha": "a2183edf5e9022f26d410ee148b64da0b973e250", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 71, + "file": "template/tests/{{ package_name }}/__init__.py.jinja", + "last_commit": "2026-04-02T22:06:25+02:00", + "last_commit_sha": "a2183edf5e9022f26d410ee148b64da0b973e250", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 70, + "file": "scripts/bump_version.py", + "last_commit": "2026-04-02T19:00:55Z", + "last_commit_sha": "033f2c34e9e83c565b000a9115c827f8b49587f6", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 69, + "file": "template/src/{{ package_name }}/common/__init__.py.jinja", + "last_commit": "2026-04-02T19:04:46Z", + "last_commit_sha": "04438478666a70a4ccd21b1f5228ad4be8e0e89e", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 69, + "file": "template/src/{{ package_name }}/common/decorators.py.jinja", + "last_commit": "2026-04-02T19:04:46Z", + "last_commit_sha": "04438478666a70a4ccd21b1f5228ad4be8e0e89e", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 69, + "file": "template/src/{{ package_name }}/common/file_manager.py.jinja", + "last_commit": "2026-04-02T19:04:46Z", + "last_commit_sha": "04438478666a70a4ccd21b1f5228ad4be8e0e89e", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 62, + "file": "template/src/{{ package_name }}/common/utils.py.jinja", + "last_commit": "2026-04-02T20:19:59Z", + "last_commit_sha": "afc1839bcbf4b05953d5c59e314975c54b5e8cb6", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 54, + "file": "README.md", + "last_commit": "2026-04-03T12:43:10+02:00", + "last_commit_sha": "a5176a3a6a6f77ea581421750fe07cdfb8704947", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/coverage.md", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/docs-check.md", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/release.md", + "last_commit": "2026-04-03T16:55:34+02:00", + "last_commit_sha": "159de120871d253449198344b42a76c7eef8baf4", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/review.md", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/standards.md", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": ".claude/commands/update-claude-md.md", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": "template/.claude/commands/coverage.md.jinja", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": "template/.claude/commands/docs-check.md.jinja", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": "template/.claude/commands/release.md.jinja", + "last_commit": "2026-04-03T16:55:34+02:00", + "last_commit_sha": "159de120871d253449198344b42a76c7eef8baf4", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": "template/.claude/commands/review.md.jinja", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 52, + "file": "template/.claude/commands/standards.md.jinja", + "last_commit": "2026-04-03T16:37:11+02:00", + "last_commit_sha": "a53a5c638b09732a7d3b9babd61eaf5535a1a6c1", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 51, + "file": ".claude/commands/dependency-check.md", + "last_commit": "2026-04-03T17:52:15+02:00", + "last_commit_sha": "fff01e48c4d8b222ce3c35be5dcb633171a3ba93", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 51, + "file": ".claude/commands/validate-release.md", + "last_commit": "2026-04-03T17:52:15+02:00", + "last_commit_sha": "fff01e48c4d8b222ce3c35be5dcb633171a3ba93", + "status": "red" + }, + { + "age_days": 5, + "commits_since": 51, + "file": "template/.claude/commands/guided-template-update.md.jinja", + "last_commit": "2026-04-03T17:52:15+02:00", + "last_commit_sha": "fff01e48c4d8b222ce3c35be5dcb633171a3ba93", + "status": "red" + }, + { + "age_days": 4, + "commits_since": 50, + "file": "template/src/{{ package_name }}/core.py.jinja", + "last_commit": "2026-04-04T00:09:05+02:00", + "last_commit_sha": "cd5e96f84b194696e521e5460f06e7d7083657f8", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 40, + "file": ".github/dependabot.yml", + "last_commit": "2026-04-05T15:07:34+02:00", + "last_commit_sha": "a7c06b5409aefeb5dfbc76473746cd1bc5e999c1", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": ".github/workflows/dependency-review.yml", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": ".github/workflows/lint.yml", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": ".github/workflows/release.yml", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": ".github/workflows/stale.yml", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": "template/.github/workflows/ci.yml.jinja", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": "template/.github/workflows/dependency-review.yml.jinja", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": "template/.github/workflows/docs.yml.jinja", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": "template/.github/workflows/lint.yml.jinja", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 3, + "commits_since": 39, + "file": "template/.github/workflows/pre-commit-update.yml.jinja", + "last_commit": "2026-04-05T18:30:54+02:00", + "last_commit_sha": "4fb273b770cef7ff03c78d3dc22e50066d03a54e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-bash-pr-created.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-edit-copier-migration.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-edit-jinja.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-edit-markdown.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-edit-python.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/post-edit-template-mirror.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-bash-block-no-verify.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-bash-commit-quality.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-bash-git-push-reminder.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-compact-save-state.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-protect-uv-lock.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-suggest-compact.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-write-doc-file-warning.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/pre-write-jinja-syntax.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/session-start-bootstrap.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/stop-cost-tracker.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/stop-desktop-notify.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/stop-evaluate-session.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/hooks/stop-session-end.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/README.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/bash/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/bash/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/code-review.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/development-workflow.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/git-workflow.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/common/testing.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/jinja/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/jinja/testing.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/markdown/conventions.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/python/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/python/hooks.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/python/patterns.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/python/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/python/testing.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": ".claude/rules/yaml/conventions.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "CLAUDE.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/post-edit-markdown.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/post-edit-python.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/pre-bash-block-no-verify.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/pre-bash-commit-quality.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/pre-bash-git-push-reminder.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/hooks/pre-protect-uv-lock.sh", + "last_commit": "2026-04-06T12:48:12+02:00", + "last_commit_sha": "f8e2999109d6a6803c25de0779004247f5e0677e", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/README.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/bash/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/bash/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/code-review.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/coding-style.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/development-workflow.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/git-workflow.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/common/testing.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/markdown/conventions.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/python/coding-style.md.jinja", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/python/patterns.md.jinja", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/python/security.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 38, + "file": "template/.claude/rules/python/testing.md", + "last_commit": "2026-04-06T10:56:27Z", + "last_commit_sha": "cd27f9ccd509495d408dc8d95dc633d40488e99c", + "status": "red" + }, + { + "age_days": 2, + "commits_since": 32, + "file": "template/.github/workflows/security.yml.jinja", + "last_commit": "2026-04-06T17:19:49+02:00", + "last_commit_sha": "60c84b65ba676c5bc241f50f090530f18453a419", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": ".github/renovate.json", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": ".pre-commit-config.yaml", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": ".secrets.baseline", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/.github/renovate.json.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/.pre-commit-config.yaml.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/.secrets.baseline", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/CLAUDE.md.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/CONTRIBUTING.md.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/SECURITY.md.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/src/{{ package_name }}/__init__.py.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 31, + "file": "template/tests/test_imports.py.jinja", + "last_commit": "2026-04-07T13:26:04+02:00", + "last_commit_sha": "94b645b111f6a054d82eba17e44aaec3ec86bcac", + "status": "red" + }, + { + "age_days": 1, + "commits_since": 29, + "file": "template/README.md.jinja", + "last_commit": "2026-04-07T13:37:48+02:00", + "last_commit_sha": "c0b84317a9d442b31ce628c6ca2d58d0e14d3f7a", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 27, + "file": "env.example", + "last_commit": "2026-04-08T08:22:29+02:00", + "last_commit_sha": "6aa1e567a2d473cafaec1c46299258251684c8a6", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 27, + "file": "template/.gitignore.jinja", + "last_commit": "2026-04-08T08:22:29+02:00", + "last_commit_sha": "6aa1e567a2d473cafaec1c46299258251684c8a6", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 27, + "file": "template/env.example.jinja", + "last_commit": "2026-04-08T08:22:29+02:00", + "last_commit_sha": "6aa1e567a2d473cafaec1c46299258251684c8a6", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": ".github/workflows/labeler.yml", + "last_commit": "2026-04-08T10:27:54Z", + "last_commit_sha": "153d40916dd0b4e6c8c038597032bffa291ede2d", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": ".github/workflows/pre-commit-update.yml", + "last_commit": "2026-04-08T10:27:54Z", + "last_commit_sha": "153d40916dd0b4e6c8c038597032bffa291ede2d", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": ".github/workflows/security.yml", + "last_commit": "2026-04-08T10:27:54Z", + "last_commit_sha": "153d40916dd0b4e6c8c038597032bffa291ede2d", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": ".github/workflows/sync-skip-if-exists.yml", + "last_commit": "2026-04-08T10:27:54Z", + "last_commit_sha": "153d40916dd0b4e6c8c038597032bffa291ede2d", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": ".github/workflows/tests.yml", + "last_commit": "2026-04-08T10:27:54Z", + "last_commit_sha": "153d40916dd0b4e6c8c038597032bffa291ede2d", + "status": "red" + }, + { + "age_days": 0, + "commits_since": 26, + "file": "uv.lock", + "last_commit": "2026-04-08T10:27:45Z", + "last_commit_sha": "e7e423e5eecad68ea41d9d1d0a2e7b7dc072f98e", + "status": "red" + }, + { + "age_days": null, + "commits_since": null, + "file": "assets/freshness_ignore.json", + "last_commit": null, + "last_commit_sha": null, + "status": "red" + }, + { + "age_days": null, + "commits_since": null, + "file": "assets/file_freshness.json", + "last_commit": null, + "last_commit_sha": null, + "status": "blue" + }, + { + "age_days": null, + "commits_since": null, + "file": "assets/freshness_summary.json", + "last_commit": null, + "last_commit_sha": null, + "status": "blue" + } +] diff --git a/assets/freshness_ignore.json b/assets/freshness_ignore.json new file mode 100644 index 0000000..4986e6e --- /dev/null +++ b/assets/freshness_ignore.json @@ -0,0 +1,9 @@ +{ + "files": [ + "assets/file_freshness.json", + "assets/freshness_summary.json" + ], + "directories": [], + "extensions": [], + "patterns": [] +} diff --git a/assets/freshness_summary.json b/assets/freshness_summary.json new file mode 100644 index 0000000..ca15f64 --- /dev/null +++ b/assets/freshness_summary.json @@ -0,0 +1,10 @@ +{ + "blue": 2, + "color": "red", + "green": 3, + "label": "freshness", + "message": "137 stale", + "metric": "commits", + "red": 137, + "yellow": 41 +} diff --git a/docs/repo_file_status_report.md b/docs/repo_file_status_report.md index abd34e8..c3bec26 100644 --- a/docs/repo_file_status_report.md +++ b/docs/repo_file_status_report.md @@ -1,196 +1,207 @@ # Repository File Status Report -Last updated: **2026-04-08 17:52:36 UTC** +Last updated: **2026-04-08 18:28:20 UTC** + +_Metric: **commits**._ ## Summary -- 🟢 Green: **170** -- 🟡 Yellow: **0** -- 🔴 Red: **0** -- 🔵 Blue: **0** +- 🟢 Green: **3** +- 🟡 Yellow: **41** +- 🔴 Red: **137** +- 🔵 Blue: **2** ## 🟢 Green (recent) -- `.vscode/extensions.json` — **7** days -- `.vscode/launch.json` — **7** days -- `.vscode/settings.json` — **7** days -- `LICENSE` — **7** days -- `template/.github/CODE_OF_CONDUCT.md.jinja` — **7** days -- `template/.github/ISSUE_TEMPLATE/bug_report.md.jinja` — **7** days -- `template/.github/ISSUE_TEMPLATE/config.yml.jinja` — **7** days -- `template/.github/ISSUE_TEMPLATE/feature_request.md.jinja` — **7** days -- `template/.github/PULL_REQUEST_TEMPLATE.md.jinja` — **7** days -- `template/.vscode/extensions.json.jinja` — **7** days -- `template/.vscode/launch.json.jinja` — **7** days -- `template/.vscode/settings.json.jinja` — **7** days -- `template/LICENSE.jinja` — **7** days -- `.claude/commands/ci.md` — **6** days -- `.claude/commands/generate.md` — **6** days -- `.claude/commands/test.md` — **6** days -- `template/tests/{{ package_name }}/test_core.py.jinja` — **6** days -- `.claude/commands/coverage.md` — **5** days -- `.claude/commands/dependency-check.md` — **5** days -- `.claude/commands/docs-check.md` — **5** days -- `.claude/commands/release.md` — **5** days -- `.claude/commands/review.md` — **5** days -- `.claude/commands/standards.md` — **5** days -- `.claude/commands/update-claude-md.md` — **5** days -- `.claude/commands/validate-release.md` — **5** days -- `README.md` — **5** days -- `scripts/bump_version.py` — **5** days -- `scripts/update_files.sh` — **5** days -- `template/.claude/commands/ci.md` — **5** days -- `template/.claude/commands/coverage.md.jinja` — **5** days -- `template/.claude/commands/docs-check.md.jinja` — **5** days -- `template/.claude/commands/generate.md` — **5** days -- `template/.claude/commands/guided-template-update.md.jinja` — **5** days -- `template/.claude/commands/release.md.jinja` — **5** days -- `template/.claude/commands/review.md.jinja` — **5** days -- `template/.claude/commands/standards.md.jinja` — **5** days -- `template/.claude/commands/test.md` — **5** days -- `template/.github/CODEOWNERS.jinja` — **5** days -- `template/src/{{ package_name }}/common/__init__.py.jinja` — **5** days -- `template/src/{{ package_name }}/common/decorators.py.jinja` — **5** days -- `template/src/{{ package_name }}/common/file_manager.py.jinja` — **5** days -- `template/src/{{ package_name }}/common/utils.py.jinja` — **5** days -- `template/tests/__init__.py.jinja` — **5** days -- `template/tests/{{ package_name }}/__init__.py.jinja` — **5** days -- `template/{{_copier_conf.answers_file}}.jinja` — **5** days -- `template/src/{{ package_name }}/core.py.jinja` — **4** days -- `.github/dependabot.yml` — **3** days -- `.github/workflows/dependency-review.yml` — **3** days -- `.github/workflows/lint.yml` — **3** days -- `.github/workflows/release.yml` — **3** days -- `.github/workflows/stale.yml` — **3** days -- `template/.github/workflows/ci.yml.jinja` — **3** days -- `template/.github/workflows/dependency-review.yml.jinja` — **3** days -- `template/.github/workflows/docs.yml.jinja` — **3** days -- `template/.github/workflows/lint.yml.jinja` — **3** days -- `template/.github/workflows/pre-commit-update.yml.jinja` — **3** days -- `.claude/hooks/README.md` — **2** days -- `.claude/hooks/post-bash-pr-created.sh` — **2** days -- `.claude/hooks/post-edit-copier-migration.sh` — **2** days -- `.claude/hooks/post-edit-jinja.sh` — **2** days -- `.claude/hooks/post-edit-markdown.sh` — **2** days -- `.claude/hooks/post-edit-python.sh` — **2** days -- `.claude/hooks/post-edit-template-mirror.sh` — **2** days -- `.claude/hooks/pre-bash-block-no-verify.sh` — **2** days -- `.claude/hooks/pre-bash-commit-quality.sh` — **2** days -- `.claude/hooks/pre-bash-git-push-reminder.sh` — **2** days -- `.claude/hooks/pre-compact-save-state.sh` — **2** days -- `.claude/hooks/pre-config-protection.sh` — **2** days -- `.claude/hooks/pre-protect-uv-lock.sh` — **2** days -- `.claude/hooks/pre-suggest-compact.sh` — **2** days -- `.claude/hooks/pre-write-doc-file-warning.sh` — **2** days -- `.claude/hooks/pre-write-jinja-syntax.sh` — **2** days -- `.claude/hooks/session-start-bootstrap.sh` — **2** days -- `.claude/hooks/stop-cost-tracker.sh` — **2** days -- `.claude/hooks/stop-desktop-notify.sh` — **2** days -- `.claude/hooks/stop-evaluate-session.sh` — **2** days -- `.claude/hooks/stop-session-end.sh` — **2** days -- `.claude/rules/README.md` — **2** days -- `.claude/rules/bash/coding-style.md` — **2** days -- `.claude/rules/bash/security.md` — **2** days -- `.claude/rules/common/code-review.md` — **2** days -- `.claude/rules/common/coding-style.md` — **2** days -- `.claude/rules/common/development-workflow.md` — **2** days -- `.claude/rules/common/git-workflow.md` — **2** days -- `.claude/rules/common/security.md` — **2** days -- `.claude/rules/common/testing.md` — **2** days -- `.claude/rules/jinja/coding-style.md` — **2** days -- `.claude/rules/jinja/testing.md` — **2** days -- `.claude/rules/markdown/conventions.md` — **2** days -- `.claude/rules/python/coding-style.md` — **2** days -- `.claude/rules/python/hooks.md` — **2** days -- `.claude/rules/python/patterns.md` — **2** days -- `.claude/rules/python/security.md` — **2** days -- `.claude/rules/python/testing.md` — **2** days -- `.claude/rules/yaml/conventions.md` — **2** days -- `.claude/settings.json` — **2** days -- `CLAUDE.md` — **2** days -- `template/.claude/hooks/README.md` — **2** days -- `template/.claude/hooks/post-edit-markdown.sh` — **2** days -- `template/.claude/hooks/post-edit-python.sh` — **2** days -- `template/.claude/hooks/pre-bash-block-no-verify.sh` — **2** days -- `template/.claude/hooks/pre-bash-commit-quality.sh` — **2** days -- `template/.claude/hooks/pre-bash-git-push-reminder.sh` — **2** days -- `template/.claude/hooks/pre-config-protection.sh` — **2** days -- `template/.claude/hooks/pre-protect-uv-lock.sh` — **2** days -- `template/.claude/rules/README.md` — **2** days -- `template/.claude/rules/bash/coding-style.md` — **2** days -- `template/.claude/rules/bash/security.md` — **2** days -- `template/.claude/rules/common/code-review.md` — **2** days -- `template/.claude/rules/common/coding-style.md` — **2** days -- `template/.claude/rules/common/development-workflow.md` — **2** days -- `template/.claude/rules/common/git-workflow.md` — **2** days -- `template/.claude/rules/common/security.md` — **2** days -- `template/.claude/rules/common/testing.md` — **2** days -- `template/.claude/rules/markdown/conventions.md` — **2** days -- `template/.claude/rules/python/coding-style.md.jinja` — **2** days -- `template/.claude/rules/python/hooks.md` — **2** days -- `template/.claude/rules/python/patterns.md.jinja` — **2** days -- `template/.claude/rules/python/security.md` — **2** days -- `template/.claude/rules/python/testing.md` — **2** days -- `template/.claude/settings.json` — **2** days -- `template/.github/workflows/security.yml.jinja` — **2** days -- `.github/labeler.yml` — **1** days -- `.github/renovate.json` — **1** days -- `.pre-commit-config.yaml` — **1** days -- `.secrets.baseline` — **1** days -- `template/.github/renovate.json.jinja` — **1** days -- `template/.pre-commit-config.yaml.jinja` — **1** days -- `template/.secrets.baseline` — **1** days -- `template/CLAUDE.md.jinja` — **1** days -- `template/CONTRIBUTING.md.jinja` — **1** days -- `template/README.md.jinja` — **1** days -- `template/SECURITY.md.jinja` — **1** days -- `template/cliff.toml.jinja` — **1** days -- `template/docs/ci.md.jinja` — **1** days -- `template/docs/index.md.jinja` — **1** days -- `template/mkdocs.yml.jinja` — **1** days -- `template/src/{{ package_name }}/__init__.py.jinja` — **1** days -- `template/src/{{ package_name }}/cli.py.jinja` — **1** days -- `template/tests/test_imports.py.jinja` — **1** days -- `.claude/rules/copier/template-conventions.md` — **0** days -- `.github/workflows/file-freshness.yml` — **0** days -- `.github/workflows/labeler.yml` — **0** days -- `.github/workflows/pre-commit-update.yml` — **0** days -- `.github/workflows/security.yml` — **0** days -- `.github/workflows/sync-skip-if-exists.yml` — **0** days -- `.github/workflows/tests.yml` — **0** days -- `.gitignore` — **0** days -- `copier.yml` — **0** days -- `docs/repo_file_status_report.md` — **0** days -- `env.example` — **0** days -- `file_freshness.json` — **0** days -- `freshness_ignore.json` — **0** days -- `freshness_summary.json` — **0** days -- `justfile` — **0** days -- `pyproject.toml` — **0** days -- `scripts/repo_file_freshness.py` — **0** days -- `scripts/sync_skip_if_exists.py` — **0** days -- `template/.github/workflows/release.yml.jinja` — **0** days -- `template/.gitignore.jinja` — **0** days -- `template/env.example.jinja` — **0** days -- `template/justfile.jinja` — **0** days -- `template/pyproject.toml.jinja` — **0** days -- `template/src/{{ package_name }}/common/bump_version.py.jinja` — **0** days -- `template/src/{{ package_name }}/common/logging_manager.py.jinja` — **0** days -- `template/tests/conftest.py.jinja` — **0** days -- `template/tests/{{ package_name }}/test_support.py.jinja` — **0** days -- `tests/test_repo_file_freshness.py` — **0** days -- `tests/test_template.py` — **0** days -- `uv.lock` — **0** days +- `justfile` — **3** commits since last change +- `tests/test_template.py` — **3** commits since last change +- `.gitignore` — **0** commits since last change ## 🟡 Yellow (moderate) -_None._ +- `.claude/rules/copier/template-conventions.md` — **19** commits since last change +- `scripts/sync_skip_if_exists.py` — **19** commits since last change +- `template/.github/workflows/release.yml.jinja` — **19** commits since last change +- `template/docs/{% if include_docs %}ci.md{% endif %}.jinja` — **19** commits since last change +- `template/docs/{% if include_docs %}index.md{% endif %}.jinja` — **19** commits since last change +- `template/justfile.jinja` — **19** commits since last change +- `template/src/{{ package_name }}/common/bump_version.py.jinja` — **19** commits since last change +- `template/src/{{ package_name }}/common/logging_manager.py.jinja` — **19** commits since last change +- `template/src/{{ package_name }}/{% if include_cli %}cli.py{% endif %}.jinja` — **19** commits since last change +- `template/tests/conftest.py.jinja` — **19** commits since last change +- `template/tests/{{ package_name }}/test_support.py.jinja` — **19** commits since last change +- `template/{% if include_docs %}mkdocs.yml{% endif %}.jinja` — **19** commits since last change +- `template/{% if include_git_cliff %}cliff.toml{% endif %}.jinja` — **19** commits since last change +- `.github/workflows/file-freshness.yml` — **17** commits since last change +- `docs/repo_file_status_report.md` — **16** commits since last change +- `scripts/repo_file_freshness.py` — **16** commits since last change +- `tests/test_repo_file_freshness.py` — **16** commits since last change +- `copier.yml` — **12** commits since last change +- `.github/labeler.yml` — **10** commits since last change +- `.claude/hooks/README.md` — **9** commits since last change +- `.claude/hooks/pre-config-protection.sh` — **9** commits since last change +- `.claude/hooks/pre-write-src-test-reminder.sh` — **9** commits since last change +- `.claude/settings.json` — **9** commits since last change +- `pyproject.toml` — **9** commits since last change +- `template/.claude/hooks/README.md` — **9** commits since last change +- `template/.claude/hooks/pre-config-protection.sh` — **9** commits since last change +- `template/.claude/hooks/pre-write-src-test-reminder.sh` — **9** commits since last change +- `template/.claude/rules/python/hooks.md` — **9** commits since last change +- `template/.claude/settings.json` — **9** commits since last change +- `template/pyproject.toml.jinja` — **9** commits since last change +- `temp/justfile` — **7** commits since last change +- `temp/pyproject.toml` — **7** commits since last change +- `template/.claude/skills/pytest/SKILL.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/anti-patterns.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/assertions.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/ci-and-plugins.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/fixtures.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/mocking.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/parametrize-and-markers.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/test-organization.md` — **7** commits since last change +- `template/.claude/skills/pytest/references/test-types.md` — **7** commits since last change ## 🔴 Red (stale) -_None._ +- `.vscode/extensions.json` — **103** commits since last change +- `.vscode/launch.json` — **103** commits since last change +- `.vscode/settings.json` — **103** commits since last change +- `LICENSE` — **103** commits since last change +- `template/.github/CODE_OF_CONDUCT.md.jinja` — **103** commits since last change +- `template/.github/PULL_REQUEST_TEMPLATE.md.jinja` — **103** commits since last change +- `template/.vscode/extensions.json.jinja` — **103** commits since last change +- `template/.vscode/launch.json.jinja` — **103** commits since last change +- `template/.vscode/settings.json.jinja` — **103** commits since last change +- `template/LICENSE.jinja` — **103** commits since last change +- `template/.github/ISSUE_TEMPLATE/bug_report.md.jinja` — **101** commits since last change +- `template/.github/ISSUE_TEMPLATE/config.yml.jinja` — **101** commits since last change +- `template/.github/ISSUE_TEMPLATE/feature_request.md.jinja` — **101** commits since last change +- `.claude/commands/generate.md` — **100** commits since last change +- `.claude/commands/test.md` — **100** commits since last change +- `template/tests/{{ package_name }}/test_core.py.jinja` — **99** commits since last change +- `.claude/commands/ci.md` — **98** commits since last change +- `template/.claude/commands/ci.md` — **77** commits since last change +- `template/.claude/commands/generate.md` — **77** commits since last change +- `template/.claude/commands/test.md` — **77** commits since last change +- `template/{{_copier_conf.answers_file}}.jinja` — **77** commits since last change +- `scripts/update_files.sh` — **71** commits since last change +- `template/.github/CODEOWNERS.jinja` — **71** commits since last change +- `template/tests/__init__.py.jinja` — **71** commits since last change +- `template/tests/{{ package_name }}/__init__.py.jinja` — **71** commits since last change +- `scripts/bump_version.py` — **70** commits since last change +- `template/src/{{ package_name }}/common/__init__.py.jinja` — **69** commits since last change +- `template/src/{{ package_name }}/common/decorators.py.jinja` — **69** commits since last change +- `template/src/{{ package_name }}/common/file_manager.py.jinja` — **69** commits since last change +- `template/src/{{ package_name }}/common/utils.py.jinja` — **62** commits since last change +- `README.md` — **54** commits since last change +- `.claude/commands/coverage.md` — **52** commits since last change +- `.claude/commands/docs-check.md` — **52** commits since last change +- `.claude/commands/release.md` — **52** commits since last change +- `.claude/commands/review.md` — **52** commits since last change +- `.claude/commands/standards.md` — **52** commits since last change +- `.claude/commands/update-claude-md.md` — **52** commits since last change +- `template/.claude/commands/coverage.md.jinja` — **52** commits since last change +- `template/.claude/commands/docs-check.md.jinja` — **52** commits since last change +- `template/.claude/commands/release.md.jinja` — **52** commits since last change +- `template/.claude/commands/review.md.jinja` — **52** commits since last change +- `template/.claude/commands/standards.md.jinja` — **52** commits since last change +- `.claude/commands/dependency-check.md` — **51** commits since last change +- `.claude/commands/validate-release.md` — **51** commits since last change +- `template/.claude/commands/guided-template-update.md.jinja` — **51** commits since last change +- `template/src/{{ package_name }}/core.py.jinja` — **50** commits since last change +- `.github/dependabot.yml` — **40** commits since last change +- `.github/workflows/dependency-review.yml` — **39** commits since last change +- `.github/workflows/lint.yml` — **39** commits since last change +- `.github/workflows/release.yml` — **39** commits since last change +- `.github/workflows/stale.yml` — **39** commits since last change +- `template/.github/workflows/ci.yml.jinja` — **39** commits since last change +- `template/.github/workflows/dependency-review.yml.jinja` — **39** commits since last change +- `template/.github/workflows/docs.yml.jinja` — **39** commits since last change +- `template/.github/workflows/lint.yml.jinja` — **39** commits since last change +- `template/.github/workflows/pre-commit-update.yml.jinja` — **39** commits since last change +- `.claude/hooks/post-bash-pr-created.sh` — **38** commits since last change +- `.claude/hooks/post-edit-copier-migration.sh` — **38** commits since last change +- `.claude/hooks/post-edit-jinja.sh` — **38** commits since last change +- `.claude/hooks/post-edit-markdown.sh` — **38** commits since last change +- `.claude/hooks/post-edit-python.sh` — **38** commits since last change +- `.claude/hooks/post-edit-template-mirror.sh` — **38** commits since last change +- `.claude/hooks/pre-bash-block-no-verify.sh` — **38** commits since last change +- `.claude/hooks/pre-bash-commit-quality.sh` — **38** commits since last change +- `.claude/hooks/pre-bash-git-push-reminder.sh` — **38** commits since last change +- `.claude/hooks/pre-compact-save-state.sh` — **38** commits since last change +- `.claude/hooks/pre-protect-uv-lock.sh` — **38** commits since last change +- `.claude/hooks/pre-suggest-compact.sh` — **38** commits since last change +- `.claude/hooks/pre-write-doc-file-warning.sh` — **38** commits since last change +- `.claude/hooks/pre-write-jinja-syntax.sh` — **38** commits since last change +- `.claude/hooks/session-start-bootstrap.sh` — **38** commits since last change +- `.claude/hooks/stop-cost-tracker.sh` — **38** commits since last change +- `.claude/hooks/stop-desktop-notify.sh` — **38** commits since last change +- `.claude/hooks/stop-evaluate-session.sh` — **38** commits since last change +- `.claude/hooks/stop-session-end.sh` — **38** commits since last change +- `.claude/rules/README.md` — **38** commits since last change +- `.claude/rules/bash/coding-style.md` — **38** commits since last change +- `.claude/rules/bash/security.md` — **38** commits since last change +- `.claude/rules/common/code-review.md` — **38** commits since last change +- `.claude/rules/common/coding-style.md` — **38** commits since last change +- `.claude/rules/common/development-workflow.md` — **38** commits since last change +- `.claude/rules/common/git-workflow.md` — **38** commits since last change +- `.claude/rules/common/security.md` — **38** commits since last change +- `.claude/rules/common/testing.md` — **38** commits since last change +- `.claude/rules/jinja/coding-style.md` — **38** commits since last change +- `.claude/rules/jinja/testing.md` — **38** commits since last change +- `.claude/rules/markdown/conventions.md` — **38** commits since last change +- `.claude/rules/python/coding-style.md` — **38** commits since last change +- `.claude/rules/python/hooks.md` — **38** commits since last change +- `.claude/rules/python/patterns.md` — **38** commits since last change +- `.claude/rules/python/security.md` — **38** commits since last change +- `.claude/rules/python/testing.md` — **38** commits since last change +- `.claude/rules/yaml/conventions.md` — **38** commits since last change +- `CLAUDE.md` — **38** commits since last change +- `template/.claude/hooks/post-edit-markdown.sh` — **38** commits since last change +- `template/.claude/hooks/post-edit-python.sh` — **38** commits since last change +- `template/.claude/hooks/pre-bash-block-no-verify.sh` — **38** commits since last change +- `template/.claude/hooks/pre-bash-commit-quality.sh` — **38** commits since last change +- `template/.claude/hooks/pre-bash-git-push-reminder.sh` — **38** commits since last change +- `template/.claude/hooks/pre-protect-uv-lock.sh` — **38** commits since last change +- `template/.claude/rules/README.md` — **38** commits since last change +- `template/.claude/rules/bash/coding-style.md` — **38** commits since last change +- `template/.claude/rules/bash/security.md` — **38** commits since last change +- `template/.claude/rules/common/code-review.md` — **38** commits since last change +- `template/.claude/rules/common/coding-style.md` — **38** commits since last change +- `template/.claude/rules/common/development-workflow.md` — **38** commits since last change +- `template/.claude/rules/common/git-workflow.md` — **38** commits since last change +- `template/.claude/rules/common/security.md` — **38** commits since last change +- `template/.claude/rules/common/testing.md` — **38** commits since last change +- `template/.claude/rules/markdown/conventions.md` — **38** commits since last change +- `template/.claude/rules/python/coding-style.md.jinja` — **38** commits since last change +- `template/.claude/rules/python/patterns.md.jinja` — **38** commits since last change +- `template/.claude/rules/python/security.md` — **38** commits since last change +- `template/.claude/rules/python/testing.md` — **38** commits since last change +- `template/.github/workflows/security.yml.jinja` — **32** commits since last change +- `.github/renovate.json` — **31** commits since last change +- `.pre-commit-config.yaml` — **31** commits since last change +- `.secrets.baseline` — **31** commits since last change +- `template/.github/renovate.json.jinja` — **31** commits since last change +- `template/.pre-commit-config.yaml.jinja` — **31** commits since last change +- `template/.secrets.baseline` — **31** commits since last change +- `template/CLAUDE.md.jinja` — **31** commits since last change +- `template/CONTRIBUTING.md.jinja` — **31** commits since last change +- `template/SECURITY.md.jinja` — **31** commits since last change +- `template/src/{{ package_name }}/__init__.py.jinja` — **31** commits since last change +- `template/tests/test_imports.py.jinja` — **31** commits since last change +- `template/README.md.jinja` — **29** commits since last change +- `env.example` — **27** commits since last change +- `template/.gitignore.jinja` — **27** commits since last change +- `template/env.example.jinja` — **27** commits since last change +- `.github/workflows/labeler.yml` — **26** commits since last change +- `.github/workflows/pre-commit-update.yml` — **26** commits since last change +- `.github/workflows/security.yml` — **26** commits since last change +- `.github/workflows/sync-skip-if-exists.yml` — **26** commits since last change +- `.github/workflows/tests.yml` — **26** commits since last change +- `uv.lock` — **26** commits since last change +- `assets/freshness_ignore.json` — _unknown commit depth_ ## 🔵 Blue (ignored) -_None._ - +- `assets/file_freshness.json` +- `assets/freshness_summary.json` diff --git a/file_freshness.json b/file_freshness.json deleted file mode 100644 index 8b71fd8..0000000 --- a/file_freshness.json +++ /dev/null @@ -1,1022 +0,0 @@ -[ - { - "age_days": 7, - "file": ".vscode/extensions.json", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": ".vscode/launch.json", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": ".vscode/settings.json", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "LICENSE", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.github/CODE_OF_CONDUCT.md.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.github/ISSUE_TEMPLATE/bug_report.md.jinja", - "last_commit": "2026-04-01T18:12:59+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.github/ISSUE_TEMPLATE/config.yml.jinja", - "last_commit": "2026-04-01T18:12:59+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.github/ISSUE_TEMPLATE/feature_request.md.jinja", - "last_commit": "2026-04-01T18:12:59+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.github/PULL_REQUEST_TEMPLATE.md.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.vscode/extensions.json.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.vscode/launch.json.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/.vscode/settings.json.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 7, - "file": "template/LICENSE.jinja", - "last_commit": "2026-04-01T16:16:11+02:00", - "status": "green" - }, - { - "age_days": 6, - "file": ".claude/commands/ci.md", - "last_commit": "2026-04-01T23:54:33+00:00", - "status": "green" - }, - { - "age_days": 6, - "file": ".claude/commands/generate.md", - "last_commit": "2026-04-01T23:49:24+02:00", - "status": "green" - }, - { - "age_days": 6, - "file": ".claude/commands/test.md", - "last_commit": "2026-04-01T23:49:24+02:00", - "status": "green" - }, - { - "age_days": 6, - "file": "template/tests/{{ package_name }}/test_core.py.jinja", - "last_commit": "2026-04-02T00:35:48+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/coverage.md", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/dependency-check.md", - "last_commit": "2026-04-03T17:52:15+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/docs-check.md", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/release.md", - "last_commit": "2026-04-03T16:55:34+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/review.md", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/standards.md", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/update-claude-md.md", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": ".claude/commands/validate-release.md", - "last_commit": "2026-04-03T17:52:15+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "README.md", - "last_commit": "2026-04-03T12:43:10+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "scripts/bump_version.py", - "last_commit": "2026-04-02T19:00:55+00:00", - "status": "green" - }, - { - "age_days": 5, - "file": "scripts/update_files.sh", - "last_commit": "2026-04-02T22:06:25+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/ci.md", - "last_commit": "2026-04-02T20:14:16+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/coverage.md.jinja", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/docs-check.md.jinja", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/generate.md", - "last_commit": "2026-04-02T20:14:16+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/guided-template-update.md.jinja", - "last_commit": "2026-04-03T17:52:15+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/release.md.jinja", - "last_commit": "2026-04-03T16:55:34+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/review.md.jinja", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/standards.md.jinja", - "last_commit": "2026-04-03T16:37:11+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.claude/commands/test.md", - "last_commit": "2026-04-02T20:14:16+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/.github/CODEOWNERS.jinja", - "last_commit": "2026-04-02T22:06:25+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/src/{{ package_name }}/common/__init__.py.jinja", - "last_commit": "2026-04-02T19:04:46+00:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/src/{{ package_name }}/common/decorators.py.jinja", - "last_commit": "2026-04-02T19:04:46+00:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/src/{{ package_name }}/common/file_manager.py.jinja", - "last_commit": "2026-04-02T19:04:46+00:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/src/{{ package_name }}/common/utils.py.jinja", - "last_commit": "2026-04-02T20:19:59+00:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/tests/__init__.py.jinja", - "last_commit": "2026-04-02T22:06:25+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/tests/{{ package_name }}/__init__.py.jinja", - "last_commit": "2026-04-02T22:06:25+02:00", - "status": "green" - }, - { - "age_days": 5, - "file": "template/{{_copier_conf.answers_file}}.jinja", - "last_commit": "2026-04-02T20:14:16+02:00", - "status": "green" - }, - { - "age_days": 4, - "file": "template/src/{{ package_name }}/core.py.jinja", - "last_commit": "2026-04-04T00:09:05+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": ".github/dependabot.yml", - "last_commit": "2026-04-05T15:07:34+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": ".github/workflows/dependency-review.yml", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": ".github/workflows/lint.yml", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": ".github/workflows/release.yml", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": ".github/workflows/stale.yml", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": "template/.github/workflows/ci.yml.jinja", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": "template/.github/workflows/dependency-review.yml.jinja", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": "template/.github/workflows/docs.yml.jinja", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": "template/.github/workflows/lint.yml.jinja", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 3, - "file": "template/.github/workflows/pre-commit-update.yml.jinja", - "last_commit": "2026-04-05T18:30:54+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/README.md", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-bash-pr-created.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-edit-copier-migration.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-edit-jinja.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-edit-markdown.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-edit-python.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/post-edit-template-mirror.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-bash-block-no-verify.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-bash-commit-quality.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-bash-git-push-reminder.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-compact-save-state.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-config-protection.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-protect-uv-lock.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-suggest-compact.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-write-doc-file-warning.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/pre-write-jinja-syntax.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/session-start-bootstrap.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/stop-cost-tracker.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/stop-desktop-notify.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/stop-evaluate-session.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/hooks/stop-session-end.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/README.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/bash/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/bash/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/code-review.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/development-workflow.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/git-workflow.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/common/testing.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/jinja/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/jinja/testing.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/markdown/conventions.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/python/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/python/hooks.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/python/patterns.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/python/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/python/testing.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/rules/yaml/conventions.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": ".claude/settings.json", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "CLAUDE.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/README.md", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/post-edit-markdown.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/post-edit-python.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/pre-bash-block-no-verify.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/pre-bash-commit-quality.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/pre-bash-git-push-reminder.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/pre-config-protection.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/hooks/pre-protect-uv-lock.sh", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/README.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/bash/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/bash/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/code-review.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/coding-style.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/development-workflow.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/git-workflow.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/common/testing.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/markdown/conventions.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/python/coding-style.md.jinja", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/python/hooks.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/python/patterns.md.jinja", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/python/security.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/rules/python/testing.md", - "last_commit": "2026-04-06T10:56:27+00:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.claude/settings.json", - "last_commit": "2026-04-06T12:48:12+02:00", - "status": "green" - }, - { - "age_days": 2, - "file": "template/.github/workflows/security.yml.jinja", - "last_commit": "2026-04-06T17:19:49+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": ".github/labeler.yml", - "last_commit": "2026-04-07T13:31:18+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": ".github/renovate.json", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": ".pre-commit-config.yaml", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": ".secrets.baseline", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/.github/renovate.json.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/.pre-commit-config.yaml.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/.secrets.baseline", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/CLAUDE.md.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/CONTRIBUTING.md.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/README.md.jinja", - "last_commit": "2026-04-07T13:37:48+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/SECURITY.md.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/cliff.toml.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/docs/ci.md.jinja", - "last_commit": "2026-04-07T13:31:18+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/docs/index.md.jinja", - "last_commit": "2026-04-07T13:31:18+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/mkdocs.yml.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/src/{{ package_name }}/__init__.py.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/src/{{ package_name }}/cli.py.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 1, - "file": "template/tests/test_imports.py.jinja", - "last_commit": "2026-04-07T13:26:04+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".claude/rules/copier/template-conventions.md", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/file-freshness.yml", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/labeler.yml", - "last_commit": "2026-04-08T10:27:54+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/pre-commit-update.yml", - "last_commit": "2026-04-08T10:27:54+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/security.yml", - "last_commit": "2026-04-08T10:27:54+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/sync-skip-if-exists.yml", - "last_commit": "2026-04-08T10:27:54+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".github/workflows/tests.yml", - "last_commit": "2026-04-08T10:27:54+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": ".gitignore", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "copier.yml", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "docs/repo_file_status_report.md", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "env.example", - "last_commit": "2026-04-08T08:22:29+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "file_freshness.json", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "freshness_ignore.json", - "last_commit": "2026-04-08T17:39:28+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "freshness_summary.json", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "justfile", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "pyproject.toml", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "scripts/repo_file_freshness.py", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "scripts/sync_skip_if_exists.py", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/.github/workflows/release.yml.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/.gitignore.jinja", - "last_commit": "2026-04-08T08:22:29+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/env.example.jinja", - "last_commit": "2026-04-08T08:22:29+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/justfile.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/pyproject.toml.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/src/{{ package_name }}/common/bump_version.py.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/src/{{ package_name }}/common/logging_manager.py.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/tests/conftest.py.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "template/tests/{{ package_name }}/test_support.py.jinja", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "tests/test_repo_file_freshness.py", - "last_commit": "2026-04-08T17:46:02+00:00", - "status": "green" - }, - { - "age_days": 0, - "file": "tests/test_template.py", - "last_commit": "2026-04-08T18:35:21+02:00", - "status": "green" - }, - { - "age_days": 0, - "file": "uv.lock", - "last_commit": "2026-04-08T10:27:45+00:00", - "status": "green" - } -] diff --git a/freshness_ignore.json b/freshness_ignore.json deleted file mode 100644 index 4652b22..0000000 --- a/freshness_ignore.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "directories": [], - "extensions": [], - "patterns": [] -} - diff --git a/freshness_summary.json b/freshness_summary.json deleted file mode 100644 index 99c6f14..0000000 --- a/freshness_summary.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "blue": 0, - "color": "brightgreen", - "green": 170, - "label": "freshness", - "message": "170 recent", - "red": 0, - "yellow": 0 -} diff --git a/justfile b/justfile index 2def71d..86a5e74 100644 --- a/justfile +++ b/justfile @@ -89,9 +89,11 @@ precommit-install: precommit: @uv run --active pre-commit run --all-files --verbose -# Dependency audit matching .github/workflows/security.yml (pip-audit) +# Dependency audit matching .github/workflows/security.yml (pip-audit). +# Uses ``uv run --with pip-audit`` so the tool runs with the project Python (``uv tool run``/``uvx`` +# can pick a different interpreter whose venv ``ensurepip`` fails on some hosts). audit: - @uv export --frozen --format requirements-txt --extra dev | uv tool run pip-audit --requirement /dev/stdin + @uv export --frozen --format requirements-txt --extra dev | uv run --with pip-audit pip-audit --requirement /dev/stdin # ------------------------------------------------------------------------- # Dependency management diff --git a/scripts/repo_file_freshness.py b/scripts/repo_file_freshness.py index 105acc0..401dbc0 100644 --- a/scripts/repo_file_freshness.py +++ b/scripts/repo_file_freshness.py @@ -3,16 +3,19 @@ This script: - Lists only Git-tracked files (via ``git ls-files``). -- Computes each file's last update timestamp (via ``git log -1``). -- Classifies files into green/yellow/red/blue using age thresholds and an ignore config. +- Computes each file's last update (via ``git log -1``) as ISO time and commit hash. +- Classifies files into green/yellow/red/blue using either commit-count or calendar-age + thresholds, plus an ignore config (blue). - Writes: - ``docs/repo_file_status_report.md`` (dashboard) - - ``file_freshness.json`` (per-file details) - - ``freshness_summary.json`` (counts + optional badge metadata) + - ``assets/file_freshness.json`` (per-file details) + - ``assets/freshness_summary.json`` (counts + optional badge metadata) Design notes: - Git is the source of truth (no filesystem mtimes). - A single file failing Git history lookup must not fail the whole run. +- ``--metric commits`` adds one ``git rev-list`` call per file; large monorepos may prefer + ``--metric days``. """ from __future__ import annotations @@ -29,6 +32,7 @@ from typing import Any, Literal, Protocol, cast Status = Literal["green", "yellow", "red", "blue"] +Metric = Literal["commits", "days"] class _Args(Protocol): @@ -37,11 +41,14 @@ class _Args(Protocol): output_markdown: str output_details_json: str output_summary_json: str + metric: Metric + green_max_commits: int + yellow_max_commits: int @dataclass(frozen=True) class IgnoreConfig: - """Ignore configuration loaded from ``freshness_ignore.json``.""" + """Ignore configuration loaded from the ignore JSON (e.g. ``assets/freshness_ignore.json``).""" files: frozenset[str] directories: tuple[str, ...] @@ -104,13 +111,34 @@ def _git_ls_files(root: Path) -> list[str]: return [p.replace("\\", "/") for p in paths] -def _git_last_commit_iso(root: Path, rel_path: str) -> str | None: - """Return last commit timestamp for a file as an ISO-8601 string, or None.""" - proc = _run_git(root, ["log", "-1", "--format=%cI", "--", rel_path]) +def _git_last_commit_hash_and_iso(root: Path, rel_path: str) -> tuple[str | None, str | None]: + """Return (commit_hash, committer ISO time) for the last commit touching the path.""" + proc = _run_git( + root, + ["log", "-1", "--format=%H%x00%cI", "--", rel_path], + ) + if proc is None or proc.returncode != 0: + return None, None + raw = proc.stdout.strip() + if not raw or "\x00" not in raw: + return None, None + h, iso = raw.split("\x00", 1) + h2, iso2 = h.strip(), iso.strip() + if not h2 or not iso2: + return None, None + return h2, iso2 + + +def _git_commits_since(root: Path, commit_sha: str) -> int | None: + """Count commits reachable from HEAD but not from ``commit_sha`` (exclusive of that commit).""" + spec = f"{commit_sha}..HEAD" + proc = _run_git(root, ["rev-list", "--count", spec]) if proc is None or proc.returncode != 0: return None - value = proc.stdout.strip() - return value or None + text = proc.stdout.strip() + if not text.isdigit(): + return None + return int(text) def _parse_git_iso_datetime(value: str) -> datetime | None: @@ -214,16 +242,34 @@ def ignored_status(rel_path: str, ignore: IgnoreConfig) -> bool: return any(fnmatch(p, pat) for pat in ignore.patterns) -def classify(age_days: int | None, *, is_ignored: bool) -> Status: - """Classify into one of the four statuses.""" +def classify_days(age_days: int | None, *, is_ignored: bool) -> Status: + """Classify using calendar age: green <=2d, yellow (2,4], red >4 or unknown.""" if is_ignored: return "blue" - # Edge case: missing commit timestamp. Fold into red for the four-color contract. if age_days is None: return "red" - if age_days <= 7: + if age_days <= 2: + return "green" + if age_days <= 4: + return "yellow" + return "red" + + +def classify_commits( + commits_since: int | None, + *, + is_ignored: bool, + green_max: int, + yellow_max: int, +) -> Status: + """Classify using commits after the last file change; unknown counts as red (non-blue).""" + if is_ignored: + return "blue" + if commits_since is None: + return "red" + if commits_since <= green_max: return "green" - if age_days <= 30: + if commits_since <= yellow_max: return "yellow" return "red" @@ -236,19 +282,35 @@ def _sort_key_age_desc(item: dict[str, Any]) -> tuple[int, str]: return (10**9, cast(str, item.get("file", ""))) +def _sort_key_commits_desc(item: dict[str, Any]) -> tuple[int, str]: + c = item.get("commits_since") + if isinstance(c, int): + return (-c, cast(str, item.get("file", ""))) + return (10**9, cast(str, item.get("file", ""))) + + def write_json(path: Path, data: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) text = json.dumps(data, indent=2, sort_keys=True) + "\n" _ = path.write_text(text, encoding="utf-8") -def generate_markdown(now: datetime, summary: dict[str, int], items: list[dict[str, Any]]) -> str: +def generate_markdown( + now: datetime, + summary: dict[str, int], + items: list[dict[str, Any]], + *, + metric: Metric, +) -> str: + """Render the dashboard; green/yellow/red lines show days or commits based on ``metric``.""" updated = now.strftime("%Y-%m-%d %H:%M:%S UTC") lines: list[str] = [] lines.append("# Repository File Status Report") lines.append("") lines.append(f"Last updated: **{updated}**") lines.append("") + lines.append(f"_Metric: **{metric}**._") + lines.append("") lines.append("## Summary") lines.append("") lines.append(f"- 🟢 Green: **{summary['green']}**") @@ -261,7 +323,7 @@ def generate_markdown(now: datetime, summary: dict[str, int], items: list[dict[s for it in items: groups[cast(Status, it["status"])].append(it) - def render_section(title: str, status: Status, *, show_age: bool) -> None: + def render_section(title: str, status: Status, *, show_metric: bool) -> None: lines.append(f"## {title}") lines.append("") if not groups[status]: @@ -270,20 +332,27 @@ def render_section(title: str, status: Status, *, show_age: bool) -> None: return for it in groups[status]: path = cast(str, it["file"]) - age = it.get("age_days") - if show_age: + if not show_metric: + lines.append(f"- `{path}`") + continue + if metric == "days": + age = it.get("age_days") if isinstance(age, int): lines.append(f"- `{path}` — **{age}** days") else: lines.append(f"- `{path}` — _unknown age_") else: - lines.append(f"- `{path}`") + c = it.get("commits_since") + if isinstance(c, int): + lines.append(f"- `{path}` — **{c}** commits since last change") + else: + lines.append(f"- `{path}` — _unknown commit depth_") lines.append("") - render_section("🟢 Green (recent)", "green", show_age=True) - render_section("🟡 Yellow (moderate)", "yellow", show_age=True) - render_section("🔴 Red (stale)", "red", show_age=True) - render_section("🔵 Blue (ignored)", "blue", show_age=False) + render_section("🟢 Green (recent)", "green", show_metric=True) + render_section("🟡 Yellow (moderate)", "yellow", show_metric=True) + render_section("🔴 Red (stale)", "red", show_metric=True) + render_section("🔵 Blue (ignored)", "blue", show_metric=False) return "\n".join(lines) + "\n" @@ -314,7 +383,7 @@ def main() -> int: ) _ = parser.add_argument( "--ignore-config", - default="freshness_ignore.json", + default="assets/freshness_ignore.json", help=( "Path to ignore config JSON with keys: files, directories, extensions, patterns " "(all lists of strings)." @@ -327,36 +396,83 @@ def main() -> int: ) _ = parser.add_argument( "--output-details-json", - default="file_freshness.json", + default="assets/file_freshness.json", help="Path to write per-file JSON details.", ) _ = parser.add_argument( "--output-summary-json", - default="freshness_summary.json", + default="assets/freshness_summary.json", help="Path to write summary JSON counts (plus optional badge fields).", ) + _ = parser.add_argument( + "--metric", + choices=("commits", "days"), + default="commits", + help="Classify by commits since last file change (default) or by calendar age in days.", + ) + _ = parser.add_argument( + "--green-max-commits", + type=int, + default=5, + metavar="N", + help="Commits mode: green if commits_since <= N (default: 5).", + ) + _ = parser.add_argument( + "--yellow-max-commits", + type=int, + default=20, + metavar="N", + help="Commits mode: yellow if commits_since <= N and > green max (default: 20).", + ) args = cast(_Args, cast(object, parser.parse_args())) + if args.green_max_commits < 0 or args.yellow_max_commits < 0: + print("[freshness] Error: commit thresholds must be non-negative.", file=sys.stderr) + return 2 + if args.green_max_commits > args.yellow_max_commits: + print( + "[freshness] Error: --green-max-commits must be <= --yellow-max-commits.", + file=sys.stderr, + ) + return 2 + root = _resolve_repo_root(args.repo_root) ignore = load_ignore_config((root / args.ignore_config).resolve()) now = _now_utc_from_env() + metric = cast(Metric, args.metric) files = _git_ls_files(root) items: list[dict[str, Any]] = [] counts: dict[Status, int] = {"green": 0, "yellow": 0, "red": 0, "blue": 0} + sort_key = _sort_key_commits_desc if metric == "commits" else _sort_key_age_desc + for rel_path in files: try: is_ignored = ignored_status(rel_path, ignore) - commit_iso = _git_last_commit_iso(root, rel_path) + commit_sha, commit_iso = _git_last_commit_hash_and_iso(root, rel_path) age = _age_days(now, commit_iso) - status = classify(age, is_ignored=is_ignored) + commits_since: int | None = None + if metric == "commits" and commit_sha is not None: + commits_since = _git_commits_since(root, commit_sha) + if metric == "commits": + status = classify_commits( + commits_since, + is_ignored=is_ignored, + green_max=args.green_max_commits, + yellow_max=args.yellow_max_commits, + ) + else: + status = classify_days(age, is_ignored=is_ignored) + counts[status] += 1 items.append( { "file": rel_path, "last_commit": commit_iso, + "last_commit_sha": commit_sha, "age_days": age, + "commits_since": commits_since, "status": status, } ) @@ -365,20 +481,22 @@ def main() -> int: status = "red" counts[status] += 1 items.append( - {"file": rel_path, "last_commit": None, "age_days": None, "status": status} + { + "file": rel_path, + "last_commit": None, + "last_commit_sha": None, + "age_days": None, + "commits_since": None, + "status": status, + } ) - # Sorting rules: green/yellow/red by age desc; blue alpha. - for s in ("green", "yellow", "red"): - items_s = [it for it in items if it["status"] == s] - items_s.sort(key=_sort_key_age_desc) - # Replace in-place order by concatenation later. blue_items = [it for it in items if it["status"] == "blue"] blue_items.sort(key=lambda d: cast(str, d.get("file", "")).lower()) ordered = ( - sorted((it for it in items if it["status"] == "green"), key=_sort_key_age_desc) - + sorted((it for it in items if it["status"] == "yellow"), key=_sort_key_age_desc) - + sorted((it for it in items if it["status"] == "red"), key=_sort_key_age_desc) + sorted((it for it in items if it["status"] == "green"), key=sort_key) + + sorted((it for it in items if it["status"] == "yellow"), key=sort_key) + + sorted((it for it in items if it["status"] == "red"), key=sort_key) + blue_items ) @@ -389,9 +507,10 @@ def main() -> int: "blue": counts["blue"], } summary_out: dict[str, Any] = dict(summary) + summary_out["metric"] = metric summary_out.update(build_badge_fields(summary)) - md_text = generate_markdown(now, summary, ordered) + md_text = generate_markdown(now, summary, ordered, metric=metric) # Always generate outputs. write_json((root / args.output_details_json).resolve(), ordered) diff --git a/temp/justfile b/temp/justfile index dbef120..4c27f6b 100644 --- a/temp/justfile +++ b/temp/justfile @@ -92,7 +92,7 @@ test-first-fail: @uv run --active pytest tests/ -x # Run tests with coverage report (full HTML/XML/term output) -coverage: +coverage: @uv run --active pytest tests/ \ --cov=my_library \ --cov-report=term-missing \ @@ -120,7 +120,7 @@ sync: update: @uv lock --upgrade @just sync - + # ------------------------------------------------------------------------- # Environment # ------------------------------------------------------------------------- diff --git a/template/.claude/skills/pytest/references/fixtures.md b/template/.claude/skills/pytest/references/fixtures.md index 461a893..8810119 100644 --- a/template/.claude/skills/pytest/references/fixtures.md +++ b/template/.claude/skills/pytest/references/fixtures.md @@ -201,8 +201,8 @@ Use `monkeypatch` over `unittest.mock.patch` when you need to modify environment variables, dictionary entries, or object attributes and want automatic reversal: ```python -def test_reads_api_key_from_env(monkeypatch): - monkeypatch.setenv("API_KEY", "test-key-123") +def test_reads_setting_from_env(monkeypatch): + monkeypatch.setenv("MYAPP_SETTING", "fixture-value") config = load_config() - assert config.api_key == "test-key-123" + assert config.setting == "fixture-value" ``` diff --git a/tests/test_repo_file_freshness.py b/tests/test_repo_file_freshness.py index 2d7eed2..5384967 100644 --- a/tests/test_repo_file_freshness.py +++ b/tests/test_repo_file_freshness.py @@ -32,21 +32,34 @@ def git_commit(repo: Path, message: str, *, commit_date_iso: str) -> None: run_command(["git", "commit", "-m", message], cwd=repo, extra_env=env) +def git_empty_commit(repo: Path, message: str, *, commit_date_iso: str) -> None: + env = {"GIT_AUTHOR_DATE": commit_date_iso, "GIT_COMMITTER_DATE": commit_date_iso} + run_command(["git", "commit", "--allow-empty", "-m", message], cwd=repo, extra_env=env) + + def write_ignore(repo: Path, data: object) -> None: - (repo / "freshness_ignore.json").write_text(json.dumps(data) + "\n", encoding="utf-8") + assets = repo / "assets" + assets.mkdir(parents=True, exist_ok=True) + (assets / "freshness_ignore.json").write_text(json.dumps(data) + "\n", encoding="utf-8") -def run_script(repo: Path) -> None: +def run_script(repo: Path, *extra_args: str) -> None: script = Path(__file__).resolve().parent.parent / "scripts" / "repo_file_freshness.py" run_command( - [sys.executable, str(script), "--repo-root", str(repo)], + [ + sys.executable, + str(script), + "--repo-root", + str(repo), + *extra_args, + ], cwd=repo, extra_env={"FRESHNESS_NOW_ISO": "2026-04-08T00:00:00+00:00"}, ) def load_details(repo: Path) -> list[dict[str, object]]: - raw = json.loads((repo / "file_freshness.json").read_text(encoding="utf-8")) + raw = json.loads((repo / "assets" / "file_freshness.json").read_text(encoding="utf-8")) assert isinstance(raw, list) return raw @@ -58,27 +71,62 @@ def by_file(items: list[dict[str, object]]) -> dict[str, dict[str, object]]: return out -def test_classification_green_yellow_red(tmp_path: Path) -> None: +def test_classification_green_yellow_red_days(tmp_path: Path) -> None: + """Days mode: green <=2d, yellow (2,4], red >4 (reference 2026-04-08).""" repo = tmp_path / "repo" repo.mkdir() git_init(repo) + (repo / "assets").mkdir(parents=True, exist_ok=True) + (repo / "assets" / "freshness_ignore.json").write_text( + json.dumps({"files": [], "directories": [], "extensions": [], "patterns": []}) + "\n", + encoding="utf-8", + ) (repo / "recent.txt").write_text("recent\n", encoding="utf-8") - git_commit(repo, "add recent", commit_date_iso="2026-04-06T00:00:00+00:00") + git_commit(repo, "add recent", commit_date_iso="2026-04-07T00:00:00+00:00") (repo / "mid.txt").write_text("mid\n", encoding="utf-8") - git_commit(repo, "add mid", commit_date_iso="2026-03-20T00:00:00+00:00") + git_commit(repo, "add mid", commit_date_iso="2026-04-05T00:00:00+00:00") (repo / "old.txt").write_text("old\n", encoding="utf-8") - git_commit(repo, "add old", commit_date_iso="2025-12-01T00:00:00+00:00") + git_commit(repo, "add old", commit_date_iso="2026-04-02T00:00:00+00:00") - run_script(repo) + run_script(repo, "--metric", "days") items = by_file(load_details(repo)) assert items["recent.txt"]["status"] == "green" assert items["mid.txt"]["status"] == "yellow" assert items["old.txt"]["status"] == "red" +def test_classification_commits_since_last_change(tmp_path: Path) -> None: + repo = tmp_path / "repo" + repo.mkdir() + git_init(repo) + (repo / "assets").mkdir(parents=True, exist_ok=True) + (repo / "assets" / "freshness_ignore.json").write_text( + json.dumps({"files": [], "directories": [], "extensions": [], "patterns": []}) + "\n", + encoding="utf-8", + ) + + (repo / "tracked.txt").write_text("v1\n", encoding="utf-8") + git_commit(repo, "touch file", commit_date_iso="2026-01-01T00:00:00+00:00") + git_empty_commit(repo, "empty 1", commit_date_iso="2026-01-02T00:00:00+00:00") + git_empty_commit(repo, "empty 2", commit_date_iso="2026-01-03T00:00:00+00:00") + + run_script( + repo, + "--metric", + "commits", + "--green-max-commits", + "1", + "--yellow-max-commits", + "5", + ) + items = by_file(load_details(repo)) + assert items["tracked.txt"]["commits_since"] == 2 + assert items["tracked.txt"]["status"] == "yellow" + + def test_ignore_priority_exact_then_dir_then_ext_then_pattern(tmp_path: Path) -> None: repo = tmp_path / "repo" repo.mkdir() @@ -103,7 +151,7 @@ def test_ignore_priority_exact_then_dir_then_ext_then_pattern(tmp_path: Path) -> }, ) - run_script(repo) + run_script(repo, "--metric", "days") items = by_file(load_details(repo)) assert items["exact.txt"]["status"] == "blue" assert items["build/out.js"]["status"] == "blue" @@ -118,8 +166,10 @@ def test_invalid_ignore_config_is_graceful(tmp_path: Path) -> None: (repo / "a.txt").write_text("a\n", encoding="utf-8") git_commit(repo, "add a", commit_date_iso="2025-01-01T00:00:00+00:00") - (repo / "freshness_ignore.json").write_text("{not-json}\n", encoding="utf-8") - run_script(repo) + assets = repo / "assets" + assets.mkdir(parents=True, exist_ok=True) + (assets / "freshness_ignore.json").write_text("{not-json}\n", encoding="utf-8") + run_script(repo, "--metric", "days") items = by_file(load_details(repo)) assert items["a.txt"]["status"] in {"green", "yellow", "red", "blue"} @@ -131,6 +181,6 @@ def test_empty_repo_generates_outputs(tmp_path: Path) -> None: git_init(repo) run_script(repo) - assert (repo / "file_freshness.json").is_file() - assert (repo / "freshness_summary.json").is_file() + assert (repo / "assets" / "file_freshness.json").is_file() + assert (repo / "assets" / "freshness_summary.json").is_file() assert (repo / "docs" / "repo_file_status_report.md").is_file() diff --git a/tests/test_template.py b/tests/test_template.py index 34d7b74..1653d80 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -61,6 +61,8 @@ def get_default_command_list(test_dir: Path) -> list[str]: return [ "copier", "copy", + "--vcs-ref", + "HEAD", TEMPLATE_GIT_SRC, str(test_dir), "--data", @@ -136,8 +138,6 @@ def copy_with_data( data: Mapping of Copier variable names to values to pass via ``--data``. skip_tasks: If ``True``, skip post-generation tasks (default). """ - template_repo = Path(__file__).resolve().parent.parent - vcs_src = f"git+file://{template_repo}" cmd: list[str] = [ "copier", "copy", @@ -262,12 +262,12 @@ def test_generate_defaults_only_cli(tmp_path: Path) -> None: Pass explicit ``--data`` when you need a different distribution name. """ test_dir = tmp_path / "defaults_only" - template_repo = Path(__file__).resolve().parent.parent - vcs_src = f"git+file://{template_repo}" _ = run_command( [ "copier", "copy", + "--vcs-ref", + "HEAD", TEMPLATE_GIT_SRC, str(test_dir), "--trust", @@ -307,8 +307,6 @@ def test_copier_yaml_has_no_codecov_token_prompt() -> None: def test_package_name_validator_rejects_leading_digit(tmp_path: Path) -> None: """Digit-leading ``package_name`` values must fail Copier validation.""" test_dir = tmp_path / "bad_pkg" - template_repo = Path(__file__).resolve().parent.parent - vcs_src = f"git+file://{template_repo}" proc = run_command( [ "copier", @@ -335,6 +333,8 @@ def test_computed_values_not_recorded_in_answers_file(tmp_path: Path) -> None: [ "copier", "copy", + "--vcs-ref", + "HEAD", TEMPLATE_GIT_SRC, str(test_dir), "--trust", @@ -362,6 +362,8 @@ def test_answers_file_warns_never_edit_manually(tmp_path: Path) -> None: [ "copier", "copy", + "--vcs-ref", + "HEAD", TEMPLATE_GIT_SRC, str(test_dir), "--trust", @@ -386,7 +388,6 @@ def test_generate_programmatic_run_copy_local(tmp_path: Path) -> None: test_dir = tmp_path / "programmatic_local" # Use a VCS-style local source to avoid git hardlink issues that can occur with # local clones in some container/filesystem setups. - vcs_src = f"git+file://{Path('.').resolve()}" _worker = run_copy( TEMPLATE_GIT_SRC, test_dir, @@ -416,7 +417,14 @@ def test_generate_from_vcs_git_file_url(tmp_path: Path) -> None: template_repo, dirs_exist_ok=True, ignore=shutil.ignore_patterns( - ".git", ".venv", "__pycache__", "*.pyc", ".ruff_cache", ".pytest_cache" + ".git", + ".venv", + "__pycache__", + "*.pyc", + ".ruff_cache", + ".pytest_cache", + "files.zip", + "temp", ), ) _ = run_command(["git", "init"], cwd=template_repo) @@ -429,7 +437,17 @@ def test_generate_from_vcs_git_file_url(tmp_path: Path) -> None: vcs_src = f"git+file://{template_repo}" _ = run_command( - ["copier", "copy", vcs_src, str(dest_dir), "--trust", "--defaults", "--skip-tasks"], + [ + "copier", + "copy", + "--vcs-ref", + "HEAD", + vcs_src, + str(dest_dir), + "--trust", + "--defaults", + "--skip-tasks", + ], ) _remove_empty_optional_artifacts( dest_dir, @@ -663,7 +681,14 @@ def test_copier_update_exits_zero_after_copy_and_commit(tmp_path: Path) -> None: template_repo, dirs_exist_ok=True, ignore=shutil.ignore_patterns( - ".git", ".venv", "__pycache__", "*.pyc", ".ruff_cache", ".pytest_cache" + ".git", + ".venv", + "__pycache__", + "*.pyc", + ".ruff_cache", + ".pytest_cache", + "files.zip", + "temp", ), ) _ = run_command(["git", "init"], cwd=template_repo) @@ -680,6 +705,8 @@ def test_copier_update_exits_zero_after_copy_and_commit(tmp_path: Path) -> None: [ "copier", "copy", + "--vcs-ref", + "HEAD", vcs_src, str(test_dir), "--trust", diff --git a/uv.lock b/uv.lock index 44a28d3..3b54d3d 100644 --- a/uv.lock +++ b/uv.lock @@ -772,7 +772,7 @@ wheels = [ [[package]] name = "python-project-template" -version = "0.1.0" +version = "0.0.4" source = { virtual = "." } [package.optional-dependencies]