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.
Files changed (150) hide show
  1. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/PKG-INFO +1 -1
  2. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/pyproject.toml +1 -1
  3. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.01-import.md +8 -5
  4. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.03-review.md +92 -16
  5. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.04-sdd.md +4 -4
  6. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.05-enforce.md +3 -3
  7. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.compare.md +2 -2
  8. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.validate.md +2 -2
  9. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/__init__.py +1 -1
  10. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +13 -3
  11. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/code_analyzer.py +34 -5
  12. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +9 -23
  13. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/enforce.py +10 -9
  14. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/generate.py +15 -12
  15. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/implement.py +3 -3
  16. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/import_cmd.py +42 -63
  17. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/migrate.py +456 -0
  18. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/plan.py +56 -16
  19. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/repro.py +11 -5
  20. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/sdd.py +5 -2
  21. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/enrichers/plan_enricher.py +47 -18
  22. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/acceptance_criteria.py +40 -0
  23. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/bundle_loader.py +3 -2
  24. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/github_annotations.py +35 -10
  25. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/sdd_discovery.py +27 -15
  26. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/structure.py +334 -50
  27. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/.gitignore +0 -0
  28. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/LICENSE.md +0 -0
  29. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/README.md +0 -0
  30. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/node-async.yaml +0 -0
  31. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/python-async.yaml +0 -0
  32. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/mappings/speckit-default.yaml +0 -0
  33. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/shared/cli-enforcement.md +0 -0
  34. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.02-plan.md +0 -0
  35. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.06-sync.md +0 -0
  36. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/prompts/specfact.07-contracts.md +0 -0
  37. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/deviation.schema.json +0 -0
  38. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/plan.schema.json +0 -0
  39. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/schemas/protocol.schema.json +0 -0
  40. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/github-action.yml.j2 +0 -0
  41. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
  42. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/pr-template.md.j2 +0 -0
  43. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/protocol.yaml.j2 +0 -0
  44. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/resources/templates/telemetry.yaml.example +0 -0
  45. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/__init__.py +0 -0
  46. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/__init__.py +0 -0
  47. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
  48. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/base.py +0 -0
  49. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/plan_agent.py +0 -0
  50. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/registry.py +0 -0
  51. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/agents/sync_agent.py +0 -0
  52. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/__init__.py +0 -0
  53. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
  54. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
  55. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
  56. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
  57. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
  58. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
  59. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/cli.py +0 -0
  60. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/__init__.py +0 -0
  61. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/analyze.py +0 -0
  62. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/bridge.py +0 -0
  63. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/drift.py +0 -0
  64. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/init.py +0 -0
  65. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/run.py +0 -0
  66. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/spec.py +0 -0
  67. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/commands/sync.py +0 -0
  68. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/__init__.py +0 -0
  69. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/logger_setup.py +0 -0
  70. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/logging_utils.py +0 -0
  71. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/text_utils.py +0 -0
  72. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/common/utils.py +0 -0
  73. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/comparators/__init__.py +0 -0
  74. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
  75. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
  76. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/__init__.py +0 -0
  77. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/contract_generator.py +0 -0
  78. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/openapi_extractor.py +0 -0
  79. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/plan_generator.py +0 -0
  80. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
  81. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/report_generator.py +0 -0
  82. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/task_generator.py +0 -0
  83. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/test_to_openapi.py +0 -0
  84. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
  85. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/__init__.py +0 -0
  86. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
  87. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
  88. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/integrations/__init__.py +0 -0
  89. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/integrations/specmatic.py +0 -0
  90. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/migrations/__init__.py +0 -0
  91. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
  92. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/__init__.py +0 -0
  93. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/bridge.py +0 -0
  94. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/deviation.py +0 -0
  95. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/enforcement.py +0 -0
  96. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/plan.py +0 -0
  97. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/project.py +0 -0
  98. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/protocol.py +0 -0
  99. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/quality.py +0 -0
  100. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/sdd.py +0 -0
  101. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/source_tracking.py +0 -0
  102. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/models/task.py +0 -0
  103. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/__init__.py +0 -0
  104. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/detector.py +0 -0
  105. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/modes/router.py +0 -0
  106. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
  107. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
  108. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
  109. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/runtime.py +0 -0
  110. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/__init__.py +0 -0
  111. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
  112. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
  113. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
  114. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/change_detector.py +0 -0
  115. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
  116. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/drift_detector.py +0 -0
  117. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/repository_sync.py +0 -0
  118. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
  119. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
  120. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/speckit_sync.py +0 -0
  121. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/watcher.py +0 -0
  122. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
  123. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/telemetry.py +0 -0
  124. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/__init__.py +0 -0
  125. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
  126. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/templates/specification_templates.py +0 -0
  127. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/__init__.py +0 -0
  128. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/console.py +0 -0
  129. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/context_detection.py +0 -0
  130. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
  131. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
  132. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/feature_keys.py +0 -0
  133. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/git.py +0 -0
  134. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/ide_setup.py +0 -0
  135. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/incremental_check.py +0 -0
  136. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/optional_deps.py +0 -0
  137. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/performance.py +0 -0
  138. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/progress.py +0 -0
  139. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
  140. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/prompts.py +0 -0
  141. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/source_scanner.py +0 -0
  142. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/structured_io.py +0 -0
  143. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/suggestions.py +0 -0
  144. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
  145. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/__init__.py +0 -0
  146. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/cli_first_validator.py +0 -0
  147. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/contract_validator.py +0 -0
  148. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/fsm.py +0 -0
  149. {specfact_cli-0.14.2 → specfact_cli-0.15.1}/src/specfact_cli/validators/repro_checker.py +0 -0
  150. {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.14.2
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.14.2"
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
- specfact plan review [<bundle-name>] --list-findings --findings-format json
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
- This outputs all ambiguities and missing information in structured format.
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
- Create answers JSON from enrichment report and use with review:
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
- - Never modify `.specfact/` directly
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
- # Execute CLI to get structured output
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 findings**: `specfact plan review --list-findings --findings-format json`
266
- 2. **Analyze findings**: Review missing information (target_users, value_hypothesis, etc.)
267
- 3. **Create enrichment report**: Write Markdown file addressing findings
268
- 4. **Apply enrichment**:
269
- - **Preferred**: Use enrichment to create `--answers` JSON and run `plan review --answers`
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
- 5. **Verify**: Run `plan review` again to confirm improvements
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/sdd/<bundle-name>.<format>
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/sdd/legacy-api.yaml
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/sdd/legacy-api.yaml
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/sdd/custom.yaml
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/sdd/validation-<timestamp>.<format>
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/sdd/validation-2025-11-26T10-30-00.yaml
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/deviations-<timestamp>.md
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/deviations-2025-11-26T10-30-00.md
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
@@ -9,6 +9,6 @@ This package provides command-line tools for:
9
9
  - Validating reproducibility
10
10
  """
11
11
 
12
- __version__ = "0.14.1"
12
+ __version__ = "0.15.1"
13
13
 
14
14
  __all__ = ["__version__"]
@@ -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
- from specfact_cli.utils.acceptance_criteria import is_code_specific_criteria
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
- non_code_specific_criteria = [acc for acc in story.acceptance if not is_code_specific_criteria(acc)]
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.update(task3, completed=completed_count)
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
- progress.update(task3, completed=completed_count)
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.update(task3, completed=completed_count)
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
- progress.update(task3, completed=completed_count)
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
- progress.update(task3, completed=len(python_files))
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,
@@ -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 Given/When/Then scenarios as values
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"Given {class_name} instance, When {method_name} is called with {self._negate_condition(condition)}, Then {alternate_action}"
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/sdd/validation-<timestamp>.<format>",
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/sdd/{bundle}.yaml or .specfact/sdd/{bundle}.json[/dim]")
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
- SpecFactStructure.ensure_structure()
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 = reports_dir / f"validation-{timestamp}.{extension}"
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":