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.
- claude_code_kit-0.7.0.dist-info/METADATA +384 -0
- claude_code_kit-0.7.0.dist-info/RECORD +209 -0
- claude_code_kit-0.7.0.dist-info/WHEEL +4 -0
- claude_code_kit-0.7.0.dist-info/entry_points.txt +4 -0
- claude_code_kit-0.7.0.dist-info/licenses/LICENSE +21 -0
- claude_kit/__init__.py +10 -0
- claude_kit/__main__.py +8 -0
- claude_kit/_payload/agents/acceptance-reviewer.md +60 -0
- claude_kit/_payload/agents/auditor.md +76 -0
- claude_kit/_payload/agents/dependency-scanner.md +84 -0
- claude_kit/_payload/agents/developer.md +187 -0
- claude_kit/_payload/agents/devils-advocate.md +62 -0
- claude_kit/_payload/agents/devops-engineer.md +134 -0
- claude_kit/_payload/agents/e2e-tester.md +152 -0
- claude_kit/_payload/agents/em-reviewer.md +105 -0
- claude_kit/_payload/agents/incident-responder.md +64 -0
- claude_kit/_payload/agents/merge-reviewer.md +194 -0
- claude_kit/_payload/agents/observability-engineer.md +94 -0
- claude_kit/_payload/agents/orchestrator.md +551 -0
- claude_kit/_payload/agents/owasp-reviewer.md +76 -0
- claude_kit/_payload/agents/policy-validator.md +63 -0
- claude_kit/_payload/agents/pr-raiser.md +138 -0
- claude_kit/_payload/agents/risk-classifier.md +50 -0
- claude_kit/_payload/agents/sdlc-code-reviewer.md +196 -0
- claude_kit/_payload/agents/secret-scanner.md +70 -0
- claude_kit/_payload/agents/security-reviewer.md +80 -0
- claude_kit/_payload/agents/senior-backend-dev.md +199 -0
- claude_kit/_payload/agents/senior-frontend-dev.md +181 -0
- claude_kit/_payload/agents/senior-tester.md +206 -0
- claude_kit/_payload/agents/spec-doc-writer.md +331 -0
- claude_kit/_payload/agents/story-planner.md +56 -0
- claude_kit/_payload/agents/technical-architect.md +139 -0
- claude_kit/_payload/agents/tester.md +193 -0
- claude_kit/_payload/agents/ui-designer.md +73 -0
- claude_kit/_payload/agents/unit-tester.md +119 -0
- claude_kit/_payload/catalog/mcp.yaml +54 -0
- claude_kit/_payload/catalog/org.yaml +145 -0
- claude_kit/_payload/catalog/profiles.yaml +96 -0
- claude_kit/_payload/catalog/stacks.yaml +96 -0
- claude_kit/_payload/commands/init.md +36 -0
- claude_kit/_payload/commands/sdlc.md +18 -0
- claude_kit/_payload/commands/status.md +20 -0
- claude_kit/_payload/hooks/hooks.json +58 -0
- claude_kit/_payload/hooks/scripts/audit-log.sh +18 -0
- claude_kit/_payload/hooks/scripts/guard-secrets.sh +26 -0
- claude_kit/_payload/hooks/scripts/lint-fix.sh +38 -0
- claude_kit/_payload/hooks/scripts/load-continuity.sh +32 -0
- claude_kit/_payload/hooks/scripts/load-learnings.sh +40 -0
- claude_kit/_payload/hooks/scripts/type-check.sh +23 -0
- claude_kit/_payload/hooks/scripts/validate-frontmatter.sh +34 -0
- claude_kit/_payload/hooks/scripts/validate-settings.sh +21 -0
- claude_kit/_payload/hooks/scripts/warn-large-edits.sh +24 -0
- claude_kit/_payload/hooks/scripts/warn-missing-tests.sh +24 -0
- claude_kit/_payload/hooks/scripts/warn-sensitive-files.sh +30 -0
- claude_kit/_payload/hooks/scripts/warn-shared-modules.sh +33 -0
- claude_kit/_payload/rules/agent-guardrails.md +83 -0
- claude_kit/_payload/rules/agent-memory.md +106 -0
- claude_kit/_payload/rules/agent-resilience.md +61 -0
- claude_kit/_payload/rules/autonomy-levels.md +30 -0
- claude_kit/_payload/rules/code-organization.md +312 -0
- claude_kit/_payload/rules/continuity.md +84 -0
- claude_kit/_payload/rules/design-patterns.md +422 -0
- claude_kit/_payload/rules/devops-observability.md +57 -0
- claude_kit/_payload/rules/documentation.md +326 -0
- claude_kit/_payload/rules/evals.md +62 -0
- claude_kit/_payload/rules/frontend-best-practices.md +157 -0
- claude_kit/_payload/rules/goal-setting-and-monitoring.md +72 -0
- claude_kit/_payload/rules/human-in-the-loop.md +64 -0
- claude_kit/_payload/rules/linting-and-formatting.md +220 -0
- claude_kit/_payload/rules/mandatory-workflow.md +309 -0
- claude_kit/_payload/rules/model-tiers.md +34 -0
- claude_kit/_payload/rules/quality-gates.md +107 -0
- claude_kit/_payload/rules/rarv-cycle.md +31 -0
- claude_kit/_payload/rules/reasoning-techniques.md +62 -0
- claude_kit/_payload/rules/responsive-and-accessibility.md +353 -0
- claude_kit/_payload/rules/risk-classification.md +36 -0
- claude_kit/_payload/rules/testing.md +417 -0
- claude_kit/_payload/rules/tool-design.md +66 -0
- claude_kit/_payload/skills/_references/accessibility-checklist.md +160 -0
- claude_kit/_payload/skills/_references/orchestration-patterns.md +405 -0
- claude_kit/_payload/skills/_references/performance-checklist.md +153 -0
- claude_kit/_payload/skills/_references/security-checklist.md +134 -0
- claude_kit/_payload/skills/_references/testing-patterns.md +236 -0
- claude_kit/_payload/skills/accessibility-review/SKILL.md +56 -0
- claude_kit/_payload/skills/api-and-interface-design/SKILL.md +294 -0
- claude_kit/_payload/skills/api-integration/SKILL.md +348 -0
- claude_kit/_payload/skills/archive-sprint/SKILL.md +31 -0
- claude_kit/_payload/skills/backlog/SKILL.md +41 -0
- claude_kit/_payload/skills/backlog/item-template.md +20 -0
- claude_kit/_payload/skills/browser-testing-with-devtools/SKILL.md +302 -0
- claude_kit/_payload/skills/ci-cd-and-automation/SKILL.md +402 -0
- claude_kit/_payload/skills/code-review-and-quality/SKILL.md +347 -0
- claude_kit/_payload/skills/code-simplification/SKILL.md +331 -0
- claude_kit/_payload/skills/component-design/SKILL.md +171 -0
- claude_kit/_payload/skills/consolidate-learnings/SKILL.md +55 -0
- claude_kit/_payload/skills/context-engineering/SKILL.md +321 -0
- claude_kit/_payload/skills/debugging-and-error-recovery/SKILL.md +300 -0
- claude_kit/_payload/skills/decision/SKILL.md +46 -0
- claude_kit/_payload/skills/decision/adr-template.md +36 -0
- claude_kit/_payload/skills/deprecation-and-migration/SKILL.md +207 -0
- claude_kit/_payload/skills/documentation-and-adrs/SKILL.md +299 -0
- claude_kit/_payload/skills/doubt-driven-development/SKILL.md +243 -0
- claude_kit/_payload/skills/execute/SKILL.md +27 -0
- claude_kit/_payload/skills/frontend-ui-engineering/SKILL.md +328 -0
- claude_kit/_payload/skills/git-workflow-and-versioning/SKILL.md +300 -0
- claude_kit/_payload/skills/idea-refine/SKILL.md +178 -0
- claude_kit/_payload/skills/idea-refine/examples.md +238 -0
- claude_kit/_payload/skills/idea-refine/frameworks.md +99 -0
- claude_kit/_payload/skills/idea-refine/refinement-criteria.md +113 -0
- claude_kit/_payload/skills/idea-refine/scripts/idea-refine.sh +15 -0
- claude_kit/_payload/skills/incident-postmortem/SKILL.md +74 -0
- claude_kit/_payload/skills/incremental-implementation/SKILL.md +245 -0
- claude_kit/_payload/skills/interview-me/SKILL.md +221 -0
- claude_kit/_payload/skills/load-testing/SKILL.md +83 -0
- claude_kit/_payload/skills/manual-test/SKILL.md +516 -0
- claude_kit/_payload/skills/performance-optimization/SKILL.md +277 -0
- claude_kit/_payload/skills/planning-and-task-breakdown/SKILL.md +223 -0
- claude_kit/_payload/skills/playwright-verification/SKILL.md +205 -0
- claude_kit/_payload/skills/refresh-docs/SKILL.md +63 -0
- claude_kit/_payload/skills/remember/SKILL.md +96 -0
- claude_kit/_payload/skills/scope/SKILL.md +52 -0
- claude_kit/_payload/skills/scope/scope-template.md +82 -0
- claude_kit/_payload/skills/sdlc/SKILL.md +83 -0
- claude_kit/_payload/skills/security-and-hardening/SKILL.md +368 -0
- claude_kit/_payload/skills/security-verification/SKILL.md +209 -0
- claude_kit/_payload/skills/shipping-and-launch/SKILL.md +309 -0
- claude_kit/_payload/skills/smoke-test/SKILL.md +78 -0
- claude_kit/_payload/skills/source-driven-development/SKILL.md +195 -0
- claude_kit/_payload/skills/spec-driven-development/SKILL.md +200 -0
- claude_kit/_payload/skills/sprint/SKILL.md +67 -0
- claude_kit/_payload/skills/sprint/sprint-template.md +90 -0
- claude_kit/_payload/skills/test-driven-development/SKILL.md +383 -0
- claude_kit/_payload/skills/threat-model/SKILL.md +60 -0
- claude_kit/_payload/skills/triage/SKILL.md +87 -0
- claude_kit/_payload/skills/ui-ux-design/SKILL.md +71 -0
- claude_kit/_payload/skills/unit-test/SKILL.md +237 -0
- claude_kit/_payload/skills/using-agent-skills/SKILL.md +180 -0
- claude_kit/_payload/templates/CLAUDE.md +238 -0
- claude_kit/_payload/templates/CLAUDE.stack.md.tmpl +53 -0
- claude_kit/_payload/templates/CONTINUITY.template.md +35 -0
- claude_kit/_payload/templates/README.claude-sdlc.md.tmpl +219 -0
- claude_kit/_payload/templates/agent-memory/MEMORY.md +30 -0
- claude_kit/_payload/templates/agent-memory/api/.gitkeep +0 -0
- claude_kit/_payload/templates/agent-memory/architecture/.gitkeep +0 -0
- claude_kit/_payload/templates/agent-memory/debugging/.gitkeep +0 -0
- claude_kit/_payload/templates/agent-memory/gotchas/.gitkeep +0 -0
- claude_kit/_payload/templates/agent-memory/patterns/.gitkeep +0 -0
- claude_kit/_payload/templates/agent-memory/performance/.gitkeep +0 -0
- claude_kit/_payload/templates/artifacts/adr.md +18 -0
- claude_kit/_payload/templates/artifacts/feature-spec.md +29 -0
- claude_kit/_payload/templates/artifacts/release-plan.md +23 -0
- claude_kit/_payload/templates/artifacts/runbook.md +24 -0
- claude_kit/_payload/templates/artifacts/security-review.md +23 -0
- claude_kit/_payload/templates/artifacts/test-plan.md +22 -0
- claude_kit/_payload/templates/org/README.md +53 -0
- claude_kit/_payload/templates/org/agents/data-workflow-agent.md +59 -0
- claude_kit/_payload/templates/org/agents/founder-prototype-agent.md +61 -0
- claude_kit/_payload/templates/org/agents/internal-tools-builder.md +63 -0
- claude_kit/_payload/templates/org/agents/pm-copilot.md +60 -0
- claude_kit/_payload/templates/org/agents/support-ticket-engineer.md +63 -0
- claude_kit/_payload/templates/org/packs/devops-and-release/README.md +46 -0
- claude_kit/_payload/templates/org/packs/devops-and-release/pack.yaml +32 -0
- claude_kit/_payload/templates/org/packs/engineering-core/README.md +46 -0
- claude_kit/_payload/templates/org/packs/engineering-core/pack.yaml +44 -0
- claude_kit/_payload/templates/org/packs/non-engineer-builder/README.md +53 -0
- claude_kit/_payload/templates/org/packs/non-engineer-builder/pack.yaml +39 -0
- claude_kit/_payload/templates/org/packs/onboarding-and-docs/README.md +49 -0
- claude_kit/_payload/templates/org/packs/onboarding-and-docs/pack.yaml +26 -0
- claude_kit/_payload/templates/org/packs/product-to-code/README.md +50 -0
- claude_kit/_payload/templates/org/packs/product-to-code/pack.yaml +34 -0
- claude_kit/_payload/templates/org/packs/quality-and-review/README.md +53 -0
- claude_kit/_payload/templates/org/packs/quality-and-review/pack.yaml +40 -0
- claude_kit/_payload/templates/org/packs/security-and-compliance/README.md +50 -0
- claude_kit/_payload/templates/org/packs/security-and-compliance/pack.yaml +36 -0
- claude_kit/_payload/templates/org/rules/ai-working-agreement.md +45 -0
- claude_kit/_payload/templates/org/rules/ambiguity-resolution.md +36 -0
- claude_kit/_payload/templates/org/rules/branch-and-pr-policy.md +41 -0
- claude_kit/_payload/templates/org/rules/compliance-policy.md +50 -0
- claude_kit/_payload/templates/org/rules/non-engineer-safe-coding.md +37 -0
- claude_kit/_payload/templates/org/rules/pii-policy.md +46 -0
- claude_kit/_payload/templates/org/rules/production-data-policy.md +35 -0
- claude_kit/_payload/templates/org/rules/prompt-to-task-conversion.md +30 -0
- claude_kit/_payload/templates/org/rules/prototype-boundaries.md +40 -0
- claude_kit/_payload/templates/org/rules/secrets-policy.md +34 -0
- claude_kit/_payload/templates/org/skills/customer-issue-to-fix/SKILL.md +61 -0
- claude_kit/_payload/templates/org/skills/feature-from-idea/SKILL.md +56 -0
- claude_kit/_payload/templates/org/skills/prompt-to-safe-task/SKILL.md +59 -0
- claude_kit/_payload/templates/org/skills/prototype-to-production/SKILL.md +61 -0
- claude_kit/_payload/templates/org/skills/repo-onboarding/SKILL.md +60 -0
- claude_kit/_payload/templates/settings.json +53 -0
- claude_kit/_payload/templates/stacks/backend/python/fastapi/rules/fastapi-patterns.md +64 -0
- claude_kit/_payload/templates/stacks/db/mongodb/agents/migration-specialist.md +61 -0
- claude_kit/_payload/templates/stacks/db/mongodb/agents/mongodb-specialist.md +59 -0
- claude_kit/_payload/templates/stacks/db/mongodb/rules/mongodb-patterns.md +39 -0
- claude_kit/_payload/templates/stacks/db/postgres/agents/db-performance-reviewer.md +66 -0
- claude_kit/_payload/templates/stacks/db/postgres/agents/migration-specialist.md +56 -0
- claude_kit/_payload/templates/stacks/db/postgres/agents/postgres-specialist.md +58 -0
- claude_kit/_payload/templates/stacks/db/postgres/rules/database-performance.md +64 -0
- claude_kit/_payload/templates/stacks/db/postgres/rules/postgres-patterns.md +43 -0
- claude_kit/_payload/templates/stacks/frontend/react/rules/react-patterns.md +63 -0
- claude_kit/catalog.py +476 -0
- claude_kit/cli.py +327 -0
- claude_kit/hooks.py +246 -0
- claude_kit/models.py +205 -0
- claude_kit/prompts.py +209 -0
- claude_kit/render.py +146 -0
- claude_kit/scaffold.py +492 -0
- claude_kit/upgrader.py +294 -0
- 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
|