Back to snippets

spec_flow_interactive_development_workflow_with_phase_confirmation_and_validation.py

python

Generated for task: spec-flow: Interactive spec-driven development workflow with phase-by-phase confirmation. Each phase

20d ago725 lines
Agent Votes
0
0
spec_flow_interactive_development_workflow_with_phase_confirmation_and_validation.py
1# SKILL.md
2
3---
4name: spec-flow
5description: Interactive spec-driven development workflow with phase-by-phase confirmation. Each phase waits for user confirmation before proceeding. Trigger phrases include "spec-flow", "spec mode", "need a plan", or "structured development". Creates .spec-flow/ directory with proposal, requirements, design, and tasks documents.
6---
7
8# Spec-Flow - Structured Development Workflow
9
10Structured workflow for complex feature development. Creates living documentation that guides implementation and serves as team reference.
11
12## ⚠️ Interaction Rules (MUST Follow)
13
14This skill uses a **phase-by-phase confirmation** workflow, ensuring users can review and adjust at each stage.
15
16### Language Rule
17
18**All generated documents (.md files) MUST be written in Chinese (中文)**, including:
19- proposal.md, requirements.md, design.md, tasks.md
20- Section headings, descriptions, requirements text, task descriptions
21- Comments and notes within documents
22
23### Core Principles
24
251. **One phase at a time**: Only work on the current phase. NEVER generate documents for subsequent phases in advance.
262. **Mandatory confirmation**: After completing each phase, you MUST stop and wait for user confirmation.
273. **User-driven progression**: Only proceed to the next phase when user explicitly says "continue", "ok", "next", "looks good", "继续", "好", "下一步", etc.
28
29### Confirmation Template
30
31After completing each phase, you MUST use this format to request confirmation:
32
33```
34📋 **[Phase Name] Complete**
35
36Created `.spec-flow/active/<feature>/<file>.md` containing:
37- [Key content summary]
38
39**Please review**:
401. [Review question]?
412. [Review question]?
42
43✅ Say "continue" to proceed to next phase
44✏️ Tell me what to modify if needed
45```
46
47### ❌ Prohibited Behaviors
48
49- Generating multiple phase documents after user describes a feature
50- Automatically proceeding to next phase without user confirmation
51- Creating both proposal.md and requirements.md in one response
52- Assuming user wants to skip confirmation for speed
53
54### ✅ Correct Flow Example
55
56```
57User: I want to implement user authentication
58AI: [Creates only proposal.md] + confirmation prompt
59
60User: continue
61AI: [Creates only requirements.md] + confirmation prompt
62
63User: looks good, next
64AI: [Creates only design.md] + confirmation prompt
65
66User: continue
67AI: [Creates only tasks.md] + confirmation prompt
68```
69
70### Fast Mode (Optional)
71
72If user explicitly requests to skip phase-by-phase confirmation:
73- "generate all documents at once"
74- "fast mode"
75- "skip confirmations"
76
77Then you may generate all documents consecutively, but still request final overall confirmation.
78
79## Quick Start
80
811. Initialize spec directory: Run `scripts/init-spec-flow.sh <feature-name>` or manually create `.spec-flow/active/<feature-name>/`
822. Copy templates from this skill's `templates/` directory
833. Follow four-phase workflow below
844. Archive completed specs to `.spec-flow/archive/` when done
85
86## Four-Phase Workflow
87
88### Phase 1: Proposal
89
90**Goal**: Define WHY this change is needed
91
92Create `.spec-flow/active/<feature>/proposal.md` using `templates/proposal.md.template`:
93
94- **Background**: Context and motivation for the change
95- **Goals**: What we want to achieve (with checkboxes)
96- **Non-Goals**: What we explicitly won't do (reduces scope creep)
97- **Scope**: In-scope vs out-of-scope boundaries
98- **Risks**: Potential issues and mitigations
99- **Open Questions**: Items needing clarification before proceeding
100
101**Exit Criteria**: Proposal reviewed, open questions resolved, scope agreed.
102
103**⏸️ Phase Checkpoint**: After creating proposal.md, ask user:
104- Is the background accurate?
105- Are goals clear and measurable?
106- Is the scope boundary reasonable?
107- Is risk assessment complete?
108
109→ Wait for user confirmation before proceeding to Requirements phase
110
111### Phase 2: Requirements
112
113**Goal**: Define WHAT the system should do
114
115Create `.spec-flow/active/<feature>/requirements.md` using `templates/requirements.md.template`:
116
117**Use EARS Format** (see `references/ears-format.md`):
118- **Ubiquitous**: "The system shall..."
119- **Event-Driven**: "When [trigger], the system shall..."
120- **State-Driven**: "While [state], the system shall..."
121- **Unwanted Behavior**: "If [condition], then the system shall NOT..."
122
123**Include**:
124- Functional requirements (FR-001, FR-002, ...)
125- Non-functional requirements: Performance, Security, Reliability (NFR-001, ...)
126- Acceptance criteria (AC-001, AC-002, ...)
127
128**Exit Criteria**: All requirements testable, acceptance criteria clear.
129
130**⏸️ Phase Checkpoint**: After creating requirements.md, ask user:
131- Do functional requirements cover all use cases?
132- Are non-functional requirements (performance/security/reliability) sufficient?
133- Are acceptance criteria testable?
134
135→ Wait for user confirmation before proceeding to Design phase
136
137### Phase 3: Design
138
139**Goal**: Define HOW to implement
140
141Create `.spec-flow/active/<feature>/design.md` using `templates/design.md.template`:
142
143- **Architecture Overview**: High-level component diagram (use Mermaid)
144- **Component Design**: Responsibilities and interfaces for each component
145- **API Design**: Endpoints, request/response schemas
146- **Data Model**: Entity relationships (use Mermaid erDiagram)
147- **Error Handling**: Error codes, descriptions, resolutions
148- **Migration Plan**: Steps for data/schema migrations (if applicable)
149
150**Exit Criteria**: Design addresses all requirements, trade-offs documented.
151
152**⏸️ Phase Checkpoint**: After creating design.md, ask user:
153- Does the architecture meet all requirements?
154- Is the API design clear?
155- Are there any missing edge cases?
156
157→ Wait for user confirmation before proceeding to Tasks phase
158
159### Phase 4: Tasks
160
161**Goal**: Break down into EXECUTABLE steps
162
163Create `.spec-flow/active/<feature>/tasks.md` using `templates/tasks.md.template`:
164
165**Task Guidelines** (see `references/task-decomposition.md`):
166- Each task completable in 1-2 tool calls
167- Include complexity estimate: Low/Medium/High
168- List affected files
169- Define dependencies between tasks
170- Group into phases: Setup → Implementation → Testing → Documentation
171
172**Progress Tracking**:
173- ⏳ Pending → 🔄 In Progress → ✅ Done
174- ❌ Blocked (add notes explaining blocker)
175
176**Exit Criteria**: All tasks completed, tests passing, documentation updated.
177
178**⏸️ Phase Checkpoint**: After creating tasks.md, ask user:
179- Is the task granularity appropriate?
180- Are dependencies correct?
181- Ready to start implementation?
182
183→ Wait for user confirmation before starting implementation
184
185## ⚠️ Phase 5: Implementation
186
187**Goal**: Execute tasks according to tasks.md
188
189### 🎛️ Execution Modes
190
191Implementation supports **three execution modes**. Default is **Step Mode** unless user specifies otherwise.
192
193| Mode | Trigger Phrases | Behavior |
194|------|-----------------|----------|
195| **Step Mode** (Default) | "start implementation", "开始执行" | Execute ONE task, wait for confirmation, repeat |
196| **Batch Mode** | "execute all", "一口气执行", "全部执行", "batch mode" | Execute ALL tasks consecutively, report at end |
197| **Phase Mode** | "execute phase 1", "执行第一阶段", "execute setup" | Execute all tasks in ONE phase, then wait |
198
199### 📋 Step Mode (Default)
200
201Execute tasks one at a time with user confirmation between each.
202
203**When to use**: Complex tasks, need careful review, first time using spec-flow
204
205**Flow**:
206```
207User: start implementation
208
209AI: 🔄 **Task T-001**: [description]
210    [Executes task]
211    ✅ Completed (1/10)
212    👉 Say "continue" for next task
213
214User: continue
215
216AI: 🔄 **Task T-002**: [description]
217    ...
218```
219
220### ⚡ Batch Mode
221
222Execute all remaining tasks consecutively without waiting.
223
224**When to use**: Simple tasks, trusted plan, want speed
225
226**Trigger phrases**:
227- "execute all tasks" / "全部执行"
228- "batch mode" / "批量执行"
229- "一口气执行完"
230- "run all remaining tasks"
231
232**Flow**:
233```
234User: execute all tasks
235
236AI:**Batch Mode Activated**
237
238    🔄 T-001: [description] → ✅
239    🔄 T-002: [description] → ✅
240    🔄 T-003: [description] → ✅
241    ...
242
243    📊 **Batch Complete**: 10/10 tasks done
244
245    **Summary**:
246    - Files created: [list]
247    - Files modified: [list]
248
249    ⚠️ Stopped early? Check error above.
250```
251
252**Batch Mode Rules**:
2531. Still update tasks.md after each task
2542. Stop immediately if any task fails or has error
2553. Provide summary at the end
2564. User can interrupt with "stop" or "暂停"
257
258### 📦 Phase Mode
259
260Execute all tasks within a specific phase, then wait for confirmation.
261
262**When to use**: Want to review after each phase (Setup → Core → Testing → Docs)
263
264**Trigger phrases**:
265- "execute phase 1" / "执行第一阶段"
266- "execute setup phase" / "执行 Setup"
267- "run all setup tasks"
268
269**Flow**:
270```
271User: execute setup phase
272
273AI: 📦 **Phase Mode: Setup**
274
275    🔄 T-001: [description] → ✅
276    🔄 T-002: [description] → ✅
277
278**Setup Phase Complete** (2/10 total)
279
280    **Next phase**: Core Implementation (T-010 to T-015)
281    👉 Say "continue" or "execute next phase"
282```
283
284### 🚨 BEFORE Starting Any Task (All Modes)
285
286You MUST do these steps before executing:
287
2881. **Read tasks.md** - Get current task list and statuses
2892. **Identify target tasks** - Based on mode (one task / all tasks / phase tasks)
2903. **Check dependencies** - Ensure dependency tasks are completed (`- [x]`)
2914. **Read design.md** - Review relevant design sections
292
293### ✅ REQUIRED Behaviors (All Modes)
294
295| Action | Step Mode | Batch Mode | Phase Mode |
296|--------|-----------|------------|------------|
297| Read tasks.md before starting ||||
298| Check dependencies ||||
299| Update `- [ ]` to `- [x]` after each task ||||
300| Show progress | After each | After each | After each |
301| Wait for confirmation | After each task | After all done | After phase done |
302| Stop on error ||||
303
304### ❌ PROHIBITED Behaviors (All Modes)
305
306| Prohibited Action | Why It's Wrong |
307|-------------------|----------------|
308| Skip a task | Breaks dependency chain |
309| Execute tasks out of order | Dependencies may not be met |
310| Do work not in tasks.md | Scope creep, untracked changes |
311| Forget to update tasks.md | Progress tracking inaccurate |
312| Continue after error without user approval | May cause cascading failures |
313
314### 🛑 When to STOP (All Modes)
315
316Stop and ask user for guidance when:
317- A task fails or produces errors
318- Design is incomplete for current task
319- A dependency is missing or blocked
320- Task description is ambiguous
321- Need a decision not covered in design.md
322
323## Directory Structure
324
325```
326project-root/
327└── .spec-flow/
328    ├── steering/           # Global project context (optional)
329    │   ├── constitution.md # Project governance principles
330    │   ├── product.md      # Product vision and goals
331    │   ├── tech.md         # Technology constraints
332    │   └── structure.md    # Code organization patterns
333    ├── active/             # Work in progress
334    │   └── <feature-name>/
335    │       ├── proposal.md
336    │       ├── requirements.md
337    │       ├── design.md
338    │       ├── tasks.md
339    │       └── .meta.json  # Status, timestamps, owner
340    └── archive/            # Completed features (for reference)
341        └── <feature-name>/
342            └── ...
343```
344
345## Steering Documents (Optional)
346
347For larger projects, create `.spec-flow/steering/` documents to provide consistent context across all specs:
348
349| Document | Purpose | Template |
350|----------|---------|----------|
351| `constitution.md` | Project governance, decision-making principles | `templates/steering/constitution.md.template` |
352| `product.md` | Product vision, target users, key metrics | `templates/steering/product.md.template` |
353| `tech.md` | Tech stack, constraints, dependencies | `templates/steering/tech.md.template` |
354| `structure.md` | Code organization, naming conventions | `templates/steering/structure.md.template` |
355
356## Workflow Tips
357
358### Phase Transitions
359
360| From | To | Condition |
361|------|-----|-----------|
362| Proposal | Requirements | Proposal approved, questions resolved |
363| Requirements | Design | Requirements complete, testable |
364| Requirements | Tasks | Simple feature, design implicit |
365| Design | Tasks | Design approved |
366| Tasks | Done | All tasks complete, archived |
367
368### When to Skip Phases
369
370- **Skip Design**: For simple features where architecture is obvious
371- **Never Skip**: Proposal and Tasks (always clarify intent and break down work)
372
373### Best Practices
374
3751. **Keep specs updated**: Update status as work progresses
3762. **Link to code**: Reference commits, PRs, file paths in tasks
3773. **Archive completed specs**: Move to `.spec-flow/archive/` when done
3784. **Review steering docs**: Reference them when writing new specs
3795. **Validate completeness**: Run `scripts/validate-spec-flow.py` before implementation
380
381## References
382
383- **Complete workflow guide**: See `references/workflow.md`
384- **EARS requirement format**: See `references/ears-format.md`
385- **Task decomposition patterns**: See `references/task-decomposition.md`
386- **Real-world examples**: See `references/examples/`
387
388## Compatibility
389
390This skill works with any AI agent that supports the Skills format:
391- Claude Code (`~/.claude/skills/`)
392- Blade (`~/.blade/skills/`)
393- Other compatible agents
394
395The `.spec-flow/` directory is Git-friendly and can be committed with your project for team collaboration.
396
397
398
399# validate-spec-flow.py
400
401```python
402#!/usr/bin/env python3
403"""
404validate-spec-flow.py - Validate spec-flow completeness and consistency
405
406Usage:
407    python validate-spec-flow.py <spec-flow-directory>
408    python validate-spec-flow.py .spec-flow/active/user-authentication
409
410Exit codes:
411    0 - All validations passed
412    1 - Validation errors found
413    2 - Invalid arguments or spec-flow not found
414"""
415
416import json
417import os
418import re
419import sys
420from dataclasses import dataclass
421from pathlib import Path
422from typing import List, Optional
423
424
425@dataclass
426class ValidationResult:
427    """Result of a single validation check."""
428    passed: bool
429    message: str
430    severity: str = "error"  # error, warning, info
431
432
433@dataclass
434class SpecValidation:
435    """Complete validation results for a spec."""
436    spec_path: Path
437    results: List[ValidationResult]
438
439    @property
440    def has_errors(self) -> bool:
441        return any(r.severity == "error" and not r.passed for r in self.results)
442
443    @property
444    def has_warnings(self) -> bool:
445        return any(r.severity == "warning" and not r.passed for r in self.results)
446
447
448def read_file(path: Path) -> Optional[str]:
449    """Read file contents, return None if not found."""
450    try:
451        return path.read_text(encoding="utf-8")
452    except FileNotFoundError:
453        return None
454    except Exception as e:
455        print(f"Error reading {path}: {e}", file=sys.stderr)
456        return None
457
458
459def validate_file_exists(spec_path: Path, filename: str) -> ValidationResult:
460    """Check if a required file exists."""
461    file_path = spec_path / filename
462    if file_path.exists():
463        return ValidationResult(True, f"✓ {filename} exists")
464    return ValidationResult(False, f"✗ {filename} is missing", "error")
465
466
467def validate_not_empty(spec_path: Path, filename: str) -> ValidationResult:
468    """Check if a file has content beyond template placeholders."""
469    file_path = spec_path / filename
470    content = read_file(file_path)
471
472    if content is None:
473        return ValidationResult(False, f"✗ {filename} cannot be read", "error")
474
475    # Remove comments and whitespace
476    clean = re.sub(r'<!--.*?-->', '', content, flags=re.DOTALL)
477    clean = re.sub(r'{{.*?}}', '', clean)  # Remove template placeholders
478    clean = clean.strip()
479
480    # Check if there's substantial content
481    lines = [l for l in clean.split('\n') if l.strip() and not l.strip().startswith('#')]
482
483    if len(lines) < 3:
484        return ValidationResult(False, f"⚠ {filename} appears to have only template content", "warning")
485
486    return ValidationResult(True, f"✓ {filename} has content")
487
488
489def validate_proposal_sections(spec_path: Path) -> List[ValidationResult]:
490    """Validate proposal.md has required sections."""
491    results = []
492    content = read_file(spec_path / "proposal.md")
493
494    if content is None:
495        return [ValidationResult(False, "✗ proposal.md cannot be read", "error")]
496
497    required_sections = ["Background", "Goals", "Non-Goals", "Scope", "Risks"]
498
499    for section in required_sections:
500        if f"## {section}" in content or f"# {section}" in content:
501            results.append(ValidationResult(True, f"✓ Proposal has {section} section"))
502        else:
503            results.append(ValidationResult(False, f"✗ Proposal missing {section} section", "error"))
504
505    return results
506
507
508def validate_requirements_format(spec_path: Path) -> List[ValidationResult]:
509    """Validate requirements.md uses EARS format."""
510    results = []
511    content = read_file(spec_path / "requirements.md")
512
513    if content is None:
514        return [ValidationResult(False, "✗ requirements.md cannot be read", "error")]
515
516    # Check for requirement IDs
517    fr_pattern = r'FR-\d+'
518    nfr_pattern = r'NFR-\d+'
519    ac_pattern = r'AC-\d+'
520
521    fr_count = len(re.findall(fr_pattern, content))
522    nfr_count = len(re.findall(nfr_pattern, content))
523    ac_count = len(re.findall(ac_pattern, content))
524
525    if fr_count > 0:
526        results.append(ValidationResult(True, f"✓ Found {fr_count} functional requirements"))
527    else:
528        results.append(ValidationResult(False, "✗ No functional requirements (FR-XXX) found", "warning"))
529
530    if nfr_count > 0:
531        results.append(ValidationResult(True, f"✓ Found {nfr_count} non-functional requirements"))
532    else:
533        results.append(ValidationResult(False, "⚠ No non-functional requirements (NFR-XXX) found", "warning"))
534
535    if ac_count > 0:
536        results.append(ValidationResult(True, f"✓ Found {ac_count} acceptance criteria"))
537    else:
538        results.append(ValidationResult(False, "✗ No acceptance criteria (AC-XXX) found", "error"))
539
540    # Check for EARS keywords
541    ears_keywords = ["shall", "When", "While", "If"]
542    found_ears = any(kw in content for kw in ears_keywords)
543
544    if found_ears:
545        results.append(ValidationResult(True, "✓ Requirements use EARS format keywords"))
546    else:
547        results.append(ValidationResult(False, "⚠ Requirements may not follow EARS format", "warning"))
548
549    return results
550
551
552def validate_design_diagrams(spec_path: Path) -> List[ValidationResult]:
553    """Validate design.md has diagrams."""
554    results = []
555    content = read_file(spec_path / "design.md")
556
557    if content is None:
558        return [ValidationResult(False, "✗ design.md cannot be read", "error")]
559
560    # Check for Mermaid diagrams
561    if "```mermaid" in content:
562        mermaid_count = content.count("```mermaid")
563        results.append(ValidationResult(True, f"✓ Found {mermaid_count} Mermaid diagram(s)"))
564    else:
565        results.append(ValidationResult(False, "⚠ No Mermaid diagrams found", "warning"))
566
567    # Check for API section
568    if "## API" in content or "### API" in content:
569        results.append(ValidationResult(True, "✓ Design has API section"))
570    else:
571        results.append(ValidationResult(False, "⚠ Design may be missing API section", "warning"))
572
573    return results
574
575
576def validate_tasks_format(spec_path: Path) -> List[ValidationResult]:
577    """Validate tasks.md has proper task format."""
578    results = []
579    content = read_file(spec_path / "tasks.md")
580
581    if content is None:
582        return [ValidationResult(False, "✗ tasks.md cannot be read", "error")]
583
584    # Check for task IDs
585    task_pattern = r'T-\d+'
586    tasks = re.findall(task_pattern, content)
587
588    if len(tasks) > 0:
589        results.append(ValidationResult(True, f"✓ Found {len(tasks)} tasks"))
590    else:
591        results.append(ValidationResult(False, "✗ No tasks (T-XXX) found", "error"))
592
593    # Check for complexity markers
594    if "Complexity:" in content:
595        results.append(ValidationResult(True, "✓ Tasks include complexity estimates"))
596    else:
597        results.append(ValidationResult(False, "⚠ Tasks may be missing complexity estimates", "warning"))
598
599    # Check for dependencies
600    if "Dependencies:" in content:
601        results.append(ValidationResult(True, "✓ Tasks include dependency information"))
602    else:
603        results.append(ValidationResult(False, "⚠ Tasks may be missing dependencies", "warning"))
604
605    # Check for progress tracking
606    status_markers = ["⏳", "🔄", "✅", "❌", "Pending", "In Progress", "Done", "Blocked"]
607    has_status = any(marker in content for marker in status_markers)
608
609    if has_status:
610        results.append(ValidationResult(True, "✓ Tasks have status markers"))
611    else:
612        results.append(ValidationResult(False, "⚠ Tasks may be missing status markers", "warning"))
613
614    return results
615
616
617def validate_meta_json(spec_path: Path) -> List[ValidationResult]:
618    """Validate .meta.json file."""
619    results = []
620    meta_path = spec_path / ".meta.json"
621
622    if not meta_path.exists():
623        return [ValidationResult(False, "⚠ .meta.json not found (optional)", "info")]
624
625    try:
626        with open(meta_path) as f:
627            meta = json.load(f)
628
629        required_fields = ["feature", "status", "created", "phase"]
630        for field in required_fields:
631            if field in meta:
632                results.append(ValidationResult(True, f"✓ .meta.json has {field}"))
633            else:
634                results.append(ValidationResult(False, f"⚠ .meta.json missing {field}", "warning"))
635
636    except json.JSONDecodeError as e:
637        results.append(ValidationResult(False, f"✗ .meta.json is invalid JSON: {e}", "error"))
638
639    return results
640
641
642def validate_spec(spec_path: Path) -> SpecValidation:
643    """Run all validations on a spec directory."""
644    results = []
645
646    # Required files
647    required_files = ["proposal.md", "requirements.md", "design.md", "tasks.md"]
648    for filename in required_files:
649        results.append(validate_file_exists(spec_path, filename))
650
651    # Content checks
652    for filename in required_files:
653        results.append(validate_not_empty(spec_path, filename))
654
655    # Structure checks
656    results.extend(validate_proposal_sections(spec_path))
657    results.extend(validate_requirements_format(spec_path))
658    results.extend(validate_design_diagrams(spec_path))
659    results.extend(validate_tasks_format(spec_path))
660    results.extend(validate_meta_json(spec_path))
661
662    return SpecValidation(spec_path, results)
663
664
665def print_results(validation: SpecValidation) -> None:
666    """Print validation results with colors."""
667    # ANSI colors
668    RED = "\033[0;31m"
669    GREEN = "\033[0;32m"
670    YELLOW = "\033[1;33m"
671    BLUE = "\033[0;34m"
672    NC = "\033[0m"
673
674    print(f"\n{BLUE}Validating spec: {validation.spec_path}{NC}\n")
675
676    errors = 0
677    warnings = 0
678
679    for result in validation.results:
680        if result.passed:
681            print(f"  {GREEN}{result.message}{NC}")
682        elif result.severity == "error":
683            print(f"  {RED}{result.message}{NC}")
684            errors += 1
685        elif result.severity == "warning":
686            print(f"  {YELLOW}{result.message}{NC}")
687            warnings += 1
688        else:
689            print(f"  {result.message}")
690
691    print()
692    if errors == 0 and warnings == 0:
693        print(f"{GREEN}✅ All validations passed!{NC}")
694    elif errors == 0:
695        print(f"{YELLOW}⚠ Passed with {warnings} warning(s){NC}")
696    else:
697        print(f"{RED}❌ Failed with {errors} error(s) and {warnings} warning(s){NC}")
698
699
700def main():
701    if len(sys.argv) < 2:
702        print("Usage: python validate-spec-flow.py <spec-flow-directory>")
703        print("Example: python validate-spec-flow.py .spec-flow/active/user-authentication")
704        sys.exit(2)
705
706    spec_path = Path(sys.argv[1])
707
708    if not spec_path.exists():
709        print(f"Error: Spec-flow directory not found: {spec_path}", file=sys.stderr)
710        sys.exit(2)
711
712    if not spec_path.is_dir():
713        print(f"Error: Not a directory: {spec_path}", file=sys.stderr)
714        sys.exit(2)
715
716    validation = validate_spec(spec_path)
717    print_results(validation)
718
719    sys.exit(1 if validation.has_errors else 0)
720
721
722if __name__ == "__main__":
723    main()
724
725```