specfact-cli 0.26.13__tar.gz → 0.26.15__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.13 → specfact_cli-0.26.15}/PKG-INFO +2 -2
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/README.md +1 -1
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/pyproject.toml +1 -1
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.backlog-refine.md +26 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/__init__.py +1 -1
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/ado.py +89 -23
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/cli.py +1 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/backlog_commands.py +88 -7
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/sync.py +3 -3
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_sync.py +11 -6
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/.gitignore +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/LICENSE.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/ai_refiner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/format_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/backlog/template_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/auth.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.26.13 → specfact_cli-0.26.15}/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.15
|
|
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
|
|
@@ -617,7 +617,7 @@ hatch run contract-test-full
|
|
|
617
617
|
|
|
618
618
|
- 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
|
|
619
619
|
- 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
|
|
620
|
-
- 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. See [Debug Logging](docs/reference/debug-logging.md).
|
|
620
|
+
- 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. With `--debug`, ADO API errors include response snippet and patch paths in the log. See [Debug Logging](docs/reference/debug-logging.md).
|
|
621
621
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
622
622
|
- 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
|
|
623
623
|
|
|
@@ -339,7 +339,7 @@ hatch run contract-test-full
|
|
|
339
339
|
|
|
340
340
|
- 💬 **Questions?** [GitHub Discussions](https://github.com/nold-ai/specfact-cli/discussions)
|
|
341
341
|
- 🐛 **Found a bug?** [GitHub Issues](https://github.com/nold-ai/specfact-cli/issues)
|
|
342
|
-
- 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. See [Debug Logging](docs/reference/debug-logging.md).
|
|
342
|
+
- 🔍 **Debugging I/O or API issues?** Run with `--debug`; logs are written to `~/.specfact/logs/specfact-debug.log`. With `--debug`, ADO API errors include response snippet and patch paths in the log. See [Debug Logging](docs/reference/debug-logging.md).
|
|
343
343
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
344
344
|
- 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
|
|
345
345
|
|
|
@@ -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.15"
|
|
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"
|
|
@@ -69,6 +69,8 @@ Refine backlog items from DevOps tools (GitHub Issues, Azure DevOps, etc.) into
|
|
|
69
69
|
- Ambiguous name-only matches will prompt for explicit iteration path
|
|
70
70
|
- `--release RELEASE` - Filter by release identifier (case-insensitive)
|
|
71
71
|
- `--limit N` - Maximum number of items to process in this refinement session (caps batch size)
|
|
72
|
+
- `--ignore-refined` / `--no-ignore-refined` - When set (default), exclude already-refined items so `--limit` applies to items that need refinement. Use `--no-ignore-refined` to process the first N items in order.
|
|
73
|
+
- `--id ISSUE_ID` - Refine only this backlog item (issue or work item ID). Other items are ignored.
|
|
72
74
|
- `--persona PERSONA` - Filter templates by persona (product-owner, architect, developer)
|
|
73
75
|
- `--framework FRAMEWORK` - Filter templates by framework (agile, scrum, safe, kanban)
|
|
74
76
|
|
|
@@ -162,6 +164,30 @@ specfact backlog refine $ADAPTER \
|
|
|
162
164
|
- Use `:quit` or `:abort` to cancel the entire session gracefully
|
|
163
165
|
- Session cancellation shows summary and exits without error
|
|
164
166
|
|
|
167
|
+
### Interactive refinement (Copilot mode)
|
|
168
|
+
|
|
169
|
+
When refining backlog items in Copilot mode (e.g. after export to tmp or during a refinement session), follow this **per-story loop** so the PO and stakeholders can review and approve before any update:
|
|
170
|
+
|
|
171
|
+
1. **For each story** (one at a time):
|
|
172
|
+
- **Present** the refined story in a clear, readable format:
|
|
173
|
+
- Use headings for Title, Body, Acceptance Criteria, Metrics.
|
|
174
|
+
- Use tables or panels for structured data so it is easy to scan.
|
|
175
|
+
- **Assess specification level** so the DevOps team knows if the story is ready, under-specified, or over-specified:
|
|
176
|
+
- **Under-specified**: Missing acceptance criteria, vague scope, unclear "so that" or user value. List evidence (e.g. "No AC", "Scope could mean X or Y"). Suggest what to add.
|
|
177
|
+
- **Over-specified**: Too much implementation detail, too many sub-steps for one story, or solution prescribed instead of outcome. List evidence and suggest what to trim or split.
|
|
178
|
+
- **Fit for scope and intent**: Clear persona, capability, benefit, and testable AC; appropriate size. State briefly why it is ready (and, if DoR is in use, that DoR is satisfied).
|
|
179
|
+
- **List ambiguities** or open questions (e.g. unclear scope, missing acceptance criteria, conflicting assumptions).
|
|
180
|
+
- **Ask** the PO and other stakeholders for clarification: "Please review the refined story above. Do you want any changes? Any ambiguities to resolve? Should this story be split?"
|
|
181
|
+
- **If the user provides feedback**: Re-refine the story incorporating the feedback, then repeat from "Present" for this story.
|
|
182
|
+
- **Only when the user explicitly approves** (e.g. "looks good", "approved", "no changes"): Mark this story as done and move to the **next** story.
|
|
183
|
+
- **Do not update** the backlog item (or write to the refined file as final) until the user has approved this story.
|
|
184
|
+
|
|
185
|
+
2. **Formatting**:
|
|
186
|
+
- Use clear headings, bullet lists, and optional tables/panels so refinement sessions are easy to follow and enjoyable.
|
|
187
|
+
- Keep each story’s block self-contained so stakeholders can focus on one item at a time.
|
|
188
|
+
|
|
189
|
+
3. **Rule**: The backlog item (or exported block) must only be updated/finalized **after** the user has approved the refined content for that story. Then proceed to the next story with the same process.
|
|
190
|
+
|
|
165
191
|
### Step 3: Present Results
|
|
166
192
|
|
|
167
193
|
Display refinement results:
|
|
@@ -11,8 +11,7 @@ This follows the backlog adapter patterns established by the GitHub adapter.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
|
-
|
|
15
|
-
# import re
|
|
14
|
+
import re
|
|
16
15
|
from datetime import UTC, datetime
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
from typing import Any
|
|
@@ -28,6 +27,7 @@ from specfact_cli.adapters.base import BridgeAdapter
|
|
|
28
27
|
from specfact_cli.backlog.adapters.base import BacklogAdapter
|
|
29
28
|
from specfact_cli.backlog.filters import BacklogFilters
|
|
30
29
|
from specfact_cli.backlog.mappers.ado_mapper import AdoFieldMapper
|
|
30
|
+
from specfact_cli.common.logger_setup import LoggerSetup
|
|
31
31
|
from specfact_cli.models.backlog_item import BacklogItem
|
|
32
32
|
from specfact_cli.models.bridge import BridgeConfig
|
|
33
33
|
from specfact_cli.models.capabilities import ToolCapabilities
|
|
@@ -36,9 +36,68 @@ from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
|
|
|
36
36
|
from specfact_cli.utils.auth_tokens import get_token, set_token
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
_MAX_RESPONSE_BODY_LOG = 2048
|
|
40
|
+
|
|
39
41
|
console = Console()
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
def _log_ado_patch_failure(
|
|
45
|
+
response: requests.Response | None,
|
|
46
|
+
operations: list[dict[str, Any]],
|
|
47
|
+
url: str,
|
|
48
|
+
context: str = "",
|
|
49
|
+
) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Log ADO PATCH failure to debug log (when debug on) and return user-facing message.
|
|
52
|
+
|
|
53
|
+
Parses response body (JSON message or truncated text), extracts patch paths,
|
|
54
|
+
redacts/truncates for debug log, and builds a user message with ADO text and hint.
|
|
55
|
+
"""
|
|
56
|
+
paths = [op.get("path", "") for op in operations if isinstance(op, dict)]
|
|
57
|
+
snippet = ""
|
|
58
|
+
if response is not None:
|
|
59
|
+
try:
|
|
60
|
+
body = response.json()
|
|
61
|
+
snippet = str(body.get("message", response.text[:500]))
|
|
62
|
+
except Exception:
|
|
63
|
+
snippet = (response.text or "")[:_MAX_RESPONSE_BODY_LOG]
|
|
64
|
+
snippet = snippet[:_MAX_RESPONSE_BODY_LOG]
|
|
65
|
+
snippet = str(LoggerSetup.redact_secrets(snippet))
|
|
66
|
+
|
|
67
|
+
if is_debug_mode():
|
|
68
|
+
debug_log_operation(
|
|
69
|
+
"ado_patch",
|
|
70
|
+
url,
|
|
71
|
+
"failed",
|
|
72
|
+
error=context or snippet[:500],
|
|
73
|
+
extra={"response_body": snippet, "patch_paths": paths},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return _build_ado_user_message(response)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _build_ado_user_message(response: requests.Response | None) -> str:
|
|
80
|
+
"""Build user-facing error message from ADO response and append mapping hint."""
|
|
81
|
+
hint = " Check custom field mapping; see ado_custom.yaml or documentation."
|
|
82
|
+
if response is None:
|
|
83
|
+
return f"Azure DevOps request failed.{hint}"
|
|
84
|
+
try:
|
|
85
|
+
body = response.json()
|
|
86
|
+
msg = body.get("message", "") or (response.text or "")[:500]
|
|
87
|
+
except Exception:
|
|
88
|
+
msg = (response.text or "")[:500]
|
|
89
|
+
if not msg:
|
|
90
|
+
return f"Azure DevOps request failed (HTTP {getattr(response, 'status_code', '')}).{hint}"
|
|
91
|
+
|
|
92
|
+
m = re.search(r"Cannot find field\s+([^\s]+)", msg, re.IGNORECASE)
|
|
93
|
+
if m:
|
|
94
|
+
field = m.group(1).strip().rstrip(".")
|
|
95
|
+
user_msg = f"Field '{field}' not found.{hint}"
|
|
96
|
+
else:
|
|
97
|
+
user_msg = f"{msg}{hint}"
|
|
98
|
+
return user_msg
|
|
99
|
+
|
|
100
|
+
|
|
42
101
|
class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
43
102
|
"""
|
|
44
103
|
Azure DevOps bridge adapter implementing BridgeAdapter interface.
|
|
@@ -1642,10 +1701,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1642
1701
|
"state": ado_state,
|
|
1643
1702
|
}
|
|
1644
1703
|
except requests.RequestException as e:
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
console.print(f"[bold red]✗[/bold red] {
|
|
1704
|
+
resp = getattr(e, "response", None)
|
|
1705
|
+
user_msg = _log_ado_patch_failure(resp, patch_document, url)
|
|
1706
|
+
e.ado_user_message = user_msg
|
|
1707
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
1649
1708
|
raise
|
|
1650
1709
|
|
|
1651
1710
|
def _update_work_item_status(
|
|
@@ -1744,8 +1803,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1744
1803
|
"state": ado_state,
|
|
1745
1804
|
}
|
|
1746
1805
|
except requests.RequestException as e:
|
|
1747
|
-
|
|
1748
|
-
|
|
1806
|
+
resp = getattr(e, "response", None)
|
|
1807
|
+
user_msg = _log_ado_patch_failure(resp, patch_document, url)
|
|
1808
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
1749
1809
|
raise
|
|
1750
1810
|
|
|
1751
1811
|
def _update_work_item_body(
|
|
@@ -1876,8 +1936,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1876
1936
|
"state": ado_state,
|
|
1877
1937
|
}
|
|
1878
1938
|
except requests.RequestException as e:
|
|
1879
|
-
|
|
1880
|
-
|
|
1939
|
+
resp = getattr(e, "response", None)
|
|
1940
|
+
user_msg = _log_ado_patch_failure(resp, patch_document, url)
|
|
1941
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
1881
1942
|
raise
|
|
1882
1943
|
|
|
1883
1944
|
@beartype
|
|
@@ -1983,8 +2044,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1983
2044
|
"new_state": ado_state,
|
|
1984
2045
|
}
|
|
1985
2046
|
except requests.RequestException as e:
|
|
1986
|
-
|
|
1987
|
-
|
|
2047
|
+
resp = getattr(e, "response", None)
|
|
2048
|
+
user_msg = _log_ado_patch_failure(resp, patch_document, url)
|
|
2049
|
+
e.ado_user_message = user_msg
|
|
2050
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
1988
2051
|
raise
|
|
1989
2052
|
|
|
1990
2053
|
@beartype
|
|
@@ -2361,8 +2424,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2361
2424
|
"comment_added": True,
|
|
2362
2425
|
}
|
|
2363
2426
|
except requests.RequestException as e:
|
|
2364
|
-
|
|
2365
|
-
|
|
2427
|
+
resp = getattr(e, "response", None)
|
|
2428
|
+
user_msg = _log_ado_patch_failure(resp, [], url)
|
|
2429
|
+
e.ado_user_message = user_msg
|
|
2430
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
2366
2431
|
raise
|
|
2367
2432
|
|
|
2368
2433
|
@beartype
|
|
@@ -3200,7 +3265,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3200
3265
|
response = requests.patch(url, headers=headers, json=operations, timeout=30)
|
|
3201
3266
|
response.raise_for_status()
|
|
3202
3267
|
except requests.HTTPError as e:
|
|
3203
|
-
|
|
3268
|
+
user_msg = _log_ado_patch_failure(e.response, operations, url)
|
|
3269
|
+
e.ado_user_message = user_msg
|
|
3204
3270
|
response = None
|
|
3205
3271
|
if e.response and e.response.status_code in (400, 422):
|
|
3206
3272
|
error_message = ""
|
|
@@ -3221,14 +3287,12 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3221
3287
|
resp.raise_for_status()
|
|
3222
3288
|
response = resp
|
|
3223
3289
|
except requests.HTTPError as retry_error:
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
error=str(retry_error),
|
|
3231
|
-
)
|
|
3290
|
+
_log_ado_patch_failure(
|
|
3291
|
+
retry_error.response,
|
|
3292
|
+
operations_no_format,
|
|
3293
|
+
url,
|
|
3294
|
+
context=str(retry_error),
|
|
3295
|
+
)
|
|
3232
3296
|
|
|
3233
3297
|
if response is None and (
|
|
3234
3298
|
"already exists" in error_message.lower() or "cannot add" in error_message.lower()
|
|
@@ -3280,9 +3344,11 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3280
3344
|
resp.raise_for_status()
|
|
3281
3345
|
response = resp
|
|
3282
3346
|
except requests.HTTPError:
|
|
3347
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
3283
3348
|
raise
|
|
3284
3349
|
|
|
3285
3350
|
if response is None:
|
|
3351
|
+
console.print(f"[bold red]✗[/bold red] {user_msg}")
|
|
3286
3352
|
raise
|
|
3287
3353
|
|
|
3288
3354
|
updated_work_item = response.json()
|
|
@@ -454,6 +454,7 @@ def cli_main() -> None:
|
|
|
454
454
|
console.print() # Empty line after banner
|
|
455
455
|
elif not is_help_or_version and not is_test_mode:
|
|
456
456
|
# Show simple version line like other CLIs (skip for help/version commands and in test mode)
|
|
457
|
+
# Printed before startup checks so users see output immediately (important with slow checks e.g. xagt)
|
|
457
458
|
print_version_line()
|
|
458
459
|
|
|
459
460
|
# Run startup checks (template validation and version check)
|
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
import os
|
|
17
17
|
import re
|
|
18
18
|
import sys
|
|
19
|
+
import tempfile
|
|
19
20
|
from datetime import datetime
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any
|
|
@@ -295,6 +296,44 @@ def _parse_refined_export_markdown(content: str) -> dict[str, dict[str, Any]]:
|
|
|
295
296
|
return result
|
|
296
297
|
|
|
297
298
|
|
|
299
|
+
@beartype
|
|
300
|
+
def _item_needs_refinement(
|
|
301
|
+
item: BacklogItem,
|
|
302
|
+
detector: TemplateDetector,
|
|
303
|
+
registry: TemplateRegistry,
|
|
304
|
+
template_id: str | None,
|
|
305
|
+
normalized_adapter: str | None,
|
|
306
|
+
normalized_framework: str | None,
|
|
307
|
+
normalized_persona: str | None,
|
|
308
|
+
) -> bool:
|
|
309
|
+
"""
|
|
310
|
+
Return True if the item needs refinement (should be processed); False if already refined (skip).
|
|
311
|
+
|
|
312
|
+
Mirrors the "already refined" skip logic used in the refine loop: checkboxes + all required
|
|
313
|
+
sections, or high confidence with no missing fields.
|
|
314
|
+
"""
|
|
315
|
+
detection_result = detector.detect_template(
|
|
316
|
+
item,
|
|
317
|
+
provider=normalized_adapter,
|
|
318
|
+
framework=normalized_framework,
|
|
319
|
+
persona=normalized_persona,
|
|
320
|
+
)
|
|
321
|
+
if detection_result.template_id:
|
|
322
|
+
target = registry.get_template(detection_result.template_id) if detection_result.template_id else None
|
|
323
|
+
if target and target.required_sections:
|
|
324
|
+
has_checkboxes = bool(
|
|
325
|
+
re.search(r"^[\s]*- \[[ x]\]", item.body_markdown or "", re.MULTILINE | re.IGNORECASE)
|
|
326
|
+
)
|
|
327
|
+
all_present = all(
|
|
328
|
+
bool(re.search(rf"^#+\s+{re.escape(s)}\s*$", item.body_markdown or "", re.MULTILINE | re.IGNORECASE))
|
|
329
|
+
for s in target.required_sections
|
|
330
|
+
)
|
|
331
|
+
if has_checkboxes and all_present and not detection_result.missing_fields:
|
|
332
|
+
return False
|
|
333
|
+
already_refined = template_id is None and detection_result.confidence >= 0.8 and not detection_result.missing_fields
|
|
334
|
+
return not already_refined
|
|
335
|
+
|
|
336
|
+
|
|
298
337
|
def _fetch_backlog_items(
|
|
299
338
|
adapter_name: str,
|
|
300
339
|
search_query: str | None = None,
|
|
@@ -423,6 +462,16 @@ def refine(
|
|
|
423
462
|
"--limit",
|
|
424
463
|
help="Maximum number of items to process in this refinement session. Use to cap batch size and avoid processing too many items at once.",
|
|
425
464
|
),
|
|
465
|
+
ignore_refined: bool = typer.Option(
|
|
466
|
+
True,
|
|
467
|
+
"--ignore-refined/--no-ignore-refined",
|
|
468
|
+
help="When set (default), exclude already-refined items from the batch so --limit applies to items that need refinement. Use --no-ignore-refined to process the first N items in order (already-refined skipped in loop).",
|
|
469
|
+
),
|
|
470
|
+
issue_id: str | None = typer.Option(
|
|
471
|
+
None,
|
|
472
|
+
"--id",
|
|
473
|
+
help="Refine only this backlog item (issue or work item ID). Other items are ignored.",
|
|
474
|
+
),
|
|
426
475
|
template_id: str | None = typer.Option(None, "--template", "-t", help="Target template ID (default: auto-detect)"),
|
|
427
476
|
auto_accept_high_confidence: bool = typer.Option(
|
|
428
477
|
False, "--auto-accept-high-confidence", help="Auto-accept refinements with confidence >= 0.85"
|
|
@@ -445,12 +494,12 @@ def refine(
|
|
|
445
494
|
export_to_tmp: bool = typer.Option(
|
|
446
495
|
False,
|
|
447
496
|
"--export-to-tmp",
|
|
448
|
-
help="Export backlog items to temporary file for copilot processing (default:
|
|
497
|
+
help="Export backlog items to temporary file for copilot processing (default: <system-temp>/specfact-backlog-refine-<timestamp>.md)",
|
|
449
498
|
),
|
|
450
499
|
import_from_tmp: bool = typer.Option(
|
|
451
500
|
False,
|
|
452
501
|
"--import-from-tmp",
|
|
453
|
-
help="Import refined content from temporary file after copilot processing (default:
|
|
502
|
+
help="Import refined content from temporary file after copilot processing (default: <system-temp>/specfact-backlog-refine-<timestamp>-refined.md)",
|
|
454
503
|
),
|
|
455
504
|
tmp_file: Path | None = typer.Option(
|
|
456
505
|
None,
|
|
@@ -650,6 +699,10 @@ def refine(
|
|
|
650
699
|
init_progress.update(validate_task, description="[green]✓[/green] Configuration validated")
|
|
651
700
|
|
|
652
701
|
# Fetch backlog items with filters
|
|
702
|
+
# When ignore_refined and limit are set, fetch more candidates so we have enough after filtering
|
|
703
|
+
fetch_limit: int | None = limit
|
|
704
|
+
if ignore_refined and limit is not None and limit > 0:
|
|
705
|
+
fetch_limit = limit * 5
|
|
653
706
|
with Progress(
|
|
654
707
|
SpinnerColumn(),
|
|
655
708
|
TextColumn("[progress.description]{task.description}"),
|
|
@@ -667,7 +720,7 @@ def refine(
|
|
|
667
720
|
iteration=iteration,
|
|
668
721
|
sprint=sprint,
|
|
669
722
|
release=release,
|
|
670
|
-
limit=
|
|
723
|
+
limit=fetch_limit,
|
|
671
724
|
repo_owner=repo_owner,
|
|
672
725
|
repo_name=repo_name,
|
|
673
726
|
github_token=github_token,
|
|
@@ -705,6 +758,34 @@ def refine(
|
|
|
705
758
|
console.print("[yellow]No backlog items found.[/yellow]")
|
|
706
759
|
return
|
|
707
760
|
|
|
761
|
+
# Filter by issue ID when --id is set
|
|
762
|
+
if issue_id is not None:
|
|
763
|
+
items = [i for i in items if str(i.id) == str(issue_id)]
|
|
764
|
+
if not items:
|
|
765
|
+
console.print(
|
|
766
|
+
f"[bold red]✗[/bold red] No backlog item with id {issue_id!r} found. "
|
|
767
|
+
"Check filters and adapter configuration."
|
|
768
|
+
)
|
|
769
|
+
raise typer.Exit(1)
|
|
770
|
+
|
|
771
|
+
# When ignore_refined (default), keep only items that need refinement; then apply limit
|
|
772
|
+
if ignore_refined:
|
|
773
|
+
items = [
|
|
774
|
+
i
|
|
775
|
+
for i in items
|
|
776
|
+
if _item_needs_refinement(
|
|
777
|
+
i, detector, registry, template_id, normalized_adapter, normalized_framework, normalized_persona
|
|
778
|
+
)
|
|
779
|
+
]
|
|
780
|
+
if limit is not None and len(items) > limit:
|
|
781
|
+
items = items[:limit]
|
|
782
|
+
if ignore_refined and (limit is not None or issue_id is not None):
|
|
783
|
+
console.print(
|
|
784
|
+
f"[dim]Filtered to {len(items)} item(s) needing refinement"
|
|
785
|
+
+ (f" (limit {limit})" if limit is not None else "")
|
|
786
|
+
+ "[/dim]"
|
|
787
|
+
)
|
|
788
|
+
|
|
708
789
|
# Validate export/import flags
|
|
709
790
|
if export_to_tmp and import_from_tmp:
|
|
710
791
|
console.print("[bold red]✗[/bold red] --export-to-tmp and --import-from-tmp are mutually exclusive")
|
|
@@ -713,7 +794,7 @@ def refine(
|
|
|
713
794
|
# Handle export mode
|
|
714
795
|
if export_to_tmp:
|
|
715
796
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
716
|
-
export_file = tmp_file or Path(f"
|
|
797
|
+
export_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-backlog-refine-{timestamp}.md")
|
|
717
798
|
|
|
718
799
|
console.print(f"[bold cyan]Exporting {len(items)} backlog item(s) to: {export_file}[/bold cyan]")
|
|
719
800
|
|
|
@@ -764,7 +845,7 @@ def refine(
|
|
|
764
845
|
# Handle import mode
|
|
765
846
|
if import_from_tmp:
|
|
766
847
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
767
|
-
import_file = tmp_file or Path(f"
|
|
848
|
+
import_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-backlog-refine-{timestamp}-refined.md")
|
|
768
849
|
|
|
769
850
|
if not import_file.exists():
|
|
770
851
|
console.print(f"[bold red]✗[/bold red] Import file not found: {import_file}")
|
|
@@ -842,8 +923,8 @@ def refine(
|
|
|
842
923
|
console.print(f"[green]✓ Updated {len(updated_items)} backlog item(s)[/green]")
|
|
843
924
|
return
|
|
844
925
|
|
|
845
|
-
# Apply limit if specified
|
|
846
|
-
if limit and len(items) > limit:
|
|
926
|
+
# Apply limit if specified (when not ignore_refined; when ignore_refined we already filtered and sliced)
|
|
927
|
+
if not ignore_refined and limit is not None and len(items) > limit:
|
|
847
928
|
items = items[:limit]
|
|
848
929
|
console.print(f"[yellow]Limited to {limit} items (found {len(items)} total)[/yellow]")
|
|
849
930
|
else:
|
|
@@ -1099,19 +1099,19 @@ def sync_bridge(
|
|
|
1099
1099
|
export_to_tmp: bool = typer.Option(
|
|
1100
1100
|
False,
|
|
1101
1101
|
"--export-to-tmp",
|
|
1102
|
-
help="Export proposal content to temporary file for LLM review (default:
|
|
1102
|
+
help="Export proposal content to temporary file for LLM review (default: <system-temp>/specfact-proposal-<change-id>.md).",
|
|
1103
1103
|
hidden=True,
|
|
1104
1104
|
),
|
|
1105
1105
|
import_from_tmp: bool = typer.Option(
|
|
1106
1106
|
False,
|
|
1107
1107
|
"--import-from-tmp",
|
|
1108
|
-
help="Import sanitized content from temporary file after LLM review (default:
|
|
1108
|
+
help="Import sanitized content from temporary file after LLM review (default: <system-temp>/specfact-proposal-<change-id>-sanitized.md).",
|
|
1109
1109
|
hidden=True,
|
|
1110
1110
|
),
|
|
1111
1111
|
tmp_file: Path | None = typer.Option(
|
|
1112
1112
|
None,
|
|
1113
1113
|
"--tmp-file",
|
|
1114
|
-
help="Custom temporary file path (default:
|
|
1114
|
+
help="Custom temporary file path (default: <system-temp>/specfact-proposal-<change-id>.md).",
|
|
1115
1115
|
hidden=True,
|
|
1116
1116
|
),
|
|
1117
1117
|
update_existing: bool = typer.Option(
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import hashlib
|
|
13
13
|
import re
|
|
14
14
|
import subprocess
|
|
15
|
+
import tempfile
|
|
15
16
|
from dataclasses import dataclass
|
|
16
17
|
from urllib.parse import urlparse
|
|
17
18
|
|
|
@@ -557,7 +558,7 @@ class BridgeSync:
|
|
|
557
558
|
change_ids: Optional list of change proposal IDs to filter. If None, exports all active proposals.
|
|
558
559
|
export_to_tmp: If True, export proposal content to temporary file for LLM review.
|
|
559
560
|
import_from_tmp: If True, import sanitized content from temporary file after LLM review.
|
|
560
|
-
tmp_file: Optional custom temporary file path. Default:
|
|
561
|
+
tmp_file: Optional custom temporary file path. Default: <system-temp>/specfact-proposal-<change-id>.md.
|
|
561
562
|
|
|
562
563
|
Returns:
|
|
563
564
|
SyncResult with operation details
|
|
@@ -904,7 +905,7 @@ class BridgeSync:
|
|
|
904
905
|
# Handle temporary file workflow if requested
|
|
905
906
|
if export_to_tmp:
|
|
906
907
|
# Export proposal content to temporary file for LLM review
|
|
907
|
-
tmp_file_path = tmp_file or Path(f"
|
|
908
|
+
tmp_file_path = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}.md")
|
|
908
909
|
try:
|
|
909
910
|
# Create markdown content from proposal
|
|
910
911
|
proposal_content = self._format_proposal_for_export(proposal)
|
|
@@ -919,7 +920,9 @@ class BridgeSync:
|
|
|
919
920
|
|
|
920
921
|
if import_from_tmp:
|
|
921
922
|
# Import sanitized content from temporary file
|
|
922
|
-
sanitized_file_path = tmp_file or
|
|
923
|
+
sanitized_file_path = tmp_file or (
|
|
924
|
+
Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md"
|
|
925
|
+
)
|
|
923
926
|
try:
|
|
924
927
|
if not sanitized_file_path.exists():
|
|
925
928
|
errors.append(
|
|
@@ -933,7 +936,7 @@ class BridgeSync:
|
|
|
933
936
|
proposal_to_export = self._parse_sanitized_proposal(sanitized_content, proposal)
|
|
934
937
|
# Cleanup temporary files after import
|
|
935
938
|
try:
|
|
936
|
-
original_tmp = Path(f"
|
|
939
|
+
original_tmp = Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}.md"
|
|
937
940
|
if original_tmp.exists():
|
|
938
941
|
original_tmp.unlink()
|
|
939
942
|
if sanitized_file_path.exists():
|
|
@@ -2504,7 +2507,7 @@ class BridgeSync:
|
|
|
2504
2507
|
# Handle sanitized content updates (when import_from_tmp is used)
|
|
2505
2508
|
if import_from_tmp:
|
|
2506
2509
|
change_id = proposal.get("change_id", "unknown")
|
|
2507
|
-
sanitized_file = tmp_file or Path(f"
|
|
2510
|
+
sanitized_file = tmp_file or (Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md")
|
|
2508
2511
|
if sanitized_file.exists():
|
|
2509
2512
|
sanitized_content = sanitized_file.read_text(encoding="utf-8")
|
|
2510
2513
|
proposal_for_hash = {
|
|
@@ -2603,7 +2606,9 @@ class BridgeSync:
|
|
|
2603
2606
|
try:
|
|
2604
2607
|
if import_from_tmp:
|
|
2605
2608
|
change_id = proposal.get("change_id", "unknown")
|
|
2606
|
-
sanitized_file = tmp_file or
|
|
2609
|
+
sanitized_file = tmp_file or (
|
|
2610
|
+
Path(tempfile.gettempdir()) / f"specfact-proposal-{change_id}-sanitized.md"
|
|
2611
|
+
)
|
|
2607
2612
|
if sanitized_file.exists():
|
|
2608
2613
|
sanitized_content = sanitized_file.read_text(encoding="utf-8")
|
|
2609
2614
|
proposal_for_update = {
|
|
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.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/defect_v1.yaml
RENAMED
|
File without changes
|
{specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/enabler_v1.yaml
RENAMED
|
File without changes
|
{specfact_cli-0.26.13 → specfact_cli-0.26.15}/resources/templates/backlog/defaults/spike_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
|