specfact-cli 0.6.4__tar.gz → 0.6.6__tar.gz

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 (99) hide show
  1. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/PKG-INFO +1 -1
  2. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/pyproject.toml +2 -1
  3. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-import-from-code.md +13 -0
  4. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-review.md +4 -1
  5. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-update-feature.md +12 -4
  6. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/__init__.py +1 -1
  7. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/__init__.py +1 -1
  8. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/cli.py +7 -0
  9. specfact_cli-0.6.6/src/specfact_cli/commands/init.py +294 -0
  10. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/plan.py +5 -1
  11. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/importers/speckit_converter.py +20 -5
  12. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/ide_setup.py +163 -0
  13. specfact_cli-0.6.4/src/specfact_cli/commands/init.py +0 -143
  14. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/.gitignore +0 -0
  15. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/LICENSE.md +0 -0
  16. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/README.md +0 -0
  17. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/mappings/node-async.yaml +0 -0
  18. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/mappings/python-async.yaml +0 -0
  19. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/mappings/speckit-default.yaml +0 -0
  20. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-enforce.md +0 -0
  21. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-add-feature.md +0 -0
  22. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-add-story.md +0 -0
  23. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-compare.md +0 -0
  24. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-init.md +0 -0
  25. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-promote.md +0 -0
  26. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-select.md +0 -0
  27. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-plan-update-idea.md +0 -0
  28. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-repro.md +0 -0
  29. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/prompts/specfact-sync.md +0 -0
  30. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/schemas/deviation.schema.json +0 -0
  31. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/schemas/plan.schema.json +0 -0
  32. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/schemas/protocol.schema.json +0 -0
  33. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/templates/github-action.yml.j2 +0 -0
  34. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/templates/plan.bundle.yaml.j2 +0 -0
  35. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/templates/pr-template.md.j2 +0 -0
  36. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/templates/protocol.yaml.j2 +0 -0
  37. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/resources/templates/telemetry.yaml.example +0 -0
  38. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/__init__.py +0 -0
  39. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/analyze_agent.py +0 -0
  40. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/base.py +0 -0
  41. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/plan_agent.py +0 -0
  42. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/registry.py +0 -0
  43. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/agents/sync_agent.py +0 -0
  44. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/__init__.py +0 -0
  45. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
  46. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
  47. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  48. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
  49. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  50. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  51. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/__init__.py +0 -0
  52. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/constitution.py +0 -0
  53. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/enforce.py +0 -0
  54. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/import_cmd.py +0 -0
  55. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/repro.py +0 -0
  56. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/commands/sync.py +0 -0
  57. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/common/__init__.py +0 -0
  58. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/common/logger_setup.py +0 -0
  59. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/common/logging_utils.py +0 -0
  60. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/common/text_utils.py +0 -0
  61. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/common/utils.py +0 -0
  62. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/comparators/__init__.py +0 -0
  63. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  64. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  65. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
  66. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/generators/__init__.py +0 -0
  67. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/generators/plan_generator.py +0 -0
  68. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/generators/protocol_generator.py +0 -0
  69. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/generators/report_generator.py +0 -0
  70. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/generators/workflow_generator.py +0 -0
  71. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/importers/__init__.py +0 -0
  72. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  73. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/models/__init__.py +0 -0
  74. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/models/deviation.py +0 -0
  75. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/models/enforcement.py +0 -0
  76. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/models/plan.py +0 -0
  77. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/models/protocol.py +0 -0
  78. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/modes/__init__.py +0 -0
  79. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/modes/detector.py +0 -0
  80. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/modes/router.py +0 -0
  81. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  82. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/sync/__init__.py +0 -0
  83. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/sync/repository_sync.py +0 -0
  84. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/sync/speckit_sync.py +0 -0
  85. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/sync/watcher.py +0 -0
  86. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/telemetry.py +0 -0
  87. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/__init__.py +0 -0
  88. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/console.py +0 -0
  89. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  90. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/feature_keys.py +0 -0
  91. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/git.py +0 -0
  92. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/github_annotations.py +0 -0
  93. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/prompts.py +0 -0
  94. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/structure.py +0 -0
  95. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/utils/yaml_utils.py +0 -0
  96. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/validators/__init__.py +0 -0
  97. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/validators/fsm.py +0 -0
  98. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/validators/repro_checker.py +0 -0
  99. {specfact_cli-0.6.4 → specfact_cli-0.6.6}/src/specfact_cli/validators/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specfact-cli
3
- Version: 0.6.4
3
+ Version: 0.6.6
4
4
  Summary: Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions.
5
5
  Project-URL: Homepage, https://github.com/nold-ai/specfact-cli
6
6
  Project-URL: Repository, https://github.com/nold-ai/specfact-cli.git
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "specfact-cli"
7
- version = "0.6.4"
7
+ version = "0.6.6"
8
8
  description = "Brownfield-first CLI: Reverse engineer legacy Python → specs → enforced contracts. Automate legacy code documentation and prevent modernization regressions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -106,6 +106,7 @@ Trademarks = "https://github.com/nold-ai/specfact-cli/blob/main/TRADEMARKS.md"
106
106
 
107
107
  [project.scripts]
108
108
  specfact = "specfact_cli.cli:cli_main"
109
+ specfact-cli = "specfact_cli.cli:cli_main" # Alias for uvx compatibility
109
110
 
110
111
  # [project.entry-points."pytest11"] # Add if you have pytest plugins
111
112
  # specfact_test_plugin = "specfact_cli.pytest_plugin"
@@ -134,7 +134,11 @@ When in copilot mode, follow this three-phase workflow:
134
134
  **ALWAYS execute CLI first** to get structured, validated output:
135
135
 
136
136
  ```bash
137
+ # Full repository analysis
137
138
  specfact import from-code --repo <path> --name <name> --confidence <score>
139
+
140
+ # Partial repository analysis (analyze only specific subdirectory)
141
+ specfact import from-code --repo <path> --name <name> --entry-point <subdirectory> --confidence <score>
138
142
  ```
139
143
 
140
144
  **Note**: Mode is auto-detected by the CLI (CI/CD in non-interactive environments, CoPilot when in IDE/Copilot session). No need to specify `--mode` flag.
@@ -245,6 +249,11 @@ Extract arguments from user input:
245
249
  - `--report PATH` - Analysis report path (optional, default: `.specfact/reports/brownfield/analysis-<timestamp>.md`)
246
250
  - `--shadow-only` - Observe mode without enforcing (optional)
247
251
  - `--key-format {classname|sequential}` - Feature key format (default: `classname`)
252
+ - `--entry-point PATH` - Subdirectory path for partial analysis (relative to repo root). Analyzes only files within this directory and subdirectories. Useful for:
253
+ - Multi-project repositories (monorepos): Analyze one project at a time
254
+ - Large codebases: Focus on specific modules or subsystems
255
+ - Incremental modernization: Modernize one part of the codebase at a time
256
+ - Example: `--entry-point projects/api-service` analyzes only `projects/api-service/` and its subdirectories
248
257
 
249
258
  **Important**: If `--name` is not provided, **ask the user interactively** for a meaningful plan name and **WAIT for their response**. The name will be automatically sanitized (lowercased, spaces/special chars removed) for filesystem persistence.
250
259
 
@@ -261,7 +270,11 @@ For single quotes in args like "I'm Groot", use escape syntax: e.g `'I'\''m Groo
261
270
  **ALWAYS execute the specfact CLI first** to get structured, validated output:
262
271
 
263
272
  ```bash
273
+ # Full repository analysis
264
274
  specfact import from-code --repo <repo_path> --name <plan_name> --confidence <confidence>
275
+
276
+ # Partial repository analysis (analyze only specific subdirectory)
277
+ specfact import from-code --repo <repo_path> --name <plan_name> --entry-point <subdirectory> --confidence <confidence>
265
278
  ```
266
279
 
267
280
  **Note**: Mode is auto-detected by the CLI. No need to specify `--mode` flag.
@@ -50,7 +50,10 @@ You **MUST** consider the user input before proceeding (if not empty).
50
50
 
51
51
  **For updating features**:
52
52
 
53
- - `specfact plan update-feature --key <key> --title <title> --outcomes <outcomes> --acceptance <acceptance> --constraints <constraints> --confidence <confidence> --draft <true/false> --plan <path>`
53
+ - `specfact plan update-feature --key <key> --title <title> --outcomes <outcomes> --acceptance <acceptance> --constraints <constraints> --confidence <confidence> --draft/--no-draft --plan <path>`
54
+ - **Boolean flags**: `--draft` sets True, `--no-draft` sets False, omit to leave unchanged
55
+ - ❌ **WRONG**: `--draft true` or `--draft false` (Typer boolean flags don't accept values)
56
+ - ✅ **CORRECT**: `--draft` (sets True) or `--no-draft` (sets False)
54
57
  - Updates existing feature metadata (title, outcomes, acceptance criteria, constraints, confidence, draft status)
55
58
  - Works in CI/CD, Copilot, and interactive modes
56
59
  - Example: `specfact plan update-feature --key FEATURE-001 --title "New Title" --outcomes "Outcome 1, Outcome 2"`
@@ -85,7 +85,7 @@ The `specfact plan update-feature` command:
85
85
  - Acceptance criteria (optional, comma-separated)
86
86
  - Constraints (optional, comma-separated)
87
87
  - Confidence (optional, 0.0-1.0)
88
- - Draft status (optional, true/false)
88
+ - Draft status (optional, boolean flag: `--draft` sets True, `--no-draft` sets False, omit to leave unchanged)
89
89
  - Plan bundle path (optional, defaults to active plan or `.specfact/plans/main.bundle.yaml`)
90
90
 
91
91
  **WAIT STATE**: If feature key is missing, ask the user:
@@ -155,10 +155,16 @@ specfact plan update-feature \
155
155
  --constraints "Python 3.11+, Test coverage >= 80%" \
156
156
  --plan <plan_path>
157
157
 
158
- # Mark as draft
158
+ # Mark as draft (boolean flag: --draft sets True, --no-draft sets False)
159
159
  specfact plan update-feature \
160
160
  --key FEATURE-001 \
161
- --draft true \
161
+ --draft \
162
+ --plan <plan_path>
163
+
164
+ # Unmark draft (set to False)
165
+ specfact plan update-feature \
166
+ --key FEATURE-001 \
167
+ --no-draft \
162
168
  --plan <plan_path>
163
169
  ```
164
170
 
@@ -209,7 +215,9 @@ specfact plan update-feature \
209
215
  - **Partial updates**: Only specified fields are updated, others remain unchanged
210
216
  - **Comma-separated lists**: Outcomes, acceptance, and constraints use comma-separated strings
211
217
  - **Confidence range**: Must be between 0.0 and 1.0
212
- - **Draft status**: Use `true` or `false` (boolean)
218
+ - **Draft status**: Boolean flag - use `--draft` to set True, `--no-draft` to set False, omit to leave unchanged
219
+ - ❌ **WRONG**: `--draft true` or `--draft false` (Typer boolean flags don't accept values)
220
+ - ✅ **CORRECT**: `--draft` (sets True) or `--no-draft` (sets False) or omit (leaves unchanged)
213
221
 
214
222
  ### Field Guidelines
215
223
 
@@ -3,4 +3,4 @@ SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development.
3
3
  """
4
4
 
5
5
  # Define the package version (kept in sync with pyproject.toml and setup.py)
6
- __version__ = "0.6.4"
6
+ __version__ = "0.6.6"
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.6.4"
12
+ __version__ = "0.6.6"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -101,6 +101,7 @@ app = typer.Typer(
101
101
  help="SpecFact CLI - Spec→Contract→Sentinel tool for contract-driven development",
102
102
  add_completion=True, # Enable Typer's built-in completion (works natively for bash/zsh/fish without extensions)
103
103
  rich_markup_mode="rich",
104
+ context_settings={"help_option_names": ["-h", "--help"]}, # Add -h as alias for --help
104
105
  )
105
106
 
106
107
  console = Console()
@@ -173,6 +174,12 @@ def main(
173
174
  - Auto-detect from environment (CoPilot API, IDE integration)
174
175
  - Default to CI/CD mode
175
176
  """
177
+ # Show help if no command provided (avoids user confusion)
178
+ if ctx.invoked_subcommand is None:
179
+ # Show help by calling Typer's help callback
180
+ ctx.get_help()
181
+ raise typer.Exit()
182
+
176
183
  # Store mode in context for commands to access
177
184
  if ctx.obj is None:
178
185
  ctx.obj = {}
@@ -0,0 +1,294 @@
1
+ """
2
+ Init command - Initialize SpecFact for IDE integration.
3
+
4
+ This module provides the `specfact init` command to copy prompt templates
5
+ to IDE-specific locations for slash command integration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import typer
14
+ from beartype import beartype
15
+ from icontract import ensure, require
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+
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
+ )
27
+
28
+
29
+ app = typer.Typer(help="Initialize SpecFact for IDE integration")
30
+ console = Console()
31
+
32
+
33
+ def _is_valid_repo_path(path: Path) -> bool:
34
+ """Check if path exists and is a directory."""
35
+ return path.exists() and path.is_dir()
36
+
37
+
38
+ @app.callback(invoke_without_command=True)
39
+ @require(lambda ide: ide in IDE_CONFIG or ide == "auto", "IDE must be valid or 'auto'")
40
+ @require(lambda repo: _is_valid_repo_path(repo), "Repo path must exist and be directory")
41
+ @ensure(lambda result: result is None, "Command should return None")
42
+ @beartype
43
+ def init(
44
+ ide: str = typer.Option(
45
+ "auto",
46
+ "--ide",
47
+ help="IDE type (auto, cursor, vscode, copilot, claude, gemini, qwen, opencode, windsurf, kilocode, auggie, roo, codebuddy, amp, q)",
48
+ ),
49
+ repo: Path = typer.Option(
50
+ Path("."),
51
+ "--repo",
52
+ help="Repository path (default: current directory)",
53
+ exists=True,
54
+ file_okay=False,
55
+ dir_okay=True,
56
+ ),
57
+ force: bool = typer.Option(
58
+ False,
59
+ "--force",
60
+ help="Overwrite existing files",
61
+ ),
62
+ ) -> None:
63
+ """
64
+ Initialize SpecFact for IDE integration.
65
+
66
+ Copies prompt templates to IDE-specific locations so slash commands work.
67
+ This command detects the IDE type (or uses --ide flag) and copies
68
+ SpecFact prompt templates to the appropriate directory.
69
+
70
+ Examples:
71
+ specfact init # Auto-detect IDE
72
+ specfact init --ide cursor # Initialize for Cursor
73
+ specfact init --ide vscode --force # Overwrite existing files
74
+ specfact init --repo /path/to/repo --ide copilot
75
+ """
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"]
89
+
90
+ console.print()
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})")
94
+ console.print()
95
+
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
@@ -774,7 +774,11 @@ def update_feature(
774
774
  acceptance: str | None = typer.Option(None, "--acceptance", help="Acceptance criteria (comma-separated)"),
775
775
  constraints: str | None = typer.Option(None, "--constraints", help="Constraints (comma-separated)"),
776
776
  confidence: float | None = typer.Option(None, "--confidence", help="Confidence score (0.0-1.0)"),
777
- draft: bool | None = typer.Option(None, "--draft", help="Mark as draft (true/false)"),
777
+ draft: bool | None = typer.Option(
778
+ None,
779
+ "--draft/--no-draft",
780
+ help="Mark as draft (use --draft to set True, --no-draft to set False, omit to leave unchanged)",
781
+ ),
778
782
  plan: Path | None = typer.Option(
779
783
  None,
780
784
  "--plan",
@@ -398,8 +398,8 @@ class SpecKitConverter:
398
398
  feature_dir = self.repo_path / "specs" / f"{feature_num:03d}-{feature_name}"
399
399
  feature_dir.mkdir(parents=True, exist_ok=True)
400
400
 
401
- # Generate spec.md
402
- spec_content = self._generate_spec_markdown(feature)
401
+ # Generate spec.md (pass calculated feature_num to avoid recalculation)
402
+ spec_content = self._generate_spec_markdown(feature, feature_num=feature_num)
403
403
  (feature_dir / "spec.md").write_text(spec_content, encoding="utf-8")
404
404
 
405
405
  # Generate plan.md
@@ -416,14 +416,29 @@ class SpecKitConverter:
416
416
 
417
417
  @beartype
418
418
  @require(lambda feature: isinstance(feature, Feature), "Must be Feature instance")
419
+ @require(
420
+ lambda feature_num: feature_num is None or feature_num > 0,
421
+ "Feature number must be None or positive",
422
+ )
419
423
  @ensure(lambda result: isinstance(result, str), "Must return string")
420
424
  @ensure(lambda result: len(result) > 0, "Result must be non-empty")
421
- def _generate_spec_markdown(self, feature: Feature) -> str:
422
- """Generate Spec-Kit spec.md content from SpecFact feature."""
425
+ def _generate_spec_markdown(self, feature: Feature, feature_num: int | None = None) -> str:
426
+ """
427
+ Generate Spec-Kit spec.md content from SpecFact feature.
428
+
429
+ Args:
430
+ feature: Feature to generate spec for
431
+ feature_num: Optional pre-calculated feature number (avoids recalculation with fallback)
432
+ """
423
433
  from datetime import datetime
424
434
 
425
435
  # Extract feature branch from feature key (FEATURE-001 -> 001-feature-name)
426
- feature_num = self._extract_feature_number(feature.key)
436
+ # Use provided feature_num if available, otherwise extract from key (with fallback to 1)
437
+ if feature_num is None:
438
+ feature_num = self._extract_feature_number(feature.key)
439
+ if feature_num == 0:
440
+ # Fallback: use 1 if no number found (shouldn't happen if called from convert_to_speckit)
441
+ feature_num = 1
427
442
  feature_name = self._to_feature_dir_name(feature.title)
428
443
  feature_branch = f"{feature_num:03d}-{feature_name}"
429
444
 
@@ -9,6 +9,8 @@ from __future__ import annotations
9
9
 
10
10
  import os
11
11
  import re
12
+ import site
13
+ import sys
12
14
  from pathlib import Path
13
15
  from typing import Literal
14
16
 
@@ -387,3 +389,164 @@ def create_vscode_settings(repo_path: Path, settings_file: str) -> Path | None:
387
389
 
388
390
  console.print(f"[green]Updated:[/green] {settings_path}")
389
391
  return settings_path
392
+
393
+
394
+ @beartype
395
+ @ensure(
396
+ lambda result: isinstance(result, list) and all(isinstance(p, Path) for p in result), "Must return list of Paths"
397
+ )
398
+ def get_package_installation_locations(package_name: str) -> list[Path]:
399
+ """
400
+ Get all possible installation locations for a Python package across different OS and installation types.
401
+
402
+ This function searches for package locations in:
403
+ - User site-packages (per-user installations: ~/.local/lib/python3.X/site-packages)
404
+ - System site-packages (global installations: /usr/lib/python3.X/site-packages, C:\\Python3X\\Lib\\site-packages)
405
+ - Virtual environments (venv, conda, etc.)
406
+ - uvx cache locations (~/.cache/uv/archive-v0/...)
407
+
408
+ Args:
409
+ package_name: Name of the package to locate (e.g., "specfact_cli")
410
+
411
+ Returns:
412
+ List of Path objects representing possible package installation locations
413
+
414
+ Examples:
415
+ >>> locations = get_package_installation_locations("specfact_cli")
416
+ >>> len(locations) > 0
417
+ True
418
+ """
419
+ locations: list[Path] = []
420
+
421
+ # Method 1: Use importlib.util.find_spec() to find the actual installed location
422
+ try:
423
+ import importlib.util
424
+
425
+ spec = importlib.util.find_spec(package_name)
426
+ if spec and spec.origin:
427
+ package_path = Path(spec.origin).parent.resolve()
428
+ locations.append(package_path)
429
+ except Exception:
430
+ pass
431
+
432
+ # Method 2: Check all site-packages directories (user + system)
433
+ try:
434
+ # User site-packages (per-user installation)
435
+ # Linux/macOS: ~/.local/lib/python3.X/site-packages
436
+ # Windows: %APPDATA%\\Python\\Python3X\\site-packages
437
+ user_site = site.getusersitepackages()
438
+ if user_site:
439
+ user_package_path = Path(user_site) / package_name
440
+ if user_package_path.exists():
441
+ locations.append(user_package_path.resolve())
442
+ except Exception:
443
+ pass
444
+
445
+ try:
446
+ # System site-packages (global installation)
447
+ # Linux: /usr/lib/python3.X/dist-packages, /usr/local/lib/python3.X/dist-packages
448
+ # macOS: /Library/Frameworks/Python.framework/Versions/X/lib/pythonX.X/site-packages
449
+ # Windows: C:\\Python3X\\Lib\\site-packages
450
+ system_sites = site.getsitepackages()
451
+ for site_path in system_sites:
452
+ system_package_path = Path(site_path) / package_name
453
+ if system_package_path.exists():
454
+ locations.append(system_package_path.resolve())
455
+ except Exception:
456
+ pass
457
+
458
+ # Method 3: Check sys.path for additional locations (virtual environments, etc.)
459
+ for path_str in sys.path:
460
+ if not path_str or path_str == "":
461
+ continue
462
+ try:
463
+ path = Path(path_str).resolve()
464
+ if path.exists() and path.is_dir():
465
+ # Check if package is directly in this path
466
+ package_path = path / package_name
467
+ if package_path.exists():
468
+ locations.append(package_path.resolve())
469
+ # Check if this is a site-packages directory
470
+ if path.name == "site-packages" or "site-packages" in path.parts:
471
+ package_path = path / package_name
472
+ if package_path.exists():
473
+ locations.append(package_path.resolve())
474
+ except Exception:
475
+ continue
476
+
477
+ # Method 4: Check uvx cache locations (common on Linux/macOS/Windows)
478
+ # uvx stores packages in cache directories with varying structures
479
+ if sys.platform != "win32":
480
+ # Linux/macOS: ~/.cache/uv/archive-v0/.../lib/python3.X/site-packages/
481
+ uvx_cache_base = Path.home() / ".cache" / "uv" / "archive-v0"
482
+ if uvx_cache_base.exists():
483
+ for archive_dir in uvx_cache_base.iterdir():
484
+ if archive_dir.is_dir():
485
+ # Look for site-packages directories (rglob finds all matches)
486
+ for site_packages_dir in archive_dir.rglob("site-packages"):
487
+ if site_packages_dir.is_dir():
488
+ package_path = site_packages_dir / package_name
489
+ if package_path.exists():
490
+ locations.append(package_path.resolve())
491
+ else:
492
+ # Windows: Check %LOCALAPPDATA%\\uv\\cache\\archive-v0\\
493
+ localappdata = os.environ.get("LOCALAPPDATA")
494
+ if localappdata:
495
+ uvx_cache_base = Path(localappdata) / "uv" / "cache" / "archive-v0"
496
+ if uvx_cache_base.exists():
497
+ for archive_dir in uvx_cache_base.iterdir():
498
+ if archive_dir.is_dir():
499
+ # Look for site-packages directories
500
+ for site_packages_dir in archive_dir.rglob("site-packages"):
501
+ if site_packages_dir.is_dir():
502
+ package_path = site_packages_dir / package_name
503
+ if package_path.exists():
504
+ locations.append(package_path.resolve())
505
+
506
+ # Remove duplicates while preserving order
507
+ seen = set()
508
+ unique_locations: list[Path] = []
509
+ for loc in locations:
510
+ loc_str = str(loc)
511
+ if loc_str not in seen:
512
+ seen.add(loc_str)
513
+ unique_locations.append(loc)
514
+
515
+ return unique_locations
516
+
517
+
518
+ @beartype
519
+ @require(lambda package_name: isinstance(package_name, str) and len(package_name) > 0, "Package name must be non-empty")
520
+ @ensure(
521
+ lambda result: result is None or (isinstance(result, Path) and result.exists()),
522
+ "Result must be None or existing Path",
523
+ )
524
+ def find_package_resources_path(package_name: str, resource_subpath: str) -> Path | None:
525
+ """
526
+ Find the path to a resource within an installed package.
527
+
528
+ Searches across all possible installation locations (user, system, venv, uvx cache)
529
+ to find the package and then locates the resource subpath.
530
+
531
+ Args:
532
+ package_name: Name of the package (e.g., "specfact_cli")
533
+ resource_subpath: Subpath within the package (e.g., "resources/prompts")
534
+
535
+ Returns:
536
+ Path to the resource directory if found, None otherwise
537
+
538
+ Examples:
539
+ >>> path = find_package_resources_path("specfact_cli", "resources/prompts")
540
+ >>> path is None or path.exists()
541
+ True
542
+ """
543
+ # Get all possible package installation locations
544
+ package_locations = get_package_installation_locations(package_name)
545
+
546
+ # Try each location
547
+ for package_path in package_locations:
548
+ resource_path = (package_path / resource_subpath).resolve()
549
+ if resource_path.exists():
550
+ return resource_path
551
+
552
+ return None
@@ -1,143 +0,0 @@
1
- """
2
- Init command - Initialize SpecFact for IDE integration.
3
-
4
- This module provides the `specfact init` command to copy prompt templates
5
- to IDE-specific locations for slash command integration.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from pathlib import Path
11
-
12
- import typer
13
- from beartype import beartype
14
- from icontract import ensure, require
15
- from rich.console import Console
16
- from rich.panel import Panel
17
-
18
- from specfact_cli.telemetry import telemetry
19
- from specfact_cli.utils.ide_setup import IDE_CONFIG, copy_templates_to_ide, detect_ide
20
-
21
-
22
- app = typer.Typer(help="Initialize SpecFact for IDE integration")
23
- console = Console()
24
-
25
-
26
- def _is_valid_repo_path(path: Path) -> bool:
27
- """Check if path exists and is a directory."""
28
- return path.exists() and path.is_dir()
29
-
30
-
31
- @app.callback(invoke_without_command=True)
32
- @require(lambda ide: ide in IDE_CONFIG or ide == "auto", "IDE must be valid or 'auto'")
33
- @require(lambda repo: _is_valid_repo_path(repo), "Repo path must exist and be directory")
34
- @ensure(lambda result: result is None, "Command should return None")
35
- @beartype
36
- def init(
37
- ide: str = typer.Option(
38
- "auto",
39
- "--ide",
40
- help="IDE type (auto, cursor, vscode, copilot, claude, gemini, qwen, opencode, windsurf, kilocode, auggie, roo, codebuddy, amp, q)",
41
- ),
42
- repo: Path = typer.Option(
43
- Path("."),
44
- "--repo",
45
- help="Repository path (default: current directory)",
46
- exists=True,
47
- file_okay=False,
48
- dir_okay=True,
49
- ),
50
- force: bool = typer.Option(
51
- False,
52
- "--force",
53
- help="Overwrite existing files",
54
- ),
55
- ) -> None:
56
- """
57
- Initialize SpecFact for IDE integration.
58
-
59
- Copies prompt templates to IDE-specific locations so slash commands work.
60
- This command detects the IDE type (or uses --ide flag) and copies
61
- SpecFact prompt templates to the appropriate directory.
62
-
63
- Examples:
64
- specfact init # Auto-detect IDE
65
- specfact init --ide cursor # Initialize for Cursor
66
- specfact init --ide vscode --force # Overwrite existing files
67
- specfact init --repo /path/to/repo --ide copilot
68
- """
69
- telemetry_metadata = {
70
- "ide": ide,
71
- "force": force,
72
- }
73
-
74
- with telemetry.track_command("init", telemetry_metadata) as record:
75
- # Resolve repo path
76
- repo_path = repo.resolve()
77
-
78
- # Detect IDE
79
- detected_ide = detect_ide(ide)
80
- ide_config = IDE_CONFIG[detected_ide]
81
- ide_name = ide_config["name"]
82
-
83
- console.print()
84
- console.print(Panel("[bold cyan]SpecFact IDE Setup[/bold cyan]", border_style="cyan"))
85
- console.print(f"[cyan]Repository:[/cyan] {repo_path}")
86
- console.print(f"[cyan]IDE:[/cyan] {ide_name} ({detected_ide})")
87
- console.print()
88
-
89
- # Find templates directory
90
- # Try relative to project root first (for development)
91
- templates_dir = repo_path / "resources" / "prompts"
92
- if not templates_dir.exists():
93
- # Try relative to installed package (for distribution)
94
- import importlib.util
95
-
96
- spec = importlib.util.find_spec("specfact_cli")
97
- if spec and spec.origin:
98
- package_dir = Path(spec.origin).parent.parent
99
- templates_dir = package_dir / "site-packages" / "specfact_cli" / "resources" / "prompts"
100
- if not templates_dir.exists():
101
- # Fallback: try resources/prompts in project root
102
- templates_dir = Path(__file__).parent.parent.parent.parent / "resources" / "prompts"
103
-
104
- if not templates_dir.exists():
105
- console.print(f"[red]Error:[/red] Templates directory not found: {templates_dir}")
106
- console.print("[yellow]Expected location:[/yellow] resources/prompts/")
107
- console.print("[yellow]Please ensure SpecFact is properly installed.[/yellow]")
108
- raise typer.Exit(1)
109
-
110
- console.print(f"[cyan]Templates:[/cyan] {templates_dir}")
111
- console.print()
112
-
113
- # Copy templates to IDE location
114
- try:
115
- copied_files, settings_path = copy_templates_to_ide(repo_path, detected_ide, templates_dir, force)
116
-
117
- if not copied_files:
118
- console.print(
119
- "[yellow]No templates copied (all files already exist, use --force to overwrite)[/yellow]"
120
- )
121
- record({"files_copied": 0, "already_exists": True})
122
- raise typer.Exit(0)
123
-
124
- record(
125
- {
126
- "detected_ide": detected_ide,
127
- "files_copied": len(copied_files),
128
- "settings_updated": settings_path is not None,
129
- }
130
- )
131
-
132
- console.print()
133
- console.print(Panel("[bold green]✓ Initialization Complete[/bold green]", border_style="green"))
134
- console.print(f"[green]Copied {len(copied_files)} template(s) to {ide_config['folder']}[/green]")
135
- if settings_path:
136
- console.print(f"[green]Updated VS Code settings:[/green] {settings_path}")
137
- console.print()
138
- console.print("[dim]You can now use SpecFact slash commands in your IDE![/dim]")
139
- console.print("[dim]Example: /specfact-import-from-code --repo . --confidence 0.7[/dim]")
140
-
141
- except Exception as e:
142
- console.print(f"[red]Error:[/red] Failed to initialize IDE integration: {e}")
143
- raise typer.Exit(1) from e
File without changes
File without changes
File without changes