claude-dev-env 8.1.0 → 8.3.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/poteto-agent.md +6 -0
- package/.agents/agents/test_agent_frontmatter.py +9 -9
- package/.agents/agents/test_pr_description_writer_style.py +1 -1
- package/.agents/agents-archived/ARCHIVE-MANIFEST.md +15 -0
- package/.agents/{agents → agents-archived}/clean-coder.md +4 -4
- package/.agents/{agents → agents-archived}/code-quality-agent.md +5 -5
- package/.agents/{agents → agents-archived}/plan-packet-validator.md +2 -2
- package/.agents/{agents → agents-archived}/session-advisor.md +2 -2
- package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +2 -2
- 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/grok-spawn/SKILL.md +9 -6
- package/.agents/skills/grok-spawn/reference/flag-profiles.md +12 -8
- package/.agents/skills/issue-tracker/SKILL.md +3 -3
- package/.agents/skills/orchestrator/SKILL.md +19 -18
- 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 +4 -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/pull-request/SKILL.md +14 -5
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- 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 +11 -9
- 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/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-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 +3 -3
- 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 +5 -5
- 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/bin/install.agents-home.test.mjs +44 -94
- package/bin/install.mjs +31 -24
- package/bin/install.test.mjs +21 -0
- package/bin/resolve-package-managed-directory.test.mjs +1 -1
- package/bin/subagent-model-policy.test.mjs +15 -8
- 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/docs/subagent-model-routing.md +6 -5
- package/hooks/blocking/tdd_enforcer.py +2 -3
- package/hooks/blocking/test_bash_post_call_dispatcher.py +7 -24
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +0 -3
- package/hooks/hooks_constants/hosted_hook_runner.py +1 -1
- package/hooks/hooks_constants/msys_path_conversion_advisor_constants.py +3 -35
- package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +2 -10
- package/hooks/routing/subagent_model_routing.mjs +15 -0
- package/hooks/routing/subagent_model_routing.test.mjs +58 -25
- package/package.json +1 -1
- package/rules/archiving-agent-config.md +5 -8
- package/rules/git-workflow.md +3 -3
- package/rules/long-horizon-autonomy.md +1 -1
- package/rules/shell-invocation.md +0 -2
- package/rules/subagent-model-policy.json +5 -13
- package/rules/testing.md +0 -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/dev_env_scripts_constants/grok_worker_constants.py +3 -3
- package/scripts/grok_headless_runner.py +19 -1
- package/scripts/grok_worker_preflight.py +67 -1
- 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/resolve_advisor_model_route.mjs +2 -2
- package/scripts/subagent_model_policy.mjs +4 -1
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_active_capability_references.py +100 -33
- package/scripts/test_grok_headless_runner.py +35 -1
- package/scripts/test_grok_worker_preflight.py +73 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +3 -3
- package/scripts/tests/test_banned_prose_words.py +103 -0
- 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 +17 -6
- package/system-prompts/software-engineer.xml +7 -7
- package/hooks/advisory/msys_path_conversion_advisor.py +0 -200
- package/hooks/advisory/test_msys_path_conversion_advisor.py +0 -223
- package/hooks/hooks_constants/test_failure_recorder_constants.py +0 -34
- package/hooks/observability/test_failure_recorder.py +0 -221
- package/hooks/observability/test_test_failure_recorder.py +0 -173
- /package/.agents/{agents → agents-archived}/git-commit-crafter.md +0 -0
- /package/.agents/{agents → agents-archived}/issue-tracker.md +0 -0
- /package/.agents/{agents → agents-archived}/pr-description-writer.md +0 -0
- /package/.agents/{agents → agents-archived}/skill-writer-agent.md +0 -0
|
@@ -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"
|
|
@@ -36,14 +36,14 @@ if (input !== undefined) {
|
|
|
36
36
|
routingDecision = routingInputIssue
|
|
37
37
|
? blockedRoute(routingInputIssue)
|
|
38
38
|
: resolveSubagentModelRoute({
|
|
39
|
-
agent_type: '
|
|
39
|
+
agent_type: 'advisor',
|
|
40
40
|
...(input.model !== undefined && input.model !== null ? { model: input.model } : {}),
|
|
41
41
|
...(input.reasoning_effort !== undefined ? { reasoning_effort: input.reasoning_effort } : {}),
|
|
42
42
|
}, {
|
|
43
43
|
policyPath: input.policyPath,
|
|
44
44
|
trustedSessionMetadata: {
|
|
45
45
|
authorized: true,
|
|
46
|
-
registeredAgentType: '
|
|
46
|
+
registeredAgentType: 'advisor',
|
|
47
47
|
},
|
|
48
48
|
});
|
|
49
49
|
}
|
|
@@ -298,7 +298,10 @@ function validateRoles(roles) {
|
|
|
298
298
|
const aliases = validateStringList(roles[role], `roles.${role}`).map(normalizeToken);
|
|
299
299
|
const allAliases = [role, ...aliases];
|
|
300
300
|
for (const alias of allAliases) {
|
|
301
|
-
|
|
301
|
+
const roleParts = alias.split(':');
|
|
302
|
+
if (roleParts.length > 2 || !roleParts.every(isIdentifier)) {
|
|
303
|
+
throw new SubagentModelPolicyError(`invalid role name: ${alias}`);
|
|
304
|
+
}
|
|
302
305
|
const previousRole = roleByAlias.get(alias);
|
|
303
306
|
if (previousRole && previousRole !== role) {
|
|
304
307
|
throw new SubagentModelPolicyError(`role alias is ambiguous: ${alias}`);
|
|
@@ -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",
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ include the signature substrings the constants module lists.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import json
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
import time
|
|
@@ -336,7 +337,11 @@ def test_argv_includes_agent_when_named(
|
|
|
336
337
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
337
338
|
) -> None:
|
|
338
339
|
fake_process = _FakeProcess(returncode=0, stdout='{"ok":true}')
|
|
339
|
-
agent_name = "
|
|
340
|
+
agent_name = "poteto-agent"
|
|
341
|
+
skill_path = tmp_path / "poteto-mode" / "SKILL.md"
|
|
342
|
+
skill_path.parent.mkdir()
|
|
343
|
+
skill_path.write_text("Keep the requested duty.\n", encoding="utf-8")
|
|
344
|
+
monkeypatch.setattr(runner, "poteto_mode_skill_path", lambda: skill_path)
|
|
340
345
|
outcome, recorder, _, _, _ = _run_once(
|
|
341
346
|
monkeypatch, tmp_path, fake_process, agent_name=agent_name
|
|
342
347
|
)
|
|
@@ -345,6 +350,35 @@ def test_argv_includes_agent_when_named(
|
|
|
345
350
|
invocation = recorder.invocations[0]
|
|
346
351
|
assert AGENT_FLAG in invocation
|
|
347
352
|
assert agent_name in invocation
|
|
353
|
+
generated_prompt = Path(invocation[invocation.index(PROMPT_FILE_FLAG) + 1])
|
|
354
|
+
assert "Keep the requested duty." in generated_prompt.read_text(encoding="utf-8")
|
|
355
|
+
assert str(skill_path) in generated_prompt.read_text(encoding="utf-8")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_native_plugin_skill_is_included_in_worker_prompt(
|
|
359
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
360
|
+
) -> None:
|
|
361
|
+
config_root = tmp_path / "profile"
|
|
362
|
+
registry_path = config_root / "plugins" / "installed_plugins.json"
|
|
363
|
+
registry_path.parent.mkdir(parents=True)
|
|
364
|
+
plugin_root = tmp_path / "plugin" / "version"
|
|
365
|
+
registry_path.write_text(json.dumps({"plugins": {"pstack@pstack-claude": [
|
|
366
|
+
{"scope": "user", "installPath": str(plugin_root)},
|
|
367
|
+
]}}), encoding="utf-8")
|
|
368
|
+
skill_path = plugin_root / "skills" / "poteto-mode" / "SKILL.md"
|
|
369
|
+
skill_path.parent.mkdir(parents=True)
|
|
370
|
+
skill_path.write_text("Use the native workflow.\n", encoding="utf-8")
|
|
371
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
372
|
+
outcome, recorder, _, _, _ = _run_once(
|
|
373
|
+
monkeypatch, tmp_path, _FakeProcess(returncode=0, stdout="done"),
|
|
374
|
+
agent_name="poteto-agent",
|
|
375
|
+
)
|
|
376
|
+
assert outcome.is_ok
|
|
377
|
+
invocation = recorder.invocations[0]
|
|
378
|
+
prompt = Path(invocation[invocation.index(PROMPT_FILE_FLAG) + 1]).read_text(encoding="utf-8")
|
|
379
|
+
assert str(skill_path.resolve()) in prompt
|
|
380
|
+
assert "Use the native workflow." in prompt
|
|
381
|
+
assert "Resolve companion skill paths against that source directory." in prompt
|
|
348
382
|
|
|
349
383
|
|
|
350
384
|
def test_unique_leader_socket_path_per_call(
|
|
@@ -80,6 +80,9 @@ class _Recorder:
|
|
|
80
80
|
def _write_install_layout(claude_home: Path, *, role: str = ROLE_BUGTEAM) -> None:
|
|
81
81
|
claude_home.mkdir(parents=True, exist_ok=True)
|
|
82
82
|
(claude_home / MANIFEST_FILENAME).write_text("{}", encoding=UTF8_ENCODING)
|
|
83
|
+
skill_path = claude_home / "skills" / "poteto-mode" / "SKILL.md"
|
|
84
|
+
skill_path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
skill_path.write_text("Poteto guidance\n", encoding=UTF8_ENCODING)
|
|
83
86
|
agents_directory = claude_home / AGENTS_SUBDIRECTORY
|
|
84
87
|
agents_directory.mkdir(parents=True, exist_ok=True)
|
|
85
88
|
for each_filename in ALL_AGENT_FILENAMES_BY_ROLE[role]:
|
|
@@ -88,6 +91,76 @@ def _write_install_layout(claude_home: Path, *, role: str = ROLE_BUGTEAM) -> Non
|
|
|
88
91
|
)
|
|
89
92
|
|
|
90
93
|
|
|
94
|
+
def test_missing_poteto_skill_rejects_installed_agent(
|
|
95
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
96
|
+
) -> None:
|
|
97
|
+
_write_install_layout(tmp_path)
|
|
98
|
+
monkeypatch.setattr(preflight, "claude_config_home", lambda: tmp_path)
|
|
99
|
+
assert preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
100
|
+
preflight.poteto_mode_skill_path().unlink()
|
|
101
|
+
assert not preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _write_native_pstack(config_root: Path, plugin_root: Path) -> Path:
|
|
105
|
+
registry_path = config_root / "plugins" / "installed_plugins.json"
|
|
106
|
+
registry_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
registry_path.write_text(
|
|
108
|
+
json.dumps({"plugins": {"pstack@pstack-claude": [
|
|
109
|
+
{"scope": "user", "installPath": str(plugin_root)},
|
|
110
|
+
{"scope": "project", "installPath": str(plugin_root / "other")},
|
|
111
|
+
]}}),
|
|
112
|
+
encoding=UTF8_ENCODING,
|
|
113
|
+
)
|
|
114
|
+
skill_path = plugin_root / "skills" / "poteto-mode" / "SKILL.md"
|
|
115
|
+
skill_path.parent.mkdir(parents=True)
|
|
116
|
+
skill_path.write_text("Native poteto guidance\n", encoding=UTF8_ENCODING)
|
|
117
|
+
companion = skill_path.parent / "playbooks" / "bug-fix.md"
|
|
118
|
+
companion.parent.mkdir()
|
|
119
|
+
companion.write_text("Fix guidance\n", encoding=UTF8_ENCODING)
|
|
120
|
+
return skill_path
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _damage_native_plugin(config_root: Path, native_skill: Path, damage: str) -> None:
|
|
124
|
+
if damage == "missing":
|
|
125
|
+
native_skill.unlink()
|
|
126
|
+
return
|
|
127
|
+
if damage == "empty":
|
|
128
|
+
native_skill.write_text("", encoding=UTF8_ENCODING)
|
|
129
|
+
return
|
|
130
|
+
if damage == "encoding":
|
|
131
|
+
native_skill.write_bytes(b"\xff")
|
|
132
|
+
return
|
|
133
|
+
(config_root / "plugins" / "installed_plugins.json").write_text(
|
|
134
|
+
"{", encoding=UTF8_ENCODING
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_native_plugin_source_overrides_shared_skill(
|
|
139
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
140
|
+
) -> None:
|
|
141
|
+
config_root = tmp_path / "profile"
|
|
142
|
+
_write_install_layout(config_root)
|
|
143
|
+
native_skill = _write_native_pstack(config_root, tmp_path / "plugin-version")
|
|
144
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
145
|
+
assert preflight.poteto_mode_skill_path() == native_skill
|
|
146
|
+
assert (native_skill.parent / "playbooks" / "bug-fix.md").read_text() == "Fix guidance\n"
|
|
147
|
+
assert preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize("damage", ["missing", "empty", "encoding", "registry"])
|
|
151
|
+
def test_broken_native_plugin_does_not_fall_back_to_shared_skill(
|
|
152
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, damage: str
|
|
153
|
+
) -> None:
|
|
154
|
+
config_root = tmp_path / "profile"
|
|
155
|
+
_write_install_layout(config_root)
|
|
156
|
+
native_skill = _write_native_pstack(config_root, tmp_path / "plugin-version")
|
|
157
|
+
monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(config_root))
|
|
158
|
+
_damage_native_plugin(config_root, native_skill, damage)
|
|
159
|
+
with pytest.raises(OSError):
|
|
160
|
+
preflight.poteto_mode_skill_path()
|
|
161
|
+
assert not preflight._is_claude_dev_env_config_present(ROLE_BUGTEAM)
|
|
162
|
+
|
|
163
|
+
|
|
91
164
|
def _install_ok_static_seams(
|
|
92
165
|
monkeypatch: pytest.MonkeyPatch,
|
|
93
166
|
claude_home: Path,
|
|
@@ -12,12 +12,12 @@ function runBridge(input) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
test('advisor bridge
|
|
16
|
-
const result = runBridge(JSON.stringify({ reasoning_effort: '
|
|
15
|
+
test('advisor bridge uses the trusted advisor registration', () => {
|
|
16
|
+
const result = runBridge(JSON.stringify({ model: 'Astra', reasoning_effort: 'high' }));
|
|
17
17
|
assert.equal(result.status, 0);
|
|
18
18
|
assert.deepEqual(JSON.parse(result.stdout).selected, {
|
|
19
19
|
model: 'gpt-6-astra',
|
|
20
|
-
effort: '
|
|
20
|
+
effort: 'high',
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -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}"
|
|
@@ -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]
|