Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
594 commits
Select commit Hold shift + click to select a range
e18e855
fix(security): stop the security tier inheriting the quality tier's mask
PythonWoods-Dev Sep 10, 2026
b76d642
test: guard tomllib import for Python 3.10
PythonWoods-Dev Sep 10, 2026
370010e
docs(tutorials): quote what first-audit.md's commands actually print
PythonWoods-Dev Sep 10, 2026
5c43845
docs(examples): replace four quoted outputs with the real captures
PythonWoods-Dev Sep 10, 2026
216d513
docs(reference): correct seven surfaces that quoted output the tool n…
PythonWoods-Dev Sep 10, 2026
249abb1
fix(reporter): stop three finding messages restating their own code
PythonWoods-Dev Sep 10, 2026
8245436
fix(security): close three ways the security tier reached the wrong v…
PythonWoods-Dev Sep 10, 2026
4445fe4
docs: state the traversal rule the fix actually implements
PythonWoods-Dev Sep 10, 2026
256ebfa
docs(changelog): one upgrade notice for both security-tier changes
PythonWoods-Dev Sep 10, 2026
245d684
fix(reporter): a forbidden scheme is not a credential to rotate
PythonWoods-Dev Sep 10, 2026
4de3774
fix(docs): correct 14 commands that never existed, and gate the class
PythonWoods-Dev Sep 10, 2026
ab7dea3
fix(guard): stop the credential fixture blocking its own commits
PythonWoods-Dev Sep 10, 2026
fe88bca
fix(docs): correct the last broken command, in the fixture that block…
PythonWoods-Dev Sep 10, 2026
d50f738
fix(guard): name the second credential fixture, and stop the hook no-…
PythonWoods-Dev Sep 10, 2026
650e54b
test: inherit the environment so Windows can start Python at all
PythonWoods-Dev Sep 10, 2026
e523af6
test: decode CLI output explicitly in the three tests that still did not
PythonWoods-Dev Sep 10, 2026
f50fc40
fix(reporter): say what the security finding is, and the exit it actu…
PythonWoods-Dev Sep 10, 2026
8fc3d15
fix(security): a Markdown link in a JSX prop string is not a link
PythonWoods-Dev Sep 11, 2026
2bf2baf
fix(mdx): a spaced comment is still a comment
PythonWoods-Dev Sep 11, 2026
94bc7a0
docs(examples): quote what the fixtures actually print
PythonWoods-Dev Sep 11, 2026
8fce5ef
fix(cli): send error diagnoses to stderr
PythonWoods-Dev Sep 11, 2026
d87b8b4
test: exercise the four terminal-dependent sites under a real pty
PythonWoods-Dev Sep 11, 2026
d573b44
fix(output): report file paths the same way in every finding and format
PythonWoods-Dev Sep 11, 2026
76e1178
fix(tests,output): unbreak the Windows matrix, in the test and in the…
PythonWoods-Dev Sep 11, 2026
fce2504
docs(contract): document the breaking-change marker check, and correc…
PythonWoods-Dev Sep 11, 2026
ce79b84
docs(readme): show the pre-commit refusal, recorded from a real session
PythonWoods-Dev Sep 11, 2026
5087d50
docs(install): reference the demo from the page that teaches the hook
PythonWoods-Dev Sep 11, 2026
980c342
docs(lab): show the security tier firing, on the page that teaches th…
PythonWoods-Dev Sep 11, 2026
dbf422c
feat(mdx): analyse JSX link components, by the convention rather than…
PythonWoods-Dev Sep 11, 2026
ff6e5a6
docs(roadmap): retire an entry that became work within a day
PythonWoods-Dev Sep 11, 2026
4e3147e
fix(reporter): the breach block shows the URL, not the attribute's name
PythonWoods-Dev Sep 11, 2026
6cd0815
docs(rules): give every suppressible card its MDX spelling, and fix a…
PythonWoods-Dev Sep 11, 2026
51ba953
docs(mdx): state MDX support as capability, and correct what describe…
PythonWoods-Dev Sep 11, 2026
e16373a
docs(tutorial): the first audit scans MDX too, and now says so
PythonWoods-Dev Sep 11, 2026
c725a75
docs(tutorial): the first audit scans MDX too, and now says so
PythonWoods-Dev Sep 11, 2026
acef197
docs(rules): verify the seven policy cards, and find three of them we…
PythonWoods-Dev Sep 11, 2026
246075c
docs(adapters): the route-manifest path, because prebuilt already sol…
PythonWoods-Dev Sep 11, 2026
8d55cbd
docs(adapters): verify the recipe on Docusaurus, and split the guidan…
PythonWoods-Dev Sep 11, 2026
c27ffd1
docs(adapters): the Docusaurus recipe holds for one case, and the doc…
PythonWoods-Dev Sep 11, 2026
2d8f28b
feat(lab): an executable MDX fixture, and the defect that kept one co…
PythonWoods-Dev Sep 11, 2026
5f624ae
fix(suppressions): read the ledger when it is complete, not when the …
PythonWoods-Dev Sep 12, 2026
4e99697
fix(suppressions): the MDX form Prettier emits was not a directive at…
PythonWoods-Dev Sep 12, 2026
7664533
fix(output): the aggregate JSON could not locate a finding, and the c…
PythonWoods-Dev Sep 12, 2026
35b89db
ci: the full matrix is unconditional, because a label needed every ti…
PythonWoods-Dev Sep 12, 2026
3737eb9
ci: delete the ci:full-matrix label and everything that read it
PythonWoods-Dev Sep 12, 2026
73ebaa2
docs(ci): erase the label rather than archive it, and justify fail-fa…
PythonWoods-Dev Sep 12, 2026
f80a3b7
ci(release): the tag gets a recipe and a gate, because nothing reject…
PythonWoods-Dev Sep 12, 2026
a8b1cda
docs(release): the documented sequence could not have worked, and two…
PythonWoods-Dev Sep 12, 2026
dcbe838
fix(gate): the documented-command gate read a TOML declaration as a c…
PythonWoods-Dev Sep 12, 2026
670e742
feat: one enforcement authority, an audit-release for the core, and a…
PythonWoods-Dev Sep 12, 2026
e06f4d5
ci: make the secret scan real, and extend the parity check to .github
PythonWoods-Dev Sep 12, 2026
7415d7f
docs(cli-contract): declare which of the four requirements cannot be …
PythonWoods-Dev Sep 12, 2026
813262e
fix(release): the bump was broken in three places, and the walk never…
PythonWoods-Dev Sep 12, 2026
8ebebdc
docs(release): state the procedure, not how it was decided
PythonWoods-Dev Sep 12, 2026
282ea77
feat(json): declare the per-check shape, complete snippets[], and val…
PythonWoods-Dev Sep 12, 2026
536319d
feat(sarif): a finding keeps its identity when the line moves and whe…
PythonWoods-Dev Sep 12, 2026
039c90b
fix(score): the breakdown table was dropping the number it exists to …
PythonWoods-Dev Sep 12, 2026
ee55b10
docs(examples): a verified terminal block on every family page, and t…
PythonWoods-Dev Sep 12, 2026
49f0b86
ci: move the mutation gate off every push, and stop paying for supers…
PythonWoods-Dev Sep 12, 2026
9d3efed
docs(homepage): two panel labels described a different code entirely
PythonWoods-Dev Sep 12, 2026
cf826b0
fix(examples): a generated block carried the home directory of the ma…
PythonWoods-Dev Sep 12, 2026
31b81a4
docs(blog): the Docusaurus follow-up, and two episodes the casebook w…
PythonWoods-Dev Sep 12, 2026
c8076dc
feat(codes): remove Z504, build the two buildable fixtures, and check…
PythonWoods-Dev Sep 12, 2026
2a31711
docs(nav): order the sidebar numerically, group the rule cards, stack…
PythonWoods-Dev Sep 13, 2026
a117921
fix(core): resolve links the way a browser does, and separate travers…
PythonWoods-Dev Sep 13, 2026
891d5b8
docs: correct every internal link to the address a browser resolves
PythonWoods-Dev Sep 13, 2026
a248d07
fix(redirects): restore rules for deleted pages, and gate the ledger …
PythonWoods-Dev Sep 13, 2026
778cf24
chore(state): regenerate the lab blocks, and record this stretch's fi…
PythonWoods-Dev Sep 13, 2026
db164fc
docs(z106): document the opt-in, with the measurement that justifies it
PythonWoods-Dev Sep 13, 2026
6bedc25
test(parity): record that the proposal to close the Z106 divergence r…
PythonWoods-Dev Sep 13, 2026
67d4a57
fix(docs): Z905 is not a code, and it was in a copy-pasteable suppres…
PythonWoods-Dev Sep 13, 2026
ec19355
docs(rules): give the 70 rule cards a readable navigation label
PythonWoods-Dev Sep 13, 2026
a521849
feat(json)!: remove the six grouped arrays from check all's JSON payload
PythonWoods-Dev Sep 13, 2026
ee04c46
feat(config)!: remove [project_metadata].obsolete_names
PythonWoods-Dev Sep 13, 2026
d6b608d
fix(config): warn on an unknown key inside a declared section
PythonWoods-Dev Sep 13, 2026
3673bb8
fix(security): point the vulnerability channel at a username that sti…
PythonWoods-Dev Sep 13, 2026
d5f901c
fix(vsm): resolve a directory-form link to the route it names
PythonWoods-Dev Sep 13, 2026
26e539f
fix(links): move every live GitHub reference to the owner that resolves
PythonWoods-Dev Sep 13, 2026
38d3343
feat(lsp): detect link cycles on the incremental path, and stop exclu…
PythonWoods-Dev Sep 13, 2026
3c23ee5
docs(z106): split a sentence the engine flagged in the new section
PythonWoods-Dev Sep 13, 2026
9f0d29c
test(resolver): assert per-resolution cost as a ratio, not a wall-clo…
PythonWoods-Dev Sep 13, 2026
1a204d6
test(resolver): exclude the performance test from instrumentation
PythonWoods-Dev Sep 13, 2026
03cc5be
perf(resolver): read the href suffix without building a throwaway Path
PythonWoods-Dev Sep 13, 2026
1029918
fix(resolver)!: pin the href extension rule instead of inheriting pat…
PythonWoods-Dev Sep 13, 2026
75fde97
test(resolver): report the performance measurement on every run, not …
PythonWoods-Dev Sep 13, 2026
d7fe5d4
fix(docs): the resolver's cost penalty is the interpreter, not the pl…
PythonWoods-Dev Sep 13, 2026
214e523
fix(links): a footnote definition is not a link reference
PythonWoods-Dev Sep 13, 2026
8cf4738
ci: compare findings across Python interpreters
PythonWoods-Dev Sep 13, 2026
296f373
ci: state the empty-digest principle without citing a private rule nu…
PythonWoods-Dev Sep 13, 2026
7b25e0e
docs(changelog): frame the footnote fix by what a user must do
PythonWoods-Dev Sep 13, 2026
f8957a5
fix(core): one fence tracker, built against CommonMark
PythonWoods-Dev Sep 14, 2026
b1f14d7
docs: record the fence rewrite where a user meets it
PythonWoods-Dev Sep 14, 2026
67b565e
feat(policies): six editorial-policy checks become opt-in
PythonWoods-Dev Sep 14, 2026
0b9acee
feat(codes): give the registry the activation field it lacked
PythonWoods-Dev Sep 14, 2026
40ed1c1
feat(init): derive the generated config's activation section from the…
PythonWoods-Dev Sep 14, 2026
28ffc59
fix(cli): group findings by the registry, not by the code's number
PythonWoods-Dev Sep 14, 2026
a92d260
fix(core): Z503 stops reporting a syntax error on a snippet that teac…
PythonWoods-Dev Sep 14, 2026
efb385b
test(rules): measure check_vsm against its own primitive, not the wal…
PythonWoods-Dev Sep 14, 2026
7781edb
fix(core): stop probing hostnames RFC 2606 reserves so they cannot re…
PythonWoods-Dev Sep 14, 2026
e70511c
docs(changelog): tie the reserved-name figures to the commits they me…
PythonWoods-Dev Sep 14, 2026
34a8cd0
feat(core): exclude a generator's declared output directory, not a gu…
PythonWoods-Dev Sep 15, 2026
43d76c4
docs(config): give SYSTEM_EXCLUDED_DIRS a membership criterion and bi…
PythonWoods-Dev Sep 15, 2026
a6d3bdb
feat(cli): show the exclusion layers in config explain
PythonWoods-Dev Sep 15, 2026
358c88e
test(rules): run each rule card's failing example, and fix the cards …
PythonWoods-Dev Sep 15, 2026
1e1ccfb
fix(codes): register Z109 as the inactive alias it is, and check that…
PythonWoods-Dev Sep 15, 2026
18de374
docs: correct the reference pages and cards this release left describ…
PythonWoods-Dev Sep 15, 2026
6a6e698
docs(changelog)!: say what v0.31.0 changes for a user, including the …
PythonWoods-Dev Sep 15, 2026
cd438fa
docs(scoring): show Z109's penalty as 0.0 pts, the form the penalty t…
PythonWoods-Dev Sep 15, 2026
04f3a5d
test(rules): import tomllib through the 3.10 backport, as the rest of…
PythonWoods-Dev Sep 15, 2026
2879b67
chore(config): drop the home page's short-content exemption, which ca…
PythonWoods-Dev Sep 15, 2026
a92b4a6
fix(assets): read srcset on img and source, so a responsive image var…
PythonWoods-Dev Sep 15, 2026
01bf95e
fix(suppression): consume the directive when a brand, frontmatter or …
PythonWoods-Dev Sep 15, 2026
6983c40
feat(suppression)!: count the declared exceptions in use, directory p…
PythonWoods-Dev Sep 15, 2026
e23f869
chore(config): declare the suppression cap at the exemptions this pro…
PythonWoods-Dev Sep 15, 2026
5d1121d
docs(adr): record ADR 061, the suppression debt model the scorer has …
PythonWoods-Dev Sep 15, 2026
e306161
docs(suppression): state that a directory policy in use costs a point…
PythonWoods-Dev Sep 15, 2026
2d931db
docs(examples): recapture every example page's expected output from i…
PythonWoods-Dev Sep 15, 2026
56c9c7e
chore(baseline): re-snapshot at 84 because the unit of the score chan…
PythonWoods-Dev Sep 15, 2026
a860c31
chore(readme): stamp the score badge at 84, the score under the curre…
PythonWoods-Dev Sep 15, 2026
d0dabac
feat(mkdocs)!: honour not_in_nav on the page side, and retire the twe…
PythonWoods-Dev Sep 16, 2026
a05d895
fix(mkdocs): a malformed not_in_nav pattern no longer aborts the scan
PythonWoods-Dev Sep 16, 2026
4484f9c
feat(mkdocs): report an engine pattern that cannot be parsed, instead…
PythonWoods-Dev Sep 16, 2026
6a5cea2
feat(mkdocs)!: honour exclude_docs and draft_docs, so a page the buil…
PythonWoods-Dev Sep 16, 2026
13c8abb
docs(changelog): record the three engine changes this cycle, framed b…
PythonWoods-Dev Sep 16, 2026
979c820
fix(explain): stop presenting an unemittable code as live, and give e…
PythonWoods-Dev Sep 16, 2026
5c40c05
fix(core): a quoted attribute value containing '>' no longer truncate…
PythonWoods-Dev Sep 16, 2026
ce001bd
docs(rules): give every card that can have a runnable example one, an…
PythonWoods-Dev Sep 16, 2026
36fff9f
docs(examples): regenerate the two example blocks that no longer matc…
PythonWoods-Dev Sep 16, 2026
3afd834
docs(contribute): label the SPDX header blocks as the Python they are
PythonWoods-Dev Sep 16, 2026
afdfd52
docs(ci-cd): show a command and its response as a console block, not …
PythonWoods-Dev Sep 16, 2026
4ba1539
fix(assets): read srcset through a tag whose earlier attribute contai…
PythonWoods-Dev Sep 16, 2026
e2c26b2
fix(content): report a duplicate H1 with the title the document actua…
PythonWoods-Dev Sep 16, 2026
3e4b578
docs(blog): bring the posts back in line with the engine, by record o…
PythonWoods-Dev Sep 16, 2026
fb45504
docs(blog): let the update note's own box carry the label, once
PythonWoods-Dev Sep 16, 2026
d6766af
docs(diagrams): give every node a declared class, and draw the two ax…
PythonWoods-Dev Sep 16, 2026
4a005d8
docs(diagrams): a finding being reported is the engine working, not a…
PythonWoods-Dev Sep 16, 2026
7164efc
docs(gate): state the palette gate's perimeter, and close it with a s…
PythonWoods-Dev Sep 16, 2026
367df91
docs(blog): say which date the filename prefix is, and which one counts
PythonWoods-Dev Sep 16, 2026
8b5e2fe
docs(cards): let the rule card icons follow the theme instead of rest…
PythonWoods-Dev Sep 16, 2026
b238f1c
fix(init): the generated config no longer contradicts the invariant i…
PythonWoods-Dev Sep 16, 2026
6903b5c
fix(mutation): keep each mutant's fate beside the aggregate score
PythonWoods-Dev Sep 17, 2026
633b591
build(verify): run the tree-deterministic half once per tree
PythonWoods-Dev Sep 17, 2026
2b453db
docs(roadmap): retire Sphinx with its reason, name CommonMark as its …
PythonWoods-Dev Sep 17, 2026
ce920ac
fix(content): a reference definition is indented at most three spaces
PythonWoods-Dev Sep 17, 2026
3feb4ff
fix(reporter): route the breach banner's colour through the palette
PythonWoods-Dev Sep 17, 2026
6a19c37
docs: the CLI/LSP graph parity is closed, and the roadmap says so
PythonWoods-Dev Sep 17, 2026
7bb1183
feat(init): --interactive asks about the engine and each opt-in code,…
PythonWoods-Dev Sep 17, 2026
14176f2
docs(readme): every claim re-measured; gitpython lifted past PYSEC-20…
PythonWoods-Dev Sep 17, 2026
caf4a48
build(verify): run the test stage under pytest -n auto; verdict-cache…
PythonWoods-Dev Sep 17, 2026
9c2b664
fix(policy): warnings to stderr after the banner; Z518 exclusions; ev…
PythonWoods-Dev Sep 17, 2026
bec5b84
test(guard): the commit-gate exemption names three fixtures, re-verified
PythonWoods-Dev Sep 17, 2026
d212464
fix(ci): allowlist the README-capture fixture in the CI secret scan; …
PythonWoods-Dev Sep 17, 2026
8679e47
test(policy): inherit the environment in the CLI subprocess helper
PythonWoods-Dev Sep 17, 2026
9965d69
fix(lsp): watch pyproject.toml; state that allowed_external_domains i…
PythonWoods-Dev Sep 17, 2026
3b842bd
docs(changelog): name both config files in the LSP watcher entry
PythonWoods-Dev Sep 17, 2026
44b59dc
docs(changelog): name the reload defect users have on core v0.30.0
PythonWoods-Dev Sep 17, 2026
e8f402e
build(hooks): pre-push runs only the checks a later run cannot undo
PythonWoods-Dev Sep 18, 2026
8bbf234
docs: say which gate runs where, and make the release checklist call …
PythonWoods-Dev Sep 18, 2026
1b94797
fix(scanner): index legal files as assets so a link to an existing LI…
PythonWoods-Dev Sep 18, 2026
dc71ebd
docs(init): the generated config says where the codes it omits are do…
PythonWoods-Dev Sep 18, 2026
f916fec
fix(findings): fifteen link and policy codes carry the column of the …
PythonWoods-Dev Sep 18, 2026
d69ca5b
docs(roadmap): the Sphinx retirement is about reStructuredText, not a…
PythonWoods-Dev Sep 18, 2026
06b340c
feat(core): the adapter declares which extensions are on, the core kn…
PythonWoods-Dev Sep 18, 2026
813cfb5
feat(core): the container vocabulary is required, and comes from the …
PythonWoods-Dev Sep 18, 2026
461ffd0
fix(anchors): a link to a content tab resolves, and the replicas are …
PythonWoods-Dev Sep 18, 2026
4de5a9f
docs(tests): describe the property instead of citing a private rule n…
PythonWoods-Dev Sep 18, 2026
9e3faa3
fix(links): a backslash-escaped bracket opens no link
PythonWoods-Dev Sep 18, 2026
fe29614
docs(changelog): the escape fix, and the upgrade notice for documenta…
PythonWoods-Dev Sep 18, 2026
f418938
fix(ast): the tracker knows frontmatter, and setext headings exist
PythonWoods-Dev Sep 18, 2026
0660f0a
refactor(governance): the policy rules ask the tracker what frontmatt…
PythonWoods-Dev Sep 18, 2026
b184d15
fix(content): one heading recogniser, and eleven loops that stop reim…
PythonWoods-Dev Sep 18, 2026
7d9f83f
docs(changelog): two invisible constructs, and the upgrade notice tha…
PythonWoods-Dev Sep 18, 2026
9e11e65
fix(ast): a block quote is not a paragraph, and a message quotes a li…
PythonWoods-Dev Sep 18, 2026
4432b81
test(paths): a path compared as a string is normalised, and the shape…
PythonWoods-Dev Sep 18, 2026
b0ab43d
fix(html): three tag patterns stop ending at the first angle bracket
PythonWoods-Dev Sep 18, 2026
4170985
fix(html): a blank line closes a block, the tag list is the specifica…
PythonWoods-Dev Sep 18, 2026
cd64469
docs(changelog): two HTML-block defects that hid whole passages, and …
PythonWoods-Dev Sep 18, 2026
2323fc9
docs(changelog): name the published release the backtick defect is in
PythonWoods-Dev Sep 18, 2026
136f505
ci(corpus): pin an external corpus and compare its findings per code
PythonWoods-Dev Sep 18, 2026
a573a0a
fix(content): a tag in backticks is prose to three more readers
PythonWoods-Dev Sep 19, 2026
9d1ada5
feat(adapters): say so when a declared engine is replaced by standalone
PythonWoods-Dev Sep 19, 2026
587e606
ci(corpus): the pin declares its file count and fails when the tree m…
PythonWoods-Dev Sep 19, 2026
47cc3f6
docs(adapter): the Astro manifest carries the locale prefix, and MDX …
PythonWoods-Dev Sep 19, 2026
0e70085
fix(mdx): six constructs the engine read as prose, and the count goes…
PythonWoods-Dev Sep 19, 2026
6f766f9
fix(init): the generated config names the engines the registry holds
PythonWoods-Dev Sep 19, 2026
75a8f7c
docs(mdx): the warning this morning's fixes falsified, corrected the …
PythonWoods-Dev Sep 19, 2026
d12b421
docs(test): name the two slug implementations that disagree on attr-list
PythonWoods-Dev Sep 19, 2026
61aa270
fix(z107): a heading that declares its own anchor is seen, and three …
PythonWoods-Dev Sep 19, 2026
b70a0e6
feat(z503)!: snippet checking is opt-in, and a JSONC block is JSONC
PythonWoods-Dev Sep 19, 2026
eb88b99
feat(tests): an exemption expires when the condition it describes sto…
PythonWoods-Dev Sep 19, 2026
afd49e5
ci(corpus): pin a second corpus, and say what the baseline is a basel…
PythonWoods-Dev Sep 19, 2026
6372144
docs(changelog): two CommonMark constructs the block tracker reads wr…
PythonWoods-Dev Sep 19, 2026
afa5608
fix(ast)!: a tab opens an indented code block, as CommonMark §2.2 say…
PythonWoods-Dev Sep 19, 2026
d8b9fb1
feat(z115)!: a scan that examined nothing cannot exit 0, and a stale …
PythonWoods-Dev Sep 19, 2026
47f7dae
fix(engine)!: the product stops lying about its own configuration, an…
PythonWoods-Dev Sep 19, 2026
455329c
fix(tests): the Windows runner was reporting defects that did not exist
PythonWoods-Dev Sep 19, 2026
00a89da
fix(guard)!: a secret scan that opened nothing is inconclusive, not c…
PythonWoods-Dev Sep 19, 2026
9de59e0
feat(surfaces): name the generators where a user decides, and make th…
PythonWoods-Dev Sep 19, 2026
c8d93f0
fix(tests): assert the telemetry line, not the glyph that separates it
PythonWoods-Dev Sep 19, 2026
4f5e583
fix(tests): three assertions that described the behaviour the last tw…
PythonWoods-Dev Sep 19, 2026
6c820fa
feat(prebuilt)!: a declared engine without its artefact is a configur…
PythonWoods-Dev Sep 19, 2026
89db2cb
fix(mkdocs): the configuration filename is one answer, and it was eight
PythonWoods-Dev Sep 19, 2026
d99a008
fix(tests): the debounce test used a path that is not one on Windows
PythonWoods-Dev Sep 19, 2026
71a1b87
fix(score): a badge gate that moves with someone else's uptime
PythonWoods-Dev Sep 19, 2026
05f86c7
refactor(core): two questions that had eight answers between them
PythonWoods-Dev Sep 19, 2026
d0331ed
refactor(core): "what do we print for this file" had 24 answers
PythonWoods-Dev Sep 19, 2026
e22dbbb
fix(lsp): the editor excluded less than the CLI, measured both ways
PythonWoods-Dev Sep 19, 2026
23d1a68
refactor(core): D4 closes by naming three scheme sets, not by merging…
PythonWoods-Dev Sep 19, 2026
18ca542
test(lab): the fixture-purity inventory becomes a control
PythonWoods-Dev Sep 19, 2026
18352db
feat(output): the substitution notice reaches a consumer, and so does…
PythonWoods-Dev Sep 19, 2026
7faa2ec
fix(incremental): correct a claim I made by reading instead of executing
PythonWoods-Dev Sep 19, 2026
48fa335
docs(landing): two opt-in codes shown as defaults, and the generators…
PythonWoods-Dev Sep 20, 2026
f054f90
docs(landing): contrast to AA, engines out of the invariants section,…
PythonWoods-Dev Sep 20, 2026
ae051b7
docs(landing): three trailing sections into one, and a logo size that…
PythonWoods-Dev Sep 20, 2026
e38a4bc
style(docs): neutral links with a permanent underline, and 1.75 leading
PythonWoods-Dev Sep 20, 2026
48827cd
fix(landing): the tab bar's indigo, a missing marker column, an unclo…
PythonWoods-Dev Sep 20, 2026
a6948b1
fix(landing): restate Tailwind's scale on the wrapper, not the root
PythonWoods-Dev Sep 20, 2026
73fd71d
fix(docs): 99 list items that stopped mid-sentence, and the gate that…
PythonWoods-Dev Sep 20, 2026
84b13df
fix(docs): minify_css was inert, and Z111's config example did not parse
PythonWoods-Dev Sep 20, 2026
ce27d4e
fix(z503): the editor inherits the opt-in gate the CLI has honoured s…
PythonWoods-Dev Sep 20, 2026
297f267
test(activation): the opt-in declaration gets the enforcement the oth…
PythonWoods-Dev Sep 20, 2026
4a83a2f
fix(config): base_url does what its page promised, and init stops off…
PythonWoods-Dev Sep 20, 2026
09d692e
fix(tests): the new init-output module imported tomllib unconditionally
PythonWoods-Dev Sep 20, 2026
b0aa3a9
fix(landing): the section markers travel with their sections, and the…
PythonWoods-Dev Sep 20, 2026
bc863e2
docs(install): one hand-drawn frame becomes a table, and the Tip pane…
PythonWoods-Dev Sep 20, 2026
8f28c43
docs(diagrams): three ad-hoc drawings become Mermaid, and two path sp…
PythonWoods-Dev Sep 21, 2026
cdf2c05
build(css): pin the Tailwind CLI at the version the committed bundle …
PythonWoods-Dev Sep 21, 2026
392a67b
fix(security): guard scan stops bypassing the redaction it is told ne…
PythonWoods-Dev Sep 21, 2026
cc8a944
build(css): the input was v3 syntax fed to the v4 CLI, and 32 used cl…
PythonWoods-Dev Sep 21, 2026
b9de77a
feat(tooling): a gate that rebuilds a committed artifact and diffs it…
PythonWoods-Dev Sep 21, 2026
eb53730
docs(landing): the hero title was rendering at a browser default, and…
PythonWoods-Dev Sep 21, 2026
62c1021
fix(landing): a rule written for four metric numbers was capping seve…
PythonWoods-Dev Sep 21, 2026
e01fd99
feat(engine)!: a declared engine with no configuration is an error, e…
PythonWoods-Dev Sep 21, 2026
1e48b4c
fix(cli): the file count left out the trees `content_roots` declares
PythonWoods-Dev Sep 21, 2026
ac91711
fix(lsp): the site map lost its link graph on every rebuild
PythonWoods-Dev Sep 21, 2026
26e88ae
docs(tests): state the principle rather than citing the private rule
PythonWoods-Dev Sep 21, 2026
5867bf8
docs: say which library each reproduction was measured against
PythonWoods-Dev Sep 21, 2026
fdad52d
feat(tooling): a gate for the root documents no gate was reading
PythonWoods-Dev Sep 21, 2026
c5455c4
docs: point at the destination instead of the redirect
PythonWoods-Dev Sep 21, 2026
ed8926b
docs(reference): the route manifest's contract, measured not deduced
PythonWoods-Dev Sep 22, 2026
62dd714
feat(prebuilt): the manifest can declare its own anchors
PythonWoods-Dev Sep 22, 2026
609b170
docs(lsp): narrow a comment to the scope it actually defends
PythonWoods-Dev Sep 22, 2026
a7ef073
docs: five comments claimed more than the code does
PythonWoods-Dev Sep 22, 2026
95b2d01
fix(build): track package-lock.json so generated-provenance can verif…
PythonWoods-Dev Sep 28, 2026
74dfadd
fix(build): generated-provenance now requires the lockfile tracked, n…
PythonWoods-Dev Sep 28, 2026
8b4b7ec
fix(build): generated-provenance's lockfile check reads HEAD, not the…
PythonWoods-Dev Sep 28, 2026
2ef445b
docs(cli): the exit-code remap's scope is measured, not assumed
PythonWoods-Dev Sep 28, 2026
7004c9d
build(deps): lift urllib3 to 2.8.0 and virtualenv to 21.7.13 in the lock
PythonWoods-Dev Oct 13, 2026
8c49aa8
build(deps): require anyio>=4.14.2 and guard the host encoding it rec…
PythonWoods-Dev Oct 13, 2026
7f7df48
docs: file the anyio floor under Security and name anyio as a direct …
PythonWoods-Dev Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
59 changes: 44 additions & 15 deletions .bumpversion.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,28 +73,32 @@ filename = "RELEASE.md"
search = "- [ ] `pyproject.toml` version matches the tag (`{current_version}`)"
replace = "- [ ] `pyproject.toml` version matches the tag (`{new_version}`)"

[[tool.bumpversion.files]]
filename = "RELEASE.md"
search = 'git tag -s -m "Release v{current_version}" v{current_version}'
replace = 'git tag -s -m "Release v{new_version}" v{new_version}'


[[tool.bumpversion.files]]
filename = "RELEASE.md"
search = '(?m)^(\| Date\s+\|\s+)\d{{4}}-\d{{2}}-\d{{2}}(\s*\|)$'
replace = '| Date | {now:%Y-%m-%d} |'
regex = true

[[tool.bumpversion.files]]
filename = "RELEASE.md"
search = "- [ ] Create GitHub Release from the tag, using the `## [{current_version}]` CHANGELOG section as the release body."
replace = "- [ ] Create GitHub Release from the tag, using the `## [{new_version}]` CHANGELOG section as the release body."

[[tool.bumpversion.files]]
filename = ".github/ISSUE_TEMPLATE/security_vulnerability.yml"
search = 'placeholder: "{current_version}"'
replace = 'placeholder: "{new_version}"'

[[tool.bumpversion.files]]
# The supported-version table on the security page. Registered 2026-09-08 after
# an audit found it unregistered here and in zenzic-vscode, while zenzic-action
# had it right: at a tag both would have gone on claiming 0.30.x was current and
# the new release unsupported -- on the one page a user reads when deciding
# whether to upgrade after a vulnerability (ADR-092).
# The table writes `0.30.x`, not the full version, so the search uses the
# major/minor components; {current_version} would not match anything here.
filename = "SECURITY.md"
search = '''| `{current_major}.{current_minor}.x` (current) | ✅ All security fixes |
| `< {current_major}.{current_minor}` | ❌ Unsupported — upgrade to the current release |'''
replace = '''| `{new_major}.{new_minor}.x` (current) | ✅ All security fixes |
| `< {new_major}.{new_minor}` | ❌ Unsupported — upgrade to the current release |'''

[[tool.bumpversion.files]]
filename = ".github/ISSUE_TEMPLATE/bug_report.yml"
search = 'placeholder: "{current_version}"'
Expand All @@ -108,11 +112,6 @@ filename = ".pre-commit-hooks.yaml"
search = "rev: v{current_version}"
replace = "rev: v{new_version}"

[[tool.bumpversion.files]]
filename = "README.md"
search = '"version": "{current_version}"'
replace = '"version": "{new_version}"'

[[tool.bumpversion.files]]
filename = "README.md"
search = "uvx zenzic@{current_version} check all"
Expand All @@ -123,6 +122,11 @@ filename = "README.md"
search = "rev: v{current_version}"
replace = "rev: v{new_version}"

[[tool.bumpversion.files]]
filename = "README.md"
search = 'docs = ["zenzic~={current_version}"]'
replace = 'docs = ["zenzic~={new_version}"]'

# Manages the "Version baseline" annotation in the CLI reference so it never drifts.
[[tool.bumpversion.files]]
filename = "docs/reference/cli.md"
Expand Down Expand Up @@ -153,3 +157,28 @@ replace = 'version: "{new_version}"'
filename = "docs/how-to/configure-ci-cd.md"
search = "rev: v{current_version}"
replace = "rev: v{new_version}"

[[tool.bumpversion.files]]
filename = "docs/how-to/configure-ci-cd.md"
search = "uvx zenzic@{current_version} check all --ci"
replace = "uvx zenzic@{new_version} check all --ci"

[[tool.bumpversion.files]]
filename = "docs/how-to/install.md"
search = "rev: v{current_version}"
replace = "rev: v{new_version}"

[[tool.bumpversion.files]]
filename = "docs/how-to/install.md"
search = '"zenzic~={current_version}"'
replace = '"zenzic~={new_version}"'

[[tool.bumpversion.files]]
filename = "docs/how-to/install.md"
search = "uvx zenzic@{current_version} check all"
replace = "uvx zenzic@{new_version} check all"

[[tool.bumpversion.files]]
filename = "src/zenzic/cli/templates.py"
search = "# rev: v{current_version}"
replace = "# rev: v{new_version}"
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ body:
attributes:
value: |
Before opening a feature request, please open a discussion on the
[issue tracker](https://github.com/PythonWoods/zenzic/issues) so we can agree on
[issue tracker](https://github.com/PythonWoods-Dev/zenzic/issues) so we can agree on
the approach before you invest time implementing it.

- type: dropdown
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/security_vulnerability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ body:

If this vulnerability could expose user secrets or enable remote code execution,
**do NOT open a public issue.** Use GitHub's private
[Security Advisory](https://github.com/PythonWoods/zenzic/security/advisories/new)
[Security Advisory](https://github.com/PythonWoods-Dev/zenzic/security/advisories/new)
feature instead, or email **security@pythonwoods.dev**.

This public template is appropriate for:
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Fixes #

## Architectural Quality Gates (Core Python)

- [ ] **Mirror Law:** I have updated the finding codes catalog, scoring matrices, rule cards, and reference documentation if adding or modifying a rule.
- [ ] **Mirror Law:** I have updated all 10 mandatory targets (including the zenzic init template in templates.py and the VS Code IntelliSense schema in zenzic.schema.json) if adding or modifying a rule.
- [ ] **Local Quality Pipeline:** `just verify` (or `pytest tests/` + `zenzic check all --strict`) passes with 100% test pass rate and no DQS regression.
- [ ] **Zero Subprocess & Determinism:** No unauthorized subprocess executions, shell wrappers, or non-deterministic file I/O are introduced into the core analysis engine.
- [ ] **Custom Rule SDK v3:** If modifying the SDK or rule extensions, changes maintain backward compatibility with `ZenzicRuleV3`.
162 changes: 148 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ on:
- 'uv.lock'
- '.github/workflows/ci.yml'
pull_request:
# No label-driven types any more: the matrix below no longer depends on a
# label, so `labeled`/`unlabeled` would only start runs that report exactly
# what the previous run reported.
types: [opened, synchronize, reopened]
workflow_dispatch:

# Three pushes in five minutes paid for three full runs. This cancels the
# superseded ones, which on this workflow is ~19.6 billed minutes each.
#
# NOT on `main`, and that exclusion is the point: a cancelled run reports its
# checks as cancelled, not as passed, and `main` is where the release tag is cut
# from. Cancelling there could leave the branch without a completed required
# check at the moment it matters most. On a pull request the transient state is
# harmless — the superseding run re-reports every context, and a PR with a run
# in flight is not mergeable anyway.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

permissions:
contents: read
Expand All @@ -24,16 +42,46 @@ jobs:
strategy:
fail-fast: false
matrix:
# TEMPORARY EXCLUSION: Windows and Python 3.14 are temporarily excluded
# to accelerate CI cycle and isolate environment-specific runners.
# Original configuration:
# os: [ubuntu-latest, windows-latest]
# python-version: ["3.10", "3.14"]
# exclude:
# - os: windows-latest
# python-version: "3.14"
os: [ubuntu-latest]
python-version: ["3.10"]
# The matrix is unconditional, and that is a correctness requirement
# rather than a thoroughness preference.
#
# It briefly widened only on demand, while branch protection required
# contexts that only the wide form emits -- so an ordinary pull request
# could never satisfy them, and one sat with six green checks, `MERGEABLE`,
# and permanently `BLOCKED` on a context nothing would ever report. Either
# the required list matches what an ordinary run emits, or the wide run is
# not optional. Those are the only two coherent configurations; this is the
# second, and it needs nobody to remember anything.
#
# Windows earns its place on evidence, not symmetry: two failures this
# release were Windows-only and invisible on Linux -- a hand-built
# subprocess `env` that dropped `SystemRoot` and stopped Python starting,
# then `text=True` with no explicit codec failing to decode cp1252. Both
# were in test scaffolding, so both would have merged under a
# Linux-only gate.
#
# Measured cost of widening, from the real run at `7664533` rather than an
# estimate: ubuntu/3.14 124 s, ubuntu/3.10 171 s, windows/3.10 214 s. The
# jobs run in parallel, so the wall-clock a developer waits is 214 s
# against 124 s -- **+90 s** -- and the billed cost is ~12 minutes against
# ~2, Windows counting double.
#
# `fail-fast: false` above is kept on a count rather than a preference.
# Across 100 runs of this workflow on the v0.31.0 branch, 21 failed, and in
# 18 of those exactly one matrix job failed -- 16 of them `windows-latest`
# alone. The job that fails most often is also the slowest (214 s against
# 171 s and 124 s), so by the time it fails its siblings have finished and
# cancelling them would save almost nothing. What it would cost is the
# pattern: "only Windows failed" is the diagnosis, and it is legible only
# because the other two ran to completion.
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.14"]
exclude:
# Windows is exercised on the floor version only. The interpreter
# difference is what 3.14 tests; the platform difference is what Windows
# tests, and the cross product costs a fourth job to test both at once.
- os: windows-latest
python-version: "3.14"

steps:
- name: Checkout Repository
Expand All @@ -53,14 +101,100 @@ jobs:
- name: Sync dependencies
run: uv sync --all-groups --all-extras --frozen

- name: Run unit tests
run: just test
# pre-commit enforces this too, but only on staged files and only when
# it is not bypassed; a formatter check that never runs in CI let six
# unformatted files reach main. Authoritative version is pinned in
# pyproject.toml to match .pre-commit-config.yaml's ruff-pre-commit rev.
- name: Formatting check (ruff format)
run: uv run ruff format --check src tests

- name: Run unit tests (coverage enforced, fail_under=80)
run: just test-cov

# The credential scanner is the one path where a test that stops catching a
# regression is a security problem rather than a coverage statistic. The
# harness was configured in pyproject.toml but never invoked anywhere, so no
# score had ever been produced; this step is what makes it real.
- name: Run Zenzic Quality Gate
run: uv run zenzic check all --strict --format github-annotations --ci
# --no-external: the PR gate must not depend on third-party uptime.
# Observed on run 34860816951: Audit failed on three platforms with nine
# distinct URLs, every one a github.com timeout (>10 s) from the runner —
# not a broken link. A third-party outage is a real defect but it is not
# this pull request's regression, and it left #233 unmergeable.
# The external surface is swept on a schedule instead — see
# external-link-sweep.yml.
run: uv run zenzic check all --strict --no-external --format github-annotations --ci

- name: Build Docs
run: uv run mkdocs build --strict

- name: Redirects structure tripwire
run: python3 scripts/check_redirects_structure.py

- name: Blog link schedule gate
run: uv run python scripts/check_blog_link_schedule.py

# Determinism across interpreters is a Tier-0 invariant and nothing was
# checking it. `pathlib.PurePath.suffix` changed semantics in 3.12 and the
# engine inherited the change, so the same corpus could yield different
# findings on 3.10 and 3.14 -- for two years, with both legs green, because
# each was only ever compared against itself. This emits one digest per
# Ubuntu leg; the `determinism` job below diffs them. Ubuntu only: the
# comparison is about the interpreter, and adding Windows would conflate
# platform with version, which is precisely the confound that produced two
# wrong diagnoses while this was being found.
- name: Emit interpreter-determinism digest
if: runner.os == 'Linux'
run: uv run python scripts/check_interpreter_determinism.py --emit findings-py${{ matrix.python-version }}.json

- name: Upload interpreter-determinism digest
if: runner.os == 'Linux'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: determinism-py${{ matrix.python-version }}
path: findings-py${{ matrix.python-version }}.json
retention-days: 7

- name: Verify Badge Freshness
run: uv run zenzic score --check-stamp --ci
# --no-external, for the same reason the Quality Gate above uses it, and
# because this step was left out of that decision when it was taken.
# Measured on run 35455833729, commit d99a008: this step computed 89/100
# on Ubuntu 3.10 while the other two legs computed 97 on the same commit,
# and a re-run of that identical commit produced 97 everywhere. The gap is
# exactly one Z104 (penalty 8.0) -- an external URL that did not answer
# from that runner. A badge gate that moves with third-party uptime fails
# a pull request that changed nothing, and stamps a number that is not the
# repository's. The external surface is swept on a schedule instead.
run: uv run zenzic score --check-stamp --ci --no-external

# ── Interpreter determinism ────────────────────────────────────────────────
# The same corpus must produce the same findings on every supported Python.
# This costs a download and a diff, not a test run: both digests come from the
# matrix legs that already ran.
#
# Its limit is written into the script and repeated here because a green run
# here is not proof of determinism: it compares findings over the fixtures in
# `examples/`, so a divergence in an input shape no fixture contains is
# invisible to it. The defect that motivated it lived exactly there -- of the 35
# inputs where 3.10 and 3.14 disagreed, zero appear in this repository. This
# catches the next one, if that one touches a shape a fixture exercises.
determinism:
name: Interpreter Determinism
runs-on: ubuntu-latest
needs: verify
steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download 3.10 digest
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: determinism-py3.10

- name: Download 3.14 digest
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: determinism-py3.14

- name: Compare findings across interpreters
run: python3 scripts/check_interpreter_determinism.py --compare findings-py3.10.json findings-py3.14.json
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ jobs:

steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.7
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@v4.37.7
uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.37.7
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
59 changes: 59 additions & 0 deletions .github/workflows/external-corpus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2026 PythonWoods <dev@pythonwoods.dev>
# SPDX-License-Identifier: Apache-2.0
#
# A high score on our own repository measures agreement between an engine and
# the corpus it grew up with. This is the only independent measurement the
# project has, and it runs here rather than in the pre-push path: it clones a
# repository, and network dependence in a pre-push hook cost this project five
# failed pushes with no diagnosable output.
#
# On pull requests rather than on a schedule. Measured 2026-09-18: 7 s to fetch
# the pinned commit, 3 s to scan, against catching a regression on somebody
# else's documentation before merge instead of the next morning. The corpus is
# pinned, so it cannot move under a pull request.
#
# ── The blind spot, stated here and in the script ────────────────────────────
# A corpus pinned at a commit exercises the constructs that corpus contains.
# Four defects closed in this cycle moved neither corpus -- nested fences,
# backslash escapes, setext headings, and a frontmatter closed with YAML's
# document-end marker -- and each was covered by a probe and a suite instead.
# One pinned corpus is better than none and is not coverage.

name: External Corpus

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'src/**'
- 'scripts/external_corpus_check.py'
- 'scripts/external-corpus-pin.json'
- '.github/workflows/external-corpus.yml'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: external-corpus-${{ github.ref }}
cancel-in-progress: true

jobs:
corpus:
name: Findings per code, against the pinned corpus
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout Repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1

- name: Sync dependencies
run: uv sync --all-groups

- name: Compare the external corpus per code
run: uv run python3 scripts/external_corpus_check.py
Loading
Loading