specfact-cli 0.11.4__tar.gz → 0.11.5__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.11.4 → specfact_cli-0.11.5}/PKG-INFO +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/pyproject.toml +1 -1
- specfact_cli-0.11.5/resources/prompts/specfact.03-review.md +220 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/__init__.py +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/plan.py +8 -2
- specfact_cli-0.11.5/src/specfact_cli/utils/progress.py +182 -0
- specfact_cli-0.11.4/resources/prompts/specfact.03-review.md +0 -112
- specfact_cli-0.11.4/src/specfact_cli/utils/progress.py +0 -126
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/.gitignore +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/LICENSE.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/README.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/bridge.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/implement.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/run.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.11.5}/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.11.
|
|
3
|
+
Version: 0.11.5
|
|
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.11.
|
|
7
|
+
version = "0.11.5"
|
|
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"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review project bundle to identify ambiguities, resolve gaps, and prepare for promotion.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# SpecFact Review Command
|
|
6
|
+
|
|
7
|
+
## User Input
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
$ARGUMENTS
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You **MUST** consider the user input before proceeding (if not empty).
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
Review project bundle to identify/resolve ambiguities and missing information. Asks targeted questions for promotion readiness.
|
|
18
|
+
|
|
19
|
+
**When to use:** After import/creation, before promotion, when clarification needed.
|
|
20
|
+
|
|
21
|
+
**Quick:** `/specfact.03-review` (uses active plan) or `/specfact.03-review legacy-api`
|
|
22
|
+
|
|
23
|
+
## Parameters
|
|
24
|
+
|
|
25
|
+
### Target/Input
|
|
26
|
+
|
|
27
|
+
- `bundle NAME` (optional argument) - Project bundle name (e.g., legacy-api, auth-module). Default: active plan (set via `plan select`)
|
|
28
|
+
- `--category CATEGORY` - Focus on specific taxonomy category. Default: None (all categories)
|
|
29
|
+
|
|
30
|
+
### Output/Results
|
|
31
|
+
|
|
32
|
+
- `--list-questions` - Output questions in JSON format. Default: False
|
|
33
|
+
- `--list-findings` - Output all findings in structured format. Default: False
|
|
34
|
+
- `--findings-format FORMAT` - Output format: json, yaml, or table. Default: json for non-interactive, table for interactive
|
|
35
|
+
|
|
36
|
+
### Behavior/Options
|
|
37
|
+
|
|
38
|
+
- `--no-interactive` - Non-interactive mode (for CI/CD). Default: False (interactive mode)
|
|
39
|
+
- `--answers JSON` - JSON object with question_id -> answer mappings. Default: None
|
|
40
|
+
- `--auto-enrich` - Automatically enrich vague acceptance criteria. Default: False
|
|
41
|
+
|
|
42
|
+
### Advanced/Configuration
|
|
43
|
+
|
|
44
|
+
- `--max-questions INT` - Maximum questions per session. Default: 5 (range: 1-10)
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
### Step 1: Parse Arguments
|
|
49
|
+
|
|
50
|
+
- Extract bundle name (defaults to active plan if not specified)
|
|
51
|
+
- Extract optional parameters (max-questions, category, etc.)
|
|
52
|
+
|
|
53
|
+
### Step 2: Execute CLI to Get Findings
|
|
54
|
+
|
|
55
|
+
**First, get findings to understand what needs enrichment:**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
specfact plan review [<bundle-name>] --list-findings --findings-format json
|
|
59
|
+
# Uses active plan if bundle not specified
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This outputs all ambiguities and missing information in structured format.
|
|
63
|
+
|
|
64
|
+
### Step 3: Create Enrichment Report (if needed)
|
|
65
|
+
|
|
66
|
+
Based on the findings, create a Markdown enrichment report that addresses:
|
|
67
|
+
|
|
68
|
+
- **Business Context**: Priorities, constraints, unknowns
|
|
69
|
+
- **Confidence Adjustments**: Feature confidence score updates (if needed)
|
|
70
|
+
- **Missing Features**: New features to add (if any)
|
|
71
|
+
- **Manual Updates**: Guidance for updating `idea.yaml` fields like `target_users`, `value_hypothesis`, `narrative`
|
|
72
|
+
|
|
73
|
+
**Enrichment Report Format:**
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
## Business Context
|
|
77
|
+
|
|
78
|
+
### Priorities
|
|
79
|
+
- Priority 1
|
|
80
|
+
- Priority 2
|
|
81
|
+
|
|
82
|
+
### Constraints
|
|
83
|
+
- Constraint 1
|
|
84
|
+
- Constraint 2
|
|
85
|
+
|
|
86
|
+
### Unknowns
|
|
87
|
+
- Unknown 1
|
|
88
|
+
- Unknown 2
|
|
89
|
+
|
|
90
|
+
## Confidence Adjustments
|
|
91
|
+
|
|
92
|
+
FEATURE-KEY → 0.95
|
|
93
|
+
FEATURE-OTHER → 0.8
|
|
94
|
+
|
|
95
|
+
## Missing Features
|
|
96
|
+
|
|
97
|
+
(If any features are missing)
|
|
98
|
+
|
|
99
|
+
## Recommendations for Manual Updates
|
|
100
|
+
|
|
101
|
+
### idea.yaml Updates Required
|
|
102
|
+
|
|
103
|
+
**target_users:**
|
|
104
|
+
- Primary: [description]
|
|
105
|
+
- Secondary: [description]
|
|
106
|
+
|
|
107
|
+
**value_hypothesis:**
|
|
108
|
+
[Value proposition]
|
|
109
|
+
|
|
110
|
+
**narrative:**
|
|
111
|
+
[Improved narrative]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Step 4: Apply Enrichment
|
|
115
|
+
|
|
116
|
+
#### Option A: Use enrichment to answer review questions
|
|
117
|
+
|
|
118
|
+
Create answers JSON from enrichment report and use with review:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
specfact plan review [<bundle-name>] --answers '{"Q001": "answer1", "Q002": "answer2"}'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Option B: Update idea fields directly via CLI
|
|
125
|
+
|
|
126
|
+
Use `plan update-idea` to update idea fields from enrichment recommendations:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
specfact plan update-idea --bundle [<bundle-name>] --value-hypothesis "..." --narrative "..." --target-users "..."
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Option C: Apply enrichment via import (only if bundle needs regeneration)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
specfact import from-code [<bundle-name>] --repo . --enrichment enrichment-report.md
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Note:**
|
|
139
|
+
|
|
140
|
+
- **Preferred**: Use Option A (answers) or Option B (update-idea) for most cases
|
|
141
|
+
- Only use Option C if you need to regenerate the bundle
|
|
142
|
+
- Never manually edit `.specfact/` files directly - always use CLI commands
|
|
143
|
+
|
|
144
|
+
### Step 5: Present Results
|
|
145
|
+
|
|
146
|
+
- Display Q&A, sections touched, coverage summary (initial/updated)
|
|
147
|
+
- Note: Clarifications don't affect hash (stable across review sessions)
|
|
148
|
+
- If enrichment report was created, summarize what was addressed
|
|
149
|
+
|
|
150
|
+
## CLI Enforcement
|
|
151
|
+
|
|
152
|
+
**CRITICAL**: Always use SpecFact CLI commands. See [CLI Enforcement Rules](./shared/cli-enforcement.md) for details.
|
|
153
|
+
|
|
154
|
+
**Rules:** Execute CLI first, use `--no-interactive` in CI/CD, never modify `.specfact/` directly, use CLI output as grounding.
|
|
155
|
+
|
|
156
|
+
## Expected Output
|
|
157
|
+
|
|
158
|
+
### Success
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
✓ Review complete: 5 question(s) answered
|
|
162
|
+
|
|
163
|
+
Project Bundle: legacy-api
|
|
164
|
+
Questions Asked: 5
|
|
165
|
+
|
|
166
|
+
Sections Touched:
|
|
167
|
+
• idea.narrative
|
|
168
|
+
• features[FEATURE-001].acceptance
|
|
169
|
+
• features[FEATURE-002].outcomes
|
|
170
|
+
|
|
171
|
+
Coverage Summary:
|
|
172
|
+
✅ Functional Scope: clear
|
|
173
|
+
✅ Technical Constraints: clear
|
|
174
|
+
⚠️ Business Context: partial
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Error (Missing Bundle)
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
✗ Project bundle 'legacy-api' not found
|
|
181
|
+
Create one with: specfact plan init legacy-api
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Common Patterns
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Get findings first
|
|
188
|
+
/specfact.03-review --list-findings # List all findings
|
|
189
|
+
/specfact.03-review --list-findings --findings-format json # JSON format for enrichment
|
|
190
|
+
|
|
191
|
+
# Interactive review
|
|
192
|
+
/specfact.03-review # Uses active plan
|
|
193
|
+
/specfact.03-review legacy-api # Specific bundle
|
|
194
|
+
/specfact.03-review --max-questions 3 # Limit questions
|
|
195
|
+
/specfact.03-review --category "Functional Scope" # Focus category
|
|
196
|
+
|
|
197
|
+
# Non-interactive with answers
|
|
198
|
+
/specfact.03-review --answers '{"Q001": "answer"}' # Provide answers directly
|
|
199
|
+
/specfact.03-review --list-questions # Output questions as JSON
|
|
200
|
+
|
|
201
|
+
# Auto-enrichment
|
|
202
|
+
/specfact.03-review --auto-enrich # Auto-enrich vague criteria
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Enrichment Workflow
|
|
206
|
+
|
|
207
|
+
**Typical workflow when enrichment is needed:**
|
|
208
|
+
|
|
209
|
+
1. **Get findings**: `specfact plan review --list-findings --findings-format json`
|
|
210
|
+
2. **Analyze findings**: Review missing information (target_users, value_hypothesis, etc.)
|
|
211
|
+
3. **Create enrichment report**: Write Markdown file addressing findings
|
|
212
|
+
4. **Apply enrichment**:
|
|
213
|
+
- **Preferred**: Use enrichment to create `--answers` JSON and run `plan review --answers`
|
|
214
|
+
- **Alternative**: Use `plan update-idea` to update idea fields directly
|
|
215
|
+
- **Last resort**: If bundle needs regeneration, use `import from-code --enrichment`
|
|
216
|
+
5. **Verify**: Run `plan review` again to confirm improvements
|
|
217
|
+
|
|
218
|
+
## Context
|
|
219
|
+
|
|
220
|
+
{ARGS}
|
|
@@ -3607,10 +3607,12 @@ def _handle_no_questions_case(
|
|
|
3607
3607
|
)
|
|
3608
3608
|
console.print(f" {status_icon} {cat.value}: {status.value}")
|
|
3609
3609
|
|
|
3610
|
+
return
|
|
3611
|
+
|
|
3610
3612
|
|
|
3611
3613
|
@beartype
|
|
3612
3614
|
@require(lambda questions_to_ask: isinstance(questions_to_ask, list), "Questions must be list")
|
|
3613
|
-
@ensure(lambda result: None, "Must return None")
|
|
3615
|
+
@ensure(lambda result: result is None, "Must return None")
|
|
3614
3616
|
def _handle_list_questions_mode(questions_to_ask: list[tuple[Any, str]]) -> None:
|
|
3615
3617
|
"""
|
|
3616
3618
|
Handle --list-questions mode by outputting questions as JSON.
|
|
@@ -3638,6 +3640,8 @@ def _handle_list_questions_mode(questions_to_ask: list[tuple[Any, str]]) -> None
|
|
|
3638
3640
|
sys.stdout.write("\n")
|
|
3639
3641
|
sys.stdout.flush()
|
|
3640
3642
|
|
|
3643
|
+
return
|
|
3644
|
+
|
|
3641
3645
|
|
|
3642
3646
|
@beartype
|
|
3643
3647
|
@require(lambda answers: isinstance(answers, str), "Answers must be string")
|
|
@@ -3824,7 +3828,7 @@ def _ask_questions_interactive(
|
|
|
3824
3828
|
@require(lambda report: report is not None, "Report must not be None")
|
|
3825
3829
|
@require(lambda current_stage: isinstance(current_stage, str), "Current stage must be str")
|
|
3826
3830
|
@require(lambda today_session: today_session is not None, "Today session must not be None")
|
|
3827
|
-
@ensure(lambda result: None, "Must return None")
|
|
3831
|
+
@ensure(lambda result: result is None, "Must return None")
|
|
3828
3832
|
def _display_review_summary(
|
|
3829
3833
|
plan_bundle: PlanBundle,
|
|
3830
3834
|
scanner: Any, # AmbiguityScanner
|
|
@@ -3898,6 +3902,8 @@ def _display_review_summary(
|
|
|
3898
3902
|
console.print(" • Plan is ready for approval")
|
|
3899
3903
|
console.print(" • Run: specfact plan promote --stage approved")
|
|
3900
3904
|
|
|
3905
|
+
return
|
|
3906
|
+
|
|
3901
3907
|
|
|
3902
3908
|
@app.command("review")
|
|
3903
3909
|
@beartype
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Progress display utilities for consistent UI/UX across all commands.
|
|
3
|
+
|
|
4
|
+
This module provides unified progress display functions that ensure
|
|
5
|
+
consistent formatting and user experience across all CLI commands.
|
|
6
|
+
Includes timing information for visibility into operation duration.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from time import time
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
|
19
|
+
|
|
20
|
+
from specfact_cli.models.project import ProjectBundle
|
|
21
|
+
from specfact_cli.utils.bundle_loader import load_project_bundle, save_project_bundle
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _is_test_mode() -> bool:
|
|
28
|
+
"""Check if running in test mode."""
|
|
29
|
+
return os.environ.get("TEST_MODE") == "true" or os.environ.get("PYTEST_CURRENT_TEST") is not None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _safe_progress_display(display_console: Console) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Check if it's safe to create a Progress display.
|
|
35
|
+
|
|
36
|
+
Returns True if Progress can be created, False if it should be skipped.
|
|
37
|
+
"""
|
|
38
|
+
# Always skip in test mode
|
|
39
|
+
if _is_test_mode():
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Try to detect if a Progress is already active by checking console state
|
|
43
|
+
# This is a best-effort check - we'll catch LiveError if it fails
|
|
44
|
+
try:
|
|
45
|
+
# Rich stores active Live displays in Console._live
|
|
46
|
+
if hasattr(display_console, "_live") and display_console._live is not None:
|
|
47
|
+
return False
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_progress_callback(progress: Progress, task_id: Any, prefix: str = "") -> Callable[[int, int, str], None]:
|
|
55
|
+
"""
|
|
56
|
+
Create a standardized progress callback function.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
progress: Rich Progress instance
|
|
60
|
+
task_id: Task ID from progress.add_task()
|
|
61
|
+
prefix: Optional prefix for progress messages (e.g., "Loading", "Saving")
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Callback function that updates progress with n/m counter format
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def callback(current: int, total: int, artifact: str) -> None:
|
|
68
|
+
"""Update progress with n/m counter format."""
|
|
69
|
+
if prefix:
|
|
70
|
+
description = f"{prefix} artifact {current}/{total}: {artifact}"
|
|
71
|
+
else:
|
|
72
|
+
description = f"Processing artifact {current}/{total}: {artifact}"
|
|
73
|
+
progress.update(task_id, description=description)
|
|
74
|
+
|
|
75
|
+
return callback
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_bundle_with_progress(
|
|
79
|
+
bundle_dir: Path,
|
|
80
|
+
validate_hashes: bool = False,
|
|
81
|
+
console_instance: Console | None = None,
|
|
82
|
+
) -> ProjectBundle:
|
|
83
|
+
"""
|
|
84
|
+
Load project bundle with unified progress display.
|
|
85
|
+
|
|
86
|
+
Uses consistent n/m counter format: "Loading artifact 3/12: FEATURE-001.yaml"
|
|
87
|
+
Includes timing information showing elapsed time.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
bundle_dir: Path to bundle directory
|
|
91
|
+
validate_hashes: Whether to validate file checksums
|
|
92
|
+
console_instance: Optional Console instance (defaults to module console)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Loaded ProjectBundle instance
|
|
96
|
+
"""
|
|
97
|
+
display_console = console_instance or console
|
|
98
|
+
start_time = time()
|
|
99
|
+
|
|
100
|
+
# Try to use Progress display, but fall back to direct load if it fails
|
|
101
|
+
# (e.g., if another Progress is already active)
|
|
102
|
+
use_progress = _safe_progress_display(display_console)
|
|
103
|
+
|
|
104
|
+
if use_progress:
|
|
105
|
+
try:
|
|
106
|
+
with Progress(
|
|
107
|
+
SpinnerColumn(),
|
|
108
|
+
TextColumn("[progress.description]{task.description}"),
|
|
109
|
+
TimeElapsedColumn(),
|
|
110
|
+
console=display_console,
|
|
111
|
+
) as progress:
|
|
112
|
+
task = progress.add_task("Loading project bundle...", total=None)
|
|
113
|
+
|
|
114
|
+
progress_callback = create_progress_callback(progress, task, prefix="Loading")
|
|
115
|
+
|
|
116
|
+
bundle = load_project_bundle(
|
|
117
|
+
bundle_dir,
|
|
118
|
+
validate_hashes=validate_hashes,
|
|
119
|
+
progress_callback=progress_callback,
|
|
120
|
+
)
|
|
121
|
+
elapsed = time() - start_time
|
|
122
|
+
progress.update(task, description=f"✓ Bundle loaded ({elapsed:.2f}s)")
|
|
123
|
+
return bundle
|
|
124
|
+
except Exception:
|
|
125
|
+
# If Progress creation fails (e.g., LiveError), fall back to direct load
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# No progress display - just load directly
|
|
129
|
+
return load_project_bundle(
|
|
130
|
+
bundle_dir,
|
|
131
|
+
validate_hashes=validate_hashes,
|
|
132
|
+
progress_callback=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def save_bundle_with_progress(
|
|
137
|
+
bundle: ProjectBundle,
|
|
138
|
+
bundle_dir: Path,
|
|
139
|
+
atomic: bool = True,
|
|
140
|
+
console_instance: Console | None = None,
|
|
141
|
+
) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Save project bundle with unified progress display.
|
|
144
|
+
|
|
145
|
+
Uses consistent n/m counter format: "Saving artifact 3/12: FEATURE-001.yaml"
|
|
146
|
+
Includes timing information showing elapsed time.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
bundle: ProjectBundle instance to save
|
|
150
|
+
bundle_dir: Path to bundle directory
|
|
151
|
+
atomic: Whether to use atomic writes
|
|
152
|
+
console_instance: Optional Console instance (defaults to module console)
|
|
153
|
+
"""
|
|
154
|
+
display_console = console_instance or console
|
|
155
|
+
start_time = time()
|
|
156
|
+
|
|
157
|
+
# Try to use Progress display, but fall back to direct save if it fails
|
|
158
|
+
# (e.g., if another Progress is already active)
|
|
159
|
+
use_progress = _safe_progress_display(display_console)
|
|
160
|
+
|
|
161
|
+
if use_progress:
|
|
162
|
+
try:
|
|
163
|
+
with Progress(
|
|
164
|
+
SpinnerColumn(),
|
|
165
|
+
TextColumn("[progress.description]{task.description}"),
|
|
166
|
+
TimeElapsedColumn(),
|
|
167
|
+
console=display_console,
|
|
168
|
+
) as progress:
|
|
169
|
+
task = progress.add_task("Saving project bundle...", total=None)
|
|
170
|
+
|
|
171
|
+
progress_callback = create_progress_callback(progress, task, prefix="Saving")
|
|
172
|
+
|
|
173
|
+
save_project_bundle(bundle, bundle_dir, atomic=atomic, progress_callback=progress_callback)
|
|
174
|
+
elapsed = time() - start_time
|
|
175
|
+
progress.update(task, description=f"✓ Bundle saved ({elapsed:.2f}s)")
|
|
176
|
+
return
|
|
177
|
+
except Exception:
|
|
178
|
+
# If Progress creation fails (e.g., LiveError), fall back to direct save
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
# No progress display - just save directly
|
|
182
|
+
save_project_bundle(bundle, bundle_dir, atomic=atomic, progress_callback=None)
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Review project bundle to identify ambiguities, resolve gaps, and prepare for promotion.
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# SpecFact Review Command
|
|
6
|
-
|
|
7
|
-
## User Input
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
$ARGUMENTS
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
You **MUST** consider the user input before proceeding (if not empty).
|
|
14
|
-
|
|
15
|
-
## Purpose
|
|
16
|
-
|
|
17
|
-
Review project bundle to identify/resolve ambiguities and missing information. Asks targeted questions for promotion readiness.
|
|
18
|
-
|
|
19
|
-
**When to use:** After import/creation, before promotion, when clarification needed.
|
|
20
|
-
|
|
21
|
-
**Quick:** `/specfact.03-review` (uses active plan) or `/specfact.03-review legacy-api`
|
|
22
|
-
|
|
23
|
-
## Parameters
|
|
24
|
-
|
|
25
|
-
### Target/Input
|
|
26
|
-
|
|
27
|
-
- `bundle NAME` (optional argument) - Project bundle name (e.g., legacy-api, auth-module). Default: active plan (set via `plan select`)
|
|
28
|
-
- `--category CATEGORY` - Focus on specific taxonomy category. Default: None (all categories)
|
|
29
|
-
|
|
30
|
-
### Output/Results
|
|
31
|
-
|
|
32
|
-
- `--list-questions` - Output questions in JSON format. Default: False
|
|
33
|
-
- `--list-findings` - Output all findings in structured format. Default: False
|
|
34
|
-
- `--findings-format FORMAT` - Output format: json, yaml, or table. Default: json for non-interactive, table for interactive
|
|
35
|
-
|
|
36
|
-
### Behavior/Options
|
|
37
|
-
|
|
38
|
-
- `--no-interactive` - Non-interactive mode (for CI/CD). Default: False (interactive mode)
|
|
39
|
-
- `--answers JSON` - JSON object with question_id -> answer mappings. Default: None
|
|
40
|
-
- `--auto-enrich` - Automatically enrich vague acceptance criteria. Default: False
|
|
41
|
-
|
|
42
|
-
### Advanced/Configuration
|
|
43
|
-
|
|
44
|
-
- `--max-questions INT` - Maximum questions per session. Default: 5 (range: 1-10)
|
|
45
|
-
|
|
46
|
-
## Workflow
|
|
47
|
-
|
|
48
|
-
### Step 1: Parse Arguments
|
|
49
|
-
|
|
50
|
-
- Extract bundle name (defaults to active plan if not specified)
|
|
51
|
-
- Extract optional parameters (max-questions, category, etc.)
|
|
52
|
-
|
|
53
|
-
### Step 2: Execute CLI
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
specfact plan review [<bundle-name>] [--max-questions <n>] [--category <category>] [--list-questions] [--list-findings] [--answers JSON]
|
|
57
|
-
# Uses active plan if bundle not specified
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Step 3: Present Results
|
|
61
|
-
|
|
62
|
-
- Display Q&A, sections touched, coverage summary (initial/updated)
|
|
63
|
-
- Note: Clarifications don't affect hash (stable across review sessions)
|
|
64
|
-
|
|
65
|
-
## CLI Enforcement
|
|
66
|
-
|
|
67
|
-
**CRITICAL**: Always use SpecFact CLI commands. See [CLI Enforcement Rules](./shared/cli-enforcement.md) for details.
|
|
68
|
-
|
|
69
|
-
**Rules:** Execute CLI first, use `--no-interactive` in CI/CD, never modify `.specfact/` directly, use CLI output as grounding.
|
|
70
|
-
|
|
71
|
-
## Expected Output
|
|
72
|
-
|
|
73
|
-
### Success
|
|
74
|
-
|
|
75
|
-
```text
|
|
76
|
-
✓ Review complete: 5 question(s) answered
|
|
77
|
-
|
|
78
|
-
Project Bundle: legacy-api
|
|
79
|
-
Questions Asked: 5
|
|
80
|
-
|
|
81
|
-
Sections Touched:
|
|
82
|
-
• idea.narrative
|
|
83
|
-
• features[FEATURE-001].acceptance
|
|
84
|
-
• features[FEATURE-002].outcomes
|
|
85
|
-
|
|
86
|
-
Coverage Summary:
|
|
87
|
-
✅ Functional Scope: clear
|
|
88
|
-
✅ Technical Constraints: clear
|
|
89
|
-
⚠️ Business Context: partial
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Error (Missing Bundle)
|
|
93
|
-
|
|
94
|
-
```text
|
|
95
|
-
✗ Project bundle 'legacy-api' not found
|
|
96
|
-
Create one with: specfact plan init legacy-api
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Common Patterns
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
/specfact.03-review # Uses active plan
|
|
103
|
-
/specfact.03-review legacy-api # Specific bundle
|
|
104
|
-
/specfact.03-review --max-questions 3 # Limit questions
|
|
105
|
-
/specfact.03-review --category "Functional Scope" # Focus category
|
|
106
|
-
/specfact.03-review --list-questions # JSON output
|
|
107
|
-
/specfact.03-review --auto-enrich # Auto-enrichment
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Context
|
|
111
|
-
|
|
112
|
-
{ARGS}
|