feat(B-0156): Phase 6 .py policy CI gate — no-python-files lint#3949
Conversation
Smallest safe slice of B-0156 (TypeScript standardization). Phases 1-5 were already DONE per the row (all six named .sh files ported to .ts and deleted); Phase 6 (.py policy enforcement) was the only outstanding acceptance bullet. - tools/lint/no-python-files.ts — TS+Bun lint (Rule 0: no .sh outside install graph). Walks the tree, hard-excludes references/upstreams, .venv, __pycache__, site-packages, tools/lean4/.lake, node_modules, bin/obj. Reads tools/lint/no-python-files.allowlist for explicit exceptions. Exit 0 clean / 1 flagged / 2 allowlist-missing. - tools/lint/no-python-files.allowlist — starts empty (current repo state: 0 .py files in our scope, matching the row's audit baseline). Legitimate exceptions land here with reason comments. - tools/lint/no-python-files.test.ts — 9 bun tests against synthetic trees (clean, flagged, allowlisted, each hard-exclude segment, --list mode, comment-line handling, missing-allowlist). - .github/workflows/gate.yml — lint-no-python-files job adjacent to lint-no-empty-dirs, same shape (3 min timeout, install toolchain, run lint). No untrusted input in run: lines. - docs/backlog/P1/B-0156-...md — Phase 6 marked DONE; last_updated bumped to 2026-05-16. Focused checks: 9/9 unit tests pass; real-repo lint reports "0 allowlisted, 0 flagged"; no-empty-dirs regression green; gate.yml parses cleanly (17 jobs, new job present). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a Bun/TypeScript CI lint gate for B-0156 Phase 6 to prevent first-party .py files outside approved exclusions/allowlist.
Changes:
- Adds
no-python-fileslint tool, allowlist, and Bun tests. - Wires the lint into
gate.yml. - Updates backlog/tick documentation for the completed phase.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
tools/lint/no-python-files.ts |
Implements the .py file scanner and policy enforcement. |
tools/lint/no-python-files.test.ts |
Adds unit tests for lint outcomes and exclusions. |
tools/lint/no-python-files.allowlist |
Documents the explicit allowed .py path list. |
.github/workflows/gate.yml |
Adds the CI job that runs the new lint. |
docs/backlog/P1/B-0156-typescript-standardization-non-install-scripts-aaron-2026-05-01.md |
Marks Phase 6 complete and updates status details. |
docs/hygiene-history/ticks/2026/05/16/2157Z.md |
Records the implementation tick for this phase. |
Comments suppressed due to low confidence (1)
tools/lint/no-python-files.ts:62
- This second
spawnSync("git", ...)has the same SonarJS issue as therepoRoot()call: TypeScript tools in this repo suppresssonarjs/no-os-command-from-pathwith a short rationale when intentionally invokinggitfrom PATH. Without that, the new tool is likely to break the TypeScript lint gate.
const result = spawnSync("git", ["check-ignore", "--stdin"], {
| ]; | ||
|
|
||
| function repoRoot(): string { | ||
| const result = spawnSync("git", ["rev-parse", "--show-toplevel"], { |
|
|
||
| function makeRepo(): string { | ||
| const root = mkdtempSync(join(tmpdir(), "no-python-files-")); | ||
| spawnSync("git", ["init", "-q", root], { encoding: "utf8" }); |
| ); | ||
| for (const f of flagged) process.stderr.write(` ${f}\n`); | ||
| process.stderr.write("\n"); | ||
| process.stderr.write("Per B-0156 (Aaron 2026-05-01): TS is preferred over Python in our codebase.\n"); |
| const ignored = new Set(gitCheckIgnore(candidates)); | ||
| const filtered = candidates.filter((f) => !ignored.has(f)); |
| # .venv, node_modules, __pycache__, site-packages, .lake are hard- | ||
| # excluded by the script itself (HARD_EXCLUDE_PREFIXES / | ||
| # HARD_EXCLUDE_SEGMENTS) and do NOT need to be listed here. |
| # references/upstreams, .venv, __pycache__, site-packages, .lake | ||
| # are hard-excluded by the script itself. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72ab8dc69c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (isHardExcluded(rel)) continue; | ||
| if (e.isDirectory()) { | ||
| stack.push(full); | ||
| } else if (e.isFile() && e.name.endsWith(".py")) { |
There was a problem hiding this comment.
Detect Python extensions case-insensitively
findPythonFiles() only flags names ending with lowercase .py, so a committed file like scripts/tool.PY or tool.Py is treated as non-Python and the new CI job passes even though policy intent is to block Python files. This creates a straightforward bypass of the enforcement introduced in this commit; normalize the filename case (or use a case-insensitive suffix check) before testing the extension.
Useful? React with 👍 / 👎.
Per-tick shard documenting: - Cron sentinel re-arm at session start (catch 43 prevention) - Substrate-drift discriminator on B-0156 (audit stale; Phases 1-5 effectively complete; Phase 6 landed via #3949) - Smallest safe slice picked: tools/profile.test.ts - 8/8 tests pass verification trace - Pure-git tier — PR creation deferred to post-rate-limit-reset operative-authorization: aaron 2026-05-14: "- **Devil-pole** (edge-runner drive): keep pushing, discover, go hard, never-be-idle" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Smallest safe slice of B-0156 (TypeScript standardization). Closes Phase 6 —
.pypolicy CI enforcement.Substrate-drift discriminator at session start confirmed Phases 1-5 = DONE (all six named non-install
.shfiles already ported to.tsand deleted;tools/profile.ts,tools/peer-call/amara.ts,tools/peer-call/ani.ts,tools/hygiene/{snapshot,check}-github-settings.ts,tools/hygiene/check-tick-history-shard-schema.tsall exist). Phase 6 (.pypolicy enforcement) was the only outstanding acceptance bullet.What lands
tools/lint/no-python-files.ts— TS+Bun lint (per Rule 0: no.shoutside install graph). Walks the tree, hard-excludesreferences/upstreams,.venv,__pycache__,site-packages,tools/lean4/.lake,node_modules,bin/obj. Readstools/lint/no-python-files.allowlistfor explicit exceptions. Exit 0 clean / 1 flagged / 2 allowlist-missing.tools/lint/no-python-files.allowlist— starts empty (current repo state: 0.pyfiles in our scope, matching the row's audit baseline). Legitimate exceptions land here with reason comments.tools/lint/no-python-files.test.ts— 9bun testunit tests against synthetic trees (clean / flagged / allowlisted / each hard-exclude segment /--listmode / comment-line handling / missing-allowlist)..github/workflows/gate.yml— newlint-no-python-filesjob adjacent tolint-no-empty-dirs, same shape (3-min timeout, install toolchain, run lint). No untrusted input inrun:lines.last_updated: 2026-05-16.docs/hygiene-history/ticks/2026/05/16/2157Z.md.Focused checks
bun test tools/lint/no-python-files.test.tsbun tools/lint/no-python-files.ts(real repo)OK (0 allowlisted, 0 flagged)— exit 0bun tools/lint/no-empty-dirs.ts(regression check after adding new files)js-yamlparse of.github/workflows/gate.ymllint-no-python-filespresentgit ls-tree HEAD | wc -lvsorigin/main(broken-commit canary)Test plan
lint-no-python-filesstep — green on this PR (0.pyfiles in scope)lint-no-empty-dirsor other lint jobsdocs/BACKLOG.md(verified locally; no diff)Composes with
.pypolicy lint added to gate.yml)findbash the row had draftedno-empty-dirs.tstemplate — same allowlist + hard-exclude + posix-rel + bun-test patterns🤖 Generated with Claude Code