claude-dev-env 8.0.0 → 8.2.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/.agents/agents/clean-coder.md +4 -4
- package/.agents/agents/code-quality-agent.md +4 -4
- package/.agents/agents/plan-packet-validator.md +2 -2
- package/.agents/agents/session-advisor.md +1 -1
- package/.agents/skills/cert-classification-rule/SKILL.md +3 -3
- package/.agents/skills/e-code-review/SKILL.md +2 -2
- package/.agents/skills/e-code-review/reference/fix.md +1 -1
- package/.agents/skills/e-code-review/reference/loop.md +1 -1
- package/.agents/skills/e-code-review/reference/medium.md +4 -4
- package/.agents/skills/e-code-review/reference/xhigh.md +5 -5
- package/.agents/skills/everything-search/SKILL.md +3 -3
- package/.agents/skills/issue-tracker/SKILL.md +1 -1
- package/.agents/skills/orchestrator/SKILL.md +3 -3
- package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +1 -1
- package/.agents/skills/orchestrator/reference/executor-consult-block.md +1 -1
- package/.agents/skills/orchestrator-refresh/SKILL.md +2 -2
- package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +96 -0
- package/.agents/skills/plugin-eval-standalone-skill/reference/graders-and-commands.md +108 -0
- package/.agents/skills/privacy-hygiene/SKILL.md +5 -6
- package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +5 -6
- package/.agents/skills/repairing-hook-boundaries/SKILL.md +1 -1
- package/.agents/skills/syncing-submodules/SKILL.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +9 -7
- package/.agents/skills/team-advisor/reference/advisor-docs-review.md +28 -1
- package/.agents/skills/usage-pause/SKILL.md +3 -3
- package/_shared/advisor/advisor-protocol.md +7 -3
- package/_shared/advisor/reference/advisor-block.md +8 -1
- package/audit-rubrics/audit-categories.json +0 -4
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +2 -2
- package/audit-rubrics/category_rubrics/category-i-concurrency.md +1 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -2
- package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +1 -1
- package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +5 -5
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +5 -5
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +3 -3
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +1 -1
- package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +3 -3
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +3 -3
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +1 -1
- package/audit-rubrics/prompts/category-e-dead-code.md +5 -5
- package/audit-rubrics/prompts/category-f-silent-failures.md +12 -12
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
- package/audit-rubrics/prompts/category-h-security-boundaries.md +4 -4
- package/audit-rubrics/prompts/category-i-concurrency.md +6 -6
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +6 -29
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +1 -1
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +7 -7
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +3 -3
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +2 -2
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/ever-shipped-skills.test.mjs +4 -0
- package/docs/BDD_SCENARIO_QUALITY.md +2 -2
- package/docs/CODE_RULES.md +7 -7
- package/docs/TEST_QUALITY.md +1 -1
- package/docs/nas-ssh-invocation.md +1 -1
- package/docs/references/advisor-tool.md +2 -2
- package/docs/references/prose-style-enforcement.md +1 -1
- package/docs/references/team-advisor-skill.md +2 -2
- package/docs/references/weak-executor-advisor.md +1 -1
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/package.json +1 -1
- package/rules/anti-corollary-tests.md +3 -1
- package/rules/archiving-agent-config.md +67 -0
- package/rules/asd-ste100-language.md +1 -1
- package/rules/ci-owns-the-gate.md +15 -0
- package/rules/cleanup-temp-files.md +1 -1
- package/rules/code-standards.md +2 -2
- package/rules/docstring-prose-matches-implementation.md +1 -1
- package/rules/explore-thoroughly.md +2 -2
- package/rules/failure-blast-radius.md +9 -8
- package/rules/git-workflow.md +7 -4
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/nas-ssh-invocation.md +1 -1
- package/rules/research-mode.md +3 -1
- package/rules/retired-hook-prose.md +10 -2
- package/rules/testing.md +3 -3
- package/rules/verify-before-asking.md +3 -0
- package/rules/verify-runtime-state.md +2 -2
- package/scripts/active_capability_references.py +74 -19
- package/scripts/banned_prose_words.py +159 -0
- package/scripts/dev_env_scripts_constants/active_capability_constants.py +12 -10
- package/scripts/dev_env_scripts_constants/banned_prose_word_constants.py +95 -0
- package/scripts/policy_lint/adapter_retired_hook_prose.py +17 -7
- package/scripts/policy_lint/adapters.py +6 -6
- package/scripts/policy_lint/config/constants.py +8 -0
- package/scripts/policy_lint/registry.py +1 -1
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_active_capability_references.py +100 -33
- package/scripts/tests/test_banned_prose_words.py +103 -0
- package/scripts/tests/test_codex_compat_materializer.py +3 -3
- package/scripts/tests/test_engine.py +1 -1
- package/scripts/tests/test_policy_lint_rules_retired_hook_prose.py +46 -0
- package/scripts/tests/test_rules.py +12 -1
- package/system-prompts/software-engineer.xml +7 -7
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
Builds inventories from committed skill manifests, agent markdown files, and
|
|
5
5
|
commands. Scans active prompt text for slash and backticked capability names.
|
|
6
|
-
Fails when an active (non-inert) reference names a
|
|
6
|
+
Fails when an active (non-inert) reference names a capability the tree
|
|
7
|
+
dropped, resolved from the ever-shipped registry against what the tree
|
|
8
|
+
holds today.
|
|
7
9
|
|
|
8
10
|
::
|
|
9
11
|
|
|
10
12
|
unresolved = unresolved_active_capabilities(package_root)
|
|
11
13
|
ok: unresolved == []
|
|
12
|
-
flag: active text mentions
|
|
14
|
+
flag: active text mentions a name the tree dropped
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
from __future__ import annotations
|
|
@@ -19,10 +21,12 @@ from dataclasses import dataclass
|
|
|
19
21
|
from pathlib import Path
|
|
20
22
|
|
|
21
23
|
from dev_env_scripts_constants.active_capability_constants import (
|
|
22
|
-
ALL_BANNED_ACTIVE_CAPABILITY_NAMES,
|
|
23
24
|
ALL_INERT_FENCE_LANGUAGES,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
ALL_QUALIFIED_CAPABILITY_PATTERNS,
|
|
26
|
+
ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES,
|
|
27
|
+
BLANKED_SPAN,
|
|
28
|
+
EVER_SHIPPED_NAME_PATTERN,
|
|
29
|
+
EVER_SHIPPED_REGISTRY_RELATIVE_PATH,
|
|
26
30
|
FENCE_CLOSE_PATTERN,
|
|
27
31
|
FENCE_OPEN_PATTERN,
|
|
28
32
|
NEWLINE_JOIN_SEPARATOR,
|
|
@@ -31,8 +35,11 @@ from dev_env_scripts_constants.active_capability_constants import (
|
|
|
31
35
|
PACKAGE_ROOT_AGENTS_DIRECTORY,
|
|
32
36
|
PACKAGE_ROOT_SKILLS_DIRECTORY,
|
|
33
37
|
PACKAGE_SKILLS_DIRECTORY,
|
|
38
|
+
RETIRED_REASON_PREFIX,
|
|
34
39
|
SKILL_MANIFEST_FILENAME,
|
|
40
|
+
MULTI_SEGMENT_PATH_PATTERN,
|
|
35
41
|
SLASH_CAPABILITY_PATTERN,
|
|
42
|
+
URL_PATTERN,
|
|
36
43
|
UTF8_ENCODING,
|
|
37
44
|
)
|
|
38
45
|
|
|
@@ -52,7 +59,7 @@ class CapabilityInventory:
|
|
|
52
59
|
|
|
53
60
|
@dataclass(frozen=True)
|
|
54
61
|
class UnresolvedCapabilityReference:
|
|
55
|
-
"""One active reference that names a
|
|
62
|
+
"""One active reference that names a capability outside the shipped tree."""
|
|
56
63
|
|
|
57
64
|
file_path: str
|
|
58
65
|
line_number: int
|
|
@@ -148,6 +155,10 @@ def strip_inert_fenced_blocks(markdown_text: str) -> str:
|
|
|
148
155
|
def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]:
|
|
149
156
|
"""Return (1-based line, capability name) for active references.
|
|
150
157
|
|
|
158
|
+
A slash reference counts as an invocation, and a qualified reference
|
|
159
|
+
names a skill directory or manifest. A URL and a multi-segment path
|
|
160
|
+
carry neither, so both are blanked before the scan.
|
|
161
|
+
|
|
151
162
|
Args:
|
|
152
163
|
markdown_text: Prompt or skill markdown.
|
|
153
164
|
|
|
@@ -155,31 +166,72 @@ def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]
|
|
|
155
166
|
Ordered list of line number and capability name pairs.
|
|
156
167
|
"""
|
|
157
168
|
slash_pattern = re.compile(SLASH_CAPABILITY_PATTERN)
|
|
158
|
-
|
|
169
|
+
url_pattern = re.compile(URL_PATTERN)
|
|
170
|
+
path_pattern = re.compile(MULTI_SEGMENT_PATH_PATTERN)
|
|
171
|
+
all_qualified_patterns = [
|
|
172
|
+
re.compile(each_pattern) for each_pattern in ALL_QUALIFIED_CAPABILITY_PATTERNS
|
|
173
|
+
]
|
|
159
174
|
active_text = strip_inert_fenced_blocks(markdown_text)
|
|
160
175
|
all_hits: list[tuple[int, str]] = []
|
|
161
176
|
for each_line_number, each_line in enumerate(active_text.splitlines(), start=1):
|
|
162
|
-
for
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
for each_pattern in all_qualified_patterns:
|
|
178
|
+
for each_match in each_pattern.finditer(each_line):
|
|
179
|
+
all_hits.append((each_line_number, each_match.group(1)))
|
|
180
|
+
invocation_line = path_pattern.sub(
|
|
181
|
+
BLANKED_SPAN, url_pattern.sub(BLANKED_SPAN, each_line)
|
|
182
|
+
)
|
|
183
|
+
for each_match in slash_pattern.finditer(invocation_line):
|
|
165
184
|
all_hits.append((each_line_number, each_match.group(1)))
|
|
166
185
|
return all_hits
|
|
167
186
|
|
|
168
187
|
|
|
169
|
-
def
|
|
170
|
-
|
|
188
|
+
def retired_capability_names(
|
|
189
|
+
from_package_root: Path, inventory: CapabilityInventory
|
|
190
|
+
) -> frozenset[str]:
|
|
191
|
+
"""Return the capability names the shipped tree dropped.
|
|
171
192
|
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
The ever-shipped registry keeps every skill name the package released.
|
|
194
|
+
Subtracting what the tree holds today leaves the retired set, so no hand
|
|
195
|
+
kept list decides which name fails.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
from_package_root: ``packages/claude-dev-env`` root.
|
|
199
|
+
inventory: Names the tree holds today.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Retired capability names.
|
|
203
|
+
"""
|
|
204
|
+
registry_path = from_package_root / EVER_SHIPPED_REGISTRY_RELATIVE_PATH
|
|
205
|
+
all_ever_shipped_names: set[str] = set(
|
|
206
|
+
ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES
|
|
207
|
+
)
|
|
208
|
+
if registry_path.is_file():
|
|
209
|
+
all_ever_shipped_names.update(
|
|
210
|
+
re.findall(
|
|
211
|
+
EVER_SHIPPED_NAME_PATTERN,
|
|
212
|
+
registry_path.read_text(encoding=UTF8_ENCODING),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
return frozenset(all_ever_shipped_names - inventory.all_known_names())
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def classify_capability_reference(
|
|
219
|
+
capability_name: str, all_retired_names: frozenset[str]
|
|
220
|
+
) -> str | None:
|
|
221
|
+
"""Return a failure reason when the capability left the tree.
|
|
222
|
+
|
|
223
|
+
Extractors also match ordinary prose tokens, so a name the package never
|
|
224
|
+
shipped passes. A name the registry records and the tree dropped fails.
|
|
174
225
|
|
|
175
226
|
Args:
|
|
176
227
|
capability_name: Extracted skill/command-like name.
|
|
228
|
+
all_retired_names: Names resolved by ``retired_capability_names``.
|
|
177
229
|
|
|
178
230
|
Returns:
|
|
179
231
|
Reason string, or None when the reference is allowed.
|
|
180
232
|
"""
|
|
181
|
-
if capability_name in
|
|
182
|
-
return f"{
|
|
233
|
+
if capability_name in all_retired_names:
|
|
234
|
+
return f"{RETIRED_REASON_PREFIX}{capability_name}"
|
|
183
235
|
return None
|
|
184
236
|
|
|
185
237
|
|
|
@@ -188,7 +240,7 @@ def unresolved_active_capabilities(
|
|
|
188
240
|
*,
|
|
189
241
|
all_relative_markdown_paths: list[str] | None = None,
|
|
190
242
|
) -> list[UnresolvedCapabilityReference]:
|
|
191
|
-
"""Scan package markdown for
|
|
243
|
+
"""Scan package markdown for references to retired capabilities.
|
|
192
244
|
|
|
193
245
|
Args:
|
|
194
246
|
from_package_root: ``packages/claude-dev-env`` root.
|
|
@@ -196,10 +248,13 @@ def unresolved_active_capabilities(
|
|
|
196
248
|
defaults to all skills, agents, and commands markdown.
|
|
197
249
|
|
|
198
250
|
Returns:
|
|
199
|
-
|
|
251
|
+
Retired references with file, line, name, and reason.
|
|
200
252
|
"""
|
|
201
253
|
if all_relative_markdown_paths is None:
|
|
202
254
|
all_relative_markdown_paths = _default_markdown_paths(from_package_root)
|
|
255
|
+
all_retired_names = retired_capability_names(
|
|
256
|
+
from_package_root, build_capability_inventory(from_package_root)
|
|
257
|
+
)
|
|
203
258
|
all_unresolved: list[UnresolvedCapabilityReference] = []
|
|
204
259
|
for each_relative_path in all_relative_markdown_paths:
|
|
205
260
|
absolute_path = from_package_root / each_relative_path
|
|
@@ -209,7 +264,7 @@ def unresolved_active_capabilities(
|
|
|
209
264
|
for each_line_number, each_name in extract_active_capability_names(
|
|
210
265
|
markdown_text
|
|
211
266
|
):
|
|
212
|
-
reason = classify_capability_reference(each_name)
|
|
267
|
+
reason = classify_capability_reference(each_name, all_retired_names)
|
|
213
268
|
if reason is None:
|
|
214
269
|
continue
|
|
215
270
|
all_unresolved.append(
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Find banned emphasis words in authored prose.
|
|
3
|
+
|
|
4
|
+
The package AGENTS.md bans ``real``, ``really``, ``real-world``, ``actual``,
|
|
5
|
+
``actually``, ``genuine``, and ``true`` from every authored surface. This
|
|
6
|
+
module reports each occurrence that survives after code spans, paths, and
|
|
7
|
+
link targets are removed, so a caller can grade a document or a whole tree.
|
|
8
|
+
|
|
9
|
+
::
|
|
10
|
+
|
|
11
|
+
all_hits = find_banned_prose_words("The real cause is a timeout.\n")
|
|
12
|
+
ok: all_hits == [(1, "real")]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path, PurePosixPath
|
|
18
|
+
|
|
19
|
+
from dev_env_scripts_constants.banned_prose_word_constants import (
|
|
20
|
+
ADJECTIVAL_TRUE_PATTERN,
|
|
21
|
+
ALL_EXEMPT_PATH_SEGMENTS,
|
|
22
|
+
ALL_GOVERNED_PATH_SEGMENTS,
|
|
23
|
+
ALL_GOVERNED_SUFFIXES,
|
|
24
|
+
ALL_PREDICATIVE_TRUE_LEADS,
|
|
25
|
+
BANNED_PROSE_WORD_PATTERN,
|
|
26
|
+
BLANKED_SPAN,
|
|
27
|
+
FENCED_CODE_PATTERN,
|
|
28
|
+
INDENTED_CODE_PATTERN,
|
|
29
|
+
INLINE_CODE_PATTERN,
|
|
30
|
+
MARKDOWN_LINK_TARGET_PATTERN,
|
|
31
|
+
PATH_PATTERN,
|
|
32
|
+
URL_PATTERN,
|
|
33
|
+
UTF8_ENCODING,
|
|
34
|
+
XML_TAG_PATTERN,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def governs_path(relative_posix_path: str) -> bool:
|
|
39
|
+
"""Return whether the ban applies to one repository path.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
relative_posix_path: Repository-relative path with forward slashes.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True for an authored instruction surface the ban covers.
|
|
46
|
+
"""
|
|
47
|
+
normalized_path = f"/{relative_posix_path.lower().lstrip('/')}"
|
|
48
|
+
if PurePosixPath(normalized_path).suffix not in ALL_GOVERNED_SUFFIXES:
|
|
49
|
+
return False
|
|
50
|
+
if any(
|
|
51
|
+
each_segment in normalized_path for each_segment in ALL_EXEMPT_PATH_SEGMENTS
|
|
52
|
+
):
|
|
53
|
+
return False
|
|
54
|
+
return any(
|
|
55
|
+
each_segment in normalized_path for each_segment in ALL_GOVERNED_PATH_SEGMENTS
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def prose_lines(document_text: str) -> list[str]:
|
|
60
|
+
"""Return one prose line for each source line, with code spans blanked.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
document_text: Full document source.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Lines of the same count as the source, holding prose only.
|
|
67
|
+
"""
|
|
68
|
+
all_prose_lines: list[str] = []
|
|
69
|
+
is_inside_fence = False
|
|
70
|
+
for each_line in document_text.splitlines():
|
|
71
|
+
if FENCED_CODE_PATTERN.match(each_line):
|
|
72
|
+
is_inside_fence = not is_inside_fence
|
|
73
|
+
all_prose_lines.append("")
|
|
74
|
+
continue
|
|
75
|
+
if is_inside_fence or INDENTED_CODE_PATTERN.match(each_line):
|
|
76
|
+
all_prose_lines.append("")
|
|
77
|
+
continue
|
|
78
|
+
prose_line = INLINE_CODE_PATTERN.sub(BLANKED_SPAN, each_line)
|
|
79
|
+
prose_line = XML_TAG_PATTERN.sub(BLANKED_SPAN, prose_line)
|
|
80
|
+
prose_line = MARKDOWN_LINK_TARGET_PATTERN.sub(BLANKED_SPAN, prose_line)
|
|
81
|
+
prose_line = URL_PATTERN.sub(BLANKED_SPAN, prose_line)
|
|
82
|
+
all_prose_lines.append(PATH_PATTERN.sub(BLANKED_SPAN, prose_line))
|
|
83
|
+
return all_prose_lines
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _adjectival_true_columns(prose_line: str) -> list[int]:
|
|
87
|
+
all_columns: list[int] = []
|
|
88
|
+
for each_match in ADJECTIVAL_TRUE_PATTERN.finditer(prose_line):
|
|
89
|
+
leading_words = prose_line[: each_match.start()].lower().split()
|
|
90
|
+
if leading_words and leading_words[-1].strip(",;:(") in (
|
|
91
|
+
ALL_PREDICATIVE_TRUE_LEADS
|
|
92
|
+
):
|
|
93
|
+
continue
|
|
94
|
+
all_columns.append(each_match.start() + 1)
|
|
95
|
+
return all_columns
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def find_banned_prose_words(document_text: str) -> list[tuple[int, int, str]]:
|
|
99
|
+
"""Return each banned word occurrence in authored prose.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
document_text: Full document source.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Ordered ``(line number, column, word)`` triples, both 1-based.
|
|
106
|
+
"""
|
|
107
|
+
all_hits: list[tuple[int, int, str]] = []
|
|
108
|
+
for each_line_number, each_prose_line in enumerate(prose_lines(document_text), 1):
|
|
109
|
+
for each_match in BANNED_PROSE_WORD_PATTERN.finditer(each_prose_line):
|
|
110
|
+
all_hits.append(
|
|
111
|
+
(each_line_number, each_match.start() + 1, each_match.group(1).lower())
|
|
112
|
+
)
|
|
113
|
+
for each_column in _adjectival_true_columns(each_prose_line):
|
|
114
|
+
all_hits.append((each_line_number, each_column, "true"))
|
|
115
|
+
return sorted(all_hits)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def governed_paths(from_package_root: Path) -> list[str]:
|
|
119
|
+
"""Return every governed surface under one package root.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
from_package_root: ``packages/claude-dev-env`` root.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Sorted package-relative paths the ban covers.
|
|
126
|
+
"""
|
|
127
|
+
all_paths: list[str] = []
|
|
128
|
+
for each_path in from_package_root.rglob("*"):
|
|
129
|
+
if not each_path.is_file():
|
|
130
|
+
continue
|
|
131
|
+
relative_path = each_path.relative_to(from_package_root).as_posix()
|
|
132
|
+
if governs_path(relative_path):
|
|
133
|
+
all_paths.append(relative_path)
|
|
134
|
+
return sorted(all_paths)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def banned_prose_words_in_tree(
|
|
138
|
+
from_package_root: Path,
|
|
139
|
+
) -> list[tuple[str, int, int, str]]:
|
|
140
|
+
"""Return every banned word occurrence across the governed surfaces.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
from_package_root: ``packages/claude-dev-env`` root.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Ordered ``(package-relative path, line number, column, word)`` tuples.
|
|
147
|
+
"""
|
|
148
|
+
all_hits: list[tuple[str, int, int, str]] = []
|
|
149
|
+
for each_relative_path in governed_paths(from_package_root):
|
|
150
|
+
document_text = (from_package_root / each_relative_path).read_text(
|
|
151
|
+
encoding=UTF8_ENCODING, errors="replace"
|
|
152
|
+
)
|
|
153
|
+
for each_line_number, each_column, each_word in find_banned_prose_words(
|
|
154
|
+
document_text
|
|
155
|
+
):
|
|
156
|
+
all_hits.append(
|
|
157
|
+
(each_relative_path, each_line_number, each_column, each_word)
|
|
158
|
+
)
|
|
159
|
+
return all_hits
|
|
@@ -14,18 +14,16 @@ PACKAGE_ROOT_SKILLS_DIRECTORY: str = "skills"
|
|
|
14
14
|
PACKAGE_ROOT_AGENTS_DIRECTORY: str = "agents"
|
|
15
15
|
SKILL_MANIFEST_FILENAME: str = "SKILL.md"
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
ALL_BANNED_ACTIVE_CAPABILITY_NAMES: frozenset[str] = frozenset(
|
|
17
|
+
ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES: frozenset[str] = frozenset(
|
|
19
18
|
{
|
|
20
19
|
"stub-detector",
|
|
21
20
|
"superpowers",
|
|
22
|
-
"qbug",
|
|
23
|
-
"findbugs",
|
|
24
|
-
"fixbugs",
|
|
25
|
-
"monitor-open-prs",
|
|
26
21
|
}
|
|
27
22
|
)
|
|
28
23
|
|
|
24
|
+
EVER_SHIPPED_REGISTRY_RELATIVE_PATH: str = "bin/ever-shipped-skills.mjs"
|
|
25
|
+
EVER_SHIPPED_NAME_PATTERN: str = r"'([a-z][a-z0-9-]*)'"
|
|
26
|
+
|
|
29
27
|
# Fence languages treated as inert historical/example content.
|
|
30
28
|
ALL_INERT_FENCE_LANGUAGES: frozenset[str] = frozenset(
|
|
31
29
|
{
|
|
@@ -37,13 +35,17 @@ ALL_INERT_FENCE_LANGUAGES: frozenset[str] = frozenset(
|
|
|
37
35
|
}
|
|
38
36
|
)
|
|
39
37
|
|
|
40
|
-
SLASH_CAPABILITY_PATTERN: str = r"(?<![`\w])/([a-z][a-z0-9-]{2,})"
|
|
41
|
-
|
|
42
|
-
r"
|
|
38
|
+
SLASH_CAPABILITY_PATTERN: str = r"(?<![`\w./-])/([a-z][a-z0-9-]{2,})(?![\w/-])"
|
|
39
|
+
ALL_QUALIFIED_CAPABILITY_PATTERNS: tuple[str, ...] = (
|
|
40
|
+
r"(?<![\w-])([a-z][a-z0-9-]{2,})/SKILL\.md",
|
|
41
|
+
r"`skills/([a-z][a-z0-9-]{2,})`",
|
|
43
42
|
)
|
|
43
|
+
URL_PATTERN: str = r"\b[a-z][a-z0-9+.-]*://\S+"
|
|
44
|
+
MULTI_SEGMENT_PATH_PATTERN: str = r"(?<![\w-])[\w.{}-]*/[\w.{}/-]*/[\w.{}-]+"
|
|
45
|
+
BLANKED_SPAN: str = " "
|
|
44
46
|
FENCE_OPEN_PATTERN: str = r"^```([A-Za-z0-9_-]*)\s*$"
|
|
45
47
|
FENCE_CLOSE_PATTERN: str = r"^```\s*$"
|
|
46
|
-
|
|
48
|
+
RETIRED_REASON_PREFIX: str = "retired_capability:"
|
|
47
49
|
|
|
48
50
|
UTF8_ENCODING: str = "utf-8"
|
|
49
51
|
NEWLINE_JOIN_SEPARATOR: str = "\n"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Constants for the banned-prose-word detector.
|
|
2
|
+
|
|
3
|
+
The package AGENTS.md bans a family of emphasis words from every authored
|
|
4
|
+
surface. These patterns describe where the ban applies and which spans of a
|
|
5
|
+
document are code rather than prose.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
UTF8_ENCODING: str = "utf-8"
|
|
13
|
+
|
|
14
|
+
ALL_GOVERNED_PATH_SEGMENTS: tuple[str, ...] = (
|
|
15
|
+
"/.agents/agents/",
|
|
16
|
+
"/.agents/skills/",
|
|
17
|
+
"/audit-rubrics/",
|
|
18
|
+
"/commands/",
|
|
19
|
+
"/docs/",
|
|
20
|
+
"/output-styles/",
|
|
21
|
+
"/rules/",
|
|
22
|
+
"/system-prompts/",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
ALL_EXEMPT_PATH_SEGMENTS: tuple[str, ...] = (
|
|
26
|
+
"-archived/",
|
|
27
|
+
"/fixtures/",
|
|
28
|
+
"/tests/",
|
|
29
|
+
"/changelog.md",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
ALL_GOVERNED_SUFFIXES: frozenset[str] = frozenset({".md", ".mdx", ".xml"})
|
|
33
|
+
|
|
34
|
+
ALL_BANNED_PROSE_WORDS: tuple[str, ...] = (
|
|
35
|
+
"real",
|
|
36
|
+
"really",
|
|
37
|
+
"real-world",
|
|
38
|
+
"actual",
|
|
39
|
+
"actually",
|
|
40
|
+
"genuine",
|
|
41
|
+
"genuinely",
|
|
42
|
+
"truly",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
BANNED_PROSE_WORD_PATTERN: re.Pattern[str] = re.compile(
|
|
46
|
+
r"(?<![\w-])(" + "|".join(ALL_BANNED_PROSE_WORDS) + r")(?![\w-])",
|
|
47
|
+
re.IGNORECASE,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
ADJECTIVAL_TRUE_PATTERN: re.Pattern[str] = re.compile(
|
|
51
|
+
r"(?<![\w-])true(?![\w-])\s+[a-z]", re.IGNORECASE
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
ALL_PREDICATIVE_TRUE_LEADS: frozenset[str] = frozenset(
|
|
55
|
+
{
|
|
56
|
+
"is",
|
|
57
|
+
"are",
|
|
58
|
+
"was",
|
|
59
|
+
"were",
|
|
60
|
+
"be",
|
|
61
|
+
"been",
|
|
62
|
+
"being",
|
|
63
|
+
"always",
|
|
64
|
+
"still",
|
|
65
|
+
"holds",
|
|
66
|
+
"hold",
|
|
67
|
+
"held",
|
|
68
|
+
"stays",
|
|
69
|
+
"stay",
|
|
70
|
+
"remains",
|
|
71
|
+
"remain",
|
|
72
|
+
"returns",
|
|
73
|
+
"return",
|
|
74
|
+
"reads",
|
|
75
|
+
"read",
|
|
76
|
+
"and",
|
|
77
|
+
"or",
|
|
78
|
+
"not",
|
|
79
|
+
"never",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
FENCED_CODE_PATTERN: re.Pattern[str] = re.compile(r"^\s*```")
|
|
84
|
+
INDENTED_CODE_PATTERN: re.Pattern[str] = re.compile(r"^(?: {4,}|\t)\S")
|
|
85
|
+
INLINE_CODE_PATTERN: re.Pattern[str] = re.compile(r"`[^`]*`")
|
|
86
|
+
URL_PATTERN: re.Pattern[str] = re.compile(r"\b(?:https?|file)://\S+")
|
|
87
|
+
PATH_PATTERN: re.Pattern[str] = re.compile(r"(?<![\w])[\w./-]*/[\w./-]+")
|
|
88
|
+
MARKDOWN_LINK_TARGET_PATTERN: re.Pattern[str] = re.compile(r"\]\([^)]*\)")
|
|
89
|
+
XML_TAG_PATTERN: re.Pattern[str] = re.compile(r"<[^>]+>")
|
|
90
|
+
BLANKED_SPAN: str = " "
|
|
91
|
+
|
|
92
|
+
BANNED_PROSE_WORD_RULE_ID: str = "banned-prose-word"
|
|
93
|
+
BANNED_PROSE_WORD_MESSAGE: str = (
|
|
94
|
+
"Banned word: {word}. Delete it and name the evidence instead."
|
|
95
|
+
)
|
|
@@ -15,23 +15,33 @@ from .config import constants
|
|
|
15
15
|
from .model import Diagnostic, Document, Location, Severity
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
19
|
-
"""Return whether the document is
|
|
18
|
+
def accepts_instruction_markdown(document: Document) -> bool:
|
|
19
|
+
"""Return whether the document is authored instruction Markdown.
|
|
20
|
+
|
|
21
|
+
Rules, skills, agents, commands, documentation and stored system prompts
|
|
22
|
+
all describe hooks to a reader, so each one carries the same claim.
|
|
20
23
|
|
|
21
24
|
Args:
|
|
22
25
|
document: Candidate document.
|
|
23
26
|
|
|
24
27
|
Returns:
|
|
25
|
-
True for
|
|
28
|
+
True for Markdown on an instruction surface.
|
|
26
29
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
if document.path.suffix.lower() not in constants.ALL_MARKDOWN_SUFFIXES:
|
|
31
|
+
return False
|
|
32
|
+
normalized_path = f"/{document.path.as_posix().lower()}"
|
|
33
|
+
return any(
|
|
34
|
+
each_segment in normalized_path
|
|
35
|
+
for each_segment in constants.ALL_HOOK_PROSE_SURFACE_SEGMENTS
|
|
30
36
|
)
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
def _package_root(repository_root: Path, document: Document) -> Path:
|
|
34
|
-
|
|
40
|
+
for each_ancestor in document.path.parents:
|
|
41
|
+
candidate_root = repository_root / each_ancestor.as_posix()
|
|
42
|
+
if (candidate_root / constants.HOOKS_DIRECTORY_NAME).is_dir():
|
|
43
|
+
return candidate_root
|
|
44
|
+
return repository_root / document.path.parent.as_posix()
|
|
35
45
|
|
|
36
46
|
|
|
37
47
|
def _read_text(path: Path) -> str:
|
|
@@ -427,25 +427,25 @@ def accepts_plans(document: Document) -> bool:
|
|
|
427
427
|
)
|
|
428
428
|
|
|
429
429
|
|
|
430
|
-
def
|
|
431
|
-
"""Return whether the document is
|
|
430
|
+
def accepts_instruction_markdown(document: Document) -> bool:
|
|
431
|
+
"""Return whether the document is authored instruction Markdown.
|
|
432
432
|
|
|
433
433
|
Args:
|
|
434
434
|
document: Candidate document.
|
|
435
435
|
|
|
436
436
|
Returns:
|
|
437
|
-
True for
|
|
437
|
+
True for Markdown on an instruction surface.
|
|
438
438
|
"""
|
|
439
|
-
return adapter_retired_hook_prose.
|
|
439
|
+
return adapter_retired_hook_prose.accepts_instruction_markdown(document)
|
|
440
440
|
|
|
441
441
|
|
|
442
442
|
def retired_hook_prose_diagnostics(
|
|
443
443
|
document: Document, repository_root: Path
|
|
444
444
|
) -> tuple[Diagnostic, ...]:
|
|
445
|
-
"""Report
|
|
445
|
+
"""Report instruction prose that credits an unregistered hook with a live action.
|
|
446
446
|
|
|
447
447
|
Args:
|
|
448
|
-
document: Current
|
|
448
|
+
document: Current instruction Markdown text and path.
|
|
449
449
|
repository_root: Request repository root for package resolution.
|
|
450
450
|
|
|
451
451
|
Returns:
|
|
@@ -92,6 +92,14 @@ VIOLATION_SCOPING_MODULE_NAME = "code_rules_gate_parts.violation_scoping"
|
|
|
92
92
|
|
|
93
93
|
RETIRED_HOOK_PROSE_RULE_ID = "retired-hook-prose"
|
|
94
94
|
RULES_DIRECTORY_NAME = "rules"
|
|
95
|
+
ALL_HOOK_PROSE_SURFACE_SEGMENTS = (
|
|
96
|
+
"/.agents/",
|
|
97
|
+
"/commands/",
|
|
98
|
+
"/docs/",
|
|
99
|
+
"/output-styles/",
|
|
100
|
+
"/rules/",
|
|
101
|
+
"/system-prompts/",
|
|
102
|
+
)
|
|
95
103
|
HOOKS_DIRECTORY_NAME = "hooks"
|
|
96
104
|
HOOKS_CONFIGURATION_FILE_NAME = "hooks.json"
|
|
97
105
|
HOOK_CONSTANTS_DIRECTORY_NAME = "hooks_constants"
|
|
@@ -73,7 +73,7 @@ _merged_mapping_key_order = (
|
|
|
73
73
|
_code_standards_section_order = (
|
|
74
74
|
"COMMENT PRESERVATION",
|
|
75
75
|
"CORE PRINCIPLES",
|
|
76
|
-
"⚡
|
|
76
|
+
"⚡ LINT-ENFORCED RULES",
|
|
77
77
|
"3. REUSE CONSTANTS / 4. CONFIG LOCATIONS",
|
|
78
78
|
"5. NO ABBREVIATIONS",
|
|
79
79
|
"6. COMPLETE TYPE HINTS",
|