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.
Files changed (122) hide show
  1. package/.agents/agents/poteto-agent.md +6 -0
  2. package/.agents/agents/test_agent_frontmatter.py +9 -9
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -1
  4. package/.agents/agents-archived/ARCHIVE-MANIFEST.md +15 -0
  5. package/.agents/{agents → agents-archived}/clean-coder.md +4 -4
  6. package/.agents/{agents → agents-archived}/code-quality-agent.md +5 -5
  7. package/.agents/{agents → agents-archived}/plan-packet-validator.md +2 -2
  8. package/.agents/{agents → agents-archived}/session-advisor.md +2 -2
  9. package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +2 -2
  10. package/.agents/skills/cert-classification-rule/SKILL.md +3 -3
  11. package/.agents/skills/e-code-review/SKILL.md +2 -2
  12. package/.agents/skills/e-code-review/reference/fix.md +1 -1
  13. package/.agents/skills/e-code-review/reference/loop.md +1 -1
  14. package/.agents/skills/e-code-review/reference/medium.md +4 -4
  15. package/.agents/skills/e-code-review/reference/xhigh.md +5 -5
  16. package/.agents/skills/everything-search/SKILL.md +3 -3
  17. package/.agents/skills/grok-spawn/SKILL.md +9 -6
  18. package/.agents/skills/grok-spawn/reference/flag-profiles.md +12 -8
  19. package/.agents/skills/issue-tracker/SKILL.md +3 -3
  20. package/.agents/skills/orchestrator/SKILL.md +19 -18
  21. package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +1 -1
  22. package/.agents/skills/orchestrator/reference/executor-consult-block.md +1 -1
  23. package/.agents/skills/orchestrator-refresh/SKILL.md +4 -2
  24. package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +96 -0
  25. package/.agents/skills/plugin-eval-standalone-skill/reference/graders-and-commands.md +108 -0
  26. package/.agents/skills/privacy-hygiene/SKILL.md +5 -6
  27. package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +5 -6
  28. package/.agents/skills/pull-request/SKILL.md +14 -5
  29. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  30. package/.agents/skills/repairing-hook-boundaries/SKILL.md +1 -1
  31. package/.agents/skills/syncing-submodules/SKILL.md +1 -1
  32. package/.agents/skills/team-advisor/SKILL.md +11 -9
  33. package/.agents/skills/team-advisor/reference/advisor-docs-review.md +28 -1
  34. package/.agents/skills/usage-pause/SKILL.md +3 -3
  35. package/_shared/advisor/advisor-protocol.md +7 -3
  36. package/_shared/advisor/reference/advisor-block.md +8 -1
  37. package/audit-rubrics/category_rubrics/category-f-silent-failures.md +2 -2
  38. package/audit-rubrics/category_rubrics/category-i-concurrency.md +1 -1
  39. package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +1 -1
  40. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +5 -5
  41. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +5 -5
  42. package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +3 -3
  43. package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +1 -1
  44. package/audit-rubrics/prompts/category-a-api-contracts.md +2 -2
  45. package/audit-rubrics/prompts/category-b-selector-engine-compat.md +3 -3
  46. package/audit-rubrics/prompts/category-c-resource-cleanup.md +3 -3
  47. package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +1 -1
  48. package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
  49. package/audit-rubrics/prompts/category-f-silent-failures.md +12 -12
  50. package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +2 -2
  51. package/audit-rubrics/prompts/category-h-security-boundaries.md +4 -4
  52. package/audit-rubrics/prompts/category-i-concurrency.md +6 -6
  53. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +5 -5
  54. package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +1 -1
  55. package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +7 -7
  56. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  57. package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +3 -3
  58. package/audit-rubrics/prompts/category-q-cross-surface-claims.md +2 -2
  59. package/bin/ever-shipped-skills.mjs +1 -0
  60. package/bin/ever-shipped-skills.test.mjs +4 -0
  61. package/bin/install.agents-home.test.mjs +44 -94
  62. package/bin/install.mjs +31 -24
  63. package/bin/install.test.mjs +21 -0
  64. package/bin/resolve-package-managed-directory.test.mjs +1 -1
  65. package/bin/subagent-model-policy.test.mjs +15 -8
  66. package/docs/BDD_SCENARIO_QUALITY.md +2 -2
  67. package/docs/CODE_RULES.md +7 -7
  68. package/docs/TEST_QUALITY.md +1 -1
  69. package/docs/nas-ssh-invocation.md +1 -1
  70. package/docs/references/advisor-tool.md +2 -2
  71. package/docs/references/prose-style-enforcement.md +1 -1
  72. package/docs/references/team-advisor-skill.md +2 -2
  73. package/docs/references/weak-executor-advisor.md +1 -1
  74. package/docs/subagent-model-routing.md +6 -5
  75. package/hooks/blocking/tdd_enforcer.py +2 -3
  76. package/hooks/blocking/test_bash_post_call_dispatcher.py +7 -24
  77. package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
  78. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +0 -3
  79. package/hooks/hooks_constants/hosted_hook_runner.py +1 -1
  80. package/hooks/hooks_constants/msys_path_conversion_advisor_constants.py +3 -35
  81. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +2 -10
  82. package/hooks/routing/subagent_model_routing.mjs +15 -0
  83. package/hooks/routing/subagent_model_routing.test.mjs +58 -25
  84. package/package.json +1 -1
  85. package/rules/archiving-agent-config.md +5 -8
  86. package/rules/git-workflow.md +3 -3
  87. package/rules/long-horizon-autonomy.md +1 -1
  88. package/rules/shell-invocation.md +0 -2
  89. package/rules/subagent-model-policy.json +5 -13
  90. package/rules/testing.md +0 -2
  91. package/scripts/active_capability_references.py +74 -19
  92. package/scripts/banned_prose_words.py +159 -0
  93. package/scripts/dev_env_scripts_constants/active_capability_constants.py +12 -10
  94. package/scripts/dev_env_scripts_constants/banned_prose_word_constants.py +95 -0
  95. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +3 -3
  96. package/scripts/grok_headless_runner.py +19 -1
  97. package/scripts/grok_worker_preflight.py +67 -1
  98. package/scripts/policy_lint/adapter_retired_hook_prose.py +17 -7
  99. package/scripts/policy_lint/adapters.py +6 -6
  100. package/scripts/policy_lint/config/constants.py +8 -0
  101. package/scripts/policy_lint/registry.py +1 -1
  102. package/scripts/resolve_advisor_model_route.mjs +2 -2
  103. package/scripts/subagent_model_policy.mjs +4 -1
  104. package/scripts/sync_to_cursor/rules.py +1 -1
  105. package/scripts/test_active_capability_references.py +100 -33
  106. package/scripts/test_grok_headless_runner.py +35 -1
  107. package/scripts/test_grok_worker_preflight.py +73 -0
  108. package/scripts/tests/resolve_advisor_model_route.test.mjs +3 -3
  109. package/scripts/tests/test_banned_prose_words.py +103 -0
  110. package/scripts/tests/test_engine.py +1 -1
  111. package/scripts/tests/test_policy_lint_rules_retired_hook_prose.py +46 -0
  112. package/scripts/tests/test_rules.py +17 -6
  113. package/system-prompts/software-engineer.xml +7 -7
  114. package/hooks/advisory/msys_path_conversion_advisor.py +0 -200
  115. package/hooks/advisory/test_msys_path_conversion_advisor.py +0 -223
  116. package/hooks/hooks_constants/test_failure_recorder_constants.py +0 -34
  117. package/hooks/observability/test_failure_recorder.py +0 -221
  118. package/hooks/observability/test_test_failure_recorder.py +0 -173
  119. /package/.agents/{agents → agents-archived}/git-commit-crafter.md +0 -0
  120. /package/.agents/{agents → agents-archived}/issue-tracker.md +0 -0
  121. /package/.agents/{agents → agents-archived}/pr-description-writer.md +0 -0
  122. /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 accepts_rules_markdown(document: Document) -> bool:
431
- """Return whether the document is Markdown in a rules directory.
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 a Markdown file directly under a ``rules`` directory.
437
+ True for Markdown on an instruction surface.
438
438
  """
439
- return adapter_retired_hook_prose.accepts_rules_markdown(document)
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 rules prose that credits an unregistered hook with a live action.
445
+ """Report instruction prose that credits an unregistered hook with a live action.
446
446
 
447
447
  Args:
448
- document: Current rules Markdown text and path.
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"
@@ -106,7 +106,7 @@ def _text_document_rules() -> tuple[model.Rule, ...]:
106
106
  ),
107
107
  build_document_rule(
108
108
  "retired-hook-prose",
109
- adapters.accepts_rules_markdown,
109
+ adapters.accepts_instruction_markdown,
110
110
  adapters.retired_hook_prose_diagnostics,
111
111
  ),
112
112
  )
@@ -36,14 +36,14 @@ if (input !== undefined) {
36
36
  routingDecision = routingInputIssue
37
37
  ? blockedRoute(routingInputIssue)
38
38
  : resolveSubagentModelRoute({
39
- agent_type: 'session-advisor',
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: 'session-advisor',
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
- if (!isIdentifier(alias)) throw new SubagentModelPolicyError(`invalid role name: ${alias}`);
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
- "⚡ HOOK-ENFORCED RULES",
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 test_extract_active_capability_names_finds_slash_and_backticks() -> None:
47
- markdown = "Run /privacy-hygiene then open `issue-tracker`.\n"
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" in all_names
51
- assert "issue-tracker" in all_names
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 test_banned_names_always_fail_classification() -> None:
55
- for each_banned_name in sorted(ALL_BANNED_ACTIVE_CAPABILITY_NAMES):
56
- reason = classify_capability_reference(each_banned_name)
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 each_banned_name in reason
77
+ assert each_retired_name in reason
59
78
 
60
79
 
61
- def test_known_and_unknown_non_banned_names_pass() -> None:
80
+ def test_shipped_and_unknown_names_pass() -> None:
62
81
  inventory = build_capability_inventory(PACKAGE_ROOT)
63
- known_name = next(iter(inventory.all_skill_names), "e-code-review")
64
- assert classify_capability_reference(known_name) is None
65
- assert classify_capability_reference("not-a-real-capability") is None
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 test_unresolved_reports_file_and_line_for_fixture(tmp_path: Path) -> None:
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
- skills = package_root / "skills" / "real-skill"
71
- skills.mkdir(parents=True)
72
- (skills / "SKILL.md").write_text(
73
- "Active instruction: call /qbug now.\n",
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" / "real-agent.md").write_text("# agent\n", encoding="utf-8")
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" / "real-cmd.md").write_text("# cmd\n", encoding="utf-8")
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/real-skill/SKILL.md"],
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/real-skill/SKILL.md"
89
- assert "banned" in first.reason
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
- skill_dir = package_root / "skills" / "ok-skill"
95
- skill_dir.mkdir(parents=True)
96
- (skill_dir / "SKILL.md").write_text(
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
- each.capability_name != "qbug" for each in all_unresolved
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 = "code-quality-agent"
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 selects the policy default', () => {
16
- const result = runBridge(JSON.stringify({ reasoning_effort: 'xhigh' }));
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: 'medium',
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
- "⚡ HOOK-ENFORCED RULES",
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]