Skip to content

[Bug]: Template files are inconsistently formatted #4335

Open

Description

@961028

Bug Description

The speckit template files are inconsistently formatted.

Steps to Reproduce

  1. Clone the latest release and enter it:

    git clone --depth 1 --branch v1.0.1 https://github.com/github/spec-kit.git
    cd spec-kit
  2. Read the five templates in templates/:

    for f in spec plan tasks checklist constitution; do
      echo "===== $f-template.md ====="; cat "templates/$f-template.md"
    done

Version checked: v1.0.1, tagged 2026-08-21. templates/ tree hash 8730ddb.

Expected Behavior

Consistent templates.

Actual Behavior

Inconsistent templates.

Specify CLI Version

1.0.0

AI Agent

Claude Code

Operating System

Not relevant

Python Version

Not relevant

Error Logs

Additional Context

I've been working with the stock templates and noticed a handful of small inconsistencies between them. Individually none of these matter much, but I think this is a place where consistency pays off more than usual: these templates are the standard the model follows when it fills them in. If the standard is ambiguous, the output inherits the ambiguity. So I'd rather raise them than not.

I looked at spec-template.md, plan-template.md, tasks-template.md, checklist-template.md, and constitution-template.md.

Issues

The pointer to the upstream document has three names and three formats. Three templates each point at the document they derive from, and no two agree on what to call it:

Template Field Value
plan-template.md:5 Input Feature specification from /specs/[###-feature-name]/spec.md
tasks-template.md:8 Input Design documents from /specs/[###-feature-name]/
checklist-template.md:5 Feature [Link to spec.md or relevant documentation]

plan-template.md names it twice, under two names, in two formats: line 3 carries **Spec**: [link] (an unfilled placeholder) and line 5 carries the Input field above (a backticked path to that same document).

The values also differ in mechanism, not just wording: two are literal paths containing a [###-feature-name] placeholder, one is a bare [Link to ...] placeholder. Nothing states which is expected.

spec-template.md uses Input for something else. Line 9 is **Input**: User description: "$ARGUMENTS" — the raw user request, substituted by the command, not a pointer to an upstream document. That may be a reasonable use of the word, but it means Input denotes two unrelated things across the template set: provenance in plan/tasks, captured arguments in spec.

The date fields could be more precise. spec-template.md and checklist-template.md use Created; plan-template.md uses Date, which doesn't say whether it means created or last-edited. Date Created and Date Last Edited would be unambiguous, and would let a template carry one, the other, or both as appropriate.

Some of these fields could be filled mechanically, and currently are not. $ARGUMENTS shows the templates already support machine substitution, but the values that are mechanically derivable are left to the model:

  • [DATE] is never substituted. create-new-feature.sh:362 copies the spec template through verbatim (printf '%s' "$SPEC_TEMPLATE_CONTENT" > "$SPEC_FILE") with no rewriting of the body. The only date logic in the scripts (create-new-feature.sh:263, create_new_feature.py:276, and the PowerShell equivalent) generates a timestamp for the feature number in a fallback path — it never touches [DATE]. So the creation date reaches the model as a literal placeholder for it to fill in. templates/commands/specify.md:152 emits the same **Created**: [DATE] into a generated checklist, reproducing the gap downstream.
  • The upstream pointer is always derivable. The Input of plan.md is always the spec.md of the same feature.
  • Could [FEATURE NAME] be substituted too? create-new-feature.sh already computes BRANCH_NAME and FEATURE_NUM and returns them on stdout, so a feature identifier exists at generation time. I have not looked into whether the name itself is available at that point, or whether leaving it to the model is deliberate.

Status exists in one template only, with undefined values. spec-template.md ships Status: Draft. Nothing I could find defines the other legal values, and no other template carries the field. I'm unsure whether the intent is that only specs are status-tracked, or whether it's meant to be broader.

Header layout varies, including within one file. Most templates put one field per line. plan-template.md pipe-joins three fields on its first metadata line and then switches to one-per-line.

tasks-template.md is the only template with YAML front matter, and there's a blank line between the opening --- and the description: key, which I believe stops most parsers from reading it as front matter at all. Since no other template has front matter, I wasn't sure whether it's needed here. Either way I think it might not be a bad idea for all front matter to be YAML, but I assume you have already considered this.

Less important

Instructional comments use a few different conventions. Some blocks are marked ACTION REQUIRED, others are boxed IMPORTANT … SAMPLE blocks, and constitution-template.md uses inline <!-- Example: -->. The Example: usage reads as genuinely different to me — it labels illustrative values rather than flagging content to delete — but the first two seem to overlap.

Sample content isn't flagged uniformly. tasks-template.md and checklist-template.md both say explicitly not to keep the sample items. spec-template.md ships three sample user stories without an equivalent instruction, though they look like the same kind of placeholder.

Probably deliberate

A couple of differences look deliberate rather than accidental, and I'd leave them alone: constitution-template.md reasonably has no branch, input, or status fields since it isn't per-feature, and its version/ratified footer is a document lifecycle rather than header metadata.

Caveats

Happy to be told some of these are intentional. I may be missing context on how the commands consume these fields.

The field names, values, and line numbers above are quoted from the v1.0.1 tag, and the claim that [DATE] is never substituted was checked against the scripts. The reasoning about intent is mine and may be missing context. I don't have time to spend more time on this without knowing that the issue is something you think is important enough to dive into. If you agree that this is a real issue I could sit down and do the actual work after we decide what the appropriate solution should be.

Possible long-term solution

Perhaps the long term solution should even be to create some kind of template generator. This would allow mechanically verifiable consistency and the possibility for users to change the formatting of templates if they want to, or create new ones which might be needed for extensions. For example, I got started with this review because I prefer links in my documents to be proper markdown links so they can be followed by humans.

Activity

  1. github-actions commented on Aug 31, 2026

    @github-actions
    Contributor

    Bug assessment — template-inconsistent-formatting: Likely valid, needs reproduction · severity low


    Bug Assessment: Template files are inconsistently formatted

    Report (summarized)

    Reporter @961028 audited the five core templates in templates/ at v1.0.1 and catalogued a set of small inconsistencies: differing names and formats for the "upstream document" pointer, ambiguous date field names, [DATE] placeholders that are never substituted by any script, a broken YAML front-matter block in tasks-template.md, and varying conventions for instructional comments and sample content. No stack trace or runtime error is involved — this is a template quality / consistency issue.

    Symptom

    The five templates (spec-template.md, plan-template.md, tasks-template.md, checklist-template.md, constitution-template.md) use different field names, placeholder conventions, and structural layouts for equivalent concepts. Because AI agents consume these templates as the specification they fill in, ambiguity in the template propagates into generated documents.

    Reproduction

    1. Clone v1.0.1: git clone --depth 1 --branch v1.0.1 https://github.com/github/spec-kit.git && cd spec-kit
    2. Read the five templates under templates/ and compare metadata headers.
    3. Observe the discrepancies listed below.

    Suspected Code Paths

    Confirmed by reading the checked-out codebase at the paths cited:

    • templates/plan-template.md:3 — three fields (Branch, Date, Spec) pipe-joined on one line; then switches to one-field-per-line on lines 5+.
    • templates/plan-template.md:5 — **Input**: Feature specification from \/specs/[###-feature-name]/spec.md``
    • templates/tasks-template.md:1–4 — YAML front matter with a blank line between --- and description:, which causes most YAML parsers to reject it as front matter.
    • templates/tasks-template.md:8 — **Input**: Design documents from \/specs/[###-feature-name]/``
    • templates/checklist-template.md:5 — **Feature**: [Link to spec.md or relevant documentation]
    • templates/spec-template.md:5 — **Created**: [DATE]; plan-template.md:3 uses **Date**: [DATE] — different field names for the same concept.
    • templates/spec-template.md:9 — **Input**: User description: "$ARGUMENTS" — reuses Input to mean the raw user request, not a provenance pointer.
    • templates/spec-template.md:7 — **Status**: Draft — only template carrying a Status field.
    • templates/commands/specify.md:152 — emits **Created**: [DATE] into generated checklists, inheriting the unsubstituted placeholder.
    • scripts/bash/create-new-feature.sh:362 — printf '%s' "$SPEC_TEMPLATE_CONTENT" > "$SPEC_FILE" — copies the spec template verbatim with no [DATE] substitution.
    • scripts/bash/create-new-feature.sh:263 — date logic (date +%Y%m%d-%H%M%S) is used only for generating a fallback feature number, never for substituting [DATE] in template bodies.

    Equivalent gaps exist in scripts/python/create_new_feature.py (Python port) and the PowerShell variant.

    Root Cause Hypothesis

    There is no canonical "template schema" that all five files are validated against. Each template was authored independently, leading to field-name drift (Date vs. Created), role-name drift (Input for two unrelated concepts), and format drift (inline vs. line-per-field metadata). The [DATE] placeholder gap is a concrete functional regression: the scripts copy template content verbatim and never substitute [DATE], so the creation date is always left for the AI model to fill in rather than being set mechanically at generation time. The YAML front-matter issue in tasks-template.md is a separate minor defect. Confidence: high on the factual discrepancies; medium on whether the [DATE] omission is intentional design or an oversight (the report argues omission, the code confirms it).

    Proposed Remediation

    Preferred: Define a shared metadata header schema for feature-scoped templates (spec, plan, tasks, checklist) and bring all four into alignment:

    1. Standardize field names: Created (not Date), Branch, Spec (link to spec.md), Input (raw user request in spec only; provenance pointer elsewhere).
    2. Fix tasks-template.md YAML front matter: remove the blank line between --- and description:, or drop front matter entirely to match the other templates.
    3. Substitute [DATE] at generation time in create-new-feature.sh, create_new_feature.py, and the PowerShell equivalent — a single sed / string-replace pass at the point where the spec file is written (scripts/bash/create-new-feature.sh:362 and Python/PS equivalents).
    4. Propagate the same fix to templates/commands/specify.md:152 (generated checklist).
    5. Optionally consolidate instructional comment conventions (ACTION REQUIRED vs. IMPORTANT blocks) for readability, but this is lower priority.

    Alternatives:

    • Leave [DATE] substitution to the AI model (current behavior) and simply document that choice — avoids script changes but keeps the inconsistency.
    • Introduce a template linter (CI check) that asserts required fields are present in each template — catches regressions automatically but is build infrastructure work.

    Files likely to change:

    • templates/spec-template.md
    • templates/plan-template.md
    • templates/tasks-template.md
    • templates/checklist-template.md
    • templates/commands/specify.md
    • scripts/bash/create-new-feature.sh
    • scripts/python/create_new_feature.py
    • scripts/powershell/create-new-feature.ps1 (or equivalent)

    Tests to add or update:

    • Add a test asserting [DATE] is not present in a spec file generated by create-new-feature (all three script types).
    • Add a test asserting tasks-template.md front matter is valid YAML (no blank line between --- and first key).
    • Extend existing template-rendering tests to assert that generated plan/tasks/checklist files carry the same canonical field names.

    Risks & Considerations

    • Changing field names in templates is a breaking change for existing projects that reference those field names in prompts or workflows — low blast radius for a template-only repo, but worth a clear changelog entry.
    • Substituting [DATE] in scripts must handle edge cases: timezone selection, format consistency across sh/ps/py variants.
    • The reporter's "template generator" idea is interesting long-term but out of scope for this fix.

    Open Questions

    • [NEEDS CLARIFICATION: Is the [DATE] placeholder intentionally left for the AI model to fill in, or is it an oversight?]
    • [NEEDS CLARIFICATION: Is the Status field in spec-template.md intended to be carried by other templates as well?]
    • [NEEDS CLARIFICATION: Are there defined valid values for Status beyond Draft?]
    • [NEEDS CLARIFICATION: Is tasks-template.md YAML front matter intentional or a copy-paste artifact? No other template uses it.]

    Generated by 🐛 Assess Bug from Labeled Issue for issue #4335 · 104.5 AIC · ⌖ 11 AIC · ⊞ 34.1K · ◷

  2. added
    triage-can-waitVerdict: valid and in-scope but deprioritized; held behind the evidence gate
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug-assessseverity-lowtriage-can-waitVerdict: valid and in-scope but deprioritized; held behind the evidence gate

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions