Skip to content

Commit 3fb7d36

Browse files
committed
fix(ask): wire speckit.ask into ecosystem — command map, routing, 30 tests (89 total)
1 parent e3b4fc6 commit 3fb7d36

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

templates/commands/ask.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ Use this routing table:
132132
| A task is missing or mis-ordered | `/speckit.tasks` |
133133
| An error or broken behavior was surfaced | `/speckit.fix "[the error]"` |
134134
| Everything looks correct | No action needed — state this explicitly |
135+
| Tasks are ready to execute | `/speckit.implement` |
136+
| Edge cases should be tracked as issues | `/speckit.taskstoissues` |
135137
| Cross-feature impact is possible | `/speckit.analyze` (after the fix or change) |
136138

137139
**Multiple suggestions are allowed** — rank them by urgency (most blocking first).

templates/commands/fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ You **MUST** consider the user input before proceeding (if not empty). This may
2929
/speckit.implement → executes tasks
3030
/speckit.taskstoissues → converts tasks into GitHub issues
3131
/speckit.fix → (you) post-implementation error correction
32+
/speckit.ask → ask any question, get a grounded answer and routing suggestion
3233
```
3334

3435
---

tests/test_fix_feature.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,161 @@ def test_fix_command_in_commands_directory(self):
478478
assert fix_cmd.is_file(), (
479479
"templates/commands/fix.md must exist for scaffold loop inclusion"
480480
)
481+
482+
483+
# ---------------------------------------------------------------------------
484+
# 5. ask.md — grounded Q&A command
485+
# ---------------------------------------------------------------------------
486+
487+
_ASK_CMD = _REPO_ROOT / "templates" / "commands" / "ask.md"
488+
489+
490+
class TestAskCommand:
491+
"""Validate the /speckit.ask command template."""
492+
493+
@pytest.fixture(scope="class")
494+
def content(self) -> str:
495+
return _ASK_CMD.read_text(encoding="utf-8")
496+
497+
@pytest.fixture(scope="class")
498+
def frontmatter(self, content) -> dict:
499+
fm, _ = _parse_frontmatter(content)
500+
return fm
501+
502+
@pytest.fixture(scope="class")
503+
def body(self, content) -> str:
504+
_, b = _parse_frontmatter(content)
505+
return b
506+
507+
# --- File & frontmatter ---
508+
509+
def test_file_exists(self):
510+
assert _ASK_CMD.is_file(), "templates/commands/ask.md is missing"
511+
512+
def test_frontmatter_parseable(self, content):
513+
assert content.startswith("---"), "ask.md must start with YAML frontmatter"
514+
fm, _ = _parse_frontmatter(content)
515+
assert isinstance(fm, dict)
516+
517+
def test_has_nonempty_description(self, frontmatter):
518+
assert frontmatter.get("description"), "ask.md frontmatter must have a non-empty 'description'"
519+
520+
def test_has_scripts_sh_and_ps(self, frontmatter):
521+
scripts = frontmatter.get("scripts", {}) or {}
522+
assert "sh" in scripts, "ask.md frontmatter must have 'scripts.sh'"
523+
assert "ps" in scripts, "ask.md frontmatter must have 'scripts.ps'"
524+
525+
def test_scripts_reference_check_prerequisites(self, frontmatter):
526+
scripts = frontmatter.get("scripts", {}) or {}
527+
assert "check-prerequisites.sh" in scripts.get("sh", "")
528+
assert "check-prerequisites.ps1" in scripts.get("ps", "")
529+
530+
def test_has_arguments_placeholder(self, body):
531+
assert "$ARGUMENTS" in body, "ask.md must contain $ARGUMENTS placeholder"
532+
533+
def test_no_toml_double_brace_leak(self, body):
534+
assert "{{args}}" not in body
535+
536+
# --- Phase 0: question classification ---
537+
538+
def test_phase_zero_present(self, body):
539+
assert "Phase 0" in body, "ask.md must contain Phase 0 (question classification)"
540+
541+
def test_classification_table_covers_workflow(self, body):
542+
assert "workflow" in body, "ask.md Phase 0 must cover 'workflow' question category"
543+
544+
def test_classification_table_covers_spec(self, body):
545+
assert "spec" in body.lower(), "ask.md Phase 0 must cover 'spec' question category"
546+
547+
def test_classification_table_covers_constitution(self, body):
548+
assert "constitution" in body, "ask.md Phase 0 must cover 'constitution' question category"
549+
550+
def test_fast_redirect_to_fix(self, body):
551+
"""Error questions must be immediately redirected to /speckit.fix."""
552+
assert "speckit.fix" in body, "ask.md must redirect error questions to /speckit.fix"
553+
554+
def test_fast_redirect_to_specify(self, body):
555+
"""Feature-gap questions must be immediately redirected to /speckit.specify."""
556+
assert "speckit.specify" in body, "ask.md must redirect feature requests to /speckit.specify"
557+
558+
# --- Phase 2: structured answer block ---
559+
560+
def test_answer_block_has_question_field(self, body):
561+
assert "QUESTION" in body, "ask.md Phase 2 answer block must contain QUESTION field"
562+
563+
def test_answer_block_has_category_field(self, body):
564+
assert "CATEGORY" in body, "ask.md Phase 2 answer block must contain CATEGORY field"
565+
566+
def test_answer_block_has_grounded_in_field(self, body):
567+
assert "GROUNDED IN" in body, "ask.md Phase 2 answer block must contain GROUNDED IN field"
568+
569+
def test_answer_block_has_confidence_field(self, body):
570+
assert "CONFIDENCE" in body, "ask.md Phase 2 answer block must contain CONFIDENCE field"
571+
572+
def test_constitution_read_when_decision_touched(self, body):
573+
"""constitution.md must be loaded when the answer touches a project principle."""
574+
assert "constitution" in body.lower(), (
575+
"ask.md must instruct loading constitution.md when architectural decisions are involved"
576+
)
577+
578+
# --- Phase 3: routing ---
579+
580+
def test_routing_section_present(self, body):
581+
assert "SUGGESTED NEXT" in body or "Phase 3" in body, (
582+
"ask.md must contain a routing phase (Phase 3 / SUGGESTED NEXT)"
583+
)
584+
585+
def test_routing_covers_clarify(self, body):
586+
assert "speckit.clarify" in body
587+
588+
def test_routing_covers_plan(self, body):
589+
assert "speckit.plan" in body
590+
591+
def test_routing_covers_analyze(self, body):
592+
assert "speckit.analyze" in body
593+
594+
def test_routing_covers_tasks(self, body):
595+
assert "speckit.tasks" in body
596+
597+
def test_routing_covers_implement(self, body):
598+
assert "speckit.implement" in body, (
599+
"ask.md routing must include /speckit.implement for when tasks are ready to execute"
600+
)
601+
602+
def test_routing_covers_taskstoissues(self, body):
603+
assert "speckit.taskstoissues" in body, (
604+
"ask.md routing must include /speckit.taskstoissues for edge-case tracking"
605+
)
606+
607+
def test_routing_requires_reason_per_suggestion(self, body):
608+
"""Each routing suggestion must be accompanied by a reason (no blind suggestions)."""
609+
assert "reason" in body.lower() or "why" in body.lower() or "warranted" in body.lower(), (
610+
"ask.md must require that every routing suggestion includes a reason"
611+
)
612+
613+
# --- Phase 4: confidence check ---
614+
615+
def test_low_confidence_triggers_clarification(self, body):
616+
assert "low" in body.lower() and "CONFIDENCE" in body, (
617+
"ask.md must handle low-confidence answers with a clarification block"
618+
)
619+
620+
def test_max_two_clarifying_questions(self, body):
621+
assert "2" in body or "two" in body.lower(), (
622+
"ask.md must cap clarifying questions at 2 when confidence is low"
623+
)
624+
625+
# --- Bundle inclusion ---
626+
627+
def test_ask_stem_in_commands_directory(self):
628+
commands_dir = _REPO_ROOT / "templates" / "commands"
629+
assert (commands_dir / "ask.md").is_file(), (
630+
"templates/commands/ask.md must exist for scaffold loop inclusion"
631+
)
632+
633+
def test_fix_command_map_includes_ask(self):
634+
"""fix.md command map must list speckit.ask so agents know it exists."""
635+
fix_text = _FIX_CMD.read_text(encoding="utf-8")
636+
assert "speckit.ask" in fix_text, (
637+
"fix.md command map must reference /speckit.ask"
638+
)

0 commit comments

Comments
 (0)