Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
45eb22f
fix: honor template overrides for tasks-template (#2278)
Nimraakram12 Apr 22, 2026
b856a51
fix: remove stray EOF tokens from setup-tasks scripts
Nimraakram12 Apr 22, 2026
41889f1
fix: improve error messages for unresolved tasks-template
Nimraakram12 Apr 22, 2026
6685b92
test: update file inventory tests to include setup-tasks scripts
Nimraakram12 Apr 23, 2026
759cc47
fix: use Console::Error.WriteLine instead of Write-Error in setup-tas…
Nimraakram12 Apr 23, 2026
8a52583
fix: write prerequisite error messages to stderr in setup-tasks.ps1
Nimraakram12 Apr 23, 2026
ab511bf
fix: validate tasks template is a file and normalize path in setup-ta…
Nimraakram12 Apr 24, 2026
d14439b
fix: improve tasks-template error message to mention full override stack
Nimraakram12 Apr 24, 2026
5e7723b
test: add setup-tasks.sh to TestCopilotSkillsMode file inventory
Nimraakram12 Apr 24, 2026
2b56c7d
fix: skip feature-branch validation when feature.json pins FEATURE_DIR
Nimraakram12 Apr 24, 2026
860363b
fix: correct override path in tasks-template error messages
Nimraakram12 Apr 27, 2026
f1b733a
test: add integration tests for setup-tasks template resolution and b…
Nimraakram12 Apr 30, 2026
d78be91
Potential fix for pull request finding
Nimraakram22 May 1, 2026
d26efa1
Potential fix for pull request finding
Nimraakram22 May 1, 2026
e606a1f
Potential fix for pull request finding
Nimraakram22 May 1, 2026
2b94ef7
fix: correct fixture paths and add spec.md prerequisite checks
Nimraakram12 May 1, 2026
8daf1ba
fix: use correct .registry schema in preset priority test
Nimraakram12 May 1, 2026
4301fdf
fix: remove stale aaa-preset block and duplicate comment in preset pr…
Nimraakram12 May 1, 2026
e257796
fix: align preset directory names with registry IDs in priority test
Nimraakram12 May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions scripts/bash/setup-tasks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash

set -e

# Parse command line arguments
JSON_MODE=false

for arg in "$@"; do
case "$arg" in
--json) JSON_MODE=true ;;
--help|-h)
echo "Usage: $0 [--json]"
echo " --json Output results in JSON format"
echo " --help Show this help message"
exit 0
;;
*) echo "ERROR: Unknown option '$arg'" >&2; exit 1 ;;
esac
done

# Source common functions
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

# Get feature paths
_paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
eval "$_paths_output"
unset _paths_output

# Validate branch
# If feature.json pins an existing feature directory, branch naming is not required.
if ! feature_json_matches_feature_dir "$REPO_ROOT" "$FEATURE_DIR"; then
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
fi

if [[ ! -f "$IMPL_PLAN" ]]; then
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.plan first to create the implementation plan." >&2
exit 1
fi

if [[ ! -f "$FEATURE_SPEC" ]]; then
echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.specify first to create the feature structure." >&2
exit 1
fi

# Build available docs list
docs=()
[[ -f "$RESEARCH" ]] && docs+=("research.md")
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
docs+=("contracts/")
fi
[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")

# Resolve tasks template through override stack
TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true
if [[ -z "$TASKS_TEMPLATE" ]] || [[ ! -f "$TASKS_TEMPLATE" ]]; then
echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2
echo "Template 'tasks-template' was not found in any supported location (overrides, presets, extensions, or shared core). Add an override at .specify/templates/overrides/tasks-template.md, or run 'specify init' / reinstall shared infra to restore the core .specify/templates/tasks-template.md template." >&2
exit 1
fi
Comment thread
mnriem marked this conversation as resolved.

# Output results
if $JSON_MODE; then
if has_jq; then
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
fi
jq -cn \
--arg feature_dir "$FEATURE_DIR" \
--argjson docs "$json_docs" \
--arg tasks_template "${TASKS_TEMPLATE:-}" \
'{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs,TASKS_TEMPLATE:$tasks_template}'
else
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
json_docs="[${json_docs%,}]"
fi
printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s,"TASKS_TEMPLATE":"%s"}\n' \
"$(json_escape "$FEATURE_DIR")" "$json_docs" "$(json_escape "${TASKS_TEMPLATE:-}")"
fi
else
echo "FEATURE_DIR: $FEATURE_DIR"
echo "TASKS_TEMPLATE: ${TASKS_TEMPLATE:-not found}"
echo "AVAILABLE_DOCS:"
check_file "$RESEARCH" "research.md"
check_file "$DATA_MODEL" "data-model.md"
check_dir "$CONTRACTS_DIR" "contracts/"
check_file "$QUICKSTART" "quickstart.md"
fi
74 changes: 74 additions & 0 deletions scripts/powershell/setup-tasks.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env pwsh

[CmdletBinding()]
param(
[switch]$Json,
[switch]$Help
)

$ErrorActionPreference = 'Stop'

if ($Help) {
Write-Output "Usage: setup-tasks.ps1 [-Json] [-Help]"
exit 0
}

# Source common functions
. "$PSScriptRoot/common.ps1"

# Get feature paths and validate branch
$paths = Get-FeaturePathsEnv

# If feature.json pins an existing feature directory, branch naming is not required.
if (-not (Test-FeatureJsonMatchesFeatureDir -RepoRoot $paths.REPO_ROOT -ActiveFeatureDir $paths.FEATURE_DIR)) {
if (-not (Test-FeatureBranch -Branch $paths.CURRENT_BRANCH -HasGit $paths.HAS_GIT)) {
exit 1
}
}

if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
[Console]::Error.WriteLine("ERROR: plan.md not found in $($paths.FEATURE_DIR)")
[Console]::Error.WriteLine("Run /speckit.plan first to create the implementation plan.")
exit 1
}

if (-not (Test-Path $paths.FEATURE_SPEC -PathType Leaf)) {
[Console]::Error.WriteLine("ERROR: spec.md not found in $($paths.FEATURE_DIR)")
[Console]::Error.WriteLine("Run /speckit.specify first to create the feature structure.")
exit 1
}

# Build available docs list
$docs = @()
if (Test-Path $paths.RESEARCH) { $docs += 'research.md' }
if (Test-Path $paths.DATA_MODEL) { $docs += 'data-model.md' }
if ((Test-Path $paths.CONTRACTS_DIR) -and (Get-ChildItem -Path $paths.CONTRACTS_DIR -ErrorAction SilentlyContinue | Select-Object -First 1)) {
$docs += 'contracts/'
}
if (Test-Path $paths.QUICKSTART) { $docs += 'quickstart.md' }

# Resolve tasks template through override stack
$tasksTemplate = Resolve-Template -TemplateName 'tasks-template' -RepoRoot $paths.REPO_ROOT
if (-not $tasksTemplate -or -not (Test-Path -LiteralPath $tasksTemplate -PathType Leaf)) {
$expectedCoreTemplate = Join-Path $paths.REPO_ROOT '.specify/templates/tasks-template.md'
[Console]::Error.WriteLine("ERROR: Tasks template not found for repository root: $($paths.REPO_ROOT)`nTemplate resolution order: overrides -> presets -> extensions -> core.`nExpected shared/core template location: $expectedCoreTemplate`nTo continue, verify whether 'tasks-template.md' is available in '.specify/templates/overrides/', preset templates, extension templates, or restore the shared/core templates (for example by re-running 'specify init') so that '.specify/templates/tasks-template.md' exists.")
exit 1
}
Comment thread
mnriem marked this conversation as resolved.
$tasksTemplate = (Resolve-Path -LiteralPath $tasksTemplate).Path

# Output results
if ($Json) {
[PSCustomObject]@{
FEATURE_DIR = $paths.FEATURE_DIR
AVAILABLE_DOCS = $docs
TASKS_TEMPLATE = $tasksTemplate
} | ConvertTo-Json -Compress
Comment thread
mnriem marked this conversation as resolved.
} else {
Write-Output "FEATURE_DIR: $($paths.FEATURE_DIR)"
Write-Output "TASKS_TEMPLATE: $(if ($tasksTemplate) { $tasksTemplate } else { 'not found' })"
Write-Output "AVAILABLE_DOCS:"
Test-FileExists -Path $paths.RESEARCH -Description 'research.md' | Out-Null
Test-FileExists -Path $paths.DATA_MODEL -Description 'data-model.md' | Out-Null
Test-DirHasFiles -Path $paths.CONTRACTS_DIR -Description 'contracts/' | Out-Null
Test-FileExists -Path $paths.QUICKSTART -Description 'quickstart.md' | Out-Null
}
8 changes: 4 additions & 4 deletions templates/commands/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ handoffs:
prompt: Start the implementation in phases
send: true
scripts:
sh: scripts/bash/check-prerequisites.sh --json
ps: scripts/powershell/check-prerequisites.ps1 -Json
sh: scripts/bash/setup-tasks.sh --json
ps: scripts/powershell/setup-tasks.ps1 -Json
Comment thread
mnriem marked this conversation as resolved.
---

## User Input
Expand Down Expand Up @@ -58,7 +58,7 @@ You **MUST** consider the user input before proceeding (if not empty).

## Outline

1. **Setup**: Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
1. **Setup**: Run `{SCRIPT}` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").

2. **Load design documents**: Read from FEATURE_DIR:
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
Expand All @@ -76,7 +76,7 @@ You **MUST** consider the user input before proceeding (if not empty).
- Create parallel execution examples per user story
- Validate task completeness (each user story has all needed tasks, independently testable)

4. **Generate tasks.md**: Use `templates/tasks-template.md` as structure, fill with:
4. **Generate tasks.md**: Read the tasks template from TASKS_TEMPLATE (from the JSON output above) and use it as structure. If TASKS_TEMPLATE is empty, fall back to `.specify/templates/tasks-template.md`. Fill with:
Comment thread
mnriem marked this conversation as resolved.
- Correct feature name from plan.md
- Phase 1: Setup tasks (project initialization)
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
Expand Down
4 changes: 2 additions & 2 deletions tests/integrations/test_integration_base_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,11 @@ def _expected_files(self, script_variant: str) -> list[str]:

if script_variant == "sh":
for name in ["check-prerequisites.sh", "common.sh", "create-new-feature.sh",
"setup-plan.sh"]:
"setup-plan.sh", "setup-tasks.sh"]:
files.append(f".specify/scripts/bash/{name}")
else:
for name in ["check-prerequisites.ps1", "common.ps1", "create-new-feature.ps1",
"setup-plan.ps1"]:
"setup-plan.ps1", "setup-tasks.ps1"]:
files.append(f".specify/scripts/powershell/{name}")

for name in ["checklist-template.md",
Expand Down
2 changes: 2 additions & 0 deletions tests/integrations/test_integration_base_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,15 @@ def _expected_files(self, script_variant: str) -> list[str]:
".specify/scripts/bash/common.sh",
".specify/scripts/bash/create-new-feature.sh",
".specify/scripts/bash/setup-plan.sh",
".specify/scripts/bash/setup-tasks.sh",
]
else:
files += [
".specify/scripts/powershell/check-prerequisites.ps1",
".specify/scripts/powershell/common.ps1",
".specify/scripts/powershell/create-new-feature.ps1",
".specify/scripts/powershell/setup-plan.ps1",
".specify/scripts/powershell/setup-tasks.ps1",
]
# Templates
files += [
Expand Down
2 changes: 2 additions & 0 deletions tests/integrations/test_integration_base_toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ def _expected_files(self, script_variant: str) -> list[str]:
"common.sh",
"create-new-feature.sh",
"setup-plan.sh",
"setup-tasks.sh",
]:
files.append(f".specify/scripts/bash/{name}")
else:
Expand All @@ -524,6 +525,7 @@ def _expected_files(self, script_variant: str) -> list[str]:
"common.ps1",
"create-new-feature.ps1",
"setup-plan.ps1",
"setup-tasks.ps1",
]:
files.append(f".specify/scripts/powershell/{name}")

Expand Down
2 changes: 2 additions & 0 deletions tests/integrations/test_integration_base_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ def _expected_files(self, script_variant: str) -> list[str]:
"common.sh",
"create-new-feature.sh",
"setup-plan.sh",
"setup-tasks.sh",
]:
files.append(f".specify/scripts/bash/{name}")
else:
Expand All @@ -403,6 +404,7 @@ def _expected_files(self, script_variant: str) -> list[str]:
"common.ps1",
"create-new-feature.ps1",
"setup-plan.ps1",
"setup-tasks.ps1",
]:
files.append(f".specify/scripts/powershell/{name}")

Expand Down
3 changes: 3 additions & 0 deletions tests/integrations/test_integration_copilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def test_complete_file_inventory_sh(self, tmp_path):
".specify/scripts/bash/common.sh",
".specify/scripts/bash/create-new-feature.sh",
".specify/scripts/bash/setup-plan.sh",
".specify/scripts/bash/setup-tasks.sh",
".specify/templates/checklist-template.md",
".specify/templates/constitution-template.md",
".specify/templates/plan-template.md",
Expand Down Expand Up @@ -265,6 +266,7 @@ def test_complete_file_inventory_ps(self, tmp_path):
".specify/scripts/powershell/common.ps1",
".specify/scripts/powershell/create-new-feature.ps1",
".specify/scripts/powershell/setup-plan.ps1",
".specify/scripts/powershell/setup-tasks.ps1",
".specify/templates/checklist-template.md",
".specify/templates/constitution-template.md",
".specify/templates/plan-template.md",
Expand Down Expand Up @@ -614,6 +616,7 @@ def test_complete_file_inventory_skills_sh(self, tmp_path):
".specify/scripts/bash/common.sh",
".specify/scripts/bash/create-new-feature.sh",
".specify/scripts/bash/setup-plan.sh",
".specify/scripts/bash/setup-tasks.sh",
# Templates
".specify/templates/checklist-template.md",
".specify/templates/constitution-template.md",
Expand Down
2 changes: 2 additions & 0 deletions tests/integrations/test_integration_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def test_complete_file_inventory_sh(self, tmp_path):
".specify/scripts/bash/common.sh",
".specify/scripts/bash/create-new-feature.sh",
".specify/scripts/bash/setup-plan.sh",
".specify/scripts/bash/setup-tasks.sh",
".specify/templates/checklist-template.md",
".specify/templates/constitution-template.md",
".specify/templates/plan-template.md",
Expand Down Expand Up @@ -319,6 +320,7 @@ def test_complete_file_inventory_ps(self, tmp_path):
".specify/scripts/powershell/common.ps1",
".specify/scripts/powershell/create-new-feature.ps1",
".specify/scripts/powershell/setup-plan.ps1",
".specify/scripts/powershell/setup-tasks.ps1",
".specify/templates/checklist-template.md",
".specify/templates/constitution-template.md",
".specify/templates/plan-template.md",
Expand Down
Loading
Loading