claude-code-conductor 0.2.0__tar.gz → 0.2.1__tar.gz

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 (60) hide show
  1. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/CHANGELOG.md +25 -0
  2. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/PKG-INFO +1 -1
  3. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/hatch_build.py +21 -22
  4. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/__init__.py +1 -1
  5. claude_code_conductor-0.2.1/src/c3/_excludes.py +43 -0
  6. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli_init.py +17 -3
  7. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli_update.py +4 -23
  8. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/paths.py +26 -14
  9. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/CLAUDE.md +0 -0
  10. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/architect.md +0 -0
  11. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/code-reviewer.md +0 -0
  12. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/developer.md +0 -0
  13. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/doc-writer.md +0 -0
  14. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/interviewer.md +0 -0
  15. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/planner.md +0 -0
  16. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/project-setup.md +0 -0
  17. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/security-reviewer.md +0 -0
  18. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/tdd-develop.md +0 -0
  19. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/agents/tester.md +0 -0
  20. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/develop.md +0 -0
  21. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/doc.md +0 -0
  22. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/extract-lib.md +0 -0
  23. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/init-session.md +0 -0
  24. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/mcp.md +0 -0
  25. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/promote-pattern.md +0 -0
  26. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/review.md +0 -0
  27. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/setup.md +0 -0
  28. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/commands/start.md +0 -0
  29. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/docs/parallel-orchestra-manifest.md +0 -0
  30. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/clear_file_history.py +0 -0
  31. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/enable_sandbox.py +0 -0
  32. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/pre_compact.py +0 -0
  33. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/pre_tool.py +0 -0
  34. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/statusline.py +0 -0
  35. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/stop.py +0 -0
  36. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/validate_skill_change.py +0 -0
  37. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/hooks/worktree_guard.py +0 -0
  38. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/memory/.gitkeep +0 -0
  39. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/rules/code-review-checklist.md +0 -0
  40. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/rules/promoted/index.md +0 -0
  41. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/rules/security-review-checklist.md +0 -0
  42. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/settings.json +0 -0
  43. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/settings.local.json +0 -0
  44. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/skills/dev-workflow.md +0 -0
  45. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/skills/parallel-execution.md +0 -0
  46. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/skills/promoted/index.md +0 -0
  47. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.claude/skills/worktree-tdd-workflow.md +0 -0
  48. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/.gitignore +0 -0
  49. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/LICENSE +0 -0
  50. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/README.md +0 -0
  51. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/pyproject.toml +0 -0
  52. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/__main__.py +0 -0
  53. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli.py +0 -0
  54. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli_doctor.py +0 -0
  55. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli_list.py +0 -0
  56. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/cli_po.py +0 -0
  57. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/po/__init__.py +0 -0
  58. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/po/detect.py +0 -0
  59. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/po/manifest.py +0 -0
  60. {claude_code_conductor-0.2.0 → claude_code_conductor-0.2.1}/src/c3/po/run.py +0 -0
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1] - 2026-05-01
4
+
5
+ ### Fixed
6
+ - `c3 init` no longer copies personal/working files when run against the live
7
+ development tree. Two regressions in 0.2.0 caused this:
8
+ 1. `templates_dir()` walked up from `__file__` looking for any ancestor with
9
+ `.claude/` + `pyproject.toml`. A wheel install in a venv that happened to
10
+ live inside the C3 source tree (e.g. `claude-code-conductor/.venv/...`)
11
+ therefore resolved to the dirty live `.claude/` instead of the bundled
12
+ `_template/`. The dev fallback is now anchored to `<root>/src/c3/` ancestry
13
+ so site-packages-loaded copies always use `importlib.resources`.
14
+ 2. `_copytree` did not apply the same exclusion rules as the build hook,
15
+ so even legitimate editable installs (which intentionally serve the live
16
+ `.claude/`) could leak personal files. `cli_init` and `cli_update` now
17
+ share `c3._excludes` with `hatch_build.py`.
18
+
19
+ ### Added
20
+ - `src/c3/_excludes.py` — single source of truth for excluded paths
21
+ (reports/, memory/sessions/, memory/patterns.json, docs/decisions.md, etc.).
22
+ - Regression tests:
23
+ - `tests/test_paths.py` — `_resolve_dev_template` rejects site-packages paths.
24
+ - `tests/test_excludes.py` — KEEP_PATTERNS override EXCLUDE_PATTERNS.
25
+ - `tests/test_cli_init.py::test_init_excludes_personal_files` — init does not
26
+ leak personal files even when given a "dirty" template tree.
27
+
3
28
  ## [0.2.0] - 2026-05-01
4
29
 
5
30
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-conductor
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Multi-agent orchestration framework for Claude Code (C3)
5
5
  Project-URL: Homepage, https://github.com/satoh-y-0323/claude-code-conductor
6
6
  Project-URL: Repository, https://github.com/satoh-y-0323/claude-code-conductor
@@ -17,25 +17,25 @@ from pathlib import Path
17
17
  from hatchling.builders.hooks.plugin.interface import BuildHookInterface
18
18
 
19
19
 
20
- # Files / directories that must NOT be redistributed (matched against paths
21
- # relative to the project root). Globs use fnmatch semantics.
20
+ # Patterns matched against paths relative to ``.claude/``.
21
+ # IMPORTANT: keep these in sync with ``src/c3/_excludes.py``. The build hook
22
+ # runs before the package is importable, so we duplicate rather than import.
22
23
  EXCLUDE_PATTERNS: tuple[str, ...] = (
23
- ".claude/reports/*",
24
- ".claude/memory/sessions/*",
25
- ".claude/memory/patterns.json",
26
- ".claude/memory/agent-audit.log",
27
- ".claude/tmp/*",
28
- ".claude/docs/decisions.md",
29
- ".claude/docs/taxonomy.md",
30
- ".claude/docs/game-studios-research.md",
24
+ "reports/*",
25
+ "memory/sessions/*",
26
+ "memory/patterns.json",
27
+ "memory/agent-audit.log",
28
+ "tmp/*",
29
+ "docs/decisions.md",
30
+ "docs/taxonomy.md",
31
+ "docs/game-studios-research.md",
31
32
  )
32
33
 
33
- # Patterns that should always survive even if their parent matches an exclude.
34
34
  KEEP_PATTERNS: tuple[str, ...] = (
35
- ".claude/reports/.gitkeep",
36
- ".claude/memory/.gitkeep",
37
- ".claude/memory/sessions/.gitkeep",
38
- ".claude/tmp/.gitkeep",
35
+ "reports/.gitkeep",
36
+ "memory/.gitkeep",
37
+ "memory/sessions/.gitkeep",
38
+ "tmp/.gitkeep",
39
39
  )
40
40
 
41
41
 
@@ -53,17 +53,16 @@ class StageTemplateHook(BuildHookInterface):
53
53
  if dest.exists():
54
54
  shutil.rmtree(dest)
55
55
  dest.mkdir(parents=True, exist_ok=True)
56
- _copy_filtered(source, dest, root)
56
+ _copy_filtered(source, dest, source)
57
57
 
58
58
 
59
- def _copy_filtered(src: Path, dst: Path, project_root: Path) -> None:
59
+ def _copy_filtered(src: Path, dst: Path, claude_root: Path) -> None:
60
60
  for entry in src.iterdir():
61
- rel = entry.relative_to(project_root).as_posix()
61
+ rel = entry.relative_to(claude_root).as_posix()
62
62
  if entry.is_dir():
63
63
  sub_dst = dst / entry.name
64
64
  sub_dst.mkdir(exist_ok=True)
65
- _copy_filtered(entry, sub_dst, project_root)
66
- # Drop any empty dirs that ended up with nothing to keep.
65
+ _copy_filtered(entry, sub_dst, claude_root)
67
66
  if not any(sub_dst.iterdir()):
68
67
  sub_dst.rmdir()
69
68
  elif entry.is_file():
@@ -73,6 +72,6 @@ def _copy_filtered(src: Path, dst: Path, project_root: Path) -> None:
73
72
 
74
73
 
75
74
  def _should_skip(rel: str) -> bool:
76
- if any(fnmatch.fnmatch(rel, p) for p in KEEP_PATTERNS):
75
+ if any(fnmatch.fnmatchcase(rel, p) for p in KEEP_PATTERNS):
77
76
  return False
78
- return any(fnmatch.fnmatch(rel, p) for p in EXCLUDE_PATTERNS)
77
+ return any(fnmatch.fnmatchcase(rel, p) for p in EXCLUDE_PATTERNS)
@@ -1,3 +1,3 @@
1
1
  """Claude Code Conductor (C3) - multi-agent orchestration framework for Claude Code."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -0,0 +1,43 @@
1
+ """Files inside ``.claude/`` that are personal/working state.
2
+
3
+ Used by:
4
+
5
+ - ``c3 init`` — never copied to the destination project
6
+ - ``c3 update`` — never overwritten in the destination project
7
+ - ``hatch_build.py`` — never bundled into the wheel template (the patterns are
8
+ duplicated there because the build hook runs *before* the package is
9
+ importable; keep both lists in sync)
10
+
11
+ Patterns are POSIX-style and relative to the ``.claude/`` directory itself
12
+ (e.g. ``"reports/*"``, not ``".claude/reports/*"``). ``KEEP_PATTERNS`` win
13
+ over ``EXCLUDE_PATTERNS`` so that placeholder ``.gitkeep`` files survive.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import fnmatch
19
+
20
+ EXCLUDE_PATTERNS: tuple[str, ...] = (
21
+ "reports/*",
22
+ "memory/sessions/*",
23
+ "memory/patterns.json",
24
+ "memory/agent-audit.log",
25
+ "tmp/*",
26
+ "docs/decisions.md",
27
+ "docs/taxonomy.md",
28
+ "docs/game-studios-research.md",
29
+ )
30
+
31
+ KEEP_PATTERNS: tuple[str, ...] = (
32
+ "reports/.gitkeep",
33
+ "memory/.gitkeep",
34
+ "memory/sessions/.gitkeep",
35
+ "tmp/.gitkeep",
36
+ )
37
+
38
+
39
+ def should_skip(rel_posix: str) -> bool:
40
+ """Return True if the path (relative to ``.claude/``) is personal state."""
41
+ if any(fnmatch.fnmatchcase(rel_posix, p) for p in KEEP_PATTERNS):
42
+ return False
43
+ return any(fnmatch.fnmatchcase(rel_posix, p) for p in EXCLUDE_PATTERNS)
@@ -7,6 +7,7 @@ import shutil
7
7
  import sys
8
8
  from pathlib import Path
9
9
 
10
+ from c3._excludes import should_skip
10
11
  from c3.paths import templates_dir
11
12
 
12
13
 
@@ -56,15 +57,28 @@ def handle(args: argparse.Namespace) -> int:
56
57
  return 0
57
58
 
58
59
 
59
- def _copytree(src: Path, dst: Path) -> int:
60
- """Copy src -> dst recursively; return number of regular files written."""
60
+ def _copytree(src: Path, dst: Path, *, root: Path | None = None) -> int:
61
+ """Copy ``src`` -> ``dst`` recursively, skipping personal/working files.
62
+
63
+ ``root`` defaults to ``src`` and represents the ``.claude/`` directory; the
64
+ relative path from ``root`` is what ``should_skip`` matches against.
65
+ Returns the number of regular files written.
66
+ """
67
+ if root is None:
68
+ root = src
61
69
  dst.mkdir(parents=True, exist_ok=True)
62
70
  count = 0
63
71
  for entry in src.iterdir():
72
+ rel = entry.relative_to(root).as_posix()
64
73
  target = dst / entry.name
65
74
  if entry.is_dir():
66
- count += _copytree(entry, target)
75
+ count += _copytree(entry, target, root=root)
76
+ # Drop directories that ended up empty (everything inside was skipped).
77
+ if not any(target.iterdir()):
78
+ target.rmdir()
67
79
  elif entry.is_file():
80
+ if should_skip(rel):
81
+ continue
68
82
  shutil.copy2(entry, target)
69
83
  count += 1
70
84
  return count
@@ -8,23 +8,9 @@ import shutil
8
8
  import sys
9
9
  from pathlib import Path
10
10
 
11
+ from c3._excludes import should_skip
11
12
  from c3.paths import templates_dir
12
13
 
13
- # Files that the user is expected to edit locally; never overwrite them.
14
- # These match the .gitignore entries used in the C3 source repo.
15
- _LOCAL_FILES: tuple[str, ...] = (
16
- "docs/decisions.md",
17
- "docs/taxonomy.md",
18
- "docs/game-studios-research.md",
19
- "memory/patterns.json",
20
- "memory/agent-audit.log",
21
- )
22
- _LOCAL_DIRS: tuple[str, ...] = (
23
- "memory/sessions",
24
- "reports",
25
- "tmp",
26
- )
27
-
28
14
 
29
15
  def register(subparsers: argparse._SubParsersAction) -> None:
30
16
  parser = subparsers.add_parser(
@@ -90,10 +76,12 @@ def _walk_diff(template: Path, dest: Path):
90
76
  """Yield (action, absolute_dest_path) tuples for files that differ.
91
77
 
92
78
  Only ``add`` and ``update`` are emitted; we never delete files in dest.
79
+ Personal/working files (per ``c3._excludes``) are skipped both as bundle
80
+ sources and as overwrite targets.
93
81
  """
94
82
  for src_file in _iter_files(template):
95
83
  rel = src_file.relative_to(template)
96
- if _is_local(rel):
84
+ if should_skip(rel.as_posix()):
97
85
  continue
98
86
  target = dest / rel
99
87
  if not target.exists():
@@ -108,10 +96,3 @@ def _iter_files(root: Path):
108
96
  yield from _iter_files(entry)
109
97
  elif entry.is_file():
110
98
  yield entry
111
-
112
-
113
- def _is_local(rel: Path) -> bool:
114
- rel_posix = rel.as_posix()
115
- if rel_posix in _LOCAL_FILES:
116
- return True
117
- return any(rel_posix == d or rel_posix.startswith(d + "/") for d in _LOCAL_DIRS)
@@ -30,24 +30,22 @@ def templates_dir() -> Path:
30
30
 
31
31
  Resolution order:
32
32
 
33
- 1. Dev source: walk up from this file looking for a sibling ``.claude/``
34
- next to a ``pyproject.toml``. This makes editable installs (``pip install
35
- -e .``) reflect live edits to the source ``.claude/`` without rebuilding.
36
- In a wheel-installed environment the ``.py`` files live under
37
- ``site-packages/`` and this lookup naturally returns no match.
38
- 2. Installed location: ``importlib.resources.files("c3") / "_template" / ".claude"``.
33
+ 1. Editable / source install: this module is being loaded directly from
34
+ ``<root>/src/c3/paths.py`` (i.e. via ``pip install -e .`` or
35
+ ``PYTHONPATH=src``) AND ``<root>/.claude/`` and ``<root>/pyproject.toml``
36
+ both exist. Use the live ``.claude/`` so dev edits are reflected
37
+ immediately. The check is anchored on the ``src/c3/`` ancestry so that a
38
+ venv that happens to live *inside* the C3 source tree does not trigger
39
+ this branch when a wheel-installed copy of the package is in use.
40
+ 2. Wheel install: ``importlib.resources.files("c3") / "_template" / ".claude"``.
39
41
  This is the path produced by hatchling's build hook + ``force-include``
40
42
  during ``pip install``.
41
43
 
42
- Raises ``FileNotFoundError`` if neither location exists. This usually means
43
- the package was built without the template (manually copied source tree) -
44
- reinstall via pip to fix.
44
+ Raises ``FileNotFoundError`` if neither location resolves.
45
45
  """
46
- here = Path(__file__).resolve()
47
- for parent in here.parents:
48
- candidate = parent / ".claude"
49
- if candidate.is_dir() and (parent / "pyproject.toml").is_file():
50
- return candidate
46
+ dev = _resolve_dev_template(Path(__file__).resolve())
47
+ if dev is not None:
48
+ return dev
51
49
 
52
50
  try:
53
51
  bundled = _resource_files("c3").joinpath("_template", ".claude")
@@ -62,3 +60,17 @@ def templates_dir() -> Path:
62
60
  "Reinstall claude-code-conductor with "
63
61
  "`pip install --force-reinstall claude-code-conductor`."
64
62
  )
63
+
64
+
65
+ def _resolve_dev_template(here: Path) -> Path | None:
66
+ """Return the live ``.claude/`` only when ``here`` is loaded from ``<root>/src/c3/``.
67
+
68
+ Split out so that the resolution logic is testable with synthetic paths.
69
+ """
70
+ if here.parent.name != "c3" or here.parent.parent.name != "src":
71
+ return None
72
+ project_root = here.parent.parent.parent
73
+ candidate = project_root / ".claude"
74
+ if candidate.is_dir() and (project_root / "pyproject.toml").is_file():
75
+ return candidate
76
+ return None