mirror of
https://github.com/github/spec-kit.git
synced 2026-07-05 21:49:47 +08:00
* Stage 6: Complete migration — remove legacy scaffold path (#1924) Remove the legacy GitHub download and offline scaffold code paths. All 26 agents now use the integration system exclusively. Code removal (~1073 lines from __init__.py): - download_template_from_github(), download_and_extract_template() - scaffold_from_core_pack(), _locate_release_script() - install_ai_skills(), _get_skills_dir (restored slim version for presets) - _has_bundled_skills(), _migrate_legacy_kimi_dotted_skills() - AGENT_SKILLS_MIGRATIONS, _handle_agent_skills_migration() - _parse_rate_limit_headers(), _format_rate_limit_error() - Three-way branch in init() collapsed to integration-only Config derivation (single source of truth): - AGENT_CONFIG derived from INTEGRATION_REGISTRY (replaced 180-line dict) - CommandRegistrar.AGENT_CONFIGS derived from INTEGRATION_REGISTRY (replaced 160-line dict) - Backward-compat constants kept for presets/extensions: SKILL_DESCRIPTIONS, NATIVE_SKILLS_AGENTS, DEFAULT_SKILLS_DIR Release pipeline cleanup: - Deleted create-release-packages.sh/.ps1 (948 lines of ZIP packaging) - Deleted create-github-release.sh, generate-release-notes.sh - Deleted simulate-release.sh, get-next-version.sh, update-version.sh - Removed .github/workflows/scripts/ directory entirely - release.yml is now self-contained: check, notes, release all inlined - Install instructions use uv tool install with version tag Test cleanup: - Deleted test_ai_skills.py (tested removed functions) - Deleted test_core_pack_scaffold.py (tested removed scaffold) - Cleaned test_agent_config_consistency.py (removed 19 release-script tests) - Fixed test_branch_numbering.py (removed dead monkeypatches) - Updated auto-promote tests (verify files created, not tip messages) 1089 tests pass, 0 failures, ruff clean. * fix: resolve merge conflicts with #2051 (claude as skills) - Fix circular import: move CommandRegistrar import in claude integration to inside method bodies (was at module level) - Lazy-populate AGENT_CONFIGS via _ensure_configs() to avoid circular import at class definition time - Set claude registrar_config to .claude/commands (extension/preset target) since the integration handles .claude/skills in setup() - Update tests from #2051 to match: registrar_config assertions, remove --integration tip assertions, remove install_ai_skills mocks 1086 tests pass. * fix: properly preserve claude skills migration from #2051 Restore ClaudeIntegration.registrar_config to .claude/skills (not .claude/commands) so extension/preset registrations write to the correct skills directory. Update tests that simulate claude setup to use .claude/skills and check for SKILL.md layout. Some tests still need updating for the full skills path — 10 remaining failures from the #2051 test expectations around the extension/preset skill registration flow. WIP: 1076/1086 pass. * fix: properly handle SKILL.md paths in extension update rollback and tests Fix extension update rollback using _compute_output_name() for SKILL.md agents (converts dots to hyphens in skill directory names). Previously the backup and cleanup code constructed paths with raw command names (e.g. speckit.test-ext.hello/SKILL.md) instead of the correct computed names (speckit-test-ext-hello/SKILL.md). Test fixes for claude skills migration: - Update claude tests to use .claude/skills paths and SKILL.md layout - Use qwen (not claude) for skills-guard tests since claude's agent dir IS the skills dir — creating it triggers command registration - Fix test_extension_command_registered_when_extension_present to check skills path format 1086 tests pass, 0 failures, ruff clean. * fix: address PR review — lazy init, assertions, deprecated flags - _ensure_configs(): catch ImportError (not Exception), don't set _configs_loaded on failure so retries work - Move _ensure_configs() before unregister loop (not inside it) - Module-level try/except catches ImportError specifically - Remove tautology assertion (or True) in test_extensions.py - Strengthen preset provenance assertion to check source: field - Mark --offline, --skip-tls, --debug, --github-token as hidden deprecated no-ops in init() 1086 tests pass. * fix: remove deleted release scripts from pyproject.toml force-include Removes force-include entries for create-release-packages.sh/.ps1 which were deleted but still referenced in [tool.hatch.build].
110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
"""Claude Code integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import yaml
|
|
|
|
from ..base import SkillsIntegration
|
|
from ..manifest import IntegrationManifest
|
|
|
|
|
|
class ClaudeIntegration(SkillsIntegration):
|
|
"""Integration for Claude Code skills."""
|
|
|
|
key = "claude"
|
|
config = {
|
|
"name": "Claude Code",
|
|
"folder": ".claude/",
|
|
"commands_subdir": "skills",
|
|
"install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
|
|
"requires_cli": True,
|
|
}
|
|
registrar_config = {
|
|
"dir": ".claude/skills",
|
|
"format": "markdown",
|
|
"args": "$ARGUMENTS",
|
|
"extension": "/SKILL.md",
|
|
}
|
|
context_file = "CLAUDE.md"
|
|
|
|
def command_filename(self, template_name: str) -> str:
|
|
"""Claude skills live at .claude/skills/<name>/SKILL.md."""
|
|
skill_name = f"speckit-{template_name.replace('.', '-')}"
|
|
return f"{skill_name}/SKILL.md"
|
|
|
|
def _render_skill(self, template_name: str, frontmatter: dict[str, Any], body: str) -> str:
|
|
"""Render a processed command template as a Claude skill."""
|
|
skill_name = f"speckit-{template_name.replace('.', '-')}"
|
|
description = frontmatter.get(
|
|
"description",
|
|
f"Spec-kit workflow command: {template_name}",
|
|
)
|
|
skill_frontmatter = self._build_skill_fm(
|
|
skill_name, description, f"templates/commands/{template_name}.md"
|
|
)
|
|
frontmatter_text = yaml.safe_dump(skill_frontmatter, sort_keys=False).strip()
|
|
return f"---\n{frontmatter_text}\n---\n\n{body.strip()}\n"
|
|
|
|
def _build_skill_fm(self, name: str, description: str, source: str) -> dict:
|
|
from specify_cli.agents import CommandRegistrar
|
|
return CommandRegistrar.build_skill_frontmatter(
|
|
self.key, name, description, source
|
|
)
|
|
|
|
def setup(
|
|
self,
|
|
project_root: Path,
|
|
manifest: IntegrationManifest,
|
|
parsed_options: dict[str, Any] | None = None,
|
|
**opts: Any,
|
|
) -> list[Path]:
|
|
"""Install Claude skills into .claude/skills."""
|
|
templates = self.list_command_templates()
|
|
if not templates:
|
|
return []
|
|
|
|
project_root_resolved = project_root.resolve()
|
|
if manifest.project_root != project_root_resolved:
|
|
raise ValueError(
|
|
f"manifest.project_root ({manifest.project_root}) does not match "
|
|
f"project_root ({project_root_resolved})"
|
|
)
|
|
|
|
dest = self.skills_dest(project_root).resolve()
|
|
try:
|
|
dest.relative_to(project_root_resolved)
|
|
except ValueError as exc:
|
|
raise ValueError(
|
|
f"Integration destination {dest} escapes "
|
|
f"project root {project_root_resolved}"
|
|
) from exc
|
|
dest.mkdir(parents=True, exist_ok=True)
|
|
|
|
script_type = opts.get("script_type", "sh")
|
|
arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS")
|
|
from specify_cli.agents import CommandRegistrar
|
|
registrar = CommandRegistrar()
|
|
created: list[Path] = []
|
|
|
|
for src_file in templates:
|
|
raw = src_file.read_text(encoding="utf-8")
|
|
processed = self.process_template(raw, self.key, script_type, arg_placeholder)
|
|
frontmatter, body = registrar.parse_frontmatter(processed)
|
|
if not isinstance(frontmatter, dict):
|
|
frontmatter = {}
|
|
|
|
rendered = self._render_skill(src_file.stem, frontmatter, body)
|
|
dst_file = self.write_file_and_record(
|
|
rendered,
|
|
dest / self.command_filename(src_file.stem),
|
|
project_root,
|
|
manifest,
|
|
)
|
|
created.append(dst_file)
|
|
|
|
created.extend(self.install_scripts(project_root, manifest))
|
|
return created
|