Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[Bug]: preset-provided scripts are never resolved — `type: script` and `$CORE_SCRIPT` have no effect

未关闭
#4,551 8 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
58/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
bash, python
领域
cli, tooling

调研方向

从列出的 scaffold 和 re-scaffold 复现开始,然后跟踪 specify_cli/presets/_commands.py:495 中的 caller,直到 specify_cli/presets/init.py 中的脚本处理逻辑。将 core_pack/scripts/bash/common.sh:509 和 core_pack/scripts/python/common.py:308 中的 .md-only 解析路径与现有的脚本组合逻辑及其单元测试覆盖率进行比较。完成的标准是:文档所述的脚本行为能够端到端地工作,或者不受支持的脚本声明会被拒绝,并且文档得到修正。

由索引模型根据 Issue 内容生成。

描述

author-awaiting bug-assess needs-triage severity-low triage-nice-to-have
Bug Description

The preset reference documents scripts as a first-class layer of the resolution stack:

"Presets can provide command files, template files (like plan-template.md), and script files.
Templates and scripts are looked up from the stack when Spec Kit needs them.
Scripts support replace and wrap; script wrappers use $CORE_SCRIPT as the placeholder."

and lists the project-local override location as .specify/templates/overrides/scripts/.

None of this takes effect. A preset may declare type: script, and specify preset add accepts and
installs it without complaint, but nothing ever asks the resolver for a script, so the file that
actually runs is always the core one.

Three independent places in the shipped code show why:

  1. No caller requests the script type. The only production call into preset composition
    determines the type as
    template_type = "command" if is_command else "template"
    (specify_cli/presets/_commands.py:495). The value "script" is never passed, so
    PresetResolver.resolve() / collect_all_layers() are never invoked for scripts — even though
    both handle template_type == "script" (specify_cli/presets/__init__.py:5524, 5531, 5613,
    and the $CORE_SCRIPT substitution at :6168).
  2. The Bash runtime resolves only .md. resolve_template() in
    core_pack/scripts/bash/common.sh:509 looks for "$base/overrides/${template_name}.md", and
    every subsequent tier appends .md as well. There is no .sh branch and no
    overrides/scripts/ lookup anywhere in the file.
  3. The Python twin mirrors that. core_pack/scripts/python/common.py:308 documents its order as
    "mirrors resolve_template in scripts/bash/common.sh" and resolves
    overrides/f"{template_name}.md" only.
    So the composition engine for scripts exists and is reachable by unit test, but is not wired to
    anything that runs.

This looks like the script half of #2132 / #2133 never landed on the runtime side, while #3143
documented it as shipped. I could not find an existing report: no open issue or PR with "script" in
the title covers it (#1964 is adjacent but is about extensions and has been open since March 2026),
and #4443 / #4445 concern PyYAML availability, not resolution.
runs the command.
Suggestion. Either wire a caller for template_type="script" and add .sh resolution to
common.sh and its Python twin, or — if scripts are not meant to be resolvable yet — adjust
docs/reference/presets.md and have preset.yml validation reject type: script so the
declaration fails loudly instead of silently.

Steps to Reproduce
# 1. Scaffold a project
mkdir demo && cd demo && git init
specify init . --integration claude --script sh --force
 
# 2. Record the core script's hash
sha256sum .specify/scripts/bash/setup-plan.sh
 
# 3a. Try the documented project-local override
mkdir -p .specify/templates/overrides/scripts
printf '#!/usr/bin/env bash\necho "OVERRIDE RAN"\n' \
  > .specify/templates/overrides/scripts/setup-plan.sh
chmod +x .specify/templates/overrides/scripts/setup-plan.sh
 
# 3b. Or try a preset that provides the script
mkdir -p /tmp/p/scripts
cat > /tmp/p/preset.yml <<'YAML'
schema_version: "1.0"
preset:
  id: "gate"
  name: "Gate"
  version: "1.0.0"
  description: "Wraps setup-plan."
requires:
  speckit_version: ">=1.0.0"
provides:
  templates:
    - type: "script"
      name: "setup-plan"
      file: "scripts/setup-plan.sh"
      strategy: "wrap"
YAML
printf '#!/usr/bin/env bash\necho "BEFORE"\n$CORE_SCRIPT "$@"\n' > /tmp/p/scripts/setup-plan.sh
specify preset add --dev /tmp/p
 
# 4. Re-scaffold and compare
specify init . --integration claude --script sh --force
sha256sum .specify/scripts/bash/setup-plan.sh
Expected Behavior

.specify/scripts/bash/setup-plan.sh is composed from the stack, so the wrapper runs first and
delegates to the core script through $CORE_SCRIPT — matching the documented behaviour that
"templates and scripts are looked up from the stack when Spec Kit needs them".

Actual Behavior

The hash in step 4 is identical to step 2 and still matches
.specify/integrations/speckit.manifest.json. The override file and the preset-provided script are
never read. specify preset add reports success and specify preset list shows the preset as
enabled, so there is no signal that the declared script is inert.

Tried additionally, all without effect: strategy: replace as well as wrap,
specify integration upgrade, specify init --here --force. Same result on 1.0.1.

Specify CLI Version

1.0.6

AI Agent

Claude Code

Operating System

macOS 26.6.2

Python Version

Python 3.11.15

Error Logs
# There is no error output — that is part of the problem.
# A preset declaring a script installs cleanly and is reported as active:
 
$ specify preset add --dev /tmp/p
Installing preset from /tmp/p...
✓ Preset 'Gate' v1.0.0 installed (priority 10)
 
$ specify preset list
Installed Presets (in resolution order — highest precedence first)
 
  Gate (gate) v1.0.0 — enabled — priority 10
    Wraps setup-plan.
    Templates: 1
 
# …while the script it provides is never resolved or executed.
Additional Context

Why this matters. The documented mechanism is the natural way to add a step in front of a core
script: the preset wraps setup-plan.sh, the core script stays untouched underneath, and Spec Kit
maintains the relationship. Any preset that needs to run a check, a fetch, or a validation before
the command body reads its inputs wants exactly this.

Current workaround, for anyone hitting the same wall. Override the command instead of the
script. A preset providing speckit.plan with strategy: "wrap" and a five-line body:

---
scripts:
  sh: scripts/bash/setup-plan-wrapper.sh --json
---
{CORE_TEMPLATE}

points the materialised command at a project-owned script, which ends with
exec "$ROOT/.specify/scripts/bash/setup-plan.sh" "$@". This works and survives upgrades, because
{CORE_TEMPLATE} keeps pulling the command body from core.

It has two costs that the documented mechanism would remove:

  • two artefacts instead of one. The script cannot travel inside the preset, so it lives beside
    it as a separate committed file, and it needs a distinct filename because it cannot replace
    setup-plan.sh.
  • nothing validates the link. A preset whose frontmatter names a script that does not exist
    installs without any warning and is listed as healthy; the dead path only surfaces when a user
    runs the command.
    Suggestion. Either wire a caller for template_type="script" and add .sh resolution to
    common.sh and its Python twin, or — if scripts are not meant to be resolvable yet — adjust
    docs/reference/presets.md and have preset.yml validation reject type: script so the
    declaration fails loudly instead of silently.
主要语言
Python
星标
138k
派生
12.4k
平均合并
3 天 6 小时
30 天内合并 PR
145

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

github/spec-kit 的其他 Issue

查看 github/spec-kit 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。