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
@@ -3,13 +3,15 @@
3
3
 
4
4
  Builds inventories from committed skill manifests, agent markdown files, and
5
5
  commands. Scans active prompt text for slash and backticked capability names.
6
- Fails when an active (non-inert) reference names a banned capability.
6
+ Fails when an active (non-inert) reference names a capability the tree
7
+ dropped, resolved from the ever-shipped registry against what the tree
8
+ holds today.
7
9
 
8
10
  ::
9
11
 
10
12
  unresolved = unresolved_active_capabilities(package_root)
11
13
  ok: unresolved == []
12
- flag: active text mentions /qbug or `stub-detector`
14
+ flag: active text mentions a name the tree dropped
13
15
  """
14
16
 
15
17
  from __future__ import annotations
@@ -19,10 +21,12 @@ from dataclasses import dataclass
19
21
  from pathlib import Path
20
22
 
21
23
  from dev_env_scripts_constants.active_capability_constants import (
22
- ALL_BANNED_ACTIVE_CAPABILITY_NAMES,
23
24
  ALL_INERT_FENCE_LANGUAGES,
24
- BACKTICK_CAPABILITY_PATTERN,
25
- BANNED_REASON_PREFIX,
25
+ ALL_QUALIFIED_CAPABILITY_PATTERNS,
26
+ ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES,
27
+ BLANKED_SPAN,
28
+ EVER_SHIPPED_NAME_PATTERN,
29
+ EVER_SHIPPED_REGISTRY_RELATIVE_PATH,
26
30
  FENCE_CLOSE_PATTERN,
27
31
  FENCE_OPEN_PATTERN,
28
32
  NEWLINE_JOIN_SEPARATOR,
@@ -31,8 +35,11 @@ from dev_env_scripts_constants.active_capability_constants import (
31
35
  PACKAGE_ROOT_AGENTS_DIRECTORY,
32
36
  PACKAGE_ROOT_SKILLS_DIRECTORY,
33
37
  PACKAGE_SKILLS_DIRECTORY,
38
+ RETIRED_REASON_PREFIX,
34
39
  SKILL_MANIFEST_FILENAME,
40
+ MULTI_SEGMENT_PATH_PATTERN,
35
41
  SLASH_CAPABILITY_PATTERN,
42
+ URL_PATTERN,
36
43
  UTF8_ENCODING,
37
44
  )
38
45
 
@@ -52,7 +59,7 @@ class CapabilityInventory:
52
59
 
53
60
  @dataclass(frozen=True)
54
61
  class UnresolvedCapabilityReference:
55
- """One active reference that names a banned capability."""
62
+ """One active reference that names a capability outside the shipped tree."""
56
63
 
57
64
  file_path: str
58
65
  line_number: int
@@ -148,6 +155,10 @@ def strip_inert_fenced_blocks(markdown_text: str) -> str:
148
155
  def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]:
149
156
  """Return (1-based line, capability name) for active references.
150
157
 
158
+ A slash reference counts as an invocation, and a qualified reference
159
+ names a skill directory or manifest. A URL and a multi-segment path
160
+ carry neither, so both are blanked before the scan.
161
+
151
162
  Args:
152
163
  markdown_text: Prompt or skill markdown.
153
164
 
@@ -155,31 +166,72 @@ def extract_active_capability_names(markdown_text: str) -> list[tuple[int, str]]
155
166
  Ordered list of line number and capability name pairs.
156
167
  """
157
168
  slash_pattern = re.compile(SLASH_CAPABILITY_PATTERN)
158
- backtick_pattern = re.compile(BACKTICK_CAPABILITY_PATTERN)
169
+ url_pattern = re.compile(URL_PATTERN)
170
+ path_pattern = re.compile(MULTI_SEGMENT_PATH_PATTERN)
171
+ all_qualified_patterns = [
172
+ re.compile(each_pattern) for each_pattern in ALL_QUALIFIED_CAPABILITY_PATTERNS
173
+ ]
159
174
  active_text = strip_inert_fenced_blocks(markdown_text)
160
175
  all_hits: list[tuple[int, str]] = []
161
176
  for each_line_number, each_line in enumerate(active_text.splitlines(), start=1):
162
- for each_match in slash_pattern.finditer(each_line):
163
- all_hits.append((each_line_number, each_match.group(1)))
164
- for each_match in backtick_pattern.finditer(each_line):
177
+ for each_pattern in all_qualified_patterns:
178
+ for each_match in each_pattern.finditer(each_line):
179
+ all_hits.append((each_line_number, each_match.group(1)))
180
+ invocation_line = path_pattern.sub(
181
+ BLANKED_SPAN, url_pattern.sub(BLANKED_SPAN, each_line)
182
+ )
183
+ for each_match in slash_pattern.finditer(invocation_line):
165
184
  all_hits.append((each_line_number, each_match.group(1)))
166
185
  return all_hits
167
186
 
168
187
 
169
- def classify_capability_reference(capability_name: str) -> str | None:
170
- """Return a failure reason when the capability is banned.
188
+ def retired_capability_names(
189
+ from_package_root: Path, inventory: CapabilityInventory
190
+ ) -> frozenset[str]:
191
+ """Return the capability names the shipped tree dropped.
171
192
 
172
- Extractors also match ordinary prose tokens, so unknown names pass.
173
- Only the ban list fails a reference.
193
+ The ever-shipped registry keeps every skill name the package released.
194
+ Subtracting what the tree holds today leaves the retired set, so no hand
195
+ kept list decides which name fails.
196
+
197
+ Args:
198
+ from_package_root: ``packages/claude-dev-env`` root.
199
+ inventory: Names the tree holds today.
200
+
201
+ Returns:
202
+ Retired capability names.
203
+ """
204
+ registry_path = from_package_root / EVER_SHIPPED_REGISTRY_RELATIVE_PATH
205
+ all_ever_shipped_names: set[str] = set(
206
+ ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES
207
+ )
208
+ if registry_path.is_file():
209
+ all_ever_shipped_names.update(
210
+ re.findall(
211
+ EVER_SHIPPED_NAME_PATTERN,
212
+ registry_path.read_text(encoding=UTF8_ENCODING),
213
+ )
214
+ )
215
+ return frozenset(all_ever_shipped_names - inventory.all_known_names())
216
+
217
+
218
+ def classify_capability_reference(
219
+ capability_name: str, all_retired_names: frozenset[str]
220
+ ) -> str | None:
221
+ """Return a failure reason when the capability left the tree.
222
+
223
+ Extractors also match ordinary prose tokens, so a name the package never
224
+ shipped passes. A name the registry records and the tree dropped fails.
174
225
 
175
226
  Args:
176
227
  capability_name: Extracted skill/command-like name.
228
+ all_retired_names: Names resolved by ``retired_capability_names``.
177
229
 
178
230
  Returns:
179
231
  Reason string, or None when the reference is allowed.
180
232
  """
181
- if capability_name in ALL_BANNED_ACTIVE_CAPABILITY_NAMES:
182
- return f"{BANNED_REASON_PREFIX}{capability_name}"
233
+ if capability_name in all_retired_names:
234
+ return f"{RETIRED_REASON_PREFIX}{capability_name}"
183
235
  return None
184
236
 
185
237
 
@@ -188,7 +240,7 @@ def unresolved_active_capabilities(
188
240
  *,
189
241
  all_relative_markdown_paths: list[str] | None = None,
190
242
  ) -> list[UnresolvedCapabilityReference]:
191
- """Scan package markdown for banned active capability references.
243
+ """Scan package markdown for references to retired capabilities.
192
244
 
193
245
  Args:
194
246
  from_package_root: ``packages/claude-dev-env`` root.
@@ -196,10 +248,13 @@ def unresolved_active_capabilities(
196
248
  defaults to all skills, agents, and commands markdown.
197
249
 
198
250
  Returns:
199
- Banned references with file, line, name, and reason.
251
+ Retired references with file, line, name, and reason.
200
252
  """
201
253
  if all_relative_markdown_paths is None:
202
254
  all_relative_markdown_paths = _default_markdown_paths(from_package_root)
255
+ all_retired_names = retired_capability_names(
256
+ from_package_root, build_capability_inventory(from_package_root)
257
+ )
203
258
  all_unresolved: list[UnresolvedCapabilityReference] = []
204
259
  for each_relative_path in all_relative_markdown_paths:
205
260
  absolute_path = from_package_root / each_relative_path
@@ -209,7 +264,7 @@ def unresolved_active_capabilities(
209
264
  for each_line_number, each_name in extract_active_capability_names(
210
265
  markdown_text
211
266
  ):
212
- reason = classify_capability_reference(each_name)
267
+ reason = classify_capability_reference(each_name, all_retired_names)
213
268
  if reason is None:
214
269
  continue
215
270
  all_unresolved.append(
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+ """Find banned emphasis words in authored prose.
3
+
4
+ The package AGENTS.md bans ``real``, ``really``, ``real-world``, ``actual``,
5
+ ``actually``, ``genuine``, and ``true`` from every authored surface. This
6
+ module reports each occurrence that survives after code spans, paths, and
7
+ link targets are removed, so a caller can grade a document or a whole tree.
8
+
9
+ ::
10
+
11
+ all_hits = find_banned_prose_words("The real cause is a timeout.\n")
12
+ ok: all_hits == [(1, "real")]
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path, PurePosixPath
18
+
19
+ from dev_env_scripts_constants.banned_prose_word_constants import (
20
+ ADJECTIVAL_TRUE_PATTERN,
21
+ ALL_EXEMPT_PATH_SEGMENTS,
22
+ ALL_GOVERNED_PATH_SEGMENTS,
23
+ ALL_GOVERNED_SUFFIXES,
24
+ ALL_PREDICATIVE_TRUE_LEADS,
25
+ BANNED_PROSE_WORD_PATTERN,
26
+ BLANKED_SPAN,
27
+ FENCED_CODE_PATTERN,
28
+ INDENTED_CODE_PATTERN,
29
+ INLINE_CODE_PATTERN,
30
+ MARKDOWN_LINK_TARGET_PATTERN,
31
+ PATH_PATTERN,
32
+ URL_PATTERN,
33
+ UTF8_ENCODING,
34
+ XML_TAG_PATTERN,
35
+ )
36
+
37
+
38
+ def governs_path(relative_posix_path: str) -> bool:
39
+ """Return whether the ban applies to one repository path.
40
+
41
+ Args:
42
+ relative_posix_path: Repository-relative path with forward slashes.
43
+
44
+ Returns:
45
+ True for an authored instruction surface the ban covers.
46
+ """
47
+ normalized_path = f"/{relative_posix_path.lower().lstrip('/')}"
48
+ if PurePosixPath(normalized_path).suffix not in ALL_GOVERNED_SUFFIXES:
49
+ return False
50
+ if any(
51
+ each_segment in normalized_path for each_segment in ALL_EXEMPT_PATH_SEGMENTS
52
+ ):
53
+ return False
54
+ return any(
55
+ each_segment in normalized_path for each_segment in ALL_GOVERNED_PATH_SEGMENTS
56
+ )
57
+
58
+
59
+ def prose_lines(document_text: str) -> list[str]:
60
+ """Return one prose line for each source line, with code spans blanked.
61
+
62
+ Args:
63
+ document_text: Full document source.
64
+
65
+ Returns:
66
+ Lines of the same count as the source, holding prose only.
67
+ """
68
+ all_prose_lines: list[str] = []
69
+ is_inside_fence = False
70
+ for each_line in document_text.splitlines():
71
+ if FENCED_CODE_PATTERN.match(each_line):
72
+ is_inside_fence = not is_inside_fence
73
+ all_prose_lines.append("")
74
+ continue
75
+ if is_inside_fence or INDENTED_CODE_PATTERN.match(each_line):
76
+ all_prose_lines.append("")
77
+ continue
78
+ prose_line = INLINE_CODE_PATTERN.sub(BLANKED_SPAN, each_line)
79
+ prose_line = XML_TAG_PATTERN.sub(BLANKED_SPAN, prose_line)
80
+ prose_line = MARKDOWN_LINK_TARGET_PATTERN.sub(BLANKED_SPAN, prose_line)
81
+ prose_line = URL_PATTERN.sub(BLANKED_SPAN, prose_line)
82
+ all_prose_lines.append(PATH_PATTERN.sub(BLANKED_SPAN, prose_line))
83
+ return all_prose_lines
84
+
85
+
86
+ def _adjectival_true_columns(prose_line: str) -> list[int]:
87
+ all_columns: list[int] = []
88
+ for each_match in ADJECTIVAL_TRUE_PATTERN.finditer(prose_line):
89
+ leading_words = prose_line[: each_match.start()].lower().split()
90
+ if leading_words and leading_words[-1].strip(",;:(") in (
91
+ ALL_PREDICATIVE_TRUE_LEADS
92
+ ):
93
+ continue
94
+ all_columns.append(each_match.start() + 1)
95
+ return all_columns
96
+
97
+
98
+ def find_banned_prose_words(document_text: str) -> list[tuple[int, int, str]]:
99
+ """Return each banned word occurrence in authored prose.
100
+
101
+ Args:
102
+ document_text: Full document source.
103
+
104
+ Returns:
105
+ Ordered ``(line number, column, word)`` triples, both 1-based.
106
+ """
107
+ all_hits: list[tuple[int, int, str]] = []
108
+ for each_line_number, each_prose_line in enumerate(prose_lines(document_text), 1):
109
+ for each_match in BANNED_PROSE_WORD_PATTERN.finditer(each_prose_line):
110
+ all_hits.append(
111
+ (each_line_number, each_match.start() + 1, each_match.group(1).lower())
112
+ )
113
+ for each_column in _adjectival_true_columns(each_prose_line):
114
+ all_hits.append((each_line_number, each_column, "true"))
115
+ return sorted(all_hits)
116
+
117
+
118
+ def governed_paths(from_package_root: Path) -> list[str]:
119
+ """Return every governed surface under one package root.
120
+
121
+ Args:
122
+ from_package_root: ``packages/claude-dev-env`` root.
123
+
124
+ Returns:
125
+ Sorted package-relative paths the ban covers.
126
+ """
127
+ all_paths: list[str] = []
128
+ for each_path in from_package_root.rglob("*"):
129
+ if not each_path.is_file():
130
+ continue
131
+ relative_path = each_path.relative_to(from_package_root).as_posix()
132
+ if governs_path(relative_path):
133
+ all_paths.append(relative_path)
134
+ return sorted(all_paths)
135
+
136
+
137
+ def banned_prose_words_in_tree(
138
+ from_package_root: Path,
139
+ ) -> list[tuple[str, int, int, str]]:
140
+ """Return every banned word occurrence across the governed surfaces.
141
+
142
+ Args:
143
+ from_package_root: ``packages/claude-dev-env`` root.
144
+
145
+ Returns:
146
+ Ordered ``(package-relative path, line number, column, word)`` tuples.
147
+ """
148
+ all_hits: list[tuple[str, int, int, str]] = []
149
+ for each_relative_path in governed_paths(from_package_root):
150
+ document_text = (from_package_root / each_relative_path).read_text(
151
+ encoding=UTF8_ENCODING, errors="replace"
152
+ )
153
+ for each_line_number, each_column, each_word in find_banned_prose_words(
154
+ document_text
155
+ ):
156
+ all_hits.append(
157
+ (each_relative_path, each_line_number, each_column, each_word)
158
+ )
159
+ return all_hits
@@ -14,18 +14,16 @@ PACKAGE_ROOT_SKILLS_DIRECTORY: str = "skills"
14
14
  PACKAGE_ROOT_AGENTS_DIRECTORY: str = "agents"
15
15
  SKILL_MANIFEST_FILENAME: str = "SKILL.md"
16
16
 
17
- # Slash-command and skill names that must never appear as active instructions.
18
- ALL_BANNED_ACTIVE_CAPABILITY_NAMES: frozenset[str] = frozenset(
17
+ ALL_UNREGISTERED_RETIRED_CAPABILITY_NAMES: frozenset[str] = frozenset(
19
18
  {
20
19
  "stub-detector",
21
20
  "superpowers",
22
- "qbug",
23
- "findbugs",
24
- "fixbugs",
25
- "monitor-open-prs",
26
21
  }
27
22
  )
28
23
 
24
+ EVER_SHIPPED_REGISTRY_RELATIVE_PATH: str = "bin/ever-shipped-skills.mjs"
25
+ EVER_SHIPPED_NAME_PATTERN: str = r"'([a-z][a-z0-9-]*)'"
26
+
29
27
  # Fence languages treated as inert historical/example content.
30
28
  ALL_INERT_FENCE_LANGUAGES: frozenset[str] = frozenset(
31
29
  {
@@ -37,13 +35,17 @@ ALL_INERT_FENCE_LANGUAGES: frozenset[str] = frozenset(
37
35
  }
38
36
  )
39
37
 
40
- SLASH_CAPABILITY_PATTERN: str = r"(?<![`\w])/([a-z][a-z0-9-]{2,})"
41
- BACKTICK_CAPABILITY_PATTERN: str = (
42
- r"`(?:skills/)?([a-z][a-z0-9-]{2,})(?:/SKILL\.md)?`"
38
+ SLASH_CAPABILITY_PATTERN: str = r"(?<![`\w./-])/([a-z][a-z0-9-]{2,})(?![\w/-])"
39
+ ALL_QUALIFIED_CAPABILITY_PATTERNS: tuple[str, ...] = (
40
+ r"(?<![\w-])([a-z][a-z0-9-]{2,})/SKILL\.md",
41
+ r"`skills/([a-z][a-z0-9-]{2,})`",
43
42
  )
43
+ URL_PATTERN: str = r"\b[a-z][a-z0-9+.-]*://\S+"
44
+ MULTI_SEGMENT_PATH_PATTERN: str = r"(?<![\w-])[\w.{}-]*/[\w.{}/-]*/[\w.{}-]+"
45
+ BLANKED_SPAN: str = " "
44
46
  FENCE_OPEN_PATTERN: str = r"^```([A-Za-z0-9_-]*)\s*$"
45
47
  FENCE_CLOSE_PATTERN: str = r"^```\s*$"
46
- BANNED_REASON_PREFIX: str = "banned_active_capability:"
48
+ RETIRED_REASON_PREFIX: str = "retired_capability:"
47
49
 
48
50
  UTF8_ENCODING: str = "utf-8"
49
51
  NEWLINE_JOIN_SEPARATOR: str = "\n"
@@ -0,0 +1,95 @@
1
+ """Constants for the banned-prose-word detector.
2
+
3
+ The package AGENTS.md bans a family of emphasis words from every authored
4
+ surface. These patterns describe where the ban applies and which spans of a
5
+ document are code rather than prose.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ UTF8_ENCODING: str = "utf-8"
13
+
14
+ ALL_GOVERNED_PATH_SEGMENTS: tuple[str, ...] = (
15
+ "/.agents/agents/",
16
+ "/.agents/skills/",
17
+ "/audit-rubrics/",
18
+ "/commands/",
19
+ "/docs/",
20
+ "/output-styles/",
21
+ "/rules/",
22
+ "/system-prompts/",
23
+ )
24
+
25
+ ALL_EXEMPT_PATH_SEGMENTS: tuple[str, ...] = (
26
+ "-archived/",
27
+ "/fixtures/",
28
+ "/tests/",
29
+ "/changelog.md",
30
+ )
31
+
32
+ ALL_GOVERNED_SUFFIXES: frozenset[str] = frozenset({".md", ".mdx", ".xml"})
33
+
34
+ ALL_BANNED_PROSE_WORDS: tuple[str, ...] = (
35
+ "real",
36
+ "really",
37
+ "real-world",
38
+ "actual",
39
+ "actually",
40
+ "genuine",
41
+ "genuinely",
42
+ "truly",
43
+ )
44
+
45
+ BANNED_PROSE_WORD_PATTERN: re.Pattern[str] = re.compile(
46
+ r"(?<![\w-])(" + "|".join(ALL_BANNED_PROSE_WORDS) + r")(?![\w-])",
47
+ re.IGNORECASE,
48
+ )
49
+
50
+ ADJECTIVAL_TRUE_PATTERN: re.Pattern[str] = re.compile(
51
+ r"(?<![\w-])true(?![\w-])\s+[a-z]", re.IGNORECASE
52
+ )
53
+
54
+ ALL_PREDICATIVE_TRUE_LEADS: frozenset[str] = frozenset(
55
+ {
56
+ "is",
57
+ "are",
58
+ "was",
59
+ "were",
60
+ "be",
61
+ "been",
62
+ "being",
63
+ "always",
64
+ "still",
65
+ "holds",
66
+ "hold",
67
+ "held",
68
+ "stays",
69
+ "stay",
70
+ "remains",
71
+ "remain",
72
+ "returns",
73
+ "return",
74
+ "reads",
75
+ "read",
76
+ "and",
77
+ "or",
78
+ "not",
79
+ "never",
80
+ }
81
+ )
82
+
83
+ FENCED_CODE_PATTERN: re.Pattern[str] = re.compile(r"^\s*```")
84
+ INDENTED_CODE_PATTERN: re.Pattern[str] = re.compile(r"^(?: {4,}|\t)\S")
85
+ INLINE_CODE_PATTERN: re.Pattern[str] = re.compile(r"`[^`]*`")
86
+ URL_PATTERN: re.Pattern[str] = re.compile(r"\b(?:https?|file)://\S+")
87
+ PATH_PATTERN: re.Pattern[str] = re.compile(r"(?<![\w])[\w./-]*/[\w./-]+")
88
+ MARKDOWN_LINK_TARGET_PATTERN: re.Pattern[str] = re.compile(r"\]\([^)]*\)")
89
+ XML_TAG_PATTERN: re.Pattern[str] = re.compile(r"<[^>]+>")
90
+ BLANKED_SPAN: str = " "
91
+
92
+ BANNED_PROSE_WORD_RULE_ID: str = "banned-prose-word"
93
+ BANNED_PROSE_WORD_MESSAGE: str = (
94
+ "Banned word: {word}. Delete it and name the evidence instead."
95
+ )
@@ -85,14 +85,14 @@ ROLE_BUGTEAM: str = "bugteam"
85
85
  """Role name whose agent definition set the preflight validates by default."""
86
86
 
87
87
  ROLE_CLEAN_CODER: str = "clean-coder"
88
- """Role name that maps to the clean-coder agent for FIX and standards-fix."""
88
+ """Compatibility role for FIX and standards-fix using poteto-agent."""
89
89
 
90
90
  DEFAULT_ROLE: str = ROLE_BUGTEAM
91
91
  """Role applied when the caller does not pass ``--role``."""
92
92
 
93
93
  ALL_AGENT_FILENAMES_BY_ROLE: dict[str, tuple[str, ...]] = {
94
- ROLE_BUGTEAM: ("code-quality-agent.md", "clean-coder.md"),
95
- ROLE_CLEAN_CODER: ("clean-coder.md",),
94
+ ROLE_BUGTEAM: ("poteto-agent.md",),
95
+ ROLE_CLEAN_CODER: ("poteto-agent.md",),
96
96
  }
97
97
  """Agent definition filenames required under ``agents/`` for each known role."""
98
98
 
@@ -19,7 +19,7 @@ Import ``run_headless_worker`` for the outcome object::
19
19
  working_directory=cwd,
20
20
  run_state_directory=run_dir,
21
21
  timeout_seconds=5400,
22
- agent_name="code-quality-agent",
22
+ agent_name="poteto-agent",
23
23
  )
24
24
  """
25
25
 
@@ -72,6 +72,7 @@ from dev_env_scripts_constants.grok_worker_constants import (
72
72
  WORKER_SPEC_TIMEOUT_KEY,
73
73
  )
74
74
  from shared_tree_paths import resolve_shared_process_tree_scripts_directory
75
+ from grok_worker_preflight import poteto_mode_skill_path
75
76
 
76
77
  _shared_process_tree_scripts_directory = resolve_shared_process_tree_scripts_directory(
77
78
  __file__,
@@ -420,6 +421,23 @@ def run_headless_worker(
420
421
  if leader_socket_path is not None
421
422
  else _mint_leader_socket_path(run_state_directory)
422
423
  )
424
+ if agent_name == "poteto-agent":
425
+ try:
426
+ skill_path = poteto_mode_skill_path().resolve()
427
+ skill_text = skill_path.read_text(encoding=UTF8_ENCODING)
428
+ task_text = prompt_file.read_text(encoding=UTF8_ENCODING)
429
+ run_state_directory.mkdir(parents=True, exist_ok=True)
430
+ prompt_file = run_state_directory / f"{resolved_leader_socket_path.stem}-poteto.txt"
431
+ prompt_file.write_text(
432
+ f"Poteto skill source: {skill_path}\n"
433
+ "Resolve companion skill paths against that source directory.\n"
434
+ "The task below defines your review, build, or translation duty. "
435
+ "Preserve its scope, write permissions, and report contract.\n\n"
436
+ f"{skill_text}\n\nTask:\n{task_text}",
437
+ encoding=UTF8_ENCODING,
438
+ )
439
+ except OSError as error:
440
+ return _launch_failure_outcome(error)
423
441
  all_arguments = _build_invocation(
424
442
  prompt_file=prompt_file,
425
443
  working_directory=working_directory,
@@ -45,6 +45,7 @@ from __future__ import annotations
45
45
 
46
46
  import argparse
47
47
  import json
48
+ import os
48
49
  import shutil
49
50
  import subprocess
50
51
  import sys
@@ -61,6 +62,7 @@ from dev_env_scripts_constants.grok_worker_constants import (
61
62
  CLAUDE_HOME_SUBDIRECTORY,
62
63
  CLI_PING_FLAG,
63
64
  CLI_ROLE_FLAG,
65
+ CLAUDE_CONFIG_DIR_ENV_VAR,
64
66
  CLI_RUN_STATE_DIR_FLAG,
65
67
  DEFAULT_AUTH_TIMEOUT_SECONDS,
66
68
  DEFAULT_PING_TIMEOUT_SECONDS,
@@ -111,7 +113,67 @@ class PreflightOutcome:
111
113
 
112
114
  def claude_config_home() -> Path:
113
115
  """Return the per-user Claude configuration directory."""
114
- return Path.home() / CLAUDE_HOME_SUBDIRECTORY
116
+ configured_root = os.environ.get(CLAUDE_CONFIG_DIR_ENV_VAR)
117
+ return Path(configured_root) if configured_root else Path.home() / CLAUDE_HOME_SUBDIRECTORY
118
+
119
+
120
+ def _read_pstack_registry(registry_path: Path) -> dict[str, object]:
121
+ try:
122
+ registry = json.loads(registry_path.read_text(encoding=UTF8_ENCODING))
123
+ except (UnicodeError, json.JSONDecodeError) as error:
124
+ raise OSError(f"Invalid pstack installation registry {registry_path}: {error}") from error
125
+ if not isinstance(registry, dict):
126
+ raise OSError(f"Invalid pstack installation registry {registry_path}: root must be an object")
127
+ return registry
128
+
129
+
130
+ def _user_pstack_entries(all_registry: dict[str, object], registry_path: Path) -> list[dict[str, object]]:
131
+ plugins = all_registry.get("plugins")
132
+ if not isinstance(plugins, dict):
133
+ raise OSError(f"Invalid pstack installation registry {registry_path}: plugins must be an object")
134
+ entries = plugins.get("pstack@pstack-claude", [])
135
+ if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
136
+ raise OSError(f"Invalid pstack installation registry {registry_path}: pstack entries must be objects")
137
+ user_entries = [entry for entry in entries if entry.get("scope") == "user"]
138
+ if len(user_entries) > 1:
139
+ raise OSError(f"Invalid pstack installation registry {registry_path}: multiple user pstack installations")
140
+ return user_entries
141
+
142
+
143
+ def _native_pstack_skill_path(registry_path: Path, fallback_path: Path) -> Path:
144
+ if not registry_path.exists():
145
+ return fallback_path
146
+ user_entries = _user_pstack_entries(_read_pstack_registry(registry_path), registry_path)
147
+ if not user_entries:
148
+ return fallback_path
149
+ install_path = user_entries[0].get("installPath")
150
+ if not isinstance(install_path, str) or not Path(install_path).is_absolute():
151
+ raise OSError(f"Invalid pstack installation registry {registry_path}: pstack installPath must be absolute")
152
+ return Path(install_path) / "skills" / "poteto-mode" / "SKILL.md"
153
+
154
+
155
+ def poteto_mode_skill_path() -> Path:
156
+ """Return the selected installed pstack poteto-mode skill path.
157
+
158
+ Returns:
159
+ The validated poteto-mode skill path.
160
+
161
+ Raises:
162
+ OSError: If the registry or selected skill is invalid.
163
+ """
164
+ config_root = claude_config_home()
165
+ fallback_path = config_root / "skills" / "poteto-mode" / "SKILL.md"
166
+ skill_path = _native_pstack_skill_path(
167
+ config_root / "plugins" / "installed_plugins.json",
168
+ fallback_path,
169
+ )
170
+ try:
171
+ skill_text = skill_path.read_text(encoding=UTF8_ENCODING)
172
+ except UnicodeError as error:
173
+ raise OSError(f"Invalid UTF-8 poteto skill {skill_path}") from error
174
+ if not skill_text.strip():
175
+ raise OSError(f"Empty poteto skill {skill_path}")
176
+ return skill_path
115
177
 
116
178
 
117
179
  def _install_manifest_path() -> Path:
@@ -218,6 +280,10 @@ def _is_grok_binary_resolvable() -> bool:
218
280
  def _is_claude_dev_env_config_present(role: str) -> bool:
219
281
  if not _install_manifest_path().is_file():
220
282
  return False
283
+ try:
284
+ poteto_mode_skill_path()
285
+ except OSError:
286
+ return False
221
287
  all_agent_filenames = ALL_AGENT_FILENAMES_BY_ROLE.get(role)
222
288
  if all_agent_filenames is None:
223
289
  return False
@@ -15,23 +15,33 @@ from .config import constants
15
15
  from .model import Diagnostic, Document, Location, Severity
16
16
 
17
17
 
18
- def accepts_rules_markdown(document: Document) -> bool:
19
- """Return whether the document is Markdown in a rules directory.
18
+ def accepts_instruction_markdown(document: Document) -> bool:
19
+ """Return whether the document is authored instruction Markdown.
20
+
21
+ Rules, skills, agents, commands, documentation and stored system prompts
22
+ all describe hooks to a reader, so each one carries the same claim.
20
23
 
21
24
  Args:
22
25
  document: Candidate document.
23
26
 
24
27
  Returns:
25
- True for a Markdown file directly under a ``rules`` directory.
28
+ True for Markdown on an instruction surface.
26
29
  """
27
- return (
28
- document.path.suffix.lower() in constants.ALL_MARKDOWN_SUFFIXES
29
- and document.path.parent.name == constants.RULES_DIRECTORY_NAME
30
+ if document.path.suffix.lower() not in constants.ALL_MARKDOWN_SUFFIXES:
31
+ return False
32
+ normalized_path = f"/{document.path.as_posix().lower()}"
33
+ return any(
34
+ each_segment in normalized_path
35
+ for each_segment in constants.ALL_HOOK_PROSE_SURFACE_SEGMENTS
30
36
  )
31
37
 
32
38
 
33
39
  def _package_root(repository_root: Path, document: Document) -> Path:
34
- return repository_root / document.path.parent.parent.as_posix()
40
+ for each_ancestor in document.path.parents:
41
+ candidate_root = repository_root / each_ancestor.as_posix()
42
+ if (candidate_root / constants.HOOKS_DIRECTORY_NAME).is_dir():
43
+ return candidate_root
44
+ return repository_root / document.path.parent.as_posix()
35
45
 
36
46
 
37
47
  def _read_text(path: Path) -> str: