specfact-cli 0.11.4__tar.gz → 0.12.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.11.4 → specfact_cli-0.12.1}/PKG-INFO +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/pyproject.toml +1 -1
- specfact_cli-0.12.1/resources/prompts/specfact.03-review.md +220 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/__init__.py +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/code_analyzer.py +74 -52
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/graph_analyzer.py +22 -7
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/relationship_mapper.py +8 -2
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/import_cmd.py +169 -111
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/plan.py +8 -2
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/openapi_extractor.py +203 -24
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/test_to_openapi.py +8 -1
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/project.py +16 -4
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/incremental_check.py +16 -4
- specfact_cli-0.12.1/src/specfact_cli/utils/progress.py +182 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/source_scanner.py +8 -2
- 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.12.1}/.gitignore +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/LICENSE.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/README.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/bridge.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/implement.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/run.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/speckit_sync.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.11.4 → specfact_cli-0.12.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.12.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.12.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"
|
|
@@ -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}
|
|
@@ -175,9 +175,13 @@ class CodeAnalyzer:
|
|
|
175
175
|
files_to_analyze = [f for f in python_files if not self._should_skip_file(f)]
|
|
176
176
|
|
|
177
177
|
# Process files in parallel
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
# In test mode, use fewer workers to avoid resource contention
|
|
179
|
+
if os.environ.get("TEST_MODE") == "true":
|
|
180
|
+
max_workers = max(1, min(2, len(files_to_analyze))) # Max 2 workers in test mode
|
|
181
|
+
else:
|
|
182
|
+
max_workers = max(
|
|
183
|
+
1, min(os.cpu_count() or 4, 8, len(files_to_analyze))
|
|
184
|
+
) # Cap at 8 workers, ensure at least 1
|
|
181
185
|
completed_count = 0
|
|
182
186
|
|
|
183
187
|
def analyze_file_safe(file_path: Path) -> dict[str, Any]:
|
|
@@ -185,58 +189,76 @@ class CodeAnalyzer:
|
|
|
185
189
|
return self._analyze_file_parallel(file_path)
|
|
186
190
|
|
|
187
191
|
if files_to_analyze:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
# In test mode, use sequential processing to avoid ThreadPoolExecutor deadlocks
|
|
193
|
+
is_test_mode = os.environ.get("TEST_MODE") == "true"
|
|
194
|
+
if is_test_mode:
|
|
195
|
+
# Sequential processing in test mode - avoids ThreadPoolExecutor deadlocks entirely
|
|
196
|
+
for file_path in files_to_analyze:
|
|
197
|
+
try:
|
|
198
|
+
results = analyze_file_safe(file_path)
|
|
199
|
+
self._merge_analysis_results(results)
|
|
200
|
+
completed_count += 1
|
|
201
|
+
progress.update(task3, completed=completed_count)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
console.print(f"[dim]⚠ Warning: Failed to analyze {file_path}: {e}[/dim]")
|
|
204
|
+
completed_count += 1
|
|
205
|
+
progress.update(task3, completed=completed_count)
|
|
206
|
+
else:
|
|
207
|
+
executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
208
|
+
interrupted = False
|
|
209
|
+
# In test mode, use wait=False to avoid hanging on shutdown
|
|
210
|
+
wait_on_shutdown = not is_test_mode
|
|
195
211
|
try:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
# Submit all tasks
|
|
213
|
+
future_to_file = {executor.submit(analyze_file_safe, f): f for f in files_to_analyze}
|
|
214
|
+
|
|
215
|
+
# Collect results as they complete
|
|
216
|
+
try:
|
|
217
|
+
for future in as_completed(future_to_file):
|
|
218
|
+
try:
|
|
219
|
+
results = future.result()
|
|
220
|
+
# Merge results into instance variables (sequential merge is fast)
|
|
221
|
+
self._merge_analysis_results(results)
|
|
222
|
+
completed_count += 1
|
|
223
|
+
progress.update(task3, completed=completed_count)
|
|
224
|
+
except KeyboardInterrupt:
|
|
225
|
+
# Cancel remaining tasks and break out of loop immediately
|
|
226
|
+
interrupted = True
|
|
227
|
+
for f in future_to_file:
|
|
228
|
+
if not f.done():
|
|
229
|
+
f.cancel()
|
|
230
|
+
break
|
|
231
|
+
except Exception as e:
|
|
232
|
+
# Log error but continue processing
|
|
233
|
+
file_path = future_to_file[future]
|
|
234
|
+
console.print(f"[dim]⚠ Warning: Failed to analyze {file_path}: {e}[/dim]")
|
|
235
|
+
completed_count += 1
|
|
236
|
+
progress.update(task3, completed=completed_count)
|
|
237
|
+
except KeyboardInterrupt:
|
|
238
|
+
# Also catch KeyboardInterrupt from as_completed() itself
|
|
239
|
+
interrupted = True
|
|
240
|
+
for f in future_to_file:
|
|
241
|
+
if not f.done():
|
|
242
|
+
f.cancel()
|
|
243
|
+
|
|
244
|
+
# If interrupted, re-raise KeyboardInterrupt after breaking out of loop
|
|
245
|
+
if interrupted:
|
|
246
|
+
raise KeyboardInterrupt
|
|
216
247
|
except KeyboardInterrupt:
|
|
217
|
-
#
|
|
248
|
+
# Gracefully shutdown executor on interrupt (cancel pending tasks, don't wait)
|
|
218
249
|
interrupted = True
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
finally:
|
|
232
|
-
# Ensure executor is properly shutdown
|
|
233
|
-
# If interrupted, don't wait for tasks (they're already cancelled)
|
|
234
|
-
# shutdown() is safe to call multiple times
|
|
235
|
-
if not interrupted:
|
|
236
|
-
executor.shutdown(wait=True)
|
|
237
|
-
else:
|
|
238
|
-
# Already shutdown with wait=False, just ensure cleanup
|
|
239
|
-
executor.shutdown(wait=False)
|
|
250
|
+
executor.shutdown(wait=False, cancel_futures=True)
|
|
251
|
+
raise
|
|
252
|
+
finally:
|
|
253
|
+
# Ensure executor is properly shutdown
|
|
254
|
+
# If interrupted, don't wait for tasks (they're already cancelled)
|
|
255
|
+
# shutdown() is safe to call multiple times
|
|
256
|
+
# In test mode, use wait=False to avoid hanging
|
|
257
|
+
if not interrupted:
|
|
258
|
+
executor.shutdown(wait=wait_on_shutdown)
|
|
259
|
+
else:
|
|
260
|
+
# Already shutdown with wait=False, just ensure cleanup
|
|
261
|
+
executor.shutdown(wait=False)
|
|
240
262
|
|
|
241
263
|
# Update progress for skipped files
|
|
242
264
|
skipped_count = len(python_files) - len(files_to_analyze)
|
|
@@ -149,11 +149,17 @@ class GraphAnalyzer:
|
|
|
149
149
|
|
|
150
150
|
# Add edges from AST imports (parallelized for performance)
|
|
151
151
|
import multiprocessing
|
|
152
|
+
|
|
153
|
+
# In test mode, use fewer workers to avoid resource contention
|
|
154
|
+
import os
|
|
152
155
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
1, min(
|
|
156
|
-
|
|
157
|
+
if os.environ.get("TEST_MODE") == "true":
|
|
158
|
+
max_workers = max(1, min(2, len(python_files))) # Max 2 workers in test mode
|
|
159
|
+
else:
|
|
160
|
+
max_workers = max(
|
|
161
|
+
1, min(multiprocessing.cpu_count() or 4, 16, len(python_files))
|
|
162
|
+
) # Increased for faster processing, ensure at least 1
|
|
157
163
|
|
|
158
164
|
# Get list of known modules for matching (needed for parallel processing)
|
|
159
165
|
known_modules = list(graph.nodes())
|
|
@@ -176,8 +182,12 @@ class GraphAnalyzer:
|
|
|
176
182
|
return edges
|
|
177
183
|
|
|
178
184
|
# Process AST imports in parallel
|
|
179
|
-
|
|
180
|
-
|
|
185
|
+
import os
|
|
186
|
+
|
|
187
|
+
executor1 = ThreadPoolExecutor(max_workers=max_workers)
|
|
188
|
+
wait_on_shutdown = os.environ.get("TEST_MODE") != "true"
|
|
189
|
+
try:
|
|
190
|
+
future_to_file = {executor1.submit(process_imports, file_path): file_path for file_path in python_files}
|
|
181
191
|
|
|
182
192
|
for future in as_completed(future_to_file):
|
|
183
193
|
try:
|
|
@@ -186,11 +196,14 @@ class GraphAnalyzer:
|
|
|
186
196
|
graph.add_edge(module_name, matching_module)
|
|
187
197
|
except Exception:
|
|
188
198
|
continue
|
|
199
|
+
finally:
|
|
200
|
+
executor1.shutdown(wait=wait_on_shutdown)
|
|
189
201
|
|
|
190
202
|
# Extract call graphs using pyan (if available) - parallelized for performance
|
|
191
|
-
|
|
203
|
+
executor2 = ThreadPoolExecutor(max_workers=max_workers)
|
|
204
|
+
try:
|
|
192
205
|
future_to_file = {
|
|
193
|
-
|
|
206
|
+
executor2.submit(self.extract_call_graph, file_path): file_path for file_path in python_files
|
|
194
207
|
}
|
|
195
208
|
|
|
196
209
|
for future in as_completed(future_to_file):
|
|
@@ -206,6 +219,8 @@ class GraphAnalyzer:
|
|
|
206
219
|
except Exception:
|
|
207
220
|
# Skip if call graph extraction fails for this file
|
|
208
221
|
continue
|
|
222
|
+
finally:
|
|
223
|
+
executor2.shutdown(wait=wait_on_shutdown)
|
|
209
224
|
|
|
210
225
|
self.dependency_graph = graph
|
|
211
226
|
return graph
|
{specfact_cli-0.11.4 → specfact_cli-0.12.1}/src/specfact_cli/analyzers/relationship_mapper.py
RENAMED
|
@@ -380,10 +380,16 @@ class RelationshipMapper:
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
# Use ThreadPoolExecutor for parallel processing
|
|
383
|
-
|
|
383
|
+
# In test mode, use fewer workers to avoid resource contention
|
|
384
|
+
if os.environ.get("TEST_MODE") == "true":
|
|
385
|
+
max_workers = max(1, min(2, len(python_files))) # Max 2 workers in test mode
|
|
386
|
+
else:
|
|
387
|
+
max_workers = min(os.cpu_count() or 4, 16, len(python_files)) # Cap at 16 workers for faster processing
|
|
384
388
|
|
|
385
389
|
executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
386
390
|
interrupted = False
|
|
391
|
+
# In test mode, use wait=False to avoid hanging on shutdown
|
|
392
|
+
wait_on_shutdown = os.environ.get("TEST_MODE") != "true"
|
|
387
393
|
try:
|
|
388
394
|
# Submit all tasks
|
|
389
395
|
future_to_file = {executor.submit(self._analyze_file_parallel, f): f for f in python_files}
|
|
@@ -424,7 +430,7 @@ class RelationshipMapper:
|
|
|
424
430
|
raise
|
|
425
431
|
finally:
|
|
426
432
|
if not interrupted:
|
|
427
|
-
executor.shutdown(wait=
|
|
433
|
+
executor.shutdown(wait=wait_on_shutdown)
|
|
428
434
|
else:
|
|
429
435
|
executor.shutdown(wait=False)
|
|
430
436
|
|