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.
- specfact_cli/__init__.py +1 -1
- specfact_cli/agents/analyze_agent.py +2 -3
- specfact_cli/analyzers/__init__.py +2 -1
- specfact_cli/analyzers/ambiguity_scanner.py +601 -0
- specfact_cli/analyzers/code_analyzer.py +462 -30
- specfact_cli/analyzers/constitution_evidence_extractor.py +491 -0
- specfact_cli/analyzers/contract_extractor.py +419 -0
- specfact_cli/analyzers/control_flow_analyzer.py +281 -0
- specfact_cli/analyzers/requirement_extractor.py +337 -0
- specfact_cli/analyzers/test_pattern_extractor.py +330 -0
- specfact_cli/cli.py +151 -206
- specfact_cli/commands/constitution.py +281 -0
- specfact_cli/commands/enforce.py +42 -34
- specfact_cli/commands/import_cmd.py +481 -152
- specfact_cli/commands/init.py +224 -55
- specfact_cli/commands/plan.py +2133 -547
- specfact_cli/commands/repro.py +100 -78
- specfact_cli/commands/sync.py +701 -186
- specfact_cli/enrichers/constitution_enricher.py +765 -0
- specfact_cli/enrichers/plan_enricher.py +294 -0
- specfact_cli/importers/speckit_converter.py +364 -48
- specfact_cli/importers/speckit_scanner.py +65 -0
- specfact_cli/models/plan.py +42 -0
- specfact_cli/resources/mappings/node-async.yaml +49 -0
- specfact_cli/resources/mappings/python-async.yaml +47 -0
- specfact_cli/resources/mappings/speckit-default.yaml +82 -0
- specfact_cli/resources/prompts/specfact-enforce.md +185 -0
- specfact_cli/resources/prompts/specfact-import-from-code.md +626 -0
- specfact_cli/resources/prompts/specfact-plan-add-feature.md +188 -0
- specfact_cli/resources/prompts/specfact-plan-add-story.md +212 -0
- specfact_cli/resources/prompts/specfact-plan-compare.md +571 -0
- specfact_cli/resources/prompts/specfact-plan-init.md +531 -0
- specfact_cli/resources/prompts/specfact-plan-promote.md +352 -0
- specfact_cli/resources/prompts/specfact-plan-review.md +1276 -0
- specfact_cli/resources/prompts/specfact-plan-select.md +401 -0
- specfact_cli/resources/prompts/specfact-plan-update-feature.md +242 -0
- specfact_cli/resources/prompts/specfact-plan-update-idea.md +211 -0
- specfact_cli/resources/prompts/specfact-repro.md +268 -0
- specfact_cli/resources/prompts/specfact-sync.md +497 -0
- specfact_cli/resources/schemas/deviation.schema.json +61 -0
- specfact_cli/resources/schemas/plan.schema.json +204 -0
- specfact_cli/resources/schemas/protocol.schema.json +53 -0
- specfact_cli/resources/templates/github-action.yml.j2 +140 -0
- specfact_cli/resources/templates/plan.bundle.yaml.j2 +141 -0
- specfact_cli/resources/templates/pr-template.md.j2 +58 -0
- specfact_cli/resources/templates/protocol.yaml.j2 +24 -0
- specfact_cli/resources/templates/telemetry.yaml.example +35 -0
- specfact_cli/sync/__init__.py +10 -1
- specfact_cli/sync/watcher.py +268 -0
- specfact_cli/telemetry.py +440 -0
- specfact_cli/utils/acceptance_criteria.py +127 -0
- specfact_cli/utils/enrichment_parser.py +445 -0
- specfact_cli/utils/feature_keys.py +12 -3
- specfact_cli/utils/ide_setup.py +170 -0
- specfact_cli/utils/structure.py +179 -2
- specfact_cli/utils/yaml_utils.py +33 -0
- specfact_cli/validators/repro_checker.py +22 -1
- specfact_cli/validators/schema.py +15 -4
- specfact_cli-0.6.8.dist-info/METADATA +456 -0
- specfact_cli-0.6.8.dist-info/RECORD +99 -0
- {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/entry_points.txt +1 -0
- specfact_cli-0.6.8.dist-info/licenses/LICENSE.md +202 -0
- specfact_cli-0.4.2.dist-info/METADATA +0 -370
- specfact_cli-0.4.2.dist-info/RECORD +0 -62
- specfact_cli-0.4.2.dist-info/licenses/LICENSE.md +0 -61
- {specfact_cli-0.4.2.dist-info → specfact_cli-0.6.8.dist-info}/WHEEL +0 -0
specfact_cli/commands/repro.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
117
|
+
telemetry_metadata = {
|
|
118
|
+
"mode": "repro",
|
|
119
|
+
"files_analyzed": python_file_count,
|
|
120
|
+
}
|
|
145
121
|
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.print("
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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)
|