specfact-cli 0.46.0__tar.gz → 0.46.2__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.46.0 → specfact_cli-0.46.2}/PKG-INFO +16 -5
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/README.md +15 -4
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/pyproject.toml +5 -4
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/__init__.py +1 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/__init__.py +1 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/ado.py +9 -9
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/github.py +22 -3
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/speckit.py +14 -11
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/code_analyzer.py +40 -15
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/graph_analyzer.py +23 -26
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/logger_setup.py +2 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/persona_exporter.py +2 -2
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/test_to_openapi.py +13 -7
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/module-package.yaml +3 -3
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/src/first_run_selection.py +1 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_installer.py +3 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_watch.py +6 -2
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/source_scanner.py +0 -4
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/cli_first_validator.py +20 -12
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/crosshair_runner.py +5 -1
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/crosshair_summary.py +2 -2
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +11 -6
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/.gitignore +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/LICENSE +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/keys/README.md +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/importers/speckit_scanner.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/bridge.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/capabilities.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/src/commands.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_lifecycle.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/ide_setup.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/project_artifact_write.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/startup_checks.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.46.0 → specfact_cli-0.46.2}/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.46.
|
|
3
|
+
Version: 0.46.2
|
|
4
4
|
Summary: The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases.
|
|
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
|
|
@@ -308,7 +308,7 @@ uvx specfact-cli code review run --path . --scope full
|
|
|
308
308
|
**Sample output:**
|
|
309
309
|
|
|
310
310
|
```text
|
|
311
|
-
SpecFact CLI - v0.46.
|
|
311
|
+
SpecFact CLI - v0.46.1
|
|
312
312
|
|
|
313
313
|
Running Ruff checks...
|
|
314
314
|
Running Radon complexity checks...
|
|
@@ -361,16 +361,27 @@ It exists because delivery drifts in predictable ways:
|
|
|
361
361
|
|
|
362
362
|
## Add SpecFact to your workflow
|
|
363
363
|
|
|
364
|
-
|
|
364
|
+
### Pre-commit hook
|
|
365
|
+
|
|
366
|
+
This repository uses a **modular** local hook layout (parity with `specfact-cli-modules`: `fail_fast`,
|
|
367
|
+
separate verify / format / YAML / Markdown / workflow / lint / Block 2 hooks). If you copy
|
|
368
|
+
[`.pre-commit-config.yaml`](.pre-commit-config.yaml) into another repo, you must also vendor the
|
|
369
|
+
referenced `scripts/*.sh` entrypoints (at minimum `scripts/pre-commit-quality-checks.sh`,
|
|
370
|
+
`scripts/pre-commit-verify-modules.sh`, and `scripts/git-branch-module-signature-flag.sh`) so hook
|
|
371
|
+
`entry:` paths resolve. Alternatively, skip vendoring the modular file and use the remote hook below.
|
|
372
|
+
|
|
373
|
+
For a **single-hook** setup in downstream repos, keep using the stable id and script shim:
|
|
365
374
|
|
|
366
375
|
```yaml
|
|
367
376
|
- repo: https://github.com/nold-ai/specfact-cli
|
|
368
|
-
rev: v0.46.
|
|
377
|
+
rev: v0.46.1
|
|
369
378
|
hooks:
|
|
370
379
|
- id: specfact-smart-checks
|
|
371
380
|
```
|
|
372
381
|
|
|
373
|
-
|
|
382
|
+
The shim runs `scripts/pre-commit-quality-checks.sh all` (full pipeline including module verify).
|
|
383
|
+
|
|
384
|
+
### GitHub Actions
|
|
374
385
|
|
|
375
386
|
```yaml
|
|
376
387
|
- name: SpecFact Gate
|
|
@@ -27,7 +27,7 @@ uvx specfact-cli code review run --path . --scope full
|
|
|
27
27
|
**Sample output:**
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
|
-
SpecFact CLI - v0.46.
|
|
30
|
+
SpecFact CLI - v0.46.1
|
|
31
31
|
|
|
32
32
|
Running Ruff checks...
|
|
33
33
|
Running Radon complexity checks...
|
|
@@ -80,16 +80,27 @@ It exists because delivery drifts in predictable ways:
|
|
|
80
80
|
|
|
81
81
|
## Add SpecFact to your workflow
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
### Pre-commit hook
|
|
84
|
+
|
|
85
|
+
This repository uses a **modular** local hook layout (parity with `specfact-cli-modules`: `fail_fast`,
|
|
86
|
+
separate verify / format / YAML / Markdown / workflow / lint / Block 2 hooks). If you copy
|
|
87
|
+
[`.pre-commit-config.yaml`](.pre-commit-config.yaml) into another repo, you must also vendor the
|
|
88
|
+
referenced `scripts/*.sh` entrypoints (at minimum `scripts/pre-commit-quality-checks.sh`,
|
|
89
|
+
`scripts/pre-commit-verify-modules.sh`, and `scripts/git-branch-module-signature-flag.sh`) so hook
|
|
90
|
+
`entry:` paths resolve. Alternatively, skip vendoring the modular file and use the remote hook below.
|
|
91
|
+
|
|
92
|
+
For a **single-hook** setup in downstream repos, keep using the stable id and script shim:
|
|
84
93
|
|
|
85
94
|
```yaml
|
|
86
95
|
- repo: https://github.com/nold-ai/specfact-cli
|
|
87
|
-
rev: v0.46.
|
|
96
|
+
rev: v0.46.1
|
|
88
97
|
hooks:
|
|
89
98
|
- id: specfact-smart-checks
|
|
90
99
|
```
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
The shim runs `scripts/pre-commit-quality-checks.sh all` (full pipeline including module verify).
|
|
102
|
+
|
|
103
|
+
### GitHub Actions
|
|
93
104
|
|
|
94
105
|
```yaml
|
|
95
106
|
- name: SpecFact Gate
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.46.
|
|
7
|
+
version = "0.46.2"
|
|
8
8
|
description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -209,7 +209,8 @@ validate-prompts = "python tools/validate_prompts.py"
|
|
|
209
209
|
test = "pytest {args}"
|
|
210
210
|
test-cov = "pytest --cov=src --cov-report=term-missing {args}"
|
|
211
211
|
type-check = "basedpyright --pythonpath $(python -c 'import sys; print(sys.executable)') {args}"
|
|
212
|
-
|
|
212
|
+
# basedpyright --level error: suppress warning noise in pre-commit (Block 1 runs `hatch run lint`).
|
|
213
|
+
lint = "ruff format . --check && basedpyright --level error --pythonpath $(python -c 'import sys; print(sys.executable)') && ruff check . && pylint src tests tools && python scripts/verify_safe_project_writes.py"
|
|
213
214
|
governance = "pylint src tests tools --reports=y --output-format=parseable"
|
|
214
215
|
format = "ruff check . --fix && ruff format ."
|
|
215
216
|
|
|
@@ -236,7 +237,8 @@ check-cross-site-links = "python scripts/check-cross-site-links.py"
|
|
|
236
237
|
doc-frontmatter-check = "python scripts/check_doc_frontmatter.py"
|
|
237
238
|
validate-agent-rule-signals = "python scripts/validate_agent_rule_applies_when.py"
|
|
238
239
|
check-version-sources = "python scripts/check_version_sources.py"
|
|
239
|
-
|
|
240
|
+
check-pypi-ahead = "python scripts/check_local_version_ahead_of_pypi.py"
|
|
241
|
+
release = "python scripts/check_local_version_ahead_of_pypi.py && python scripts/check_version_sources.py"
|
|
240
242
|
docs-validate = "python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py && python scripts/validate_agent_rule_applies_when.py"
|
|
241
243
|
|
|
242
244
|
# Legacy entry (kept for compatibility); prefer `workflows-lint` above
|
|
@@ -286,7 +288,6 @@ contract-prune = "python tools/migrate_tests_to_contracts.py --prune --dry-run"
|
|
|
286
288
|
# Pre-commit hooks
|
|
287
289
|
pre-commit-install = "bash scripts/setup-git-hooks.sh"
|
|
288
290
|
pre-commit-checks = "bash scripts/pre-commit-smart-checks.sh"
|
|
289
|
-
pre-commit-test = "bash scripts/pre-commit-smart-test.sh"
|
|
290
291
|
|
|
291
292
|
[tool.hatch.envs.py311]
|
|
292
293
|
python = "3.11"
|
|
@@ -54,7 +54,7 @@ console = Console()
|
|
|
54
54
|
|
|
55
55
|
@dataclass(frozen=True, slots=True)
|
|
56
56
|
class _AdoCreatedWorkItemRef:
|
|
57
|
-
work_item_id:
|
|
57
|
+
work_item_id: int | str
|
|
58
58
|
work_item_url: str
|
|
59
59
|
org: str
|
|
60
60
|
project: str
|
|
@@ -744,7 +744,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
744
744
|
},
|
|
745
745
|
}
|
|
746
746
|
source_tracking = proposal_data.get("source_tracking")
|
|
747
|
-
if source_tracking
|
|
747
|
+
if isinstance(source_tracking, list):
|
|
748
|
+
cast(list[dict[str, Any]], source_tracking).append(tracking_update)
|
|
749
|
+
return
|
|
750
|
+
if source_tracking is None or (isinstance(source_tracking, dict) and len(source_tracking) == 0):
|
|
748
751
|
proposal_data["source_tracking"] = tracking_update
|
|
749
752
|
return
|
|
750
753
|
if isinstance(source_tracking, dict):
|
|
@@ -752,11 +755,6 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
752
755
|
st.update(tracking_update)
|
|
753
756
|
proposal_data["source_tracking"] = st
|
|
754
757
|
return
|
|
755
|
-
if isinstance(source_tracking, list):
|
|
756
|
-
if not source_tracking:
|
|
757
|
-
proposal_data["source_tracking"] = [tracking_update]
|
|
758
|
-
return
|
|
759
|
-
cast(list[dict[str, Any]], source_tracking).append(tracking_update)
|
|
760
758
|
|
|
761
759
|
def _is_on_premise(self) -> bool:
|
|
762
760
|
"""
|
|
@@ -2270,8 +2268,10 @@ class AdoAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
2270
2268
|
Returns:
|
|
2271
2269
|
Dict with updated work item data: {"work_item_id": int, "work_item_url": str, "state": str}
|
|
2272
2270
|
"""
|
|
2273
|
-
|
|
2274
|
-
|
|
2271
|
+
work_item_id = self._get_source_tracking_work_item_id(
|
|
2272
|
+
proposal_data.get("source_tracking", {}),
|
|
2273
|
+
f"{org}/{project}",
|
|
2274
|
+
)
|
|
2275
2275
|
ado_state = self._resolve_proposal_ado_state(proposal_data)
|
|
2276
2276
|
work_item_data = self._patch_work_item(
|
|
2277
2277
|
org,
|
|
@@ -159,7 +159,11 @@ def _get_github_token_from_gh_cli() -> str | None:
|
|
|
159
159
|
return None
|
|
160
160
|
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
# Line-anchored so ``pushurl =`` / ``insteadOf`` lines do not match the ``url`` token inside another key.
|
|
163
|
+
# Matches: https://, http://, ssh://, git://, and git@host:path remotes.
|
|
164
|
+
_GITHUB_GIT_CONFIG_URL_RE = re.compile(
|
|
165
|
+
r"(?m)^\s*url\s*=\s*(https?://[^\s]+|ssh://[^\s]+|git://[^\s]+|git@[^:]+:[^\s]+)"
|
|
166
|
+
)
|
|
163
167
|
|
|
164
168
|
|
|
165
169
|
def _git_config_content_indicates_github(config_content: str) -> bool:
|
|
@@ -1471,7 +1475,14 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1471
1475
|
title = raw_title
|
|
1472
1476
|
|
|
1473
1477
|
body = self._render_issue_body(
|
|
1474
|
-
_IssueBodyRenderInput(
|
|
1478
|
+
_IssueBodyRenderInput(
|
|
1479
|
+
title=title,
|
|
1480
|
+
description=description,
|
|
1481
|
+
rationale=rationale,
|
|
1482
|
+
impact=impact,
|
|
1483
|
+
change_id=change_id,
|
|
1484
|
+
raw_body=raw_body,
|
|
1485
|
+
)
|
|
1475
1486
|
)
|
|
1476
1487
|
|
|
1477
1488
|
# Check for API token before making request
|
|
@@ -1735,7 +1746,15 @@ class GitHubAdapter(BridgeAdapter, BacklogAdapterMixin, BacklogAdapter):
|
|
|
1735
1746
|
current_body, current_title, current_state = self._fetch_issue_snapshot(repo_owner, repo_name, issue_number)
|
|
1736
1747
|
preserved_sections = self._preserved_issue_sections(current_body, change_id)
|
|
1737
1748
|
body = self._render_issue_body(
|
|
1738
|
-
_IssueBodyRenderInput(
|
|
1749
|
+
_IssueBodyRenderInput(
|
|
1750
|
+
title=title,
|
|
1751
|
+
description=description,
|
|
1752
|
+
rationale=rationale,
|
|
1753
|
+
impact=impact,
|
|
1754
|
+
change_id=change_id,
|
|
1755
|
+
raw_body=raw_body,
|
|
1756
|
+
preserved_sections=preserved_sections,
|
|
1757
|
+
)
|
|
1739
1758
|
)
|
|
1740
1759
|
|
|
1741
1760
|
# Update issue body via GitHub API PATCH
|
|
@@ -462,15 +462,19 @@ class SpecKitAdapter(BridgeAdapter):
|
|
|
462
462
|
story_title = sd.get("title", "Unknown Story")
|
|
463
463
|
priority = sd.get("priority", "P3")
|
|
464
464
|
acceptance_raw = sd.get("acceptance", [])
|
|
465
|
-
default_acceptance = [f"{story_title} is implemented"]
|
|
466
465
|
if isinstance(acceptance_raw, list) and acceptance_raw:
|
|
467
466
|
if all(isinstance(x, str) for x in acceptance_raw):
|
|
468
|
-
acceptance =
|
|
467
|
+
acceptance = [s.strip() for s in acceptance_raw if isinstance(s, str) and s.strip()]
|
|
469
468
|
else:
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
acceptance = [
|
|
470
|
+
s.strip() for s in self._extract_text_list(cast(list[Any], acceptance_raw)) if s.strip()
|
|
471
|
+
]
|
|
472
|
+
if not acceptance:
|
|
473
|
+
acceptance = [f"{story_title} is implemented"]
|
|
474
|
+
if not acceptance:
|
|
475
|
+
acceptance = [f"{story_title} is implemented"]
|
|
472
476
|
else:
|
|
473
|
-
acceptance =
|
|
477
|
+
acceptance = [f"{story_title} is implemented"]
|
|
474
478
|
story_points = priority_map.get(str(priority), 3)
|
|
475
479
|
stories.append(
|
|
476
480
|
Story(
|
|
@@ -548,14 +552,13 @@ class SpecKitAdapter(BridgeAdapter):
|
|
|
548
552
|
|
|
549
553
|
if existing_feature:
|
|
550
554
|
existing_feature.title = payload.feature_title
|
|
551
|
-
existing_feature.outcomes = (
|
|
552
|
-
|
|
553
|
-
)
|
|
554
|
-
existing_feature.acceptance = (
|
|
555
|
-
list(payload.acceptance) if payload.acceptance else list(existing_feature.acceptance or [])
|
|
556
|
-
)
|
|
555
|
+
existing_feature.outcomes = list(payload.outcomes)
|
|
556
|
+
existing_feature.acceptance = list(payload.acceptance)
|
|
557
557
|
existing_feature.stories = [s.model_copy(deep=True) for s in payload.stories]
|
|
558
558
|
existing_feature.constraints = list(constraints)
|
|
559
|
+
existing_feature.source_tracking = self._build_speckit_source_tracking(
|
|
560
|
+
payload.spec_path, payload.bridge_config
|
|
561
|
+
)
|
|
559
562
|
return
|
|
560
563
|
|
|
561
564
|
feature = Feature(
|
|
@@ -738,19 +738,8 @@ class CodeAnalyzer:
|
|
|
738
738
|
@staticmethod
|
|
739
739
|
def _themes_for_import_module(module_name: str, theme_keywords: dict[str, str]) -> set[str]:
|
|
740
740
|
lowered = module_name.lower()
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
found: set[str] = set()
|
|
744
|
-
for keyword, theme in theme_keywords.items():
|
|
745
|
-
if keyword == lowered or lowered.startswith(f"{keyword}."):
|
|
746
|
-
found.add(theme)
|
|
747
|
-
continue
|
|
748
|
-
if keyword == top_level or top_level.startswith(f"{keyword}."):
|
|
749
|
-
found.add(theme)
|
|
750
|
-
continue
|
|
751
|
-
if keyword in tokens:
|
|
752
|
-
found.add(theme)
|
|
753
|
-
return found
|
|
741
|
+
segments = {p for p in lowered.replace("-", ".").split(".") if p}
|
|
742
|
+
return {theme for keyword, theme in theme_keywords.items() if keyword in segments}
|
|
754
743
|
|
|
755
744
|
def _themes_for_import_node(self, node: ast.Import | ast.ImportFrom, theme_keywords: dict[str, str]) -> set[str]:
|
|
756
745
|
if isinstance(node, ast.Import):
|
|
@@ -1745,14 +1734,50 @@ class CodeAnalyzer:
|
|
|
1745
1734
|
self.async_patterns[module_name].extend(async_methods)
|
|
1746
1735
|
return async_methods
|
|
1747
1736
|
|
|
1748
|
-
|
|
1737
|
+
@staticmethod
|
|
1738
|
+
def _build_ast_parent_map(tree: ast.AST) -> dict[ast.AST, ast.AST | None]:
|
|
1739
|
+
parents: dict[ast.AST, ast.AST | None] = {}
|
|
1740
|
+
|
|
1741
|
+
def visit(node: ast.AST, parent: ast.AST | None) -> None:
|
|
1742
|
+
parents[node] = parent
|
|
1743
|
+
for child in ast.iter_child_nodes(node):
|
|
1744
|
+
visit(child, node)
|
|
1745
|
+
|
|
1746
|
+
visit(tree, None)
|
|
1747
|
+
return parents
|
|
1748
|
+
|
|
1749
|
+
@staticmethod
|
|
1750
|
+
def _function_name_holding_await(parents: dict[ast.AST, ast.AST | None], await_node: ast.Await) -> str | None:
|
|
1751
|
+
current: ast.AST | None = await_node
|
|
1752
|
+
while current is not None:
|
|
1753
|
+
par = parents.get(current)
|
|
1754
|
+
if par is None:
|
|
1755
|
+
return None
|
|
1756
|
+
if isinstance(par, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
1757
|
+
return par.name
|
|
1758
|
+
current = par
|
|
1759
|
+
return None
|
|
1760
|
+
|
|
1761
|
+
def _detect_async_patterns_parallel(self, tree: ast.AST, _file_path: Path) -> list[str]:
|
|
1749
1762
|
"""
|
|
1750
1763
|
Detect async/await patterns in code (thread-safe version).
|
|
1751
1764
|
|
|
1752
1765
|
Returns:
|
|
1753
1766
|
List of async method/function names
|
|
1754
1767
|
"""
|
|
1755
|
-
|
|
1768
|
+
async_methods: list[str] = []
|
|
1769
|
+
parents = self._build_ast_parent_map(tree)
|
|
1770
|
+
|
|
1771
|
+
for node in ast.walk(tree):
|
|
1772
|
+
if isinstance(node, ast.AsyncFunctionDef):
|
|
1773
|
+
async_methods.append(node.name)
|
|
1774
|
+
if not isinstance(node, ast.Await):
|
|
1775
|
+
continue
|
|
1776
|
+
host = self._function_name_holding_await(parents, node)
|
|
1777
|
+
if host and host not in async_methods:
|
|
1778
|
+
async_methods.append(host)
|
|
1779
|
+
|
|
1780
|
+
return async_methods
|
|
1756
1781
|
|
|
1757
1782
|
def _apply_commit_hash_to_matching_features(self, feature_num: str, commit_hash: str) -> None:
|
|
1758
1783
|
for feature in self.features:
|
|
@@ -191,22 +191,12 @@ class GraphAnalyzer:
|
|
|
191
191
|
graph: StrDiGraph,
|
|
192
192
|
module_name: str,
|
|
193
193
|
call_graph: dict[str, list[str]],
|
|
194
|
-
|
|
194
|
+
loaded_contents: list[tuple[str, str]],
|
|
195
195
|
) -> None:
|
|
196
|
-
"""Add
|
|
197
|
-
|
|
198
|
-
Args:
|
|
199
|
-
graph: Dependency graph being populated.
|
|
200
|
-
module_name: Source module name for emitted edges.
|
|
201
|
-
call_graph: Mapping of caller symbols to callee symbol lists.
|
|
202
|
-
python_files: Repository Python files used for callee module resolution.
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
None.
|
|
206
|
-
"""
|
|
196
|
+
"""Add directed edges from ``module_name`` to callees that resolve to known graph nodes."""
|
|
207
197
|
for _caller, callees in call_graph.items():
|
|
208
198
|
for callee in callees:
|
|
209
|
-
callee_module = self.
|
|
199
|
+
callee_module = self._resolve_module_from_function_preloaded(callee, loaded_contents)
|
|
210
200
|
if callee_module is None or callee_module not in graph:
|
|
211
201
|
continue
|
|
212
202
|
graph.add_edge(module_name, callee_module)
|
|
@@ -222,6 +212,7 @@ class GraphAnalyzer:
|
|
|
222
212
|
"""Populate graph with edges derived from pyan call graphs (parallel phase 2)."""
|
|
223
213
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
224
214
|
|
|
215
|
+
loaded_contents = self._load_python_file_contents_index(python_files)
|
|
225
216
|
executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
226
217
|
completed = 0
|
|
227
218
|
try:
|
|
@@ -231,7 +222,7 @@ class GraphAnalyzer:
|
|
|
231
222
|
try:
|
|
232
223
|
call_graph = future.result()
|
|
233
224
|
module_name = self._path_to_module_name(file_path)
|
|
234
|
-
self._add_call_graph_edges_for_module(graph, module_name, call_graph,
|
|
225
|
+
self._add_call_graph_edges_for_module(graph, module_name, call_graph, loaded_contents)
|
|
235
226
|
except (OSError, RuntimeError):
|
|
236
227
|
pass
|
|
237
228
|
completed += 1
|
|
@@ -443,24 +434,30 @@ class GraphAnalyzer:
|
|
|
443
434
|
return self._find_matching_module_suffix_overlap(imported, known_modules)
|
|
444
435
|
|
|
445
436
|
@beartype
|
|
446
|
-
@require(lambda function_name: isinstance(function_name, str), "Function name must be str")
|
|
447
437
|
@require(lambda python_files: isinstance(python_files, list), "Python files must be list")
|
|
448
|
-
@ensure(lambda result:
|
|
449
|
-
def
|
|
450
|
-
"""
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
This is a heuristic - tries to find the module containing the function.
|
|
454
|
-
"""
|
|
455
|
-
# Simple heuristic: search for function name in files
|
|
438
|
+
@ensure(lambda result: isinstance(result, list), "Must return list")
|
|
439
|
+
def _load_python_file_contents_index(self, python_files: list[Path]) -> list[tuple[str, str]]:
|
|
440
|
+
"""Preload (module_name, source_text) pairs once per graph build for callee resolution."""
|
|
441
|
+
loaded: list[tuple[str, str]] = []
|
|
456
442
|
for file_path in python_files:
|
|
457
443
|
try:
|
|
458
444
|
content = file_path.read_text(encoding="utf-8")
|
|
459
|
-
|
|
460
|
-
return self._path_to_module_name(file_path)
|
|
461
|
-
except (UnicodeDecodeError, Exception):
|
|
445
|
+
except (OSError, UnicodeDecodeError):
|
|
462
446
|
continue
|
|
447
|
+
loaded.append((self._path_to_module_name(file_path), content))
|
|
448
|
+
return loaded
|
|
463
449
|
|
|
450
|
+
@beartype
|
|
451
|
+
@require(lambda function_name: isinstance(function_name, str), "Function name must be str")
|
|
452
|
+
@require(lambda loaded_contents: isinstance(loaded_contents, list), "Loaded contents must be list")
|
|
453
|
+
@ensure(lambda result: result is None or isinstance(result, str), "Must return None or str")
|
|
454
|
+
def _resolve_module_from_function_preloaded(
|
|
455
|
+
self, function_name: str, loaded_contents: list[tuple[str, str]]
|
|
456
|
+
) -> str | None:
|
|
457
|
+
"""Resolve module for ``function_name`` using preloaded sources (same heuristic as one-file scan)."""
|
|
458
|
+
for module_name, content in loaded_contents:
|
|
459
|
+
if f"def {function_name}" in content or f"class {function_name}" in content:
|
|
460
|
+
return module_name
|
|
464
461
|
return None
|
|
465
462
|
|
|
466
463
|
@beartype
|
|
@@ -283,7 +283,7 @@ class MessageFlowFormatter(logging.Formatter):
|
|
|
283
283
|
return formatted_message
|
|
284
284
|
|
|
285
285
|
|
|
286
|
-
@dataclass
|
|
286
|
+
@dataclass(slots=True)
|
|
287
287
|
class _FileOutputPipelineConfig:
|
|
288
288
|
logger_name: str
|
|
289
289
|
logger: logging.Logger
|
|
@@ -294,6 +294,7 @@ class _FileOutputPipelineConfig:
|
|
|
294
294
|
append_mode: bool
|
|
295
295
|
|
|
296
296
|
|
|
297
|
+
@beartype
|
|
297
298
|
@dataclass
|
|
298
299
|
class LoggerCreateOptions:
|
|
299
300
|
"""Options for :meth:`LoggerSetup.create_logger`."""
|
|
@@ -203,10 +203,10 @@ class PersonaExporter:
|
|
|
203
203
|
return
|
|
204
204
|
if spec.use_getattr:
|
|
205
205
|
val = getattr(feature, spec.field_name, None)
|
|
206
|
-
if val:
|
|
206
|
+
if val is not None:
|
|
207
207
|
feature_dict[spec.field_name] = val
|
|
208
208
|
return
|
|
209
|
-
if spec.value:
|
|
209
|
+
if spec.value is not None:
|
|
210
210
|
feature_dict[spec.field_name] = spec.value
|
|
211
211
|
|
|
212
212
|
def _merge_feature_optional_sections(
|
|
@@ -359,13 +359,7 @@ class OpenAPITestConverter:
|
|
|
359
359
|
if isinstance(node, ast.Constant):
|
|
360
360
|
return node.value
|
|
361
361
|
if isinstance(node, ast.Dict):
|
|
362
|
-
|
|
363
|
-
for k, v in zip(node.keys, node.values, strict=True):
|
|
364
|
-
key = self._extract_ast_value(k) if k else None
|
|
365
|
-
val = self._extract_ast_value(v)
|
|
366
|
-
if key is not None:
|
|
367
|
-
result[str(key)] = val
|
|
368
|
-
return result
|
|
362
|
+
return self._coerce_ast_dict_to_plain(node)
|
|
369
363
|
if isinstance(node, ast.List):
|
|
370
364
|
return [self._extract_ast_value(item) for item in node.elts]
|
|
371
365
|
if isinstance(node, ast.Name):
|
|
@@ -380,6 +374,18 @@ class OpenAPITestConverter:
|
|
|
380
374
|
return None
|
|
381
375
|
|
|
382
376
|
def _examples_from_parsed_test_file(self, tree: ast.AST, func_name: str | None) -> dict[str, dict[str, Any]]:
|
|
377
|
+
"""Collect OpenAPI-style examples from test functions in a parsed module.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
tree: Parsed AST for a test module.
|
|
381
|
+
func_name: When set, only this ``test_*`` function is scanned; otherwise every
|
|
382
|
+
``test_*`` definition is considered.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
``operation_id`` → merged example payload. Keys default to ``\"unknown\"`` when
|
|
386
|
+
``operation_id`` is missing. Later ``test_*`` blocks win on duplicate keys inside
|
|
387
|
+
the merged dict for the same operation.
|
|
388
|
+
"""
|
|
383
389
|
by_operation: dict[str, dict[str, Any]] = {}
|
|
384
390
|
for node in ast.walk(tree):
|
|
385
391
|
if not isinstance(node, ast.FunctionDef):
|
{specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/module-package.yaml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: init
|
|
2
|
-
version: 0.1.
|
|
2
|
+
version: 0.1.30
|
|
3
3
|
commands:
|
|
4
4
|
- init
|
|
5
5
|
category: core
|
|
@@ -17,5 +17,5 @@ publisher:
|
|
|
17
17
|
description: Initialize SpecFact workspace and bootstrap local configuration.
|
|
18
18
|
license: Apache-2.0
|
|
19
19
|
integrity:
|
|
20
|
-
checksum: sha256:
|
|
21
|
-
signature:
|
|
20
|
+
checksum: sha256:9e03421972d3254082307834b474e7673957de8c11ffacc563f2da3f35e7cf05
|
|
21
|
+
signature: ikUYhUJ8AFU9RkB+ZBnp0lKrDLT7ZIqkauRYUMRY/swrIjRCTRzHIpNcvCmujK6h1w6EtT/+wGG1v5bZtZB8CQ==
|
{specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/modules/init/src/first_run_selection.py
RENAMED
|
@@ -184,7 +184,7 @@ def _install_marketplace_for_bundle(bid: str, marketplace_id: str, deps: _InitBu
|
|
|
184
184
|
try:
|
|
185
185
|
deps.install_module(
|
|
186
186
|
marketplace_id,
|
|
187
|
-
InstallModuleOptions(
|
|
187
|
+
options=InstallModuleOptions(
|
|
188
188
|
install_root=deps.root,
|
|
189
189
|
non_interactive=deps.non_interactive,
|
|
190
190
|
trust_non_official=deps.trust_non_official,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import hashlib
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
8
9
|
import shutil
|
|
@@ -40,6 +41,7 @@ from specfact_cli.runtime import is_debug_mode
|
|
|
40
41
|
USER_MODULES_ROOT = Path.home() / ".specfact" / "modules"
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
@beartype
|
|
43
45
|
@dataclass(slots=True)
|
|
44
46
|
class InstallModuleOptions:
|
|
45
47
|
"""Options for :func:`install_module`."""
|
|
@@ -61,7 +63,7 @@ class _BundleDepsInstallContext:
|
|
|
61
63
|
trust_non_official: bool
|
|
62
64
|
non_interactive: bool
|
|
63
65
|
force: bool
|
|
64
|
-
logger:
|
|
66
|
+
logger: logging.Logger
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
MARKETPLACE_MODULES_ROOT = Path.home() / ".specfact" / "marketplace-modules"
|
|
@@ -32,10 +32,14 @@ def _match_feature_id_from_pattern_parts(pattern_parts: list[str], file_parts: l
|
|
|
32
32
|
feature_id_index = pattern_parts.index("{feature_id}")
|
|
33
33
|
except ValueError:
|
|
34
34
|
return None
|
|
35
|
+
if len(file_parts) != len(pattern_parts):
|
|
36
|
+
return None
|
|
35
37
|
if feature_id_index >= len(file_parts):
|
|
36
38
|
return None
|
|
37
|
-
for i in
|
|
38
|
-
if
|
|
39
|
+
for i, part in enumerate(pattern_parts):
|
|
40
|
+
if part == "{feature_id}":
|
|
41
|
+
continue
|
|
42
|
+
if i >= len(file_parts) or file_parts[i] != part:
|
|
39
43
|
return None
|
|
40
44
|
return file_parts[feature_id_index]
|
|
41
45
|
|
|
@@ -117,8 +117,6 @@ class SourceArtifactMap:
|
|
|
117
117
|
@dataclass(slots=True)
|
|
118
118
|
class _FeatureLinkingContext:
|
|
119
119
|
repo_path: Path
|
|
120
|
-
impl_files: list[Path]
|
|
121
|
-
test_files: list[Path]
|
|
122
120
|
file_functions_cache: dict[str, list[str]]
|
|
123
121
|
file_test_functions_cache: dict[str, list[str]]
|
|
124
122
|
file_hashes_cache: dict[str, str]
|
|
@@ -436,8 +434,6 @@ class SourceArtifactScanner:
|
|
|
436
434
|
|
|
437
435
|
linking_ctx = _FeatureLinkingContext(
|
|
438
436
|
repo_path=repo_path,
|
|
439
|
-
impl_files=impl_files,
|
|
440
|
-
test_files=test_files,
|
|
441
437
|
file_functions_cache=file_functions_cache,
|
|
442
438
|
file_test_functions_cache=file_test_functions_cache,
|
|
443
439
|
file_hashes_cache=file_hashes_cache,
|
{specfact_cli-0.46.0 → specfact_cli-0.46.2}/src/specfact_cli/validators/cli_first_validator.py
RENAMED
|
@@ -188,21 +188,29 @@ def detect_direct_manipulation(specfact_dir: Path) -> list[Path]:
|
|
|
188
188
|
|
|
189
189
|
# Check project bundles
|
|
190
190
|
projects_dir = specfact_dir / "projects"
|
|
191
|
-
if not projects_dir.
|
|
191
|
+
if not projects_dir.is_dir():
|
|
192
192
|
return suspicious_files
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if manifest_file.exists() and not is_cli_generated(manifest_file):
|
|
199
|
-
suspicious_files.append(manifest_file)
|
|
194
|
+
try:
|
|
195
|
+
bundle_iter = list(projects_dir.iterdir())
|
|
196
|
+
except OSError:
|
|
197
|
+
return suspicious_files
|
|
200
198
|
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
for bundle_dir in bundle_iter:
|
|
200
|
+
try:
|
|
201
|
+
if not bundle_dir.is_dir():
|
|
202
|
+
continue
|
|
203
|
+
manifest_file = bundle_dir / "bundle.manifest.yaml"
|
|
204
|
+
if manifest_file.exists() and not is_cli_generated(manifest_file):
|
|
205
|
+
suspicious_files.append(manifest_file)
|
|
206
|
+
|
|
207
|
+
features_dir = bundle_dir / "features"
|
|
208
|
+
if not features_dir.is_dir():
|
|
209
|
+
continue
|
|
210
|
+
for feature_file in features_dir.glob("*.yaml"):
|
|
211
|
+
if not is_cli_generated(feature_file):
|
|
212
|
+
suspicious_files.append(feature_file)
|
|
213
|
+
except OSError:
|
|
203
214
|
continue
|
|
204
|
-
for feature_file in features_dir.glob("*.yaml"):
|
|
205
|
-
if not is_cli_generated(feature_file):
|
|
206
|
-
suspicious_files.append(feature_file)
|
|
207
215
|
|
|
208
216
|
return suspicious_files
|