specfact-cli 0.4.2__py3-none-any.whl → 0.6.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. specfact_cli/__init__.py +1 -1
  2. specfact_cli/agents/analyze_agent.py +2 -3
  3. specfact_cli/analyzers/__init__.py +2 -1
  4. specfact_cli/analyzers/ambiguity_scanner.py +601 -0
  5. specfact_cli/analyzers/code_analyzer.py +462 -30
  6. specfact_cli/analyzers/constitution_evidence_extractor.py +491 -0
  7. specfact_cli/analyzers/contract_extractor.py +419 -0
  8. specfact_cli/analyzers/control_flow_analyzer.py +281 -0
  9. specfact_cli/analyzers/requirement_extractor.py +337 -0
  10. specfact_cli/analyzers/test_pattern_extractor.py +330 -0
  11. specfact_cli/cli.py +151 -206
  12. specfact_cli/commands/constitution.py +281 -0
  13. specfact_cli/commands/enforce.py +42 -34
  14. specfact_cli/commands/import_cmd.py +481 -152
  15. specfact_cli/commands/init.py +224 -55
  16. specfact_cli/commands/plan.py +2133 -547
  17. specfact_cli/commands/repro.py +100 -78
  18. specfact_cli/commands/sync.py +701 -186
  19. specfact_cli/enrichers/constitution_enricher.py +765 -0
  20. specfact_cli/enrichers/plan_enricher.py +294 -0
  21. specfact_cli/importers/speckit_converter.py +364 -48
  22. specfact_cli/importers/speckit_scanner.py +65 -0
  23. specfact_cli/models/plan.py +42 -0
  24. specfact_cli/resources/mappings/node-async.yaml +49 -0
  25. specfact_cli/resources/mappings/python-async.yaml +47 -0
  26. specfact_cli/resources/mappings/speckit-default.yaml +82 -0
  27. specfact_cli/resources/prompts/specfact-enforce.md +185 -0
  28. specfact_cli/resources/prompts/specfact-import-from-code.md +626 -0
  29. specfact_cli/resources/prompts/specfact-plan-add-feature.md +188 -0
  30. specfact_cli/resources/prompts/specfact-plan-add-story.md +212 -0
  31. specfact_cli/resources/prompts/specfact-plan-compare.md +571 -0
  32. specfact_cli/resources/prompts/specfact-plan-init.md +531 -0
  33. specfact_cli/resources/prompts/specfact-plan-promote.md +352 -0
  34. specfact_cli/resources/prompts/specfact-plan-review.md +1276 -0
  35. specfact_cli/resources/prompts/specfact-plan-select.md +401 -0
  36. specfact_cli/resources/prompts/specfact-plan-update-feature.md +242 -0
  37. specfact_cli/resources/prompts/specfact-plan-update-idea.md +211 -0
  38. specfact_cli/resources/prompts/specfact-repro.md +268 -0
  39. specfact_cli/resources/prompts/specfact-sync.md +497 -0
  40. specfact_cli/resources/schemas/deviation.schema.json +61 -0
  41. specfact_cli/resources/schemas/plan.schema.json +204 -0
  42. specfact_cli/resources/schemas/protocol.schema.json +53 -0
  43. specfact_cli/resources/templates/github-action.yml.j2 +140 -0
  44. specfact_cli/resources/templates/plan.bundle.yaml.j2 +141 -0
  45. specfact_cli/resources/templates/pr-template.md.j2 +58 -0
  46. specfact_cli/resources/templates/protocol.yaml.j2 +24 -0
  47. specfact_cli/resources/templates/telemetry.yaml.example +35 -0
  48. specfact_cli/sync/__init__.py +10 -1
  49. specfact_cli/sync/watcher.py +268 -0
  50. specfact_cli/telemetry.py +440 -0
  51. specfact_cli/utils/acceptance_criteria.py +127 -0
  52. specfact_cli/utils/enrichment_parser.py +445 -0
  53. specfact_cli/utils/feature_keys.py +12 -3
  54. specfact_cli/utils/ide_setup.py +170 -0
  55. specfact_cli/utils/structure.py +179 -2
  56. specfact_cli/utils/yaml_utils.py +33 -0
  57. specfact_cli/validators/repro_checker.py +22 -1
  58. specfact_cli/validators/schema.py +15 -4
  59. specfact_cli-0.6.8.dist-info/METADATA +456 -0
  60. specfact_cli-0.6.8.dist-info/RECORD +99 -0
  61. {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/entry_points.txt +1 -0
  62. specfact_cli-0.6.8.dist-info/licenses/LICENSE.md +202 -0
  63. specfact_cli-0.4.2.dist-info/METADATA +0 -370
  64. specfact_cli-0.4.2.dist-info/RECORD +0 -62
  65. specfact_cli-0.4.2.dist-info/licenses/LICENSE.md +0 -61
  66. {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/WHEEL +0 -0
@@ -16,6 +16,7 @@ from rich.console import Console
16
16
  from rich.progress import Progress, SpinnerColumn, TextColumn
17
17
  from rich.table import Table
18
18
 
19
+ from specfact_cli.telemetry import telemetry
19
20
  from specfact_cli.utils.structure import SpecFactStructure
20
21
  from specfact_cli.validators.repro_checker import ReproChecker
21
22
 
@@ -34,6 +35,11 @@ def _is_valid_output_path(path: Path | None) -> bool:
34
35
  return path is None or path.exists()
35
36
 
36
37
 
38
+ def _count_python_files(path: Path) -> int:
39
+ """Count Python files for anonymized telemetry reporting."""
40
+ return sum(1 for _ in path.rglob("*.py"))
41
+
42
+
37
43
  @app.callback(invoke_without_command=True)
38
44
  @beartype
39
45
  @require(lambda repo: _is_valid_repo_path(repo), "Repo path must exist and be directory")
@@ -106,87 +112,103 @@ def main(
106
112
  # Ensure structure exists
107
113
  SpecFactStructure.ensure_structure(repo)
108
114
 
109
- # Run all checks
110
- checker = ReproChecker(repo_path=repo, budget=budget, fail_fast=fail_fast, fix=fix)
111
-
112
- with Progress(
113
- SpinnerColumn(),
114
- TextColumn("[progress.description]{task.description}"),
115
- console=console,
116
- ) as progress:
117
- progress.add_task("Running validation checks...", total=None)
118
-
119
- # This will show progress for each check internally
120
- report = checker.run_all_checks()
121
-
122
- # Display results
123
- console.print("\n[bold]Validation Results[/bold]\n")
124
-
125
- # Summary table
126
- table = Table(title="Check Summary")
127
- table.add_column("Check", style="cyan")
128
- table.add_column("Tool", style="dim")
129
- table.add_column("Status", style="bold")
130
- table.add_column("Duration", style="dim")
131
-
132
- for check in report.checks:
133
- if check.status.value == "passed":
134
- status_icon = "[green]✓[/green] PASSED"
135
- elif check.status.value == "failed":
136
- status_icon = "[red]✗[/red] FAILED"
137
- elif check.status.value == "timeout":
138
- status_icon = "[yellow]⏱[/yellow] TIMEOUT"
139
- elif check.status.value == "skipped":
140
- status_icon = "[dim]⊘[/dim] SKIPPED"
141
- else:
142
- status_icon = "[dim]…[/dim] PENDING"
115
+ python_file_count = _count_python_files(repo)
143
116
 
144
- duration_str = f"{check.duration:.2f}s" if check.duration else "N/A"
117
+ telemetry_metadata = {
118
+ "mode": "repro",
119
+ "files_analyzed": python_file_count,
120
+ }
145
121
 
146
- table.add_row(check.name, check.tool, status_icon, duration_str)
122
+ with telemetry.track_command("repro.run", telemetry_metadata) as record_event:
123
+ # Run all checks
124
+ checker = ReproChecker(repo_path=repo, budget=budget, fail_fast=fail_fast, fix=fix)
147
125
 
148
- console.print(table)
126
+ with Progress(
127
+ SpinnerColumn(),
128
+ TextColumn("[progress.description]{task.description}"),
129
+ console=console,
130
+ ) as progress:
131
+ progress.add_task("Running validation checks...", total=None)
149
132
 
150
- # Summary stats
151
- console.print("\n[bold]Summary:[/bold]")
152
- console.print(f" Total checks: {report.total_checks}")
153
- console.print(f" [green]Passed: {report.passed_checks}[/green]")
154
- if report.failed_checks > 0:
155
- console.print(f" [red]Failed: {report.failed_checks}[/red]")
156
- if report.timeout_checks > 0:
157
- console.print(f" [yellow]Timeout: {report.timeout_checks}[/yellow]")
158
- if report.skipped_checks > 0:
159
- console.print(f" [dim]Skipped: {report.skipped_checks}[/dim]")
160
- console.print(f" Total duration: {report.total_duration:.2f}s")
133
+ # This will show progress for each check internally
134
+ report = checker.run_all_checks()
135
+
136
+ # Display results
137
+ console.print("\n[bold]Validation Results[/bold]\n")
138
+
139
+ # Summary table
140
+ table = Table(title="Check Summary")
141
+ table.add_column("Check", style="cyan")
142
+ table.add_column("Tool", style="dim")
143
+ table.add_column("Status", style="bold")
144
+ table.add_column("Duration", style="dim")
161
145
 
162
- # Show errors if verbose
163
- if verbose:
164
146
  for check in report.checks:
165
- if check.error:
166
- console.print(f"\n[bold red]{check.name} Error:[/bold red]")
167
- console.print(f"[dim]{check.error}[/dim]")
168
- if check.output and check.status.value == "failed":
169
- console.print(f"\n[bold red]{check.name} Output:[/bold red]")
170
- console.print(f"[dim]{check.output[:500]}[/dim]") # Limit output
171
-
172
- # Write report if requested
173
- if out is None:
174
- # Use default path
175
- out = SpecFactStructure.get_timestamped_report_path("enforcement", repo, "yaml")
176
- SpecFactStructure.ensure_structure(repo)
177
-
178
- out.parent.mkdir(parents=True, exist_ok=True)
179
- dump_yaml(report.to_dict(), out)
180
- console.print(f"\n[dim]Report written to: {out}[/dim]")
181
-
182
- # Exit with appropriate code
183
- exit_code = report.get_exit_code()
184
- if exit_code == 0:
185
- console.print("\n[bold green][/bold green] All validations passed!")
186
- console.print("[dim]Reproducibility verified[/dim]")
187
- elif exit_code == 1:
188
- console.print("\n[bold red]✗[/bold red] Some validations failed")
189
- raise typer.Exit(1)
190
- else:
191
- console.print("\n[yellow][/yellow] Budget exceeded")
192
- raise typer.Exit(2)
147
+ if check.status.value == "passed":
148
+ status_icon = "[green][/green] PASSED"
149
+ elif check.status.value == "failed":
150
+ status_icon = "[red]✗[/red] FAILED"
151
+ elif check.status.value == "timeout":
152
+ status_icon = "[yellow][/yellow] TIMEOUT"
153
+ elif check.status.value == "skipped":
154
+ status_icon = "[dim]⊘[/dim] SKIPPED"
155
+ else:
156
+ status_icon = "[dim]…[/dim] PENDING"
157
+
158
+ duration_str = f"{check.duration:.2f}s" if check.duration else "N/A"
159
+
160
+ table.add_row(check.name, check.tool, status_icon, duration_str)
161
+
162
+ console.print(table)
163
+
164
+ # Summary stats
165
+ console.print("\n[bold]Summary:[/bold]")
166
+ console.print(f" Total checks: {report.total_checks}")
167
+ console.print(f" [green]Passed: {report.passed_checks}[/green]")
168
+ if report.failed_checks > 0:
169
+ console.print(f" [red]Failed: {report.failed_checks}[/red]")
170
+ if report.timeout_checks > 0:
171
+ console.print(f" [yellow]Timeout: {report.timeout_checks}[/yellow]")
172
+ if report.skipped_checks > 0:
173
+ console.print(f" [dim]Skipped: {report.skipped_checks}[/dim]")
174
+ console.print(f" Total duration: {report.total_duration:.2f}s")
175
+
176
+ record_event(
177
+ {
178
+ "checks_total": report.total_checks,
179
+ "checks_failed": report.failed_checks,
180
+ "violations_detected": report.failed_checks,
181
+ }
182
+ )
183
+
184
+ # Show errors if verbose
185
+ if verbose:
186
+ for check in report.checks:
187
+ if check.error:
188
+ console.print(f"\n[bold red]{check.name} Error:[/bold red]")
189
+ console.print(f"[dim]{check.error}[/dim]")
190
+ if check.output and check.status.value == "failed":
191
+ console.print(f"\n[bold red]{check.name} Output:[/bold red]")
192
+ console.print(f"[dim]{check.output[:500]}[/dim]") # Limit output
193
+
194
+ # Write report if requested
195
+ if out is None:
196
+ # Use default path
197
+ out = SpecFactStructure.get_timestamped_report_path("enforcement", repo, "yaml")
198
+ SpecFactStructure.ensure_structure(repo)
199
+
200
+ out.parent.mkdir(parents=True, exist_ok=True)
201
+ dump_yaml(report.to_dict(), out)
202
+ console.print(f"\n[dim]Report written to: {out}[/dim]")
203
+
204
+ # Exit with appropriate code
205
+ exit_code = report.get_exit_code()
206
+ if exit_code == 0:
207
+ console.print("\n[bold green]✓[/bold green] All validations passed!")
208
+ console.print("[dim]Reproducibility verified[/dim]")
209
+ elif exit_code == 1:
210
+ console.print("\n[bold red]✗[/bold red] Some validations failed")
211
+ raise typer.Exit(1)
212
+ else:
213
+ console.print("\n[yellow]⏱[/yellow] Budget exceeded")
214
+ raise typer.Exit(2)