claude-dev-env 7.7.1 → 8.1.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 (118) hide show
  1. package/.agents/agents/clean-coder.md +5 -6
  2. package/.agents/agents/issue-tracker.md +1 -1
  3. package/.agents/agents/pr-description-writer.md +1 -1
  4. package/.agents/agents/skill-writer-agent.md +1 -12
  5. package/.agents/agents/test_agent_frontmatter.py +2 -19
  6. package/.agents/skills/issue-tracker/SKILL.md +1 -1
  7. package/.agents/skills/orchestrator/SKILL.md +4 -5
  8. package/.agents/skills/pull-request/SKILL.md +8 -7
  9. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  10. package/.agents/skills/team-advisor/SKILL.md +2 -3
  11. package/_shared/advisor/advisor-protocol.md +0 -1
  12. package/_shared/advisor/reference/lifecycle.md +0 -1
  13. package/_shared/advisor/reference/warm-up.md +1 -2
  14. package/audit-rubrics/audit-categories.json +0 -4
  15. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
  16. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -2
  17. package/audit-rubrics/prompts/category-e-dead-code.md +5 -5
  18. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -24
  19. package/bin/install-constants.mjs +0 -5
  20. package/bin/install-pstack-plugin.mjs +192 -0
  21. package/bin/install-pstack-plugin.test.mjs +181 -0
  22. package/bin/install.agents-home.test.mjs +0 -4
  23. package/bin/install.codex-rules.test.mjs +6 -0
  24. package/bin/install.cursor-rules.test.mjs +3 -253
  25. package/bin/install.mjs +55 -160
  26. package/bin/install.test.mjs +110 -149
  27. package/bin/prune-retired-pstack-store.mjs +83 -0
  28. package/bin/prune-retired-pstack-store.test.mjs +105 -0
  29. package/bin/prune-session-continuity.mjs +57 -0
  30. package/bin/prune-session-continuity.test.mjs +70 -0
  31. package/bin/resolve-install-root.mjs +2 -0
  32. package/bin/resolve-install-root.test.mjs +69 -0
  33. package/docs/CODE_RULES.md +1 -1
  34. package/docs/agent-spawn-protocol.md +1 -1
  35. package/docs/codex-compatibility.md +4 -16
  36. package/docs/nas-ssh-invocation.md +1 -1
  37. package/docs/references/prose-style-enforcement.md +2 -2
  38. package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
  39. package/hooks/blocking/code_rules_constants_config.py +1 -108
  40. package/hooks/blocking/code_rules_enforcer.py +9 -5
  41. package/hooks/blocking/code_rules_test_layout.py +6 -7
  42. package/hooks/blocking/test_code_rules_constants_config.py +13 -0
  43. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  44. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
  45. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
  46. package/hooks/blocking/test_context_control_policy_files.py +0 -11
  47. package/hooks/hooks_constants/blocking_check_limits.py +1 -1
  48. package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
  49. package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
  50. package/hooks/session/test_untracked_repo_detector.py +1 -1
  51. package/hooks/validators/code_quality_checks.py +15 -2
  52. package/hooks/validators/test_code_quality_checks.py +22 -0
  53. package/package.json +1 -2
  54. package/rules/anti-corollary-tests.md +3 -1
  55. package/rules/archiving-agent-config.md +70 -0
  56. package/rules/asd-ste100-language.md +1 -1
  57. package/rules/ci-owns-the-gate.md +73 -0
  58. package/rules/cleanup-temp-files.md +1 -1
  59. package/rules/code-standards.md +3 -4
  60. package/rules/docstring-prose-matches-implementation.md +3 -3
  61. package/rules/explore-thoroughly.md +2 -2
  62. package/rules/failure-blast-radius.md +10 -9
  63. package/rules/git-workflow.md +27 -12
  64. package/rules/long-horizon-autonomy.md +3 -1
  65. package/rules/nas-ssh-invocation.md +1 -1
  66. package/rules/orphan-css-class.md +1 -1
  67. package/rules/paired-test-coverage.md +1 -1
  68. package/rules/research-mode.md +3 -1
  69. package/rules/retired-hook-prose.md +10 -2
  70. package/rules/testing.md +3 -3
  71. package/rules/verify-before-asking.md +4 -1
  72. package/rules/verify-runtime-state.md +2 -2
  73. package/rules/windows-filesystem-safe.md +1 -1
  74. package/rules/workflow-substitution-slots.md +1 -1
  75. package/scripts/Sync-RepoMain.ps1 +71 -6
  76. package/scripts/policy_lint/adapter_detectors.py +116 -10
  77. package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
  78. package/scripts/policy_lint/config/constants.py +3 -0
  79. package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
  80. package/scripts/tests/test_codex_compat_materializer.py +3 -3
  81. package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
  82. package/.agents/skills/session-continuity/AGENTS.md +0 -1
  83. package/.agents/skills/session-continuity/CLAUDE.md +0 -1
  84. package/.agents/skills/session-continuity/README.md +0 -223
  85. package/.agents/skills/session-continuity/SKILL.md +0 -125
  86. package/.agents/skills/session-continuity/continuity.mjs +0 -445
  87. package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
  88. package/bin/install-session-continuity.mjs +0 -131
  89. package/bin/install-session-continuity.test.mjs +0 -106
  90. package/bin/install.pstack-plugin.test.mjs +0 -115
  91. package/bin/pstack-codex-discovery.test.mjs +0 -79
  92. package/bin/pstack-model-preferences.claude.json +0 -23
  93. package/bin/pstack-model-preferences.codex.json +0 -24
  94. package/bin/pstack.mjs +0 -392
  95. package/bin/pstack.test.mjs +0 -347
  96. package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
  97. package/bin/select-pstack-models.test.mjs +0 -776
  98. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
  99. package/rules/agent-spawn-protocol.md +0 -9
  100. package/rules/ask-user-question-required.md +0 -31
  101. package/rules/claims-as-quotes.md +0 -55
  102. package/rules/confirm-implementation-forks.md +0 -11
  103. package/rules/file-global-constants.md +0 -81
  104. package/rules/hedging-claims.md +0 -7
  105. package/rules/measurement-denominators.md +0 -39
  106. package/rules/prompt-workflow-context-controls.md +0 -19
  107. package/rules/pstack-host-mapping.md +0 -48
  108. package/rules/pstack-models.md +0 -142
  109. package/rules/vault-context.md +0 -5
  110. package/scripts/pstack-adapters/common.md +0 -17
  111. package/scripts/pstack-adapters/create-skill.md +0 -14
  112. package/scripts/pstack-adapters/host-claude.md +0 -7
  113. package/scripts/pstack-adapters/host-codex.md +0 -11
  114. package/scripts/pstack-adapters/host-cursor.md +0 -7
  115. package/scripts/pstack.lock.json +0 -11
  116. package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
  117. package/scripts/select_pstack_models.mjs +0 -469
  118. /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
@@ -1,231 +0,0 @@
1
- """Tests for file-global constants use-count rule (jl-cmd/claude-dev-env#180).
2
-
3
- A module-level UPPER_SNAKE constant must be referenced by at least two
4
- distinct functions/methods. A constant referenced by only one function
5
- belongs in that function's scope. Constants with zero function references
6
- are out of this rule's concern. Test files are exempt.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import importlib.util
12
- from pathlib import Path
13
- from types import ModuleType
14
-
15
-
16
- def _load_enforcer_module() -> ModuleType:
17
- module_path = Path(__file__).parent / "code_rules_enforcer.py"
18
- spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
19
- assert spec is not None
20
- assert spec.loader is not None
21
- module = importlib.util.module_from_spec(spec)
22
- spec.loader.exec_module(module)
23
- return module
24
-
25
-
26
- code_rules_enforcer = _load_enforcer_module()
27
-
28
-
29
- PRODUCTION_FILE_PATH = "packages/claude-dev-env/hooks/blocking/example_production.py"
30
- TEST_FILE_PATH = "packages/claude-dev-env/hooks/blocking/test_example.py"
31
- TYPESCRIPT_FILE_PATH = "packages/claude-dev-env/hooks/blocking/example.ts"
32
- TOP_LEVEL_CONFIG_FILE_PATH = "config/timing.py"
33
- NESTED_CONFIG_FILE_PATH = "packages/claude-dev-env/hooks/config/example_constants.py"
34
- BACKSLASH_CONFIG_FILE_PATH = "packages\\claude-dev-env\\hooks\\config\\example_constants.py"
35
- WORKFLOW_REGISTRY_FILE_PATH = "packages/claude-dev-env/hooks/blocking/workflow/app_info/states.py"
36
-
37
-
38
- def test_should_flag_constant_used_by_only_one_function() -> None:
39
- source = "UPPER = 1\n\ndef lonely_caller():\n return UPPER\n"
40
- issues = code_rules_enforcer.check_file_global_constants_use_count(
41
- source, PRODUCTION_FILE_PATH
42
- )
43
- assert any(
44
- "UPPER" in issue and "only 1 function/method" in issue for issue in issues
45
- ), f"Expected single-caller violation for UPPER, got: {issues}"
46
-
47
-
48
- def test_should_accept_constant_used_by_two_functions() -> None:
49
- source = (
50
- "UPPER = 1\n"
51
- "\n"
52
- "def first_caller():\n"
53
- " return UPPER\n"
54
- "\n"
55
- "def second_caller():\n"
56
- " return UPPER + 1\n"
57
- )
58
- issues = code_rules_enforcer.check_file_global_constants_use_count(
59
- source, PRODUCTION_FILE_PATH
60
- )
61
- assert issues == [], f"Expected no violation for 2 callers, got: {issues}"
62
-
63
-
64
- def test_should_accept_constant_used_by_method_and_function() -> None:
65
- source = (
66
- "UPPER = 1\n"
67
- "\n"
68
- "class Holder:\n"
69
- " def show(self):\n"
70
- " return UPPER\n"
71
- "\n"
72
- "def also_uses():\n"
73
- " return UPPER\n"
74
- )
75
- issues = code_rules_enforcer.check_file_global_constants_use_count(
76
- source, PRODUCTION_FILE_PATH
77
- )
78
- assert issues == [], f"Expected no violation for method + function, got: {issues}"
79
-
80
-
81
- def test_should_accept_constant_used_by_two_methods_of_same_class() -> None:
82
- source = (
83
- "UPPER = 1\n"
84
- "\n"
85
- "class Holder:\n"
86
- " def method_a(self):\n"
87
- " return UPPER\n"
88
- "\n"
89
- " def method_b(self):\n"
90
- " return UPPER + 1\n"
91
- )
92
- issues = code_rules_enforcer.check_file_global_constants_use_count(
93
- source, PRODUCTION_FILE_PATH
94
- )
95
- assert issues == [], (
96
- f"Expected no violation for two methods same class, got: {issues}"
97
- )
98
-
99
-
100
- def test_should_accept_constant_with_zero_function_references() -> None:
101
- source = "UPPER = 1\n\ndef unrelated():\n return 0\n"
102
- issues = code_rules_enforcer.check_file_global_constants_use_count(
103
- source, PRODUCTION_FILE_PATH
104
- )
105
- assert issues == [], (
106
- f"Expected no violation for zero-reference constant, got: {issues}"
107
- )
108
-
109
-
110
- def test_should_exempt_test_files() -> None:
111
- source = "UPPER = 1\n\ndef lonely_caller():\n return UPPER\n"
112
- issues = code_rules_enforcer.check_file_global_constants_use_count(
113
- source, TEST_FILE_PATH
114
- )
115
- assert issues == [], f"Expected test file exemption, got: {issues}"
116
-
117
-
118
- def test_should_exempt_workflow_registry_files() -> None:
119
- source = "UPPER = 1\n\ndef lonely_caller():\n return UPPER\n"
120
- issues = code_rules_enforcer.check_file_global_constants_use_count(
121
- source, WORKFLOW_REGISTRY_FILE_PATH
122
- )
123
- assert issues == [], f"Expected workflow-registry file exemption, got: {issues}"
124
-
125
-
126
- def test_should_flag_constant_used_only_in_decorator_of_one_function() -> None:
127
- source = (
128
- "TIMEOUT = 5.0\n"
129
- "\n"
130
- "def cache(seconds):\n"
131
- " def wrap(function):\n"
132
- " return function\n"
133
- " return wrap\n"
134
- "\n"
135
- "@cache(TIMEOUT)\n"
136
- "def fetch_data():\n"
137
- " return 0\n"
138
- )
139
- issues = code_rules_enforcer.check_file_global_constants_use_count(
140
- source, PRODUCTION_FILE_PATH
141
- )
142
- assert any(
143
- "TIMEOUT" in issue and "only 1 function/method" in issue for issue in issues
144
- ), f"Expected decorator usage to count as single caller, got: {issues}"
145
-
146
-
147
- def test_should_flag_ann_assign_constant_used_by_only_one_function() -> None:
148
- source = (
149
- "from typing import Final\n"
150
- "\n"
151
- "TIMEOUT: Final[int] = 5\n"
152
- "\n"
153
- "def lonely_caller():\n"
154
- " return TIMEOUT\n"
155
- )
156
- issues = code_rules_enforcer.check_file_global_constants_use_count(
157
- source, PRODUCTION_FILE_PATH
158
- )
159
- assert any(
160
- "TIMEOUT" in issue and "only 1 function/method" in issue for issue in issues
161
- ), f"Expected AnnAssign constant to be flagged, got: {issues}"
162
-
163
-
164
- def test_should_flag_private_upper_snake_constant_used_by_only_one_function() -> None:
165
- source = (
166
- '_PRIVATE_CONSTANT = "x"\n'
167
- "\n"
168
- "def lonely_caller():\n"
169
- " return _PRIVATE_CONSTANT\n"
170
- )
171
- issues = code_rules_enforcer.check_file_global_constants_use_count(
172
- source, PRODUCTION_FILE_PATH
173
- )
174
- assert any(
175
- "_PRIVATE_CONSTANT" in issue and "only 1 function/method" in issue
176
- for issue in issues
177
- ), f"Expected private UPPER_SNAKE to be flagged, got: {issues}"
178
-
179
-
180
- def test_should_flag_constant_referenced_only_at_module_scope() -> None:
181
- source = "A = 1\nB = A + 1\n"
182
- issues = code_rules_enforcer.check_file_global_constants_use_count(
183
- source, PRODUCTION_FILE_PATH
184
- )
185
- assert any("A" in issue and "only 1 function/method" in issue for issue in issues), (
186
- f"Expected single module-scope reference to be flagged, got: {issues}"
187
- )
188
-
189
-
190
- def test_should_skip_non_python_files() -> None:
191
- source = "const UPPER = 1;\nfunction lonelyCaller() { return UPPER; }\n"
192
- issues = code_rules_enforcer.check_file_global_constants_use_count(
193
- source, TYPESCRIPT_FILE_PATH
194
- )
195
- assert issues == [], f"Expected TypeScript file to be skipped, got: {issues}"
196
-
197
-
198
- def test_should_exempt_top_level_config_files() -> None:
199
- source = "UPPER = 1\n\ndef lonely_caller():\n return UPPER\n"
200
- issues = code_rules_enforcer.check_file_global_constants_use_count(
201
- source, TOP_LEVEL_CONFIG_FILE_PATH
202
- )
203
- assert issues == [], (
204
- f"Expected config/ file to be exempt per file-global-constants rule, got: {issues}"
205
- )
206
-
207
-
208
- def test_should_exempt_nested_config_files() -> None:
209
- source = (
210
- "ATTACHMENT_TYPE_HOOK_SUCCESS = 'hook_success'\n"
211
- "\n"
212
- "OUTCOME_BY_ATTACHMENT_TYPE = {\n"
213
- " ATTACHMENT_TYPE_HOOK_SUCCESS: 'success',\n"
214
- "}\n"
215
- )
216
- issues = code_rules_enforcer.check_file_global_constants_use_count(
217
- source, NESTED_CONFIG_FILE_PATH
218
- )
219
- assert issues == [], (
220
- f"Expected nested config/ file to be exempt per file-global-constants rule, got: {issues}"
221
- )
222
-
223
-
224
- def test_should_exempt_backslash_config_path() -> None:
225
- source = "UPPER = 1\n\ndef lonely_caller():\n return UPPER\n"
226
- issues = code_rules_enforcer.check_file_global_constants_use_count(
227
- source, BACKSLASH_CONFIG_FILE_PATH
228
- )
229
- assert issues == [], (
230
- f"Expected backslash config path to be exempt, got: {issues}"
231
- )
@@ -1,9 +0,0 @@
1
- # Agent Spawn Guidance
2
-
3
- Before any Agent or Task tool spawn, check context sufficiency: you can name the files involved, the constraints, and what success looks like, and the task is unambiguous. When you cannot, investigate or ask the user first — a spawn with incomplete context returns work you throw away.
4
-
5
- Ask each research subagent for precise file-and-line answers. A finding that names a path and a line number is one a reader can check.
6
-
7
- `/prompt-generator` is recommended for a complex spawn, or one whose output the user reads directly: feed it the goal, the target files, the constraints, the output format, and the acceptance criteria, then use its output as the agent's `prompt`. Inside a scoped autonomous run, an inline structured prompt you write yourself is fine.
8
-
9
- Full step detail and the relationship to other rules: `@~/.claude/docs/agent-spawn-protocol.md`.
@@ -1,31 +0,0 @@
1
- # AskUserQuestion Required
2
-
3
- Route every user-directed question through the `AskUserQuestion` tool — never a plain-text question in a response's final paragraph. Structure: concise `question`, `header` of 12 chars or fewer, 2-4 options (the UI adds the "Other" fallback), `multiSelect` only when choices genuinely combine.
4
-
5
- No hook checks this. Read your own final paragraph before you send it. A paragraph that ends in a question mark, or that carries an ask-phrase such as "would you like", "should I", or "let me know if", belongs in an `AskUserQuestion` call instead. A rhetorical question answered in the same paragraph is fine, and so is a question inside code or a blockquote. `verify-before-asking` settles whether the question belongs to the user at all.
6
-
7
- ## The question block stays lean
8
-
9
- `AskUserQuestion` renders as one plain unformatted text block. Detail — plans, counts, tradeoffs, background — goes in chat text before the call. The block itself carries a lean question and short choices. When a choice needs formatting, an inline visualizer tool carries it.
10
-
11
- No hook denies an over-full block, so keep an `AskUserQuestion` call inside these caps yourself. They apply to the `question` text and to the `description` under any of the `options`:
12
-
13
- | What the block carries | Cap |
14
- |---|---|
15
- | Fenced code block | none |
16
- | Heading | none |
17
- | Table row | none |
18
- | Bullet or numbered list marker | none |
19
- | Paragraphs | 1 — a blank line splits the block in two |
20
- | `question` sentences | 2 |
21
- | `question` words | 40 |
22
- | `description` sentences | 1 |
23
- | `description` words | 15 |
24
-
25
- Two fields are counted, and each one gets the same treatment: the `question` text, and the `description` under every entry of `options`. The four structure markers apply to both. The paragraph cap applies to both. The sentence and word caps differ by field, as the table shows.
26
-
27
- Structure is read at block level on the raw text: a marker counts when it opens a line, so a fence written on a single line still reads as a fence. Line endings fold to one spelling first, so a blank line counts whichever way it is written.
28
-
29
- An inline code span — a path, a flag, a command the reader needs verbatim — weighs one word against either word cap on both fields, so a question naming `--dry-run` and a choice naming `C:\dev\gate.py` both pass. A span sits inside a line, so it never opens one with a marker.
30
-
31
- A sentence closes on `.`, `!`, or `?` followed by a capitalized word or by the end of the text. A word is any whitespace-separated token carrying a letter or a digit. When a block breaks a cap, move that detail back into chat text before the call.
@@ -1,55 +0,0 @@
1
- # Claims as Quotes
2
-
3
- **When this applies:** Agent reports, hand-off artifacts, review verdicts, and PR or commit prose that state what existing code does. Always-on like `research-mode.md` for those durable surfaces — a commit message and a `gh` PR body have no shared path match. Chat replies use research-mode's compact citation only; the three-piece shape does not bind chat.
4
-
5
- ## Rule
6
-
7
- Under `research-mode.md`, a factual claim carries its source. This rule sets the shape a claim takes when it decides a design or gates an action: the `path:line` reference, the quoted lines, and the claim sentence travel together. The consumer reads those lines before acting on the claim.
8
-
9
- In a chat reply, research-mode's compact citation stands — a linked source name or a `file:line` reference. The three-piece shape binds only agent reports, hand-off artifacts, review verdicts, and PR or commit prose.
10
-
11
- Claims of this shape:
12
-
13
- - "the helper already normalizes this"
14
- - "the gate covers that path"
15
- - "this caller handles the new shape"
16
-
17
- On the surfaces the three-piece shape binds, each one settles a design question for whoever reads it and carries its quote; a claim without its quote decides nothing: it is a lead to check, not a fact to build on.
18
-
19
- ## The failure shape
20
-
21
- A paraphrase of code behavior, accepted without the source lines, ships a design premise nobody checked. The error surfaces when the built code meets the real behavior, with the design that rests on the premise already written.
22
-
23
- ## What a claim carries
24
-
25
- | Piece | Shape |
26
- |---|---|
27
- | Reference | `payments/refund.py:88-90` — the path with the line span |
28
- | Quote | The lines word for word, in a fenced block |
29
- | Claim | One sentence naming what those lines settle |
30
-
31
- ## Examples
32
-
33
- **A lead:** "`build_refund` already clamps the refund to the order total, so the new path needs no bound check."
34
-
35
- **A fact:** the reference, the lines, and the claim together.
36
-
37
- `payments/refund.py:88-90`
38
-
39
- ```python
40
- def build_refund(order, requested_amount):
41
- refund_amount = min(requested_amount, order.total)
42
- return Refund(order_id=order.id, amount=refund_amount)
43
- ```
44
-
45
- `build_refund` clamps to `order.total` on its one path, so a caller downstream of it needs no bound check.
46
-
47
- ## What the consumer does
48
-
49
- - Read the quoted lines before the sentence that summarizes them.
50
- - On the surfaces the three-piece shape binds, treat a claim that arrives without its quote as a lead: pull the lines yourself, or ask the sender for them, before any design rests on it.
51
- - When the quoted lines say something other than the claim, the lines win.
52
-
53
- ## Enforcement
54
-
55
- This rule binds as prose discipline: a reviewer applies it to the claims a report, a verdict, or a PR body makes about existing code. No hook backs it, because telling a design-deciding claim from background prose needs meaning a regex cannot read.
@@ -1,11 +0,0 @@
1
- # Confirm Implementation Forks
2
-
3
- At a material fork — two or more viable paths that change the deliverable's scope, completeness, deferred work, dependencies, or a hard-to-reverse contract — stop and ask which path via `AskUserQuestion` before implementing. A path that defers work or leaves a placeholder is itself a fork to surface, never a silent default.
4
-
5
- How to ask: plain language, one option per path with its tradeoff in the description, recommended path listed first and flagged "(Recommended)", hold edits to the forked area until the answer arrives.
6
-
7
- Not a fork (just proceed): trivially reversible internal-only choices, anything the codebase / stated goal / an existing rule already determines (see `verify-before-asking`), or a single viable path.
8
-
9
- Naming a gap in chat is not surfacing a fork. Saying "no production path calls this yet" or "that is a follow-up" describes the fork; it does not ask it. Shipping code that nothing calls is a fork, because the alternative — wire the caller, or hold the change — changes the deliverable. Route it through `AskUserQuestion` before the merge, not into the closing summary after it.
10
-
11
- When an option you offered names a repair and the answer leaves it undone, write it down before the turn moves on: a todo entry, a `spawn_task` chip, or a filed issue. An answer of "no preference" settles which path you take now. It leaves the repair you found still waiting.
@@ -1,81 +0,0 @@
1
- ---
2
- paths: **/*.py
3
- ---
4
-
5
- # File-Global Constants
6
-
7
- This rule extends the `constants-location` rule defined in `~/.claude/docs/CODE_RULES.md` — see the ⚡ HOOK-ENFORCED RULES section, the "UPPER_SNAKE constants only in `config/`" clause.
8
-
9
- **file_global_constants_use_count:** A file-global constant is a module-level named constant declared at the top of a file (for example, an `UPPER_SNAKE_CASE` value assigned at module scope). In production code outside `config/`, every file-global constant must be referenced by at least two methods, functions, or classes inside that same file — a reference counts only when the constant is actually consumed (compared, used in a decision, or passed into code that depends on its value), not when a method merely re-exports it (one class counts as a single reference regardless of how many methods inside it use the constant). Module-level usages outside any function, method, or class body also count as a reference. A default parameter value counts as one reference from the enclosing function. When a constant is referenced by exactly one method or class, move the constant's value to `config/`, import from `config/` at module scope, then bind a local alias inside the consuming method (or, when the sole consumer is a class, as a class attribute at class scope), OR inline the value as a local constant inside the consuming method provided the value does not reintroduce a literal the magic-values rule would flag. When the sole reference is a module-level expression (for example, `ALL_ITEMS = build_registry(BATCH_SIZE)` at module scope), move the value to `config/` and reference the imported name directly at module scope; no local alias is needed.
10
-
11
- ## Decision table
12
-
13
- - 0 references: dead code — remove the constant.
14
- - 1 reference: move value to `config/`, import at module scope, then bind a local alias inside the consuming method (or, when the sole consumer is a class, as a class attribute at class scope; or inline as a local constant inside the consuming method; or, when the sole consumer is a module-level expression, reference the imported name directly at module scope).
15
- - 2+ references: keep at file scope (counting only consumed references, not re-exports).
16
-
17
- ## Test files are exempt
18
-
19
- Test-file detection uses the following anchored patterns against the full relative path: filename matches `test_*.py`; filename matches `*_test.py`; filename matches `*.test.*`; filename matches `*.spec.*`; filename is `conftest.py`; path contains the segment `/tests/`.
20
-
21
- ## `config/` files are exempt
22
-
23
- Constants placed in `config/` satisfy the constants-location rule; the use-count rule applies only to production code outside `config/`.
24
-
25
- ## Dead constant in a dedicated constants module (cross-module)
26
-
27
- The use-count rule above governs a file-global constant in production code outside `config/` by counting same-file references. A dedicated constants module — a file whose name ends in `_constants.py`, or any module under a `config/` directory — exports its constants to importer modules elsewhere, so a same-file count proves nothing.
28
-
29
- This cross-module concern belongs to the audit lane, not a write-time hook: an `UPPER_SNAKE` constant defined in a constants module whose name appears in no `.py` module anywhere under the enclosing package tree — not imported, not read, not listed in another module's `__all__` literal, not named in a string annotation — is the dead exported constant CODE_RULES §9.8 targets. Category E (dead code) is where a PR audit checks a changed constants module's exports against the rest of the package tree; a reference from a test module under the tree keeps a constant live, and a constant `__all__` omits is the author's private value and is left alone.
30
-
31
- ## Examples
32
-
33
- Flag (single method references the file-global constant — move it inside the method):
34
-
35
- ```python
36
- MAXIMUM_RETRIES = 3
37
-
38
- def fetch_with_retries(url: str) -> str:
39
- for each_attempt_index in range(MAXIMUM_RETRIES):
40
- ...
41
- ```
42
-
43
- The numeric literal `3` here is illustrative only; production values live in `config/` per the magic-values rule.
44
-
45
- Accept (constant declared locally when only one method uses it):
46
-
47
- The local form may bind its value to something sourced from config (an import, a function argument, or another already-named constant), OR inline as a local constant inside the consuming method — either path is acceptable. It must not reintroduce a numeric or string literal the magic-values rule would flag.
48
-
49
- The numeric literal `3` here is illustrative only; production values live in `config/` per the magic-values rule.
50
-
51
- The original file-scope `MAXIMUM_RETRIES = ...` declaration is removed when the value moves to `config/`.
52
-
53
- ```python
54
- from config.timing import MAXIMUM_RETRIES
55
-
56
- def fetch_with_retries(url: str) -> str:
57
- maximum_retries = MAXIMUM_RETRIES
58
- for each_attempt_index in range(maximum_retries):
59
- ...
60
- ```
61
-
62
- Flag (zero references — dead code, remove):
63
-
64
- A file-global constant with zero references is dead code; remove it rather than migrate it to a local.
65
-
66
- Accept (constant kept at file scope when two or more methods reference it):
67
-
68
- A reference counts only when the constant is actually consumed — compared, used in a decision, or passed into code that depends on its value — not when a method merely re-exports it.
69
-
70
- The numeric literal `3` here is illustrative only; production values live in `config/` per the magic-values rule.
71
-
72
- ```python
73
- MAXIMUM_RETRIES = 3
74
-
75
- def fetch_with_retries(url: str) -> str:
76
- for each_attempt_index in range(MAXIMUM_RETRIES):
77
- ...
78
-
79
- def is_retry_limit_reached(attempt_count: int) -> bool:
80
- return attempt_count >= MAXIMUM_RETRIES
81
- ```
@@ -1,7 +0,0 @@
1
- # Hedging Claims
2
-
3
- State a claim with the evidence that backs it, or name the claim unverified. A hedge word (`likely`, `probably`, `might be`, `appears to`, and similar) stands in for evidence you did not gather.
4
-
5
- A label elsewhere in the response does not cover a bare hedge in a different sentence. Label the claim unverified in the same sentence as the hedge, or replace the hedge: gather the source, run the probe, or ask the user through `AskUserQuestion`.
6
-
7
- Sibling rules: [`research-mode.md`](research-mode.md) names what counts as a citation; [`verify-runtime-state.md`](verify-runtime-state.md) names the live probe a runtime verdict rests on.
@@ -1,39 +0,0 @@
1
- ---
2
- paths:
3
- - "**/*.md"
4
- - "**/*.py"
5
- ---
6
-
7
- # Measurement Denominators
8
-
9
- **When this applies:** Any Write or Edit that states a count, rate, or coverage figure in a docstring or a `.md` file.
10
-
11
- ## Rule
12
-
13
- Every count names what it scanned. "Read 10 of 10 changed files" states the scan and the whole set it was drawn from. "Swept the files" states neither, and a reader takes it as full coverage of a scan that touched a fraction.
14
-
15
- Three parts travel with each figure. The report author writes part 1 for every figure and part 2 when the figure is a rate; the consumer runs part 3:
16
-
17
- 1. **A count carries its denominator.** Name both numbers: files read of files changed, rounds fired of rounds available, tests reached of tests collected, entries compared of entries present.
18
- 2. **A rate needs two runs and a stated denominator.** One run yields a count. Written down as a rate — "fires 40% of the time" — it claims a spread across runs that one run cannot back. Report the count and the run that produced it until a second run exists; once it does, the rate stays counts across runs — "failed twice across 5 runs on this branch" — never a percentage.
19
- 3. **The consumer cross-checks the count against the scope its denominator names.** A sweep reporting 3 files read of 10 changed fails coverage: its verdict reaches 3 of the 10 files its own denominator names. A sweep reporting 3 of 3 security-sensitive files states its own scope and covers all of it, so it passes. That comparison settles the sweep before anyone reads its result.
20
-
21
- A number written into a docstring or a doc carries its denominator beside it. A number written there as a rate carries its run count too.
22
-
23
- ## Shapes
24
-
25
- | Bare figure | With its denominator |
26
- |---|---|
27
- | swept the files | read 10 of 10 changed files |
28
- | all tests pass | 412 of 412 collected tests pass, 0 skipped |
29
- | the hook catches this | the hook denies 6 of 6 recorded shapes |
30
- | fails about 40% of the time | failed twice across 5 runs on this branch |
31
- | most entries match | 88 of 90 entries match |
32
-
33
- ## Cross-check at the consumer
34
-
35
- A sweep reports "read 3 files". The change under review touches 10 files. The arithmetic reads: the sweep covered 3 of 10, so its clean verdict covers 3 of 10. The gap is the finding — send the sweep back for the other 7, and hold the verdict until the two numbers meet.
36
-
37
- ## Enforcement
38
-
39
- This rule binds as prose discipline: a reviewer applies it to the counts a PR's prose and docstrings state, and to the counts a report claims. No hook backs it, because weighing a figure's denominator against the scope it covers needs meaning a regex cannot read.
@@ -1,19 +0,0 @@
1
- # Prompt Workflow Context Controls
2
-
3
- Prompt workflows stay low-context.
4
-
5
- ## Base Minimal Instruction Layer
6
-
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
-
9
- ## On-Demand Skill Loading
10
-
11
- Load heavy or specialized skills only on explicit task intent.
12
-
13
- ## Compaction and Caching Strategy
14
-
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.
16
-
17
- ## Runtime Enforcement Signals
18
-
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,48 +0,0 @@
1
- # Pstack host mapping
2
-
3
- Pstack skills ship with one host's tool names and paths written into their steps. The
4
- active host supplies the real ones. Read a pstack step for the shape of the work, and take
5
- the tool name, the skill home, and the transcript location from the host you run on.
6
-
7
- [`pstack-models.md`](pstack-models.md) covers the model choice for each role. This rule
8
- covers everything else a pstack step names.
9
-
10
- ## What each host supplies
11
-
12
- | A pstack step names | Take from the host |
13
- |---|---|
14
- | A subagent tool | The native subagent tool this session exposes, with its own agent-type field |
15
- | A skills directory | The skills home this host reads |
16
- | A transcript directory | The session transcript location this host writes |
17
- | A model identifier | The identifiers the native subagent tool accepts today |
18
-
19
- A pstack step that names one host's directory means "the matching directory here". Resolve
20
- it before the search, and search only inside the active workspace.
21
-
22
- ## The final message is the whole report
23
-
24
- On a host where a subagent returns one message to its parent, that message is all the
25
- parent receives. Working notes, tool output, and text the child printed along the way do
26
- not travel.
27
-
28
- Every pstack prompt that asks a child for a list, a set of findings, or a verdict also
29
- asks for the whole thing, word for word, in the final message. A prompt without that line
30
- gets back a summary such as "findings reported above", and the findings are gone. The
31
- parent then reruns the child, which costs the full run a second time.
32
-
33
- ## Panel spawns
34
-
35
- Panel size and model diversity follow `pstack-models.md`. Two host limits shape a spawn
36
- beyond that:
37
-
38
- - A spawn gate can refuse a model tier. Read the denial, take the tier it names as
39
- allowed, and spawn again.
40
- - A host may accept fewer distinct models than a panel asks for. A panel that requires
41
- distinct models stops; a panel that treats diversity as optional runs with repeats.
42
-
43
- ## Editing pstack
44
-
45
- Pstack installs as a plugin. A plugin update overwrites its files, so an edit inside a
46
- pstack skill lasts until the next update. Durable changes belong in a rule beside this
47
- one. The installer copies this package's rules into the managed rules directory and
48
- generates the Cursor copy from the same source.