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
|
@@ -13,12 +13,10 @@ from active_capability_references import (
|
|
|
13
13
|
build_capability_inventory,
|
|
14
14
|
classify_capability_reference,
|
|
15
15
|
extract_active_capability_names,
|
|
16
|
+
retired_capability_names,
|
|
16
17
|
strip_inert_fenced_blocks,
|
|
17
18
|
unresolved_active_capabilities,
|
|
18
19
|
)
|
|
19
|
-
from dev_env_scripts_constants.active_capability_constants import (
|
|
20
|
-
ALL_BANNED_ACTIVE_CAPABILITY_NAMES,
|
|
21
|
-
)
|
|
22
20
|
|
|
23
21
|
PACKAGE_ROOT = Path(__file__).resolve().parent.parent
|
|
24
22
|
|
|
@@ -43,66 +41,135 @@ def test_strip_inert_fenced_blocks_drops_historical_examples() -> None:
|
|
|
43
41
|
assert "/sr-loop" in stripped
|
|
44
42
|
|
|
45
43
|
|
|
46
|
-
def
|
|
47
|
-
markdown =
|
|
44
|
+
def test_extract_active_capability_names_finds_invocations_and_manifests() -> None:
|
|
45
|
+
markdown = (
|
|
46
|
+
"Run /privacy-hygiene then open `issue-tracker/SKILL.md`.\n"
|
|
47
|
+
"See the guide at [review guide](../reviews/SKILL.md).\n"
|
|
48
|
+
)
|
|
48
49
|
all_hits = extract_active_capability_names(markdown)
|
|
49
50
|
all_names = {each_name for _line, each_name in all_hits}
|
|
50
|
-
assert "privacy-hygiene"
|
|
51
|
-
|
|
51
|
+
assert all_names == {"privacy-hygiene", "issue-tracker", "reviews"}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_urls_and_deep_paths_name_no_capability() -> None:
|
|
55
|
+
markdown = (
|
|
56
|
+
"Read [the advisor guide](https://code.claude.com/docs/en/advisor).\n"
|
|
57
|
+
"Call `POST /repos/{owner}/{repo}/issues/{number}/comments`.\n"
|
|
58
|
+
"The default role is `bugteam`.\n"
|
|
59
|
+
)
|
|
60
|
+
assert extract_active_capability_names(markdown) == []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_retired_names_come_from_the_shipped_inventory() -> None:
|
|
64
|
+
inventory = build_capability_inventory(PACKAGE_ROOT)
|
|
65
|
+
all_retired_names = retired_capability_names(PACKAGE_ROOT, inventory)
|
|
66
|
+
assert "qbug" in all_retired_names
|
|
67
|
+
assert "findbugs" in all_retired_names
|
|
68
|
+
assert not all_retired_names.intersection(inventory.all_known_names())
|
|
52
69
|
|
|
53
70
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
def test_a_retired_name_fails_classification() -> None:
|
|
72
|
+
inventory = build_capability_inventory(PACKAGE_ROOT)
|
|
73
|
+
all_retired_names = retired_capability_names(PACKAGE_ROOT, inventory)
|
|
74
|
+
for each_retired_name in sorted(all_retired_names):
|
|
75
|
+
reason = classify_capability_reference(each_retired_name, all_retired_names)
|
|
57
76
|
assert reason is not None
|
|
58
|
-
assert
|
|
77
|
+
assert each_retired_name in reason
|
|
59
78
|
|
|
60
79
|
|
|
61
|
-
def
|
|
80
|
+
def test_shipped_and_unknown_names_pass() -> None:
|
|
62
81
|
inventory = build_capability_inventory(PACKAGE_ROOT)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
all_retired_names = retired_capability_names(PACKAGE_ROOT, inventory)
|
|
83
|
+
for each_shipped_name in sorted(inventory.all_known_names()):
|
|
84
|
+
assert (
|
|
85
|
+
classify_capability_reference(each_shipped_name, all_retired_names) is None
|
|
86
|
+
), each_shipped_name
|
|
87
|
+
assert classify_capability_reference("not-a-capability", all_retired_names) is None
|
|
66
88
|
|
|
67
89
|
|
|
68
|
-
def
|
|
90
|
+
def test_a_skill_dropped_from_the_tree_reports_without_a_hand_kept_list(
|
|
91
|
+
tmp_path: Path,
|
|
92
|
+
) -> None:
|
|
69
93
|
package_root = tmp_path / "pkg"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
(
|
|
73
|
-
"
|
|
94
|
+
skill_directory = package_root / "skills" / "kept-skill"
|
|
95
|
+
skill_directory.mkdir(parents=True)
|
|
96
|
+
(skill_directory / "SKILL.md").write_text(
|
|
97
|
+
"Run /dropped-skill after /kept-skill.\n", encoding="utf-8"
|
|
98
|
+
)
|
|
99
|
+
(package_root / "agents").mkdir()
|
|
100
|
+
(package_root / "commands").mkdir()
|
|
101
|
+
(package_root / "bin").mkdir()
|
|
102
|
+
(package_root / "bin" / "ever-shipped-skills.mjs").write_text(
|
|
103
|
+
"export const EVER_SHIPPED_SKILL_NAMES = new Set([\n"
|
|
104
|
+
" 'kept-skill',\n"
|
|
105
|
+
" 'dropped-skill',\n"
|
|
106
|
+
"]);\n",
|
|
74
107
|
encoding="utf-8",
|
|
75
108
|
)
|
|
109
|
+
all_unresolved = unresolved_active_capabilities(
|
|
110
|
+
package_root,
|
|
111
|
+
all_relative_markdown_paths=["skills/kept-skill/SKILL.md"],
|
|
112
|
+
)
|
|
113
|
+
assert [each.capability_name for each in all_unresolved] == ["dropped-skill"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _write_fixture_package(package_root: Path, skill_name: str, prose: str) -> None:
|
|
117
|
+
skill_directory = package_root / "skills" / skill_name
|
|
118
|
+
skill_directory.mkdir(parents=True)
|
|
119
|
+
(skill_directory / "SKILL.md").write_text(prose, encoding="utf-8")
|
|
76
120
|
(package_root / "agents").mkdir()
|
|
77
|
-
(package_root / "agents" / "
|
|
121
|
+
(package_root / "agents" / "shipped-agent.md").write_text(
|
|
122
|
+
"# agent\n", encoding="utf-8"
|
|
123
|
+
)
|
|
78
124
|
(package_root / "commands").mkdir()
|
|
79
|
-
(package_root / "commands" / "
|
|
125
|
+
(package_root / "commands" / "shipped-command.md").write_text(
|
|
126
|
+
"# command\n", encoding="utf-8"
|
|
127
|
+
)
|
|
128
|
+
(package_root / "bin").mkdir()
|
|
129
|
+
(package_root / "bin" / "ever-shipped-skills.mjs").write_text(
|
|
130
|
+
"export const EVER_SHIPPED_SKILL_NAMES = new Set([\n"
|
|
131
|
+
f" '{skill_name}',\n"
|
|
132
|
+
" 'qbug',\n"
|
|
133
|
+
"]);\n",
|
|
134
|
+
encoding="utf-8",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_unresolved_reports_file_and_line_for_fixture(tmp_path: Path) -> None:
|
|
139
|
+
package_root = tmp_path / "pkg"
|
|
140
|
+
_write_fixture_package(
|
|
141
|
+
package_root, "shipped-skill", "Active instruction: call /qbug now.\n"
|
|
142
|
+
)
|
|
80
143
|
all_unresolved = unresolved_active_capabilities(
|
|
81
144
|
package_root,
|
|
82
|
-
all_relative_markdown_paths=["skills/
|
|
145
|
+
all_relative_markdown_paths=["skills/shipped-skill/SKILL.md"],
|
|
83
146
|
)
|
|
84
147
|
assert all_unresolved
|
|
85
148
|
first = all_unresolved[0]
|
|
86
149
|
assert first.capability_name == "qbug"
|
|
87
150
|
assert first.line_number == 1
|
|
88
|
-
assert first.file_path == "skills/
|
|
89
|
-
assert "
|
|
151
|
+
assert first.file_path == "skills/shipped-skill/SKILL.md"
|
|
152
|
+
assert "retired" in first.reason
|
|
90
153
|
|
|
91
154
|
|
|
92
155
|
def test_inert_historical_qbug_does_not_fail(tmp_path: Path) -> None:
|
|
93
156
|
package_root = tmp_path / "pkg"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
157
|
+
_write_fixture_package(
|
|
158
|
+
package_root,
|
|
159
|
+
"ok-skill",
|
|
97
160
|
"```historical\nLegacy /qbug workflow.\n```\nUse /ok-skill.\n",
|
|
98
|
-
encoding="utf-8",
|
|
99
161
|
)
|
|
100
|
-
(package_root / "agents").mkdir()
|
|
101
|
-
(package_root / "commands").mkdir()
|
|
102
162
|
all_unresolved = unresolved_active_capabilities(
|
|
103
163
|
package_root,
|
|
104
164
|
all_relative_markdown_paths=["skills/ok-skill/SKILL.md"],
|
|
105
165
|
)
|
|
106
|
-
assert all(
|
|
107
|
-
|
|
166
|
+
assert all(each.capability_name != "qbug" for each in all_unresolved)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_the_package_names_no_retired_capability() -> None:
|
|
170
|
+
all_unresolved = unresolved_active_capabilities(PACKAGE_ROOT)
|
|
171
|
+
report = "\n".join(
|
|
172
|
+
f"{each.file_path}:{each.line_number} {each.capability_name}"
|
|
173
|
+
for each in all_unresolved
|
|
108
174
|
)
|
|
175
|
+
assert not all_unresolved, report
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Behavioral tests for the banned emphasis word detector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from banned_prose_words import (
|
|
8
|
+
banned_prose_words_in_tree,
|
|
9
|
+
find_banned_prose_words,
|
|
10
|
+
governed_paths,
|
|
11
|
+
governs_path,
|
|
12
|
+
prose_lines,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_prose_occurrence_is_reported_with_its_line() -> None:
|
|
17
|
+
all_hits = find_banned_prose_words("First line.\nThe real cause is a timeout.\n")
|
|
18
|
+
assert [(each_line, each_word) for each_line, _column, each_word in all_hits] == [
|
|
19
|
+
(2, "real")
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_every_banned_form_is_reported() -> None:
|
|
24
|
+
document_text = (
|
|
25
|
+
"It is real.\n"
|
|
26
|
+
"It is really late.\n"
|
|
27
|
+
"A real-world case.\n"
|
|
28
|
+
"The actual value.\n"
|
|
29
|
+
"It actually ran.\n"
|
|
30
|
+
"A genuine failure.\n"
|
|
31
|
+
"It genuinely ran.\n"
|
|
32
|
+
"It truly ran.\n"
|
|
33
|
+
"The true cause.\n"
|
|
34
|
+
)
|
|
35
|
+
all_words = {each_word for _line, _column, each_word in
|
|
36
|
+
find_banned_prose_words(document_text)}
|
|
37
|
+
assert all_words == {
|
|
38
|
+
"real",
|
|
39
|
+
"really",
|
|
40
|
+
"real-world",
|
|
41
|
+
"actual",
|
|
42
|
+
"actually",
|
|
43
|
+
"genuine",
|
|
44
|
+
"genuinely",
|
|
45
|
+
"truly",
|
|
46
|
+
"true",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_code_spans_and_paths_carry_no_finding() -> None:
|
|
51
|
+
document_text = (
|
|
52
|
+
"Read `actual_value` from the map.\n"
|
|
53
|
+
"```python\n"
|
|
54
|
+
"actual = compute()\n"
|
|
55
|
+
"```\n"
|
|
56
|
+
" while True:\n"
|
|
57
|
+
"See docs/really-long-name.md for the table.\n"
|
|
58
|
+
"The flag reads always_apply: true.\n"
|
|
59
|
+
)
|
|
60
|
+
assert find_banned_prose_words(document_text) == []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_predicative_true_carries_no_finding() -> None:
|
|
64
|
+
document_text = (
|
|
65
|
+
"The condition is true when the port answers.\n"
|
|
66
|
+
"A branch that is always true never runs the other arm.\n"
|
|
67
|
+
)
|
|
68
|
+
assert find_banned_prose_words(document_text) == []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_prose_lines_blank_code_and_keep_the_line_count() -> None:
|
|
72
|
+
document_text = "Prose here.\n```python\nactual = 1\n```\nMore prose.\n"
|
|
73
|
+
assert prose_lines(document_text) == ["Prose here.", "", "", "", "More prose."]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_governed_paths_lists_the_shipped_surfaces(tmp_path: Path) -> None:
|
|
77
|
+
(tmp_path / "rules").mkdir()
|
|
78
|
+
(tmp_path / "rules" / "one.md").write_text("Prose.\n", encoding="utf-8")
|
|
79
|
+
(tmp_path / "rules-archived").mkdir()
|
|
80
|
+
(tmp_path / "rules-archived" / "two.md").write_text("Prose.\n", encoding="utf-8")
|
|
81
|
+
(tmp_path / "scripts").mkdir()
|
|
82
|
+
(tmp_path / "scripts" / "three.py").write_text("x = 1\n", encoding="utf-8")
|
|
83
|
+
assert governed_paths(tmp_path) == ["rules/one.md"]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_governed_paths_cover_instruction_surfaces_and_skip_archives() -> None:
|
|
87
|
+
assert governs_path("rules/git-workflow.md")
|
|
88
|
+
assert governs_path(".agents/skills/eli5/SKILL.md")
|
|
89
|
+
assert governs_path("audit-rubrics/prompts/category-e-dead-code.md")
|
|
90
|
+
assert governs_path("system-prompts/software-engineer.xml")
|
|
91
|
+
assert not governs_path("rules-archived/manifest.md")
|
|
92
|
+
assert not governs_path("scripts/banned_prose_words.py")
|
|
93
|
+
assert not governs_path("CHANGELOG.md")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_shipped_instruction_surfaces_carry_no_banned_word() -> None:
|
|
97
|
+
package_root = Path(__file__).resolve().parent.parent.parent
|
|
98
|
+
all_hits = banned_prose_words_in_tree(package_root)
|
|
99
|
+
report = "\n".join(
|
|
100
|
+
f"{each_path}:{each_line}:{each_column} {each_word}"
|
|
101
|
+
for each_path, each_line, each_column, each_word in all_hits[:40]
|
|
102
|
+
)
|
|
103
|
+
assert not all_hits, f"{len(all_hits)} banned words on shipped surfaces:\n{report}"
|
|
@@ -826,7 +826,7 @@ def test_failure_blast_radius_projection_uses_the_canonical_excerpt() -> None:
|
|
|
826
826
|
projected_instruction = materializer.render_codex_failure_blast_radius(canonical_rule)
|
|
827
827
|
|
|
828
828
|
assert projected_instruction.startswith("Failure handling for this run")
|
|
829
|
-
assert "Three
|
|
829
|
+
assert "Three attempts, then park." in projected_instruction
|
|
830
830
|
assert "Report as: N of M complete" in projected_instruction
|
|
831
831
|
|
|
832
832
|
|
|
@@ -851,7 +851,7 @@ def test_build_plan_publishes_owned_agents_projection_and_tracks_drift(
|
|
|
851
851
|
assert manifest_record["source"] == "rules/failure-blast-radius.md"
|
|
852
852
|
|
|
853
853
|
rule_path.write_text(
|
|
854
|
-
rule_path.read_text(encoding="utf-8").replace("Three
|
|
854
|
+
rule_path.read_text(encoding="utf-8").replace("Three attempts, then park.", "Three tested attempts, then park."),
|
|
855
855
|
encoding="utf-8",
|
|
856
856
|
)
|
|
857
857
|
drifted_plan, _ = build_plan(config, all_agents=[])
|
|
@@ -1021,7 +1021,7 @@ def _assert_projection_and_manifest(
|
|
|
1021
1021
|
) -> None:
|
|
1022
1022
|
assert not retired_owned_path.exists()
|
|
1023
1023
|
projected_instruction = (target / "AGENTS.md").read_text(encoding="utf-8")
|
|
1024
|
-
assert "Three
|
|
1024
|
+
assert "Three attempts, then park." in projected_instruction
|
|
1025
1025
|
manifest = json.loads((target / "hooks.json").read_text(encoding="utf-8"))
|
|
1026
1026
|
assert manifest["hooks"]["PreToolUse"][0]["matcher"] == "apply_patch"
|
|
1027
1027
|
assert manifest["hooks"]["PreToolUse"][1] == existing_hooks["hooks"]["PreToolUse"][1]
|
|
@@ -16,7 +16,7 @@ from sync_to_cursor.engine import run as run_sync_to_cursor
|
|
|
16
16
|
_CODE_STANDARDS_SECTION_ORDER = (
|
|
17
17
|
"COMMENT PRESERVATION",
|
|
18
18
|
"CORE PRINCIPLES",
|
|
19
|
-
"⚡
|
|
19
|
+
"⚡ LINT-ENFORCED RULES",
|
|
20
20
|
"3. REUSE CONSTANTS / 4. CONFIG LOCATIONS",
|
|
21
21
|
"5. NO ABBREVIATIONS",
|
|
22
22
|
"6. COMPLETE TYPE HINTS",
|
|
@@ -195,3 +195,49 @@ def test_the_committed_rules_directory_names_no_gate_that_stopped_running() -> N
|
|
|
195
195
|
)
|
|
196
196
|
)
|
|
197
197
|
assert all_messages == []
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _diagnostics_for_path(
|
|
201
|
+
repository_root: Path, document_path: str, prose: str
|
|
202
|
+
) -> tuple[str, ...]:
|
|
203
|
+
document = Document(
|
|
204
|
+
PurePosixPath(document_path),
|
|
205
|
+
prose,
|
|
206
|
+
None,
|
|
207
|
+
None,
|
|
208
|
+
ContentOrigin.WORKTREE,
|
|
209
|
+
)
|
|
210
|
+
all_diagnostics = adapter_retired_hook_prose.retired_hook_prose_diagnostics(
|
|
211
|
+
document, repository_root
|
|
212
|
+
)
|
|
213
|
+
return tuple(each_diagnostic.message for each_diagnostic in all_diagnostics)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_instruction_surfaces_outside_rules_are_in_scope() -> None:
|
|
217
|
+
for each_path in (
|
|
218
|
+
"packages/claude-dev-env/rules/example.md",
|
|
219
|
+
"packages/claude-dev-env/.agents/skills/example/SKILL.md",
|
|
220
|
+
"packages/claude-dev-env/.agents/agents/example.md",
|
|
221
|
+
"packages/claude-dev-env/commands/example.md",
|
|
222
|
+
"packages/claude-dev-env/docs/example.md",
|
|
223
|
+
):
|
|
224
|
+
document = Document(
|
|
225
|
+
PurePosixPath(each_path), "", None, None, ContentOrigin.WORKTREE
|
|
226
|
+
)
|
|
227
|
+
assert adapter_retired_hook_prose.accepts_instruction_markdown(document), each_path
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_a_skill_claim_for_a_surviving_unregistered_hook_is_reported(
|
|
231
|
+
tmp_path: Path,
|
|
232
|
+
) -> None:
|
|
233
|
+
_build_package(tmp_path)
|
|
234
|
+
(tmp_path / "packages" / "claude-dev-env" / ".agents" / "skills" / "example").mkdir(
|
|
235
|
+
parents=True
|
|
236
|
+
)
|
|
237
|
+
all_messages = _diagnostics_for_path(
|
|
238
|
+
tmp_path,
|
|
239
|
+
"packages/claude-dev-env/.agents/skills/example/SKILL.md",
|
|
240
|
+
"The `windows_rmtree_blocker.py` PreToolUse hook blocks the unsafe pattern.\n",
|
|
241
|
+
)
|
|
242
|
+
assert len(all_messages) == 1
|
|
243
|
+
assert "windows_rmtree_blocker" in all_messages[0]
|
|
@@ -9,7 +9,7 @@ _SCRIPTS_DIR = Path(__file__).resolve().parent.parent
|
|
|
9
9
|
if str(_SCRIPTS_DIR) not in sys.path:
|
|
10
10
|
sys.path.insert(0, str(_SCRIPTS_DIR))
|
|
11
11
|
|
|
12
|
-
from sync_to_cursor.rules import build_mappings
|
|
12
|
+
from sync_to_cursor.rules import build_mappings, merge_code_standards
|
|
13
13
|
|
|
14
14
|
_PACKAGE_ROOT = _SCRIPTS_DIR.parent
|
|
15
15
|
_SKIPPED_RULE_FILE_NAMES = frozenset({"CLAUDE.md", "AGENTS.md"})
|
|
@@ -225,3 +225,14 @@ def test_comment_policy_removes_directive_justification_guidance() -> None:
|
|
|
225
225
|
assert "without clear justification" not in react_patterns
|
|
226
226
|
assert "changed `# noqa` directives are removed or resolved" in category_e_prompt
|
|
227
227
|
assert "every `# noqa` on an import line must be justified" not in category_e_prompt
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_merge_code_standards_resolves_every_heading_it_names() -> None:
|
|
231
|
+
merged_text = merge_code_standards(
|
|
232
|
+
(
|
|
233
|
+
_PACKAGE_ROOT / "rules" / "code-standards.md",
|
|
234
|
+
_PACKAGE_ROOT / "docs" / "CODE_RULES.md",
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
assert "## Reference (full text:" in merged_text
|
|
238
|
+
assert "## COMMENT PRESERVATION" in merged_text
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
inferring are replaced by honest acknowledgment. Second, every recommendation, claim, or piece of advice cites a
|
|
73
73
|
specific source: a file in the current project, an external URL found via web search, a
|
|
74
74
|
named expert or paper, or official documentation. Third, when working from documents, extract
|
|
75
|
-
the
|
|
75
|
+
the text first before analyzing — ground responses in direct quotes, and reference
|
|
76
76
|
the quote when making a point.
|
|
77
77
|
|
|
78
78
|
Creative thinking, brainstorming, and novel ideas are exempt from citation requirements;
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
error message, verify the assumption that produced it, and apply a targeted fix. Commit to
|
|
86
86
|
a viable approach and see it through; switch approaches only when new information directly
|
|
87
87
|
contradicts the original reasoning. Escalate to the user only after investigation is
|
|
88
|
-
complete and a
|
|
88
|
+
complete and a decision point requires their input.
|
|
89
89
|
|
|
90
90
|
Treat OWASP top 10 vulnerability classes — command injection, XSS, SQL injection, and
|
|
91
91
|
related categories — as first-class design criteria. Build code that is inherently resistant
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
Types — complete coverage required:
|
|
116
116
|
- Every parameter and return value carries an explicit type hint
|
|
117
117
|
- Replace Any with the most specific type available
|
|
118
|
-
- Avoid `# type: ignore`; remove it and use a typed boundary or
|
|
118
|
+
- Avoid `# type: ignore`; remove it and use a typed boundary or an explicit type (AGENTS Types)
|
|
119
119
|
|
|
120
120
|
Configuration — one source of truth:
|
|
121
121
|
- Every constant lives in config/ only
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
desired behaviour. Surface assumptions early so they can be confirmed or corrected.
|
|
157
157
|
|
|
158
158
|
Illustrate phase. Explore goals, constraints, and concrete examples with the user. Use
|
|
159
|
-
|
|
159
|
+
scenarios — "given X, when Y happens, the system should Z" — to ground the conversation
|
|
160
160
|
in observable outcomes.
|
|
161
161
|
|
|
162
162
|
Formulate phase. Express behaviour as developer-facing executable specifications using the
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
|
|
194
194
|
Right-sized engineering: functions over classes, concrete over abstract, YAGNI on API
|
|
195
195
|
surface. Abstract base classes for single implementations, dependency injection frameworks,
|
|
196
|
-
CQRS, microservices, and multiple inheritance hierarchies belong only when a
|
|
196
|
+
CQRS, microservices, and multiple inheritance hierarchies belong only when a second
|
|
197
197
|
use case has arrived.
|
|
198
198
|
</scope_discipline>
|
|
199
199
|
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
When multiple tool calls are mutually independent, make all calls in a single response.
|
|
225
225
|
Sequence calls only when a later call requires an earlier call's result. Reading three files:
|
|
226
226
|
call all three Read operations at once. Running independent searches: launch all Grep
|
|
227
|
-
and Glob calls simultaneously. Use
|
|
227
|
+
and Glob calls simultaneously. Use concrete parameter values only. When uncertain whether calls are
|
|
228
228
|
independent, run them sequentially.
|
|
229
229
|
</agent_workflow>
|
|
230
230
|
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
Prefer working in memory over creating scratchpad files. Use variables and tool results
|
|
364
364
|
over writing intermediate data to disk.
|
|
365
365
|
|
|
366
|
-
When a temporary file is
|
|
366
|
+
When a temporary file is needed — a helper script, a test fixture, a debug output
|
|
367
367
|
— track it for cleanup. After each task completes, remove every temporary file, script, or
|
|
368
368
|
helper file created during the task. Leave the working directory cleaner than it was found.
|
|
369
369
|
|