claude-dev-env 8.36.3 → 8.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/codex-compatibility.md +1 -1
- package/docs/rule-guides/bdd.md +28 -0
- package/docs/rule-guides/code-standards.md +38 -0
- package/docs/rule-guides/correction-lens-excerpt.md +35 -0
- package/docs/rule-guides/doc-inventory-integrity.md +39 -0
- package/docs/rule-guides/docstring-prose-matches-implementation.md +49 -0
- package/docs/rule-guides/failure-blast-radius.md +123 -0
- package/docs/rule-guides/orphan-css-class.md +25 -0
- package/docs/rule-guides/paired-test-coverage.md +39 -0
- package/docs/rule-guides/plain-illustrative-docstrings.md +88 -0
- package/docs/rule-guides/windows-filesystem-safe.md +11 -0
- package/hooks/hooks.json +10 -0
- package/hooks/hooks_constants/spawn_readiness_hook_constants.py +111 -0
- package/hooks/routing/spawn_readiness_hook.py +195 -0
- package/hooks/routing/spawn_readiness_steps.py +266 -0
- package/hooks/routing/test_spawn_readiness_hook.py +278 -0
- package/hooks/routing/test_spawn_readiness_steps.py +87 -0
- package/package.json +1 -1
- package/rules/bdd.md +2 -23
- package/rules/code-standards.md +3 -32
- package/rules/correction-lens.md +2 -30
- package/rules/doc-inventory-integrity.md +6 -32
- package/rules/docstring-prose-matches-implementation.md +4 -42
- package/rules/failure-blast-radius.md +4 -114
- package/rules/orphan-css-class.md +4 -18
- package/rules/paired-test-coverage.md +4 -32
- package/rules/plain-illustrative-docstrings.md +4 -81
- package/rules/windows-filesystem-safe.md +3 -5
- package/scripts/codex_compat_materializer.py +4 -1
- package/scripts/tests/test_codex_compat_materializer.py +18 -18
|
@@ -11,120 +11,10 @@ paths:
|
|
|
11
11
|
|
|
12
12
|
**When this applies:** Batch code that processes assets, rows, accounts, messages, or files where one member can fail while the others are fine.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Every raise reached through per-member work names what stops. A type ending in `RunFatal` stops the whole run. A type ending in `ItemBlocked` stops one member, and the batch carries on. Define `RunFatal` outside the `ItemBlocked` branch, and put the `try`/`except` inside the loop body with the `RunFatal` re-raise first.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Four failures end a run: the source bytes changed, a provenance or digest mismatch, authentication is required, or a runtime crash such as `TypeError`. Work every other failure. Repair in place, take three attempts (three theories of the cause), then park the member and move on. The batch always reaches a deliverable. Three members parked with the same signature share one cause. The closing report gives every issue one line with how it ended, plus a recommended durable fix.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
**Enforcement:** `code_rules_blast_radius.py`, which the staged policy lint runs through `code_rules_enforcer.py` under its `code-rules` rule. CI runs it against the merge base.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## What ends a run
|
|
23
|
-
|
|
24
|
-
Four failures end a run, and they share one property: continuing compromises delivery integrity.
|
|
25
|
-
|
|
26
|
-
Three of them are declared, and carry a `RunFatal` type:
|
|
27
|
-
|
|
28
|
-
- The source bytes changed under the run.
|
|
29
|
-
- A provenance or digest comparison failed.
|
|
30
|
-
- Authentication is required.
|
|
31
|
-
|
|
32
|
-
The fourth is a runtime crash, such as `TypeError` or `AttributeError`. Its runtime type reaches run-level handling because the per-member boundary recognizes the contract's named types.
|
|
33
|
-
|
|
34
|
-
All remaining failures are member failures. A size mismatch, an optional input error, a path resolution error, or a manifest-field error each stops one member while the batch continues with its other members.
|
|
35
|
-
|
|
36
|
-
## The boundary
|
|
37
|
-
|
|
38
|
-
Put the `try`/`except` inside the loop body, around the per-member work:
|
|
39
|
-
|
|
40
|
-
```python
|
|
41
|
-
for each_member in all_members:
|
|
42
|
-
try:
|
|
43
|
-
remaster(each_member)
|
|
44
|
-
except AssetRunFatal:
|
|
45
|
-
raise
|
|
46
|
-
except AssetItemBlocked as failure:
|
|
47
|
-
park(each_member, failure)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The re-raise comes first, sending an escalation directly through the boundary.
|
|
51
|
-
|
|
52
|
-
**The boundary recognizes declared types.** A runtime crash inside member work — a `TypeError`, an `AttributeError` — ends the run because its type identifies a code defect. `except Exception` triggers the rule (`CODE_RULES.md` §31).
|
|
53
|
-
|
|
54
|
-
## Repair, park, and the deliverable
|
|
55
|
-
|
|
56
|
-
An agent that hits a member failure keeps working the problem. Three things bound how:
|
|
57
|
-
|
|
58
|
-
- **Repair in place.** The current run preserves every completed member.
|
|
59
|
-
- **Three attempts, then park.** An attempt is a theory of the cause, acted on. Re-running the same code on the same input counts as one attempt. Three theories cover the obvious cause, the second guess, and the cause revealed by the first two attempts. Judgment selects each theory; the third attempt sets the stopping point. After the third theory fails, park the member with its reason and move to the next. Parked members return after the batch.
|
|
60
|
-
- **The batch always reaches a deliverable.** Complete every member that can complete, produce the packaged artifact, then work the parked list. A run with 34 of 37 members complete and 3 parked records progress and continues to delivery.
|
|
61
|
-
|
|
62
|
-
## Three alike means one cause
|
|
63
|
-
|
|
64
|
-
When three or more members park with the same failure signature — same exception type, same `file:line` — one shared defect affects all three members. Route repair to the shared cause.
|
|
65
|
-
|
|
66
|
-
The run report groups parked members by that signature and names every group of three or more as a suspected shared cause. The raise site provides a reliable grouping key and removes message-text normalization.
|
|
67
|
-
|
|
68
|
-
## Close the run with every outcome
|
|
69
|
-
|
|
70
|
-
Every issue the run hit gets one line in the closing report: what failed, and how it ended — repaired, worked around, or parked. Members that finished after a repair belong in that list beside the parked ones. A workaround patched past mid-run is the likeliest defect in the batch, because the closing report becomes its durable record.
|
|
71
|
-
|
|
72
|
-
The report then presents each candidate to the owner for a durable-fix decision and names the fix the run recommends for each one. The report is a statement, not a gate: the current run completes its deliverable and ends. A later run builds whichever fixes the owner selects, and the recommendation stands as the default for any the owner does not rule on.
|
|
73
|
-
|
|
74
|
-
## Enforcement
|
|
75
|
-
|
|
76
|
-
`code_rules_blast_radius.py` runs inside `code_rules_enforcer.py`, which the staged policy lint applies to each changed file under its `code-rules` rule. No write-time hook runs it, so CI is where it reports. CI runs the same lint against the merge base.
|
|
77
|
-
|
|
78
|
-
The check requires each raised type written directly inside a loop body to end in `RunFatal` or `ItemBlocked`. The lexical check covers raises written directly in loop bodies. Shared helpers carry multiple caller contexts, so their callers classify the boundary.
|
|
79
|
-
|
|
80
|
-
Findings compare each changed file against its baseline. A raise already present in the baseline stays accepted, so the check reports only newly written raises.
|
|
81
|
-
|
|
82
|
-
## Excerpt for repository-instruction sessions
|
|
83
|
-
|
|
84
|
-
Codex reads its repository `AGENTS.md`; this excerpt supplies the standalone failure-handling contract.
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
Failure handling for this run — from rules/failure-blast-radius.md.
|
|
88
|
-
|
|
89
|
-
Keep solving problems. You own the fix. Each blast radius sets the repair
|
|
90
|
-
scope, and the deliverable remains the run priority.
|
|
91
|
-
|
|
92
|
-
Repair in place and preserve every completed asset in the current run.
|
|
93
|
-
|
|
94
|
-
Three attempts, then park. An attempt is a theory of the cause, acted
|
|
95
|
-
on. Repeated execution of one theory remains one attempt.
|
|
96
|
-
Three theories cover the obvious cause, the second guess, and the cause
|
|
97
|
-
revealed by the first two attempts. Use your judgment to select each theory;
|
|
98
|
-
the third attempt sets the stopping point. After the third theory fails, park
|
|
99
|
-
the asset with its reason and continue. Parked assets return after the batch.
|
|
100
|
-
|
|
101
|
-
The batch always reaches a deliverable. Finish every asset you can, produce
|
|
102
|
-
the packaged artifact, then work the parked list.
|
|
103
|
-
|
|
104
|
-
When three or more assets fail the same way, one shared defect affects all
|
|
105
|
-
three assets. Route repair to the shared cause.
|
|
106
|
-
|
|
107
|
-
Four things end a run outright: the source bytes changed, a provenance or
|
|
108
|
-
digest mismatch, authentication is required, or the code crashed with a
|
|
109
|
-
runtime failure that requires run-level handling. Work every other failure. The
|
|
110
|
-
named-type boundary defines the accepted exception handling; broad `except`
|
|
111
|
-
handling triggers the rule.
|
|
112
|
-
|
|
113
|
-
When you add a check that raises, name what it stops. End the type in
|
|
114
|
-
RunFatal when the whole run stops, or ItemBlocked when a single asset
|
|
115
|
-
stops. For an asset-level stop, put the handling inside the loop body.
|
|
116
|
-
|
|
117
|
-
Close the run by reporting what broke and what you did about it. Every issue
|
|
118
|
-
gets one line: what failed, and how it ended — repaired, worked around, or
|
|
119
|
-
parked. Include the ones you solved; a workaround you patched past in attempt
|
|
120
|
-
two is the likeliest defect in the list, because the closing report becomes
|
|
121
|
-
its durable record.
|
|
122
|
-
Present each candidate for the owner's durable-fix decision and state which
|
|
123
|
-
fix you recommend for each and why. Do not wait on the answer. Complete this
|
|
124
|
-
run's deliverable and end. A later run builds whichever fixes the owner
|
|
125
|
-
selects, and your recommendation stands as the default for the rest.
|
|
126
|
-
|
|
127
|
-
Report as: N of M complete, K parked, and what you are working now.
|
|
128
|
-
Close with: what broke, how each one ended, and which of them deserve a
|
|
129
|
-
durable fix.
|
|
130
|
-
```
|
|
20
|
+
**Full text:** [`docs/rule-guides/failure-blast-radius.md`](../docs/rule-guides/failure-blast-radius.md), with the boundary example and the Codex excerpt.
|
|
@@ -5,24 +5,10 @@ paths:
|
|
|
5
5
|
|
|
6
6
|
# Orphan CSS Class in Generated Markup
|
|
7
7
|
|
|
8
|
-
**When this applies:**
|
|
8
|
+
**When this applies:** Production `.py` that emits `class="..."` attributes in string literals beside a `<style>` block.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Every class the markup references has a `.<class>` selector in a nearby `<style>` block. Add the selector in the change that adds the class, and drop the class in the change that drops its selector.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Enforcement:** `check_orphan_css_classes` in `code_rules_orphan_css_class.py`, which the staged policy lint runs through `code_rules_enforcer.py`.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## What the check covers
|
|
17
|
-
|
|
18
|
-
The `check_orphan_css_classes` check in `code_rules_orphan_css_class.py` reaches production Python through the staged policy lint, whose `code-rules` rule loads `code_rules_enforcer.py` and applies it to each changed file. CI runs that lint against the merge base. It:
|
|
19
|
-
|
|
20
|
-
1. Collects each class name referenced in a `class="..."` attribute across the file's string literals.
|
|
21
|
-
2. Collects each class selector defined in a `<style>` block — both in the file under edit and in every Python module beside it (its own directory and immediate child directories), since a markup module commonly imports its style constant from a companion package directory.
|
|
22
|
-
3. Flags each referenced class with no matching selector in that whole set.
|
|
23
|
-
|
|
24
|
-
The check stays quiet for a file that emits no `class="..."` markup, and for a file whose markup has no `<style>` source nearby (its stylesheet lives outside the scan, so the check cannot judge it). Test files are exempt, since a fixture may carry intentional orphan markup.
|
|
25
|
-
|
|
26
|
-
## Why this check is mechanical
|
|
27
|
-
|
|
28
|
-
A class attribute with no matching selector reads as styled but renders unstyled. Native elements such as `<details>` stay functional without CSS, so the gap survives review as a cosmetic defect rather than a crash. That is the class of issue a manual pass slips past, and it lands later as a deferred code-standard finding. Running the lint on every staged change keeps the markup and the stylesheet in step.
|
|
14
|
+
**Full text:** [`docs/rule-guides/orphan-css-class.md`](../docs/rule-guides/orphan-css-class.md).
|
|
@@ -5,38 +5,10 @@ paths:
|
|
|
5
5
|
|
|
6
6
|
# Public-Function Paired-Test Coverage
|
|
7
7
|
|
|
8
|
-
**When this applies:**
|
|
8
|
+
**When this applies:** Writing a production Python module whose stem-matched test file (`test_<stem>.py` or `<stem>_test.py`) already exercises it, or writing that test file.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- A Write or Edit to a stem-matched test file (`test_<stem>.py` or `<stem>_test.py`) whose paired production module exists on disk and whose post-edit suite already exercises at least one of that module's public functions.
|
|
10
|
+
Every public function such a module defines gets a behavioral test in its paired suite. When you add a public function there, add a test that calls it and asserts on its return value or side effect in the same change. `main` and underscore-prefixed functions need none.
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
**Enforcement:** `check_public_function_missing_paired_test` and `check_test_file_omits_module_public_function` in `code_rules_paired_test.py`, which the staged policy lint runs through `code_rules_enforcer.py`. Both record smells per [`flag-non-breaking-findings.md`](flag-non-breaking-findings.md).
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
When you add a public function to a module whose test suite already exercises that module — covering a sibling public function, or testing one of its private helpers — add a behavioral test that calls the new function and asserts on its return value or side effect — in the same change that adds the function. A suite that exercises only a private helper (such as a color-conversion helper) while leaving the module's public renderers untested is the exact gap this rule closes. This is the function-level half of the project rule "Every new production code path gets a paired behavioral test ... call the path and assert on what it does."
|
|
18
|
-
|
|
19
|
-
## What the check covers
|
|
20
|
-
|
|
21
|
-
Two complementary checks in `code_rules_paired_test.py` reach changed files through `code_rules_enforcer.py`, which the staged policy lint runs under its `code-rules` rule. No write-time hook runs them, so CI is where they report, against the merge base. The two checks cover the two write orders.
|
|
22
|
-
|
|
23
|
-
Both checks record smells, per [`flag-non-breaking-findings.md`](flag-non-breaking-findings.md). `SEVERITY_BY_CHECK_ID` in `scripts/policy_lint/config/check_catalog_constants.py` declares `code-rules/paired-test-missing-function` and `code-rules/paired-test-omitted-function` as smells. The lint prints each finding as a warning, records it in `.claude/followups/smells.jsonl`, and exits zero when every finding is a warning. A later pull request adds the missing tests.
|
|
24
|
-
|
|
25
|
-
`check_public_function_missing_paired_test` runs on a production Python write or edit and flags a public function when all of these hold:
|
|
26
|
-
|
|
27
|
-
1. The target is production code — not a test module, hook infrastructure, config module, migration, workflow registry, or `__init__.py`.
|
|
28
|
-
2. A stem-matched test file exists for the module — `test_<stem>.py` or `<stem>_test.py` beside the module, or `test_<stem>.py` under an ancestor `tests/` directory.
|
|
29
|
-
3. That suite already exercises the module — referencing at least one public function the module defines, or referencing one of its private (underscore-prefixed) helper functions by name — the signature of a maintained per-module suite rather than a placeholder or unrelated test file.
|
|
30
|
-
4. The public function is referenced by no test file in the directory that holds the stem-matched test.
|
|
31
|
-
|
|
32
|
-
`check_test_file_omits_module_public_function` runs on a stem-matched test-file write or edit and closes the reverse order, in which the production module is written before its test file exists. It resolves the production module the written `test_<stem>.py` or `<stem>_test.py` file pairs with — beside the test file, or in the parent of the `tests/` directory that holds it — reads that module from disk, and flags every public function the post-edit suite references nowhere, subject to the same established-suite precondition (the suite already covers at least one of the module's public functions). A production module that is itself exempt — a test module, hook infrastructure, config module, migration, workflow registry, or `__init__.py` — is skipped.
|
|
33
|
-
|
|
34
|
-
A public function counts as covered when its name appears — imported, called, or named — in any `test_*.py` or `*_test.py` file in the suite directory, so a function exercised by a differently-named sibling test still counts. `main` and underscore-prefixed functions are never required to carry a test.
|
|
35
|
-
|
|
36
|
-
## Relationship to the file-level TDD order
|
|
37
|
-
|
|
38
|
-
No hook or lint checks the order in which a test and its module are written. Red-green-refactor is the default loop, and the TDD skill (`pstack:tdd`) carries it; a prototype may run ahead of its tests and adds them before the pull request goes ready. This check judges coverage one function at a time for a module that already carries a stem-matched test file, and reports any public function that file leaves uncovered.
|
|
39
|
-
|
|
40
|
-
## Why this check is mechanical
|
|
41
|
-
|
|
42
|
-
A public function with no test reads as covered when the module's test file sits right beside it and exercises its siblings. The gap survives review because the suite looks complete. Running the lint on every staged change records each gap in the follow-up ledger, where `cde followup list` names it until a pull request closes it.
|
|
14
|
+
**Full text:** [`docs/rule-guides/paired-test-coverage.md`](../docs/rule-guides/paired-test-coverage.md).
|
|
@@ -5,87 +5,10 @@ paths:
|
|
|
5
5
|
|
|
6
6
|
# Plain, Illustrative Docstrings
|
|
7
7
|
|
|
8
|
-
**When this applies:**
|
|
8
|
+
**When this applies:** Writing or editing the narrative prose of a public function, method, class, or module docstring, the text before its first `Args:` section.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Write the narrative so a general developer follows it on the first read. Paint a concrete scene in short sentences, one idea each. Name what the reader sees and why it matters. Say what a thing is. Once the explanation grows past two or three lines, use one summary line, then a `::` literal block or a doctest that shows the input and the outcome, then two or three short lines, then the Google `Args:` and `Returns:` sections.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Enforcement:** `check_docstring_runon_sentence` and `check_docstring_prose_wall_without_illustration` in `code_rules_docstrings.py`, which the staged policy lint runs through `code_rules_enforcer.py`. Category O sub-bucket O9 of the audit rubric carries the judgment.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- **Brief.** The narrative makes its point in few words. Short sentences, each carrying one idea.
|
|
16
|
-
|
|
17
|
-
Two shapes break the standard. Hold the prose clear of both:
|
|
18
|
-
|
|
19
|
-
- **No machinery nouns stacked into a wall.** A sentence that chains abstract machinery terms (`the SIGINT install/restore/installability check, the atexit terminal-record registration, and the interrupted-run finalizer`) names parts without painting a scene. Name what the reader sees and why it matters.
|
|
20
|
-
- **No defining by negation.** Prose that explains a thing by what it is not (`the non-promoter-specific machinery`) leaves the reader without a picture. Say what the thing is.
|
|
21
|
-
|
|
22
|
-
## Shape: a summary line, then a diagram
|
|
23
|
-
|
|
24
|
-
The clearest way to be illustrative is to show a worked example, not describe one. A docstring that lands well reads in four parts:
|
|
25
|
-
|
|
26
|
-
1. **One summary line** that says what the code does.
|
|
27
|
-
2. **A diagram block** that carries the explanation by sight — a reStructuredText literal block (a line ending in `::`, then an indented example) or a doctest (`>>>`). Give the concrete input, mark the outcome, and add `ok:` / `flag:` contrast lines where a pass-and-fail pair makes the point. Keep the diagram clear of a bare number or an ALL-CAPS `NAME = value` line, which read as a magic value or a stray constant to a line-based lint pass.
|
|
28
|
-
3. **A couple of short narrative lines** after the block — two or three at most.
|
|
29
|
-
4. **The Google `Args:` / `Returns:` sections.**
|
|
30
|
-
|
|
31
|
-
Keep the wording neutral in the diagram and the prose: two names "contradict" or "clash", two names "agree". Skip words that pass judgment.
|
|
32
|
-
|
|
33
|
-
Canonical example:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
Flag a boolean assignment whose target and callee assert opposite polarity.
|
|
37
|
-
|
|
38
|
-
::
|
|
39
|
-
|
|
40
|
-
is_inside_allowed = _point_hits_any_forbidden(...)
|
|
41
|
-
^^^^^^^ ^^^^^^^^^
|
|
42
|
-
allowed vs. forbidden ⚠ the two names clash
|
|
43
|
-
ok: is_inside_allowed = _point_inside_allowed_region(...)
|
|
44
|
-
flag: is_inside_allowed = _point_hits_any_forbidden(...)
|
|
45
|
-
|
|
46
|
-
The target token and the callee token contradict each other, so the reader
|
|
47
|
-
cannot tell which name states the truth. Rename the callee to a neutral form
|
|
48
|
-
the two names agree on at every call site.
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The live version of this docstring sits on `check_polarity_name_contradiction` in `~/.claude/hooks/blocking/code_rules_naming_collection.py`.
|
|
52
|
-
|
|
53
|
-
A short narrative with no diagram is fine when a couple of plain sentences carry the whole picture. The diagram earns its place once the explanation grows past what two or three lines hold — the moment a wall of prose starts to form.
|
|
54
|
-
|
|
55
|
-
## What to check before you write the docstring
|
|
56
|
-
|
|
57
|
-
Read the narrative back as a stranger would:
|
|
58
|
-
|
|
59
|
-
- Does one sentence run long while joining clauses with an em-dash or a semicolon? That is the wall mark — break it into short sentences.
|
|
60
|
-
- Does the prose name a concrete moment, input, and outcome, or only abstract parts?
|
|
61
|
-
- Does any sentence define the thing by what it is not? Rewrite it to say what the thing is.
|
|
62
|
-
|
|
63
|
-
## Worked example
|
|
64
|
-
|
|
65
|
-
A dense wall — one long sentence, machinery nouns, a term defined by negation:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Owns the SIGINT install/restore/installability check, the atexit terminal-record
|
|
69
|
-
registration, and the interrupted-run finalizer — the non-promoter-specific
|
|
70
|
-
machinery that brackets a run so the JSONL artifact always carries a terminal
|
|
71
|
-
record and an in-flight theme record on interrupt.
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
The same contract, plain and illustrative:
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
Make sure a run's log always records how it ended.
|
|
78
|
-
|
|
79
|
-
So when you reopen the report, the last line tells you the truth: the run
|
|
80
|
-
finished cleanly, or you hit Ctrl-C while theme 42 was processing, or it died
|
|
81
|
-
on an unexpected error. Without this, a killed run looks identical to a clean
|
|
82
|
-
one — and you're debugging blind.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Enforcement
|
|
86
|
-
|
|
87
|
-
Two surfaces carry this standard:
|
|
88
|
-
|
|
89
|
-
- **Lint (the run-on backstop).** `check_docstring_runon_sentence` in `code_rules_docstrings.py` flags the one mechanical mark of a wall, a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. The staged policy lint reaches it through `code_rules_enforcer.py`, and no write-time hook runs it. A mechanical check cannot judge whether prose paints a picture, so it catches only this structural mark. It reads the narrative through a shared partition that sets aside any `::` literal block and any doctest, so a diagram's own arrows and dashes never count against the sentence.
|
|
90
|
-
- **Lint (the prose-wall backstop).** `check_docstring_prose_wall_without_illustration` in the same module flags a narrative that runs more than six prose lines with no diagram block. It marks the wall so the writer shows the behavior with a `::` example or a doctest and trims the prose to a few short lines. It cannot judge whether the diagram illustrates well, and that stays with the audit lane.
|
|
91
|
-
- **Audit (the judgment lane).** Category O sub-bucket O9 in `~/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` carries the illustrative-and-brief judgment the lint cannot. The audit teammate reads each changed docstring's narrative and asks whether a general developer follows it on the first read.
|
|
14
|
+
**Full text:** [`docs/rule-guides/plain-illustrative-docstrings.md`](../docs/rule-guides/plain-illustrative-docstrings.md), with the canonical example.
|
|
@@ -8,10 +8,8 @@ paths:
|
|
|
8
8
|
|
|
9
9
|
# Windows Filesystem Safety
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Call `shutil.rmtree` with an `onexc` (Python 3.12 and later) or `onerror` handler that runs `os.chmod(target_path, stat.S_IWRITE)` and retries. `ignore_errors=True` leaves read-only trees on disk. In Node, call `mkdirSync(targetPath, { recursive: true })` on a path that may exist. Import the shared `force_rmtree` handler trio from its one shared Windows-filesystem module.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**Enforcement:** the staged policy lint's `rmtree-safety` rule, which returns the full `force_rmtree` code. CI runs it against the merge base.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Define the safe handler trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`, and the `inspect.signature` onexc/onerror guard) once in a shared Windows-filesystem utility module, and import it from every call site. A second local copy drifts from the first, so a fix lands in one and the other keeps the bug (CODE_RULES.md, CORE PRINCIPLES, "Reuse before create"). The same `rmtree-safety` lint rule reports a local re-definition of any trio member outside the shared home and points the writer at the import. This complements the same-directory `check_duplicate_function_body_across_files` check, which a copy between two distant packages slips past.
|
|
15
|
+
**Full text:** [`docs/rule-guides/windows-filesystem-safe.md`](../docs/rule-guides/windows-filesystem-safe.md).
|
|
@@ -27,7 +27,10 @@ publish_plan_max_positional_arguments = 3
|
|
|
27
27
|
publish_plan_failure_injector_position = 2
|
|
28
28
|
frontmatter_unsupported_fields = ("tools", "model", "color", "disable-model-invocation")
|
|
29
29
|
instruction_alias_filenames = frozenset({"AGENTS.md", "CLAUDE.md"})
|
|
30
|
-
codex_instruction_rule_relative_paths = (
|
|
30
|
+
codex_instruction_rule_relative_paths = (
|
|
31
|
+
"docs/rule-guides/failure-blast-radius.md",
|
|
32
|
+
"docs/rule-guides/correction-lens-excerpt.md",
|
|
33
|
+
)
|
|
31
34
|
codex_instruction_source_separator = ", "
|
|
32
35
|
codex_instruction_target_path = "AGENTS.md"
|
|
33
36
|
codex_instruction_section_heading = "## Excerpt for repository-instruction sessions"
|
|
@@ -174,8 +174,8 @@ def test_public_legacy_call_forms_validate_collection_entries(tmp_path: Path) ->
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def test_render_codex_instruction_excerpt_requires_the_excerpt_heading() -> None:
|
|
177
|
-
with pytest.raises(MaterializerError, match="
|
|
178
|
-
materializer.render_codex_instruction_excerpt("# No excerpt\n", "
|
|
177
|
+
with pytest.raises(MaterializerError, match="docs/rule-guides/correction-lens-excerpt.md requires a Codex excerpt"):
|
|
178
|
+
materializer.render_codex_instruction_excerpt("# No excerpt\n", "docs/rule-guides/correction-lens-excerpt.md")
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
def test_validation_rejects_reparse_point_from_portable_attribute_seam(
|
|
@@ -820,11 +820,11 @@ def test_frontmatter_rejects_non_string_name() -> None:
|
|
|
820
820
|
|
|
821
821
|
|
|
822
822
|
def test_failure_blast_radius_projection_uses_the_canonical_excerpt() -> None:
|
|
823
|
-
canonical_rule_path = Path(__file__).parents[2] / "
|
|
823
|
+
canonical_rule_path = Path(__file__).parents[2] / "docs" / "rule-guides" / "failure-blast-radius.md"
|
|
824
824
|
canonical_rule = canonical_rule_path.read_text(encoding="utf-8")
|
|
825
825
|
|
|
826
826
|
projected_instruction = materializer.render_codex_instruction_excerpt(
|
|
827
|
-
canonical_rule, "
|
|
827
|
+
canonical_rule, "docs/rule-guides/failure-blast-radius.md"
|
|
828
828
|
)
|
|
829
829
|
|
|
830
830
|
assert projected_instruction.startswith("Failure handling for this run")
|
|
@@ -833,12 +833,12 @@ def test_failure_blast_radius_projection_uses_the_canonical_excerpt() -> None:
|
|
|
833
833
|
|
|
834
834
|
|
|
835
835
|
def test_codex_instruction_projection_carries_every_listed_rule(tmp_path: Path) -> None:
|
|
836
|
-
|
|
836
|
+
package_root = Path(__file__).parents[2]
|
|
837
837
|
source = tmp_path / "source"
|
|
838
838
|
target = tmp_path / "target"
|
|
839
|
-
(source / "rules").mkdir(parents=True)
|
|
840
839
|
for each_relative_path in materializer.codex_instruction_rule_relative_paths:
|
|
841
|
-
canonical_path =
|
|
840
|
+
canonical_path = package_root / each_relative_path
|
|
841
|
+
(source / each_relative_path).parent.mkdir(parents=True, exist_ok=True)
|
|
842
842
|
(source / each_relative_path).write_text(
|
|
843
843
|
canonical_path.read_text(encoding="utf-8"), encoding="utf-8"
|
|
844
844
|
)
|
|
@@ -851,17 +851,17 @@ def test_codex_instruction_projection_carries_every_listed_rule(tmp_path: Path)
|
|
|
851
851
|
assert "Failure handling for this run" in projected_instruction
|
|
852
852
|
assert "Correction handling for this run" in projected_instruction
|
|
853
853
|
manifest_record = _manifest_files(_required_manifest_path(config))["AGENTS.md"]
|
|
854
|
-
assert manifest_record["source"] == "
|
|
854
|
+
assert manifest_record["source"] == "docs/rule-guides/failure-blast-radius.md, docs/rule-guides/correction-lens-excerpt.md"
|
|
855
855
|
|
|
856
856
|
|
|
857
857
|
def test_codex_instruction_projection_skips_an_absent_listed_rule(
|
|
858
858
|
tmp_path: Path,
|
|
859
859
|
) -> None:
|
|
860
|
-
|
|
860
|
+
guides_root = Path(__file__).parents[2] / "docs" / "rule-guides"
|
|
861
861
|
source = tmp_path / "source"
|
|
862
|
-
(source / "
|
|
863
|
-
(source / "
|
|
864
|
-
(
|
|
862
|
+
(source / "docs" / "rule-guides").mkdir(parents=True)
|
|
863
|
+
(source / "docs" / "rule-guides" / "correction-lens-excerpt.md").write_text(
|
|
864
|
+
(guides_root / "correction-lens-excerpt.md").read_text(encoding="utf-8"),
|
|
865
865
|
encoding="utf-8",
|
|
866
866
|
)
|
|
867
867
|
config = MaterializerConfig(source, tmp_path / "target", should_apply=False)
|
|
@@ -869,7 +869,7 @@ def test_codex_instruction_projection_skips_an_absent_listed_rule(
|
|
|
869
869
|
projection = materializer._build_codex_instruction_projection(config)
|
|
870
870
|
|
|
871
871
|
assert projection is not None
|
|
872
|
-
assert projection.source_identity == "
|
|
872
|
+
assert projection.source_identity == "docs/rule-guides/correction-lens-excerpt.md"
|
|
873
873
|
assert "Correction handling for this run" in projection.content
|
|
874
874
|
assert "Failure handling for this run" not in projection.content
|
|
875
875
|
|
|
@@ -877,10 +877,10 @@ def test_codex_instruction_projection_skips_an_absent_listed_rule(
|
|
|
877
877
|
def test_build_plan_publishes_owned_agents_projection_and_tracks_drift(
|
|
878
878
|
tmp_path: Path,
|
|
879
879
|
) -> None:
|
|
880
|
-
canonical_rule_path = Path(__file__).parents[2] / "
|
|
880
|
+
canonical_rule_path = Path(__file__).parents[2] / "docs" / "rule-guides" / "failure-blast-radius.md"
|
|
881
881
|
source = tmp_path / "source"
|
|
882
882
|
target = tmp_path / "target"
|
|
883
|
-
rule_path = source / "
|
|
883
|
+
rule_path = source / "docs" / "rule-guides" / "failure-blast-radius.md"
|
|
884
884
|
rule_path.parent.mkdir(parents=True)
|
|
885
885
|
rule_path.write_text(canonical_rule_path.read_text(encoding="utf-8"), encoding="utf-8")
|
|
886
886
|
config = MaterializerConfig(source, target, should_apply=True)
|
|
@@ -892,7 +892,7 @@ def test_build_plan_publishes_owned_agents_projection_and_tracks_drift(
|
|
|
892
892
|
first_projection = projected_path.read_text(encoding="utf-8")
|
|
893
893
|
manifest_record = _manifest_files(_required_manifest_path(config))["AGENTS.md"]
|
|
894
894
|
assert manifest_record["ownership"] == "codex-compat"
|
|
895
|
-
assert manifest_record["source"] == "
|
|
895
|
+
assert manifest_record["source"] == "docs/rule-guides/failure-blast-radius.md"
|
|
896
896
|
|
|
897
897
|
rule_path.write_text(
|
|
898
898
|
rule_path.read_text(encoding="utf-8").replace("Three attempts, then park.", "Three tested attempts, then park."),
|
|
@@ -944,12 +944,12 @@ def _prepare_projection_fixture(
|
|
|
944
944
|
) -> tuple[Path, Path, dict[str, Any], Path]:
|
|
945
945
|
source = tmp_path / "source"
|
|
946
946
|
target = tmp_path.parent / "codex-prod-target"
|
|
947
|
-
source_rule = source / "
|
|
947
|
+
source_rule = source / "docs" / "rule-guides" / "failure-blast-radius.md"
|
|
948
948
|
source_hooks = source / "hooks" / "hooks.json"
|
|
949
949
|
source_rule.parent.mkdir(parents=True)
|
|
950
950
|
source_hooks.parent.mkdir(parents=True)
|
|
951
951
|
source_rule.write_text(
|
|
952
|
-
(Path(__file__).parents[2] / "
|
|
952
|
+
(Path(__file__).parents[2] / "docs" / "rule-guides" / "failure-blast-radius.md").read_text(
|
|
953
953
|
encoding="utf-8"
|
|
954
954
|
),
|
|
955
955
|
encoding="utf-8",
|