claude-dev-env 1.50.4 → 1.52.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.
Files changed (73) hide show
  1. package/CLAUDE.md +0 -8
  2. package/_shared/pr-loop/audit-contract.md +3 -3
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/preflight_self_heal_constants.py +28 -0
  4. package/_shared/pr-loop/scripts/preflight.py +18 -6
  5. package/_shared/pr-loop/scripts/preflight_self_heal.py +164 -0
  6. package/_shared/pr-loop/scripts/tests/test_preflight.py +39 -0
  7. package/_shared/pr-loop/scripts/tests/test_preflight_self_heal.py +273 -0
  8. package/agents/clean-coder.md +1 -1
  9. package/agents/code-quality-agent.md +7 -5
  10. package/audit-rubrics/category_rubrics/category-a-api-contracts.md +3 -0
  11. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +3 -0
  12. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +8 -2
  13. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +3 -0
  14. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +39 -0
  15. package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +40 -0
  16. package/audit-rubrics/prompts/category-a-api-contracts.md +11 -4
  17. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
  18. package/audit-rubrics/prompts/category-c-resource-cleanup.md +1 -1
  19. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +1 -1
  20. package/audit-rubrics/prompts/category-e-dead-code.md +1 -1
  21. package/audit-rubrics/prompts/category-f-silent-failures.md +13 -2
  22. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +1 -1
  23. package/audit-rubrics/prompts/category-h-security-boundaries.md +1 -1
  24. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  25. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -1
  26. package/audit-rubrics/prompts/category-k-codebase-conflicts.md +15 -5
  27. package/audit-rubrics/prompts/category-l-behavior-equivalence.md +1 -1
  28. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +1 -1
  29. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +10 -3
  30. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +74 -0
  31. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +75 -0
  32. package/docs/CODE_RULES.md +24 -346
  33. package/hooks/blocking/code_rules_enforcer.py +367 -42
  34. package/hooks/blocking/tdd_enforcer.py +211 -19
  35. package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +519 -0
  36. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
  37. package/hooks/blocking/test_tdd_enforcer.py +399 -0
  38. package/hooks/hooks.json +0 -15
  39. package/hooks/hooks_constants/code_rules_enforcer_constants.py +5 -0
  40. package/package.json +1 -1
  41. package/rules/ask-user-question-required.md +2 -41
  42. package/rules/confirm-implementation-forks.md +3 -44
  43. package/rules/gh-body-file.md +2 -78
  44. package/rules/gh-paginate.md +2 -78
  45. package/rules/plain-language.md +2 -41
  46. package/rules/prompt-workflow-context-controls.md +9 -38
  47. package/rules/shell-invocation-policy.md +2 -141
  48. package/rules/testing.md +10 -0
  49. package/rules/vault-context.md +3 -32
  50. package/rules/windows-filesystem-safe.md +3 -87
  51. package/scripts/sync_to_cursor/rules.py +201 -79
  52. package/scripts/tests/test_sync_to_cursor.py +122 -26
  53. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +2 -0
  54. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +51 -4
  55. package/skills/auditing-claude-config/SKILL.md +6 -1
  56. package/skills/bugteam/CONSTRAINTS.md +1 -1
  57. package/skills/bugteam/PROMPTS.md +8 -6
  58. package/skills/bugteam/SKILL.md +5 -5
  59. package/skills/bugteam/reference/audit-and-teammates.md +1 -1
  60. package/skills/bugteam/reference/audit-contract.md +4 -4
  61. package/skills/bugteam/reference/design-rationale.md +1 -1
  62. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +1 -1
  63. package/skills/bugteam/reference/team-setup.md +17 -5
  64. package/skills/bugteam/scripts/bugteam_preflight.py +22 -10
  65. package/skills/bugteam/scripts/test_bugteam_preflight.py +32 -0
  66. package/skills/copilot-review/SKILL.md +5 -8
  67. package/skills/doc-gist/SKILL.md +5 -8
  68. package/skills/fixbugs/SKILL.md +1 -1
  69. package/skills/gh-paginate/SKILL.md +84 -0
  70. package/skills/pre-compact/SKILL.md +4 -9
  71. package/skills/refine/SKILL.md +8 -2
  72. package/skills/structure-prompt/SKILL.md +5 -10
  73. package/skills/update/SKILL.md +143 -0
@@ -1,79 +1,3 @@
1
- # gh API Pagination Rule
1
+ # gh API Pagination
2
2
 
3
- **Root cause:** GitHub REST API list endpoints paginate by default. Without `--paginate --slurp`, callers see only the oldest page, and cross-page jq operations (e.g., `sort_by | last`) operate within a single page producing wrong-but-confident results.
4
-
5
- **Rule:** All `gh api` calls that read `pulls/<number>/reviews`, `pulls/<number>/comments`, `issues/<number>/comments`, or any other paginated GitHub list endpoint **must** request the full set of pages AND apply any cross-page jq operation through external `jq`, not through `gh`'s built-in `--jq`. Use `--paginate --slurp | jq` (preferred — see [Safe patterns](#safe-patterns)). Never call these endpoints with their default pagination, and never use `gh`'s `--jq` for cross-page operations like `sort_by | last` or `| reverse | .[0]`.
6
-
7
- ## Two defects, one rule
8
-
9
- This rule guards against two distinct silent-truncation defects that compound:
10
-
11
- 1. **Default page truncation.** Without `--paginate`, only the first page is fetched.
12
- 2. **`--jq` runs per-page, not on the concatenated result.** Per [GitHub CLI #10459](https://github.com/cli/cli/issues/10459), `gh api --paginate --jq '<filter>'` applies `<filter>` to each page **separately** and emits one output per page. Cross-page operations like `sort_by(.submitted_at) | last` therefore operate within each page independently, not across the merged result set.
13
-
14
- The safe patterns below fix both defects together: `--paginate --slurp` walks every page AND emits a single merged structure, and an **external** `jq` then runs cross-page operations on that merged structure.
15
-
16
- ## Affected endpoints
17
-
18
- The rule applies to every paginated read from the GitHub REST API. Common offenders in this repo's PR-loop skills:
19
-
20
- - `gh api repos/<owner>/<repo>/pulls/<number>/reviews`
21
- - `gh api repos/<owner>/<repo>/pulls/<number>/comments`
22
- - `gh api repos/<owner>/<repo>/pulls/<number>/files`
23
- - `gh api repos/<owner>/<repo>/issues/<number>/comments`
24
- - `gh api repos/<owner>/<repo>/pulls`
25
- - `gh api repos/<owner>/<repo>/issues`
26
-
27
- The same rule applies to any other endpoint documented as paginated by GitHub (see [GitHub REST API pagination](https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api)).
28
-
29
- Single-object endpoints (e.g., `repos/<owner>/<repo>/pulls/<number>` returning one PR object) are not paginated — `?per_page=...` is silently ignored, and neither `--paginate` nor external `jq` is required. Use `gh`'s `--jq` directly on those endpoints.
30
-
31
- ## Safe patterns
32
-
33
- ### Preferred — `--paginate --slurp` piped to external `jq`
34
-
35
- `gh --paginate --slurp` walks every page and emits a single merged JSON array of page-arrays (`[[page1_items...], [page2_items...], ...]`). Pipe to external `jq` to flatten and filter across the full result set:
36
-
37
- ```bash
38
- gh api 'repos/<owner>/<repo>/pulls/<number>/reviews?per_page=100' --paginate --slurp \
39
- | jq '[.[][] | select(.user.login=="cursor[bot]")] | sort_by(.submitted_at) | last'
40
- ```
41
-
42
- The `.[][]` flattens the array-of-pages into one stream of items before the cross-page operators (`sort_by`, `last`, `reverse`) run. Combine with `?per_page=100` to reduce round-trips on long PRs.
43
-
44
- `gh`'s `--jq` flag and `--slurp` flag are mutually exclusive (gh CLI rejects `--paginate --slurp --jq` with `the --slurp option is not supported with --jq or --template`), which is why the filter must run in an external `jq` invocation.
45
-
46
- ### Acceptable — single-page bound on a paginated list endpoint when result fits
47
-
48
- When you have an explicit reason to read at most one page from a **paginated** list endpoint (e.g., a known-small list), document the bound in a comment and use `?per_page=100` without `--paginate`. Cross-page operators are not in play here, so `gh`'s `--jq` is safe:
49
-
50
- ```bash
51
- # Bound: a freshly created issue is expected to have <= 100 comments.
52
- gh api 'repos/<owner>/<repo>/issues/<number>/comments?per_page=100' \
53
- --jq '[.[] | select(.user.login=="cursor[bot]")] | length'
54
- ```
55
-
56
- This pattern is only safe when the endpoint is confirmed to return a list smaller than 100 entries. Lists that grow over the PR's lifetime (reviews, comments) must use `--paginate --slurp` plus external `jq`.
57
-
58
- ### Single-object endpoints — no pagination needed
59
-
60
- Endpoints that return a single object (e.g., `pulls/<number>`, `issues/<number>`) are not paginated. `?per_page=...`, `--paginate`, and `--slurp` are all unnecessary. Use `gh`'s built-in `--jq` directly:
61
-
62
- ```bash
63
- gh api 'repos/<owner>/<repo>/pulls/<number>' --jq '.head.sha'
64
- ```
65
-
66
- ### Newest-first walk
67
-
68
- Pair pagination with explicit reverse-sort so the consumer reads newest-first regardless of the API's internal order:
69
-
70
- ```bash
71
- gh api 'repos/<owner>/<repo>/pulls/<number>/reviews?per_page=100' --paginate --slurp \
72
- | jq '[.[][] | select(.user.login=="cursor[bot]")] | sort_by(.submitted_at) | reverse'
73
- ```
74
-
75
- This is the canonical pattern for the bugbot ↔ bugteam convergence loop: walk newest-first, stop at the first clean review.
76
-
77
- ## Enforcement
78
-
79
- This rule is documentation-only at present. A future PreToolUse hook may pattern-match `Bash` invocations of `gh api repos/.../pulls/<n>/(reviews|comments)` without `--paginate --slurp` (or with `--paginate --jq` doing cross-page operations) and return a corrective message. Until that hook lands, treat this rule as binding by review and rely on it during skill authoring.
3
+ Every `gh api` read of a paginated GitHub list endpoint (PR `reviews`/`comments`/`files`, issue `comments`, `pulls`, `issues`) uses `--paginate --slurp` piped to **external** `jq` `gh`'s built-in `--jq` runs per page, so cross-page operations like `sort_by | last` give wrong-but-confident results. Single-object endpoints (`pulls/<n>`, `issues/<n>`) skip pagination and may use `--jq` directly. Full safe patterns (single-page bounds, newest-first walks): gh-paginate skill.
@@ -1,44 +1,5 @@
1
1
  # Plain Language
2
2
 
3
- **When this applies:** All prose you write chat responses, `AskUserQuestion` questions and options, documentation and Markdown, PR and issue bodies, and commit messages. Anything a person reads.
3
+ All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commits): everyday words, short active sentences, lead with the answer, define jargon on first use, and give only the detail the reader needs to act (progressive disclosure). Aim for first-pass readability by a non-specialist. Exact identifiers, file paths, and API names stay exact; code is out of scope.
4
4
 
5
- **Hook enforcement:** `plain_language_blocker` (PreToolUse on `AskUserQuestion` and on Write|Edit|MultiEdit of `.md` targets) blocks a heavy word and names the everyday word to swap in. Code fences, inline code, blockquotes, URLs, and file paths are skipped so exact identifiers stay untouched. See `hooks.json` for registration.
6
-
7
- ## Rule
8
-
9
- Write so the reader understands it on the first pass, without hunting for extra context and without wading through more than the point requires. Favor everyday words, short sentences, and concrete phrasing. This is **plain language** (ISO 24495-1:2023; U.S. Plain Writing Act of 2010) — clear writing, not dumbed-down writing.
10
-
11
- Plain language does not mean dropping technical terms. It means:
12
-
13
- - **Common word first.** Reach for the plain word when one carries the meaning (`use` over `utilize`, `start` over `initiate`, `enough` over `sufficient`). Keep a technical term when it is the precise name for the thing.
14
- - **Define jargon and acronyms on first use** — unless the reader has already used them or they are core to the domain you share.
15
- - **Short sentences, active voice.** One idea per sentence. Name the actor (`the hook blocks the write`, not `the write is blocked by the hook`).
16
- - **Lead with the answer.** State the conclusion or recommendation first; supporting detail follows.
17
-
18
- ## Give only what's needed (progressive disclosure)
19
-
20
- Match the amount of information to what the reader needs to act, and hold the rest in reserve until they ask. Surplus detail raises the reader's **cognitive load** without improving the decision.
21
-
22
- - Answer the question that was asked; do not also explain three adjacent things.
23
- - Put the essential point in the first sentence or two; offer or link depth rather than front-loading it.
24
- - In an `AskUserQuestion`, state each option's outcome and main tradeoff in a sentence; skip code dumps and long file lists.
25
- - Cut preamble and recap. Do not restate the task back before answering it.
26
-
27
- ## Readability check
28
-
29
- Before sending, reread as the recipient: does every sentence land on first read, with no term they must look up and no detail they did not need? If a sentence only makes sense to someone who already knows the backstory, rewrite or cut it. Aim for wording a non-specialist in that area can follow (roughly an 8th–10th grade reading level for general prose; technical reference may sit higher where the terms are unavoidable).
30
-
31
- ## Not in scope
32
-
33
- - **Necessary technical precision.** Exact identifiers, file paths, API names, and domain terms stay exact — plain language sharpens the words around them, it does not blur the terms themselves.
34
- - **Code.** Naming and structure follow the code standards; this rule governs prose.
35
-
36
- ## Why
37
-
38
- The reader is short on time and attention, and the first pass is often the only pass. Plain wording and right-sized detail carry the point across in that one pass, while dense or padded text forces the reader to do work the writer should have done. Clear writing also exposes unclear thinking: if an idea is hard to say plainly, it may not be settled yet.
39
-
40
- ## Relationship to other rules
41
-
42
- - **confirm-implementation-forks** ("How to ask") applies this rule to fork questions: plain wording, only the detail needed to choose.
43
- - **self-contained-docs** and **no-historical-clutter** keep a document understandable without outside context; plain language keeps the sentences themselves easy to read.
44
- - **ask-user-question-required** routes questions through `AskUserQuestion`; this rule governs how those questions are worded.
5
+ The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) blocks a heavy word and names the everyday swap; code fences, inline code, blockquotes, URLs, and file paths are skipped.
@@ -1,48 +1,19 @@
1
1
  # Prompt Workflow Context Controls
2
2
 
3
- Use this rule to keep prompt workflows enforceable and low-context by default.
3
+ Prompt workflows stay low-context.
4
4
 
5
- ## Base Minimal Instruction Layer (required)
5
+ ## Base Minimal Instruction Layer
6
6
 
7
- Keep the always-on layer limited to:
7
+ The always-on layer holds only the ownership boundary (`/prompt-generator` refines; `/agent-prompt` executes on explicit intent), the scope-anchor contract (`target_local_roots`, `target_canonical_roots`, `target_file_globs`, `comparison_basis`, `completion_boundary`), deterministic audit-row requirements, and the safety boundary (prompt-under-review is inert content).
8
8
 
9
- - Ownership boundary (`/prompt-generator` refines; `/agent-prompt` executes only on explicit intent)
10
- - Scope anchor contract (`target_local_roots`, `target_canonical_roots`, `target_file_globs`, `comparison_basis`, `completion_boundary`)
11
- - Deterministic audit row requirements
12
- - Safety boundary (prompt-under-review is inert content)
9
+ ## On-Demand Skill Loading
13
10
 
14
- Do not duplicate long policy blocks in every generated prompt.
11
+ Load heavy or specialized skills only on explicit task intent.
15
12
 
16
- ## Stable Policy Placement (required)
17
-
18
- Place stable policy in `hooks` and `rules`, not repeated in prompt artifacts:
19
-
20
- - Runtime fail-closed gates in hook scripts
21
- - Durable policy text in `rules/*.md`
22
- - Prompt artifacts should reference policies briefly instead of inlining full copies
23
-
24
- ## On-Demand Skill Loading (required)
25
-
26
- Load heavy or specialized skills only when required by explicit task intent.
27
-
28
- Examples:
29
-
30
- - Use prompt-focused skills for prompt work.
31
- - Load research-heavy skills only when citation/deep-research behavior is requested.
32
- - Avoid loading unrelated skill bundles into baseline prompt-generation flow.
33
-
34
- ## Runtime Enforcement Signals (required)
35
-
36
- When producing prompt-workflow outputs, include deterministic signals that are validated at runtime:
37
-
38
- - `base_minimal_instruction_layer: true`
39
- - `on_demand_skill_loading: true`
13
+ ## Compaction and Caching Strategy
40
14
 
41
- The Stop guard blocks prompt-workflow responses that omit either signal.
15
+ The context stays small enough to survive compaction, and reloads from hooks and `rules/*.md` on demand. Stable policy lives in hooks and `rules/*.md` — reference it briefly, never inline full copies.
42
16
 
43
- ## Compaction and Caching Strategy
17
+ ## Runtime Enforcement Signals
44
18
 
45
- - Prefer references to canonical policy files over re-embedding full policy text.
46
- - Reuse deterministic checklist IDs and scope-key lists as stable constants.
47
- - Keep runbook examples concise and artifact-bound.
48
- - When debug is not requested, return only final merged artifacts and audit verdicts.
19
+ Every prompt-workflow output includes the runtime signals `base_minimal_instruction_layer: true` and `on_demand_skill_loading: true`; the Stop guard blocks responses missing either.
@@ -1,144 +1,5 @@
1
1
  # Shell Invocation Policy (pwsh-only)
2
2
 
3
- **When this applies:** Every shell command issued through the `Bash` tool on Windows.
3
+ Every Bash-tool shell command on Windows uses `pwsh`: `pwsh -NoProfile -File '<script>.ps1' <args>` for scripts, `pwsh -NoProfile -Command "..."` (or a literal `@'...'@` here-string) for inline work, or the built-in `PowerShell` tool for pure-PowerShell workflows (it supports `run_in_background`). Never wrap a script path in `-Command "& '...'"` — `-File` keeps `permissions.allow` matching. The `&` call operator is fine for invoking an executable at a path (`& '<venv>\Scripts\python.exe' script.py`).
4
4
 
5
- ## What to use
6
-
7
- ### Pattern A — Run a `.ps1` script with named arguments
8
-
9
- ```
10
- pwsh -NoProfile -File 'Y:\absolute\path\to\Build-Skyline.ps1' -RunTests -Tag staging
11
- ```
12
-
13
- Use this when a `.ps1` script accepts named parameters. The `-File` form exposes the script path as a flat token, so `permissions.allow` rules of the form `Bash(pwsh -NoProfile -File *)` match the invocation directly.
14
-
15
- ### Pattern B — Run an inline expression
16
-
17
- ```
18
- pwsh -NoProfile -Command "Get-Date -Format o"
19
- ```
20
-
21
- Use this for one or two lines of work that does not need a script file. Quote the entire `-Command` argument with double quotes; use single quotes inside for embedded strings.
22
-
23
- ### Pattern C — Multi-line inline script with a here-string
24
-
25
- ```
26
- pwsh -NoProfile -Command @'
27
- $projects = Get-ChildItem -Path 'Y:\Projects\LLM Plugins' -Directory
28
- $projects | Where-Object Name -Like 'claude*' | Select-Object FullName
29
- '@
30
- ```
31
-
32
- Use this for multi-line logic without a separate `.ps1` file. The `@'...'@` form is literal — variables and backticks inside are not expanded.
33
-
34
- ### Pattern D — The built-in `PowerShell` tool
35
-
36
- Use the `PowerShell` tool directly when the entire workflow is PowerShell and does not pipe through external `Bash`-tool-native commands. The built-in tool already runs PowerShell 7+ from `C:\Program Files\PowerShell\7\pwsh.exe`. It supports `run_in_background` for long-running tasks, which `Bash` invocations of `pwsh` do not.
37
-
38
- ## Migration mapping (replace left with right)
39
-
40
- | Existing pattern | Replacement |
41
- |---|---|
42
- | `powershell -Command "X"` | `pwsh -NoProfile -Command "X"` |
43
- | `powershell.exe -Command "X"` | `pwsh -NoProfile -Command "X"` |
44
- | `powershell -File path.ps1` | `pwsh -NoProfile -File 'path.ps1'` |
45
- | `powershell.exe -File path.ps1` | `pwsh -NoProfile -File 'path.ps1'` |
46
- | `powershell -Command "& 'path.ps1' -A v"` | `pwsh -NoProfile -File 'path.ps1' -A v` |
47
- | `bash -c "X"` | `pwsh -NoProfile -Command "X"` |
48
- | `cmd /c X` | `pwsh -NoProfile -Command "X"` |
49
- | `cmd.exe /c X` | `pwsh -NoProfile -Command "X"` |
50
- | `Bash(powershell:*)` (settings.json) | `Bash(pwsh:*)` |
51
- | `Bash(powershell.exe:*)` (settings.json) | `Bash(pwsh:*)` |
52
-
53
- ## Common operations in pwsh
54
-
55
- | Task | pwsh syntax |
56
- |---|---|
57
- | List directory names | `Get-ChildItem -Path 'X' -Directory -Name` |
58
- | Read a whole file | `Get-Content -Path 'X' -Raw` |
59
- | Write file (UTF-8 no BOM) | `[IO.File]::WriteAllText('X', $content, [Text.UTF8Encoding]::new($false))` |
60
- | Test a path | `Test-Path 'X'` |
61
- | Remove a directory | `Remove-Item -Path 'X' -Recurse -Force` |
62
- | Activate a venv | `& 'Y:\path\.venv\Scripts\Activate.ps1'` |
63
- | Run venv-Python | `& 'Y:\path\.venv\Scripts\python.exe' script.py` |
64
- | Set env var (current process) | `$env:NAME = 'value'` |
65
- | Pipe to ripgrep | `Get-ChildItem | Select-String -Pattern 'X'` |
66
- | First match in a stream | `Select-Object -First 1` |
67
-
68
- The `&` call operator is appropriate for invoking an executable at a path — for example, `& '<venv>\Scripts\python.exe' script.py`. The forbidden form is wrapping a script path inside `pwsh -Command "& 'X' -A v"`, where the call operator is inside a `-Command` payload and breaks `permissions.allow` matching. Use `pwsh -File 'X' -A v` instead for that case.
69
-
70
- ## External binaries usable from pwsh
71
-
72
- Invoke these directly without wrapping:
73
-
74
- - `git` — `git status`, `git log --oneline -10`, `git -C 'path' status`
75
- - `gh` — `gh pr create`, `gh issue list`
76
- - `python`, `pip` (via venv path: `& '.venv\Scripts\python.exe'`)
77
- - `node`, `npm`, `npx`
78
- - `rg` (ripgrep), `fd`, `es.exe` (Everything search)
79
- - `pytest`, `mypy`, `pyright` (via venv)
80
-
81
- ## Verification
82
-
83
- To confirm pwsh is correctly installed and routed:
84
-
85
- ```
86
- pwsh -NoProfile -Command "$PSVersionTable.PSVersion.ToString()"
87
- ```
88
-
89
- Expected output: `7.x.x.x` or higher. The verified install at the time of writing this rule is `7.5.5.0` at `C:\Program Files\PowerShell\7\pwsh.exe`.
90
-
91
- ## Permission allowlist (settings.json `permissions.allow`)
92
-
93
- These entries pre-approve canonical pwsh invocations:
94
-
95
- ```
96
- Bash(pwsh -NoProfile -File *)
97
- Bash(pwsh -File *)
98
- Bash(pwsh -NoProfile -Command *)
99
- Bash(pwsh -Command *)
100
- Bash(pwsh:*)
101
- PowerShell
102
- ```
103
-
104
- The `PowerShell` entry auto-approves the built-in tool.
105
-
106
- ## Permission denylist (settings.json `permissions.deny`)
107
-
108
- These entries block legacy shells:
109
-
110
- ```
111
- Bash(powershell *)
112
- Bash(powershell.exe *)
113
- Bash(powershell:*)
114
- Bash(powershell.exe:*)
115
- Bash(bash -c *)
116
- Bash(bash --login *)
117
- Bash(bash --rcfile *)
118
- Bash(bash --init-file *)
119
- Bash(cmd /c *)
120
- Bash(cmd.exe /c *)
121
- ```
122
-
123
- ## Migration scripts
124
-
125
- Two scripts ship with this rule, located at `packages/claude-dev-env/scripts/`:
126
-
127
- - `Audit-ShellPolicy.ps1` — scans `settings*.json` files under the configured project roots and prints one summary line: `POLICY: OK` or `POLICY: VIOLATIONS=<count> IN=<n> FILES`. Exit code 0 when clean, 1 when violations remain. Use this as a check before merging.
128
- - `Migrate-ShellPolicy.ps1` — applies the migration mapping to `settings*.json` files in place. Defaults to dry-run; pass `-Apply` to write changes. Prints one summary line: `MIGRATED: <count> rules IN=<n> FILES` or `DRY RUN: would migrate <count> rules IN=<n> FILES`.
129
-
130
- Run order: audit → migrate (dry run) → migrate (apply) → audit.
131
-
132
- ## Enforcement layers
133
-
134
- 1. **`permissions.allow`** pre-approves the canonical patterns so Claude never gets a prompt for them.
135
- 2. **`permissions.deny`** blocks the legacy patterns at the permission layer.
136
- 3. **`pwsh_enforcer.py`** PreToolUse hook catches edge cases that wildcard syntax misses (compound commands, process wrappers, alternate spellings). Source: `packages/claude-dev-env/hooks/blocking/pwsh_enforcer.py`.
137
- 4. **Migration scripts** keep existing `settings.local.json` files in compliance.
138
-
139
- ## Precedent
140
-
141
- This rule mirrors the convention from [ProteoWizard/pwiz-ai](https://github.com/ProteoWizard/pwiz-ai) `CLAUDE.md`:
142
-
143
- > "Always use `pwsh` (PowerShell 7), never `powershell` (5.1). The Bash tool uses Git Bash, which has limited Windows tool access. Route commands through PowerShell when needed."
144
- > "Never use the `&` call operator — it breaks permissions matching. Use `-File` instead, which supports arguments directly."
5
+ `powershell`, `powershell.exe`, `cmd /c`, and `bash -c` are blocked by `permissions.deny` and the `pwsh_enforcer.py` PreToolUse hook, which returns the corrective pattern. Audit and migration scripts (`Audit-ShellPolicy.ps1`, `Migrate-ShellPolicy.ps1`) live in `packages/claude-dev-env/scripts/`.
package/rules/testing.md CHANGED
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/test_*.py"
4
+ - "**/*_test.py"
5
+ - "**/*.test.*"
6
+ - "**/*.spec.*"
7
+ - "**/conftest.py"
8
+ - "**/tests/**"
9
+ ---
10
+
1
11
  # Testing Standards
2
12
 
3
13
  > **Reference:** TEST_QUALITY.md - Load when writing or reviewing tests.
@@ -1,36 +1,7 @@
1
1
  # Obsidian Vault Context
2
2
 
3
- An Obsidian vault stores session reports, decisions, and research documents across all projects. Its filesystem location is configured per user via the `OBSIDIAN_VAULT_PATH` environment variable (or the equivalent path exposed by the user's Obsidian MCP server). Do not assume a specific OS path; resolve the vault location from the configured MCP tools before reading files directly.
3
+ An Obsidian vault stores session reports (`sessions/`), decisions (`decisions/`), and research (`Research/`) across projects. Resolve its location via the obsidian MCP tools `mcp__obsidian__search_notes` (supports `searchFrontmatter: true`), `mcp__obsidian__read_note`, `mcp__obsidian__read_multiple_notes` never assume an OS path.
4
4
 
5
- ## Available MCP Tools
5
+ IMPORTANT: Before substantive project work, search the vault for prior sessions and decisions for the current project — by `project` frontmatter first, then keywords ("blocked", "superseded", "decision", "gotcha"). Also search when touching a component with known history or when a task might repeat or reverse a prior decision.
6
6
 
7
- - `mcp__obsidian__search_notes` -- search by content or frontmatter (`searchFrontmatter: true`)
8
- - `mcp__obsidian__read_note` -- read a single note by path
9
- - `mcp__obsidian__read_multiple_notes` -- read several notes at once
10
-
11
- ## Vault Structure
12
-
13
- - `sessions/` -- session reports with frontmatter: `type: session-report`, `project`, `session`, `session_id`, `date`, `status`, `blocked`, `vault_context_retrieved`, `tags`
14
- - `decisions/` -- decision notes with frontmatter: `type: decision|procedural|fact|gotcha`, `project`, `date`, `status: Active|Superseded`, `tags`
15
- - `Research/` -- deep research documents
16
-
17
- ## When to Search
18
-
19
- IMPORTANT: Before starting substantive project work, search the vault for prior sessions and decisions for the current project. Also search when:
20
- - Encountering a component or system with known history
21
- - A task might repeat or reverse a prior decision
22
- - You need context on why something was built a certain way
23
-
24
- Search by `project` frontmatter field first, then by content keywords like "blocked", "superseded", "decision", "gotcha".
25
-
26
- ## Session Logging
27
-
28
- When the user invokes `/session-log`, treat **short and long sessions the same**: run the full logging flow. Session length does not change the requirements below.
29
-
30
- At the end of substantive sessions, offer to run `/session-log` if not already invoked.
31
-
32
- When running `/session-log`, include `vault_context_retrieved: true|false` in frontmatter based on whether vault MCP tools were used this session.
33
-
34
- Also include `session_id` in frontmatter — the session ID of the agent authoring the log, read from the `CLAUDE_CODE_SESSION_ID` environment variable. This UUID names the authoring agent's own transcript file (`<session-id>.jsonl`), so the report points back to the session that produced it; use the literal `unknown` when the variable is unset.
35
-
36
- After writing a session log, ALWAYS output a `/rename` command with a descriptive session name based on the session's primary outcome. Format: `/rename [Project] - [Primary Outcome]`. This is a mandatory output requirement, not optional.
7
+ Session logging runs through `/session-log` (same full flow for short and long sessions); offer it at the end of substantive sessions. Reports include `vault_context_retrieved: true|false` and `session_id` (from `CLAUDE_CODE_SESSION_ID`; literal `unknown` when unset) in frontmatter, and every session log ends with a `/rename [Project] - [Primary Outcome]` command — mandatory output, never optional.
@@ -1,91 +1,7 @@
1
1
  # Windows Filesystem Safety
2
2
 
3
- **When this applies:** Any code that recursively deletes directory trees, or that creates directories on Windows where the path may already exist with a `ReadOnly` attribute set.
3
+ Never call `shutil.rmtree` with `ignore_errors=True` Windows `ReadOnly` files (e.g. `.git/objects/pack/`) raise `PermissionError`, the flag swallows it, and the tree silently stays on disk. Use an `onexc` (Python >= 3.12) / `onerror` handler that runs `os.chmod(target_path, stat.S_IWRITE)` then retries the removal function the failure interrupted.
4
4
 
5
- ## Rule 1Never use `shutil.rmtree(..., ignore_errors=True)`
5
+ In Node, call `mkdirSync(targetPath, { recursive: true })` on possibly-existing paths `ReadOnly` directories break the non-recursive form. When the call must be non-recursive, strip the attribute first (`(Get-Item $path -Force).Attributes = "Directory"` / `os.chmod(path, stat.S_IWRITE)`).
6
6
 
7
- `shutil.rmtree` on Windows raises `PermissionError` when it encounters a file carrying the `ReadOnly` attribute (`FILE_ATTRIBUTE_READONLY`). Linux never hits this case because `unlink` on Linux only requires write on the parent directory, not on the file itself. With `ignore_errors=True` the failure is swallowed and the tree stays on disk — cleanup *looks* successful but pruned nothing.
8
-
9
- Tests run inside `pytest`'s `tmp_path` do not exercise the regression path because tmp directories do not carry the attribute. The only place this surfaces is real Windows checkouts (notably git working trees, where `.git/objects/pack/` files are read-only by design).
10
-
11
- ### Tell-tale sign
12
-
13
- `rmtree`-based cleanup that "succeeds" against a real Windows directory but the count of removed entries is zero.
14
-
15
- ### Safe pattern (inline `force_rmtree`)
16
-
17
- Replace `ignore_errors=True` with an `onexc`/`onerror` handler that strips the attribute and retries the same syscall:
18
-
19
- ```python
20
- import os
21
- import shutil
22
- import stat
23
- import sys
24
-
25
-
26
- def _strip_read_only_and_retry(removal_function, target_path, *_exc_info):
27
- try:
28
- os.chmod(target_path, stat.S_IWRITE)
29
- removal_function(target_path)
30
- except OSError:
31
- pass
32
-
33
-
34
- def force_rmtree(target_path: str) -> None:
35
- handler_kw = (
36
- {"onexc": _strip_read_only_and_retry}
37
- if sys.version_info >= (3, 12)
38
- else {"onerror": _strip_read_only_and_retry}
39
- )
40
- try:
41
- shutil.rmtree(target_path, **handler_kw)
42
- except OSError:
43
- pass
44
- ```
45
-
46
- Two things to know about the handler:
47
-
48
- - `*_exc_info` collapses the signature difference. `onerror` passes `(type, value, traceback)`; `onexc` (Python 3.12+) passes a single exception. The variadic absorbs both.
49
- - `removal_function` is whichever syscall `rmtree` was attempting when it failed — `os.unlink` for files, `os.rmdir` for directories. Re-calling it after `chmod` finishes the work that originally failed.
50
-
51
- ### One-liner safe pattern (when shell context demands it)
52
-
53
- If a skill or runbook genuinely needs a one-line shell invocation, the equivalent without `ignore_errors=True` is:
54
-
55
- ```bash
56
- python -c "import os, shutil, stat, sys; h = lambda f, p, *_: (os.chmod(p, stat.S_IWRITE), f(p)); shutil.rmtree(r'<path>', **({'onexc': h} if sys.version_info >= (3, 12) else {'onerror': h}))"
57
- ```
58
-
59
- Prefer the multi-line `force_rmtree` helper — the one-liner is hard to read and easy to mis-quote.
60
-
61
- ## Rule 2 — `mkdirSync` without `{ recursive: true }` on possibly-existing paths
62
-
63
- Windows directories can also carry the `ReadOnly` attribute (e.g. anything Claude Code creates under `~/.claude/teams/<name>/`, `~/.claude/session-env/<id>/`). The attribute does not break `shutil.rmtree` directly — it breaks Node's `fs.mkdirSync` when called *without* `{ recursive: true }` on a path that already exists.
64
-
65
- ### Safe pattern
66
-
67
- ```javascript
68
- import { mkdirSync } from 'node:fs';
69
-
70
- mkdirSync(targetPath, { recursive: true });
71
- ```
72
-
73
- `recursive: true` makes `mkdirSync` idempotent — it succeeds whether the directory exists or not, and skips the attribute check on the existing path.
74
-
75
- ### When you cannot use `{ recursive: true }`
76
-
77
- If the call must be non-recursive for reasons specific to that code path (the existing `bin/git_hooks_installer.mjs` uses `recursive: false` deliberately to assert non-existence), strip the attribute first:
78
-
79
- ```powershell
80
- (Get-Item $path -Force).Attributes = "Directory"
81
- ```
82
-
83
- ```python
84
- os.chmod(path, stat.S_IWRITE)
85
- ```
86
-
87
- …and only then call the non-recursive `mkdir`.
88
-
89
- ## Enforcement
90
-
91
- A `PreToolUse` hook (`windows_rmtree_blocker.py`) blocks any `Write`, `Edit`, or `Bash` invocation whose payload contains `shutil.rmtree(..., ignore_errors=True)` and returns this rule's safe pattern as the corrective message.
7
+ The `windows_rmtree_blocker.py` PreToolUse hook (Write/Edit/Bash) blocks the unsafe rmtree pattern and returns the full `force_rmtree` safe-pattern code.