specfact-cli 0.26.9__tar.gz → 0.26.11__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.26.9 → specfact_cli-0.26.11}/.gitignore +4 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/PKG-INFO +1 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/pyproject.toml +1 -1
- specfact_cli-0.26.11/src/__init__.py +6 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/ado.py +14 -2
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/openspec.py +11 -6
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/openspec_parser.py +34 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/converter.py +18 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/backlog_commands.py +156 -4
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/backlog_item.py +5 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/bridge.py +1 -1
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_sync.py +14 -1
- specfact_cli-0.26.9/src/__init__.py +0 -6
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/LICENSE.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/README.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.backlog-refine.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/ai_refiner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/format_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/backlog/template_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/auth.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/versioning/analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specfact-cli
|
|
3
|
-
Version: 0.26.
|
|
3
|
+
Version: 0.26.11
|
|
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.26.
|
|
7
|
+
version = "0.26.11"
|
|
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"
|
|
@@ -2938,7 +2938,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2938
2938
|
from specfact_cli.backlog.converter import convert_ado_work_item_to_backlog_item
|
|
2939
2939
|
|
|
2940
2940
|
for work_item in work_items_data.get("value", []):
|
|
2941
|
-
backlog_item = convert_ado_work_item_to_backlog_item(
|
|
2941
|
+
backlog_item = convert_ado_work_item_to_backlog_item(
|
|
2942
|
+
work_item,
|
|
2943
|
+
provider="ado",
|
|
2944
|
+
base_url=self.base_url,
|
|
2945
|
+
org=self.org,
|
|
2946
|
+
project_name=self.project,
|
|
2947
|
+
)
|
|
2942
2948
|
items.append(backlog_item)
|
|
2943
2949
|
|
|
2944
2950
|
# Apply post-fetch filters that ADO API doesn't support directly
|
|
@@ -3258,4 +3264,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3258
3264
|
# Convert back to BacklogItem
|
|
3259
3265
|
from specfact_cli.backlog.converter import convert_ado_work_item_to_backlog_item
|
|
3260
3266
|
|
|
3261
|
-
return convert_ado_work_item_to_backlog_item(
|
|
3267
|
+
return convert_ado_work_item_to_backlog_item(
|
|
3268
|
+
updated_work_item,
|
|
3269
|
+
provider="ado",
|
|
3270
|
+
base_url=self.base_url,
|
|
3271
|
+
org=self.org,
|
|
3272
|
+
project_name=self.project,
|
|
3273
|
+
)
|
|
@@ -57,11 +57,12 @@ class OpenSpecAdapter(BridgeAdapter):
|
|
|
57
57
|
if bridge_config and bridge_config.external_base_path:
|
|
58
58
|
base_path = bridge_config.external_base_path
|
|
59
59
|
|
|
60
|
-
# Check for OpenSpec structure
|
|
60
|
+
# Check for OpenSpec structure (OPSX: config.yaml; legacy: project.md; or specs dir)
|
|
61
|
+
config_yaml = base_path / "openspec" / "config.yaml"
|
|
61
62
|
project_md = base_path / "openspec" / "project.md"
|
|
62
63
|
specs_dir = base_path / "openspec" / "specs"
|
|
63
64
|
|
|
64
|
-
return project_md.exists() or (specs_dir.exists() and specs_dir.is_dir())
|
|
65
|
+
return config_yaml.exists() or project_md.exists() or (specs_dir.exists() and specs_dir.is_dir())
|
|
65
66
|
|
|
66
67
|
@beartype
|
|
67
68
|
@require(lambda repo_path: repo_path.exists(), "Repository path must exist")
|
|
@@ -179,8 +180,9 @@ class OpenSpecAdapter(BridgeAdapter):
|
|
|
179
180
|
"""
|
|
180
181
|
config = BridgeConfig.preset_openspec()
|
|
181
182
|
|
|
182
|
-
# Check if OpenSpec is in external repo
|
|
183
|
-
|
|
183
|
+
# Check if OpenSpec is in external repo (OPSX: config.yaml; legacy: project.md)
|
|
184
|
+
openspec_dir = repo_path / "openspec"
|
|
185
|
+
if not (openspec_dir / "config.yaml").exists() and not (openspec_dir / "project.md").exists():
|
|
184
186
|
# Try to find external OpenSpec (this is a simple heuristic)
|
|
185
187
|
# In practice, external_base_path should be provided via CLI option
|
|
186
188
|
pass
|
|
@@ -422,10 +424,13 @@ class OpenSpecAdapter(BridgeAdapter):
|
|
|
422
424
|
bridge_config: BridgeConfig | None,
|
|
423
425
|
base_path: Path | None,
|
|
424
426
|
) -> None:
|
|
425
|
-
"""Import project context from OpenSpec project.md."""
|
|
427
|
+
"""Import project context from OpenSpec project.md (legacy) or config.yaml (OPSX)."""
|
|
426
428
|
from specfact_cli.models.plan import Idea
|
|
427
429
|
|
|
428
|
-
|
|
430
|
+
if project_md_path.name == "config.yaml" or project_md_path.suffix in (".yaml", ".yml"):
|
|
431
|
+
parsed = self.parser.parse_config_yaml(project_md_path)
|
|
432
|
+
else:
|
|
433
|
+
parsed = self.parser.parse_project_md(project_md_path)
|
|
429
434
|
|
|
430
435
|
# Create Idea if it doesn't exist
|
|
431
436
|
if not hasattr(project_bundle, "idea") or project_bundle.idea is None:
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
OpenSpec parser for adapter-specific OpenSpec format parsing.
|
|
3
3
|
|
|
4
4
|
This module provides parsing functionality for OpenSpec artifacts:
|
|
5
|
-
- project.md (project context)
|
|
5
|
+
- project.md (project context, legacy)
|
|
6
|
+
- config.yaml (OPSX project context)
|
|
6
7
|
- spec.md (feature specifications)
|
|
7
8
|
- proposal.md (change proposals)
|
|
8
9
|
- spec.md with ADDED/MODIFIED/REMOVED markers (delta specs)
|
|
@@ -13,6 +14,7 @@ from __future__ import annotations
|
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
17
|
+
import yaml
|
|
16
18
|
from beartype import beartype
|
|
17
19
|
from icontract import ensure, require
|
|
18
20
|
|
|
@@ -55,6 +57,37 @@ class OpenSpecParser:
|
|
|
55
57
|
# Return None on parse error (consistent with missing file)
|
|
56
58
|
return None
|
|
57
59
|
|
|
60
|
+
@beartype
|
|
61
|
+
@require(lambda path: isinstance(path, Path), "Path must be Path")
|
|
62
|
+
@ensure(lambda result: result is None or isinstance(result, dict), "Must return dict or None")
|
|
63
|
+
def parse_config_yaml(self, path: Path) -> dict[str, Any] | None:
|
|
64
|
+
"""
|
|
65
|
+
Parse OpenSpec OPSX config.yaml (project context).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path: Path to openspec/config.yaml file
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Dictionary compatible with project context import:
|
|
72
|
+
- "context": List of context string(s) from context: block
|
|
73
|
+
- "purpose": Optional (empty list if not in YAML)
|
|
74
|
+
- "raw_content": Full file content
|
|
75
|
+
"""
|
|
76
|
+
if not path.exists():
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
content = path.read_text(encoding="utf-8")
|
|
81
|
+
data = yaml.safe_load(content) or {}
|
|
82
|
+
result: dict[str, Any] = {"purpose": [], "context": [], "raw_content": content}
|
|
83
|
+
if isinstance(data.get("context"), str):
|
|
84
|
+
result["context"] = [data["context"].strip()]
|
|
85
|
+
elif isinstance(data.get("context"), list):
|
|
86
|
+
result["context"] = [str(c).strip() for c in data["context"] if c]
|
|
87
|
+
return result
|
|
88
|
+
except Exception:
|
|
89
|
+
return None
|
|
90
|
+
|
|
58
91
|
@beartype
|
|
59
92
|
@require(lambda path: isinstance(path, Path), "Path must be Path")
|
|
60
93
|
@ensure(lambda result: result is None or isinstance(result, dict), "Must return dict or None")
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from datetime import UTC, datetime
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
|
+
from urllib.parse import quote
|
|
13
14
|
|
|
14
15
|
from beartype import beartype
|
|
15
16
|
from icontract import ensure, require
|
|
@@ -157,7 +158,12 @@ def convert_github_issue_to_backlog_item(item_data: dict[str, Any], provider: st
|
|
|
157
158
|
@require(lambda provider: isinstance(provider, str) and len(provider) > 0, "Provider must be non-empty string")
|
|
158
159
|
@ensure(lambda result: isinstance(result, BacklogItem), "Must return BacklogItem")
|
|
159
160
|
def convert_ado_work_item_to_backlog_item(
|
|
160
|
-
item_data: dict[str, Any],
|
|
161
|
+
item_data: dict[str, Any],
|
|
162
|
+
provider: str = "ado",
|
|
163
|
+
custom_mapping_file: str | Path | None = None,
|
|
164
|
+
base_url: str | None = None,
|
|
165
|
+
org: str | None = None,
|
|
166
|
+
project_name: str | None = None,
|
|
161
167
|
) -> BacklogItem:
|
|
162
168
|
"""
|
|
163
169
|
Convert Azure DevOps work item data to BacklogItem.
|
|
@@ -167,6 +173,10 @@ def convert_ado_work_item_to_backlog_item(
|
|
|
167
173
|
Args:
|
|
168
174
|
item_data: ADO work item data from API (dict)
|
|
169
175
|
provider: Provider name (default: "ado")
|
|
176
|
+
custom_mapping_file: Optional path to custom ADO field mapping file.
|
|
177
|
+
base_url: ADO base URL (e.g. https://dev.azure.com) for canonical URL.
|
|
178
|
+
org: ADO organization name for canonical URL.
|
|
179
|
+
project_name: ADO project name (URL-encoded in canonical URL) for opening in browser.
|
|
170
180
|
|
|
171
181
|
Returns:
|
|
172
182
|
BacklogItem instance with normalized fields
|
|
@@ -294,10 +304,17 @@ def convert_ado_work_item_to_backlog_item(
|
|
|
294
304
|
"_links": item_data.get("_links", {}),
|
|
295
305
|
}
|
|
296
306
|
|
|
307
|
+
canonical_url = None
|
|
308
|
+
if base_url and org and project_name:
|
|
309
|
+
base = base_url.rstrip("/")
|
|
310
|
+
encoded_project = quote(project_name, safe="")
|
|
311
|
+
canonical_url = f"{base}/{org}/{encoded_project}/_workitems/edit/{work_item_id}"
|
|
312
|
+
|
|
297
313
|
return BacklogItem(
|
|
298
314
|
id=work_item_id,
|
|
299
315
|
provider=provider,
|
|
300
316
|
url=url,
|
|
317
|
+
canonical_url=canonical_url,
|
|
301
318
|
title=title,
|
|
302
319
|
body_markdown=body_markdown,
|
|
303
320
|
state=state,
|
|
@@ -14,6 +14,7 @@ SpecFact CLI Architecture:
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import os
|
|
17
|
+
import re
|
|
17
18
|
import sys
|
|
18
19
|
from datetime import datetime
|
|
19
20
|
from pathlib import Path
|
|
@@ -202,6 +203,97 @@ def _build_adapter_kwargs(
|
|
|
202
203
|
return kwargs
|
|
203
204
|
|
|
204
205
|
|
|
206
|
+
def _extract_body_from_block(block: str) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Extract **Body** content from a refined export block, handling nested fenced code.
|
|
209
|
+
|
|
210
|
+
The body is wrapped in ```markdown ... ```. If the body itself contains fenced
|
|
211
|
+
code blocks (e.g. ```python ... ```), the closing fence is matched by tracking
|
|
212
|
+
depth: a line that is exactly ``` closes the current fence (body or inner).
|
|
213
|
+
"""
|
|
214
|
+
start_marker = "**Body**:"
|
|
215
|
+
fence_open = "```markdown"
|
|
216
|
+
if start_marker not in block or fence_open not in block:
|
|
217
|
+
return ""
|
|
218
|
+
idx = block.find(start_marker)
|
|
219
|
+
rest = block[idx + len(start_marker) :].lstrip()
|
|
220
|
+
if not rest.startswith("```"):
|
|
221
|
+
return ""
|
|
222
|
+
if not rest.startswith(fence_open + "\n") and not rest.startswith(fence_open + "\r\n"):
|
|
223
|
+
return ""
|
|
224
|
+
after_open = rest[len(fence_open) :].lstrip("\n\r")
|
|
225
|
+
if not after_open:
|
|
226
|
+
return ""
|
|
227
|
+
lines = after_open.split("\n")
|
|
228
|
+
body_lines: list[str] = []
|
|
229
|
+
depth = 1
|
|
230
|
+
for line in lines:
|
|
231
|
+
stripped = line.rstrip()
|
|
232
|
+
if stripped == "```":
|
|
233
|
+
if depth == 1:
|
|
234
|
+
break
|
|
235
|
+
depth -= 1
|
|
236
|
+
body_lines.append(line)
|
|
237
|
+
elif stripped.startswith("```") and stripped != "```":
|
|
238
|
+
depth += 1
|
|
239
|
+
body_lines.append(line)
|
|
240
|
+
else:
|
|
241
|
+
body_lines.append(line)
|
|
242
|
+
return "\n".join(body_lines).strip()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _parse_refined_export_markdown(content: str) -> dict[str, dict[str, Any]]:
|
|
246
|
+
"""
|
|
247
|
+
Parse refined export markdown (same format as --export-to-tmp) into id -> fields.
|
|
248
|
+
|
|
249
|
+
Splits by ## Item blocks, extracts **ID**, **Body** (from ```markdown ... ```),
|
|
250
|
+
**Acceptance Criteria**, and optionally title and **Metrics** (story_points,
|
|
251
|
+
business_value, priority). Body extraction is fence-aware so bodies containing
|
|
252
|
+
nested code blocks are parsed correctly. Returns a dict mapping item id to
|
|
253
|
+
parsed fields (body_markdown, acceptance_criteria, title?, story_points?,
|
|
254
|
+
business_value?, priority?).
|
|
255
|
+
"""
|
|
256
|
+
result: dict[str, dict[str, Any]] = {}
|
|
257
|
+
blocks = re.split(r"\n## Item \d+:", content)
|
|
258
|
+
for block in blocks:
|
|
259
|
+
block = block.strip()
|
|
260
|
+
if not block or block.startswith("# SpecFact") or "**ID**:" not in block:
|
|
261
|
+
continue
|
|
262
|
+
id_match = re.search(r"\*\*ID\*\*:\s*(.+?)(?:\n|$)", block)
|
|
263
|
+
if not id_match:
|
|
264
|
+
continue
|
|
265
|
+
item_id = id_match.group(1).strip()
|
|
266
|
+
fields: dict[str, Any] = {}
|
|
267
|
+
|
|
268
|
+
fields["body_markdown"] = _extract_body_from_block(block)
|
|
269
|
+
|
|
270
|
+
ac_match = re.search(r"\*\*Acceptance Criteria\*\*:\s*\n(.*?)(?=\n\*\*|\n---|\Z)", block, re.DOTALL)
|
|
271
|
+
if ac_match:
|
|
272
|
+
fields["acceptance_criteria"] = ac_match.group(1).strip() or None
|
|
273
|
+
else:
|
|
274
|
+
fields["acceptance_criteria"] = None
|
|
275
|
+
|
|
276
|
+
first_line = block.split("\n")[0].strip() if block else ""
|
|
277
|
+
if first_line and not first_line.startswith("**"):
|
|
278
|
+
fields["title"] = first_line
|
|
279
|
+
|
|
280
|
+
if "Story Points:" in block:
|
|
281
|
+
sp_match = re.search(r"Story Points:\s*(\d+)", block)
|
|
282
|
+
if sp_match:
|
|
283
|
+
fields["story_points"] = int(sp_match.group(1))
|
|
284
|
+
if "Business Value:" in block:
|
|
285
|
+
bv_match = re.search(r"Business Value:\s*(\d+)", block)
|
|
286
|
+
if bv_match:
|
|
287
|
+
fields["business_value"] = int(bv_match.group(1))
|
|
288
|
+
if "Priority:" in block:
|
|
289
|
+
pri_match = re.search(r"Priority:\s*(\d+)", block)
|
|
290
|
+
if pri_match:
|
|
291
|
+
fields["priority"] = int(pri_match.group(1))
|
|
292
|
+
|
|
293
|
+
result[item_id] = fields
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
|
|
205
297
|
def _fetch_backlog_items(
|
|
206
298
|
adapter_name: str,
|
|
207
299
|
search_query: str | None = None,
|
|
@@ -635,6 +727,8 @@ def refine(
|
|
|
635
727
|
export_content += f"## Item {idx}: {item.title}\n\n"
|
|
636
728
|
export_content += f"**ID**: {item.id}\n"
|
|
637
729
|
export_content += f"**URL**: {item.url}\n"
|
|
730
|
+
if item.canonical_url:
|
|
731
|
+
export_content += f"**Canonical URL**: {item.canonical_url}\n"
|
|
638
732
|
export_content += f"**State**: {item.state}\n"
|
|
639
733
|
export_content += f"**Provider**: {item.provider}\n"
|
|
640
734
|
|
|
@@ -678,9 +772,65 @@ def refine(
|
|
|
678
772
|
raise typer.Exit(1)
|
|
679
773
|
|
|
680
774
|
console.print(f"[bold cyan]Importing refined content from: {import_file}[/bold cyan]")
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
775
|
+
raw = import_file.read_text(encoding="utf-8")
|
|
776
|
+
parsed_by_id = _parse_refined_export_markdown(raw)
|
|
777
|
+
if not parsed_by_id:
|
|
778
|
+
console.print(
|
|
779
|
+
"[yellow]No valid item blocks found in import file (expected ## Item N: and **ID**:)[/yellow]"
|
|
780
|
+
)
|
|
781
|
+
raise typer.Exit(1)
|
|
782
|
+
|
|
783
|
+
updated_items: list[BacklogItem] = []
|
|
784
|
+
for item in items:
|
|
785
|
+
if item.id not in parsed_by_id:
|
|
786
|
+
continue
|
|
787
|
+
data = parsed_by_id[item.id]
|
|
788
|
+
item.body_markdown = data.get("body_markdown", item.body_markdown or "")
|
|
789
|
+
if "acceptance_criteria" in data:
|
|
790
|
+
item.acceptance_criteria = data["acceptance_criteria"]
|
|
791
|
+
if data.get("title"):
|
|
792
|
+
item.title = data["title"]
|
|
793
|
+
if "story_points" in data:
|
|
794
|
+
item.story_points = data["story_points"]
|
|
795
|
+
if "business_value" in data:
|
|
796
|
+
item.business_value = data["business_value"]
|
|
797
|
+
if "priority" in data:
|
|
798
|
+
item.priority = data["priority"]
|
|
799
|
+
updated_items.append(item)
|
|
800
|
+
|
|
801
|
+
if not write:
|
|
802
|
+
console.print(f"[green]Would update {len(updated_items)} item(s)[/green]")
|
|
803
|
+
console.print("[dim]Run with --write to apply changes to the backlog[/dim]")
|
|
804
|
+
return
|
|
805
|
+
|
|
806
|
+
writeback_kwargs = _build_adapter_kwargs(
|
|
807
|
+
adapter,
|
|
808
|
+
repo_owner=repo_owner,
|
|
809
|
+
repo_name=repo_name,
|
|
810
|
+
github_token=github_token,
|
|
811
|
+
ado_org=ado_org,
|
|
812
|
+
ado_project=ado_project,
|
|
813
|
+
ado_team=ado_team,
|
|
814
|
+
ado_token=ado_token,
|
|
815
|
+
)
|
|
816
|
+
adapter_instance = adapter_registry.get_adapter(adapter, **writeback_kwargs)
|
|
817
|
+
if not isinstance(adapter_instance, BacklogAdapter):
|
|
818
|
+
console.print("[bold red]✗[/bold red] Adapter does not support backlog updates")
|
|
819
|
+
raise typer.Exit(1)
|
|
820
|
+
|
|
821
|
+
for item in updated_items:
|
|
822
|
+
update_fields_list = ["title", "body_markdown"]
|
|
823
|
+
if item.acceptance_criteria:
|
|
824
|
+
update_fields_list.append("acceptance_criteria")
|
|
825
|
+
if item.story_points is not None:
|
|
826
|
+
update_fields_list.append("story_points")
|
|
827
|
+
if item.business_value is not None:
|
|
828
|
+
update_fields_list.append("business_value")
|
|
829
|
+
if item.priority is not None:
|
|
830
|
+
update_fields_list.append("priority")
|
|
831
|
+
adapter_instance.update_backlog_item(item, update_fields=update_fields_list)
|
|
832
|
+
console.print(f"[green]✓ Updated backlog item: {item.url}[/green]")
|
|
833
|
+
console.print(f"[green]✓ Updated {len(updated_items)} backlog item(s)[/green]")
|
|
684
834
|
return
|
|
685
835
|
|
|
686
836
|
# Apply limit if specified
|
|
@@ -806,6 +956,8 @@ def refine(
|
|
|
806
956
|
console.print("\n[bold]Preview Mode: Full Item Details[/bold]")
|
|
807
957
|
console.print(f"[bold]Title:[/bold] {item.title}")
|
|
808
958
|
console.print(f"[bold]URL:[/bold] {item.url}")
|
|
959
|
+
if item.canonical_url:
|
|
960
|
+
console.print(f"[bold]Canonical URL:[/bold] {item.canonical_url}")
|
|
809
961
|
console.print(f"[bold]State:[/bold] {item.state}")
|
|
810
962
|
console.print(f"[bold]Provider:[/bold] {item.provider}")
|
|
811
963
|
console.print(f"[bold]Assignee:[/bold] {', '.join(item.assignees) if item.assignees else 'Unassigned'}")
|
|
@@ -1227,7 +1379,7 @@ def map_fields(
|
|
|
1227
1379
|
import re
|
|
1228
1380
|
import sys
|
|
1229
1381
|
|
|
1230
|
-
import questionary
|
|
1382
|
+
import questionary # type: ignore[reportMissingImports]
|
|
1231
1383
|
import requests
|
|
1232
1384
|
|
|
1233
1385
|
from specfact_cli.backlog.mappers.template_config import FieldMappingConfig
|
|
@@ -32,7 +32,11 @@ class BacklogItem(BaseModel):
|
|
|
32
32
|
# Identity fields
|
|
33
33
|
id: str = Field(..., description="Backlog item identifier (provider-specific)")
|
|
34
34
|
provider: str = Field(..., description="Provider name (github, ado, jira, linear, etc.)")
|
|
35
|
-
url: str = Field(..., description="Backlog item URL")
|
|
35
|
+
url: str = Field(..., description="Backlog item URL (API or provider URL)")
|
|
36
|
+
canonical_url: str | None = Field(
|
|
37
|
+
default=None,
|
|
38
|
+
description="User-friendly URL for opening in browser (e.g. ADO: org/project-name/_workitems/edit/id)",
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
# Content fields
|
|
38
42
|
title: str = Field(..., description="Backlog item title")
|
|
@@ -499,7 +499,7 @@ class BridgeConfig(BaseModel):
|
|
|
499
499
|
Returns:
|
|
500
500
|
BridgeConfig for OpenSpec integration with artifact mappings for:
|
|
501
501
|
- specification: openspec/specs/{feature_id}/spec.md
|
|
502
|
-
- project_context: openspec/project.md
|
|
502
|
+
- project_context: openspec/config.yaml (OPSX) if present, else openspec/project.md (legacy)
|
|
503
503
|
- change_proposal: openspec/changes/{change_name}/proposal.md
|
|
504
504
|
- change_tasks: openspec/changes/{change_name}/tasks.md
|
|
505
505
|
- change_spec_delta: openspec/changes/{change_name}/specs/{feature_id}/spec.md
|
|
@@ -31,7 +31,7 @@ from rich.progress import Progress
|
|
|
31
31
|
from rich.table import Table
|
|
32
32
|
|
|
33
33
|
from specfact_cli.adapters.registry import AdapterRegistry
|
|
34
|
-
from specfact_cli.models.bridge import BridgeConfig
|
|
34
|
+
from specfact_cli.models.bridge import AdapterType, BridgeConfig
|
|
35
35
|
from specfact_cli.runtime import get_configured_console
|
|
36
36
|
from specfact_cli.sync.bridge_probe import BridgeProbe
|
|
37
37
|
from specfact_cli.utils.bundle_loader import load_project_bundle, save_project_bundle
|
|
@@ -188,6 +188,19 @@ class BridgeSync:
|
|
|
188
188
|
msg = "Bridge config not initialized"
|
|
189
189
|
raise ValueError(msg)
|
|
190
190
|
|
|
191
|
+
base_path = self.repo_path
|
|
192
|
+
if self.bridge_config.external_base_path is not None:
|
|
193
|
+
base_path = self.bridge_config.external_base_path
|
|
194
|
+
|
|
195
|
+
if artifact_key == "project_context" and self.bridge_config.adapter == AdapterType.OPENSPEC:
|
|
196
|
+
config_yaml = base_path / "openspec" / "config.yaml"
|
|
197
|
+
project_md = base_path / "openspec" / "project.md"
|
|
198
|
+
if config_yaml.exists():
|
|
199
|
+
return config_yaml
|
|
200
|
+
if project_md.exists():
|
|
201
|
+
return project_md
|
|
202
|
+
return project_md
|
|
203
|
+
|
|
191
204
|
context = {
|
|
192
205
|
"feature_id": feature_id,
|
|
193
206
|
"bundle_name": bundle_name,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/defect_v1.yaml
RENAMED
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/enabler_v1.yaml
RENAMED
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/spike_v1.yaml
RENAMED
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/backlog/defaults/user_story_v1.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/resources/templates/persona/product-owner.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specfact_cli-0.26.9 → specfact_cli-0.26.11}/src/specfact_cli/analyzers/ambiguity_scanner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|