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/init.py
CHANGED
|
@@ -7,6 +7,7 @@ to IDE-specific locations for slash command integration.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import sys
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
import typer
|
|
@@ -15,7 +16,14 @@ from icontract import ensure, require
|
|
|
15
16
|
from rich.console import Console
|
|
16
17
|
from rich.panel import Panel
|
|
17
18
|
|
|
18
|
-
from specfact_cli.
|
|
19
|
+
from specfact_cli.telemetry import telemetry
|
|
20
|
+
from specfact_cli.utils.ide_setup import (
|
|
21
|
+
IDE_CONFIG,
|
|
22
|
+
copy_templates_to_ide,
|
|
23
|
+
detect_ide,
|
|
24
|
+
find_package_resources_path,
|
|
25
|
+
get_package_installation_locations,
|
|
26
|
+
)
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
app = typer.Typer(help="Initialize SpecFact for IDE integration")
|
|
@@ -65,61 +73,222 @@ def init(
|
|
|
65
73
|
specfact init --ide vscode --force # Overwrite existing files
|
|
66
74
|
specfact init --repo /path/to/repo --ide copilot
|
|
67
75
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Find templates directory
|
|
83
|
-
# Try relative to project root first (for development)
|
|
84
|
-
templates_dir = repo_path / "resources" / "prompts"
|
|
85
|
-
if not templates_dir.exists():
|
|
86
|
-
# Try relative to installed package (for distribution)
|
|
87
|
-
import importlib.util
|
|
88
|
-
|
|
89
|
-
spec = importlib.util.find_spec("specfact_cli")
|
|
90
|
-
if spec and spec.origin:
|
|
91
|
-
package_dir = Path(spec.origin).parent.parent
|
|
92
|
-
templates_dir = package_dir / "resources" / "prompts"
|
|
93
|
-
if not templates_dir.exists():
|
|
94
|
-
# Fallback: try resources/prompts in project root
|
|
95
|
-
templates_dir = Path(__file__).parent.parent.parent.parent / "resources" / "prompts"
|
|
96
|
-
|
|
97
|
-
if not templates_dir.exists():
|
|
98
|
-
console.print(f"[red]Error:[/red] Templates directory not found: {templates_dir}")
|
|
99
|
-
console.print("[yellow]Expected location:[/yellow] resources/prompts/")
|
|
100
|
-
console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
|
|
101
|
-
raise typer.Exit(1)
|
|
102
|
-
|
|
103
|
-
console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
|
|
104
|
-
console.print()
|
|
105
|
-
|
|
106
|
-
# Copy templates to IDE location
|
|
107
|
-
try:
|
|
108
|
-
copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
|
|
109
|
-
|
|
110
|
-
if not copied_files:
|
|
111
|
-
console.print("[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]")
|
|
112
|
-
raise typer.Exit(0)
|
|
76
|
+
telemetry_metadata = {
|
|
77
|
+
"ide": ide,
|
|
78
|
+
"force": force,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
with telemetry.track_command("init", telemetry_metadata) as record:
|
|
82
|
+
# Resolve repo path
|
|
83
|
+
repo_path = repo.resolve()
|
|
84
|
+
|
|
85
|
+
# Detect IDE
|
|
86
|
+
detected_ide = detect_ide(ide)
|
|
87
|
+
ide_config = IDE_CONFIG[detected_ide]
|
|
88
|
+
ide_name = ide_config["name"]
|
|
113
89
|
|
|
114
90
|
console.print()
|
|
115
|
-
console.print(Panel("[bold
|
|
116
|
-
console.print(f"[
|
|
117
|
-
|
|
118
|
-
console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
|
|
91
|
+
console.print(Panel("[bold cyan]SpecFact IDE Setup[/bold cyan]", border_style="cyan"))
|
|
92
|
+
console.print(f"[cyan]Repository:[/cyan] {repo_path}")
|
|
93
|
+
console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
|
|
119
94
|
console.print()
|
|
120
|
-
console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
|
|
121
|
-
console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
# Find templates directory
|
|
97
|
+
# Priority order:
|
|
98
|
+
# 1. Development: relative to project root (resources/prompts)
|
|
99
|
+
# 2. Installed package: use importlib.resources to find package location
|
|
100
|
+
# 3. Fallback: try relative to this file (for edge cases)
|
|
101
|
+
templates_dir: Path | None = None
|
|
102
|
+
package_templates_dir: Path | None = None
|
|
103
|
+
tried_locations: list[Path] = []
|
|
104
|
+
|
|
105
|
+
# Try 1: Development mode - relative to repo root
|
|
106
|
+
dev_templates_dir = (repo_path / "resources" / "prompts").resolve()
|
|
107
|
+
tried_locations.append(dev_templates_dir)
|
|
108
|
+
console.print(f"[dim]Debug:[/dim] Trying development path: {dev_templates_dir}")
|
|
109
|
+
if dev_templates_dir.exists():
|
|
110
|
+
templates_dir = dev_templates_dir
|
|
111
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
112
|
+
else:
|
|
113
|
+
console.print("[dim]Debug:[/dim] Development path not found, trying installed package...")
|
|
114
|
+
# Try 2: Installed package - use importlib.resources
|
|
115
|
+
# Note: importlib is part of Python's standard library (since Python 3.1)
|
|
116
|
+
# importlib.resources.files() is available since Python 3.9
|
|
117
|
+
# Since we require Python >=3.11, this should always be available
|
|
118
|
+
# However, we catch exceptions for robustness (minimal installations, edge cases)
|
|
119
|
+
package_templates_dir = None
|
|
120
|
+
try:
|
|
121
|
+
import importlib.resources
|
|
122
|
+
|
|
123
|
+
console.print("[dim]Debug:[/dim] Using importlib.resources.files() API...")
|
|
124
|
+
# Use files() API (Python 3.9+) - recommended approach
|
|
125
|
+
resources_ref = importlib.resources.files("specfact_cli")
|
|
126
|
+
templates_ref = resources_ref / "resources" / "prompts"
|
|
127
|
+
# Convert Traversable to Path
|
|
128
|
+
# Traversable objects can be converted to Path via str()
|
|
129
|
+
# Use resolve() to handle Windows/Linux/macOS path differences
|
|
130
|
+
package_templates_dir = Path(str(templates_ref)).resolve()
|
|
131
|
+
tried_locations.append(package_templates_dir)
|
|
132
|
+
console.print(f"[dim]Debug:[/dim] Package templates path: {package_templates_dir}")
|
|
133
|
+
if package_templates_dir.exists():
|
|
134
|
+
templates_dir = package_templates_dir
|
|
135
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
136
|
+
else:
|
|
137
|
+
console.print("[yellow]⚠[/yellow] Package templates path exists but directory not found")
|
|
138
|
+
except (ImportError, ModuleNotFoundError) as e:
|
|
139
|
+
console.print(
|
|
140
|
+
f"[yellow]⚠[/yellow] importlib.resources not available or module not found: {type(e).__name__}: {e}"
|
|
141
|
+
)
|
|
142
|
+
console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
|
|
143
|
+
except (TypeError, AttributeError, ValueError) as e:
|
|
144
|
+
console.print(f"[yellow]⚠[/yellow] Error converting Traversable to Path: {e}")
|
|
145
|
+
console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
|
|
146
|
+
except Exception as e:
|
|
147
|
+
console.print(f"[yellow]⚠[/yellow] Unexpected error with importlib.resources: {type(e).__name__}: {e}")
|
|
148
|
+
console.print("[dim]Debug:[/dim] Falling back to importlib.util.find_spec()...")
|
|
149
|
+
|
|
150
|
+
# Fallback: importlib.util.find_spec() + comprehensive package location search
|
|
151
|
+
if not templates_dir or not templates_dir.exists():
|
|
152
|
+
try:
|
|
153
|
+
import importlib.util
|
|
154
|
+
|
|
155
|
+
console.print("[dim]Debug:[/dim] Using importlib.util.find_spec() fallback...")
|
|
156
|
+
spec = importlib.util.find_spec("specfact_cli")
|
|
157
|
+
if spec and spec.origin:
|
|
158
|
+
# spec.origin points to __init__.py
|
|
159
|
+
# Go up to package root, then to resources/prompts
|
|
160
|
+
# Use resolve() for cross-platform compatibility
|
|
161
|
+
package_root = Path(spec.origin).parent.resolve()
|
|
162
|
+
package_templates_dir = (package_root / "resources" / "prompts").resolve()
|
|
163
|
+
tried_locations.append(package_templates_dir)
|
|
164
|
+
console.print(f"[dim]Debug:[/dim] Package root from spec.origin: {package_root}")
|
|
165
|
+
console.print(f"[dim]Debug:[/dim] Templates path from spec: {package_templates_dir}")
|
|
166
|
+
if package_templates_dir.exists():
|
|
167
|
+
templates_dir = package_templates_dir
|
|
168
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
169
|
+
else:
|
|
170
|
+
console.print("[yellow]⚠[/yellow] Templates path from spec not found")
|
|
171
|
+
else:
|
|
172
|
+
console.print("[yellow]⚠[/yellow] Could not find specfact_cli module spec")
|
|
173
|
+
if spec is None:
|
|
174
|
+
console.print("[dim]Debug:[/dim] spec is None")
|
|
175
|
+
elif not spec.origin:
|
|
176
|
+
console.print("[dim]Debug:[/dim] spec.origin is None or empty")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
console.print(f"[yellow]⚠[/yellow] Error with importlib.util.find_spec(): {type(e).__name__}: {e}")
|
|
179
|
+
|
|
180
|
+
# Fallback: Comprehensive package location search (cross-platform)
|
|
181
|
+
if not templates_dir or not templates_dir.exists():
|
|
182
|
+
try:
|
|
183
|
+
console.print("[dim]Debug:[/dim] Searching all package installation locations...")
|
|
184
|
+
package_locations = get_package_installation_locations("specfact_cli")
|
|
185
|
+
console.print(f"[dim]Debug:[/dim] Found {len(package_locations)} possible package location(s)")
|
|
186
|
+
for i, loc in enumerate(package_locations, 1):
|
|
187
|
+
console.print(f"[dim]Debug:[/dim] {i}. {loc}")
|
|
188
|
+
# Check for resources/prompts in this package location
|
|
189
|
+
resource_path = (loc / "resources" / "prompts").resolve()
|
|
190
|
+
tried_locations.append(resource_path)
|
|
191
|
+
if resource_path.exists():
|
|
192
|
+
templates_dir = resource_path
|
|
193
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
194
|
+
break
|
|
195
|
+
if not templates_dir or not templates_dir.exists():
|
|
196
|
+
# Try using the helper function as a final attempt
|
|
197
|
+
console.print("[dim]Debug:[/dim] Trying find_package_resources_path() helper...")
|
|
198
|
+
resource_path = find_package_resources_path("specfact_cli", "resources/prompts")
|
|
199
|
+
if resource_path and resource_path.exists():
|
|
200
|
+
tried_locations.append(resource_path)
|
|
201
|
+
templates_dir = resource_path
|
|
202
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
203
|
+
else:
|
|
204
|
+
console.print("[yellow]⚠[/yellow] Resources not found in any package location")
|
|
205
|
+
except Exception as e:
|
|
206
|
+
console.print(f"[yellow]⚠[/yellow] Error searching package locations: {type(e).__name__}: {e}")
|
|
207
|
+
|
|
208
|
+
# Try 3: Fallback - relative to this file (for edge cases)
|
|
209
|
+
if not templates_dir or not templates_dir.exists():
|
|
210
|
+
try:
|
|
211
|
+
console.print("[dim]Debug:[/dim] Trying fallback: relative to __file__...")
|
|
212
|
+
# Get the directory containing this file (init.py)
|
|
213
|
+
# init.py is in: src/specfact_cli/commands/init.py
|
|
214
|
+
# Go up: commands -> specfact_cli -> src -> project root
|
|
215
|
+
current_file = Path(__file__).resolve()
|
|
216
|
+
fallback_dir = (current_file.parent.parent.parent.parent / "resources" / "prompts").resolve()
|
|
217
|
+
tried_locations.append(fallback_dir)
|
|
218
|
+
console.print(f"[dim]Debug:[/dim] Current file: {current_file}")
|
|
219
|
+
console.print(f"[dim]Debug:[/dim] Fallback templates path: {fallback_dir}")
|
|
220
|
+
if fallback_dir.exists():
|
|
221
|
+
templates_dir = fallback_dir
|
|
222
|
+
console.print(f"[green]✓[/green] Found templates at: {templates_dir}")
|
|
223
|
+
else:
|
|
224
|
+
console.print("[yellow]⚠[/yellow] Fallback path not found")
|
|
225
|
+
except Exception as e:
|
|
226
|
+
console.print(f"[yellow]⚠[/yellow] Error with __file__ fallback: {type(e).__name__}: {e}")
|
|
227
|
+
|
|
228
|
+
if not templates_dir or not templates_dir.exists():
|
|
229
|
+
console.print()
|
|
230
|
+
console.print("[red]Error:[/red] Templates directory not found after all attempts")
|
|
231
|
+
console.print()
|
|
232
|
+
console.print("[yellow]Tried locations:[/yellow]")
|
|
233
|
+
for i, location in enumerate(tried_locations, 1):
|
|
234
|
+
exists = "✓" if location.exists() else "✗"
|
|
235
|
+
console.print(f" {i}. {exists} {location}")
|
|
236
|
+
console.print()
|
|
237
|
+
console.print("[yellow]Debug information:[/yellow]")
|
|
238
|
+
console.print(f" - Python version: {sys.version}")
|
|
239
|
+
console.print(f" - Platform: {sys.platform}")
|
|
240
|
+
console.print(f" - Current working directory: {Path.cwd()}")
|
|
241
|
+
console.print(f" - Repository path: {repo_path}")
|
|
242
|
+
console.print(f" - __file__ location: {Path(__file__).resolve()}")
|
|
243
|
+
try:
|
|
244
|
+
import importlib.util
|
|
245
|
+
|
|
246
|
+
spec = importlib.util.find_spec("specfact_cli")
|
|
247
|
+
if spec:
|
|
248
|
+
console.print(f" - Module spec found: {spec}")
|
|
249
|
+
console.print(f" - Module origin: {spec.origin}")
|
|
250
|
+
if spec.origin:
|
|
251
|
+
console.print(f" - Module location: {Path(spec.origin).parent.resolve()}")
|
|
252
|
+
else:
|
|
253
|
+
console.print(" - Module spec: Not found")
|
|
254
|
+
except Exception as e:
|
|
255
|
+
console.print(f" - Error checking module spec: {e}")
|
|
256
|
+
console.print()
|
|
257
|
+
console.print("[yellow]Expected location:[/yellow] resources/prompts/")
|
|
258
|
+
console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
|
|
259
|
+
raise typer.Exit(1)
|
|
260
|
+
|
|
261
|
+
console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
|
|
262
|
+
console.print()
|
|
263
|
+
|
|
264
|
+
# Copy templates to IDE location
|
|
265
|
+
try:
|
|
266
|
+
copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
|
|
267
|
+
|
|
268
|
+
if not copied_files:
|
|
269
|
+
console.print(
|
|
270
|
+
"[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]"
|
|
271
|
+
)
|
|
272
|
+
record({"files_copied": 0, "already_exists": True})
|
|
273
|
+
raise typer.Exit(0)
|
|
274
|
+
|
|
275
|
+
record(
|
|
276
|
+
{
|
|
277
|
+
"detected_ide": detected_ide,
|
|
278
|
+
"files_copied": len(copied_files),
|
|
279
|
+
"settings_updated": settings_path is not None,
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
console.print()
|
|
284
|
+
console.print(Panel("[bold green]✓ Initialization Complete[/bold green]", border_style="green"))
|
|
285
|
+
console.print(f"[green]Copied {len(copied_files)} template(s) to {ide_config['folder']}[/green]")
|
|
286
|
+
if settings_path:
|
|
287
|
+
console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
|
|
288
|
+
console.print()
|
|
289
|
+
console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
|
|
290
|
+
console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
console.print(f"[red]Error:[/red] Failed to initialize IDE integration: {e}")
|
|
294
|
+
raise typer.Exit(1) from e
|