claude-dev-env 2.7.1 → 2.9.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 (127) hide show
  1. package/CLAUDE.md +7 -1
  2. package/_shared/CLAUDE.md +1 -0
  3. package/_shared/advisor/advisor-protocol.md +19 -9
  4. package/_shared/pr-loop/audit-contract.md +4 -4
  5. package/_shared/pr-loop/precatch-rubric.md +2 -2
  6. package/_shared/process-tree/CLAUDE.md +41 -0
  7. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/__init__.py +1 -0
  8. package/_shared/process-tree/scripts/config/process_tree_scripts_constants/process_tree_kill_constants.py +27 -0
  9. package/_shared/process-tree/scripts/process_tree_kill.py +141 -0
  10. package/_shared/process-tree/scripts/pyproject.toml +16 -0
  11. package/_shared/process-tree/scripts/test_process_tree_kill.py +278 -0
  12. package/agents/clean-coder.md +9 -19
  13. package/agents/code-quality-agent.md +6 -5
  14. package/agents/deep-research.md +7 -24
  15. package/agents/docs-agent.md +1 -27
  16. package/agents/issue-tracker.md +1 -7
  17. package/agents/skill-writer-agent.md +1 -2
  18. package/agents/test_agent_frontmatter.py +335 -12
  19. package/docs/CODE_RULES.md +4 -2
  20. package/docs/references/CLAUDE.md +2 -2
  21. package/docs/references/advisor-tool.md +44 -6
  22. package/docs/references/team-advisor-skill.md +14 -8
  23. package/hooks/blocking/CLAUDE.md +2 -0
  24. package/hooks/blocking/fable_spawn_gate.py +187 -0
  25. package/hooks/blocking/piped_pytest_blocker.py +1223 -0
  26. package/hooks/blocking/plain_language_blocker.py +287 -15
  27. package/hooks/blocking/test_fable_spawn_gate.py +374 -0
  28. package/hooks/blocking/test_piped_pytest_blocker.py +587 -0
  29. package/hooks/blocking/test_plain_language_blocker.py +277 -2
  30. package/hooks/blocking/test_pre_tool_use_dispatcher.py +34 -2
  31. package/hooks/git-hooks/CLAUDE.md +2 -2
  32. package/hooks/git-hooks/git_hooks_constants/__init__.py +28 -0
  33. package/hooks/git-hooks/pre_push.py +343 -54
  34. package/hooks/git-hooks/test_pre_push.py +852 -6
  35. package/hooks/hooks.json +9 -19
  36. package/hooks/hooks_constants/CLAUDE.md +2 -0
  37. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -0
  38. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  39. package/hooks/hooks_constants/fable_spawn_gate_constants.py +62 -0
  40. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +360 -0
  41. package/hooks/hooks_constants/plain_language_blocker_constants.py +64 -1
  42. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -0
  43. package/hooks/hooks_constants/shell_command_segments.py +1 -1
  44. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +1 -0
  45. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +27 -0
  46. package/hooks/hooks_constants/test_prose_metrics_parity.py +124 -0
  47. package/output-styles/CLAUDE.md +17 -0
  48. package/output-styles/caveman-agent.md +37 -0
  49. package/package.json +2 -1
  50. package/rules/CLAUDE.md +1 -0
  51. package/rules/ask-user-question-required.md +26 -0
  52. package/rules/claims-as-quotes.md +65 -0
  53. package/rules/code-standards.md +33 -7
  54. package/rules/eli11-replies.md +1 -1
  55. package/scripts/CLAUDE.md +3 -3
  56. package/scripts/_code_review_test_support.py +6 -0
  57. package/scripts/check.ps1 +18 -5
  58. package/scripts/claude_chain_runner.py +203 -31
  59. package/scripts/codec_forwarding_test_support.py +2 -0
  60. package/scripts/dev_env_scripts_constants/CLAUDE.md +4 -4
  61. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +38 -0
  62. package/scripts/dev_env_scripts_constants/code_review_constants.py +403 -2
  63. package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
  64. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +132 -24
  65. package/scripts/grok_headless_runner.py +16 -83
  66. package/scripts/grok_patch_artifacts.py +123 -0
  67. package/scripts/grok_run_ledger.py +318 -0
  68. package/scripts/spawn_grok_batch.py +591 -10
  69. package/scripts/test_claude_chain_runner.py +358 -0
  70. package/scripts/test_grok_headless_runner.py +18 -63
  71. package/scripts/test_grok_patch_artifacts.py +82 -0
  72. package/scripts/test_grok_run_ledger.py +116 -0
  73. package/scripts/test_invoke_code_review.py +298 -0
  74. package/scripts/test_resolve_worker_spawn.py +6 -0
  75. package/scripts/test_spawn_grok_batch.py +396 -0
  76. package/scripts/tests/CLAUDE.md +1 -0
  77. package/scripts/tests/test_grok_worker_constants.py +59 -0
  78. package/skills/CLAUDE.md +4 -2
  79. package/skills/_shared/CLAUDE.md +37 -4
  80. package/skills/_shared/advisor/CLAUDE.md +9 -0
  81. package/skills/_shared/advisor/advisor-protocol.md +5 -0
  82. package/skills/_shared/advisor/scripts/README.md +9 -0
  83. package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
  84. package/skills/_shared/pr-loop/CLAUDE.md +18 -1
  85. package/skills/_shared/pr-loop/audit-contract.md +5 -0
  86. package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
  87. package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
  88. package/skills/_shared/pr-loop/fix-protocol.md +5 -0
  89. package/skills/_shared/pr-loop/gh-payloads.md +5 -0
  90. package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
  91. package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
  92. package/skills/_shared/pr-loop/scripts/CLAUDE.md +8 -1
  93. package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
  94. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +46 -0
  95. package/skills/_shared/pr-loop/state-schema.md +5 -0
  96. package/skills/_shared/pr-loop/worker-spawn.md +5 -0
  97. package/skills/autoconverge/workflow/converge.contract.test.mjs +105 -2
  98. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +34 -1
  99. package/skills/autoconverge/workflow/converge.mjs +66 -33
  100. package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +0 -4
  101. package/skills/codex-review/scripts/codex_usage_probe.py +20 -33
  102. package/skills/codex-review/scripts/run_codex_review.py +16 -64
  103. package/skills/codex-review/scripts/test_codex_usage_probe.py +46 -41
  104. package/skills/codex-review/scripts/test_run_codex_review.py +1 -33
  105. package/skills/e-code-review/SKILL.md +15 -9
  106. package/skills/e-code-review/reference/fix.md +29 -7
  107. package/skills/e-code-review/reference/loop.md +230 -14
  108. package/skills/e-code-review/reference/low.md +33 -15
  109. package/skills/e-code-review/reference/medium.md +55 -21
  110. package/skills/e-code-review/reference/runner-selection.md +40 -0
  111. package/skills/e-code-review/reference/xhigh.md +30 -12
  112. package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
  113. package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
  114. package/skills/e-code-review/scripts/grok_code_review.py +221 -0
  115. package/skills/e-code-review/scripts/test_grok_code_review.py +212 -0
  116. package/skills/fresh-branch/CLAUDE.md +5 -5
  117. package/skills/fresh-branch/SKILL.md +14 -6
  118. package/skills/fresh-branch/scripts/create_fresh_branch.py +122 -39
  119. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +10 -3
  120. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +251 -0
  121. package/skills/grok-spawn/SKILL.md +10 -0
  122. package/skills/orchestrator/SKILL.md +9 -1
  123. package/skills/orchestrator-refresh/SKILL.md +5 -1
  124. package/skills/task-build/reference/tool-routing.md +3 -0
  125. package/skills/team-advisor/SKILL.md +23 -41
  126. package/system-prompts/software-engineer.xml +6 -3
  127. package/skills/test_markdown_link_integrity.py +0 -107
@@ -1,34 +1,24 @@
1
1
  ---
2
2
  name: clean-coder
3
- description: "Use PROACTIVELY for ALL code generation — feature development, bug fixes, refactoring, hook creation, automation scripts, and any task that produces code. Internalizes CODE_RULES.md and the 8-dimension readability standard so thoroughly that /check finds zero issues. The definitive code-writing agent."
3
+ description: "Use PROACTIVELY for ALL code generation — feature development, bug fixes, refactoring, hook creation, automation scripts, and any task that produces code. Internalizes AGENTS.md (canonical) via the CODE_RULES.md projection and the 8-dimension readability standard so thoroughly that /check finds zero issues. The definitive code-writing agent."
4
4
  tools: Read, Write, Edit, Bash, Grep, Glob, Task, Skill, SendMessage
5
5
  color: green
6
6
  ---
7
7
 
8
8
  # Clean Coder — Zero-Defect Code Generation
9
9
 
10
- You are the definitive code-writing agent. You produce code so clean that reviewers find nothing. Every rule from CODE_RULES.md and every dimension from the readability rubric is internalized into your generation process. The goal: `/check` returns CLEAN on every file you touch.
10
+ You are the definitive code-writing agent. You produce code so clean that reviewers find nothing. Canonical policy is repository-root `AGENTS.md`; `CODE_RULES.md` is its compact projection. Every dimension from the readability rubric is internalized into your generation process. The goal: `/check` returns CLEAN on every file you touch.
11
11
 
12
- **Announce at start:** "Using clean-coder agent — CODE_RULES.md internalized, targeting 160/160 readability."
12
+ **Announce at start:** "Using clean-coder agent — AGENTS.md / CODE_RULES projection internalized, targeting 160/160 readability."
13
13
 
14
14
  ## First Action (MANDATORY)
15
15
 
16
- Before writing a single line:
16
+ Before writing a single line — **task-local discovery only** (no project-wide preload):
17
17
 
18
18
  1. **Read project CLAUDE.md** (when one exists) — load project-specific rules, naming overrides, and any extended ruleset.
19
- 2. **Glob for existing config files** using these patterns from the project root. Issue all seven Glob calls in parallel (single message, multiple tool calls they have no dependencies on each other):
20
- - `**/config/constants.py`
21
- - `**/config/timing.py`
22
- - `**/config/selectors.py`
23
- - `**/config.py`
24
- - `**/settings.py`
25
- - `**/.env`
26
- - `**/.env.*`
27
- 3. **Read every config file the globs return.** Extract every `UPPER_SNAKE_CASE` binding into a local name → value table. Before writing any constant in the new code:
28
- - Exact value match in the table → import the existing name.
29
- - Semantic match → reuse the existing name.
30
- - No match → add the constant to the appropriate `config/` file.
31
- 4. **Read the file you are about to edit** (when editing existing code). Note every existing comment so you can leave each one untouched on lines that remain otherwise unchanged.
19
+ 2. **Read the file you are about to edit** (when editing existing code). Note every existing comment so you can leave each one untouched on lines that remain otherwise unchanged.
20
+ 3. **Discover config only next to the task files.** From each file you will write or edit, walk up to the nearest package or repo root and open only the config modules that package already uses for constants — typically `config/constants.py`, `config/timing.py`, `config/selectors.py`, or a sibling `*_constants` package. Do **not** glob the whole tree for every config file. Do **not** glob or open `.env`, `.env.*`, or other secret files.
21
+ 4. **Reuse constants from that local table.** Exact value match → import the existing name. Semantic match → reuse it. No match → add the constant to the appropriate `config/` file for that package.
32
22
 
33
23
  ## The 8 Generation Laws
34
24
 
@@ -171,7 +161,7 @@ Hooks under `~/.claude/hooks/` are standalone scripts; module-level `UPPER_SNAKE
171
161
 
172
162
  ### Reuse before create
173
163
 
174
- Search first. Import second. Create last. Before writing a constant, scan the name → value table built in First Action step 3.
164
+ Search first. Import second. Create last. Before writing a constant, scan the name → value table built in First Action step 4.
175
165
 
176
166
  ### File-global constants use-count rule
177
167
 
@@ -422,7 +412,7 @@ When reading transcripts under the user projects directory, pass explicit file p
422
412
 
423
413
  Decision tree before writing any constant:
424
414
 
425
- 1. Search the existing `config/` directory (using the table from First Action step 3).
415
+ 1. Search the existing `config/` directory (using the table from First Action step 4).
426
416
  2. Found exact value → **import it**.
427
417
  3. Found semantic match → **reuse the existing name**.
428
418
  4. Config file exists for this category → **add to the existing file**.
@@ -8,7 +8,7 @@ color: red
8
8
 
9
9
  You audit a pull request diff for bugs and CODE_RULES.md compliance issues. You return findings; the orchestrator handles fixes.
10
10
 
11
- **Announce at start:** "Using code-quality-agent — auditing diff against A–P categories with CODE_RULES.md awareness."
11
+ **Announce at start:** "Using code-quality-agent — auditing diff against A–Q categories with CODE_RULES.md awareness."
12
12
 
13
13
  ## Scope
14
14
 
@@ -18,7 +18,7 @@ Audit only added or modified lines in the diff. Pre-existing code on untouched l
18
18
 
19
19
  This agent runs in one of two modes depending on the calling prompt:
20
20
 
21
- - **Unscoped (default):** the prompt names no categories. Walk all of A through P and produce Shape A/B for every category.
21
+ - **Unscoped (default):** the prompt names no categories. Walk all of A through Q and produce Shape A/B for every category.
22
22
  - **Category-restricted:** the prompt names a subset of categories ("audit only category F" or "investigate only H, I, and K"). Audit only the named categories and produce Shape A/B for those alone; skip the rest.
23
23
 
24
24
  Tradeoff for callers picking the category-restricted mode: parallel category invocation loses cross-category reasoning. A security finding in Category H may inform a Category J classification, and a parallel split misses that connection. When categories need to inform each other, prefer the unscoped mode.
@@ -31,9 +31,9 @@ Preserve every existing comment. Findings on production code report only on new
31
31
 
32
32
  Report findings only. Author zero edits. Author zero diffs. Run zero commits or pushes. The orchestrator (and the calling skill) handles fix application, commit creation, and PR posting based on your finding list.
33
33
 
34
- ## Bug Categories A–P
34
+ ## Bug Categories A–Q
35
35
 
36
- Every audit pass walks all sixteen categories. Each category produces either at least one Shape A finding (concrete bug at a file:line) or at least one Shape B proof-of-absence entry (audited and clean, with adversarial probes documented). A category that returns neither is a protocol gap per the audit contract.
36
+ Every audit pass walks all seventeen categories. Each category produces either at least one Shape A finding (concrete bug at a file:line) or at least one Shape B proof-of-absence entry (audited and clean, with adversarial probes documented). A category that returns neither is a protocol gap per the audit contract.
37
37
 
38
38
  For each category's full description, examples, sub-bucket decomposition, and concrete checks, read the matching rubric in `../audit-rubrics/category_rubrics/`:
39
39
 
@@ -55,6 +55,7 @@ For each category's full description, examples, sub-bucket decomposition, and co
55
55
  | N | Test-name scenario verifier | `../audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md` |
56
56
  | O | Docstring / fixture-prose vs implementation drift | `../audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` |
57
57
  | P | Name / regex / word-list vs behavior-contract precision | `../audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md` |
58
+ | Q | Cross-surface claim consistency (terminology, PR-description claims, message-vs-guard) | `../audit-rubrics/category_rubrics/category-q-cross-surface-claims.md` |
58
59
 
59
60
  Test files (`test_*.py`, `*_test.py`, `*.test.*`, `*.spec.*`, `conftest.py`, and any path under `/tests/`) are exempt from category J. The exempt path families documented in the J reference also opt out of the constants-location sub-item.
60
61
 
@@ -114,7 +115,7 @@ A bare verified-clean label is inadequate: every Shape B entry lists the files o
114
115
 
115
116
  ## Per-Category Expectation
116
117
 
117
- Every category A through P is investigated. The output for each category is one of:
118
+ Every category A through Q is investigated. The output for each category is one of:
118
119
  - one or more Shape A findings, or
119
120
  - one Shape B proof-of-absence entry with concrete files, quoted lines, and adversarial probes.
120
121
 
@@ -1,25 +1,6 @@
1
1
  ---
2
2
  name: deep-research
3
- description: Use this agent for iterative, multi-source deep research that produces comprehensive Obsidian reports with full citations. Official-docs-first methodology with anti-hallucination constraints. Examples:
4
-
5
- <example>
6
- Context: User wants thorough research on a technical topic
7
- user: "Research the current state of WebSocket authentication best practices"
8
- assistant: "I'll use the deep-research agent to conduct iterative multi-source research and produce a cited report."
9
- <commentary>
10
- Multi-source research requiring iteration and synthesis — exactly what deep-research handles.
11
- </commentary>
12
- </example>
13
-
14
- <example>
15
- Context: User needs a landscape survey with citations
16
- user: "Compare the major vector database options for production RAG systems in 2026"
17
- assistant: "I'll launch the deep-research agent to survey the landscape across multiple sources."
18
- <commentary>
19
- Broad survey requiring many sources, comparison, and synthesis — deep-research with exhaustive depth.
20
- </commentary>
21
- </example>
22
-
3
+ description: "Use this agent for iterative, multi-source deep research that produces comprehensive Obsidian reports with full citations. Official-docs-first methodology with anti-hallucination constraints. Examples:\n\n <example>\n Context: User wants thorough research on a technical topic\n user: \"Research the current state of WebSocket authentication best practices\"\n assistant: \"I'll use the deep-research agent to conduct iterative multi-source research and produce a cited report.\"\n <commentary>\n Multi-source research requiring iteration and synthesis — exactly what deep-research handles.\n </commentary>\n </example>\n\n <example>\n Context: User needs a landscape survey with citations\n user: \"Compare the major vector database options for production RAG systems in 2026\"\n assistant: \"I'll launch the deep-research agent to survey the landscape across multiple sources.\"\n <commentary>\n Broad survey requiring many sources, comparison, and synthesis — deep-research with exhaustive depth.\n </commentary>\n </example>"
23
4
  color: cyan
24
5
  ---
25
6
 
@@ -31,10 +12,9 @@ You receive a `<research_brief>` from the orchestrating skill. Your job is to ex
31
12
 
32
13
  On receiving the research brief, write the state file:
33
14
 
34
- `.deep-research-state.md`:
15
+ `.deep-research-state.md` opens with a YAML frontmatter block — a three-hyphen fence line, these fields, then a closing three-hyphen fence line:
35
16
 
36
- ```markdown
37
- ---
17
+ ```yaml
38
18
  topic: "[from brief]"
39
19
  brief: "[one-line summary from brief]"
40
20
  iteration: 0
@@ -42,8 +22,11 @@ max_iterations: [from brief]
42
22
  status: researching
43
23
  source_count: 0
44
24
  official_docs_found: false
45
- ---
25
+ ```
26
+
27
+ The rest of the file holds the tracking sections:
46
28
 
29
+ ```markdown
47
30
  ## Sources Found
48
31
 
49
32
  (none yet)
@@ -1,32 +1,6 @@
1
1
  ---
2
2
  name: docs-agent
3
- description: Use this agent for all documentation tasks — managing/consolidating project docs, analyzing existing documentation to prevent code duplication, and writing user-facing guides for non-technical audiences. Trigger with requests like 'update our docs', 'check if we already have this documented', 'write a user guide', or 'consolidate documentation'.
4
-
5
- Examples:
6
- - <example>
7
- Context: User wants comprehensive documentation management
8
- user: "call the doc manager"
9
- assistant: "I'll use the docs-agent to analyze and update the documentation"
10
- <commentary>
11
- Documentation management request — use docs-agent in orchestration mode.
12
- </commentary>
13
- </example>
14
- - <example>
15
- Context: User is implementing a new feature and wants to avoid duplicating existing functionality
16
- user: "I need to add a function that validates user input"
17
- assistant: "Let me consult the docs-agent to check if we already have validation methods documented"
18
- <commentary>
19
- Before implementing new functionality, use docs-agent in analysis mode to check for existing documented methods.
20
- </commentary>
21
- </example>
22
- - <example>
23
- Context: User needs documentation for non-technical staff
24
- user: "Write documentation for the new export feature so our office staff can use it"
25
- assistant: "I'll use the docs-agent to create clear, step-by-step documentation that anyone can follow"
26
- <commentary>
27
- Non-technical audience — use docs-agent in user-docs writing mode.
28
- </commentary>
29
- </example>
3
+ description: "Use this agent for all documentation tasks — managing/consolidating project docs, analyzing existing documentation to prevent code duplication, and writing user-facing guides for non-technical audiences. Trigger with requests like 'update our docs', 'check if we already have this documented', 'write a user guide', or 'consolidate documentation'.\n\nExamples:\n- <example>\n Context: User wants comprehensive documentation management\n user: \"call the doc manager\"\n assistant: \"I'll use the docs-agent to analyze and update the documentation\"\n <commentary>\n Documentation management request — use docs-agent in orchestration mode.\n </commentary>\n</example>\n- <example>\n Context: User is implementing a new feature and wants to avoid duplicating existing functionality\n user: \"I need to add a function that validates user input\"\n assistant: \"Let me consult the docs-agent to check if we already have validation methods documented\"\n <commentary>\n Before implementing new functionality, use docs-agent in analysis mode to check for existing documented methods.\n </commentary>\n</example>\n- <example>\n Context: User needs documentation for non-technical staff\n user: \"Write documentation for the new export feature so our office staff can use it\"\n assistant: \"I'll use the docs-agent to create clear, step-by-step documentation that anyone can follow\"\n <commentary>\n Non-technical audience — use docs-agent in user-docs writing mode.\n </commentary>\n</example>"
30
4
  color: cyan
31
5
  ---
32
6
 
@@ -1,12 +1,6 @@
1
1
  ---
2
2
  name: issue-tracker
3
- description: >-
4
- Primary handler for one GitHub issue action per spawn on a work-stream:
5
- open an epic, file a sub-issue, update status in place, refresh the epic
6
- checklist, or close a sub-issue. Spawn with one action plus the issue-candidate
7
- or issue number it needs; loads the issue-tracker skill; returns affected
8
- issue numbers and URLs. Prefer the same warm agent for follow-ups on the same
9
- issue or the same epic work-stream.
3
+ description: "Primary handler for one GitHub issue action per spawn on a work-stream: open an epic, file a sub-issue, update status in place, refresh the epic checklist, or close a sub-issue. Spawn with one action plus the issue-candidate or issue number it needs; loads the issue-tracker skill; returns affected issue numbers and URLs. Prefer the same warm agent for follow-ups on the same issue or the same epic work-stream."
10
4
  tools: Read, Bash, Skill, mcp__github__search_issues, mcp__github__issue_read, mcp__github__issue_write, mcp__github__sub_issue_write, mcp__github__add_issue_comment
11
5
  color: green
12
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: skill-writer-agent
3
- description: >-
4
- Authors a skill package — SKILL.md plus companion files — to skill-builder conventions: hub layout, trigger-catalog description, deterministic work shipped as scripts and task-seed lists. Triggers: author a skill, write a SKILL.md, skill-writer-agent, delegate skill file authoring, produce skill package files.
3
+ description: "Authors a skill package — SKILL.md plus companion files — to skill-builder conventions: hub layout, trigger-catalog description, deterministic work shipped as scripts and task-seed lists. Triggers: author a skill, write a SKILL.md, skill-writer-agent, delegate skill file authoring, produce skill package files."
5
4
  tools: Read, Write, Edit, Grep, Glob
6
5
  color: green
7
6
  ---
@@ -16,6 +16,53 @@ model on every spawn, so no agent definition names one, concrete or
16
16
  flag: model: inherit <- caller can no longer choose the model
17
17
  flag: model: opus <- pinned concrete model, caller can't override
18
18
 
19
+ Every block must also load through `yaml.safe_load`. An unquoted colon inside
20
+ a plain scalar reads as a mapping key and makes the whole block unloadable::
21
+
22
+ ok: description: ... constraints. Examples are below.
23
+ flag: description: ... constraints. Examples: <- block no longer loads
24
+
25
+ Two parsers read these files, and a block that satisfies one can still be
26
+ unreadable by the other. `scripts/codex_compat_materializer.py` reads the
27
+ block line by line, so every field fits on the line that names it::
28
+
29
+ ok: description: "Use this agent ... Examples:\\n\\n <example> ..."
30
+ flag: description: | <- the line scan cannot follow the block scalar
31
+
32
+ That reader also counts fence lines across the whole file, so a bare `---`
33
+ anywhere in the body reads as a second frontmatter fence::
34
+
35
+ ok: ```yaml ... ``` example blocks in the body
36
+ flag: a `---` line inside a body example <- counted as a fence
37
+
38
+ That reader also cannot express an empty `tools: []` list, which is a defect
39
+ in the reader rather than in the one definition that declares it, so that
40
+ definition is named in `_codex_materializable_paths` and covered by every
41
+ other check here.
42
+
43
+ Each definition also has to carry a `name` and a `description` bound to a
44
+ non-empty string, and a `name` equal to its file stem — a mapping that loads
45
+ but binds `description` to nothing, or names an agent the file does not,
46
+ registers a subagent the caller cannot spawn::
47
+
48
+ ok: docs-agent.md -> name: docs-agent
49
+ flag: docs-agent.md -> name: doc-manager <- wrong spawn id
50
+ flag: description: <- loads as None, loader needs text
51
+
52
+ Every check above is parametrized over the definitions that yield a
53
+ frontmatter block, so a file that yields none would drop out of all of them
54
+ and leave the suite green while unreadable. The block is what the fence lines
55
+ delimit, so the file that opens no fence or never closes one is exactly the
56
+ broken file these checks exist to catch::
57
+
58
+ ok: docs-agent.md -> --- name/description --- <- block found
59
+ flag: docs-agent.md -> --- name/description <- no closing fence,
60
+ silently uncovered
61
+
62
+ `test_every_agent_definition_yields_a_frontmatter_block` holds that floor: it
63
+ walks the same directory the parametrized checks draw from and fails on any
64
+ definition missing from them.
65
+
19
66
  Frontmatter parsing is self-contained here (stdlib only): the block is the
20
67
  text between the file's opening and closing `---` fence lines, and top-level
21
68
  keys are read with a line scan so an agent whose `description` embeds
@@ -24,18 +71,27 @@ informal `<example>` prose is not mistaken for one carrying extra keys.
24
71
 
25
72
  from __future__ import annotations
26
73
 
74
+ import importlib.util
27
75
  import re
76
+ import sys
28
77
  from functools import cache
29
78
  from pathlib import Path
79
+ from types import ModuleType
30
80
 
31
81
  import pytest
32
82
  import yaml
33
83
 
34
84
  ACCEPTED_FRONTMATTER_KEYS = frozenset({"name", "description", "tools", "color"})
35
- CODE_VERIFIER_AGENT_NAME = "code-verifier"
85
+ EMPTY_TOOLS_LIST_FILENAME = "code-advisor.md"
36
86
  EXEMPT_MARKDOWN_FILENAME = "CLAUDE.md"
37
87
  FRONTMATTER_FENCE_LINE = "---"
88
+ MATERIALIZER_MODULE_NAME = "codex_compat_materializer"
89
+ MATERIALIZER_MODULE_PATH = (
90
+ Path(__file__).resolve().parent.parent / "scripts" / f"{MATERIALIZER_MODULE_NAME}.py"
91
+ )
38
92
  MODEL_KEY_NAME = "model"
93
+ RENAMED_AGENT_NAME = "an-agent-name-no-file-carries"
94
+ REQUIRED_FRONTMATTER_FIELDS = ("name", "description")
39
95
  TOP_LEVEL_KEY_PATTERN = re.compile(r"^([a-z][a-z0-9_]*):", re.MULTILINE)
40
96
 
41
97
 
@@ -60,14 +116,30 @@ def _extract_frontmatter_block(markdown_text: str) -> str | None:
60
116
 
61
117
 
62
118
  @cache
63
- def _agent_definition_paths() -> tuple[Path, ...]:
119
+ def _agent_definition_candidate_paths() -> tuple[Path, ...]:
120
+ """Return every markdown file in this directory that must be a definition.
121
+
122
+ This is the floor the parametrized checks are measured against: each of
123
+ these files is expected to yield a frontmatter block, so one that does not
124
+ is a broken definition rather than a file to pass over.
125
+
126
+ Returns:
127
+ Every `*.md` path in this directory except the exempt one, sorted.
128
+ """
64
129
  agents_directory = Path(__file__).parent
65
- all_markdown_files = sorted(agents_directory.glob("*.md"))
66
130
  return tuple(
67
131
  each_markdown_file
68
- for each_markdown_file in all_markdown_files
132
+ for each_markdown_file in sorted(agents_directory.glob("*.md"))
69
133
  if each_markdown_file.name != EXEMPT_MARKDOWN_FILENAME
70
- and _extract_frontmatter_block(each_markdown_file.read_text(encoding="utf-8"))
134
+ )
135
+
136
+
137
+ @cache
138
+ def _agent_definition_paths() -> tuple[Path, ...]:
139
+ return tuple(
140
+ each_markdown_file
141
+ for each_markdown_file in _agent_definition_candidate_paths()
142
+ if _extract_frontmatter_block(each_markdown_file.read_text(encoding="utf-8"))
71
143
  is not None
72
144
  )
73
145
 
@@ -84,6 +156,124 @@ def _top_level_keys(frontmatter_block: str) -> set[str]:
84
156
  return set(TOP_LEVEL_KEY_PATTERN.findall(frontmatter_block))
85
157
 
86
158
 
159
+ @cache
160
+ def _codex_materializable_paths() -> tuple[Path, ...]:
161
+ """Return the definitions the Codex materializer is expected to read.
162
+
163
+ `code-advisor.md` declares `tools: []`, and its body states the agent has
164
+ zero tools, so the empty list is the field saying what the author meant.
165
+ The materializer's list parser raises on an empty list, so it cannot
166
+ express a correct declaration::
167
+
168
+ tools: Read, Bash -> ok: parsed
169
+ tools: [] -> flag: MaterializerError, though the file is right
170
+
171
+ The gap belongs to that parser, so the definition stays as written and only
172
+ this one check steps around it. The YAML-load, required-field, accepted-key,
173
+ model-ban, and name checks all still cover the file.
174
+
175
+ Returns:
176
+ Every agent definition path except the one whose correct frontmatter
177
+ the materializer's list parser cannot express.
178
+ """
179
+ return tuple(
180
+ each_path
181
+ for each_path in _agent_definition_paths()
182
+ if each_path.name != EMPTY_TOOLS_LIST_FILENAME
183
+ )
184
+
185
+
186
+ @cache
187
+ def _codex_materializer_module() -> ModuleType:
188
+ """Load the package's own line-oriented frontmatter parser from disk.
189
+
190
+ Returns:
191
+ The imported `codex_compat_materializer` module.
192
+ """
193
+ module_specification = importlib.util.spec_from_file_location(
194
+ MATERIALIZER_MODULE_NAME, MATERIALIZER_MODULE_PATH
195
+ )
196
+ assert module_specification is not None, (
197
+ f"no import specification for {MATERIALIZER_MODULE_PATH}"
198
+ )
199
+ assert module_specification.loader is not None, (
200
+ f"no import loader for {MATERIALIZER_MODULE_PATH}"
201
+ )
202
+ materializer_module = importlib.util.module_from_spec(module_specification)
203
+ sys.modules.setdefault(MATERIALIZER_MODULE_NAME, materializer_module)
204
+ module_specification.loader.exec_module(materializer_module)
205
+ return materializer_module
206
+
207
+
208
+ def _required_field_problem(parsed_frontmatter: object) -> str | None:
209
+ """Describe what stops the loader using this frontmatter, or None.
210
+
211
+ The loader needs text for every required field, so a field that loads as
212
+ nothing or as a nested mapping is a problem even though the block itself
213
+ is valid YAML::
214
+
215
+ description: Use this agent ... -> ok: None
216
+ description: -> flag: bound to NoneType
217
+ description: -> flag: bound to dict
218
+ nested: value
219
+
220
+ Args:
221
+ parsed_frontmatter: Value `yaml.safe_load` produced for the block.
222
+
223
+ Returns:
224
+ A sentence naming the problem, or None when every required field is a
225
+ non-empty string.
226
+ """
227
+ if not isinstance(parsed_frontmatter, dict):
228
+ return (
229
+ f"frontmatter loads as {type(parsed_frontmatter).__name__} rather "
230
+ "than a key/value mapping"
231
+ )
232
+ for each_field in REQUIRED_FRONTMATTER_FIELDS:
233
+ if each_field not in parsed_frontmatter:
234
+ return f"frontmatter carries no {each_field} field"
235
+ field_value = parsed_frontmatter[each_field]
236
+ if not isinstance(field_value, str):
237
+ return (
238
+ f"frontmatter binds {each_field} to "
239
+ f"{type(field_value).__name__} rather than a string"
240
+ )
241
+ if not field_value.strip():
242
+ return f"frontmatter binds {each_field} to an empty string"
243
+ return None
244
+
245
+
246
+ def _agent_name_problem(parsed_frontmatter: object, expected_name: str) -> str | None:
247
+ """Describe a name that disagrees with the file stem, or None.
248
+
249
+ A subagent registers under the name in its frontmatter, so a name that is
250
+ not the file stem is spawned by an id no caller uses::
251
+
252
+ docs-agent.md -> name: docs-agent -> ok: None
253
+ docs-agent.md -> name: doc-manager -> flag: wrong spawn id
254
+
255
+ Args:
256
+ parsed_frontmatter: Value `yaml.safe_load` produced for the block.
257
+ expected_name: The definition file's stem.
258
+
259
+ Returns:
260
+ A sentence naming the problem, or None when the name matches the stem.
261
+ """
262
+ if not isinstance(parsed_frontmatter, dict):
263
+ return (
264
+ f"frontmatter loads as {type(parsed_frontmatter).__name__} rather "
265
+ "than a key/value mapping"
266
+ )
267
+ declared_name = parsed_frontmatter.get("name")
268
+ if declared_name == expected_name:
269
+ return None
270
+ return (
271
+ f"frontmatter declares name {declared_name!r} rather than "
272
+ f"{expected_name!r}, so the agent registers under a spawn id that does "
273
+ "not match its file"
274
+ )
275
+
276
+
87
277
  @pytest.mark.parametrize(
88
278
  "agent_definition_path",
89
279
  _agent_definition_paths(),
@@ -100,14 +290,121 @@ def test_agent_frontmatter_uses_only_accepted_keys(
100
290
  )
101
291
 
102
292
 
103
- def test_code_verifier_frontmatter_parses_and_names_the_agent() -> None:
104
- agents_directory = Path(__file__).parent
105
- code_verifier_block = _frontmatter_block(
106
- agents_directory / f"{CODE_VERIFIER_AGENT_NAME}.md"
293
+ @pytest.mark.parametrize(
294
+ "agent_definition_path",
295
+ _agent_definition_paths(),
296
+ ids=lambda each_path: each_path.name,
297
+ )
298
+ def test_agent_frontmatter_loads_as_a_yaml_mapping(
299
+ agent_definition_path: Path,
300
+ ) -> None:
301
+ frontmatter_block = _frontmatter_block(agent_definition_path)
302
+ try:
303
+ parsed_frontmatter = yaml.safe_load(frontmatter_block)
304
+ except yaml.YAMLError as yaml_error:
305
+ pytest.fail(
306
+ f"{agent_definition_path.name} frontmatter is not loadable YAML, so "
307
+ f"the subagent loader cannot read the definition: {yaml_error}"
308
+ )
309
+ assert isinstance(parsed_frontmatter, dict), (
310
+ f"{agent_definition_path.name} frontmatter loads as "
311
+ f"{type(parsed_frontmatter).__name__} rather than a key/value mapping"
312
+ )
313
+ unaccepted_keys = set(parsed_frontmatter) - ACCEPTED_FRONTMATTER_KEYS
314
+ assert not unaccepted_keys, (
315
+ f"{agent_definition_path.name} loads frontmatter keys the subagent "
316
+ f"loader does not accept: {sorted(unaccepted_keys)}"
317
+ )
318
+
319
+
320
+ def test_every_agent_definition_yields_a_frontmatter_block() -> None:
321
+ covered_names = {each_path.name for each_path in _agent_definition_paths()}
322
+ uncovered_names = sorted(
323
+ each_path.name
324
+ for each_path in _agent_definition_candidate_paths()
325
+ if each_path.name not in covered_names
326
+ )
327
+ assert not uncovered_names, (
328
+ f"{', '.join(uncovered_names)} yield no frontmatter block, so every "
329
+ "parametrized check in this module passes over them and the suite "
330
+ "stays green while the definitions are unreadable"
107
331
  )
108
- parsed_frontmatter = yaml.safe_load(code_verifier_block)
109
- assert parsed_frontmatter["name"] == CODE_VERIFIER_AGENT_NAME
110
- assert set(parsed_frontmatter) <= ACCEPTED_FRONTMATTER_KEYS
332
+
333
+
334
+ @pytest.mark.parametrize(
335
+ "agent_definition_path",
336
+ _codex_materializable_paths(),
337
+ ids=lambda each_path: each_path.name,
338
+ )
339
+ def test_agent_frontmatter_parses_with_the_codex_materializer(
340
+ agent_definition_path: Path,
341
+ ) -> None:
342
+ materializer_module = _codex_materializer_module()
343
+ try:
344
+ materializer_module.parse_frontmatter(
345
+ agent_definition_path,
346
+ agent_definition_path.read_text(encoding="utf-8"),
347
+ agent_definition_path.name,
348
+ )
349
+ except materializer_module.MaterializerError as materializer_error:
350
+ pytest.fail(
351
+ f"{agent_definition_path.name} frontmatter is unreadable by "
352
+ f"{MATERIALIZER_MODULE_NAME}, so the agent cannot be materialized "
353
+ f"for Codex: {materializer_error}"
354
+ )
355
+
356
+
357
+ @pytest.mark.parametrize(
358
+ "agent_definition_path",
359
+ _agent_definition_paths(),
360
+ ids=lambda each_path: each_path.name,
361
+ )
362
+ def test_agent_frontmatter_binds_required_fields_to_non_empty_strings(
363
+ agent_definition_path: Path,
364
+ ) -> None:
365
+ parsed_frontmatter = yaml.safe_load(_frontmatter_block(agent_definition_path))
366
+ field_problem = _required_field_problem(parsed_frontmatter)
367
+ assert field_problem is None, f"{agent_definition_path.name} {field_problem}"
368
+
369
+
370
+ def test_required_field_check_rejects_a_description_bound_to_nothing() -> None:
371
+ parsed_frontmatter = yaml.safe_load("name: docs-agent\ndescription:\n")
372
+ assert _required_field_problem(parsed_frontmatter) == (
373
+ "frontmatter binds description to NoneType rather than a string"
374
+ )
375
+
376
+
377
+ def test_required_field_check_rejects_a_description_bound_to_a_mapping() -> None:
378
+ parsed_frontmatter = yaml.safe_load(
379
+ "name: docs-agent\ndescription:\n nested: value\n"
380
+ )
381
+ assert _required_field_problem(parsed_frontmatter) == (
382
+ "frontmatter binds description to dict rather than a string"
383
+ )
384
+
385
+
386
+ @pytest.mark.parametrize(
387
+ "agent_definition_path",
388
+ _agent_definition_paths(),
389
+ ids=lambda each_path: each_path.name,
390
+ )
391
+ def test_agent_frontmatter_names_the_agent_after_its_file(
392
+ agent_definition_path: Path,
393
+ ) -> None:
394
+ parsed_frontmatter = yaml.safe_load(_frontmatter_block(agent_definition_path))
395
+ name_problem = _agent_name_problem(parsed_frontmatter, agent_definition_path.stem)
396
+ assert name_problem is None, f"{agent_definition_path.name} {name_problem}"
397
+
398
+
399
+ def test_name_check_rejects_an_agent_renamed_away_from_its_file_stem() -> None:
400
+ agent_definition_path = _agent_definition_paths()[0]
401
+ renamed_block = _frontmatter_block(agent_definition_path).replace(
402
+ f"name: {agent_definition_path.stem}", f"name: {RENAMED_AGENT_NAME}", 1
403
+ )
404
+ name_problem = _agent_name_problem(
405
+ yaml.safe_load(renamed_block), agent_definition_path.stem
406
+ )
407
+ assert name_problem is not None and RENAMED_AGENT_NAME in name_problem
111
408
 
112
409
 
113
410
  @pytest.mark.parametrize(
@@ -125,3 +422,29 @@ def test_agent_frontmatter_carries_no_model_key(
125
422
  "the caller supplies the model on every spawn, so agent definitions "
126
423
  "carry no model key at all, not even model: inherit"
127
424
  )
425
+
426
+
427
+ def _clean_coder_body() -> str:
428
+ return (Path(__file__).parent / "clean-coder.md").read_text(encoding="utf-8")
429
+
430
+
431
+ def test_clean_coder_never_globs_or_reads_dotenv_files() -> None:
432
+ body = _clean_coder_body()
433
+ assert "`**/.env`" not in body
434
+ assert "`**/.env.*`" not in body
435
+ assert "Never open `.env`" in body or "Do **not** glob or open `.env`" in body
436
+ assert re.search(r"(?i)glob.*\.env|\.env.*glob", body) is None or "Do **not** glob or open `.env`" in body
437
+
438
+
439
+ def test_clean_coder_uses_task_local_config_discovery() -> None:
440
+ body = _clean_coder_body()
441
+ assert "task-local" in body.lower()
442
+ assert "project-wide preload" in body.lower() or "Do **not** glob the whole tree" in body
443
+ assert "Issue all five Glob calls" not in body
444
+ assert "Issue all seven Glob calls" not in body
445
+
446
+
447
+ def test_clean_coder_examples_import_constants_from_config() -> None:
448
+ body = _clean_coder_body()
449
+ assert "from config.timing import MAXIMUM_RETRIES" in body
450
+ assert re.search(r"(?m)^MAXIMUM_RETRIES\s*=\s*\d+", body) is None
@@ -1,6 +1,8 @@
1
1
  # Code Rules Reference
2
2
 
3
- Compact reference for agents. marks rules enforced by `code_rules_enforcer.py` the hook blocks the Write/Edit and returns the corrective detail at violation time, so this document lists those rules by name only.
3
+ Compact **projection** of the repository-root canonical policy [`AGENTS.md`](../../../AGENTS.md). When this file and `AGENTS.md` disagree, **`AGENTS.md` wins** update this projection in the same change.
4
+
5
+ ⚡ marks rules enforced by hand-maintained `code_rules_enforcer.py` — the hook blocks the Write/Edit and returns the corrective detail at violation time, so this document lists those rules by name only. Session policy (question routing, task tracking) lives in `rules/*.md`; see [`code-standards.md`](../rules/code-standards.md).
4
6
 
5
7
  ---
6
8
 
@@ -46,7 +48,7 @@ Full words only (`context`, not `ctx`). Exceptions: `i`/`j`/`k` in loops, `e` fo
46
48
 
47
49
  ## 6. COMPLETE TYPE HINTS
48
50
 
49
- ALL parameters typed, ALL returns typed. No `Any`, no `# type: ignore` (also enforced by the mypy_validator.py hook).
51
+ ALL parameters typed, ALL returns typed. No `Any`. A `# type: ignore` is allowed only with a second trailing `#` justification of at least five characters (e.g. `# type: ignore[misc] # stubs missing in foo library`); bare ignores are blocked by `code_rules_enforcer.py`. Prefer fixing the type over an ignore when a real annotation is available.
50
52
 
51
53
  ## 6.5 FILE LENGTH GUIDANCE
52
54
 
@@ -8,8 +8,8 @@ Pointer documents to external sources, standard terminology, and internal tool o
8
8
  |---|---|
9
9
  | `dead-code-elimination.md` | External sources and standard terms behind CODE_RULES §9.8 (remove code you orphan): DCE, tree shaking, reachability analysis, and the Lava Flow anti-pattern |
10
10
  | `code-review-enforcement.md` | How the code-review gates work: the two required efforts (push at low, PR creation at xhigh), the stamp bound to the branch-surface hash, the single sanctioned minter, the two-layer stamp-directory guard, and the bypass surfaces the gates leave open |
11
- | `advisor-tool.md` | The `advisor()` tool: a no-parameter review call that forwards the full conversation history to a stronger reviewer model, and when to call it |
12
- | `team-advisor-skill.md` | The `/team-advisor` skill: spawning a standing review agent at the strongest reachable tier, and how it relates to the `advisor()` tool |
11
+ | `advisor-tool.md` | Canonical consult bones for any stronger reviewer: when to call, hard rule before first write, how to treat advice; maps to the Anthropic advisor tool |
12
+ | `team-advisor-skill.md` | `/team-advisor` map: sole-consumer warm bind, ref index, and how it pairs with `advisor()` |
13
13
 
14
14
  ## Role
15
15