Skip to content

specify init does not copy templates/commands/ to .specify/templates/commands/ — wrap composition strategy fails #3086

@djimit

Description

@djimit

specify init does not copy templates/commands/ to .specify/templates/commands/

Problem

During specify init, the install_shared_infra function in shared_infra.py copies template files from the bundled templates/ directory to .specify/templates/. However, it only processes files at the top level (src.is_file() check), skipping subdirectories — specifically templates/commands/.

This means the core command templates (e.g., implement.md, specify.md, plan.md, etc.) are installed directly to the agent directory (e.g., .opencode/commands/speckit.implement.md) via the integration system, but they are not kept in .specify/templates/commands/ for the preset resolver to find.

Impact

Preset authors who use the wrap composition strategy for command overrides (e.g., wrapping speckit.implement with a preamble) cannot resolve the core command as a base layer. The PresetResolver.collect_all_layers() method looks for core commands at:

c = self.templates_dir / "commands" / f"{template_name}.md"  # or stem fallback

Since .specify/templates/commands/ doesn't exist after specify init, the resolver finds no base layer, composition fails, and the command is silently not registered (registered_commands stays empty {}).

Reproduction

specify init --here --integration opencode --script sh --ignore-agent-tools
# .specify/templates/commands/ does NOT exist
# .opencode/commands/speckit.implement.md exists (installed by integration)

# Create a preset with a wrap strategy for speckit.implement
specify preset add --dev /path/to/my-preset
# registered_commands is {} — the wrap composition failed silently

Workaround

Manually copy command templates after init:

mkdir -p .specify/templates/commands
cp <spec-kit-source>/templates/commands/*.md .specify/templates/commands/

Suggested fix

In shared_infra.py, the install_shared_infra function should also copy the templates/commands/ subdirectory (with the same overwrite/manifest logic). Something like:

# After the top-level template files loop:
commands_src = templates_src / "commands"
if commands_src.is_dir():
    dest_commands = dest_templates / "commands"
    if _ensure_or_bucket_dir(dest_commands):
        for src_path in commands_src.iterdir():
            if not src_path.is_file() or src_path.name.startswith("."):
                continue
            dst = dest_commands / src_path.name
            rel = dst.relative_to(project_path).as_posix()
            # ... same overwrite/manifest logic as above ...

Alternatively, the PresetResolver could fall back to reading core commands from the agent directory (e.g., .opencode/commands/speckit.implement.md) when .specify/templates/commands/ doesn't have them.

Environment

  • Spec Kit v0.11.3
  • macOS Darwin arm64
  • Integration: opencode

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions