specfact-cli 0.42.6__tar.gz → 0.43.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.42.6 → specfact_cli-0.43.2}/PKG-INFO +6 -2
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/README.md +5 -1
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/pyproject.toml +15 -2
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/__init__.py +1 -1
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/__init__.py +4 -1
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/speckit.py +89 -23
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/importers/speckit_scanner.py +123 -1
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/bridge.py +66 -3
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/capabilities.py +6 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/init/module-package.yaml +3 -3
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/init/src/commands.py +3 -4
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_lifecycle.py +5 -3
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/ide_setup.py +4 -2
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/startup_checks.py +53 -32
- specfact_cli-0.42.6/resources/prompts/shared/cli-enforcement.md +0 -119
- specfact_cli-0.42.6/resources/prompts/specfact.01-import.md +0 -263
- specfact_cli-0.42.6/resources/prompts/specfact.02-plan.md +0 -177
- specfact_cli-0.42.6/resources/prompts/specfact.03-review.md +0 -714
- specfact_cli-0.42.6/resources/prompts/specfact.04-sdd.md +0 -160
- specfact_cli-0.42.6/resources/prompts/specfact.05-enforce.md +0 -166
- specfact_cli-0.42.6/resources/prompts/specfact.06-sync.md +0 -202
- specfact_cli-0.42.6/resources/prompts/specfact.07-contracts.md +0 -364
- specfact_cli-0.42.6/resources/prompts/specfact.compare.md +0 -159
- specfact_cli-0.42.6/resources/prompts/specfact.validate.md +0 -166
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/.gitignore +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/LICENSE +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/keys/README.md +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/keys/module-signing-public.pem +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/mappings/node-async.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/mappings/python-async.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/mappings/speckit-default.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/schemas/deviation.schema.json +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/schemas/plan.schema.json +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/schemas/protocol.schema.json +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/github-action.yml.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/persona/architect.md.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/persona/developer.md.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/persona/product-owner.md.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/plan.bundle.yaml.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/policies/kanban.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/policies/mixed.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/policies/safe.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/policies/scrum.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/pr-template.md.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/protocol.yaml.j2 +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/resources/templates/telemetry.yaml.example +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/__main__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/ado.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/backlog_base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/github.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/openspec.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/openspec_parser.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/adapters/registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/analyze_agent.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/plan_agent.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/agents/sync_agent.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/ambiguity_scanner.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/code_analyzer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/constitution_evidence_extractor.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/contract_extractor.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/control_flow_analyzer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/graph_analyzer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/relationship_mapper.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/requirement_extractor.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/analyzers/test_pattern_extractor.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/adapters/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/adapters/base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/converter.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/filters.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/mappers/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/mappers/ado_mapper.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/mappers/base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/mappers/github_mapper.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/backlog/mappers/template_config.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/cli.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/_bundle_shim.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/analyze.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/contract_cmd.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/drift.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/enforce.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/generate.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/import_cmd.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/init.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/migrate.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/plan.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/project_cmd.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/repro.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/sdd.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/spec.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/sync.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/update.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/commands/validate.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/bundle_factory.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/logger_setup.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/logging_utils.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/text_utils.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/common/utils.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/comparators/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/comparators/plan_comparator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/contracts/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/contracts/crosshair_props.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/contracts/module_interface.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/enrichers/constitution_enricher.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/enrichers/plan_enricher.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/contract_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/openapi_extractor.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/persona_exporter.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/plan_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/protocol_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/report_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/task_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/test_to_openapi.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/generators/workflow_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/codebase_group.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/govern_group.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/member_group.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/project_group.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/groups/spec_group.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/importers/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/importers/speckit_converter.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/integrations/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/integrations/specmatic.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/merge/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/merge/resolver.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/migrations/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/migrations/plan_migrator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/backlog_item.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/change.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/contract.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/deviation.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/dor_config.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/enforcement.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/module_package.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/persona_template.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/plan.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/project.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/protocol.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/quality.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/sdd.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/source_tracking.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/task.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/models/validation.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modes/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modes/detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modes/router.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/_bundle_import.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/init/src/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/init/src/app.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/init/src/first_run_selection.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/module_io_shim.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/module_registry/module-package.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/module_registry/src/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/module_registry/src/app.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/module_registry/src/commands.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/upgrade/module-package.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/upgrade/src/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/upgrade/src/app.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/modules/upgrade/src/commands.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/parsers/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/parsers/persona_importer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/alias_manager.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/bootstrap.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/bridge_registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/crypto_validator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/custom_registries.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/dependency_resolver.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/extension_registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/help_cache.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/marketplace_client.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/metadata.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_discovery.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_grouping.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_installer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_packages.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_security.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/module_state.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/registry/registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/resources/semgrep/async.yml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/resources/semgrep/code-quality.yml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/resources/semgrep/feature-detection.yml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/runtime.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_probe.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_openspec_md_parse.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_requirement_from_proposal.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_requirement_helpers.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_tasks_from_proposal.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_what_changes_format.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_sync_write_openspec_from_proposal.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/bridge_watch.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/change_detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/code_to_spec.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/drift_detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/repository_sync.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/spec_to_code.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/spec_to_tests.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/watcher.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/sync/watcher_enhanced.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/telemetry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/defaults/defect_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/defaults/enabler_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/defaults/spike_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/defaults/user_story_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/frameworks/scrum/user_story_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/personas/product-owner/user_story_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/providers/ado/work_item_v1.yaml +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/registry.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/templates/specification_templates.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/acceptance_criteria.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/auth_tokens.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/bundle_converters.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/bundle_loader.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/code_change_detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/console.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/content_sanitizer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/context_detection.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/contract_predicates.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/enrichment_context.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/enrichment_parser.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/env_manager.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/feature_keys.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/git.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/github_annotations.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/icontract_helpers.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/incremental_check.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/metadata.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/optional_deps.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/performance.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/persona_ownership.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/progress.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/progressive_disclosure.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/prompts.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/sdd_discovery.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/source_scanner.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/structure.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/structured_io.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/suggestions.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/terminal.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/utils/yaml_utils.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validation/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validation/command_audit.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/agile_validation.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/change_proposal_integration.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/cli_first_validator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/contract_validator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/fsm.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/repro_checker.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/schema.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/contract_populator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/crosshair_runner.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/crosshair_summary.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/dependency_installer.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/framework_detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/base.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/django.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/drf.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/fastapi.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/frameworks/flask.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/harness_generator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/models.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/orchestrator.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/specmatic_runner.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/validators/sidecar/unannotated_detector.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.2}/src/specfact_cli/versioning/__init__.py +0 -0
- {specfact_cli-0.42.6 → specfact_cli-0.43.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.
|
|
3
|
+
Version: 0.43.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
|
|
@@ -282,11 +282,15 @@ Description-Content-Type: text/markdown
|
|
|
282
282
|
# SpecFact CLI
|
|
283
283
|
|
|
284
284
|
> **The "swiss knife" CLI that turns any codebase into a clear, safe, and shippable workflow.**
|
|
285
|
-
> Keep backlog, specs, tests, and code in sync so AI
|
|
285
|
+
> Keep backlog, specs, tests, and code in sync so changes made by people or AI copilots do not break production.
|
|
286
286
|
> Works for brand-new projects and long-lived codebases - even if you are new to coding.
|
|
287
287
|
|
|
288
288
|
**No API keys required. Works offline. Zero vendor lock-in.**
|
|
289
289
|
|
|
290
|
+
SpecFact CLI does **not** include built-in AI. It is a deterministic local CLI
|
|
291
|
+
that can be paired with IDE slash-command prompts so your chosen AI copilot can
|
|
292
|
+
invoke SpecFact as part of a command chain.
|
|
293
|
+
|
|
290
294
|
[](https://pypi.org/project/specfact-cli/)
|
|
291
295
|
[](https://pypi.org/project/specfact-cli/)
|
|
292
296
|
[](LICENSE)
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# SpecFact CLI
|
|
2
2
|
|
|
3
3
|
> **The "swiss knife" CLI that turns any codebase into a clear, safe, and shippable workflow.**
|
|
4
|
-
> Keep backlog, specs, tests, and code in sync so AI
|
|
4
|
+
> Keep backlog, specs, tests, and code in sync so changes made by people or AI copilots do not break production.
|
|
5
5
|
> Works for brand-new projects and long-lived codebases - even if you are new to coding.
|
|
6
6
|
|
|
7
7
|
**No API keys required. Works offline. Zero vendor lock-in.**
|
|
8
8
|
|
|
9
|
+
SpecFact CLI does **not** include built-in AI. It is a deterministic local CLI
|
|
10
|
+
that can be paired with IDE slash-command prompts so your chosen AI copilot can
|
|
11
|
+
invoke SpecFact as part of a command chain.
|
|
12
|
+
|
|
9
13
|
[](https://pypi.org/project/specfact-cli/)
|
|
10
14
|
[](https://pypi.org/project/specfact-cli/)
|
|
11
15
|
[](LICENSE)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfact-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.43.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"
|
|
@@ -217,6 +217,12 @@ workflows-lint = "bash scripts/yaml-tools.sh workflows-lint {args}"
|
|
|
217
217
|
yaml-fix-all = "bash scripts/yaml-tools.sh fix-all {args}"
|
|
218
218
|
yaml-check-all = "bash scripts/yaml-tools.sh check-all {args}"
|
|
219
219
|
|
|
220
|
+
# Docs validation (docs-12): command examples vs CLI; modules.specfact.io URLs in docs
|
|
221
|
+
check-docs-commands = "python scripts/check-docs-commands.py"
|
|
222
|
+
check-cross-site-links = "python scripts/check-cross-site-links.py"
|
|
223
|
+
doc-frontmatter-check = "python scripts/check_doc_frontmatter.py"
|
|
224
|
+
docs-validate = "python scripts/check-docs-commands.py && python scripts/check-cross-site-links.py --warn-only && python scripts/check_doc_frontmatter.py"
|
|
225
|
+
|
|
220
226
|
# Legacy entry (kept for compatibility); prefer `workflows-lint` above
|
|
221
227
|
lint-workflows = "bash scripts/run_actionlint.sh {args}"
|
|
222
228
|
|
|
@@ -386,7 +392,6 @@ only-include = [
|
|
|
386
392
|
sources = ["src"]
|
|
387
393
|
|
|
388
394
|
[tool.hatch.build.targets.wheel.force-include]
|
|
389
|
-
"resources/prompts" = "specfact_cli/resources/prompts"
|
|
390
395
|
"resources/templates" = "specfact_cli/resources/templates"
|
|
391
396
|
"resources/schemas" = "specfact_cli/resources/schemas"
|
|
392
397
|
"resources/mappings" = "specfact_cli/resources/mappings"
|
|
@@ -714,6 +719,14 @@ ignore = [
|
|
|
714
719
|
"SLF001", # private member accessed
|
|
715
720
|
]
|
|
716
721
|
|
|
722
|
+
"tests/unit/tools/test_smart_test_coverage.py" = [
|
|
723
|
+
"E402", # imports after sys.path bootstrap for tool harness loading
|
|
724
|
+
]
|
|
725
|
+
|
|
726
|
+
"tests/unit/tools/test_smart_test_coverage_enhanced.py" = [
|
|
727
|
+
"E402", # imports after sys.path bootstrap for tool harness loading
|
|
728
|
+
]
|
|
729
|
+
|
|
717
730
|
# Tools and scripts can be more lenient
|
|
718
731
|
"tools/**/*" = [
|
|
719
732
|
"T20", # print found
|
|
@@ -6,6 +6,9 @@ This package provides command-line tools for:
|
|
|
6
6
|
- Keeping backlog, specs, tests, and code in sync
|
|
7
7
|
- Enforcing validation and contract checks before production
|
|
8
8
|
- Supporting agile ceremonies and team workflows
|
|
9
|
+
|
|
10
|
+
When a sibling ``specfact-cli-modules`` checkout exists, startup prepends each bundle's ``src``
|
|
11
|
+
to ``sys.path`` so local development can load marketplace packages without installing wheels.
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
from __future__ import annotations
|
|
@@ -42,6 +45,6 @@ def _bootstrap_bundle_paths() -> None:
|
|
|
42
45
|
|
|
43
46
|
_bootstrap_bundle_paths()
|
|
44
47
|
|
|
45
|
-
__version__ = "0.
|
|
48
|
+
__version__ = "0.43.2"
|
|
46
49
|
|
|
47
50
|
__all__ = ["__version__"]
|
|
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import hashlib
|
|
11
11
|
import re
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
from typing import Any
|
|
14
16
|
|
|
@@ -16,6 +18,7 @@ from beartype import beartype
|
|
|
16
18
|
from icontract import ensure, require
|
|
17
19
|
|
|
18
20
|
from specfact_cli.adapters.base import BridgeAdapter
|
|
21
|
+
from specfact_cli.common import get_bridge_logger
|
|
19
22
|
from specfact_cli.importers.speckit_converter import SpecKitConverter
|
|
20
23
|
from specfact_cli.importers.speckit_scanner import SpecKitScanner
|
|
21
24
|
from specfact_cli.models.bridge import BridgeConfig
|
|
@@ -35,6 +38,9 @@ from specfact_cli.utils.icontract_helpers import (
|
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
logger = get_bridge_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
38
44
|
class SpecKitAdapter(BridgeAdapter):
|
|
39
45
|
"""
|
|
40
46
|
Spec-Kit bridge adapter implementing BridgeAdapter interface.
|
|
@@ -83,6 +89,73 @@ class SpecKitAdapter(BridgeAdapter):
|
|
|
83
89
|
or (docs_specs_dir.exists() and docs_specs_dir.is_dir())
|
|
84
90
|
)
|
|
85
91
|
|
|
92
|
+
@beartype
|
|
93
|
+
@ensure(lambda result: result is None or isinstance(result, str), "Must return str or None")
|
|
94
|
+
def _detect_version_from_cli(self, repo_path: Path) -> str | None:
|
|
95
|
+
"""Attempt to detect spec-kit version by running the specify CLI."""
|
|
96
|
+
if not shutil.which("specify"):
|
|
97
|
+
return None
|
|
98
|
+
try:
|
|
99
|
+
result = subprocess.run(
|
|
100
|
+
["specify", "--version"],
|
|
101
|
+
capture_output=True,
|
|
102
|
+
text=True,
|
|
103
|
+
timeout=5,
|
|
104
|
+
cwd=str(repo_path),
|
|
105
|
+
)
|
|
106
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
107
|
+
version_match = re.search(r"(\d+\.\d+\.\d+)", result.stdout)
|
|
108
|
+
if version_match:
|
|
109
|
+
return version_match.group(1)
|
|
110
|
+
except subprocess.TimeoutExpired:
|
|
111
|
+
logger.debug("specify --version timed out after 5 seconds")
|
|
112
|
+
except OSError as exc:
|
|
113
|
+
logger.debug("specify --version failed due to OSError: %s", exc)
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
@beartype
|
|
117
|
+
@ensure(lambda result: result is None or isinstance(result, str), "Must return str or None")
|
|
118
|
+
def _detect_version_from_heuristics(self, repo_path: Path) -> str | None:
|
|
119
|
+
"""Estimate spec-kit version from directory structure presence."""
|
|
120
|
+
if (repo_path / "presets").is_dir():
|
|
121
|
+
return ">=0.3.0"
|
|
122
|
+
if (repo_path / "extensions").is_dir():
|
|
123
|
+
return ">=0.2.0"
|
|
124
|
+
if (repo_path / ".specify").is_dir():
|
|
125
|
+
return ">=0.1.0"
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _detect_layout(base_path: Path) -> tuple[str, str]:
|
|
130
|
+
"""Determine spec-kit layout type and specs directory from repo structure."""
|
|
131
|
+
if (base_path / "docs" / "specs").exists():
|
|
132
|
+
return "modern", "docs/specs"
|
|
133
|
+
if (base_path / ".specify").exists():
|
|
134
|
+
return "modern", "specs"
|
|
135
|
+
return "classic", "specs"
|
|
136
|
+
|
|
137
|
+
def _detect_version(self, base_path: Path, *, skip_cli: bool) -> tuple[str | None, str | None]:
|
|
138
|
+
"""Detect spec-kit version, returning (version, source)."""
|
|
139
|
+
if not skip_cli:
|
|
140
|
+
version = self._detect_version_from_cli(base_path)
|
|
141
|
+
if version:
|
|
142
|
+
return version, "cli"
|
|
143
|
+
version = self._detect_version_from_heuristics(base_path)
|
|
144
|
+
if version:
|
|
145
|
+
return version, "heuristic"
|
|
146
|
+
return None, None
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _extract_extension_fields(
|
|
150
|
+
ext_list: list[dict[str, Any]],
|
|
151
|
+
) -> tuple[list[str] | None, dict[str, list[str]] | None]:
|
|
152
|
+
"""Extract extension names and command maps from scanner output."""
|
|
153
|
+
if not ext_list:
|
|
154
|
+
return None, None
|
|
155
|
+
names = [e["name"] for e in ext_list]
|
|
156
|
+
commands = {e["name"]: e.get("commands", []) for e in ext_list}
|
|
157
|
+
return names, commands
|
|
158
|
+
|
|
86
159
|
@beartype
|
|
87
160
|
@require(require_repo_path_exists, "Repository path must exist")
|
|
88
161
|
@require(require_repo_path_is_dir, "Repository path must be a directory")
|
|
@@ -98,37 +171,30 @@ class SpecKitAdapter(BridgeAdapter):
|
|
|
98
171
|
Returns:
|
|
99
172
|
ToolCapabilities instance for Spec-Kit adapter
|
|
100
173
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
base_path = bridge_config.external_base_path
|
|
104
|
-
|
|
105
|
-
# Determine layout (classic vs modern)
|
|
106
|
-
specify_dir = base_path / ".specify"
|
|
107
|
-
docs_specs_dir = base_path / "docs" / "specs"
|
|
108
|
-
|
|
109
|
-
if docs_specs_dir.exists():
|
|
110
|
-
layout = "modern"
|
|
111
|
-
specs_dir_path = "docs/specs"
|
|
112
|
-
elif specify_dir.exists():
|
|
113
|
-
layout = "modern"
|
|
114
|
-
specs_dir_path = "specs"
|
|
115
|
-
else:
|
|
116
|
-
layout = "classic"
|
|
117
|
-
specs_dir_path = "specs"
|
|
174
|
+
is_cross_repo = bridge_config is not None and bridge_config.external_base_path is not None
|
|
175
|
+
base_path: Path = bridge_config.external_base_path if is_cross_repo else repo_path # type: ignore[assignment]
|
|
118
176
|
|
|
119
|
-
|
|
177
|
+
layout, specs_dir_path = self._detect_layout(base_path)
|
|
120
178
|
scanner = SpecKitScanner(base_path)
|
|
121
179
|
has_constitution, _ = scanner.has_constitution()
|
|
122
|
-
|
|
180
|
+
version, detected_version_source = self._detect_version(base_path, skip_cli=is_cross_repo)
|
|
181
|
+
extensions, extension_commands = self._extract_extension_fields(scanner.scan_extensions())
|
|
182
|
+
preset_names = scanner.scan_presets()
|
|
183
|
+
hook_list = scanner.scan_hook_events()
|
|
123
184
|
|
|
124
185
|
return ToolCapabilities(
|
|
125
186
|
tool="speckit",
|
|
126
|
-
version=
|
|
187
|
+
version=version,
|
|
127
188
|
layout=layout,
|
|
128
189
|
specs_dir=specs_dir_path,
|
|
129
|
-
has_external_config=
|
|
130
|
-
has_custom_hooks=
|
|
131
|
-
supported_sync_modes=["bidirectional", "unidirectional"],
|
|
190
|
+
has_external_config=is_cross_repo,
|
|
191
|
+
has_custom_hooks=has_constitution,
|
|
192
|
+
supported_sync_modes=["bidirectional", "unidirectional"],
|
|
193
|
+
extensions=extensions,
|
|
194
|
+
extension_commands=extension_commands,
|
|
195
|
+
presets=preset_names or None,
|
|
196
|
+
hook_events=hook_list or None,
|
|
197
|
+
detected_version_source=detected_version_source,
|
|
132
198
|
)
|
|
133
199
|
|
|
134
200
|
@beartype
|
|
@@ -11,14 +11,20 @@ generate markdown artifacts in specs/ and .specify/ directories.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import json
|
|
14
15
|
import re
|
|
15
16
|
from contextlib import suppress
|
|
16
17
|
from pathlib import Path
|
|
17
|
-
from typing import Any
|
|
18
|
+
from typing import Any, cast
|
|
18
19
|
|
|
19
20
|
from beartype import beartype
|
|
20
21
|
from icontract import ensure, require
|
|
21
22
|
|
|
23
|
+
from specfact_cli.common import get_bridge_logger
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
logger = get_bridge_logger(__name__)
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
def _spec_file_is_markdown(spec_file: Path) -> bool:
|
|
24
30
|
return spec_file.suffix == ".md"
|
|
@@ -687,6 +693,122 @@ class SpecKitScanner:
|
|
|
687
693
|
self._parse_constitution_principles(content, memory_data)
|
|
688
694
|
self._parse_constitution_governance_constraints(content, memory_data)
|
|
689
695
|
|
|
696
|
+
def _load_extensionignore(self) -> set[str]:
|
|
697
|
+
"""Load extension names to ignore from .extensionignore file."""
|
|
698
|
+
extensionignore = self.repo_path / ".extensionignore"
|
|
699
|
+
if not extensionignore.exists():
|
|
700
|
+
return set()
|
|
701
|
+
try:
|
|
702
|
+
content = extensionignore.read_text(encoding="utf-8")
|
|
703
|
+
return {line.strip() for line in content.splitlines() if line.strip() and not line.startswith("#")}
|
|
704
|
+
except OSError:
|
|
705
|
+
logger.debug("Failed to read .extensionignore, proceeding without ignore rules")
|
|
706
|
+
return set()
|
|
707
|
+
|
|
708
|
+
def _parse_catalog_file(self, catalog_path: Path, ignored: set[str], extensions: list[dict[str, Any]]) -> None:
|
|
709
|
+
"""Parse a single extension catalog JSON file and append results."""
|
|
710
|
+
if not catalog_path.exists():
|
|
711
|
+
return
|
|
712
|
+
try:
|
|
713
|
+
raw = json.loads(catalog_path.read_text(encoding="utf-8"))
|
|
714
|
+
parsed: Any = raw
|
|
715
|
+
items: list[Any] = (
|
|
716
|
+
parsed if isinstance(parsed, list) else cast("dict[str, Any]", parsed).get("extensions", [])
|
|
717
|
+
)
|
|
718
|
+
for item in items:
|
|
719
|
+
if not isinstance(item, dict):
|
|
720
|
+
continue
|
|
721
|
+
item_dict = cast("dict[str, Any]", item)
|
|
722
|
+
name: str = str(item_dict.get("name", ""))
|
|
723
|
+
if name and name not in ignored:
|
|
724
|
+
commands: list[str] = list(item_dict.get("commands") or [])
|
|
725
|
+
ext_version = item_dict.get("version")
|
|
726
|
+
extensions.append(
|
|
727
|
+
{"name": name, "commands": commands, "version": str(ext_version) if ext_version else None}
|
|
728
|
+
)
|
|
729
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
730
|
+
logger.warning("Malformed extension catalog %s: %s", catalog_path, exc)
|
|
731
|
+
|
|
732
|
+
@beartype
|
|
733
|
+
@ensure(lambda result: isinstance(result, list), "Must return list")
|
|
734
|
+
def scan_extensions(self) -> list[dict[str, Any]]:
|
|
735
|
+
"""
|
|
736
|
+
Scan for spec-kit extension catalogs and return parsed extension metadata.
|
|
737
|
+
|
|
738
|
+
Parses extensions/catalog.community.json and extensions/catalog.core.json,
|
|
739
|
+
filtering out extensions listed in .extensionignore.
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
List of extension metadata dicts with at minimum 'name' and 'commands' keys.
|
|
743
|
+
"""
|
|
744
|
+
extensions_dir = self.repo_path / "extensions"
|
|
745
|
+
if not extensions_dir.exists() or not extensions_dir.is_dir():
|
|
746
|
+
return []
|
|
747
|
+
|
|
748
|
+
ignored = self._load_extensionignore()
|
|
749
|
+
extensions: list[dict[str, Any]] = []
|
|
750
|
+
for catalog_name in ("catalog.core.json", "catalog.community.json"):
|
|
751
|
+
self._parse_catalog_file(extensions_dir / catalog_name, ignored, extensions)
|
|
752
|
+
|
|
753
|
+
return extensions
|
|
754
|
+
|
|
755
|
+
@beartype
|
|
756
|
+
@ensure(lambda result: isinstance(result, list), "Must return list")
|
|
757
|
+
def scan_presets(self) -> list[str]:
|
|
758
|
+
"""
|
|
759
|
+
Scan for spec-kit preset catalogs in the presets/ directory.
|
|
760
|
+
|
|
761
|
+
Returns:
|
|
762
|
+
List of detected preset names.
|
|
763
|
+
"""
|
|
764
|
+
presets_dir = self.repo_path / "presets"
|
|
765
|
+
if not presets_dir.exists() or not presets_dir.is_dir():
|
|
766
|
+
return []
|
|
767
|
+
|
|
768
|
+
preset_names: list[str] = []
|
|
769
|
+
for item in presets_dir.iterdir():
|
|
770
|
+
if item.is_file() and item.suffix == ".json":
|
|
771
|
+
try:
|
|
772
|
+
data: Any = json.loads(item.read_text(encoding="utf-8"))
|
|
773
|
+
name = (
|
|
774
|
+
str(cast("dict[str, Any]", data).get("name", item.stem))
|
|
775
|
+
if isinstance(data, dict)
|
|
776
|
+
else item.stem
|
|
777
|
+
)
|
|
778
|
+
preset_names.append(name)
|
|
779
|
+
except (json.JSONDecodeError, OSError):
|
|
780
|
+
preset_names.append(item.stem)
|
|
781
|
+
elif item.is_dir():
|
|
782
|
+
preset_names.append(item.name)
|
|
783
|
+
return preset_names
|
|
784
|
+
|
|
785
|
+
@beartype
|
|
786
|
+
@ensure(lambda result: isinstance(result, list), "Must return list")
|
|
787
|
+
def scan_hook_events(self) -> list[str]:
|
|
788
|
+
"""
|
|
789
|
+
Detect before/after hook event wiring in .specify/prompts/ template files.
|
|
790
|
+
|
|
791
|
+
Returns:
|
|
792
|
+
List of detected hook event types (e.g., ["before_task", "after_task"]).
|
|
793
|
+
"""
|
|
794
|
+
prompts_dir = self.repo_path / ".specify" / "prompts"
|
|
795
|
+
if not prompts_dir.exists() or not prompts_dir.is_dir():
|
|
796
|
+
return []
|
|
797
|
+
|
|
798
|
+
hook_events: set[str] = set()
|
|
799
|
+
hook_pattern = re.compile(r"(before|after)[_-](task|plan|specify|implement|constitution)", re.IGNORECASE)
|
|
800
|
+
|
|
801
|
+
for template_file in prompts_dir.glob("*.md"):
|
|
802
|
+
try:
|
|
803
|
+
content = template_file.read_text(encoding="utf-8")
|
|
804
|
+
for match in hook_pattern.finditer(content):
|
|
805
|
+
event = f"{match.group(1).lower()}_{match.group(2).lower()}"
|
|
806
|
+
hook_events.add(event)
|
|
807
|
+
except OSError:
|
|
808
|
+
continue
|
|
809
|
+
|
|
810
|
+
return sorted(hook_events)
|
|
811
|
+
|
|
690
812
|
@ensure(lambda result: isinstance(result, dict), "Must return dict")
|
|
691
813
|
def parse_memory_files(self, memory_dir: Path) -> dict[str, Any]:
|
|
692
814
|
"""
|
|
@@ -255,7 +255,7 @@ class BridgeConfig(BaseModel):
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
commands = {
|
|
258
|
-
"
|
|
258
|
+
"specify": CommandMapping(
|
|
259
259
|
trigger="/speckit.specify",
|
|
260
260
|
input_ref="specification",
|
|
261
261
|
),
|
|
@@ -264,6 +264,27 @@ class BridgeConfig(BaseModel):
|
|
|
264
264
|
input_ref="specification",
|
|
265
265
|
output_ref="plan",
|
|
266
266
|
),
|
|
267
|
+
"tasks": CommandMapping(
|
|
268
|
+
trigger="/speckit.tasks",
|
|
269
|
+
input_ref="plan",
|
|
270
|
+
output_ref="tasks",
|
|
271
|
+
),
|
|
272
|
+
"implement": CommandMapping(
|
|
273
|
+
trigger="/speckit.implement",
|
|
274
|
+
input_ref="tasks",
|
|
275
|
+
),
|
|
276
|
+
"constitution": CommandMapping(
|
|
277
|
+
trigger="/speckit.constitution",
|
|
278
|
+
input_ref="constitution",
|
|
279
|
+
),
|
|
280
|
+
"clarify": CommandMapping(
|
|
281
|
+
trigger="/speckit.clarify",
|
|
282
|
+
input_ref="specification",
|
|
283
|
+
),
|
|
284
|
+
"analyze": CommandMapping(
|
|
285
|
+
trigger="/speckit.analyze",
|
|
286
|
+
input_ref="specification",
|
|
287
|
+
),
|
|
267
288
|
}
|
|
268
289
|
|
|
269
290
|
templates = TemplateMapping(
|
|
@@ -319,7 +340,7 @@ class BridgeConfig(BaseModel):
|
|
|
319
340
|
}
|
|
320
341
|
|
|
321
342
|
commands = {
|
|
322
|
-
"
|
|
343
|
+
"specify": CommandMapping(
|
|
323
344
|
trigger="/speckit.specify",
|
|
324
345
|
input_ref="specification",
|
|
325
346
|
),
|
|
@@ -328,6 +349,27 @@ class BridgeConfig(BaseModel):
|
|
|
328
349
|
input_ref="specification",
|
|
329
350
|
output_ref="plan",
|
|
330
351
|
),
|
|
352
|
+
"tasks": CommandMapping(
|
|
353
|
+
trigger="/speckit.tasks",
|
|
354
|
+
input_ref="plan",
|
|
355
|
+
output_ref="tasks",
|
|
356
|
+
),
|
|
357
|
+
"implement": CommandMapping(
|
|
358
|
+
trigger="/speckit.implement",
|
|
359
|
+
input_ref="tasks",
|
|
360
|
+
),
|
|
361
|
+
"constitution": CommandMapping(
|
|
362
|
+
trigger="/speckit.constitution",
|
|
363
|
+
input_ref="constitution",
|
|
364
|
+
),
|
|
365
|
+
"clarify": CommandMapping(
|
|
366
|
+
trigger="/speckit.clarify",
|
|
367
|
+
input_ref="specification",
|
|
368
|
+
),
|
|
369
|
+
"analyze": CommandMapping(
|
|
370
|
+
trigger="/speckit.analyze",
|
|
371
|
+
input_ref="specification",
|
|
372
|
+
),
|
|
331
373
|
}
|
|
332
374
|
|
|
333
375
|
templates = TemplateMapping(
|
|
@@ -380,7 +422,7 @@ class BridgeConfig(BaseModel):
|
|
|
380
422
|
}
|
|
381
423
|
|
|
382
424
|
commands = {
|
|
383
|
-
"
|
|
425
|
+
"specify": CommandMapping(
|
|
384
426
|
trigger="/speckit.specify",
|
|
385
427
|
input_ref="specification",
|
|
386
428
|
),
|
|
@@ -389,6 +431,27 @@ class BridgeConfig(BaseModel):
|
|
|
389
431
|
input_ref="specification",
|
|
390
432
|
output_ref="plan",
|
|
391
433
|
),
|
|
434
|
+
"tasks": CommandMapping(
|
|
435
|
+
trigger="/speckit.tasks",
|
|
436
|
+
input_ref="plan",
|
|
437
|
+
output_ref="tasks",
|
|
438
|
+
),
|
|
439
|
+
"implement": CommandMapping(
|
|
440
|
+
trigger="/speckit.implement",
|
|
441
|
+
input_ref="tasks",
|
|
442
|
+
),
|
|
443
|
+
"constitution": CommandMapping(
|
|
444
|
+
trigger="/speckit.constitution",
|
|
445
|
+
input_ref="constitution",
|
|
446
|
+
),
|
|
447
|
+
"clarify": CommandMapping(
|
|
448
|
+
trigger="/speckit.clarify",
|
|
449
|
+
input_ref="specification",
|
|
450
|
+
),
|
|
451
|
+
"analyze": CommandMapping(
|
|
452
|
+
trigger="/speckit.analyze",
|
|
453
|
+
input_ref="specification",
|
|
454
|
+
),
|
|
392
455
|
}
|
|
393
456
|
|
|
394
457
|
templates = TemplateMapping(
|
|
@@ -18,3 +18,9 @@ class ToolCapabilities:
|
|
|
18
18
|
supported_sync_modes: list[str] | None = (
|
|
19
19
|
None # Supported sync modes (e.g., ["bidirectional", "unidirectional", "read-only", "export-only"])
|
|
20
20
|
)
|
|
21
|
+
# Spec-Kit v0.4.x alignment fields
|
|
22
|
+
extensions: list[str] | None = None # Detected extension names (e.g., ["reconcile", "sync", "verify"])
|
|
23
|
+
extension_commands: dict[str, list[str]] | None = None # Extension name → provided commands
|
|
24
|
+
presets: list[str] | None = None # Active preset names
|
|
25
|
+
hook_events: list[str] | None = None # Detected hook event types (e.g., ["before_task", "after_task"])
|
|
26
|
+
detected_version_source: str | None = None # How version was detected: "cli", "heuristic", or None
|
{specfact_cli-0.42.6 → specfact_cli-0.43.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.19
|
|
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:a0ca0fb136f278a11a113be78047c3c7037de9a393c27f8e677a26c4ab2ba659
|
|
21
|
+
signature: r3czsyG/tinaxMTd/lNkhjXQqRUU0ecLywXt1iDWPO0QuExVM+msp5tuAud03QPnuCsMXNdyBWWSDBovPeY+Aw==
|
|
@@ -38,7 +38,6 @@ from specfact_cli.utils.ide_setup import (
|
|
|
38
38
|
discover_prompt_sources_catalog,
|
|
39
39
|
discover_prompt_template_files,
|
|
40
40
|
expected_ide_prompt_export_paths,
|
|
41
|
-
find_package_resources_path,
|
|
42
41
|
load_ide_prompt_export_source_ids,
|
|
43
42
|
write_ide_prompt_export_state,
|
|
44
43
|
)
|
|
@@ -310,8 +309,8 @@ def _select_module_ids_interactive(action: str, modules_list: list[dict[str, Any
|
|
|
310
309
|
|
|
311
310
|
|
|
312
311
|
def _resolve_templates_dir(repo_path: Path) -> Path | None:
|
|
313
|
-
"""Resolve templates directory from
|
|
314
|
-
prompt_files = discover_prompt_template_files(repo_path, include_package_fallback=
|
|
312
|
+
"""Resolve a representative templates directory from installed modules or a dev repo checkout."""
|
|
313
|
+
prompt_files = discover_prompt_template_files(repo_path, include_package_fallback=True)
|
|
315
314
|
if prompt_files:
|
|
316
315
|
return prompt_files[0].parent
|
|
317
316
|
|
|
@@ -319,7 +318,7 @@ def _resolve_templates_dir(repo_path: Path) -> Path | None:
|
|
|
319
318
|
if dev_templates_dir.exists():
|
|
320
319
|
return dev_templates_dir
|
|
321
320
|
|
|
322
|
-
return
|
|
321
|
+
return None
|
|
323
322
|
|
|
324
323
|
|
|
325
324
|
def _audit_prompt_installation(repo_path: Path) -> None:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, cast
|
|
6
6
|
|
|
7
7
|
from beartype import beartype
|
|
8
8
|
from icontract import ensure, require
|
|
@@ -120,7 +120,8 @@ def _questionary_style() -> Any:
|
|
|
120
120
|
import questionary # type: ignore[reportMissingImports]
|
|
121
121
|
except ImportError:
|
|
122
122
|
return None
|
|
123
|
-
|
|
123
|
+
q = cast(Any, questionary)
|
|
124
|
+
return q.Style(
|
|
124
125
|
[
|
|
125
126
|
("qmark", "fg:#00af87 bold"),
|
|
126
127
|
("question", "bold"),
|
|
@@ -209,7 +210,8 @@ def select_module_ids_interactive(action: str, modules_list: list[dict[str, Any]
|
|
|
209
210
|
console.print(f"[cyan]{action_title} Modules[/cyan] (currently {current_state})")
|
|
210
211
|
console.print("[dim]Controls: arrows navigate, space toggle, enter confirm[/dim]")
|
|
211
212
|
display_to_id, choices = _checkbox_choices_for_modules(candidates)
|
|
212
|
-
|
|
213
|
+
q = cast(Any, questionary)
|
|
214
|
+
selected: list[str] | None = q.checkbox(
|
|
213
215
|
f"{action_title} module(s):",
|
|
214
216
|
choices=choices,
|
|
215
217
|
instruction="(multi-select)",
|
|
@@ -259,8 +259,10 @@ def discover_prompt_sources_catalog(
|
|
|
259
259
|
"""
|
|
260
260
|
Build prompt templates grouped by owning source: ``core`` or a module id (``module-package.yaml`` name).
|
|
261
261
|
|
|
262
|
-
Core templates come from
|
|
263
|
-
are
|
|
262
|
+
Core templates may come from a repo checkout under ``resources/prompts`` or the installed package when
|
|
263
|
+
present; workflow prompts are normally provided by bundle modules under ``resources/prompts`` at the
|
|
264
|
+
module root. Module templates are discovered from effective module roots (builtin, project, user,
|
|
265
|
+
marketplace, custom).
|
|
264
266
|
|
|
265
267
|
When a module ships a template with the same source filename as core (e.g. ``specfact.01-import.md``),
|
|
266
268
|
the module copy wins: core does not list that basename so exports stay single-sourced.
|