claude-dev-env 8.63.0 → 8.64.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.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: search-before-acting
3
+ description: >-
4
+ Search for what already does an ask before logging, planning, delegating,
5
+ or building it, then report what exists in four points and wait for the
6
+ user. Use when a user asks for new or changed behavior, a tool, a check,
7
+ a workflow, a report, a tracker entry, or a plan, and before writing a
8
+ brief for a worker.
9
+ ---
10
+
11
+ # Search before acting
12
+
13
+ The user should hear about work that already does their ask before anyone duplicates it. Search first, report what you find, and let the user decide what to add.
14
+
15
+ ## Steps
16
+
17
+ 1. Restate the ask as the outcome the user wants, in their words.
18
+ 2. Search every place in [`references/search-places.md`](references/search-places.md) that this session can reach. Note each place you could not reach.
19
+ 3. Sort each hit as full, partial, or related. A hit is full when it already produces the outcome from step 1.
20
+ 4. When any hit is full or partial, stop. Send the four-point report from [`references/report-shape.md`](references/report-shape.md) and wait for the user. When a hit is full, tell the user they already have it.
21
+ 5. When nothing is found, say where you looked in one line, then continue with the playbook below.
22
+
23
+ ## Playbooks
24
+
25
+ Open the playbook that matches the next move.
26
+
27
+ | Next move | Playbook |
28
+ |---|---|
29
+ | Build or change code, a hook, a skill, a rule, a workflow, or config | [`playbooks/build.md`](playbooks/build.md) |
30
+ | Log the ask in a tracker, or write a plan for it | [`playbooks/plan-or-log.md`](playbooks/plan-or-log.md) |
31
+ | Hand the ask to a worker, a thread, or another session | [`playbooks/delegate.md`](playbooks/delegate.md) |
32
+
33
+ ## Layout
34
+
35
+ | File | What it holds |
36
+ |---|---|
37
+ | `SKILL.md` | The steps and the playbook index |
38
+ | `references/search-places.md` | Where to search, with the query to run in each place |
39
+ | `references/report-shape.md` | The four-point report and the "you already have it" answer |
40
+ | `playbooks/build.md` | The search and report before a build, and the pull request section |
41
+ | `playbooks/plan-or-log.md` | The search and report before a tracker entry or plan |
42
+ | `playbooks/delegate.md` | The search before a brief, and what the brief carries |
@@ -0,0 +1,18 @@
1
+ # Build
2
+
3
+ Use this playbook before you build or change code, a hook, a skill, a rule, a workflow, or config for an ask.
4
+
5
+ 1. Run the skill steps. Search the places in [`../references/search-places.md`](../references/search-places.md), with production config and launcher options first when the ask names a running system.
6
+ 2. When a hit is full or partial, send the [report](../references/report-shape.md) and wait for the user's answer.
7
+ 3. When the user says go, build only the missing part from point 3 of the report. Extend the existing piece where it lives. Start a new piece only when the existing one cannot hold the addition, and say why in the pull request.
8
+ 4. When nothing exists, follow `prefer-existing-tools.md` to check open-source options, then build.
9
+ 5. Add an "Existing work" section to the pull request body. List each hit with its link or `file:line` and what the pull request adds on top of it. When nothing was found, say so and name the places searched. The pull request gate denies a body without this section.
10
+
11
+ Example section:
12
+
13
+ ```markdown
14
+ ## Existing work
15
+
16
+ - `scripts/run_batch.py:40` already runs jobs in parallel with `--workers`. This pull request adds a per-job timeout on top of it.
17
+ - Searched open and merged pull requests for "timeout" and "workers"; none add a timeout.
18
+ ```
@@ -0,0 +1,9 @@
1
+ # Delegate
2
+
3
+ Use this playbook before you hand an ask to a worker, a thread, or another session.
4
+
5
+ 1. Run the skill steps yourself before you write the brief. The worker starts from your search.
6
+ 2. When a hit is full or partial, send the user the [report](../references/report-shape.md) and wait. Delegate only after the user says go.
7
+ 3. Put the search result in the brief. Name each existing piece with its link or `file:line`, what it covers, and the missing part the worker builds.
8
+ 4. When nothing exists, put the places searched in the brief, so the worker skips them and searches what you could not reach.
9
+ 5. Ask the worker to report any existing piece it finds that the brief missed, and to stop on it before it builds.
@@ -0,0 +1,9 @@
1
+ # Plan or log
2
+
3
+ Use this playbook before you add an ask to a tracker, open an issue or card for it, or write a plan for it.
4
+
5
+ 1. Run the skill steps. Search open and closed tracker entries for the ask first, then the rest of [`../references/search-places.md`](../references/search-places.md).
6
+ 2. When a tracker entry already names the ask, update that entry and link it. Skip the new entry.
7
+ 3. When code, config, or a merged pull request already does the ask in full or in part, send the [report](../references/report-shape.md) and wait for the user before you log or plan.
8
+ 4. When the user says go, log or plan only the missing part. Link each existing piece in the entry or plan, so the reader starts from what is live.
9
+ 5. When nothing exists, write the entry or plan with one line that names the places searched.
@@ -0,0 +1,14 @@
1
+ # Report shape
2
+
3
+ Send this report when the search finds something that does the ask in full or in part. Send it before any log entry, plan, brief, or edit, and wait for the user.
4
+
5
+ 1. **What exists and where.** Each hit with a link or `file:line`, and whether it is live.
6
+ 2. **What it covers of the ask.** The parts of the outcome it already produces.
7
+ 3. **What is missing.** The parts of the outcome nothing produces yet.
8
+ 4. **What the addition would add.** What the new work gives on top of what exists, and the smallest change that closes the gap.
9
+
10
+ When nothing is missing, the report is one line: the user already has it, with the link and how to use it.
11
+
12
+ When a place in [`search-places.md`](search-places.md) was out of reach, add one line naming it and the check that would reach it.
13
+
14
+ Keep the report as short as the user's channel asks for. When the channel limits length, put the four points in a linked file and send a short message that says what exists, what is missing, and what the user decides.
@@ -0,0 +1,26 @@
1
+ # Search places
2
+
3
+ Search each place this session can reach. Start from the outcome words in the ask and from the names of the systems it touches. Try two or three phrasings, since an existing feature often uses different words than the ask, such as "workers" for "parallel" or "queue" for "batch".
4
+
5
+ | Place | What to run |
6
+ |---|---|
7
+ | Repository code | `rg -i "<outcome words>"` across each repository the work touches, and a search for the system's entry points and their flags, such as `--help` output and argument parsers |
8
+ | Open pull requests | `gh pr list --state open --search "<words>"`, or the GitHub search tool with `is:pr is:open` |
9
+ | Merged pull requests | `gh pr list --state merged --search "<words>"`, and `git log --oneline -i --grep "<words>"` on the default branch |
10
+ | Hooks | the hook registry, such as `hooks.json` or `settings.json`, and the hook directories, for a gate or reminder that already does the step |
11
+ | Rules and instructions | `rules/`, `AGENTS.md`, `CLAUDE.md`, project instructions, and memory notes |
12
+ | Skills and commands | the installed skills list, `skills/`, `.agents/skills/`, and `commands/` |
13
+ | Workflows and schedules | `.github/workflows/`, scheduled routines, cron entries, and task schedulers |
14
+ | Trackers | open and closed issues, project boards, and task cards that name the ask |
15
+ | Production config | config files, environment settings, feature flags, and launcher options that the running system reads, including values that turn on a mode the ask describes |
16
+ | Run logs and dashboards | recent run output that shows the behavior already happening |
17
+
18
+ ## What to record for each hit
19
+
20
+ - The place and a link or `file:line`.
21
+ - One sentence on what it does now.
22
+ - Whether it is live: merged and running, merged and unused, or still open.
23
+
24
+ ## When a place is out of reach
25
+
26
+ Name the place and the check that would reach it, such as "the production config on the build machine, read through a remote session". The report states the gap. The user may know the answer at once.
@@ -95,6 +95,7 @@ export const EVER_SHIPPED_SKILL_NAMES = new Set([
95
95
  'reviewer-gates',
96
96
  'reviews',
97
97
  'run-claude-dev-env',
98
+ 'search-before-acting',
98
99
  'second-account-workers',
99
100
  'session-continuity',
100
101
  'session-log',
@@ -45,6 +45,10 @@ test('EVER_SHIPPED_SKILL_NAMES includes the correction skill', () => {
45
45
  assert.equal(EVER_SHIPPED_SKILL_NAMES.has('correction'), true);
46
46
  });
47
47
 
48
+ test('EVER_SHIPPED_SKILL_NAMES includes the search before acting skill', () => {
49
+ assert.equal(EVER_SHIPPED_SKILL_NAMES.has('search-before-acting'), true);
50
+ });
51
+
48
52
  test('EVER_SHIPPED_SKILL_NAMES includes the windows scheduled task skill', () => {
49
53
  assert.equal(EVER_SHIPPED_SKILL_NAMES.has('windows-scheduled-task'), true);
50
54
  });
@@ -6,6 +6,16 @@ Source: [Anthropic - Overthinking and Excessive Thoroughness](https://platform.c
6
6
 
7
7
  Note: This deliberately chooses exploration depth over the "commit and execute quickly" pattern from the same source. Thorough upfront exploration is preferred for the intended workflow.
8
8
 
9
+ ## Search for existing work first
10
+
11
+ Before you log, plan, delegate, or build an ask, search for what already does it in full or in part. An ask is any request for new or changed behavior, a tool, a check, a workflow, a report, a tracker entry, or a plan. The `search-before-acting` skill holds the search places, the four-point report, and a playbook for each next move.
12
+
13
+ When the search finds something, stop before you act and report what exists and where, what it covers of the ask, what is missing, and what the addition would add. When it covers the whole ask, tell the user they already have it. When the search finds nothing, say where you looked and continue.
14
+
15
+ [`prefer-existing-tools.md`](../../rules/prefer-existing-tools.md) and [`build-needs-a-user.md`](../../rules/build-needs-a-user.md) load only when a session touches a script, hook, or manifest path. This rule loads in every session, so the search comes before the plan is set.
16
+
17
+ The pull request gate reads the body of each new pull request for an "Existing work" heading. Under it, list what the search found with a link or `file:line` and what the pull request adds on top of it, or state that nothing was found and name the places searched.
18
+
9
19
  ## Before committing to an approach
10
20
 
11
21
  - Read the relevant files. Understand what exists before proposing what to change.
@@ -53,7 +53,7 @@ from hooks_constants.hook_specific_output_keys import (
53
53
  from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
54
54
  from hooks_constants.setup_project_paths_constants import DECODE_ERRORS_POLICY, UTF8_ENCODING
55
55
  from blocking.followup_pr_dedupe import duplicate_followup_reason
56
- from blocking.pull_request_proof import missing_proof_reason
56
+ from blocking.pull_request_proof import missing_existing_work_reason, missing_proof_reason
57
57
  from transcript_skill_scan import is_skill_loaded_after_last_compaction
58
58
 
59
59
 
@@ -199,7 +199,7 @@ def _subagent_transcript_path(all_payload_fields: dict[str, object]) -> str | No
199
199
 
200
200
 
201
201
  def decision_for(all_payload_fields: dict[str, object]) -> dict[str, object] | None:
202
- """Return a deny for an unloaded skill, a new pull request with no proof, or a second follow-up.
202
+ """Return a deny for an unloaded skill, a new pull request with no proof or existing-work section, or a second follow-up.
203
203
 
204
204
  Args:
205
205
  all_payload_fields: The parsed PreToolUse input.
@@ -211,6 +211,9 @@ def decision_for(all_payload_fields: dict[str, object]) -> dict[str, object] | N
211
211
  proof_reason = missing_proof_reason(all_payload_fields)
212
212
  if proof_reason is not None:
213
213
  return _deny(proof_reason)
214
+ search_reason = missing_existing_work_reason(all_payload_fields)
215
+ if search_reason is not None:
216
+ return _deny(search_reason)
214
217
  duplicate_reason = duplicate_followup_reason(all_payload_fields)
215
218
  return None if duplicate_reason is None else _deny(duplicate_reason)
216
219
 
@@ -1,4 +1,4 @@
1
- """Deny a new pull request whose body carries no 'Proof in practice' section."""
1
+ """Deny a new pull request whose body lacks a 'Proof in practice' or an 'Existing work' section."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -18,8 +18,10 @@ from hooks_constants.pull_request_proof_constants import (
18
18
  ALL_PYTHON_PROGRAM_NAMES,
19
19
  COMMAND_MARKER,
20
20
  CREATE_PULL_REQUEST_TOOL_SUFFIX,
21
+ EXISTING_WORK_HEADING_PATTERN,
21
22
  LINE_SEPARATOR,
22
23
  MISSING_BODY_REASON,
24
+ MISSING_EXISTING_WORK_REASON,
23
25
  MISSING_PROOF_REASON,
24
26
  NEXT_HEADING_PATTERN,
25
27
  PROOF_HEADING_PATTERN,
@@ -48,6 +50,16 @@ def _lines_until_heading(all_lines: list[str], heading_level: int) -> list[str]:
48
50
  return all_lines
49
51
 
50
52
 
53
+ def _section_text(body: str, heading_pattern: str) -> str | None:
54
+ all_lines = body.splitlines()
55
+ for each_index, each_line in enumerate(all_lines):
56
+ heading_level = _heading_level(each_line, heading_pattern)
57
+ if heading_level is not None:
58
+ all_section_lines = _lines_until_heading(all_lines[each_index + 1 :], heading_level)
59
+ return LINE_SEPARATOR.join(all_section_lines).strip()
60
+ return None
61
+
62
+
51
63
  def proof_section(body: str) -> str | None:
52
64
  """Return the text under the body's 'Proof in practice' heading, or None.
53
65
 
@@ -58,13 +70,20 @@ def proof_section(body: str) -> str | None:
58
70
 
59
71
  The section runs to the next heading of the same or a higher level.
60
72
  """
61
- all_lines = body.splitlines()
62
- for each_index, each_line in enumerate(all_lines):
63
- heading_level = _heading_level(each_line, PROOF_HEADING_PATTERN)
64
- if heading_level is not None:
65
- all_section_lines = _lines_until_heading(all_lines[each_index + 1 :], heading_level)
66
- return LINE_SEPARATOR.join(all_section_lines).strip()
67
- return None
73
+ return _section_text(body, PROOF_HEADING_PATTERN)
74
+
75
+
76
+ def section_under_existing_work(body: str) -> str | None:
77
+ """Return the text under the body's 'Existing work' heading, or None.
78
+
79
+ ::
80
+
81
+ "## Existing work\\n`a.py:4` runs it\\n## Notes" -> "`a.py:4` runs it"
82
+ "## Summary\\nno existing work heading" -> None
83
+
84
+ The section runs to the next heading of the same or a higher level.
85
+ """
86
+ return _section_text(body, EXISTING_WORK_HEADING_PATTERN)
68
87
 
69
88
 
70
89
  def _script_arguments(program: str, all_arguments: list[str]) -> list[str] | None:
@@ -139,3 +158,26 @@ def missing_proof_reason(all_payload_fields: Mapping[str, object]) -> str | None
139
158
  if not section or COMMAND_MARKER not in section:
140
159
  return MISSING_PROOF_REASON
141
160
  return None
161
+
162
+
163
+ def missing_existing_work_reason(all_payload_fields: Mapping[str, object]) -> str | None:
164
+ """Return a deny reason when a new pull request's body has no existing-work section, else None.
165
+
166
+ ::
167
+
168
+ mcp__github__create_pull_request, body has the section -> None
169
+ mcp__github__create_pull_request, body without it -> MISSING_EXISTING_WORK_REASON
170
+ gh pr create --fill -> None
171
+ gh pr edit, git push, any other tool -> None
172
+
173
+ The section is an 'Existing work' heading with text under it. A create
174
+ command whose body this gate cannot read passes here, because
175
+ missing_proof_reason denies it first.
176
+
177
+ Args:
178
+ all_payload_fields: The parsed PreToolUse input.
179
+ """
180
+ for each_body in _create_bodies(all_payload_fields):
181
+ if each_body is not None and not section_under_existing_work(each_body):
182
+ return MISSING_EXISTING_WORK_REASON
183
+ return None
@@ -15,7 +15,10 @@ from blocking import pr_lifecycle_skill_gate as gate
15
15
  from hooks_constants.pr_lifecycle_skill_gate_constants import DENY_REASON
16
16
  from hooks_constants.pull_request_proof_constants import MISSING_PROOF_REASON
17
17
 
18
- PROVEN_FOLLOWUP_BODY = "Follow-up to #1731\n\n## Proof in practice\nRan `python probe.py`.\n"
18
+ PROVEN_FOLLOWUP_BODY = (
19
+ "Follow-up to #1731\n\n## Existing work\nNothing found in open or merged pull requests.\n\n"
20
+ "## Proof in practice\nRan `python probe.py`.\n"
21
+ )
19
22
 
20
23
 
21
24
  def _transcript(tmp_path: Path, skill_name: str | None = None, compact: bool = False) -> Path:
@@ -1,4 +1,4 @@
1
- """Behavior tests for the proof-in-practice check on a new pull request."""
1
+ """Behavior tests for the proof-in-practice and existing-work checks on a new pull request."""
2
2
 
3
3
  import sys
4
4
  from pathlib import Path
@@ -8,10 +8,19 @@ if str(HOOKS_DIRECTORY) not in sys.path:
8
8
  sys.path.insert(0, str(HOOKS_DIRECTORY))
9
9
 
10
10
  from blocking import pull_request_proof as gate
11
- from hooks_constants.pull_request_proof_constants import MISSING_BODY_REASON, MISSING_PROOF_REASON
11
+ from hooks_constants.pull_request_proof_constants import (
12
+ MISSING_BODY_REASON,
13
+ MISSING_EXISTING_WORK_REASON,
14
+ MISSING_PROOF_REASON,
15
+ )
12
16
 
13
17
  PROVEN_BODY = "## Summary\nAdds a gate.\n\n## Proof in practice\nRan `python run.py`.\n> denied\n"
14
18
  UNPROVEN_BODY = "## Summary\nAdds a gate.\n\n## Verification\nUnit tests pass.\n"
19
+ SEARCHED_BODY = (
20
+ "## Summary\nAdds a gate.\n\n## Existing work\n`gate.py:12` runs the check; this adds a flag.\n\n"
21
+ "## Proof in practice\nRan `python run.py`.\n"
22
+ )
23
+ EMPTY_EXISTING_WORK_BODY = "## Existing work\n\n## Proof in practice\nRan `python run.py`.\n"
15
24
 
16
25
 
17
26
  def _shell(command: str, working_directory: Path | None = None) -> dict[str, object]:
@@ -90,3 +99,37 @@ def test_other_pull_request_commands_should_pass() -> None:
90
99
  def test_other_tools_should_pass() -> None:
91
100
  payload = {"tool_name": "mcp__github__update_pull_request", "tool_input": {"body": "x"}}
92
101
  assert gate.missing_proof_reason(payload) is None
102
+
103
+
104
+ def test_existing_work_section_should_stop_at_a_heading_of_the_same_level() -> None:
105
+ assert gate.section_under_existing_work(SEARCHED_BODY) == "`gate.py:12` runs the check; this adds a flag."
106
+
107
+
108
+ def test_mcp_create_without_existing_work_should_be_denied() -> None:
109
+ assert gate.missing_existing_work_reason(_mcp_create(PROVEN_BODY)) == MISSING_EXISTING_WORK_REASON
110
+
111
+
112
+ def test_mcp_create_with_an_empty_existing_work_section_should_be_denied() -> None:
113
+ payload = _mcp_create(EMPTY_EXISTING_WORK_BODY)
114
+ assert gate.missing_existing_work_reason(payload) == MISSING_EXISTING_WORK_REASON
115
+
116
+
117
+ def test_mcp_create_with_existing_work_should_pass() -> None:
118
+ assert gate.missing_existing_work_reason(_mcp_create(SEARCHED_BODY)) is None
119
+
120
+
121
+ def test_gh_create_body_file_without_existing_work_should_be_denied(tmp_path: Path) -> None:
122
+ (tmp_path / "body.md").write_text(PROVEN_BODY, encoding="utf-8")
123
+ payload = _shell("gh pr create --draft --body-file body.md", tmp_path)
124
+ assert gate.missing_existing_work_reason(payload) == MISSING_EXISTING_WORK_REASON
125
+
126
+
127
+ def test_unreadable_body_should_leave_the_deny_to_the_proof_check() -> None:
128
+ payload = _shell("gh pr create --fill")
129
+ assert gate.missing_existing_work_reason(payload) is None
130
+ assert gate.missing_proof_reason(payload) == MISSING_BODY_REASON
131
+
132
+
133
+ def test_other_pull_request_commands_should_pass_the_existing_work_check() -> None:
134
+ for each_command in ("gh pr edit 3 --body x", "git push -u origin b", "gh pr view"):
135
+ assert gate.missing_existing_work_reason(_shell(each_command)) is None
@@ -25,3 +25,17 @@ MISSING_PROOF_REASON = (
25
25
  " prove the change, leave the pull request unopened and tell the user what you could"
26
26
  " not run. Read " + PROOF_GUIDE_PATH + " for the full rule."
27
27
  )
28
+ EXISTING_WORK_HEADING_PATTERN = r"^(#{1,6})[ \t]+existing work[ \t]*#*[ \t]*$"
29
+ EXISTING_WORK_GUIDE_PATH = "~/.claude/docs/rule-guides/explore-thoroughly.md"
30
+ MISSING_EXISTING_WORK_REASON = (
31
+ "This pull request body has no 'Existing work' section. Before you open a pull"
32
+ " request, search for what already does the change in full or in part: repository"
33
+ " code, open and merged pull requests, hooks, rules, skills, workflows, trackers and"
34
+ " production config. Add an 'Existing work' heading to the body. Under it, list each"
35
+ " piece you found with its link or file:line and what this pull request adds on top"
36
+ " of it, or state that nothing was found and name the places you searched. When the"
37
+ " search finds a piece that already does the change, stop and report it to the user"
38
+ " before you open the pull request. Run the search-before-acting skill and read "
39
+ + EXISTING_WORK_GUIDE_PATH
40
+ + " for the full rule."
41
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.63.0",
3
+ "version": "8.64.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,9 +1,9 @@
1
1
  # Explore thoroughly
2
2
 
3
- **When:** Choosing an implementation approach or recommending an architectural direction.
3
+ **When:** Before you log, plan, delegate, or build an ask, or choose an approach.
4
4
 
5
- Read relevant files, map local patterns, and identify constraints before committing to an approach. Scale exploration to risk: inspect nearby files for a small change, broader examples for a new feature, and the full landscape for an architectural decision. Once the files, constraints, and success condition are known, act without repeating settled research.
5
+ Search for what already does the ask, in full or in part. When something exists, stop and report it to the user in four points; run the `search-before-acting` skill. Then read files, patterns, and constraints, scaled to risk. Once the files, constraints, and success condition are known, act without repeating settled research.
6
6
 
7
- **Enforcement:** none, the agent applies it.
7
+ **Enforcement:** the pull request gate denies a body with no "Existing work" section.
8
8
 
9
- **Full text:** [`docs/rule-guides/explore-thoroughly.md`](../docs/rule-guides/explore-thoroughly.md). Read it when setting exploration depth.
9
+ **Full text:** [`docs/rule-guides/explore-thoroughly.md`](../docs/rule-guides/explore-thoroughly.md). Read it for search scope and depth.