specfact-cli 0.14.2__tar.gz → 0.15.1__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.
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/PKG-INFO +1 -1
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/pyproject.toml +1 -1
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.01-import.md +8 -5
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.03-review.md +92 -16
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.04-sdd.md +4 -4
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.05-enforce.md +3 -3
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.compare.md +2 -2
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.validate.md +2 -2
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +13 -3
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/code_analyzer.py +34 -5
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +9 -23
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/enforce.py +10 -9
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/generate.py +15 -12
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/implement.py +3 -3
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/import_cmd.py +42 -63
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/migrate.py +456 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/plan.py +56 -16
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/repro.py +11 -5
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/sdd.py +5 -2
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/enrichers/plan_enricher.py +47 -18
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/acceptance_criteria.py +40 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/bundle_loader.py +3 -2
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/github_annotations.py +35 -10
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/sdd_discovery.py +27 -15
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/structure.py +334 -50
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/.gitignore +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/LICENSE.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/README.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/bridge.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/run.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.14.2 → specfact_cli-0.15.1}/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.
|
|
3
|
+
Version: 0.15.1
|
|
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.
|
|
7
|
+
version = "0.15.1"
|
|
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"
|
|
@@ -20,7 +20,7 @@ Import codebase → plan bundle. CLI extracts routes/schemas/relationships/contr
|
|
|
20
20
|
|
|
21
21
|
**Target/Input**: `--bundle NAME` (optional, defaults to active plan), `--repo PATH`, `--entry-point PATH`, `--enrichment PATH`
|
|
22
22
|
**Output/Results**: `--report PATH`
|
|
23
|
-
**Behavior/Options**: `--shadow-only`, `--enrich-for-speckit`
|
|
23
|
+
**Behavior/Options**: `--shadow-only`, `--enrich-for-speckit/--no-enrich-for-speckit` (default: enabled, uses PlanEnricher for consistent enrichment)
|
|
24
24
|
**Advanced/Configuration**: `--confidence FLOAT` (0.0-1.0), `--key-format FORMAT` (classname|sequential)
|
|
25
25
|
|
|
26
26
|
## Workflow
|
|
@@ -28,12 +28,14 @@ Import codebase → plan bundle. CLI extracts routes/schemas/relationships/contr
|
|
|
28
28
|
1. **Execute CLI**: `specfact import from-code [<bundle>] --repo <path> [options]`
|
|
29
29
|
- CLI extracts: routes (FastAPI/Flask/Django), schemas (Pydantic), relationships, contracts (OpenAPI scaffolds), source tracking
|
|
30
30
|
- Uses active plan if bundle not specified
|
|
31
|
+
- **Auto-enrichment enabled by default**: Automatically enhances vague acceptance criteria, incomplete requirements, and generic tasks using PlanEnricher (same logic as `plan review --auto-enrich`)
|
|
32
|
+
- Use `--no-enrich-for-speckit` to disable auto-enrichment
|
|
31
33
|
|
|
32
34
|
2. **LLM Enrichment** (if `--enrichment` provided):
|
|
33
35
|
- Read `.specfact/projects/<bundle>/enrichment_context.md`
|
|
34
36
|
- Enrich: business context, "why" reasoning, missing acceptance criteria
|
|
35
37
|
- Validate: contracts vs code, feature/story alignment
|
|
36
|
-
- Save enrichment report to `.specfact/reports/enrichment/` (if created)
|
|
38
|
+
- Save enrichment report to `.specfact/projects/<bundle-name>/reports/enrichment/` (bundle-specific, Phase 8.5, if created)
|
|
37
39
|
|
|
38
40
|
3. **Present**: Bundle location, report path, summary (features/stories/contracts/relationships)
|
|
39
41
|
|
|
@@ -86,7 +88,7 @@ specfact import from-code [<bundle>] --repo <path> --no-interactive
|
|
|
86
88
|
- ❌ Write to `.specfact/` folder directly (always use CLI)
|
|
87
89
|
- ❌ Use direct file manipulation tools for writing (use CLI commands)
|
|
88
90
|
|
|
89
|
-
**Output**: Generate enrichment report (Markdown) saved to `.specfact/reports/enrichment/`
|
|
91
|
+
**Output**: Generate enrichment report (Markdown) saved to `.specfact/projects/<bundle-name>/reports/enrichment/` (bundle-specific, Phase 8.5)
|
|
90
92
|
|
|
91
93
|
### Phase 3: CLI Artifact Creation (REQUIRED)
|
|
92
94
|
|
|
@@ -107,8 +109,9 @@ specfact import from-code [<bundle>] --repo <path> --enrichment <enrichment-repo
|
|
|
107
109
|
## Common Patterns
|
|
108
110
|
|
|
109
111
|
```bash
|
|
110
|
-
/specfact.01-import --repo . # Uses active plan
|
|
111
|
-
/specfact.01-import --bundle legacy-api --repo .
|
|
112
|
+
/specfact.01-import --repo . # Uses active plan, auto-enrichment enabled by default
|
|
113
|
+
/specfact.01-import --bundle legacy-api --repo . # Auto-enrichment enabled
|
|
114
|
+
/specfact.01-import --repo . --no-enrich-for-speckit # Disable auto-enrichment
|
|
112
115
|
/specfact.01-import --repo . --entry-point src/auth/
|
|
113
116
|
/specfact.01-import --repo . --enrichment report.md
|
|
114
117
|
```
|
|
@@ -30,6 +30,7 @@ Review project bundle to identify/resolve ambiguities and missing information. A
|
|
|
30
30
|
### Output/Results
|
|
31
31
|
|
|
32
32
|
- `--list-questions` - Output questions in JSON format. Default: False
|
|
33
|
+
- `--output-questions PATH` - Save questions directly to file (JSON format). Use with `--list-questions` to save instead of stdout. Default: None
|
|
33
34
|
- `--list-findings` - Output all findings in structured format. Default: False
|
|
34
35
|
- `--findings-format FORMAT` - Output format: json, yaml, or table. Default: json for non-interactive, table for interactive
|
|
35
36
|
|
|
@@ -37,7 +38,7 @@ Review project bundle to identify/resolve ambiguities and missing information. A
|
|
|
37
38
|
|
|
38
39
|
- `--no-interactive` - Non-interactive mode (for CI/CD). Default: False (interactive mode)
|
|
39
40
|
- `--answers JSON` - JSON object with question_id -> answer mappings. Default: None
|
|
40
|
-
- `--auto-enrich` - Automatically enrich vague acceptance criteria. Default: False
|
|
41
|
+
- `--auto-enrich` - Automatically enrich vague acceptance criteria using PlanEnricher (same enrichment logic as `import from-code`). Default: False (opt-in for review, but import has auto-enrichment enabled by default)
|
|
41
42
|
|
|
42
43
|
### Advanced/Configuration
|
|
43
44
|
|
|
@@ -55,11 +56,15 @@ Review project bundle to identify/resolve ambiguities and missing information. A
|
|
|
55
56
|
**First, get findings to understand what needs enrichment:**
|
|
56
57
|
|
|
57
58
|
```bash
|
|
58
|
-
|
|
59
|
+
# Get findings (saves to stdout - can redirect to file)
|
|
60
|
+
specfact plan review [<bundle-name>] --list-findings --findings-format json --no-interactive > findings.json
|
|
61
|
+
|
|
62
|
+
# Or get questions and save directly to file (recommended)
|
|
63
|
+
specfact plan review [<bundle-name>] --list-questions --output-questions questions.json --no-interactive
|
|
59
64
|
# Uses active plan if bundle not specified
|
|
60
65
|
```
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
**Note**: The `--output-questions` option saves questions directly to a file, avoiding the need for complex JSON parsing. The ambiguity scanner now recognizes the simplified format (e.g., "Must verify X works correctly (see contract examples)") as valid and will not flag it as vague.
|
|
63
68
|
|
|
64
69
|
### Step 3: Create Enrichment Report (if needed)
|
|
65
70
|
|
|
@@ -115,7 +120,38 @@ FEATURE-OTHER → 0.8
|
|
|
115
120
|
|
|
116
121
|
#### Option A: Use enrichment to answer review questions
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
**Recommended workflow:**
|
|
124
|
+
|
|
125
|
+
1. **Get questions and save to file:**
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
specfact plan review [<bundle-name>] --list-questions --output-questions questions.json --no-interactive
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
2. **Edit the JSON file** to add answers:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"questions": [...],
|
|
136
|
+
"total": 5,
|
|
137
|
+
"answers": {
|
|
138
|
+
"Q001": "Answer for question 1",
|
|
139
|
+
"Q002": "Answer for question 2"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
3. **Extract answers and provide to CLI:**
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Extract answers from file (simple approach)
|
|
148
|
+
specfact plan review [<bundle-name>] --answers "$(jq -c '.answers' questions.json)" --no-interactive
|
|
149
|
+
|
|
150
|
+
# Or provide answers directly
|
|
151
|
+
specfact plan review [<bundle-name>] --answers '{"Q001": "answer1", "Q002": "answer2"}' --no-interactive
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Alternative**: Create answers JSON from enrichment report:
|
|
119
155
|
|
|
120
156
|
```bash
|
|
121
157
|
specfact plan review [<bundle-name>] --answers '{"Q001": "answer1", "Q002": "answer2"}'
|
|
@@ -139,7 +175,10 @@ specfact import from-code [<bundle-name>] --repo . --enrichment enrichment-repor
|
|
|
139
175
|
|
|
140
176
|
- **Preferred**: Use Option A (answers) or Option B (update-idea) for most cases
|
|
141
177
|
- Only use Option C if you need to regenerate the bundle
|
|
142
|
-
- Never manually edit `.specfact/` files directly - always use CLI commands
|
|
178
|
+
- **CRITICAL**: Never manually edit `.specfact/` files directly - always use CLI commands
|
|
179
|
+
- This includes `idea.yaml`, `product.yaml`, feature files, story files, etc.
|
|
180
|
+
- Even if a file doesn't exist yet, use CLI commands to create it (e.g., `plan update-idea` will create `idea.yaml` if needed)
|
|
181
|
+
- Direct file modification bypasses validation and can cause inconsistencies
|
|
143
182
|
|
|
144
183
|
### Step 5: Present Results
|
|
145
184
|
|
|
@@ -155,10 +194,16 @@ specfact import from-code [<bundle-name>] --repo . --enrichment enrichment-repor
|
|
|
155
194
|
|
|
156
195
|
- Execute CLI first - never create artifacts directly
|
|
157
196
|
- Use `--no-interactive` flag in CI/CD environments
|
|
158
|
-
-
|
|
197
|
+
- **NEVER modify `.specfact/` files directly** - always use CLI commands
|
|
198
|
+
- ❌ **DO NOT** edit `idea.yaml`, `product.yaml`, feature files, or any other artifacts directly
|
|
199
|
+
- ❌ **DO NOT** create new artifact files manually (even if they don't exist yet)
|
|
200
|
+
- ✅ **DO** use CLI commands: `plan update-idea`, `plan update-feature`, `plan update-story`, etc.
|
|
201
|
+
- ✅ **DO** use CLI commands to create new artifacts: `plan init`, `plan add-feature`, etc.
|
|
159
202
|
- Use CLI output as grounding for validation
|
|
160
203
|
- Code generation requires LLM (only via AI IDE slash prompts, not CLI-only)
|
|
161
204
|
|
|
205
|
+
**Important**: If an artifact file doesn't exist yet, use the appropriate CLI command to create it. Never create or modify `.specfact/` files manually, as this bypasses validation and can cause inconsistencies.
|
|
206
|
+
|
|
162
207
|
## Dual-Stack Workflow (Copilot Mode)
|
|
163
208
|
|
|
164
209
|
When in copilot mode, follow this three-phase workflow:
|
|
@@ -166,16 +211,22 @@ When in copilot mode, follow this three-phase workflow:
|
|
|
166
211
|
### Phase 1: CLI Grounding (REQUIRED)
|
|
167
212
|
|
|
168
213
|
```bash
|
|
169
|
-
#
|
|
170
|
-
specfact plan review [<bundle-name>] --list-findings --findings-format json --no-interactive
|
|
214
|
+
# Option 1: Get findings (redirect to file if needed)
|
|
215
|
+
specfact plan review [<bundle-name>] --list-findings --findings-format json --no-interactive > findings.json
|
|
216
|
+
|
|
217
|
+
# Option 2: Get questions and save directly to file (recommended - avoids JSON parsing)
|
|
218
|
+
specfact plan review [<bundle-name>] --list-questions --output-questions questions.json --no-interactive
|
|
171
219
|
```
|
|
172
220
|
|
|
173
221
|
**Capture**:
|
|
174
222
|
|
|
175
223
|
- CLI-generated findings (ambiguities, missing information)
|
|
224
|
+
- Questions saved directly to file (no complex parsing needed)
|
|
176
225
|
- Structured JSON/YAML output for bulk processing
|
|
177
226
|
- Metadata (timestamps, confidence scores)
|
|
178
227
|
|
|
228
|
+
**Note**: Use `--output-questions` to save questions directly to a file. This avoids the need for complex on-the-fly Python code to extract JSON from CLI output.
|
|
229
|
+
|
|
179
230
|
### Phase 2: LLM Enrichment (OPTIONAL, Copilot Only)
|
|
180
231
|
|
|
181
232
|
**Purpose**: Add semantic understanding to CLI findings
|
|
@@ -189,8 +240,10 @@ specfact plan review [<bundle-name>] --list-findings --findings-format json --no
|
|
|
189
240
|
|
|
190
241
|
**What NOT to do**:
|
|
191
242
|
|
|
192
|
-
- ❌ Create YAML/JSON artifacts directly
|
|
243
|
+
- ❌ Create YAML/JSON artifacts directly (even if they don't exist yet)
|
|
193
244
|
- ❌ Modify CLI artifacts directly (use CLI commands to update)
|
|
245
|
+
- ❌ Edit `idea.yaml`, `product.yaml`, feature files, or story files manually
|
|
246
|
+
- ❌ Create new artifact files manually - use CLI commands instead
|
|
194
247
|
- ❌ Bypass CLI validation
|
|
195
248
|
- ❌ Write to `.specfact/` folder directly (always use CLI)
|
|
196
249
|
|
|
@@ -252,7 +305,8 @@ Create one with: specfact plan init legacy-api
|
|
|
252
305
|
|
|
253
306
|
# Non-interactive with answers
|
|
254
307
|
/specfact.03-review --answers '{"Q001": "answer"}' # Provide answers directly
|
|
255
|
-
/specfact.03-review --list-questions # Output questions as JSON
|
|
308
|
+
/specfact.03-review --list-questions # Output questions as JSON to stdout
|
|
309
|
+
/specfact.03-review --list-questions --output-questions questions.json # Save questions to file
|
|
256
310
|
|
|
257
311
|
# Auto-enrichment
|
|
258
312
|
/specfact.03-review --auto-enrich # Auto-enrich vague criteria
|
|
@@ -260,16 +314,38 @@ Create one with: specfact plan init legacy-api
|
|
|
260
314
|
|
|
261
315
|
## Enrichment Workflow
|
|
262
316
|
|
|
317
|
+
**Note**: Import command (`specfact import from-code`) has **auto-enrichment enabled by default** using PlanEnricher. Review command requires explicit `--auto-enrich` flag.
|
|
318
|
+
|
|
263
319
|
**Typical workflow when enrichment is needed:**
|
|
264
320
|
|
|
265
|
-
1. **Get
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
321
|
+
1. **Get questions** (save to file for easy editing):
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
specfact plan review --list-questions --output-questions questions.json --no-interactive
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
2. **Get findings** (optional, for comprehensive analysis):
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
specfact plan review --list-findings --findings-format json --no-interactive > findings.json
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
3. **Analyze findings**: Review missing information (target_users, value_hypothesis, etc.)
|
|
334
|
+
|
|
335
|
+
4. **Apply automatic enrichment** (if needed):
|
|
336
|
+
|
|
337
|
+
- **During import**: Auto-enrichment happens automatically (enabled by default)
|
|
338
|
+
- **After import**: Use `specfact plan review --auto-enrich` to enhance vague criteria
|
|
339
|
+
- **Note**: The scanner now recognizes simplified format (e.g., "Must verify X works correctly (see contract examples)") as valid
|
|
340
|
+
|
|
341
|
+
5. **Create enrichment report** (for business context, confidence adjustments, missing features): Write Markdown file addressing findings
|
|
342
|
+
|
|
343
|
+
6. **Apply manual enrichment**:
|
|
344
|
+
- **Preferred**: Edit `questions.json` file to add answers, then use `--answers` with the file
|
|
270
345
|
- **Alternative**: Use `plan update-idea` to update idea fields directly
|
|
271
346
|
- **Last resort**: If bundle needs regeneration, use `import from-code --enrichment`
|
|
272
|
-
|
|
347
|
+
|
|
348
|
+
7. **Verify**: Run `plan review` again to confirm improvements
|
|
273
349
|
|
|
274
350
|
## Context
|
|
275
351
|
|
|
@@ -25,7 +25,7 @@ Create/update SDD manifest from project bundle. Captures WHY (intent/constraints
|
|
|
25
25
|
### Target/Input
|
|
26
26
|
|
|
27
27
|
- `bundle NAME` (optional argument) - Project bundle name (e.g., legacy-api, auth-module). Default: active plan (set via `plan select`)
|
|
28
|
-
- `--sdd PATH` - Output SDD manifest path. Default: .specfact/
|
|
28
|
+
- `--sdd PATH` - Output SDD manifest path. Default: bundle-specific .specfact/projects/<bundle-name>/sdd.<format> (Phase 8.5)
|
|
29
29
|
|
|
30
30
|
### Output/Results
|
|
31
31
|
|
|
@@ -119,12 +119,12 @@ specfact plan harden [<bundle-name>] --no-interactive
|
|
|
119
119
|
### Success
|
|
120
120
|
|
|
121
121
|
```text
|
|
122
|
-
✓ SDD manifest created: .specfact/
|
|
122
|
+
✓ SDD manifest created: .specfact/projects/legacy-api/sdd.yaml
|
|
123
123
|
|
|
124
124
|
SDD Manifest Summary:
|
|
125
125
|
Project Bundle: .specfact/projects/legacy-api/
|
|
126
126
|
Bundle Hash: abc123def456...
|
|
127
|
-
SDD Path: .specfact/
|
|
127
|
+
SDD Path: .specfact/projects/legacy-api/sdd.yaml
|
|
128
128
|
|
|
129
129
|
WHY (Intent):
|
|
130
130
|
Build secure authentication system
|
|
@@ -151,7 +151,7 @@ Create one with: specfact plan init legacy-api
|
|
|
151
151
|
/specfact.04-sdd # Uses active plan
|
|
152
152
|
/specfact.04-sdd legacy-api # Specific bundle
|
|
153
153
|
/specfact.04-sdd --output-format json # JSON format
|
|
154
|
-
/specfact.04-sdd --sdd .specfact/
|
|
154
|
+
/specfact.04-sdd --sdd .specfact/projects/custom-bundle/sdd.yaml
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
## Context
|
|
@@ -25,12 +25,12 @@ Validate SDD manifest against project bundle and contracts. Checks hash matching
|
|
|
25
25
|
### Target/Input
|
|
26
26
|
|
|
27
27
|
- `bundle NAME` (optional argument) - Project bundle name (e.g., legacy-api, auth-module). Default: active plan (set via `plan select`)
|
|
28
|
-
- `--sdd PATH` - Path to SDD manifest. Default: .specfact/sdd/<bundle-name>.<format>
|
|
28
|
+
- `--sdd PATH` - Path to SDD manifest. Default: bundle-specific .specfact/projects/<bundle-name>/sdd.<format> (Phase 8.5), with fallback to legacy .specfact/sdd/<bundle-name>.<format>
|
|
29
29
|
|
|
30
30
|
### Output/Results
|
|
31
31
|
|
|
32
32
|
- `--output-format FORMAT` - Output format (yaml, json, markdown). Default: yaml
|
|
33
|
-
- `--out PATH` - Output file path. Default: .specfact/reports/
|
|
33
|
+
- `--out PATH` - Output file path. Default: bundle-specific .specfact/projects/<bundle-name>/reports/enforcement/report-<timestamp>.<format> (Phase 8.5)
|
|
34
34
|
|
|
35
35
|
### Behavior/Options
|
|
36
36
|
|
|
@@ -131,7 +131,7 @@ Total deviations: 0
|
|
|
131
131
|
Medium: 0
|
|
132
132
|
Low: 0
|
|
133
133
|
|
|
134
|
-
Report saved to: .specfact/reports/
|
|
134
|
+
Report saved to: .specfact/projects/<bundle-name>/reports/enforcement/report-2025-11-26T10-30-00.yaml
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
### Failure (Hash Mismatch)
|
|
@@ -31,7 +31,7 @@ Compare two project bundles (or legacy plan bundles) to detect deviations, misma
|
|
|
31
31
|
### Output/Results
|
|
32
32
|
|
|
33
33
|
- `--output-format FORMAT` - Output format (markdown, json, yaml). Default: markdown
|
|
34
|
-
- `--out PATH` - Output file path. Default: .specfact/reports/comparison/
|
|
34
|
+
- `--out PATH` - Output file path. Default: bundle-specific .specfact/projects/<bundle-name>/reports/comparison/report-<timestamp>.md (Phase 8.5), or global .specfact/reports/comparison/ if no bundle context
|
|
35
35
|
|
|
36
36
|
### Behavior/Options
|
|
37
37
|
|
|
@@ -125,7 +125,7 @@ specfact plan compare [--bundle <name>] --no-interactive
|
|
|
125
125
|
```text
|
|
126
126
|
✓ Comparison complete
|
|
127
127
|
|
|
128
|
-
Comparison Report: .specfact/reports/comparison/
|
|
128
|
+
Comparison Report: .specfact/projects/<bundle-name>/reports/comparison/report-2025-11-26T10-30-00.md
|
|
129
129
|
|
|
130
130
|
Deviations Summary:
|
|
131
131
|
Total: 5
|
|
@@ -28,7 +28,7 @@ Run full validation suite for reproducibility and contract compliance. Executes
|
|
|
28
28
|
|
|
29
29
|
### Output/Results
|
|
30
30
|
|
|
31
|
-
- `--out PATH` - Output report path. Default: .specfact/reports/enforcement/report-<timestamp>.yaml
|
|
31
|
+
- `--out PATH` - Output report path. Default: bundle-specific .specfact/projects/<bundle-name>/reports/enforcement/report-<timestamp>.yaml (Phase 8.5), or global .specfact/reports/enforcement/ if no bundle context
|
|
32
32
|
|
|
33
33
|
### Behavior/Options
|
|
34
34
|
|
|
@@ -135,7 +135,7 @@ Check Summary:
|
|
|
135
135
|
Property Tests ✓ Passed
|
|
136
136
|
Smoke Tests ✓ Passed
|
|
137
137
|
|
|
138
|
-
Report saved to: .specfact/reports/enforcement/report-2025-11-26T10-30-00.yaml
|
|
138
|
+
Report saved to: .specfact/projects/<bundle-name>/reports/enforcement/report-2025-11-26T10-30-00.yaml
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
### Failure
|
|
@@ -521,7 +521,11 @@ class AmbiguityScanner:
|
|
|
521
521
|
else:
|
|
522
522
|
# Check for vague acceptance criteria patterns
|
|
523
523
|
# BUT: Skip if criteria are already code-specific (preserve code-specific criteria from code2spec)
|
|
524
|
-
|
|
524
|
+
# AND: Skip if criteria use the new simplified format (post-GWT refactoring)
|
|
525
|
+
from specfact_cli.utils.acceptance_criteria import (
|
|
526
|
+
is_code_specific_criteria,
|
|
527
|
+
is_simplified_format_criteria,
|
|
528
|
+
)
|
|
525
529
|
|
|
526
530
|
vague_patterns = [
|
|
527
531
|
"is implemented",
|
|
@@ -532,10 +536,16 @@ class AmbiguityScanner:
|
|
|
532
536
|
"is ready",
|
|
533
537
|
]
|
|
534
538
|
|
|
535
|
-
# Only check criteria that are NOT code-specific
|
|
539
|
+
# Only check criteria that are NOT code-specific AND NOT using simplified format
|
|
536
540
|
# Note: Acceptance criteria are simple text descriptions (not OpenAPI format)
|
|
537
541
|
# Detailed testable examples are stored in OpenAPI contract files (.openapi.yaml)
|
|
538
|
-
|
|
542
|
+
# The new simplified format (e.g., "Must verify X works correctly (see contract examples)")
|
|
543
|
+
# is VALID and should not be flagged as vague
|
|
544
|
+
non_code_specific_criteria = [
|
|
545
|
+
acc
|
|
546
|
+
for acc in story.acceptance
|
|
547
|
+
if not is_code_specific_criteria(acc) and not is_simplified_format_criteria(acc)
|
|
548
|
+
]
|
|
539
549
|
|
|
540
550
|
vague_criteria = [
|
|
541
551
|
acc
|
|
@@ -144,6 +144,8 @@ class CodeAnalyzer:
|
|
|
144
144
|
SpinnerColumn(),
|
|
145
145
|
TextColumn("[progress.description]{task.description}"),
|
|
146
146
|
BarColumn(),
|
|
147
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
148
|
+
TextColumn("({task.completed}/{task.total})"),
|
|
147
149
|
TimeElapsedColumn(),
|
|
148
150
|
console=console,
|
|
149
151
|
) as progress:
|
|
@@ -202,7 +204,13 @@ class CodeAnalyzer:
|
|
|
202
204
|
prev_features_count = len(self.features)
|
|
203
205
|
self._merge_analysis_results(results)
|
|
204
206
|
completed_count += 1
|
|
205
|
-
progress
|
|
207
|
+
# Update progress with feature count in description
|
|
208
|
+
features_count = len(self.features)
|
|
209
|
+
progress.update(
|
|
210
|
+
task3,
|
|
211
|
+
completed=completed_count,
|
|
212
|
+
description=f"[cyan]Phase 3: Analyzing files and extracting features... ({features_count} features discovered)",
|
|
213
|
+
)
|
|
206
214
|
|
|
207
215
|
# Phase 4.9: Report incremental results for quick first value
|
|
208
216
|
if self.incremental_callback and len(self.features) > prev_features_count:
|
|
@@ -211,7 +219,12 @@ class CodeAnalyzer:
|
|
|
211
219
|
except Exception as e:
|
|
212
220
|
console.print(f"[dim]⚠ Warning: Failed to analyze {file_path}: {e}[/dim]")
|
|
213
221
|
completed_count += 1
|
|
214
|
-
|
|
222
|
+
features_count = len(self.features)
|
|
223
|
+
progress.update(
|
|
224
|
+
task3,
|
|
225
|
+
completed=completed_count,
|
|
226
|
+
description=f"[cyan]Phase 3: Analyzing files and extracting features... ({features_count} features discovered)",
|
|
227
|
+
)
|
|
215
228
|
else:
|
|
216
229
|
executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
217
230
|
interrupted = False
|
|
@@ -230,7 +243,13 @@ class CodeAnalyzer:
|
|
|
230
243
|
prev_features_count = len(self.features)
|
|
231
244
|
self._merge_analysis_results(results)
|
|
232
245
|
completed_count += 1
|
|
233
|
-
progress
|
|
246
|
+
# Update progress with feature count in description
|
|
247
|
+
features_count = len(self.features)
|
|
248
|
+
progress.update(
|
|
249
|
+
task3,
|
|
250
|
+
completed=completed_count,
|
|
251
|
+
description=f"[cyan]Phase 3: Analyzing files and extracting features... ({features_count} features discovered)",
|
|
252
|
+
)
|
|
234
253
|
|
|
235
254
|
# Phase 4.9: Report incremental results for quick first value
|
|
236
255
|
if self.incremental_callback and len(self.features) > prev_features_count:
|
|
@@ -248,7 +267,12 @@ class CodeAnalyzer:
|
|
|
248
267
|
file_path = future_to_file[future]
|
|
249
268
|
console.print(f"[dim]⚠ Warning: Failed to analyze {file_path}: {e}[/dim]")
|
|
250
269
|
completed_count += 1
|
|
251
|
-
|
|
270
|
+
features_count = len(self.features)
|
|
271
|
+
progress.update(
|
|
272
|
+
task3,
|
|
273
|
+
completed=completed_count,
|
|
274
|
+
description=f"[cyan]Phase 3: Analyzing files and extracting features... ({features_count} features discovered)",
|
|
275
|
+
)
|
|
252
276
|
except KeyboardInterrupt:
|
|
253
277
|
# Also catch KeyboardInterrupt from as_completed() itself
|
|
254
278
|
interrupted = True
|
|
@@ -278,7 +302,12 @@ class CodeAnalyzer:
|
|
|
278
302
|
# Update progress for skipped files
|
|
279
303
|
skipped_count = len(python_files) - len(files_to_analyze)
|
|
280
304
|
if skipped_count > 0:
|
|
281
|
-
|
|
305
|
+
features_count = len(self.features)
|
|
306
|
+
progress.update(
|
|
307
|
+
task3,
|
|
308
|
+
completed=len(python_files),
|
|
309
|
+
description=f"[cyan]Phase 3: Analyzing files and extracting features... ({features_count} features discovered)",
|
|
310
|
+
)
|
|
282
311
|
|
|
283
312
|
progress.update(
|
|
284
313
|
task3,
|
{specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/control_flow_analyzer.py
RENAMED
|
@@ -53,7 +53,7 @@ class ControlFlowAnalyzer:
|
|
|
53
53
|
method_name: Name of the method
|
|
54
54
|
|
|
55
55
|
Returns:
|
|
56
|
-
Dictionary with scenario types as keys and lists of
|
|
56
|
+
Dictionary with scenario types as keys and lists of scenario descriptions as values
|
|
57
57
|
"""
|
|
58
58
|
scenarios: dict[str, list[str]] = {
|
|
59
59
|
"primary": [],
|
|
@@ -67,9 +67,7 @@ class ControlFlowAnalyzer:
|
|
|
67
67
|
|
|
68
68
|
# If no scenarios found, generate default primary scenario
|
|
69
69
|
if not any(scenarios.values()):
|
|
70
|
-
scenarios["primary"].append(
|
|
71
|
-
f"Given {class_name} instance, When {method_name} is called, Then method executes successfully"
|
|
72
|
-
)
|
|
70
|
+
scenarios["primary"].append(f"{method_name} executes successfully")
|
|
73
71
|
|
|
74
72
|
return scenarios
|
|
75
73
|
|
|
@@ -103,15 +101,13 @@ class ControlFlowAnalyzer:
|
|
|
103
101
|
# Primary scenario: if branch (happy path)
|
|
104
102
|
if if_node.body:
|
|
105
103
|
primary_action = self._extract_action_from_body(if_node.body)
|
|
106
|
-
scenarios["primary"].append(
|
|
107
|
-
f"Given {class_name} instance, When {method_name} is called with {condition}, Then {primary_action}"
|
|
108
|
-
)
|
|
104
|
+
scenarios["primary"].append(f"{method_name} called with {condition}: {primary_action}")
|
|
109
105
|
|
|
110
106
|
# Alternate scenario: else branch
|
|
111
107
|
if if_node.orelse:
|
|
112
108
|
alternate_action = self._extract_action_from_body(if_node.orelse)
|
|
113
109
|
scenarios["alternate"].append(
|
|
114
|
-
f"
|
|
110
|
+
f"{method_name} called with {self._negate_condition(condition)}: {alternate_action}"
|
|
115
111
|
)
|
|
116
112
|
|
|
117
113
|
@beartype
|
|
@@ -122,9 +118,7 @@ class ControlFlowAnalyzer:
|
|
|
122
118
|
# Primary scenario: try block (happy path)
|
|
123
119
|
if try_node.body:
|
|
124
120
|
primary_action = self._extract_action_from_body(try_node.body)
|
|
125
|
-
scenarios["primary"].append(
|
|
126
|
-
f"Given {class_name} instance, When {method_name} is called, Then {primary_action}"
|
|
127
|
-
)
|
|
121
|
+
scenarios["primary"].append(f"{method_name} executes: {primary_action}")
|
|
128
122
|
|
|
129
123
|
# Exception scenarios: except blocks
|
|
130
124
|
for handler in try_node.handlers:
|
|
@@ -133,22 +127,16 @@ class ControlFlowAnalyzer:
|
|
|
133
127
|
exception_type = self._extract_exception_type(handler.type)
|
|
134
128
|
|
|
135
129
|
exception_action = self._extract_action_from_body(handler.body) if handler.body else "error is handled"
|
|
136
|
-
scenarios["exception"].append(
|
|
137
|
-
f"Given {class_name} instance, When {method_name} is called and {exception_type} occurs, Then {exception_action}"
|
|
138
|
-
)
|
|
130
|
+
scenarios["exception"].append(f"{method_name} raises {exception_type}: {exception_action}")
|
|
139
131
|
|
|
140
132
|
# Check for retry/recovery logic in exception handler
|
|
141
133
|
if self._has_retry_logic(handler.body):
|
|
142
|
-
scenarios["recovery"].append(
|
|
143
|
-
f"Given {class_name} instance, When {method_name} fails with {exception_type}, Then system retries and recovers"
|
|
144
|
-
)
|
|
134
|
+
scenarios["recovery"].append(f"{method_name} retries and recovers after {exception_type}")
|
|
145
135
|
|
|
146
136
|
# Recovery scenario: finally block or retry logic
|
|
147
137
|
if try_node.finalbody:
|
|
148
138
|
recovery_action = self._extract_action_from_body(try_node.finalbody)
|
|
149
|
-
scenarios["recovery"].append(
|
|
150
|
-
f"Given {class_name} instance, When {method_name} completes or fails, Then {recovery_action}"
|
|
151
|
-
)
|
|
139
|
+
scenarios["recovery"].append(f"{method_name} cleanup: {recovery_action}")
|
|
152
140
|
|
|
153
141
|
@beartype
|
|
154
142
|
def _extract_loop_scenario(
|
|
@@ -157,9 +145,7 @@ class ControlFlowAnalyzer:
|
|
|
157
145
|
"""Extract scenario from loop (might indicate retry logic)."""
|
|
158
146
|
# Check if loop contains retry/retry logic
|
|
159
147
|
if self._has_retry_logic(loop_node.body):
|
|
160
|
-
scenarios["recovery"].append(
|
|
161
|
-
f"Given {class_name} instance, When {method_name} is called, Then system retries on failure until success"
|
|
162
|
-
)
|
|
148
|
+
scenarios["recovery"].append(f"{method_name} retries on failure until success")
|
|
163
149
|
|
|
164
150
|
@beartype
|
|
165
151
|
def _extract_condition(self, test_node: ast.AST) -> str:
|
|
@@ -129,7 +129,7 @@ def enforce_sdd(
|
|
|
129
129
|
sdd: Path | None = typer.Option(
|
|
130
130
|
None,
|
|
131
131
|
"--sdd",
|
|
132
|
-
help="Path to SDD manifest. Default: .specfact/sdd/<bundle-name>.<format>",
|
|
132
|
+
help="Path to SDD manifest. Default: bundle-specific .specfact/projects/<bundle-name>/sdd.<format> (Phase 8.5), with fallback to legacy .specfact/sdd/<bundle-name>.<format>",
|
|
133
133
|
),
|
|
134
134
|
# Output/Results
|
|
135
135
|
output_format: str = typer.Option(
|
|
@@ -140,7 +140,7 @@ def enforce_sdd(
|
|
|
140
140
|
out: Path | None = typer.Option(
|
|
141
141
|
None,
|
|
142
142
|
"--out",
|
|
143
|
-
help="Output file path. Default: .specfact/reports/
|
|
143
|
+
help="Output file path. Default: bundle-specific .specfact/projects/<bundle-name>/reports/enforcement/report-<timestamp>.<format> (Phase 8.5)",
|
|
144
144
|
),
|
|
145
145
|
# Behavior/Options
|
|
146
146
|
no_interactive: bool = typer.Option(
|
|
@@ -213,7 +213,8 @@ def enforce_sdd(
|
|
|
213
213
|
discovered_sdd = find_sdd_for_bundle(bundle, base_path, sdd)
|
|
214
214
|
if discovered_sdd is None:
|
|
215
215
|
console.print("[bold red]✗[/bold red] SDD manifest not found")
|
|
216
|
-
console.print(f"[dim]Searched for: .specfact/
|
|
216
|
+
console.print(f"[dim]Searched for: .specfact/projects/{bundle}/sdd.yaml (bundle-specific, Phase 8.5)[/dim]")
|
|
217
|
+
console.print(f"[dim]Fallback: .specfact/sdd/{bundle}.yaml or .specfact/sdd/{bundle}.json[/dim]")
|
|
217
218
|
console.print("[dim]Legacy fallback: .specfact/sdd.yaml or .specfact/sdd.json[/dim]")
|
|
218
219
|
console.print(f"[dim]Create one with: specfact plan harden {bundle}[/dim]")
|
|
219
220
|
raise typer.Exit(1)
|
|
@@ -382,15 +383,15 @@ def enforce_sdd(
|
|
|
382
383
|
else:
|
|
383
384
|
console.print("[dim]No API contracts found in bundle[/dim]")
|
|
384
385
|
|
|
385
|
-
# Generate output report
|
|
386
|
+
# Generate output report (Phase 8.5: bundle-specific location)
|
|
386
387
|
output_format_str = output_format.lower()
|
|
387
388
|
if out is None:
|
|
388
|
-
|
|
389
|
-
reports_dir = Path(".") / SpecFactStructure.ROOT / "reports" / "sdd"
|
|
390
|
-
reports_dir.mkdir(parents=True, exist_ok=True)
|
|
391
|
-
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
389
|
+
# Use bundle-specific enforcement report path
|
|
392
390
|
extension = "md" if output_format_str == "markdown" else output_format_str
|
|
393
|
-
out =
|
|
391
|
+
out = SpecFactStructure.get_bundle_enforcement_report_path(bundle_name=bundle, base_path=base_path)
|
|
392
|
+
# Update extension if needed
|
|
393
|
+
if extension != "yaml" and out.suffix != f".{extension}":
|
|
394
|
+
out = out.with_suffix(f".{extension}")
|
|
394
395
|
|
|
395
396
|
# Save report
|
|
396
397
|
if output_format_str == "markdown":
|