Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions scripts/bash/check-prerequisites.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#
# OPTIONS:
# --json Output in JSON format
# --require-spec Require spec.md to exist (for analysis phase)
# --require-tasks Require tasks.md to exist (for implementation phase)
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
# --paths-only Only output path variables (no validation)
Expand All @@ -24,6 +25,7 @@ set -e

# Parse command line arguments
JSON_MODE=false
REQUIRE_SPEC=false
REQUIRE_TASKS=false
INCLUDE_TASKS=false
PATHS_ONLY=false
Expand All @@ -34,6 +36,9 @@ while [[ $# -gt 0 ]]; do
--json)
JSON_MODE=true
;;
--require-spec)
REQUIRE_SPEC=true
;;
--require-tasks)
REQUIRE_TASKS=true
;;
Expand All @@ -59,6 +64,7 @@ Consolidated prerequisite checking for Spec-Driven Development workflow.

OPTIONS:
--json Output in JSON format
--require-spec Require spec.md to exist (for analysis phase)
--require-tasks Require tasks.md to exist (for implementation phase)
--include-tasks Include tasks.md in AVAILABLE_DOCS list
--paths-only Only output path variables (no prerequisite validation)
Expand Down Expand Up @@ -142,6 +148,13 @@ if [[ ! -f "$IMPL_PLAN" ]]; then
exit 1
fi

# Check for spec.md if required
if $REQUIRE_SPEC && [[ ! -f "$FEATURE_SPEC" ]]; then
echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
echo "Run $(format_speckit_command specify "$REPO_ROOT") first to create the feature specification." >&2
exit 1
fi

# Check for tasks.md if required
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
Expand Down
14 changes: 13 additions & 1 deletion scripts/powershell/check-prerequisites.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
#
# OPTIONS:
# -Json Output in JSON format
# -RequireTasks Require tasks.md to exist (for implementation phase)
# -RequireSpec Require spec.md to exist (for analysis phase)
# -RequireSpec Require spec.md to exist (for analysis phase)
-RequireTasks Require tasks.md to exist (for implementation phase)
# -IncludeTasks Include tasks.md in AVAILABLE_DOCS list
# -PathsOnly Only output path variables (no validation)
# -Template NAME Include composed template content in JSON output
Expand All @@ -18,6 +20,7 @@
[CmdletBinding()]
param(
[switch]$Json,
[switch]$RequireSpec,
[switch]$RequireTasks,
[switch]$IncludeTasks,
[switch]$PathsOnly,
Expand All @@ -36,6 +39,7 @@ Consolidated prerequisite checking for Spec-Driven Development workflow.

OPTIONS:
-Json Output in JSON format
-RequireSpec Require spec.md to exist (for analysis phase)
-RequireTasks Require tasks.md to exist (for implementation phase)
-IncludeTasks Include tasks.md in AVAILABLE_DOCS list
-PathsOnly Only output path variables (no prerequisite validation)
Expand Down Expand Up @@ -105,6 +109,14 @@ if (-not (Test-Path $paths.IMPL_PLAN -PathType Leaf)) {
exit 1
}

# Check for spec.md if required
if ($RequireSpec -and -not (Test-Path $paths.FEATURE_SPEC -PathType Leaf)) {
[Console]::Error.WriteLine("ERROR: spec.md not found in $($paths.FEATURE_DIR)")
$specifyCommand = Format-SpecKitCommand -CommandName 'specify' -RepoRoot $paths.REPO_ROOT
[Console]::Error.WriteLine("Run $specifyCommand first to create the feature specification.")
exit 1
}

# Check for tasks.md if required
if ($RequireTasks -and -not (Test-Path $paths.TASKS -PathType Leaf)) {
[Console]::Error.WriteLine("ERROR: tasks.md not found in $($paths.FEATURE_DIR)")
Expand Down
14 changes: 14 additions & 0 deletions scripts/python/check_prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def _json_line(payload: object) -> str:

OPTIONS:
--json Output in JSON format
--require-spec Require spec.md to exist (for analysis phase)
--require-tasks Require tasks.md to exist (for implementation phase)
--include-tasks Include tasks.md in AVAILABLE_DOCS list
--paths-only Only output path variables (no prerequisite validation)
Expand All @@ -59,6 +60,7 @@ def _json_line(payload: object) -> str:
@dataclass(frozen=True)
class Args:
json_mode: bool = False
require_spec: bool = False
require_tasks: bool = False
include_tasks: bool = False
paths_only: bool = False
Expand All @@ -67,6 +69,7 @@ class Args:

def _parse_args(argv: list[str]) -> Args:
json_mode = False
require_spec = False
require_tasks = False
include_tasks = False
paths_only = False
Expand All @@ -77,6 +80,8 @@ def _parse_args(argv: list[str]) -> Args:
arg = argv[index]
if arg == "--json":
json_mode = True
elif arg == "--require-spec":
require_spec = True
elif arg == "--require-tasks":
require_tasks = True
elif arg == "--include-tasks":
Expand Down Expand Up @@ -105,6 +110,7 @@ def _parse_args(argv: list[str]) -> Args:

return Args(
json_mode=json_mode,
require_spec=require_spec,
require_tasks=require_tasks,
include_tasks=include_tasks,
paths_only=paths_only,
Expand Down Expand Up @@ -230,6 +236,14 @@ def main(argv: list[str] | None = None) -> int:
)
return 1

if args.require_spec and not paths.feature_spec.is_file():
print(f"ERROR: spec.md not found in {paths.feature_dir}", file=sys.stderr)
print(
f"Run {format_speckit_command('specify', paths.repo_root)} first to create the feature specification.",
file=sys.stderr,
)
return 1

if args.require_tasks and not paths.tasks.is_file():
print(f"ERROR: tasks.md not found in {paths.feature_dir}", file=sys.stderr)
print(
Expand Down
4 changes: 2 additions & 2 deletions templates/commands/analyze.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation.
scripts:
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
sh: scripts/bash/check-prerequisites.sh --json --require-spec --require-tasks --include-tasks
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireSpec -RequireTasks -IncludeTasks
py: scripts/python/check_prerequisites.py --json --require-tasks --include-tasks
---

Expand Down
4 changes: 2 additions & 2 deletions templates/commands/converge.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it.
scripts:
sh: scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireTasks -IncludeTasks
sh: scripts/bash/check-prerequisites.sh --json --require-spec --require-tasks --include-tasks
ps: scripts/powershell/check-prerequisites.ps1 -Json -RequireSpec -RequireTasks -IncludeTasks
py: scripts/python/check_prerequisites.py --json --require-tasks --include-tasks
---

Expand Down
36 changes: 36 additions & 0 deletions tests/test_check_prerequisites_python_parity.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,42 @@ def test_python_json_output_matches_bash(prereq_repo: Path, args: tuple[str, ...
assert _json_stdout(py) == _json_stdout(bash)


@requires_bash
def test_python_require_spec_matches_bash(prereq_repo: Path) -> None:
feat = prereq_repo / "specs" / "001-my-feature"
feat.mkdir(parents=True)
(feat / "plan.md").write_text("# plan\n", encoding="utf-8")
(feat / "tasks.md").write_text("# tasks\n", encoding="utf-8")
_write_feature_json(prereq_repo)

# spec.md is missing, and without the flag that stays the caller's problem
bash_without = _run(_bash_cmd(prereq_repo, "--json", "--require-tasks"), prereq_repo)
py_without = _run(_py_cmd(prereq_repo, "--json", "--require-tasks"), prereq_repo)
assert py_without.returncode == bash_without.returncode == 0

# with the flag both variants fail the same way and name the same command
bash_missing = _run(
_bash_cmd(prereq_repo, "--json", "--require-spec", "--require-tasks"), prereq_repo
)
py_missing = _run(
_py_cmd(prereq_repo, "--json", "--require-spec", "--require-tasks"), prereq_repo
)
assert py_missing.returncode == bash_missing.returncode == 1
assert py_missing.stderr == bash_missing.stderr
assert "spec.md not found" in bash_missing.stderr

# and once the spec exists the flag is satisfied
(feat / "spec.md").write_text("# spec\n", encoding="utf-8")
bash_present = _run(
_bash_cmd(prereq_repo, "--json", "--require-spec", "--require-tasks"), prereq_repo
)
py_present = _run(
_py_cmd(prereq_repo, "--json", "--require-spec", "--require-tasks"), prereq_repo
)
assert py_present.returncode == bash_present.returncode == 0
assert _json_stdout(py_present) == _json_stdout(bash_present)


@requires_bash
def test_python_text_output_matches_bash(prereq_repo: Path) -> None:
feat = prereq_repo / "specs" / "001-my-feature"
Expand Down