claude-dev-env 1.55.1 → 1.55.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -41,6 +41,15 @@ Ask the subagent for a specific answer: "return the file:line where X is defined
41
41
 
42
42
  Reserve `Read`/`Grep`/`Glob` for files you will actually touch this turn. Compose subagent prompts via the protocol in `agent-spawn-protocol`.
43
43
 
44
+ ## Target Execution Workflow for Code Tasks
45
+
46
+ Run every multi-step code task in two phases:
47
+
48
+ 1. **Coders** — one Sonnet agent per scoped assignment writes the code. A coder that hits a decision it can't reasonably solve consults the tool-less `fable-advisor` agent — which returns a plan, a correction, or a stop signal — and resumes. Source: Anthropic's advisor strategy (https://claude.com/blog/the-advisor-strategy).
49
+ 2. **Verification** — when the coders finish, the main session spawns the `fable-verifier` agent in a fresh context. It derives and runs the checks itself rather than trusting coder reports: the task's named gates, tests against baselines recorded before the coders ran, and a two-way diff-vs-assignment reading (every task item maps to a hunk, every hunk maps to a task item, nothing missing). A finding must cite a failing command or a named task item. Source: the fresh-context review step in Claude Code best practices (https://code.claude.com/docs/en/best-practices) — the agent doing the work isn't the one grading it.
50
+
51
+ Repair agents run only on reported findings; the verifier re-checks after each repair. Work lands (commit, push, draft PR) only on a clean verdict — enforced by the `verified_commit_gate` hook, which blocks `git commit`/`git push` unless a hook-minted verdict covers the current branch diff. The one exemption is mechanical, not discretionary: a diff whose every changed file is non-code or has an unchanged Python AST once docstrings are stripped (docs, docstrings, comments).
52
+
44
53
  ## Additional Non-overlapping Rules
45
54
 
46
55
  - **task_scope:** Match every action to what was explicitly requested. When intent is ambiguous, research official docs and present options via AskUserQuestion before making any changes. Proceed with edits only on explicit instruction.
@@ -21,12 +21,10 @@ from hooks_constants.md_to_html_blocker_constants import ( # noqa: E402
21
21
  ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS,
22
22
  ALL_EXEMPT_ROOT_FILENAMES_LOWER,
23
23
  CLAUDE_DEV_ENV_REPO_NAME_SEGMENT,
24
- CLAUDE_DIRECTORY_PATH_PREFIX,
25
- CLAUDE_DIRECTORY_SEGMENT_MARKER,
24
+ CLAUDE_DIRECTORY_NAME,
25
+ CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX,
26
26
  MINIMUM_SEGMENT_COUNT_TO_MATCH_INDICATOR,
27
27
  PACKAGES_TOP_LEVEL_SEGMENT,
28
- PLUGIN_DIRECTORY_PATH_PREFIX,
29
- PLUGIN_DIRECTORY_SEGMENT_MARKER,
30
28
  PLUGIN_ROOT_MARKER_DIRECTORY_NAME,
31
29
  REPO_ROOT_MARKER_NAME,
32
30
  RESOLVED_HOME_DIRECTORY_LOWER,
@@ -38,7 +36,9 @@ def is_exempt_path(file_path: str) -> bool:
38
36
  """Return True when the .md file path is exempt from the blocker policy.
39
37
 
40
38
  Exemption sources, in order of evaluation:
41
- - Any segment under `.claude/` or `.claude-plugin/` (case-insensitive)
39
+ - Any directory segment named `.claude` or prefixed `.claude-`
40
+ (case-insensitive): project infrastructure, profile directories like
41
+ `.claude-mel/`, and `.claude-plugin/`
42
42
  - Basename in `ALL_EXEMPT_ANYWHERE_FILENAMES` (e.g. SKILL.md)
43
43
  - Anchored under `packages/claude-dev-env/<one of
44
44
  ALL_CLAUDE_CODE_SOURCE_TOP_DIRECTORIES>/...` (docs, rules,
@@ -60,15 +60,7 @@ def is_exempt_path(file_path: str) -> bool:
60
60
  expanded_path = os.path.expanduser(file_path)
61
61
  normalized = os.path.normpath(expanded_path).replace("\\", "/")
62
62
  lower_normalized = normalized.lower()
63
- if (
64
- CLAUDE_DIRECTORY_SEGMENT_MARKER in lower_normalized
65
- or lower_normalized.startswith(CLAUDE_DIRECTORY_PATH_PREFIX)
66
- ):
67
- return True
68
- if (
69
- PLUGIN_DIRECTORY_SEGMENT_MARKER in lower_normalized
70
- or lower_normalized.startswith(PLUGIN_DIRECTORY_PATH_PREFIX)
71
- ):
63
+ if _has_claude_infrastructure_segment(lower_normalized):
72
64
  return True
73
65
  basename_lower = os.path.basename(normalized).lower()
74
66
  if basename_lower in ALL_EXEMPT_ANYWHERE_FILENAMES_LOWER:
@@ -101,6 +93,33 @@ def _resolve_absolute_directory(normalized_path: str) -> str:
101
93
  return os.path.abspath(directory)
102
94
 
103
95
 
96
+ def _has_claude_infrastructure_segment(lower_normalized_path: str) -> bool:
97
+ """A directory named `.claude` or prefixed `.claude-` (profile and
98
+ plugin directories) holds Claude infrastructure; any path inside one
99
+ is exempt.
100
+
101
+ Only directory segments are matched. The final segment is always the
102
+ file's basename (``normpath`` strips any trailing slash), so a file
103
+ merely named with the ``.claude-`` prefix in an ordinary directory
104
+ stays subject to the policy.
105
+
106
+ Args:
107
+ lower_normalized_path: Lowercased path with separators normalized
108
+ to forward slashes.
109
+
110
+ Returns:
111
+ True when any directory segment names a Claude infrastructure
112
+ directory.
113
+ """
114
+ all_directory_segments = lower_normalized_path.split("/")[:-1]
115
+ for each_segment in all_directory_segments:
116
+ if each_segment == CLAUDE_DIRECTORY_NAME:
117
+ return True
118
+ if each_segment.startswith(CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX):
119
+ return True
120
+ return False
121
+
122
+
104
123
  def _has_plugin_directory_segment(lower_normalized_path: str) -> bool:
105
124
  for each_directory_segment in ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS:
106
125
  segment_marker = f"/{each_directory_segment}/"
@@ -25,6 +25,7 @@ from hooks_constants.md_to_html_blocker_constants import ( # noqa: E402
25
25
  ALL_EXEMPT_PLUGIN_DIRECTORY_SEGMENTS,
26
26
  CLAUDE_DEV_ENV_REPO_NAME_SEGMENT,
27
27
  CLAUDE_DIRECTORY_NAME,
28
+ CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX,
28
29
  PACKAGES_TOP_LEVEL_SEGMENT,
29
30
  PLUGIN_ROOT_MARKER_DIRECTORY_NAME,
30
31
  )
@@ -63,7 +64,7 @@ def _block_context() -> str:
63
64
  "Reference for HTML effectiveness patterns:\n"
64
65
  f"{_html_effectiveness_url}\n"
65
66
  "Exceptions (.md still allowed):\n"
66
- f"- Files inside {CLAUDE_DIRECTORY_NAME}/ or {PLUGIN_ROOT_MARKER_DIRECTORY_NAME}/ directories\n"
67
+ f"- Files inside {CLAUDE_DIRECTORY_NAME}/ or {CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX}*/ directories\n"
67
68
  f"- {_exempt_anywhere_filenames_summary} anywhere\n"
68
69
  f"- Files under {_exempt_plugin_segments_summary} directories\n"
69
70
  f"- Files under {_claude_dev_env_source_directories_summary} source directories\n"
@@ -79,7 +80,7 @@ def _block_system_message() -> str:
79
80
  ".md files are blocked in this project — generate a self-contained .html "
80
81
  f"file instead. See {_html_effectiveness_url} for "
81
82
  f"design patterns and examples. Exemptions: {CLAUDE_DIRECTORY_NAME}/ and "
82
- f"{PLUGIN_ROOT_MARKER_DIRECTORY_NAME}/ infrastructure, "
83
+ f"{CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX}*/ infrastructure, "
83
84
  f"{_exempt_anywhere_filenames_summary} anywhere, {_exempt_plugin_segments_summary} trees, "
84
85
  f"{_claude_dev_env_source_directories_summary} source trees, "
85
86
  f"files under a {PLUGIN_ROOT_MARKER_DIRECTORY_NAME}/ root, "
@@ -1,7 +1,8 @@
1
1
  """Tests for md_to_html_blocker directory and filename exemptions.
2
2
 
3
- Covers which directory trees (`.claude/`, `.claude-plugin/`, source subtrees
4
- under `packages/claude-dev-env/`, `agents/`, `skills/`, `commands/`) and which
3
+ Covers which directory trees (`.claude/`, `.claude-*/` profile and plugin
4
+ directories, source subtrees under `packages/claude-dev-env/`, `agents/`,
5
+ `skills/`, `commands/`) and which
5
6
  root-level filenames (`README.md`, `CHANGELOG.md`, `CLAUDE.md`, `AGENTS.md`,
6
7
  `SKILL.md`) are exempt from the `.md` block, and the segment-anchored matching
7
8
  that prevents nested look-alike paths from bypassing the block.
@@ -366,3 +367,68 @@ def test_blocks_ordinary_docs_md_file():
366
367
  assert result.returncode == 0
367
368
  output = json.loads(result.stdout)
368
369
  assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
370
+
371
+
372
+ def test_passes_claude_profile_memory_directory():
373
+ """A Claude profile directory (`.claude-<name>/`, e.g. `.claude-mel/`)
374
+ carries the same infrastructure as `.claude/`; per-project memory files
375
+ under it accept .md writes."""
376
+ result = _run_hook(
377
+ "Write",
378
+ {
379
+ "file_path": (
380
+ "C:/Users/sample/.claude-mel/projects"
381
+ "/sample-project/memory/fact.md"
382
+ ),
383
+ "content": "# Fact",
384
+ },
385
+ )
386
+ assert result.returncode == 0
387
+ assert result.stdout == ""
388
+
389
+
390
+ def test_passes_relative_claude_profile_directory():
391
+ result = _run_hook(
392
+ "Write",
393
+ {
394
+ "file_path": ".claude-mel/projects/sample/memory/fact.md",
395
+ "content": "# Fact",
396
+ },
397
+ )
398
+ assert result.returncode == 0
399
+ assert result.stdout == ""
400
+
401
+
402
+ def test_passes_claude_profile_directory_case_insensitive():
403
+ result = _run_hook(
404
+ "Write",
405
+ {"file_path": "C:/Users/sample/.Claude-Mel/MEMORY.md", "content": "# Index"},
406
+ )
407
+ assert result.returncode == 0
408
+ assert result.stdout == ""
409
+
410
+
411
+ def test_blocks_dot_directory_that_starts_with_claude_but_lacks_hyphen():
412
+ """`.claudette/` is not Claude infrastructure: only a directory named
413
+ exactly `.claude` or carrying the `.claude-` prefix is exempt."""
414
+ result = _run_hook(
415
+ "Write",
416
+ {"file_path": "notes/.claudette/intro.md", "content": "# Intro"},
417
+ )
418
+ assert result.returncode == 0
419
+ output = json.loads(result.stdout)
420
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
421
+
422
+
423
+ def test_blocks_claude_prefixed_filename_in_plain_directory():
424
+ """A file merely named with the `.claude-` prefix (e.g.
425
+ `docs/.claude-notes.md`) is not Claude infrastructure: the exemption
426
+ matches directory segments only, so a `.claude-*.md` basename inside
427
+ an ordinary directory is blocked."""
428
+ result = _run_hook(
429
+ "Write",
430
+ {"file_path": "docs/.claude-notes.md", "content": "# Notes"},
431
+ )
432
+ assert result.returncode == 0
433
+ output = json.loads(result.stdout)
434
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
@@ -24,10 +24,7 @@ REPO_ROOT_MARKER_NAME: str = ".git"
24
24
  CLAUDE_DIRECTORY_NAME: str = ".claude"
25
25
  PLUGIN_ROOT_MARKER_DIRECTORY_NAME: str = ".claude-plugin"
26
26
 
27
- CLAUDE_DIRECTORY_SEGMENT_MARKER: str = f"/{CLAUDE_DIRECTORY_NAME}/"
28
- CLAUDE_DIRECTORY_PATH_PREFIX: str = f"{CLAUDE_DIRECTORY_NAME}/"
29
- PLUGIN_DIRECTORY_SEGMENT_MARKER: str = f"/{PLUGIN_ROOT_MARKER_DIRECTORY_NAME}/"
30
- PLUGIN_DIRECTORY_PATH_PREFIX: str = f"{PLUGIN_ROOT_MARKER_DIRECTORY_NAME}/"
27
+ CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX: str = f"{CLAUDE_DIRECTORY_NAME}-"
31
28
 
32
29
  ALL_EXEMPT_ANYWHERE_FILENAMES_LOWER: frozenset[str] = frozenset(
33
30
  each_filename.lower() for each_filename in ALL_EXEMPT_ANYWHERE_FILENAMES
@@ -68,12 +65,9 @@ __all__ = [
68
65
  "ALL_EXEMPT_ROOT_FILENAMES_LOWER",
69
66
  "CLAUDE_DEV_ENV_REPO_NAME_SEGMENT",
70
67
  "CLAUDE_DIRECTORY_NAME",
71
- "CLAUDE_DIRECTORY_PATH_PREFIX",
72
- "CLAUDE_DIRECTORY_SEGMENT_MARKER",
68
+ "CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX",
73
69
  "MINIMUM_SEGMENT_COUNT_TO_MATCH_INDICATOR",
74
70
  "PACKAGES_TOP_LEVEL_SEGMENT",
75
- "PLUGIN_DIRECTORY_PATH_PREFIX",
76
- "PLUGIN_DIRECTORY_SEGMENT_MARKER",
77
71
  "PLUGIN_ROOT_MARKER_DIRECTORY_NAME",
78
72
  "REPO_ROOT_MARKER_NAME",
79
73
  "RESOLVED_HOME_DIRECTORY_LOWER",
@@ -115,3 +115,11 @@ def test_plugin_root_marker_directory_name_is_dot_claude_plugin() -> None:
115
115
  a plugin repo root and exempted."""
116
116
  assert constants_module.PLUGIN_ROOT_MARKER_DIRECTORY_NAME == ".claude-plugin"
117
117
  assert "PLUGIN_ROOT_MARKER_DIRECTORY_NAME" in constants_module.__all__
118
+
119
+
120
+ def test_claude_profile_directory_name_prefix_is_dot_claude_hyphen() -> None:
121
+ """A directory whose name carries the `.claude-` prefix (profile
122
+ directories like `.claude-mel/`, plus `.claude-plugin/`) is Claude
123
+ infrastructure; any path inside one bypasses the .md block."""
124
+ assert constants_module.CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX == ".claude-"
125
+ assert "CLAUDE_PROFILE_DIRECTORY_NAME_PREFIX" in constants_module.__all__
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.55.1",
3
+ "version": "1.55.2",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {