specfact-cli 0.26.10__tar.gz → 0.26.13__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.10 → specfact_cli-0.26.13}/PKG-INFO +2 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/README.md +1 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/pyproject.toml +1 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/__init__.py +1 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/__init__.py +1 -1
- specfact_cli-0.26.13/src/specfact_cli/__main__.py +6 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/ado.py +114 -83
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/github.py +8 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/cli.py +4 -2
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/analyze.py +28 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/auth.py +57 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/backlog_commands.py +161 -4
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/drift.py +26 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/enforce.py +71 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/generate.py +142 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/import_cmd.py +64 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/init.py +10 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/migrate.py +69 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/plan.py +34 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/project_cmd.py +50 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/repro.py +34 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/sdd.py +11 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/spec.py +35 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/sync.py +41 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/update.py +37 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/validate.py +42 -1
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/logger_setup.py +68 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/runtime.py +126 -4
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/.gitignore +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/LICENSE.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/shared/cli-enforcement.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.01-import.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.02-plan.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.03-review.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.04-sdd.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.05-enforce.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.06-sync.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.07-contracts.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.backlog-refine.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.compare.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.sync-backlog.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/prompts/specfact.validate.md +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_agile.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_default.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_kanban.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_safe.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/field_mappings/ado_scrum.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/frameworks/safe/safe_feature_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/personas/developer/developer_task_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/backlog/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/adapters/speckit.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/adapters/local_yaml_adapter.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/ai_refiner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/format_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/markdown_format.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/formats/structured_format.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/backlog/template_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/bridge_templates.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.26.10 → specfact_cli-0.26.13}/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.13
|
|
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,6 +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
621
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
621
622
|
- 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
|
|
622
623
|
|
|
@@ -339,6 +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
343
|
- 📧 **Need help?** [hello@noldai.com](mailto:hello@noldai.com)
|
|
343
344
|
- 🌐 **Learn more:** [specfact.com](https://specfact.com) • [specfact.io](https://specfact.io) • [specfact.dev](https://specfact.dev)
|
|
344
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.13"
|
|
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"
|
|
@@ -11,7 +11,8 @@ This follows the backlog adapter patterns established by the GitHub adapter.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
# import re
|
|
15
16
|
from datetime import UTC, datetime
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from typing import Any
|
|
@@ -31,7 +32,7 @@ from specfact_cli.models.backlog_item import BacklogItem
|
|
|
31
32
|
from specfact_cli.models.bridge import BridgeConfig
|
|
32
33
|
from specfact_cli.models.capabilities import ToolCapabilities
|
|
33
34
|
from specfact_cli.models.change import ChangeProposal, ChangeTracking
|
|
34
|
-
from specfact_cli.runtime import debug_print
|
|
35
|
+
from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
|
|
35
36
|
from specfact_cli.utils.auth_tokens import get_token, set_token
|
|
36
37
|
|
|
37
38
|
|
|
@@ -372,6 +373,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
372
373
|
rationale = ""
|
|
373
374
|
impact = ""
|
|
374
375
|
|
|
376
|
+
import re
|
|
377
|
+
|
|
375
378
|
# Parse markdown sections (Why, What Changes)
|
|
376
379
|
if description_raw:
|
|
377
380
|
# Extract "Why" section (stop at What Changes or OpenSpec footer)
|
|
@@ -696,6 +699,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
696
699
|
ValueError: If required configuration is missing
|
|
697
700
|
requests.RequestException: If Azure DevOps API call fails
|
|
698
701
|
"""
|
|
702
|
+
import re as _re
|
|
703
|
+
|
|
699
704
|
if not self.api_token:
|
|
700
705
|
msg = (
|
|
701
706
|
"Azure DevOps API token required. Options:\n"
|
|
@@ -751,7 +756,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
751
756
|
parsed = urlparse(source_url)
|
|
752
757
|
if parsed.hostname and parsed.hostname.lower() == "dev.azure.com":
|
|
753
758
|
target_org = target_repo.split("/")[0]
|
|
754
|
-
ado_org_match =
|
|
759
|
+
ado_org_match = _re.search(r"dev\.azure\.com/([^/]+)/", source_url)
|
|
755
760
|
if ado_org_match and ado_org_match.group(1) == target_org:
|
|
756
761
|
# Org matches - this is likely the same ADO organization
|
|
757
762
|
work_item_id = entry.get("source_id")
|
|
@@ -773,8 +778,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
773
778
|
# 3. AND (project is unknown in entry OR project is unknown in target OR both contain GUIDs)
|
|
774
779
|
# This prevents matching org/project-a with org/project-b when both have known project names
|
|
775
780
|
source_url = entry.get("source_url", "")
|
|
776
|
-
entry_has_guid = source_url and
|
|
777
|
-
r"dev\.azure\.com/[^/]+/[0-9a-f-]{36}", source_url,
|
|
781
|
+
entry_has_guid = source_url and _re.search(
|
|
782
|
+
r"dev\.azure\.com/[^/]+/[0-9a-f-]{36}", source_url, _re.IGNORECASE
|
|
778
783
|
)
|
|
779
784
|
project_unknown = (
|
|
780
785
|
not entry_project # Entry has no project part
|
|
@@ -967,8 +972,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
967
972
|
Returns:
|
|
968
973
|
Tuple of (org, project, work_item_id)
|
|
969
974
|
"""
|
|
975
|
+
import re as _re
|
|
976
|
+
|
|
970
977
|
cleaned = item_ref.strip().lstrip("#")
|
|
971
|
-
url_match =
|
|
978
|
+
url_match = _re.search(r"dev\.azure\.com/([^/]+)/([^/]+)/.*?/(\d+)", cleaned, _re.IGNORECASE)
|
|
972
979
|
if url_match:
|
|
973
980
|
return url_match.group(1), url_match.group(2), int(url_match.group(3))
|
|
974
981
|
|
|
@@ -1439,6 +1446,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1439
1446
|
|
|
1440
1447
|
try:
|
|
1441
1448
|
response = requests.post(url, json=wiql, headers=headers, timeout=10)
|
|
1449
|
+
if is_debug_mode():
|
|
1450
|
+
debug_log_operation(
|
|
1451
|
+
"ado_wiql",
|
|
1452
|
+
url,
|
|
1453
|
+
str(response.status_code),
|
|
1454
|
+
error=None if response.ok else (response.text[:200] if response.text else None),
|
|
1455
|
+
)
|
|
1442
1456
|
if response.status_code != 200:
|
|
1443
1457
|
return None
|
|
1444
1458
|
work_items = response.json().get("workItems", [])
|
|
@@ -1453,7 +1467,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1453
1467
|
"source_type": "ado",
|
|
1454
1468
|
"source_repo": f"{org}/{project}",
|
|
1455
1469
|
}
|
|
1456
|
-
except requests.RequestException:
|
|
1470
|
+
except requests.RequestException as e:
|
|
1471
|
+
if is_debug_mode():
|
|
1472
|
+
debug_log_operation("ado_wiql", url, "error", error=str(e))
|
|
1457
1473
|
return None
|
|
1458
1474
|
|
|
1459
1475
|
def _create_work_item_from_proposal(
|
|
@@ -1473,6 +1489,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1473
1489
|
Returns:
|
|
1474
1490
|
Dict with work item data: {"work_item_id": int, "work_item_url": str, "state": str}
|
|
1475
1491
|
"""
|
|
1492
|
+
import re as _re
|
|
1493
|
+
|
|
1476
1494
|
title = proposal_data.get("title", "Untitled Change Proposal")
|
|
1477
1495
|
description = proposal_data.get("description", "")
|
|
1478
1496
|
rationale = proposal_data.get("rationale", "")
|
|
@@ -1489,7 +1507,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1489
1507
|
else:
|
|
1490
1508
|
body_parts = []
|
|
1491
1509
|
|
|
1492
|
-
display_title =
|
|
1510
|
+
display_title = _re.sub(r"^\[change\]\s*", "", title, flags=_re.IGNORECASE).strip()
|
|
1493
1511
|
if display_title:
|
|
1494
1512
|
body_parts.append(f"# {display_title}")
|
|
1495
1513
|
body_parts.append("")
|
|
@@ -1572,6 +1590,13 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1572
1590
|
|
|
1573
1591
|
try:
|
|
1574
1592
|
response = requests.patch(url, json=patch_document, headers=headers, timeout=30)
|
|
1593
|
+
if is_debug_mode():
|
|
1594
|
+
debug_log_operation(
|
|
1595
|
+
"ado_patch",
|
|
1596
|
+
url,
|
|
1597
|
+
str(response.status_code),
|
|
1598
|
+
error=None if response.ok else (response.text[:200] if response.text else None),
|
|
1599
|
+
)
|
|
1575
1600
|
response.raise_for_status()
|
|
1576
1601
|
work_item_data = response.json()
|
|
1577
1602
|
|
|
@@ -1617,6 +1642,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1617
1642
|
"state": ado_state,
|
|
1618
1643
|
}
|
|
1619
1644
|
except requests.RequestException as e:
|
|
1645
|
+
if is_debug_mode():
|
|
1646
|
+
debug_log_operation("ado_patch", url, "error", error=str(e))
|
|
1620
1647
|
msg = f"Failed to create Azure DevOps work item: {e}"
|
|
1621
1648
|
console.print(f"[bold red]✗[/bold red] {msg}")
|
|
1622
1649
|
raise
|
|
@@ -1740,6 +1767,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1740
1767
|
Returns:
|
|
1741
1768
|
Dict with updated work item data: {"work_item_id": int, "work_item_url": str, "state": str}
|
|
1742
1769
|
"""
|
|
1770
|
+
import re as _re
|
|
1771
|
+
|
|
1743
1772
|
title = proposal_data.get("title", "Untitled Change Proposal")
|
|
1744
1773
|
description = proposal_data.get("description", "")
|
|
1745
1774
|
rationale = proposal_data.get("rationale", "")
|
|
@@ -1756,7 +1785,7 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1756
1785
|
else:
|
|
1757
1786
|
body_parts = []
|
|
1758
1787
|
|
|
1759
|
-
display_title =
|
|
1788
|
+
display_title = _re.sub(r"^\[change\]\s*", "", title, flags=_re.IGNORECASE).strip()
|
|
1760
1789
|
if display_title:
|
|
1761
1790
|
body_parts.append(f"# {display_title}")
|
|
1762
1791
|
body_parts.append("")
|
|
@@ -2908,8 +2937,22 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2908
2937
|
|
|
2909
2938
|
try:
|
|
2910
2939
|
response = requests.get(url, headers=workitems_headers, params=params, timeout=30)
|
|
2940
|
+
if is_debug_mode():
|
|
2941
|
+
debug_log_operation(
|
|
2942
|
+
"ado_workitems_get",
|
|
2943
|
+
url,
|
|
2944
|
+
str(response.status_code),
|
|
2945
|
+
error=None if response.ok else (response.text[:200] if response.text else None),
|
|
2946
|
+
)
|
|
2911
2947
|
response.raise_for_status()
|
|
2912
2948
|
except requests.HTTPError as e:
|
|
2949
|
+
if is_debug_mode():
|
|
2950
|
+
debug_log_operation(
|
|
2951
|
+
"ado_workitems_get",
|
|
2952
|
+
url,
|
|
2953
|
+
"error",
|
|
2954
|
+
error=str(e.response.status_code) if e.response is not None else str(e),
|
|
2955
|
+
)
|
|
2913
2956
|
# Provide better error message with URL details
|
|
2914
2957
|
error_detail = ""
|
|
2915
2958
|
if e.response is not None:
|
|
@@ -3095,11 +3138,12 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3095
3138
|
|
|
3096
3139
|
# Update description (body_markdown) - always use System.Description
|
|
3097
3140
|
if update_fields is None or "body" in update_fields or "body_markdown" in update_fields:
|
|
3098
|
-
# Convert TODO markers to proper Markdown checkboxes for ADO rendering
|
|
3099
3141
|
import re
|
|
3100
3142
|
|
|
3101
|
-
|
|
3102
|
-
|
|
3143
|
+
# Never send null: ADO rejects null for /fields/System.Description (HTTP 400)
|
|
3144
|
+
raw_body = item.body_markdown
|
|
3145
|
+
markdown_content = raw_body if raw_body is not None else ""
|
|
3146
|
+
# Convert TODO markers to proper Markdown checkboxes for ADO rendering
|
|
3103
3147
|
todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
|
|
3104
3148
|
markdown_content = re.sub(
|
|
3105
3149
|
todo_pattern,
|
|
@@ -3108,11 +3152,9 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3108
3152
|
flags=re.MULTILINE | re.IGNORECASE,
|
|
3109
3153
|
)
|
|
3110
3154
|
|
|
3111
|
-
# Get mapped description field name (honors custom mappings)
|
|
3112
3155
|
description_field = reverse_mappings.get("description", "System.Description")
|
|
3113
|
-
# Set multiline field format to Markdown
|
|
3156
|
+
# Set multiline field format to Markdown first (optional; many ADO instances return 400 for this path)
|
|
3114
3157
|
operations.append({"op": "add", "path": f"/multilineFieldsFormat/{description_field}", "value": "Markdown"})
|
|
3115
|
-
# Then set description content with Markdown format
|
|
3116
3158
|
operations.append({"op": "replace", "path": f"/fields/{description_field}", "value": markdown_content})
|
|
3117
3159
|
|
|
3118
3160
|
# Update acceptance criteria using mapped field name (honors custom mappings)
|
|
@@ -3158,7 +3200,8 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3158
3200
|
response = requests.patch(url, headers=headers, json=operations, timeout=30)
|
|
3159
3201
|
response.raise_for_status()
|
|
3160
3202
|
except requests.HTTPError as e:
|
|
3161
|
-
# Handle
|
|
3203
|
+
# Handle 400/422: often caused by /multilineFieldsFormat/ not being supported by ADO API
|
|
3204
|
+
response = None
|
|
3162
3205
|
if e.response and e.response.status_code in (400, 422):
|
|
3163
3206
|
error_message = ""
|
|
3164
3207
|
try:
|
|
@@ -3167,91 +3210,79 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
3167
3210
|
except Exception:
|
|
3168
3211
|
pass
|
|
3169
3212
|
|
|
3170
|
-
#
|
|
3171
|
-
|
|
3172
|
-
|
|
3213
|
+
# First retry: omit multilineFieldsFormat entirely (only /fields/ updates).
|
|
3214
|
+
# Many ADO instances reject /multilineFieldsFormat/ path with 400 Bad Request.
|
|
3215
|
+
operations_no_format = [
|
|
3216
|
+
op for op in operations if not (op.get("path") or "").startswith("/multilineFieldsFormat/")
|
|
3217
|
+
]
|
|
3218
|
+
if operations_no_format != operations:
|
|
3219
|
+
try:
|
|
3220
|
+
resp = requests.patch(url, headers=headers, json=operations_no_format, timeout=30)
|
|
3221
|
+
resp.raise_for_status()
|
|
3222
|
+
response = resp
|
|
3223
|
+
except requests.HTTPError as retry_error:
|
|
3224
|
+
# Retry with operations_no_format failed; continue to next fallback strategy.
|
|
3225
|
+
if is_debug_mode():
|
|
3226
|
+
debug_log_operation(
|
|
3227
|
+
"ado_patch",
|
|
3228
|
+
url,
|
|
3229
|
+
"failed",
|
|
3230
|
+
error=str(retry_error),
|
|
3231
|
+
)
|
|
3232
|
+
|
|
3233
|
+
if response is None and (
|
|
3234
|
+
"already exists" in error_message.lower() or "cannot add" in error_message.lower()
|
|
3235
|
+
):
|
|
3236
|
+
# Second: try "replace" instead of "add" for multilineFieldsFormat
|
|
3173
3237
|
operations_replace = []
|
|
3174
3238
|
for op in operations:
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
operations_replace.append({"op": "replace", "path":
|
|
3239
|
+
path = op.get("path") or ""
|
|
3240
|
+
if path.startswith("/multilineFieldsFormat/"):
|
|
3241
|
+
operations_replace.append({"op": "replace", "path": path, "value": op["value"]})
|
|
3178
3242
|
else:
|
|
3179
3243
|
operations_replace.append(op)
|
|
3180
|
-
|
|
3181
3244
|
try:
|
|
3182
|
-
|
|
3183
|
-
|
|
3245
|
+
resp = requests.patch(url, headers=headers, json=operations_replace, timeout=30)
|
|
3246
|
+
resp.raise_for_status()
|
|
3247
|
+
response = resp
|
|
3184
3248
|
except requests.HTTPError:
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
# Convert TODO markers to checkboxes first
|
|
3198
|
-
todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
|
|
3199
|
-
markdown_for_html = re.sub(
|
|
3200
|
-
todo_pattern,
|
|
3201
|
-
r"\1- [ ] \2",
|
|
3202
|
-
markdown_for_html,
|
|
3203
|
-
flags=re.MULTILINE | re.IGNORECASE,
|
|
3204
|
-
)
|
|
3205
|
-
# Simple markdown to HTML conversion (basic)
|
|
3206
|
-
try:
|
|
3207
|
-
import markdown
|
|
3208
|
-
|
|
3209
|
-
html_body = markdown.markdown(
|
|
3210
|
-
markdown_for_html, extensions=["fenced_code", "tables"]
|
|
3211
|
-
)
|
|
3212
|
-
op["value"] = html_body
|
|
3213
|
-
except ImportError:
|
|
3214
|
-
# markdown library not available - use raw text
|
|
3215
|
-
console.print("[yellow]⚠ markdown library not available, using raw text[/yellow]")
|
|
3216
|
-
# Keep original markdown as-is (ADO may still render it)
|
|
3217
|
-
break
|
|
3218
|
-
|
|
3219
|
-
response = requests.patch(url, headers=headers, json=operations_html, timeout=30)
|
|
3220
|
-
response.raise_for_status()
|
|
3221
|
-
else:
|
|
3222
|
-
# Other 400/422 errors - try HTML fallback
|
|
3223
|
-
console.print("[yellow]⚠ Markdown format not supported, converting to HTML[/yellow]")
|
|
3224
|
-
operations_html = [op for op in operations if "/multilineFieldsFormat/" not in op.get("path", "")]
|
|
3225
|
-
# Find description operation and convert markdown to HTML
|
|
3249
|
+
pass
|
|
3250
|
+
|
|
3251
|
+
if response is None:
|
|
3252
|
+
# Third: HTML fallback (no multilineFieldsFormat, description as HTML)
|
|
3253
|
+
import re as _re
|
|
3254
|
+
|
|
3255
|
+
console.print("[yellow]⚠ Markdown format not supported, converting description to HTML[/yellow]")
|
|
3256
|
+
operations_html = [
|
|
3257
|
+
op for op in operations if not (op.get("path") or "").startswith("/multilineFieldsFormat/")
|
|
3258
|
+
]
|
|
3259
|
+
description_field = reverse_mappings.get("description", "System.Description")
|
|
3260
|
+
desc_path = f"/fields/{description_field}"
|
|
3226
3261
|
for op in operations_html:
|
|
3227
|
-
if op.get("path") ==
|
|
3228
|
-
|
|
3229
|
-
import re
|
|
3230
|
-
|
|
3231
|
-
markdown_for_html = op["value"]
|
|
3232
|
-
# Convert TODO markers to checkboxes first
|
|
3262
|
+
if op.get("path") == desc_path:
|
|
3263
|
+
markdown_for_html = op.get("value") or ""
|
|
3233
3264
|
todo_pattern = r"^(\s*)[-*]\s*\[TODO[:\s]+([^\]]+)\](.*)$"
|
|
3234
|
-
markdown_for_html =
|
|
3265
|
+
markdown_for_html = _re.sub(
|
|
3235
3266
|
todo_pattern,
|
|
3236
3267
|
r"\1- [ ] \2",
|
|
3237
3268
|
markdown_for_html,
|
|
3238
|
-
flags=
|
|
3269
|
+
flags=_re.MULTILINE | _re.IGNORECASE,
|
|
3239
3270
|
)
|
|
3240
|
-
# Simple markdown to HTML conversion (basic)
|
|
3241
3271
|
try:
|
|
3242
3272
|
import markdown
|
|
3243
3273
|
|
|
3244
|
-
|
|
3245
|
-
op["value"] = html_body
|
|
3274
|
+
op["value"] = markdown.markdown(markdown_for_html, extensions=["fenced_code", "tables"])
|
|
3246
3275
|
except ImportError:
|
|
3247
|
-
|
|
3248
|
-
console.print("[yellow]⚠ markdown library not available, using raw text[/yellow]")
|
|
3249
|
-
# Keep original markdown as-is (ADO may still render it)
|
|
3276
|
+
pass
|
|
3250
3277
|
break
|
|
3278
|
+
try:
|
|
3279
|
+
resp = requests.patch(url, headers=headers, json=operations_html, timeout=30)
|
|
3280
|
+
resp.raise_for_status()
|
|
3281
|
+
response = resp
|
|
3282
|
+
except requests.HTTPError:
|
|
3283
|
+
raise
|
|
3251
3284
|
|
|
3252
|
-
|
|
3253
|
-
response.raise_for_status()
|
|
3254
|
-
else:
|
|
3285
|
+
if response is None:
|
|
3255
3286
|
raise
|
|
3256
3287
|
|
|
3257
3288
|
updated_work_item = response.json()
|
|
@@ -35,6 +35,7 @@ from specfact_cli.models.backlog_item import BacklogItem
|
|
|
35
35
|
from specfact_cli.models.bridge import BridgeConfig
|
|
36
36
|
from specfact_cli.models.capabilities import ToolCapabilities
|
|
37
37
|
from specfact_cli.models.change import ChangeProposal, ChangeTracking
|
|
38
|
+
from specfact_cli.runtime import debug_log_operation, is_debug_mode
|
|
38
39
|
from specfact_cli.utils.auth_tokens import get_token
|
|
39
40
|
|
|
40
41
|
|
|
@@ -817,6 +818,13 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
817
818
|
"Accept": "application/vnd.github.v3+json",
|
|
818
819
|
}
|
|
819
820
|
response = requests.get(url, headers=headers, timeout=30)
|
|
821
|
+
if is_debug_mode():
|
|
822
|
+
debug_log_operation(
|
|
823
|
+
"github_api_get",
|
|
824
|
+
url,
|
|
825
|
+
str(response.status_code),
|
|
826
|
+
error=None if response.ok else (response.text[:200] if response.text else None),
|
|
827
|
+
)
|
|
820
828
|
response.raise_for_status()
|
|
821
829
|
return response.json()
|
|
822
830
|
|
|
@@ -75,7 +75,7 @@ from specfact_cli.commands import (
|
|
|
75
75
|
validate,
|
|
76
76
|
)
|
|
77
77
|
from specfact_cli.modes import OperationalMode, detect_mode
|
|
78
|
-
from specfact_cli.runtime import get_configured_console, set_debug_mode
|
|
78
|
+
from specfact_cli.runtime import get_configured_console, init_debug_log_file, set_debug_mode
|
|
79
79
|
from specfact_cli.utils.progressive_disclosure import ProgressiveDisclosureGroup
|
|
80
80
|
from specfact_cli.utils.structured_io import StructuredFormat
|
|
81
81
|
|
|
@@ -246,7 +246,7 @@ def main(
|
|
|
246
246
|
debug: bool = typer.Option(
|
|
247
247
|
False,
|
|
248
248
|
"--debug",
|
|
249
|
-
help="Enable debug output (
|
|
249
|
+
help="Enable debug output: console diagnostics and log file at ~/.specfact/logs/specfact-debug.log (operation metadata for file I/O and API calls)",
|
|
250
250
|
),
|
|
251
251
|
skip_checks: bool = typer.Option(
|
|
252
252
|
False,
|
|
@@ -297,6 +297,8 @@ def main(
|
|
|
297
297
|
|
|
298
298
|
# Set debug mode
|
|
299
299
|
set_debug_mode(debug)
|
|
300
|
+
if debug:
|
|
301
|
+
init_debug_log_file()
|
|
300
302
|
|
|
301
303
|
runtime.configure_io_formats(input_format=input_format, output_format=output_format)
|
|
302
304
|
# Invert logic: --interactive means not non-interactive, --no-interactive means non-interactive
|
|
@@ -17,6 +17,7 @@ from rich.console import Console
|
|
|
17
17
|
from rich.table import Table
|
|
18
18
|
|
|
19
19
|
from specfact_cli.models.quality import CodeQuality, QualityTracking
|
|
20
|
+
from specfact_cli.runtime import debug_log_operation, debug_print, is_debug_mode
|
|
20
21
|
from specfact_cli.telemetry import telemetry
|
|
21
22
|
from specfact_cli.utils import print_error, print_success
|
|
22
23
|
from specfact_cli.utils.progress import load_bundle_with_progress
|
|
@@ -63,12 +64,23 @@ def analyze_contracts(
|
|
|
63
64
|
**Examples:**
|
|
64
65
|
specfact analyze contracts --repo . --bundle legacy-api
|
|
65
66
|
"""
|
|
67
|
+
if is_debug_mode():
|
|
68
|
+
debug_log_operation("command", "analyze contracts", "started", extra={"repo": str(repo), "bundle": bundle})
|
|
69
|
+
debug_print("[dim]analyze contracts: started[/dim]")
|
|
66
70
|
console = Console()
|
|
67
71
|
|
|
68
72
|
# Use active plan as default if bundle not provided
|
|
69
73
|
if bundle is None:
|
|
70
74
|
bundle = SpecFactStructure.get_active_bundle_name(repo)
|
|
71
75
|
if bundle is None:
|
|
76
|
+
if is_debug_mode():
|
|
77
|
+
debug_log_operation(
|
|
78
|
+
"command",
|
|
79
|
+
"analyze contracts",
|
|
80
|
+
"failed",
|
|
81
|
+
error="Bundle name required",
|
|
82
|
+
extra={"reason": "no_bundle"},
|
|
83
|
+
)
|
|
72
84
|
console.print("[bold red]✗[/bold red] Bundle name required")
|
|
73
85
|
console.print("[yellow]→[/yellow] Use --bundle option or run 'specfact plan select' to set active plan")
|
|
74
86
|
raise typer.Exit(1)
|
|
@@ -78,6 +90,14 @@ def analyze_contracts(
|
|
|
78
90
|
bundle_dir = SpecFactStructure.project_dir(base_path=repo_path, bundle_name=bundle)
|
|
79
91
|
|
|
80
92
|
if not bundle_dir.exists():
|
|
93
|
+
if is_debug_mode():
|
|
94
|
+
debug_log_operation(
|
|
95
|
+
"command",
|
|
96
|
+
"analyze contracts",
|
|
97
|
+
"failed",
|
|
98
|
+
error=f"Bundle not found: {bundle_dir}",
|
|
99
|
+
extra={"reason": "bundle_missing"},
|
|
100
|
+
)
|
|
81
101
|
print_error(f"Project bundle not found: {bundle_dir}")
|
|
82
102
|
raise typer.Exit(1)
|
|
83
103
|
|
|
@@ -200,6 +220,14 @@ def analyze_contracts(
|
|
|
200
220
|
"files_with_crosshair": files_with_crosshair,
|
|
201
221
|
}
|
|
202
222
|
)
|
|
223
|
+
if is_debug_mode():
|
|
224
|
+
debug_log_operation(
|
|
225
|
+
"command",
|
|
226
|
+
"analyze contracts",
|
|
227
|
+
"success",
|
|
228
|
+
extra={"files_analyzed": files_analyzed, "bundle": bundle},
|
|
229
|
+
)
|
|
230
|
+
debug_print("[dim]analyze contracts: success[/dim]")
|
|
203
231
|
|
|
204
232
|
|
|
205
233
|
def _analyze_file_quality(file_path: Path) -> CodeQuality:
|