claude-dev-env 2.14.0 → 2.15.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 (52) hide show
  1. package/AGENTS.md +106 -32
  2. package/agents/AGENTS.md +0 -5
  3. package/agents/test_agent_frontmatter.py +7 -7
  4. package/bin/AGENTS.md +6 -4
  5. package/bin/install-constants.mjs +51 -0
  6. package/bin/install.codex-rules.test.mjs +173 -0
  7. package/bin/install.cursor-rules.test.mjs +103 -0
  8. package/bin/install.mjs +130 -19
  9. package/bin/install.profile-root.test.mjs +8 -0
  10. package/bin/install.prune.test.mjs +2 -1
  11. package/bin/install.test.mjs +3 -3
  12. package/bin/install.transaction.test.mjs +1 -0
  13. package/bin/install.uninstall-transaction.test.mjs +1 -0
  14. package/bin/resolve-install-root.mjs +43 -10
  15. package/codex-rules/claude-dev-env.rules +12 -0
  16. package/commands/AGENTS.md +0 -10
  17. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +1 -1
  18. package/hooks/diagnostic/AGENTS.md +32 -0
  19. package/hooks/diagnostic/hook_log_init.py +2 -2
  20. package/output-styles/AGENTS.md +1 -1
  21. package/package.json +2 -1
  22. package/scripts/sync_to_cursor/AGENTS.md +3 -3
  23. package/scripts/sync_to_cursor/canonical_docs.py +11 -11
  24. package/scripts/sync_to_cursor/config/__init__.py +8 -0
  25. package/scripts/sync_to_cursor/engine.py +26 -1
  26. package/scripts/sync_to_cursor/rules.py +76 -5
  27. package/scripts/test_active_capability_references.py +2 -2
  28. package/scripts/tests/AGENTS.md +2 -0
  29. package/scripts/tests/test_engine.py +102 -0
  30. package/scripts/tests/test_rules.py +79 -0
  31. package/skills/anthropic-plan/AGENTS.md +1 -1
  32. package/skills/anthropic-plan/SKILL.md +1 -1
  33. package/skills/anthropic-plan/test_skill_contract.py +8 -6
  34. package/skills/prototype/SKILL.md +1 -2
  35. package/skills/prototype/reference/promotion-tasks.md +1 -1
  36. package/skills/prototype/workflows/promotion.md +1 -1
  37. package/agents/caveman.md +0 -73
  38. package/agents/clasp-deployment-orchestrator.md +0 -608
  39. package/agents/code-advisor.md +0 -23
  40. package/agents/deep-research.md +0 -152
  41. package/agents/docs-agent.md +0 -85
  42. package/commands/commit.md +0 -28
  43. package/commands/docupdate.md +0 -322
  44. package/commands/hook-log-extract.md +0 -70
  45. package/commands/hook-log-init.md +0 -76
  46. package/commands/implement.md +0 -102
  47. package/commands/plan.md +0 -14
  48. package/commands/pr-comments.md +0 -47
  49. package/commands/review-plan.md +0 -5
  50. package/commands/right-size.md +0 -15
  51. package/commands/sum.md +0 -30
  52. package/scripts/sync_to_cursor/config.py +0 -5
@@ -3,7 +3,7 @@
3
3
  import shutil
4
4
  from pathlib import Path
5
5
 
6
- from sync_to_cursor.config import CANONICAL_DOC_FILES
6
+ from sync_to_cursor.config import ALL_CANONICAL_DOC_FILES
7
7
  from sync_to_cursor.hashing import sha256_bytes
8
8
 
9
9
 
@@ -17,19 +17,19 @@ def sync_canonical_docs(
17
17
  if not dry_run:
18
18
  docs_out.mkdir(parents=True, exist_ok=True)
19
19
  new_docs: dict = {}
20
- for name in CANONICAL_DOC_FILES:
21
- src = claude / "docs" / name
22
- dst = docs_out / name
20
+ for each_name in ALL_CANONICAL_DOC_FILES:
21
+ src = claude / "docs" / each_name
22
+ dst = docs_out / each_name
23
23
  if not src.is_file():
24
24
  if dst.is_file():
25
25
  if not dry_run:
26
26
  dst.unlink()
27
27
  if not quiet:
28
- print(f"WARN docs/{name} (source removed — deleted stale copy at {dst})")
28
+ print(f"WARN docs/{each_name} (source removed — deleted stale copy at {dst})")
29
29
  elif not quiet:
30
- print(f"WARN docs/{name} (missing source: {src})")
30
+ print(f"WARN docs/{each_name} (missing source: {src})")
31
31
  continue
32
- key = f"docs/{name}"
32
+ key = f"docs/{each_name}"
33
33
  src_hash = sha256_bytes(src.read_bytes())
34
34
  if dry_run:
35
35
  if dst.is_file():
@@ -44,10 +44,10 @@ def sync_canonical_docs(
44
44
 
45
45
 
46
46
  def check_canonical_docs(claude: Path, cursor: Path, docs_entries: dict) -> bool:
47
- for name in CANONICAL_DOC_FILES:
48
- key = f"docs/{name}"
49
- src = claude / "docs" / name
50
- dst = cursor / "docs" / name
47
+ for each_name in ALL_CANONICAL_DOC_FILES:
48
+ key = f"docs/{each_name}"
49
+ src = claude / "docs" / each_name
50
+ dst = cursor / "docs" / each_name
51
51
  if not src.is_file():
52
52
  if key in docs_entries:
53
53
  return False
@@ -0,0 +1,8 @@
1
+ """Shared configuration for the sync-to-cursor package."""
2
+
3
+ GENERATOR_VERSION: str = "1.3.0"
4
+ ALL_CANONICAL_DOC_FILES: tuple[str, ...] = ("CODE_RULES.md", "TEST_QUALITY.md")
5
+ MAX_RULE_BODY_LINES: int = 50
6
+ ALL_SKIPPED_RULE_FILE_NAMES: frozenset[str] = frozenset({"CLAUDE.md", "AGENTS.md"})
7
+ MARKDOWN_SUFFIX: str = ".md"
8
+ CLAUDE_RULES_DIRECTORY_NAME: str = "rules"
@@ -140,15 +140,40 @@ def _sync_rules(
140
140
  return summary, new_entries
141
141
 
142
142
 
143
+ def _layout_from_args(args: argparse.Namespace) -> tuple[Path, Path, Path, Path]:
144
+ """Resolve Claude and Cursor roots from flags or the default layout.
145
+
146
+ Args:
147
+ args: Parsed CLI arguments.
148
+
149
+ Returns:
150
+ Claude root, Cursor root, rules output directory, and manifest path.
151
+
152
+ Raises:
153
+ SystemExit: When only one of `--claude-root` / `--cursor-root` is set.
154
+ """
155
+ claude_root = args.claude_root
156
+ cursor_root = args.cursor_root
157
+ if (claude_root is None) != (cursor_root is None):
158
+ raise SystemExit("both --claude-root and --cursor-root are required")
159
+ if claude_root is None:
160
+ return llm_layout_paths()
161
+ claude = Path(claude_root).expanduser().resolve()
162
+ cursor = Path(cursor_root).expanduser().resolve()
163
+ return claude, cursor, cursor / "rules", cursor / ".sync-manifest.json"
164
+
165
+
143
166
  def run(argv: list[str] | None = None) -> int:
144
167
  argument_parser = argparse.ArgumentParser(description="Sync Claude rules to Cursor .mdc files")
145
168
  argument_parser.add_argument("--force", action="store_true", help="Regenerate all outputs")
146
169
  argument_parser.add_argument("--dry-run", action="store_true", help="Print actions only")
147
170
  argument_parser.add_argument("--check", action="store_true", help="Exit 1 if anything stale")
148
171
  argument_parser.add_argument("--quiet", action="store_true", help="Minimal output when up to date")
172
+ argument_parser.add_argument("--claude-root", help="Claude layout root holding rules/ and docs/")
173
+ argument_parser.add_argument("--cursor-root", help="Cursor layout root receiving rules/ and docs/")
149
174
  args = argument_parser.parse_args(argv)
150
175
 
151
- claude, cursor, out_dir, manifest_path = llm_layout_paths()
176
+ claude, cursor, out_dir, manifest_path = _layout_from_args(args)
152
177
  mappings = build_mappings(claude)
153
178
  old_manifest = _load_manifest(manifest_path)
154
179
  entries_meta: dict = old_manifest.get("entries", {})
@@ -7,7 +7,12 @@ from dataclasses import dataclass
7
7
  from pathlib import Path
8
8
  from typing import Literal
9
9
 
10
- from sync_to_cursor.config import MAX_RULE_BODY_LINES
10
+ from sync_to_cursor.config import (
11
+ ALL_SKIPPED_RULE_FILE_NAMES,
12
+ CLAUDE_RULES_DIRECTORY_NAME,
13
+ MARKDOWN_SUFFIX,
14
+ MAX_RULE_BODY_LINES,
15
+ )
11
16
 
12
17
 
13
18
  def _parse_h2_sections(markdown: str) -> dict[str, str]:
@@ -412,6 +417,61 @@ def _path_scoped_mappings(rules_directory: Path, docs_directory: Path) -> tuple[
412
417
  )
413
418
 
414
419
 
420
+ def _description_from_rule_file(rule_file: Path, fallback_key: str) -> str:
421
+ """Return the first markdown heading, or the rule key with spaces.
422
+
423
+ Args:
424
+ rule_file: Claude rule markdown to read.
425
+ fallback_key: Stem used when the file has no heading.
426
+
427
+ Returns:
428
+ A one-line Cursor `description` value.
429
+ """
430
+ for each_line in rule_file.read_text(encoding="utf-8").splitlines():
431
+ stripped = each_line.strip()
432
+ if stripped.startswith("# "):
433
+ return stripped[2:].strip()
434
+ return fallback_key.replace("-", " ")
435
+
436
+
437
+ def _discovered_mappings(
438
+ rules_directory: Path, all_covered_source_names: set[str]
439
+ ) -> tuple[RuleMapping, ...]:
440
+ """Map remaining `*.md` rule files to `<stem>.mdc` Cursor rules.
441
+
442
+ Args:
443
+ rules_directory: Claude `rules/` directory.
444
+ all_covered_source_names: Rule filenames already claimed by curated mappings.
445
+
446
+ Returns:
447
+ One mapping per remaining markdown rule, sorted by filename.
448
+ """
449
+ if not rules_directory.is_dir():
450
+ return ()
451
+ all_discovered: list[RuleMapping] = []
452
+ for each_rule_file in sorted(rules_directory.glob("*.md")):
453
+ if each_rule_file.name in ALL_SKIPPED_RULE_FILE_NAMES:
454
+ continue
455
+ if each_rule_file.name in all_covered_source_names:
456
+ continue
457
+ key = each_rule_file.stem
458
+ paths_glob = _read_paths_glob(each_rule_file)
459
+ has_paths = paths_glob is not None
460
+ all_discovered.append(
461
+ RuleMapping(
462
+ key,
463
+ (each_rule_file,),
464
+ f"{key}.mdc",
465
+ not has_paths,
466
+ paths_glob,
467
+ _description_from_rule_file(each_rule_file, key),
468
+ "verbatim",
469
+ strip_leading_frontmatter=has_paths,
470
+ )
471
+ )
472
+ return tuple(all_discovered)
473
+
474
+
415
475
  def build_mappings(claude: Path) -> tuple[RuleMapping, ...]:
416
476
  """Resolve every rule into a concrete Cursor mapping against a Claude layout.
417
477
 
@@ -420,14 +480,25 @@ def build_mappings(claude: Path) -> tuple[RuleMapping, ...]:
420
480
 
421
481
  Returns:
422
482
  One RuleMapping per rule, each path-scoped rule carrying a glob derived
423
- from its source rule's `paths:` frontmatter.
483
+ from its source rule's `paths:` frontmatter. Curated mappings come first,
484
+ then one generated mapping per remaining `rules/*.md` file.
424
485
  """
425
486
  rules_directory = claude / "rules"
426
487
  docs_directory = claude / "docs"
427
- all_mappings = (
488
+ all_curated = (
428
489
  *_always_apply_mappings(rules_directory, docs_directory),
429
490
  *_path_scoped_mappings(rules_directory, docs_directory),
430
491
  )
431
- mapping_by_key = {each_mapping.key: each_mapping for each_mapping in all_mappings}
492
+ mapping_by_key = {each_mapping.key: each_mapping for each_mapping in all_curated}
432
493
  assert set(mapping_by_key) == set(_merged_mapping_key_order)
433
- return tuple(mapping_by_key[each_key] for each_key in _merged_mapping_key_order)
494
+ ordered_curated = tuple(mapping_by_key[each_key] for each_key in _merged_mapping_key_order)
495
+ all_covered_source_names = {
496
+ each_source.name
497
+ for each_mapping in ordered_curated
498
+ for each_source in each_mapping.sources
499
+ if each_source.suffix == MARKDOWN_SUFFIX
500
+ and each_source.parent.name == CLAUDE_RULES_DIRECTORY_NAME
501
+ }
502
+ return ordered_curated + _discovered_mappings(
503
+ rules_directory, all_covered_source_names
504
+ )
@@ -32,7 +32,7 @@ def test_inventory_includes_shipped_skill_and_agent_names() -> None:
32
32
 
33
33
  def test_strip_inert_fenced_blocks_drops_historical_examples() -> None:
34
34
  markdown = (
35
- "Use /commit for commits.\n"
35
+ "Use /sr-loop for cleanup.\n"
36
36
  "```historical\n"
37
37
  "Use /qbug for bugs.\n"
38
38
  "```\n"
@@ -40,7 +40,7 @@ def test_strip_inert_fenced_blocks_drops_historical_examples() -> None:
40
40
  )
41
41
  stripped = strip_inert_fenced_blocks(markdown)
42
42
  assert "/qbug" not in stripped
43
- assert "/commit" in stripped
43
+ assert "/sr-loop" in stripped
44
44
 
45
45
 
46
46
  def test_extract_active_capability_names_finds_slash_and_backticks() -> None:
@@ -10,6 +10,8 @@ pytest suite for the Python scripts and Pester suite for the PowerShell scripts
10
10
  | `test_setup_project_paths_config.py` | Configuration constants used by `setup_project_paths.py` |
11
11
  | `test_sweep_empty_dirs.py` | `sweep_empty_dirs.py` — age check, one-shot mode, and continuous-watch behavior |
12
12
  | `test_sync_to_cursor.py` | `sync_to_cursor/` package — mapping, hashing, manifest, and path resolution |
13
+ | `test_rules.py` | Discovered Claude `rules/*.md` mappings to stem-named Cursor `.mdc` files |
14
+ | `test_engine.py` | `sync_to_cursor` engine `--claude-root` / `--cursor-root` layout flags |
13
15
  | `test_grok_worker_constants.py` | `grok_worker_constants.py` — the accepted batch worker-key set stays in step with the worker key constants, and the unknown-key message names both its placeholders |
14
16
 
15
17
  ## PowerShell test files
@@ -0,0 +1,102 @@
1
+ """Tests for sync_to_cursor engine CLI roots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ _SCRIPTS_DIR = Path(__file__).resolve().parent.parent
11
+ if str(_SCRIPTS_DIR) not in sys.path:
12
+ sys.path.insert(0, str(_SCRIPTS_DIR))
13
+
14
+ from sync_to_cursor.engine import run as run_sync_to_cursor
15
+
16
+ _CODE_STANDARDS_SECTION_ORDER = (
17
+ "COMMENT PRESERVATION",
18
+ "CORE PRINCIPLES",
19
+ "⚡ HOOK-ENFORCED RULES",
20
+ "3. REUSE CONSTANTS / 4. CONFIG LOCATIONS",
21
+ "5. NO ABBREVIATIONS",
22
+ "6. COMPLETE TYPE HINTS",
23
+ "9. SELF-CONTAINED COMPONENTS",
24
+ )
25
+ _TEST_QUALITY_SECTION_ORDER = (
26
+ "Delete Useless Tests",
27
+ "Test Dependencies MUST FAIL",
28
+ "Core Testing Principles",
29
+ "React Testing Patterns",
30
+ "Test File Organization",
31
+ )
32
+
33
+
34
+ def _write_minimal_curated_rules(rules_directory: Path) -> None:
35
+ rules_directory.mkdir(parents=True, exist_ok=True)
36
+ (rules_directory / "code-standards.md").write_text(
37
+ "# Code standards stub\n", encoding="utf-8"
38
+ )
39
+ (rules_directory / "tasklings-preferences.md").write_text(
40
+ '---\npaths:\n - "Y:/x/**"\n---\n\n# Tasklings\n',
41
+ encoding="utf-8",
42
+ )
43
+ (rules_directory / "bdd.md").write_text("# BDD\n", encoding="utf-8")
44
+ (rules_directory / "testing.md").write_text(
45
+ '---\npaths:\n - "**/test_*.py"\n---\n\n# Testing\n',
46
+ encoding="utf-8",
47
+ )
48
+ (rules_directory / "research-mode.md").write_text("# RM\n", encoding="utf-8")
49
+ (rules_directory / "conservative-action.md").write_text("# CA\n", encoding="utf-8")
50
+ (rules_directory / "explore-thoroughly.md").write_text("# ET\n", encoding="utf-8")
51
+
52
+
53
+ def _write_minimal_docs(docs_directory: Path) -> None:
54
+ docs_directory.mkdir(parents=True, exist_ok=True)
55
+ (docs_directory / "CODE_RULES.md").write_text(
56
+ "\n\n".join(f"## {title}\n\nalpha" for title in _CODE_STANDARDS_SECTION_ORDER)
57
+ + "\n",
58
+ encoding="utf-8",
59
+ )
60
+ (docs_directory / "TEST_QUALITY.md").write_text(
61
+ "\n\n".join(f"## {title}\n\nbeta" for title in _TEST_QUALITY_SECTION_ORDER)
62
+ + "\n",
63
+ encoding="utf-8",
64
+ )
65
+
66
+
67
+ def test_explicit_roots_write_stem_named_mdc(
68
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
69
+ ) -> None:
70
+ claude = tmp_path / "claude-home"
71
+ cursor = tmp_path / "cursor-home"
72
+ _write_minimal_curated_rules(claude / "rules")
73
+ _write_minimal_docs(claude / "docs")
74
+ (claude / "rules" / "plain-language.md").write_text(
75
+ "# Plain language\n\nUse short sentences.\n",
76
+ encoding="utf-8",
77
+ )
78
+ (claude / "rules" / "CLAUDE.md").write_text("# Inventory\n", encoding="utf-8")
79
+ monkeypatch.delenv("LLM_SETTINGS_ROOT", raising=False)
80
+ assert cursor.exists() is False
81
+ assert (
82
+ run_sync_to_cursor(
83
+ [
84
+ "--force",
85
+ "--claude-root",
86
+ str(claude),
87
+ "--cursor-root",
88
+ str(cursor),
89
+ ]
90
+ )
91
+ == 0
92
+ )
93
+ generated = (cursor / "rules" / "plain-language.mdc").read_text(encoding="utf-8")
94
+ assert 'description: "Plain language"' in generated
95
+ assert "alwaysApply: true" in generated
96
+ assert "Use short sentences." in generated
97
+ assert not (cursor / "rules" / "CLAUDE.mdc").is_file()
98
+
99
+
100
+ def test_explicit_roots_require_both_flags(tmp_path: Path) -> None:
101
+ with pytest.raises(SystemExit):
102
+ run_sync_to_cursor(["--force", "--claude-root", str(tmp_path)])
@@ -0,0 +1,79 @@
1
+ """Tests for discovered Claude-to-Cursor rule mappings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _SCRIPTS_DIR = Path(__file__).resolve().parent.parent
9
+ if str(_SCRIPTS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(_SCRIPTS_DIR))
11
+
12
+ from sync_to_cursor.rules import build_mappings
13
+
14
+ _PACKAGE_ROOT = _SCRIPTS_DIR.parent
15
+ _SKIPPED_RULE_FILE_NAMES = frozenset({"CLAUDE.md", "AGENTS.md"})
16
+
17
+
18
+ def _write_minimal_curated_rules(rules_directory: Path) -> None:
19
+ rules_directory.mkdir(parents=True, exist_ok=True)
20
+ (rules_directory / "code-standards.md").write_text(
21
+ "# Code standards stub\n", encoding="utf-8"
22
+ )
23
+ (rules_directory / "tasklings-preferences.md").write_text(
24
+ '---\npaths:\n - "Y:/x/**"\n---\n\n# Tasklings\n',
25
+ encoding="utf-8",
26
+ )
27
+ (rules_directory / "bdd.md").write_text("# BDD\n", encoding="utf-8")
28
+ (rules_directory / "testing.md").write_text(
29
+ '---\npaths:\n - "**/test_*.py"\n---\n\n# Testing\n',
30
+ encoding="utf-8",
31
+ )
32
+ (rules_directory / "research-mode.md").write_text("# RM\n", encoding="utf-8")
33
+ (rules_directory / "conservative-action.md").write_text("# CA\n", encoding="utf-8")
34
+ (rules_directory / "explore-thoroughly.md").write_text("# ET\n", encoding="utf-8")
35
+
36
+
37
+ def test_build_mappings_emits_stem_mdc_for_remaining_claude_rules(
38
+ tmp_path: Path,
39
+ ) -> None:
40
+ claude = tmp_path / ".claude"
41
+ _write_minimal_curated_rules(claude / "rules")
42
+ (claude / "docs").mkdir(parents=True, exist_ok=True)
43
+ (claude / "rules" / "plain-language.md").write_text(
44
+ "# Plain language\n\nBe brief.\n",
45
+ encoding="utf-8",
46
+ )
47
+ (claude / "rules" / "CLAUDE.md").write_text(
48
+ "# Package inventory\n", encoding="utf-8"
49
+ )
50
+ (claude / "rules" / "AGENTS.md").write_text("# Agent inventory\n", encoding="utf-8")
51
+ mappings = build_mappings(claude)
52
+ output_by_key = {each_mapping.key: each_mapping for each_mapping in mappings}
53
+ discovered = output_by_key["plain-language"]
54
+ assert discovered.output_name == "plain-language.mdc"
55
+ assert discovered.always_apply is True
56
+ assert discovered.description == "Plain language"
57
+ assert "CLAUDE.md" not in {
58
+ each_source.name
59
+ for each_mapping in mappings
60
+ for each_source in each_mapping.sources
61
+ }
62
+ assert "AGENTS.md" not in {
63
+ each_source.name
64
+ for each_mapping in mappings
65
+ for each_source in each_mapping.sources
66
+ }
67
+
68
+
69
+ def test_every_shipped_claude_rule_maps_to_an_mdc() -> None:
70
+ mappings = build_mappings(_PACKAGE_ROOT)
71
+ output_name_by_rule_file = {}
72
+ for each_mapping in mappings:
73
+ for each_source in each_mapping.sources:
74
+ if each_source.parent.name == "rules" and each_source.suffix == ".md":
75
+ output_name_by_rule_file[each_source.name] = each_mapping.output_name
76
+ for each_rule_file in sorted((_PACKAGE_ROOT / "rules").glob("*.md")):
77
+ if each_rule_file.name in _SKIPPED_RULE_FILE_NAMES:
78
+ continue
79
+ assert each_rule_file.name in output_name_by_rule_file, each_rule_file.name
@@ -1,6 +1,6 @@
1
1
  # anthropic-plan
2
2
 
3
- **Trigger:** `/anthropic-plan`, `/plan`, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial requests that need source-grounded design before build work.
3
+ **Trigger:** `/anthropic-plan`, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial requests that need source-grounded design before build work.
4
4
 
5
5
  Creates a repo-local plan packet under `docs/plans/<slug>/` by running the `plan-packet.mjs` workflow. The skill first drafts a short starting plan and gets the user's approval in plan mode (`EnterPlanMode` / `ExitPlanMode`); on approval it runs the workflow. The packet holds context, spec, implementation steps, validation, and a handoff prompt for the build agent. The skill stops before any production code changes.
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: anthropic-plan
3
- description: Workflow-backed implementation planning that creates a deep repo-local packet under docs/plans/<slug>/ before any code changes. Use for /anthropic-plan, /plan, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial implementation requests that need source-grounded design, TDD steps, and validator approval before build work.
3
+ description: Workflow-backed implementation planning that creates a deep repo-local packet under docs/plans/<slug>/ before any code changes. Use for /anthropic-plan, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial implementation requests that need source-grounded design, TDD steps, and validator approval before build work.
4
4
  ---
5
5
 
6
6
  # Anthropic Plan
@@ -9,6 +9,7 @@ SKILL_DIRECTORY = Path(__file__).resolve().parent
9
9
  CLAUDE_DIRECTORY = SKILL_DIRECTORY.parent.parent
10
10
  SKILL_PATH = SKILL_DIRECTORY / "SKILL.md"
11
11
  PLAN_COMMAND_PATH = CLAUDE_DIRECTORY / "commands" / "plan.md"
12
+ SR_LOOP_COMMAND_PATH = CLAUDE_DIRECTORY / "commands" / "sr-loop.md"
12
13
  VALIDATOR_AGENT_PATH = CLAUDE_DIRECTORY / "agents" / "plan-packet-validator.md"
13
14
 
14
15
 
@@ -57,13 +58,14 @@ def test_skill_names_validator_and_stop_before_code_rules() -> None:
57
58
  assert "stop before implementation" in skill_text.lower()
58
59
 
59
60
 
60
- def test_plan_command_routes_to_anthropic_plan_without_stale_skills() -> None:
61
- command_text = PLAN_COMMAND_PATH.read_text(encoding="utf-8")
61
+ def test_skill_keeps_anthropic_plan_slash_without_plan_command() -> None:
62
+ skill_text = SKILL_PATH.read_text(encoding="utf-8")
62
63
 
63
- assert "anthropic-plan" in command_text
64
- assert "write-plan" not in command_text
65
- assert "review-plan" not in command_text
66
- assert "plan-executor" not in command_text
64
+ assert "/anthropic-plan" in skill_text
65
+ assert not PLAN_COMMAND_PATH.exists()
66
+ assert SR_LOOP_COMMAND_PATH.exists()
67
+ assert "write-plan" not in skill_text
68
+ assert "plan-executor" not in skill_text
67
69
 
68
70
 
69
71
  def test_validator_agent_exists_and_is_read_only() -> None:
@@ -45,7 +45,7 @@ Follow `workflows/sandbox.md`. In short:
45
45
 
46
46
  ### Phase 2 — Promotion
47
47
 
48
- Run only in the normal, fully-hooked session — never inside the sandbox. Follow `workflows/promotion.md`, which drives the clean-room task seeds in `reference/promotion-tasks.md`: fresh branch off live `origin/main`, POC content as an uncommitted diff, cleanup and privacy sweep, review and verification under the [review guide](../reviews/SKILL.md#review-workflow), then `/commit` and a draft PR handed to a PR-loop skill. State the two honest limitations from `reference/honest-limitations.md`.
48
+ Run only in the normal, fully-hooked session — never inside the sandbox. Follow `workflows/promotion.md`, which drives the clean-room task seeds in `reference/promotion-tasks.md`: fresh branch off live `origin/main`, POC content as an uncommitted diff, cleanup and privacy sweep, review and verification under the [review guide](../reviews/SKILL.md#review-workflow), then commit by hand per `git-workflow` and a draft PR handed to a PR-loop skill. State the two honest limitations from `reference/honest-limitations.md`.
49
49
 
50
50
  ## Task seeding
51
51
 
@@ -58,7 +58,6 @@ At the start of Phase 2, register every item in `reference/promotion-tasks.md` a
58
58
  | `fresh-branch` | Sandbox step 1; Promotion step 2 | isolated worktree JSON (`worktree_path`, `base_commit`, `repo_root`) | Refuse — see refusal cases |
59
59
  | `privacy-hygiene` | Promotion step 5 | personal-data and secret sweep of the diff | Warn; do a manual review before continuing |
60
60
  | [Review guide](../reviews/SKILL.md#review-workflow) | Promotion step 6 | review and verification of the real diff | Stop and report the incomplete promotion |
61
- | `/commit` (command) | Promotion step 7 | conventional commit + push | Commit and push by hand per `git-workflow` |
62
61
  | `autoconverge` (default; `pr-converge` or `bugteam` as alternatives) | Promotion step 9 | the PR converged to ready | Stop after the draft PR; tell the user to converge manually |
63
62
 
64
63
  ## Degree of freedom
@@ -16,7 +16,7 @@ Promotion runs in the **normal, fully-hooked session** — never inside the `--b
16
16
 
17
17
  6. **Review and verify the real diff.** Apply the [review guide](../../reviews/SKILL.md#review-workflow). Evidence: the checks run and each required finding repaired. Do not rely on sandbox testing as promotion evidence.
18
18
 
19
- 7. **Commit and open a draft PR.** After the review and verification record is complete, run `/commit`, then open a draft PR per the `git-workflow` rule. Evidence: the commit hash and the PR URL.
19
+ 7. **Commit and open a draft PR.** After the review and verification record is complete, commit by hand per the `git-workflow` rule, then open a draft PR. Evidence: the commit hash and the PR URL.
20
20
 
21
21
  8. **State the honest limitations.** Post the two statements from `reference/honest-limitations.md` — write-time rules never ran; TDD ordering waived — in the PR body or to the user. Evidence: the text was included.
22
22
 
@@ -16,7 +16,7 @@ The task seeds carry the full ordered detail. The shape:
16
16
  4. **Cleanup.** Remove scratch files, debug dumps, and temp helpers the POC created (`cleanup-temp-files` rule).
17
17
  5. **Privacy sweep** via `privacy-hygiene` over the diff.
18
18
  6. **Review and verify** the real diff against the [review guide](../../reviews/SKILL.md#review-workflow). Record the checks run and repair every required finding.
19
- 7. **Commit and PR.** After the review and verification record is complete, run `/commit`, then open a draft PR per the `git-workflow` rule.
19
+ 7. **Commit and PR.** After the review and verification record is complete, commit by hand per the `git-workflow` rule, then open a draft PR.
20
20
  8. **State the honest limitations** from `reference/honest-limitations.md` in the PR body or to the user.
21
21
  9. **Converge** by handing the PR to `autoconverge` by default; use `pr-converge` for paced ticks or `bugteam` for an open-loop audit.
22
22
 
package/agents/caveman.md DELETED
@@ -1,73 +0,0 @@
1
- ---
2
- name: caveman
3
- description: Trims noise from an artifact the main caller has already authored. Input is a draft (skill, doc, plan, response, README, prompt, PR description) — output is the same artifact with filler, hedging, preamble, recap, and restatement removed. Preserves structure, technical substance, frontmatter, and anything load-bearing. Does NOT redesign, restructure, or overrule the caller's scope decisions.
4
- color: red
5
- ---
6
-
7
- You are the caveman. You trim. You do not build. You do not restructure.
8
-
9
- ## What you are
10
-
11
- A noise filter. The main caller has already decided *what* the artifact is, *how* it is structured, and *what lives in it*. Your job is to strip fluff off that artifact without touching the bones.
12
-
13
- You are downstream of design decisions, not upstream.
14
-
15
- ## What you trim
16
-
17
- | Noise type | Example |
18
- |---|---|
19
- | Preamble / recap | "As discussed above, this skill will..." |
20
- | Hedging | "This might, in some cases, potentially..." |
21
- | Filler transitions | "Now, moving on to..." / "It's worth noting that..." |
22
- | Restatement | the same point made twice in different words |
23
- | Empty future-proofing | parameters, sections, or fields with no current consumer |
24
- | Dead examples | examples that duplicate another example without adding coverage |
25
- | Pleasantries | "Hope this helps." / "Feel free to..." |
26
- | Vague qualifiers | "various", "several", "a number of" — replace with the actual count or cut |
27
-
28
- Rewrite prose into the caveman pattern only where it does not change meaning: `[thing] [action] [reason]. [next step].`
29
-
30
- ## What you do NOT touch
31
-
32
- - **Structure the caller chose.** Four sections in, four sections out. Do not collapse or merge.
33
- - **Frontmatter fields.** All fields stay. Tighten values if verbose; do not drop fields.
34
- - **Technical substance.** Code, commands, paths, URLs, errors, JSON, schema — unchanged.
35
- - **Trigger words / activation phrases.** Load-bearing for skill matching.
36
- - **Safety / escape-hatch language.** Warnings about destructive ops, irreversible actions, credentials, money, production systems — preserve verbatim.
37
- - **Caller-flagged content.** If the caller said "keep X verbatim", X is untouchable.
38
- - **Counts and specifics.** Numbers, thresholds, version strings, identifiers — unchanged.
39
- - **Register in examples and docstrings.** Unless caller asked for caveman voice throughout, keep the original register of user-facing copy.
40
-
41
- ## What you do NOT decide
42
-
43
- You do not tell the caller:
44
- - "Use the existing tool instead" — design call, caller's call.
45
- - "Make this one file instead of three" — structure call, caller's call.
46
- - "Drop this section" — scope call, caller's call.
47
- - "Add tests" / "remove tests" — scope call, caller's call.
48
-
49
- If you suspect a section is pure noise, flag it in the report. Leave it in place unless the caller told you to remove it.
50
-
51
- ## Process
52
-
53
- 1. Read the artifact end to end before touching it.
54
- 2. Mark the bones — frontmatter, structure, technical substance, trigger words, safety language. Off-limits.
55
- 3. Trim noise per the table above.
56
- 4. Return the trimmed artifact in the caller's original file format.
57
-
58
- ## Output shape
59
-
60
- ```
61
- trimmed: <path or artifact name>
62
- removed: <bullets — noise categories cut, with rough line counts>
63
- preserved-verbatim: <what you refused to touch and why>
64
- flagged: <content you suspect is noise but left in place for caller to decide>
65
- ```
66
-
67
- No recap of the artifact itself. Caller has it.
68
-
69
- ## Escape hatch
70
-
71
- If trimming would drop a safety warning, remove an irreversible-action caveat, collapse a distinction the caller made deliberately, or if you are unsure whether content is load-bearing — leave it in place and flag it. Ask before cutting.
72
-
73
- Terse is for noise, not for substance.