Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file.
**Important:** Changes need to be documented below this block as this is the header section. Each section should be separated by a horizontal rule. Newer changelog entries need to be added on top of prior ones to keep the history chronological with most recent changes first.


---

## [0.42.4] - 2026-03-24

### Fixed

- Hardened terminal output handling for non-UTF-8 environments so Rich output degrades safely on Windows, Linux, and macOS terminals that cannot render Unicode symbols or box drawing characters.
- Updated `specfact init ide` to discover prompt templates and backlog field mapping resources from installed module locations first, with path-based fallback behavior that remains compatible across different install methods such as Hatch, pip, pipx, and uv.
- Improved bundled module runtime compatibility failures to surface actionable interpreter and reinstall guidance instead of opaque import/load errors.

---

## [0.42.3] - 2026-03-23
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ specfact init ide --ide cursor
specfact init ide --ide vscode
```

`specfact init ide` discovers prompt resources from installed workflow modules and exports them to your IDE. If module prompt payloads are not installed yet, the CLI uses packaged fallback resources.

### Run Your First Flow

```bash
Expand Down
11 changes: 6 additions & 5 deletions docs/guides/ide-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permalink: /guides/ide-integration/

**CLI-First Approach**: SpecFact works offline, requires no account, and integrates with your existing workflow. Works with VS Code, Cursor, GitHub Actions, pre-commit hooks, or any IDE. No platform to learn, no vendor lock-in.

**Terminal Output**: The CLI automatically detects embedded terminals (Cursor, VS Code) and CI/CD environments, adapting output formatting automatically. Progress indicators work in all environments - see [Troubleshooting](troubleshooting.md#terminal-output-issues) for details.
**Terminal Output**: The CLI automatically detects embedded terminals (Cursor, VS Code) and CI/CD environments, adapts formatting automatically, and falls back to ASCII-safe rendering when the active terminal encoding cannot display UTF-8 symbols. See [Troubleshooting](troubleshooting.md#terminal-output-issues) for details.

---

Expand Down Expand Up @@ -64,7 +64,7 @@ specfact init ide --ide cursor --install-deps
**What it does:**

1. Detects your IDE (or uses `--ide` flag)
2. Copies prompt templates from `resources/prompts/` to IDE-specific location
2. Discovers prompt templates from installed workflow modules first, then copies them to the IDE-specific location
3. Creates/updates VS Code settings if needed
4. Makes slash commands available in your IDE
5. Optionally installs required packages for contract enhancement (if `--install-deps` is provided):
Expand Down Expand Up @@ -105,10 +105,11 @@ The IDE automatically recognizes these commands and provides enhanced prompts.

Slash commands are **markdown prompt templates** (not executable CLI commands). They:

1. **Live in your repository** - Templates are stored in `resources/prompts/` (packaged with SpecFact CLI)
2. **Get copied to IDE locations** - `specfact init` copies them to IDE-specific directories
1. **Are owned by installed workflow modules** - Bundle-specific prompts ship with their corresponding module packages
2. **Get copied to IDE locations** - `specfact init ide` discovers installed module resources and copies them to IDE-specific directories
3. **Registered automatically** - The IDE reads these files and makes them available as slash commands
4. **Provide enhanced prompts** - Templates include detailed instructions for the AI assistant
4. **Fall back safely during transition** - If no installed module prompt payloads are present yet, SpecFact can still use packaged core fallback resources
5. **Provide enhanced prompts** - Templates include detailed instructions for the AI assistant

### Template Format

Expand Down
9 changes: 8 additions & 1 deletion docs/guides/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,12 @@ The CLI automatically detects terminal capabilities in this order:
- Interactive TTY with animations β†’ **GRAPHICAL** mode
- Non-interactive β†’ **BASIC** mode

5. **Default Fallback**:
5. **Encoding Safety Detection**:
- UTF-8 capable streams keep Unicode icons and Rich box drawing
- Legacy encodings (for example `cp1252`) disable emoji and switch to ASCII-safe box rendering
- Unicode-unsafe text streams are reconfigured with replacement error handling to avoid hard crashes during output

6. **Default Fallback**:
- If uncertain β†’ **BASIC** mode (safe, readable output)

### Terminal Modes
Expand All @@ -553,6 +558,8 @@ The CLI supports three terminal modes (auto-selected based on detection):
- **BASIC** - Plain text, no animations, simple progress updates for CI/CD and embedded terminals
- **MINIMAL** - Minimal output for test mode

Rich output also downgrades symbol rendering when the active terminal encoding is not UTF-8-safe. This keeps Windows legacy consoles and other non-UTF-8 terminals readable instead of failing on icon output.

### Environment Variables (Optional Overrides)

You can override auto-detection using standard environment variables:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# TDD Evidence

## Pre-Implementation Failing Run

- Timestamp: 2026-03-24T21:32:21+01:00
- Command:

```bash
HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch run pytest tests/unit/utils/test_terminal.py tests/unit/utils/test_ide_setup.py tests/unit/modules/init/test_resource_resolution.py tests/unit/specfact_cli/registry/test_module_packages.py -q
```

- Result: failed during test collection.
- Failure summary:
- `tests/unit/utils/test_terminal.py` could not import `ensure_output_stream_safety` from `specfact_cli.utils.terminal`.
- `tests/unit/utils/test_ide_setup.py` could not import `discover_prompt_template_files` from `specfact_cli.utils.ide_setup`.

## Post-Implementation Passing Run

- Timestamp: 2026-03-24T22:07:38+01:00
- Command:

```bash
HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch run pytest tests/unit/utils/test_terminal.py tests/unit/utils/test_ide_setup.py tests/unit/modules/init/test_resource_resolution.py tests/unit/specfact_cli/registry/test_module_packages.py -q
```

- Result: passed.
- Summary: 83 tests passed, covering terminal encoding fallback, runtime compatibility diagnostics, repo-scoped module discovery, duplicate prompt-id handling, and backlog field mapping resource resolution.

## Final Review Gate

- Timestamp: 2026-03-24T22:07:18+01:00
- Command:

```bash
HATCH_DATA_DIR=/tmp/hatch-data HATCH_CACHE_DIR=/tmp/hatch-cache VIRTUALENV_OVERRIDE_APP_DATA=/tmp/virtualenv-appdata hatch run specfact code review run src/specfact_cli/utils/terminal.py src/specfact_cli/runtime.py src/specfact_cli/utils/ide_setup.py src/specfact_cli/modules/init/src/commands.py src/specfact_cli/registry/module_packages.py --exclude-tests
```

- Result: passed.
- Summary: `specfact code review run` completed with no findings on the shipped production files.
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
## 1. Spec And Scope Alignment

- [ ] 1.1 Finalize spec deltas for runtime portability and module-owned IDE prompt resources.
- [ ] 1.2 Confirm the prompt ownership boundary against `init-ide-prompt-source-selection` so this change provides the discovery foundation without duplicating prompt-selection UX work.
- [ ] 1.3 Integrate with `specfact-cli-modules/packaging-01-bundle-resource-payloads` for bundle-packaged prompts and other module-owned resource payloads.
- [x] 1.1 Finalize spec deltas for runtime portability and module-owned IDE prompt resources.
- [x] 1.2 Confirm the prompt ownership boundary against `init-ide-prompt-source-selection` so this change provides the discovery foundation without duplicating prompt-selection UX work.
- [x] 1.3 Integrate with `specfact-cli-modules/packaging-01-bundle-resource-payloads` for bundle-packaged prompts and other module-owned resource payloads.

## 2. Test-First Coverage

- [ ] 2.1 Add failing tests for help/startup rendering on non-UTF-8 terminal encodings with ASCII-safe fallback behavior.
- [ ] 2.2 Add failing tests for actionable runtime/interpreter compatibility errors during backlog automation or similar programmatic invocation.
- [ ] 2.3 Add failing tests for module-owned prompt discovery and `specfact init ide` export behavior from installed module resource directories.
- [ ] 2.4 Add failing tests for module-owned non-prompt resource lookup, starting with backlog field mapping templates.
- [ ] 2.5 Record failing test evidence in `TDD_EVIDENCE.md`.
- [x] 2.1 Add failing tests for help/startup rendering on non-UTF-8 terminal encodings with ASCII-safe fallback behavior.
- [x] 2.2 Add failing tests for actionable runtime/interpreter compatibility errors during backlog automation or similar programmatic invocation.
- [x] 2.3 Add failing tests for module-owned prompt discovery and `specfact init ide` export behavior from installed module resource directories.
- [x] 2.4 Add failing tests for module-owned non-prompt resource lookup, starting with backlog field mapping templates.
- [x] 2.5 Record failing test evidence in `TDD_EVIDENCE.md`.

## 3. Runtime And Resource Implementation

- [ ] 3.1 Implement terminal encoding detection and Unicode/icon fallback in the shared runtime/terminal configuration path.
- [ ] 3.2 Replace brittle path-injection behavior with installation-scoped runtime/module resolution and explicit compatibility diagnostics.
- [ ] 3.3 Refactor `specfact init ide` to build a prompt catalog from installed module resource locations rather than `specfact_cli/resources/prompts`.
- [ ] 3.4 Refactor core init/install resource copying to resolve module-owned templates, starting with backlog field mapping templates, from installed bundle packages.
- [x] 3.1 Implement terminal encoding detection and Unicode/icon fallback in the shared runtime/terminal configuration path.
- [x] 3.2 Replace brittle path-injection behavior with installation-scoped runtime/module resolution and explicit compatibility diagnostics.
- [x] 3.3 Refactor `specfact init ide` to build a prompt catalog from installed module resource locations rather than `specfact_cli/resources/prompts`.
- [x] 3.4 Refactor core init/install resource copying to resolve module-owned templates, starting with backlog field mapping templates, from installed bundle packages.
- [ ] 3.5 Remove or relocate bundle-owned prompt/resources from core packaging so ownership matches installed modules.

## 4. Validation And Documentation

- [ ] 4.1 Re-run the new tests and record passing evidence in `TDD_EVIDENCE.md`.
- [ ] 4.2 Update docs and README guidance for cross-platform terminal behavior, supported automation invocation, and module-owned prompt resources.
- [ ] 4.3 Run `openspec validate packaging-02-cross-platform-runtime-and-module-resources --strict`.
- [x] 4.1 Re-run the new tests and record passing evidence in `TDD_EVIDENCE.md`.
- [x] 4.2 Update docs and README guidance for cross-platform terminal behavior, supported automation invocation, and module-owned prompt resources.
- [x] 4.3 Run `openspec validate packaging-02-cross-platform-runtime-and-module-resources --strict`.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "specfact-cli"
version = "0.42.3"
version = "0.42.4"
description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if __name__ == "__main__":
_setup = setup(
name="specfact-cli",
version="0.42.3",
version="0.42.4",
description=(
"The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with "
"validation and contract enforcement for new projects and long-lived codebases."
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
"""

# Package version: keep in sync with pyproject.toml, setup.py, src/specfact_cli/__init__.py
__version__ = "0.42.3"
__version__ = "0.42.4"
2 changes: 1 addition & 1 deletion src/specfact_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ def _bootstrap_bundle_paths() -> None:

_bootstrap_bundle_paths()

__version__ = "0.42.3"
__version__ = "0.42.4"

__all__ = ["__version__"]
61 changes: 37 additions & 24 deletions src/specfact_cli/analyzers/code_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,26 +644,8 @@ def _analyze_file_parallel(self, file_path: Path) -> dict[str, Any]:
# Extract classes as features
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
# For sequential keys, use placeholder (will be fixed after all features collected)
# For classname keys, we can generate immediately
current_count = 0 if self.key_format == "sequential" else len(self.features)

# Extract Semgrep evidence for confidence scoring
class_start_line = node.lineno if hasattr(node, "lineno") else None
class_end_line = node.end_lineno if hasattr(node, "end_lineno") else None
semgrep_evidence = self._extract_semgrep_evidence(
semgrep_findings, node.name, class_start_line, class_end_line
)

# Create feature with Semgrep evidence included in confidence calculation
feature = self._extract_feature_from_class_parallel(
node, file_path, current_count, semgrep_evidence
)
if feature:
# Enhance feature with detailed Semgrep findings (outcomes, constraints, themes)
self._enhance_feature_with_semgrep(
feature, semgrep_findings, file_path, node.name, class_start_line, class_end_line
)
feature = self._extract_feature_for_results(node, file_path, semgrep_findings)
if feature is not None:
results["features"].append(feature)

except (SyntaxError, UnicodeDecodeError):
Expand All @@ -672,6 +654,31 @@ def _analyze_file_parallel(self, file_path: Path) -> dict[str, Any]:

return results

def _extract_feature_for_results(
self,
node: ast.ClassDef,
file_path: Path,
semgrep_findings: list[dict[str, Any]],
) -> Feature | None:
"""Extract one feature while isolating per-class enhancement failures."""
current_count = 0 if self.key_format == "sequential" else len(self.features)
class_start_line = node.lineno if hasattr(node, "lineno") else None
class_end_line = node.end_lineno if hasattr(node, "end_lineno") else None
semgrep_evidence = self._extract_semgrep_evidence(semgrep_findings, node.name, class_start_line, class_end_line)

feature = self._extract_feature_from_class_parallel(node, file_path, current_count, semgrep_evidence)
if feature is None:
return None

try:
self._enhance_feature_with_semgrep(
feature, semgrep_findings, file_path, node.name, class_start_line, class_end_line
)
except Exception as exc:
console.print(f"[dim]⚠ Warning: Skipped Semgrep enhancement for {file_path}:{node.name}: {exc}[/dim]")

return feature

def _merge_analysis_results(self, results: dict[str, Any]) -> None:
"""Merge parallel analysis results into instance variables."""
# Merge themes
Expand Down Expand Up @@ -1327,10 +1334,16 @@ def _extract_story_artifacts(
if not methods:
return None, None
primary_method = methods[0]
scenarios = self.control_flow_analyzer.extract_scenarios_from_method(
primary_method, class_name, primary_method.name
)
contracts = self.contract_extractor.extract_function_contracts(primary_method)
try:
scenarios = self.control_flow_analyzer.extract_scenarios_from_method(
primary_method, class_name, primary_method.name
)
except Exception:
scenarios = None
try:
contracts = self.contract_extractor.extract_function_contracts(primary_method)
except Exception:
contracts = None
return scenarios, contracts

def _generate_story_title(self, group_name: str, class_name: str) -> str:
Expand Down
6 changes: 3 additions & 3 deletions src/specfact_cli/modules/init/module-package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: init
version: 0.1.10
version: 0.1.12
commands:
- init
category: core
Expand All @@ -17,5 +17,5 @@ publisher:
description: Initialize SpecFact workspace and bootstrap local configuration.
license: Apache-2.0
integrity:
checksum: sha256:e95ce8c81cc16aac931b977f78c0e4652a68f3d2e81aa09ce496d4753698d231
signature: UaFkWSDeevp4et+OM5aKrEk2E+lnTP3idTJg1K0tmFn8bF9tgU1fnnswKSQtn1wL6VEP8lv7XIDxeKxVZP2SDg==
checksum: sha256:e93721d256bf67944a6a783dea42c8bfa15d5130212f747193f6df8b2b19eaad
signature: UKbbJ+H+Zeo+na5du/EN+YG8cymPdnYpKW8cZ28xjcDl+9VD520LF7aDXeyqC9UlCooAcWdOr0bGxBmibqsxCA==
Loading
Loading