claude-code-kit 0.7.0__py3-none-any.whl

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 (209) hide show
  1. claude_code_kit-0.7.0.dist-info/METADATA +384 -0
  2. claude_code_kit-0.7.0.dist-info/RECORD +209 -0
  3. claude_code_kit-0.7.0.dist-info/WHEEL +4 -0
  4. claude_code_kit-0.7.0.dist-info/entry_points.txt +4 -0
  5. claude_code_kit-0.7.0.dist-info/licenses/LICENSE +21 -0
  6. claude_kit/__init__.py +10 -0
  7. claude_kit/__main__.py +8 -0
  8. claude_kit/_payload/agents/acceptance-reviewer.md +60 -0
  9. claude_kit/_payload/agents/auditor.md +76 -0
  10. claude_kit/_payload/agents/dependency-scanner.md +84 -0
  11. claude_kit/_payload/agents/developer.md +187 -0
  12. claude_kit/_payload/agents/devils-advocate.md +62 -0
  13. claude_kit/_payload/agents/devops-engineer.md +134 -0
  14. claude_kit/_payload/agents/e2e-tester.md +152 -0
  15. claude_kit/_payload/agents/em-reviewer.md +105 -0
  16. claude_kit/_payload/agents/incident-responder.md +64 -0
  17. claude_kit/_payload/agents/merge-reviewer.md +194 -0
  18. claude_kit/_payload/agents/observability-engineer.md +94 -0
  19. claude_kit/_payload/agents/orchestrator.md +551 -0
  20. claude_kit/_payload/agents/owasp-reviewer.md +76 -0
  21. claude_kit/_payload/agents/policy-validator.md +63 -0
  22. claude_kit/_payload/agents/pr-raiser.md +138 -0
  23. claude_kit/_payload/agents/risk-classifier.md +50 -0
  24. claude_kit/_payload/agents/sdlc-code-reviewer.md +196 -0
  25. claude_kit/_payload/agents/secret-scanner.md +70 -0
  26. claude_kit/_payload/agents/security-reviewer.md +80 -0
  27. claude_kit/_payload/agents/senior-backend-dev.md +199 -0
  28. claude_kit/_payload/agents/senior-frontend-dev.md +181 -0
  29. claude_kit/_payload/agents/senior-tester.md +206 -0
  30. claude_kit/_payload/agents/spec-doc-writer.md +331 -0
  31. claude_kit/_payload/agents/story-planner.md +56 -0
  32. claude_kit/_payload/agents/technical-architect.md +139 -0
  33. claude_kit/_payload/agents/tester.md +193 -0
  34. claude_kit/_payload/agents/ui-designer.md +73 -0
  35. claude_kit/_payload/agents/unit-tester.md +119 -0
  36. claude_kit/_payload/catalog/mcp.yaml +54 -0
  37. claude_kit/_payload/catalog/org.yaml +145 -0
  38. claude_kit/_payload/catalog/profiles.yaml +96 -0
  39. claude_kit/_payload/catalog/stacks.yaml +96 -0
  40. claude_kit/_payload/commands/init.md +36 -0
  41. claude_kit/_payload/commands/sdlc.md +18 -0
  42. claude_kit/_payload/commands/status.md +20 -0
  43. claude_kit/_payload/hooks/hooks.json +58 -0
  44. claude_kit/_payload/hooks/scripts/audit-log.sh +18 -0
  45. claude_kit/_payload/hooks/scripts/guard-secrets.sh +26 -0
  46. claude_kit/_payload/hooks/scripts/lint-fix.sh +38 -0
  47. claude_kit/_payload/hooks/scripts/load-continuity.sh +32 -0
  48. claude_kit/_payload/hooks/scripts/load-learnings.sh +40 -0
  49. claude_kit/_payload/hooks/scripts/type-check.sh +23 -0
  50. claude_kit/_payload/hooks/scripts/validate-frontmatter.sh +34 -0
  51. claude_kit/_payload/hooks/scripts/validate-settings.sh +21 -0
  52. claude_kit/_payload/hooks/scripts/warn-large-edits.sh +24 -0
  53. claude_kit/_payload/hooks/scripts/warn-missing-tests.sh +24 -0
  54. claude_kit/_payload/hooks/scripts/warn-sensitive-files.sh +30 -0
  55. claude_kit/_payload/hooks/scripts/warn-shared-modules.sh +33 -0
  56. claude_kit/_payload/rules/agent-guardrails.md +83 -0
  57. claude_kit/_payload/rules/agent-memory.md +106 -0
  58. claude_kit/_payload/rules/agent-resilience.md +61 -0
  59. claude_kit/_payload/rules/autonomy-levels.md +30 -0
  60. claude_kit/_payload/rules/code-organization.md +312 -0
  61. claude_kit/_payload/rules/continuity.md +84 -0
  62. claude_kit/_payload/rules/design-patterns.md +422 -0
  63. claude_kit/_payload/rules/devops-observability.md +57 -0
  64. claude_kit/_payload/rules/documentation.md +326 -0
  65. claude_kit/_payload/rules/evals.md +62 -0
  66. claude_kit/_payload/rules/frontend-best-practices.md +157 -0
  67. claude_kit/_payload/rules/goal-setting-and-monitoring.md +72 -0
  68. claude_kit/_payload/rules/human-in-the-loop.md +64 -0
  69. claude_kit/_payload/rules/linting-and-formatting.md +220 -0
  70. claude_kit/_payload/rules/mandatory-workflow.md +309 -0
  71. claude_kit/_payload/rules/model-tiers.md +34 -0
  72. claude_kit/_payload/rules/quality-gates.md +107 -0
  73. claude_kit/_payload/rules/rarv-cycle.md +31 -0
  74. claude_kit/_payload/rules/reasoning-techniques.md +62 -0
  75. claude_kit/_payload/rules/responsive-and-accessibility.md +353 -0
  76. claude_kit/_payload/rules/risk-classification.md +36 -0
  77. claude_kit/_payload/rules/testing.md +417 -0
  78. claude_kit/_payload/rules/tool-design.md +66 -0
  79. claude_kit/_payload/skills/_references/accessibility-checklist.md +160 -0
  80. claude_kit/_payload/skills/_references/orchestration-patterns.md +405 -0
  81. claude_kit/_payload/skills/_references/performance-checklist.md +153 -0
  82. claude_kit/_payload/skills/_references/security-checklist.md +134 -0
  83. claude_kit/_payload/skills/_references/testing-patterns.md +236 -0
  84. claude_kit/_payload/skills/accessibility-review/SKILL.md +56 -0
  85. claude_kit/_payload/skills/api-and-interface-design/SKILL.md +294 -0
  86. claude_kit/_payload/skills/api-integration/SKILL.md +348 -0
  87. claude_kit/_payload/skills/archive-sprint/SKILL.md +31 -0
  88. claude_kit/_payload/skills/backlog/SKILL.md +41 -0
  89. claude_kit/_payload/skills/backlog/item-template.md +20 -0
  90. claude_kit/_payload/skills/browser-testing-with-devtools/SKILL.md +302 -0
  91. claude_kit/_payload/skills/ci-cd-and-automation/SKILL.md +402 -0
  92. claude_kit/_payload/skills/code-review-and-quality/SKILL.md +347 -0
  93. claude_kit/_payload/skills/code-simplification/SKILL.md +331 -0
  94. claude_kit/_payload/skills/component-design/SKILL.md +171 -0
  95. claude_kit/_payload/skills/consolidate-learnings/SKILL.md +55 -0
  96. claude_kit/_payload/skills/context-engineering/SKILL.md +321 -0
  97. claude_kit/_payload/skills/debugging-and-error-recovery/SKILL.md +300 -0
  98. claude_kit/_payload/skills/decision/SKILL.md +46 -0
  99. claude_kit/_payload/skills/decision/adr-template.md +36 -0
  100. claude_kit/_payload/skills/deprecation-and-migration/SKILL.md +207 -0
  101. claude_kit/_payload/skills/documentation-and-adrs/SKILL.md +299 -0
  102. claude_kit/_payload/skills/doubt-driven-development/SKILL.md +243 -0
  103. claude_kit/_payload/skills/execute/SKILL.md +27 -0
  104. claude_kit/_payload/skills/frontend-ui-engineering/SKILL.md +328 -0
  105. claude_kit/_payload/skills/git-workflow-and-versioning/SKILL.md +300 -0
  106. claude_kit/_payload/skills/idea-refine/SKILL.md +178 -0
  107. claude_kit/_payload/skills/idea-refine/examples.md +238 -0
  108. claude_kit/_payload/skills/idea-refine/frameworks.md +99 -0
  109. claude_kit/_payload/skills/idea-refine/refinement-criteria.md +113 -0
  110. claude_kit/_payload/skills/idea-refine/scripts/idea-refine.sh +15 -0
  111. claude_kit/_payload/skills/incident-postmortem/SKILL.md +74 -0
  112. claude_kit/_payload/skills/incremental-implementation/SKILL.md +245 -0
  113. claude_kit/_payload/skills/interview-me/SKILL.md +221 -0
  114. claude_kit/_payload/skills/load-testing/SKILL.md +83 -0
  115. claude_kit/_payload/skills/manual-test/SKILL.md +516 -0
  116. claude_kit/_payload/skills/performance-optimization/SKILL.md +277 -0
  117. claude_kit/_payload/skills/planning-and-task-breakdown/SKILL.md +223 -0
  118. claude_kit/_payload/skills/playwright-verification/SKILL.md +205 -0
  119. claude_kit/_payload/skills/refresh-docs/SKILL.md +63 -0
  120. claude_kit/_payload/skills/remember/SKILL.md +96 -0
  121. claude_kit/_payload/skills/scope/SKILL.md +52 -0
  122. claude_kit/_payload/skills/scope/scope-template.md +82 -0
  123. claude_kit/_payload/skills/sdlc/SKILL.md +83 -0
  124. claude_kit/_payload/skills/security-and-hardening/SKILL.md +368 -0
  125. claude_kit/_payload/skills/security-verification/SKILL.md +209 -0
  126. claude_kit/_payload/skills/shipping-and-launch/SKILL.md +309 -0
  127. claude_kit/_payload/skills/smoke-test/SKILL.md +78 -0
  128. claude_kit/_payload/skills/source-driven-development/SKILL.md +195 -0
  129. claude_kit/_payload/skills/spec-driven-development/SKILL.md +200 -0
  130. claude_kit/_payload/skills/sprint/SKILL.md +67 -0
  131. claude_kit/_payload/skills/sprint/sprint-template.md +90 -0
  132. claude_kit/_payload/skills/test-driven-development/SKILL.md +383 -0
  133. claude_kit/_payload/skills/threat-model/SKILL.md +60 -0
  134. claude_kit/_payload/skills/triage/SKILL.md +87 -0
  135. claude_kit/_payload/skills/ui-ux-design/SKILL.md +71 -0
  136. claude_kit/_payload/skills/unit-test/SKILL.md +237 -0
  137. claude_kit/_payload/skills/using-agent-skills/SKILL.md +180 -0
  138. claude_kit/_payload/templates/CLAUDE.md +238 -0
  139. claude_kit/_payload/templates/CLAUDE.stack.md.tmpl +53 -0
  140. claude_kit/_payload/templates/CONTINUITY.template.md +35 -0
  141. claude_kit/_payload/templates/README.claude-sdlc.md.tmpl +219 -0
  142. claude_kit/_payload/templates/agent-memory/MEMORY.md +30 -0
  143. claude_kit/_payload/templates/agent-memory/api/.gitkeep +0 -0
  144. claude_kit/_payload/templates/agent-memory/architecture/.gitkeep +0 -0
  145. claude_kit/_payload/templates/agent-memory/debugging/.gitkeep +0 -0
  146. claude_kit/_payload/templates/agent-memory/gotchas/.gitkeep +0 -0
  147. claude_kit/_payload/templates/agent-memory/patterns/.gitkeep +0 -0
  148. claude_kit/_payload/templates/agent-memory/performance/.gitkeep +0 -0
  149. claude_kit/_payload/templates/artifacts/adr.md +18 -0
  150. claude_kit/_payload/templates/artifacts/feature-spec.md +29 -0
  151. claude_kit/_payload/templates/artifacts/release-plan.md +23 -0
  152. claude_kit/_payload/templates/artifacts/runbook.md +24 -0
  153. claude_kit/_payload/templates/artifacts/security-review.md +23 -0
  154. claude_kit/_payload/templates/artifacts/test-plan.md +22 -0
  155. claude_kit/_payload/templates/org/README.md +53 -0
  156. claude_kit/_payload/templates/org/agents/data-workflow-agent.md +59 -0
  157. claude_kit/_payload/templates/org/agents/founder-prototype-agent.md +61 -0
  158. claude_kit/_payload/templates/org/agents/internal-tools-builder.md +63 -0
  159. claude_kit/_payload/templates/org/agents/pm-copilot.md +60 -0
  160. claude_kit/_payload/templates/org/agents/support-ticket-engineer.md +63 -0
  161. claude_kit/_payload/templates/org/packs/devops-and-release/README.md +46 -0
  162. claude_kit/_payload/templates/org/packs/devops-and-release/pack.yaml +32 -0
  163. claude_kit/_payload/templates/org/packs/engineering-core/README.md +46 -0
  164. claude_kit/_payload/templates/org/packs/engineering-core/pack.yaml +44 -0
  165. claude_kit/_payload/templates/org/packs/non-engineer-builder/README.md +53 -0
  166. claude_kit/_payload/templates/org/packs/non-engineer-builder/pack.yaml +39 -0
  167. claude_kit/_payload/templates/org/packs/onboarding-and-docs/README.md +49 -0
  168. claude_kit/_payload/templates/org/packs/onboarding-and-docs/pack.yaml +26 -0
  169. claude_kit/_payload/templates/org/packs/product-to-code/README.md +50 -0
  170. claude_kit/_payload/templates/org/packs/product-to-code/pack.yaml +34 -0
  171. claude_kit/_payload/templates/org/packs/quality-and-review/README.md +53 -0
  172. claude_kit/_payload/templates/org/packs/quality-and-review/pack.yaml +40 -0
  173. claude_kit/_payload/templates/org/packs/security-and-compliance/README.md +50 -0
  174. claude_kit/_payload/templates/org/packs/security-and-compliance/pack.yaml +36 -0
  175. claude_kit/_payload/templates/org/rules/ai-working-agreement.md +45 -0
  176. claude_kit/_payload/templates/org/rules/ambiguity-resolution.md +36 -0
  177. claude_kit/_payload/templates/org/rules/branch-and-pr-policy.md +41 -0
  178. claude_kit/_payload/templates/org/rules/compliance-policy.md +50 -0
  179. claude_kit/_payload/templates/org/rules/non-engineer-safe-coding.md +37 -0
  180. claude_kit/_payload/templates/org/rules/pii-policy.md +46 -0
  181. claude_kit/_payload/templates/org/rules/production-data-policy.md +35 -0
  182. claude_kit/_payload/templates/org/rules/prompt-to-task-conversion.md +30 -0
  183. claude_kit/_payload/templates/org/rules/prototype-boundaries.md +40 -0
  184. claude_kit/_payload/templates/org/rules/secrets-policy.md +34 -0
  185. claude_kit/_payload/templates/org/skills/customer-issue-to-fix/SKILL.md +61 -0
  186. claude_kit/_payload/templates/org/skills/feature-from-idea/SKILL.md +56 -0
  187. claude_kit/_payload/templates/org/skills/prompt-to-safe-task/SKILL.md +59 -0
  188. claude_kit/_payload/templates/org/skills/prototype-to-production/SKILL.md +61 -0
  189. claude_kit/_payload/templates/org/skills/repo-onboarding/SKILL.md +60 -0
  190. claude_kit/_payload/templates/settings.json +53 -0
  191. claude_kit/_payload/templates/stacks/backend/python/fastapi/rules/fastapi-patterns.md +64 -0
  192. claude_kit/_payload/templates/stacks/db/mongodb/agents/migration-specialist.md +61 -0
  193. claude_kit/_payload/templates/stacks/db/mongodb/agents/mongodb-specialist.md +59 -0
  194. claude_kit/_payload/templates/stacks/db/mongodb/rules/mongodb-patterns.md +39 -0
  195. claude_kit/_payload/templates/stacks/db/postgres/agents/db-performance-reviewer.md +66 -0
  196. claude_kit/_payload/templates/stacks/db/postgres/agents/migration-specialist.md +56 -0
  197. claude_kit/_payload/templates/stacks/db/postgres/agents/postgres-specialist.md +58 -0
  198. claude_kit/_payload/templates/stacks/db/postgres/rules/database-performance.md +64 -0
  199. claude_kit/_payload/templates/stacks/db/postgres/rules/postgres-patterns.md +43 -0
  200. claude_kit/_payload/templates/stacks/frontend/react/rules/react-patterns.md +63 -0
  201. claude_kit/catalog.py +476 -0
  202. claude_kit/cli.py +327 -0
  203. claude_kit/hooks.py +246 -0
  204. claude_kit/models.py +205 -0
  205. claude_kit/prompts.py +209 -0
  206. claude_kit/render.py +146 -0
  207. claude_kit/scaffold.py +492 -0
  208. claude_kit/upgrader.py +294 -0
  209. claude_kit/validator.py +197 -0
claude_kit/scaffold.py ADDED
@@ -0,0 +1,492 @@
1
+ """Installer — writes a resolved claude-kit configuration into a target project.
2
+
3
+ Given a :class:`~claude_kit.models.ResolvedPlan` (from :func:`claude_kit.catalog.resolve`), this
4
+ module copies the profile's agent/skill/hook **subset**, the core rules, the selected stack
5
+ **overlay** rules + agents, assembles ``.claude/settings.json`` from the chosen hooks, optionally
6
+ writes ``.mcp.json``, installs artifact templates and a tuned ``CLAUDE.md`` + ``README.claude-sdlc.md``,
7
+ creates gitignored runtime dirs, and records per-file checksums in ``.claude/config/init-options.json``
8
+ for safe upgrades. It writes **no application code and no Docker** — configuration only.
9
+
10
+ ``install_sdlc`` is the single spine shared by the pip CLI and (via a thin fallback) the plugin.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import shutil
18
+ from contextlib import ExitStack
19
+ from importlib.resources import as_file, files
20
+ from pathlib import Path
21
+
22
+ import yaml
23
+
24
+ from claude_kit import __version__, hooks as hooks_mod
25
+ from claude_kit.models import FileRecord, InitOptions, ResolvedPlan
26
+ from claude_kit.render import render_text
27
+
28
+ #: Marker in the generic CLAUDE.md whose section is replaced with the stack-specific block.
29
+ _STACK_MARKER = "## Project-specific rules"
30
+
31
+ #: Selective .gitignore entries for a scaffolded project (commit the rest of .claude/).
32
+ GITIGNORE_ENTRIES = (
33
+ ".claude/settings.local.json",
34
+ "CLAUDE.local.md",
35
+ ".claude/state/",
36
+ ".claude/tmp/",
37
+ )
38
+
39
+
40
+ def payload_dir(stack: ExitStack) -> Path:
41
+ """Return a real filesystem path to the bundled payload directory.
42
+
43
+ Resolution order: (1) the bundled ``claude_kit/_payload`` (installed/built package); (2) the
44
+ repository root (two levels above this file) when running from a source checkout.
45
+
46
+ Args:
47
+ stack: An ``ExitStack`` keeping any temporary extraction alive for the caller's scope.
48
+
49
+ Returns:
50
+ Path to the payload root containing ``rules/ agents/ skills/ hooks/ templates/ catalog/``.
51
+
52
+ Raises:
53
+ FileNotFoundError: If no payload can be located by either route.
54
+ """
55
+ try:
56
+ resource = files("claude_kit").joinpath("_payload")
57
+ path = Path(stack.enter_context(as_file(resource)))
58
+ if path.is_dir():
59
+ return path
60
+ except (FileNotFoundError, ModuleNotFoundError, NotADirectoryError):
61
+ pass
62
+
63
+ repo_root = Path(__file__).resolve().parents[2]
64
+ if (repo_root / "rules").is_dir() and (repo_root / "catalog").is_dir():
65
+ return repo_root
66
+
67
+ raise FileNotFoundError(
68
+ "claude-kit payload not found — the package was built without its data files."
69
+ )
70
+
71
+
72
+ # --- small fs helpers ------------------------------------------------------------------------------
73
+
74
+
75
+ def _copy_tree(src: Path, dest: Path) -> None:
76
+ """Replace ``dest`` with a copy of ``src`` (directory)."""
77
+ if dest.exists():
78
+ shutil.rmtree(dest)
79
+ shutil.copytree(src, dest)
80
+
81
+
82
+ def _copy_user_file(
83
+ src: Path, dest: Path, *, force: bool, log: list[str], label: str
84
+ ) -> None:
85
+ """Copy a user-editable file, writing a ``.claude-kit`` sidecar instead of clobbering edits."""
86
+ dest.parent.mkdir(parents=True, exist_ok=True)
87
+ if dest.exists() and not force:
88
+ sidecar = dest.with_name(dest.name + ".claude-kit")
89
+ shutil.copy2(src, sidecar)
90
+ log.append(
91
+ f" • {label} exists — wrote {sidecar.name} (use --force to overwrite)"
92
+ )
93
+ else:
94
+ shutil.copy2(src, dest)
95
+ log.append(f" • {label} installed")
96
+
97
+
98
+ def _write_user_text(
99
+ dest: Path, text: str, *, force: bool, log: list[str], label: str
100
+ ) -> None:
101
+ """Write rendered text to a user-editable file, sidecar'ing instead of clobbering edits."""
102
+ dest.parent.mkdir(parents=True, exist_ok=True)
103
+ if dest.exists() and not force:
104
+ sidecar = dest.with_name(dest.name + ".claude-kit")
105
+ sidecar.write_text(text, encoding="utf-8")
106
+ log.append(
107
+ f" • {label} exists — wrote {sidecar.name} (use --force to overwrite)"
108
+ )
109
+ else:
110
+ dest.write_text(text, encoding="utf-8")
111
+ log.append(f" • {label} installed")
112
+
113
+
114
+ def _sha256(path: Path) -> str:
115
+ """Return the hex SHA-256 of a file's bytes."""
116
+ h = hashlib.sha256()
117
+ h.update(path.read_bytes())
118
+ return h.hexdigest()
119
+
120
+
121
+ def _find_overlay(
122
+ src: Path, stack_dirs: dict[str, str], kind_dir: str, name: str
123
+ ) -> Path | None:
124
+ """Locate an overlay file (``rules`` or ``agents``) by name across the selected stack dirs."""
125
+ stacks = src / "templates" / "stacks"
126
+ for stack_dir in stack_dirs.values():
127
+ if not stack_dir:
128
+ continue
129
+ candidate = stacks / stack_dir / kind_dir / name
130
+ if candidate.is_file():
131
+ return candidate
132
+ return None
133
+
134
+
135
+ # --- install steps ---------------------------------------------------------------------------------
136
+
137
+
138
+ def _install_rules(src: Path, dest: Path, plan: ResolvedPlan, log: list[str]) -> None:
139
+ """Install all core rules plus the selected overlay rules into ``.claude/rules/``."""
140
+ rules_dest = dest / "rules"
141
+ _copy_tree(src / "rules", rules_dest)
142
+ log.append(f" • rules/ ({sum(1 for _ in rules_dest.glob('*.md'))} core)")
143
+ for name in plan.overlay_rules:
144
+ found = _find_overlay(src, plan.stack_dirs, "rules", name)
145
+ if found:
146
+ shutil.copy2(found, rules_dest / name)
147
+ log.append(f" • overlay rule: rules/{name}")
148
+ else:
149
+ log.append(f" ! overlay rule missing (skipped): {name}")
150
+
151
+
152
+ def _install_agents(src: Path, dest: Path, plan: ResolvedPlan, log: list[str]) -> None:
153
+ """Install the profile's core-agent subset plus selected overlay agents into ``.claude/agents/``."""
154
+ agents_dest = dest / "agents"
155
+ agents_dest.mkdir(parents=True, exist_ok=True)
156
+ installed = 0
157
+ for name in plan.agents:
158
+ srcf = src / "agents" / f"{name}.md"
159
+ if srcf.is_file():
160
+ shutil.copy2(srcf, agents_dest / f"{name}.md")
161
+ installed += 1
162
+ else:
163
+ log.append(f" ! agent missing (skipped): {name}")
164
+ log.append(f" • agents/ ({installed} of {len(plan.agents)} selected)")
165
+ for name in plan.overlay_agents:
166
+ found = _find_overlay(src, plan.stack_dirs, "agents", f"{name}.md")
167
+ if found:
168
+ shutil.copy2(found, agents_dest / f"{name}.md")
169
+ log.append(f" • overlay agent: agents/{name}.md")
170
+ else:
171
+ log.append(f" ! overlay agent missing (skipped): {name}")
172
+
173
+
174
+ def _install_skills(src: Path, dest: Path, plan: ResolvedPlan, log: list[str]) -> None:
175
+ """Install the profile's skill subset into ``.claude/skills/``."""
176
+ skills_dest = dest / "skills"
177
+ skills_dest.mkdir(parents=True, exist_ok=True)
178
+ installed = 0
179
+ for name in plan.skills:
180
+ srcd = src / "skills" / name
181
+ if (srcd / "SKILL.md").is_file():
182
+ _copy_tree(srcd, skills_dest / name)
183
+ installed += 1
184
+ else:
185
+ log.append(f" ! skill missing (skipped): {name}")
186
+ log.append(f" • skills/ ({installed} of {len(plan.skills)} selected)")
187
+ # _references/ is shared support content (not a profile-selected skill), but several SKILL.md
188
+ # files link into .claude/skills/_references/…; copy it so those "See Also" links resolve.
189
+ refs_src = src / "skills" / "_references"
190
+ if refs_src.is_dir():
191
+ _copy_tree(refs_src, skills_dest / "_references")
192
+ log.append(" • skills/_references/ (shared deep-dive references)")
193
+
194
+
195
+ def _install_org(src: Path, dest: Path, plan: ResolvedPlan, log: list[str]) -> None:
196
+ """Install the org capability layer (only when ``plan.org`` is present — organization scope).
197
+
198
+ The new skills/agents/rules install into the standard auto-discovered ``.claude/`` dirs (so Claude
199
+ Code picks them up like any other component); the pack manifests install under ``.claude/org-packs/``
200
+ as a governance/catalog layer that *references* the active components.
201
+ """
202
+ org = plan.org
203
+ if org is None:
204
+ return
205
+ org_src = src / "templates" / "org"
206
+
207
+ for name in org.org_skills:
208
+ srcd = org_src / "skills" / name
209
+ if (srcd / "SKILL.md").is_file():
210
+ _copy_tree(srcd, dest / "skills" / name)
211
+ else:
212
+ log.append(f" ! org skill missing (skipped): {name}")
213
+ for name in org.org_agents:
214
+ srcf = org_src / "agents" / f"{name}.md"
215
+ if srcf.is_file():
216
+ shutil.copy2(srcf, dest / "agents" / f"{name}.md")
217
+ else:
218
+ log.append(f" ! org agent missing (skipped): {name}")
219
+ for name in org.org_rules:
220
+ srcf = org_src / "rules" / name
221
+ if srcf.is_file():
222
+ shutil.copy2(srcf, dest / "rules" / name)
223
+ else:
224
+ log.append(f" ! org rule missing (skipped): {name}")
225
+ log.append(
226
+ f" • org layer: {len(org.org_skills)} skills, {len(org.org_agents)} persona agents, "
227
+ f"{len(org.org_rules)} rules (autonomy={org.autonomy})"
228
+ )
229
+
230
+ if org.packs:
231
+ packs_dest = dest / "org-packs"
232
+ packs_dest.mkdir(parents=True, exist_ok=True)
233
+ index = org_src / "README.md"
234
+ if index.is_file():
235
+ shutil.copy2(index, packs_dest / "README.md")
236
+ installed = 0
237
+ for pack in org.packs:
238
+ srcd = org_src / "packs" / pack
239
+ if (srcd / "pack.yaml").is_file():
240
+ _copy_tree(srcd, packs_dest / pack)
241
+ installed += 1
242
+ else:
243
+ log.append(f" ! org pack missing (skipped): {pack}")
244
+ log.append(f" • org-packs/ ({installed} pack manifests)")
245
+
246
+
247
+ def _install_hooks_and_settings(
248
+ src: Path, dest: Path, plan: ResolvedPlan, *, force: bool, log: list[str]
249
+ ) -> None:
250
+ """Copy the scripts needed by selected hooks and assemble ``.claude/settings.json``."""
251
+ hooks_dest = dest / "hooks"
252
+ hooks_dest.mkdir(parents=True, exist_ok=True)
253
+ for script in hooks_mod.scripts_for(plan.hooks):
254
+ srcf = src / "hooks" / "scripts" / script
255
+ if srcf.is_file():
256
+ shutil.copy2(srcf, hooks_dest / script)
257
+ (hooks_dest / script).chmod(0o755)
258
+ log.append(f" • hooks/ ({sum(1 for _ in hooks_dest.glob('*.sh'))} scripts)")
259
+ settings = hooks_mod.build_settings(plan.hooks)
260
+ _write_user_text(
261
+ dest / "settings.json",
262
+ json.dumps(settings, indent=2) + "\n",
263
+ force=force,
264
+ log=log,
265
+ label="settings.json",
266
+ )
267
+
268
+
269
+ def _install_artifact_templates(src: Path, dest: Path, log: list[str]) -> None:
270
+ """Install the artifact markdown templates into ``.claude/templates/``."""
271
+ srcd = src / "templates" / "artifacts"
272
+ if not srcd.is_dir():
273
+ return
274
+ tdest = dest / "templates"
275
+ _copy_tree(srcd, tdest)
276
+ log.append(
277
+ f" • templates/ ({sum(1 for _ in tdest.glob('*.md'))} artifact templates)"
278
+ )
279
+
280
+
281
+ def _write_claude_md(
282
+ src: Path, target: Path, plan: ResolvedPlan, *, force: bool, log: list[str]
283
+ ) -> None:
284
+ """Write CLAUDE.md and fill its 'Project-specific rules' block from the resolved stack."""
285
+ claude_md = target / "CLAUDE.md"
286
+ base = (src / "templates" / "CLAUDE.md").read_text(encoding="utf-8")
287
+ block_tmpl = src / "templates" / "CLAUDE.stack.md.tmpl"
288
+ if block_tmpl.is_file():
289
+ block = (
290
+ render_text(block_tmpl.read_text(encoding="utf-8"), plan.context).rstrip()
291
+ + "\n"
292
+ )
293
+ idx = base.find(_STACK_MARKER)
294
+ base = (
295
+ (base[:idx].rstrip() + "\n\n" + block)
296
+ if idx != -1
297
+ else (base.rstrip() + "\n\n" + block)
298
+ )
299
+ _write_user_text(claude_md, base, force=force, log=log, label="CLAUDE.md")
300
+
301
+
302
+ def _write_mcp(
303
+ target: Path, plan: ResolvedPlan, *, force: bool, log: list[str]
304
+ ) -> None:
305
+ """Write a project-root ``.mcp.json`` only if MCP servers were selected."""
306
+ if not plan.mcp_servers:
307
+ return
308
+ doc = {"mcpServers": plan.mcp_servers}
309
+ _write_user_text(
310
+ target / ".mcp.json",
311
+ json.dumps(doc, indent=2) + "\n",
312
+ force=force,
313
+ log=log,
314
+ label=".mcp.json",
315
+ )
316
+
317
+
318
+ def _write_readme(
319
+ src: Path, target: Path, plan: ResolvedPlan, *, force: bool, log: list[str]
320
+ ) -> None:
321
+ """Render ``README.claude-sdlc.md`` from the template."""
322
+ tmpl = src / "templates" / "README.claude-sdlc.md.tmpl"
323
+ if not tmpl.is_file():
324
+ return
325
+ text = render_text(tmpl.read_text(encoding="utf-8"), plan.context)
326
+ _write_user_text(
327
+ target / "README.claude-sdlc.md",
328
+ text,
329
+ force=True,
330
+ log=log,
331
+ label="README.claude-sdlc.md",
332
+ )
333
+
334
+
335
+ def _update_gitignore(target: Path, log: list[str]) -> None:
336
+ """Append the selective claude-kit gitignore entries (idempotently)."""
337
+ gi = target / ".gitignore"
338
+ existing = gi.read_text(encoding="utf-8").splitlines() if gi.is_file() else []
339
+ have = set(existing)
340
+ missing = [e for e in GITIGNORE_ENTRIES if e not in have]
341
+ if not missing:
342
+ return
343
+ lines = list(existing)
344
+ if lines and lines[-1].strip():
345
+ lines.append("")
346
+ lines.append("# claude-kit runtime + local overrides")
347
+ lines.extend(missing)
348
+ gi.write_text("\n".join(lines) + "\n", encoding="utf-8")
349
+ log.append(f" • .gitignore (+{len(missing)} entries)")
350
+
351
+
352
+ def _seed_runtime_dirs(dest: Path, log: list[str]) -> None:
353
+ """Create gitignored runtime dirs (state/, tmp/) with a .gitkeep so they exist but stay empty."""
354
+ for name in ("state", "tmp"):
355
+ d = dest / name
356
+ d.mkdir(parents=True, exist_ok=True)
357
+ (d / ".gitkeep").write_text("", encoding="utf-8")
358
+
359
+
360
+ def _seed_agent_memory(src: Path, dest: Path, log: list[str]) -> None:
361
+ """Install the agent-memory seed (only if the project doesn't already have one)."""
362
+ if (dest / "agent-memory").exists():
363
+ return
364
+ seed = src / "templates" / "agent-memory"
365
+ if seed.is_dir():
366
+ _copy_tree(seed, dest / "agent-memory")
367
+ log.append(" • agent-memory/ seed")
368
+
369
+
370
+ def _classify_owner(rel: str, plan: ResolvedPlan) -> str:
371
+ """Classify a relative path as kit / overlay / user-editable for upgrade policy."""
372
+ user_editable = {
373
+ "CLAUDE.md",
374
+ ".mcp.json",
375
+ ".claude/settings.json",
376
+ ".claude/CONTINUITY.md",
377
+ }
378
+ if rel in user_editable or rel.startswith(".claude/agent-memory/"):
379
+ return "user-editable"
380
+ overlay_paths = {f".claude/rules/{r}" for r in plan.overlay_rules}
381
+ overlay_paths |= {f".claude/agents/{a}.md" for a in plan.overlay_agents}
382
+ if rel in overlay_paths:
383
+ return "overlay"
384
+ return "kit"
385
+
386
+
387
+ def _record_files(target: Path, plan: ResolvedPlan) -> list[FileRecord]:
388
+ """Compute checksum + ownership records for every installed file (excluding runtime/self)."""
389
+ records: list[FileRecord] = []
390
+ candidates: list[Path] = []
391
+ for top in ("CLAUDE.md", "README.claude-sdlc.md", ".mcp.json"):
392
+ p = target / top
393
+ if p.is_file():
394
+ candidates.append(p)
395
+ dest = target / ".claude"
396
+ skip_dirs = {dest / "state", dest / "tmp"}
397
+ init_options = dest / "config" / "init-options.json"
398
+ for p in sorted(dest.rglob("*")):
399
+ if not p.is_file() or p == init_options:
400
+ continue
401
+ if p.name.endswith(".claude-kit"):
402
+ continue # transient sidecar of a protected file — not a tracked install artifact
403
+ if any(sd in p.parents for sd in skip_dirs):
404
+ continue
405
+ candidates.append(p)
406
+ for p in candidates:
407
+ rel = p.relative_to(target).as_posix()
408
+ records.append(
409
+ FileRecord(path=rel, sha256=_sha256(p), owner=_classify_owner(rel, plan))
410
+ )
411
+ return sorted(records, key=lambda r: r.path)
412
+
413
+
414
+ def _write_config(src: Path, target: Path, plan: ResolvedPlan, log: list[str]) -> None:
415
+ """Write the resolved catalog snapshot and init-options.json (with file checksums)."""
416
+ config_dest = target / ".claude" / "config"
417
+ config_dest.mkdir(parents=True, exist_ok=True)
418
+ snapshot = {
419
+ "selection": plan.selection.to_dict(),
420
+ "agents": plan.agents,
421
+ "skills": plan.skills,
422
+ "overlay_rules": plan.overlay_rules,
423
+ "overlay_agents": plan.overlay_agents,
424
+ "hooks": plan.hooks,
425
+ "gates": plan.gates,
426
+ "mcp": list(plan.mcp_servers),
427
+ "org": plan.org.to_dict() if plan.org else None,
428
+ }
429
+ (config_dest / "stack-catalog.snapshot.yaml").write_text(
430
+ yaml.safe_dump(snapshot, sort_keys=False), encoding="utf-8"
431
+ )
432
+ options = InitOptions(
433
+ claude_kit_version=__version__,
434
+ selection=plan.selection,
435
+ files=_record_files(target, plan),
436
+ )
437
+ (config_dest / "init-options.json").write_text(
438
+ json.dumps(options.to_dict(), indent=2) + "\n", encoding="utf-8"
439
+ )
440
+ log.append(" • config/ (init-options.json + stack snapshot)")
441
+
442
+
443
+ def install_sdlc(
444
+ src: Path,
445
+ target: Path,
446
+ plan: ResolvedPlan,
447
+ *,
448
+ force: bool = False,
449
+ log: list[str] | None = None,
450
+ ) -> list[str]:
451
+ """Install a resolved claude-kit configuration into ``target``.
452
+
453
+ Args:
454
+ src: Payload root (contains ``rules/ agents/ skills/ hooks/ templates/ catalog/``).
455
+ target: Project root to install into.
456
+ plan: The resolved install plan from :func:`claude_kit.catalog.resolve`.
457
+ force: Overwrite user-editable files (CLAUDE.md, settings.json, .mcp.json) instead of
458
+ writing ``.claude-kit`` sidecars.
459
+ log: Optional list to append human-readable log lines to.
460
+
461
+ Returns:
462
+ The log list, one line per installed component.
463
+ """
464
+ if log is None:
465
+ log = []
466
+ target = Path(target)
467
+ dest = target / ".claude"
468
+ dest.mkdir(parents=True, exist_ok=True)
469
+
470
+ # Augment the render context with project identity + summary counts.
471
+ plan.context.setdefault("project_name", target.resolve().name)
472
+ plan.context["agent_count"] = str(len(plan.agents) + len(plan.overlay_agents))
473
+ plan.context["skill_count"] = str(len(plan.skills))
474
+ plan.context["overlay_rules_list"] = ", ".join(plan.overlay_rules) or "none"
475
+
476
+ _install_rules(src, dest, plan, log)
477
+ _write_claude_md(src, target, plan, force=force, log=log)
478
+ shutil.copy2(
479
+ src / "templates" / "CONTINUITY.template.md", dest / "CONTINUITY.template.md"
480
+ )
481
+ _install_agents(src, dest, plan, log)
482
+ _install_skills(src, dest, plan, log)
483
+ _install_org(src, dest, plan, log)
484
+ _seed_agent_memory(src, dest, log)
485
+ _install_hooks_and_settings(src, dest, plan, force=force, log=log)
486
+ _install_artifact_templates(src, dest, log)
487
+ _write_mcp(target, plan, force=force, log=log)
488
+ _write_readme(src, target, plan, force=force, log=log)
489
+ _seed_runtime_dirs(dest, log)
490
+ _update_gitignore(target, log)
491
+ _write_config(src, target, plan, log)
492
+ return log