Skip to content

fix: add module version handling#568

Open
djm81 wants to merge 457 commits into
mainfrom
dev
Open

fix: add module version handling#568
djm81 wants to merge 457 commits into
mainfrom
dev

Conversation

@djm81
Copy link
Copy Markdown
Collaborator

@djm81 djm81 commented May 14, 2026

Summary

Adds module-scope diagnostics and version mismatch enforcement for project/user scoped SpecFact modules.

  • Adds specfact module doctor to show effective vs shadowed module copies across project, user, and configured development roots.
  • Enforces versioned bundle dependency declarations during module install and versioned module dependency declarations during registration/lifecycle checks.
  • Updates module documentation, OpenSpec artifacts, source tracking, and module/package version metadata.
  • Fixes the relaxed PR module-signature verifier so --skip-checksum-verification really performs version-bump-only verification while CI signing handles signatures.

Root Cause

Users can work across split repos and monorepos with both project-local and user-scoped modules installed. Discovery priority can make one copy shadow another, while older dependency checks did not consistently enforce declared module version ranges, causing confusing command availability and runtime mismatches.

Tracking

Fixes #565
Parent feature: #353
OpenSpec change: module-scope-version-diagnostics

Validation

  • openspec validate module-scope-version-diagnostics --strict
  • hatch run pytest tests/unit/specfact_cli/registry/test_module_dependencies.py tests/unit/registry/test_module_installer.py tests/unit/modules/module_registry/test_commands.py -q (96 passed)
  • Scoped basedpyright on touched implementation/tests (0 errors)
  • hatch run specfact code review run --json --out .specfact/code-review.changed.json --scope changed (0 blocking)
  • Real tmp smoke: project/user duplicate module doctor scenario reports project as effective and user as shadowed with remediation
  • Real tmp smoke: installer rejects existing dependency 1.0.0 when tarball requires >=2.0.0
  • Pre-commit passed on commit 500337e5

djm81 and others added 30 commits February 27, 2026 23:09
* feat(cli): category groups and flat shims using real module Typer

- Add category groups (code, backlog, project, spec, govern) with flatten same-name member
- Sort commands under backlog/project groups A–Z
- Fix flat shims to expose real module Typer so 'specfact sync bridge' and 'specfact plan update-idea' work
- Add first-run init, module grouping, OpenSpec change for 0.40.x remove-flat-shims
- Bump version to 0.39.0, CHANGELOG and OpenSpec updates

Made-with: Cursor

* Fix signature

* fix: resolve module grouping regressions and stabilize CI
  tests

* fix: keep uncategorized modules flat during grouped registration

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
* docs: add module-migration-02-bundle-extraction to CHANGE_ORDER.md

* feat: implement module-migration-02 bundle extraction

* fix(ci): checkout module bundles repo for test jobs

* Fix test failures

* fix(modules): load local bundle sources in compatibility aliases

* fix: run worktree policy code in tests/CI and silence reexport deprecation

- Prefer src/<name>/main.py over app.py when SPECFACT_REPO_ROOT is set so
  policy init uses worktree templates.py (SPECFACT_POLICY_TEMPLATES_DIR).
- Policy engine module-package.yaml: version 0.1.5 and re-signed checksum.
- conftest: set SPECFACT_REPO_ROOT, SPECFACT_POLICY_TEMPLATES_DIR; add
  bundle package roots when specfact-cli-modules present.
- Policy engine integration tests: rely on conftest env, clear registry
  and re-register before invoke so loader uses worktree.
- test_reexport_shims: filter deprecation warning for legacy analyze import.

Made-with: Cursor

* fix: defer specfact_backlog import in shims so CI can register bridges

- backlog and policy_engine __init__.py: import specfact_backlog only in
  __getattr__ (cached), not at module load. Allows loading .src.adapters.*
  for bridge registration without requiring specfact_backlog installed.
- Re-sign backlog and policy_engine module-package.yaml after init changes.
- openspec: update module-migration-02 tasks.md.

Made-with: Cursor

* fix: defer bundle import in all module shims to fix CI collection errors

- Apply deferred import (only in __getattr__, cached) to analyze, contract,
  drift, enforce, generate, import_cmd, migrate, patch_mode, plan, project,
  repro, sdd, spec, sync, validate. Matches backlog and policy_engine.
- Prevents ImportError when tests import specfact_cli.modules.<name>.src.*
  without specfact_backlog/specfact_govern/specfact_project/specfact_spec
  installed (e.g. CI). Fixes 78 collection errors.
- Re-sign all affected module-package.yaml manifests.

Made-with: Cursor

* fix(ci): include module shims in hatch cache key so CI uses current code

* feat(modules): registry descriptions, --bump-version for publish, tasks and format fixes

- Add description to registry index entries in publish-module.py (module search)
- Add --bump-version patch|minor|major for bundle re-publish in publish-module.py
- Format fixes in validate-modules-repo-sync.py (SIM108, B007)
- Mark completed tasks in module-migration-02-bundle-extraction tasks.md
- Update test for publish_bundle(bump_version=) signature

Made-with: Cursor

* Add missing migration tasks to the open change to completely isolate modules into specfact-cli-modules repo.

* Add gap analysis and update changes

* Update follow-up changes to avoid ambiguities and overlaps

* docs: complete migration-02 section-18 parity and 17.8 gate evidence

* docs: mark migration-02 import-categorization commit checkpoint done

* Update change constraints and blockers for module migration

* docs: add migration-05 issue #334 and complete task 17.10.4

* Update change constraints and blockers for module migration

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
…317) (#341)

* Prepare module-migration-03 removal of old built-in modules

* feat(core): delete specfact-project module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-backlog module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-codebase module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-spec module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-govern module source from core (migration-03)

Made-with: Cursor

* chore(tests): skip tests for removed modules when source absent (migration-03)

Add pytest.importorskip() for backlog, plan, sync, enforce, generate,
patch_mode, import_cmd so tests are skipped when module source was
removed from core. Preserves tests for later move to specfact-cli-modules.
Update tasks.md and TDD_EVIDENCE.md for Task 10 completion.

Made-with: Cursor

* feat(bootstrap): remove flat shims and non-core module registrations (migration-03)

- Remove _register_category_groups_and_shims (unconditional category/shim registration).
- Trim CORE_MODULE_ORDER to 4 core: init, auth, module-registry, upgrade.
- Add @beartype to _mount_installed_category_groups.
- Category groups and flat shims only for installed bundles via _mount_installed_category_groups.

Made-with: Cursor

* docs(openspec): mark Task 11.4 done in tasks.md

Made-with: Cursor

* feat(cli): conditional category group mount from installed bundles (migration-03)

- Add _RootCLIGroup (extends ProgressiveDisclosureGroup) with resolve_command
  override: unknown commands in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES show
  actionable error (not installed + specfact init / specfact module install).
- Root app uses cls=_RootCLIGroup. Main help docstring adds init/module
  install hint for workflow bundles.

Made-with: Cursor

* docs(openspec): mark Task 12.4 done in tasks.md

Made-with: Cursor

* feat(init): enforce mandatory bundle selection and profile presets (migration-03)

* Add module removal core tests

* docs(openspec): record Task 14 module signing gate (migration-03)

* feat: complete module-migration-03 core slimming and
  follow-up alignment (#317)

* Fix format error

* fix: handle detached HEAD registry branch selection and
  stabilize migration-03 CI tests

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
#317) (#343)

* Prepare module-migration-03 removal of old built-in modules

* feat(core): delete specfact-project module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-backlog module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-codebase module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-spec module source from core (migration-03)

Made-with: Cursor

* feat(core): delete specfact-govern module source from core (migration-03)

Made-with: Cursor

* chore(tests): skip tests for removed modules when source absent (migration-03)

Add pytest.importorskip() for backlog, plan, sync, enforce, generate,
patch_mode, import_cmd so tests are skipped when module source was
removed from core. Preserves tests for later move to specfact-cli-modules.
Update tasks.md and TDD_EVIDENCE.md for Task 10 completion.

Made-with: Cursor

* feat(bootstrap): remove flat shims and non-core module registrations (migration-03)

- Remove _register_category_groups_and_shims (unconditional category/shim registration).
- Trim CORE_MODULE_ORDER to 4 core: init, auth, module-registry, upgrade.
- Add @beartype to _mount_installed_category_groups.
- Category groups and flat shims only for installed bundles via _mount_installed_category_groups.

Made-with: Cursor

* docs(openspec): mark Task 11.4 done in tasks.md

Made-with: Cursor

* feat(cli): conditional category group mount from installed bundles (migration-03)

- Add _RootCLIGroup (extends ProgressiveDisclosureGroup) with resolve_command
  override: unknown commands in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES show
  actionable error (not installed + specfact init / specfact module install).
- Root app uses cls=_RootCLIGroup. Main help docstring adds init/module
  install hint for workflow bundles.

Made-with: Cursor

* docs(openspec): mark Task 12.4 done in tasks.md

Made-with: Cursor

* feat(init): enforce mandatory bundle selection and profile presets (migration-03)

* Add module removal core tests

* docs(openspec): record Task 14 module signing gate (migration-03)

* feat: complete module-migration-03 core slimming and
  follow-up alignment (#317)

* Fix format error

* fix: handle detached HEAD registry branch selection and
  stabilize migration-03 CI tests

* Prepare module-migration-03 removal of old built-in modules

* Prepare module-migration-03 removal of old built-in modules

* chore(tests): skip tests for removed modules when source absent (migration-03)

Add pytest.importorskip() for backlog, plan, sync, enforce, generate,
patch_mode, import_cmd so tests are skipped when module source was
removed from core. Preserves tests for later move to specfact-cli-modules.
Update tasks.md and TDD_EVIDENCE.md for Task 10 completion.

Made-with: Cursor

* feat(bootstrap): remove flat shims and non-core module registrations (migration-03)

- Remove _register_category_groups_and_shims (unconditional category/shim registration).
- Trim CORE_MODULE_ORDER to 4 core: init, auth, module-registry, upgrade.
- Add @beartype to _mount_installed_category_groups.
- Category groups and flat shims only for installed bundles via _mount_installed_category_groups.

Made-with: Cursor

* docs(openspec): mark Task 11.4 done in tasks.md

Made-with: Cursor

* feat(cli): conditional category group mount from installed bundles (migration-03)

- Add _RootCLIGroup (extends ProgressiveDisclosureGroup) with resolve_command
  override: unknown commands in KNOWN_BUNDLE_GROUP_OR_SHIM_NAMES show
  actionable error (not installed + specfact init / specfact module install).
- Root app uses cls=_RootCLIGroup. Main help docstring adds init/module
  install hint for workflow bundles.

Made-with: Cursor

* docs(openspec): mark Task 12.4 done in tasks.md

Made-with: Cursor

* feat(init): enforce mandatory bundle selection and profile presets (migration-03)

* Add module removal core tests

* docs(openspec): record Task 14 module signing gate (migration-03)

* feat: complete module-migration-03 core slimming and
  follow-up alignment (#317)

* Fix format error

* fix: handle detached HEAD registry branch selection and
  stabilize migration-03 CI tests

* feat(core): remove auth module from core and route auth via backlog (migration-03)

* docs(openspec): update migration-03 PR status and tracking

* docs(openspec): finalize migration-03 checklist and defer non-blocking gates

* Fix remaining auth findings and dependency in core cli

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
* feat: remove flat command shims from grouped registry

* Finalize change module-migration-04 implementation

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
…R merge (#345)

* Implement blockers to prepare for module-migration-03 change.

* Update migration change

* docs(openspec): close migration-05 PR tracking and change order

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
* feat(migration-06): core decoupling cleanup - boundary tests and inventory

- Add test_core_does_not_import_from_bundle_packages boundary regression test
- Update spec with ownership boundary and migration acceptance criteria
- Add CORE_DECOUPLING_INVENTORY.md (keep/move/interface classification)
- Record TDD evidence in TDD_EVIDENCE.md
- Update docs/reference/architecture.md with core vs modules-repo boundary
- Update openspec/CHANGE_ORDER.md status

No move candidates identified; core already decoupled from bundle packages.
Boundary test prevents future core->bundle coupling.

Refs #338

Made-with: Cursor

* chore(migration-06): mark all tasks complete

Made-with: Cursor

* feat(migration-06): extend scope - migrate package-specific artifacts per #338

- Add MIGRATION_REMOVAL_PLAN.md with phased removal of MIGRATE-tier code
- Add test_core_modules_do_not_import_migrate_tier boundary test
- Remove templates.bridge_templates (dead code; only tests used it)
- Remove tests/unit/templates/test_bridge_templates.py
- Update CORE_DECOUPLING_INVENTORY.md with removal status
- Update spec with MIGRATE-tier enforcement and package-specific removal

Phase 1 complete. Further MIGRATE-tier removal documented in plan.
Refs #338

Made-with: Cursor

* test(migration-06): move legacy sync tests out of core

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
* test: finalize module-migration-07 core test ownership cleanup

* docs: mark module-migration-07 quality and PR tasks complete

* test: fix CI isolation failures for project and persona merge

* test: narrow migrated skips and restore core registry guardrails

* test: stabilize core CI by refining skips and bootstrap checks

* test: fix remaining PR failures via targeted core filtering

* fix: harden module package checks against import-mode class identity

* test: stabilize core slimming integration assertions

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
#376)

- Create openspec/changes/agile-01-feature-hierarchy/ with proposal.md and tasks.md
- Add Epics #256 (Architecture Layer Integration), #257 (AI IDE Integration),
  and #258 (Integration Governance and Dogfooding) to CHANGE_ORDER.md parent issues table
- 25 GitHub Feature issues created (#351-#375), linked to their parent Epics
- Feature label created; issue #185 closed (ceremony-cockpit-01, archived 2026-02-18)

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: align core docs and sync pending changes

* fix: preserve partial staging in markdown autofix hook

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
djm81 and others added 19 commits May 3, 2026 23:37
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
…551)

* chore(openspec): park 21 deferred change proposals

Move proposals that are awaiting external signal (paying enterprise customer,
third-party publisher, evidence corpus, etc.) from openspec/changes/ into
a new openspec/parking-lot/ directory, preserving full content and history.

This restores focus on the active core thesis (full-chain traceability for
agile DevOps + AI IDEs) without abandoning prior thinking. Each parked
proposal has an explicit un-park trigger documented in
openspec/parking-lot/README.md.

Parked groups:
- enterprise-01..04 (4): no paying enterprise customer yet
- finops-01..02 (2): no internal LLM workload to optimise yet
- knowledge-01..02 (2): insufficient evidence corpus for distillation
- marketplace-03..06 (4): no third-party publishers to onboard
- security-01..02 (2): no customer-driven security review demand
- review-resiliency-01-contracts (1): code-review module gap unproven
- profile-02..03 (2): profile-01 not yet shipped
- cli-val-01,02,05,06 (4): infrastructure ahead of demand;
  cli-val-03 and cli-val-04 remain active

Active changes drop from 45 to 24. No CI workflows or production code
referenced the parked directories; only comment-level references remain
in already-shipped marketplace-06 helper scripts. openspec list is clean.

Also adds a missing fenced-code language tag to a parked design.md
to satisfy the markdownlint pre-commit hook.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(openspec): rewrite CHANGE_ORDER.md to reflect parked roadmap

Reduce from 647 to 214 lines and reorganise around five active tracks
instead of ten plan-derived addenda. The previous file accumulated
historical narrative ("plan-derived addendum 2026-02-15", "addendum
2026-03-22", etc.) that belonged in git history, and listed many
changes that no longer exist as folders here (moved to modules repo
or already archived).

New structure:
- Status snapshot: 24 active / 21 parked / 104 archived with paths
- Five active tracks (full-chain, AI IDE, profile, CLI reliability, other)
- Modify queue: explicit list of proposals needing scope adjustment
  before implementation begins
- Five waves (1: adjust scope, 2: foundations, 3: chain assembly,
  4: evidence + AI surface, 5: proof + extensions) with auditable
  exit gates
- Ownership authority table (kept; still load-bearing)
- Pointers to parking-lot, archive, and the modules-repo companion plan

Stale content removed: old "Implemented" table (51 archived items
already in changes/archive/), four separate addendum sections, tables
for backlog-scrum/kanban/safe that just said "moved to modules", a
GitHub blocked-by relationships table that duplicated each track's
own Blocked-by column, and references to changes that don't exist as
active folders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(openspec): fix Modify queue count "Five" -> "Six"

PR #551 review caught the prose/table mismatch: the Modify queue
intro said "Five active changes" but the table lists six
(integration-01, architecture-02, telemetry-01, ai-integration-02,
ai-integration-04, openspec-01). The table is the canonical list;
all six adjustments are still required before implementation.

Other count wording verified consistent:
- "five independent tracks" (line 28) refers to Tracks A-E - correct.
- Wave 1 references in the wave plan name the queue by reference,
  not by count.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(openspec): archive marketplace-06 (incorrectly parked, already shipped)

Re-validation of parked changes against git history found that
marketplace-06-ci-module-signing was substantially implemented in dev
(PRs #501, #503, #504, #505 + commit a1dda6d) before the 2026-05-05
parking triage, but its tasks.md still read 0/N done so it was placed
in parking-lot/ by mistake.

Move the change to openspec/changes/archive/2026-05-06-marketplace-06-ci-module-signing/,
mark all tasks [x] in bulk, and add an "Implementation history" banner
documenting the actual merging PRs and verified on-disk deliverables
(sign-modules-on-approval.yml, git-branch-module-signature-flag.sh,
branch-aware verify-module-signatures gating).

Update parking-lot/README.md (remove marketplace-06 row) and
CHANGE_ORDER.md status snapshot (parked 21 -> 20, archived 104 -> 105).

All 20 other core parked changes and all 9 modules-side parked changes
were re-validated and have no implementation traces; they remain
correctly parked.

* fix(openspec): sync specs from archived marketplace-06 via openspec archive

The previous commit (8443297) moved marketplace-06-ci-module-signing
into the archive directory manually, which skipped the
`openspec archive` step that syncs delta specs into main specs.

Re-ran the proper flow:
1. Move change back to openspec/changes/marketplace-06-ci-module-signing/
2. Run `openspec archive marketplace-06-ci-module-signing --yes`

That command applied the delta specs to:
- openspec/specs/ci-integration/spec.md (3 added)
- openspec/specs/ci-module-signing-on-approval/spec.md (3 added, new file)

Net effect on this PR is the proper inclusion of those spec updates;
the archive folder content from 8443297 is unchanged (banner +
checked tasks preserved).

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Refine telemetry change to active opt-in

* Address telemetry opt-in review annotations

* Align telemetry payload field names

* Clarify telemetry semantic payload contract

* Align telemetry sanitizer transition spec

* Track modules telemetry docs rollout

* Tighten telemetry implementation tasks

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
* fix runtime module discovery reliability

* fix runtime discovery review findings

* fix installed module loader test fixture

* fix review feedback on runtime discovery state isolation

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Add module scope version diagnostics

* Fix CI checks for module diagnostics

* Address module diagnostics review findings

* fix: apply CodeRabbit auto-fixes

Fixed 5 file(s) based on 4 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* fix: tighten dependency fast-path identity checks

* test: cover registry-id mismatch dependency path

* style: run hatch format and apply formatter output

* fix: resolve header typing regressions in strict type-check

* test: require registry id file for already-satisfied bundle dependency assertion

Bundle dependency installs now log at info only when the dependency
directory includes .specfact-registry-id matching the marketplace id.
Update the test fixture to mirror a normal install so CI matches installer
behavior.

Co-authored-by: Dom <djm81@users.noreply.github.com>

---------

Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
@strix-security
Copy link
Copy Markdown

Strix is installed on this repository, but we could not run this PR security review because this workspace does not have an active plan. If you'd like to continue receiving code reviews, you can add a payment method or manage billing here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Review Change Stack

📝 Walkthrough

Module Scope Version Diagnostics and Dependency Enforcement

OpenSpec Change: module-scope-version-diagnostics (tracks feature #353, fixes issue #565)

User-Visible Behavior & CLI Surface

  • New command: specfact module doctor [MODULE_ID] [--repo PATH]

    • Diagnoses module scope and shadowing across project, user, marketplace, custom, and development source roots
    • Displays effective vs. shadowed duplicate modules with versions, origins, paths, and enabled status
    • Reports configured development source roots from SPECFACT_* environment variables
    • Provides recovery guidance (e.g., specfact module uninstall nold-ai/module-id --scope user) for shadowed copies
  • Installation enforcement: Bundle dependency version constraints now enforced during module install

    • Declares versioned bundle dependencies as bundle_dependencies: [{id: namespace/name, version: ">=X.Y.Z"}, ...]
    • Fails install if existing dependency version does not satisfy declared range with actionable error
    • Fails install if newly installed version still does not satisfy range
  • Registration filtering: Modules with unsatisfied versioned module dependencies are skipped from registration

    • Module registration now validates module_dependencies_versioned compatibility
    • Dependent modules not registered when declared versioned dependencies are missing, disabled, or incompatible

Contract/API Impact

New public types & functions:

  • DiscoveredModule dataclass now includes source: str field (tracks origin: builtin, project, user, marketplace, custom, dev)
  • discover_all_modules_for_project_with_shadowed(base_path: Path | None = None) -> list[DiscoveredModule] — discovers modules including lower-priority shadowed duplicates
  • VersionedModuleDependency Pydantic model (in module_package.py) — represents module dependency with optional version specifier
  • ModulePackageMetadata.module_dependencies_versioned: list[VersionedModuleDependency] — new metadata field

Internal module_installer.py changes (helper functions, not public API):

  • _BundleDependencySpec dataclass for parsed bundle dependency specs with version constraints
  • _extract_bundle_dependency_specs() — parses bundle dependencies into structured specs
  • _dependency_version_satisfies(installed_version: str, version_specifier: str) -> bool — checks version compatibility
  • _raise_if_dependency_version_mismatch() — enforces version constraint during install
  • Updated _install_bundle_dependencies_for_module() to validate installed dependency versions against specifiers

module_packages.py changes:

  • _missing_versioned_module_dependencies() helper — validates versioned module dependency compatibility
  • Updated module registration enablement checks to pass module_versions map and report version mismatches
  • Dependency graph expansion for --force safety now includes module_dependencies_versioned

module_state.py changes:

  • find_dependents() now considers both module_dependencies and module_dependencies_versioned when determining enabled dependents

Testing & Quality Gates

  • Unit tests: 96 passed (up from baseline)

    • New doctor command tests: effective/shadowed detection, fully-qualified namespace matching, dev roots reporting
    • New installer tests: registry ID collision handling, bundle dependency version mismatch rejection
    • New versioned dependency tests: version satisfaction, specifier forms (exact, range, caret, tilde), missing/disabled scenarios
    • New publish script tests: bundle dependency serialization and validation
  • Quality gates passing:

    • hatch run format (code style)
    • openspec validate module-scope-version-diagnostics --strict (OpenSpec compliance)
    • Scoped basedpyright checks (0 errors on changed modules)
    • hatch run specfact code review (0 blocking findings, 17 warnings are pre-existing patterns)
  • Real-world smoke tests passing:

    • Doctor command correctly identifies effective vs. shadowed duplicates across project/user scopes with recovery commands
    • Installer correctly rejects installation when existing dependency version is outside required range

Documentation & Versioning

  • CHANGELOG.md: 3 new release entries (0.46.22, 0.46.21, 0.46.20) documenting module review/diagnostics fixes and the new module doctor feature
  • docs/module-system/installing-modules.md: Added specfact module doctor usage, expanded bundle dependency version resolution behavior
  • docs/module-system/module-marketplace.md: Added origin column, --show-origin and module doctor documentation, shadowed duplicate recovery guidance
  • openspec/changes/module-scope-version-diagnostics/: Complete design docs, proposal, specifications, and task checklist
  • Version bumps:
    • Package version: 0.46.19 → 0.46.22 (pyproject.toml, setup.py, src/init.py, src/specfact_cli/init.py)
    • Module package: 0.1.24 → 0.1.26 (integrity.checksum and signature updated)

Additional Changes

  • scripts/publish-module.py: New _bundle_dependency_ids_for_registry() helper validates and normalizes bundle dependencies for registry publication
  • scripts/verify-modules-signature.py: Refactored checksum/signature verification path; verify_checksum=False now only validates checksum exists, signature verification only in verify_checksum=True path
  • Type annotations: Header handling broadened across GitHub and Azure DevOps adapters to support dict[str, str | bytes] (supporting auth token encoding)
  • CLI lazy command delegation: Fixed _LazyDelegateGroup.get_command() to return constructed delegate command for known bundle groups

Walkthrough

This PR implements module scope version diagnostics and enforces versioned module dependency constraints. It introduces specfact module doctor CLI to reveal effective vs. shadowed module copies across project/user/marketplace scopes with recovery commands, adds bundle dependency version validation during installation and registration, hardens HTTP adapter type safety with dict[str, str | bytes] headers and explicit cast(Any, ...) payloads, and bumps the version from 0.46.19 to 0.46.22 with corresponding changelog and OpenSpec artifacts.

Changes

Module Scope Diagnostics and Versioned Dependency Enforcement

Layer / File(s) Summary
Module doctor CLI command and discovery
src/specfact_cli/modules/module_registry/src/commands.py
New specfact module doctor command discovers modules with shadowing, filters by optional module ID, computes effective/shadowed/disabled status, and displays diagnostic table with optional development source roots.
Versioned bundle dependency spec and parsing
src/specfact_cli/registry/module_installer.py
Introduces _BundleDependencySpec dataclass and parsing logic to extract module ID and optional version specifier from manifest bundle_dependencies entries (strings or objects).
Dependency version satisfaction checking
src/specfact_cli/registry/module_installer.py
Adds _dependency_version_satisfies() and _raise_if_dependency_version_mismatch() helpers using packaging.SpecifierSet to validate installed versions against declared constraints, treating malformed specs as debug logs.
Module installer versioned dependency enforcement
src/specfact_cli/registry/module_installer.py
Updates _install_bundle_dependencies_for_module to check registry ID files and enforce version constraints for already-present dependencies; raises ValueError on version mismatches.
Module registration with versioned dependency validation
src/specfact_cli/registry/module_packages.py
Extends _validate_module_dependencies, validate_enable_safe, and dependency graph expansion to support module_dependencies_versioned; computes and passes module_versions map during registration to skip ineligible modules with unsatisfied version requirements.
Module state dependency tracking
src/specfact_cli/registry/module_state.py
find_dependents() now includes versioned module dependencies when determining dependent modules.
Publishing script normalization and CLI delegate fix
scripts/publish-module.py, src/specfact_cli/cli.py
Bundle dependency publishing normalizes dependency IDs via new _bundle_dependency_ids_for_registry helper; CLI lazy delegate's get_command returns delegate command for known bundle group/shim names.
Test coverage for diagnostics and versioned dependencies
tests/unit/modules/module_registry/test_commands.py, tests/unit/specfact_cli/registry/test_*.py, tests/unit/registry/test_module_installer.py, tests/unit/scripts/test_publish_module_bundle.py
New tests validate doctor command output (effective/shadowed modules, exact ID matching, dev roots), versioned dependency detection and enforcement (version mismatches, satisfied constraints, registration eligibility), installer reinstall scenarios (registry ID mismatches, version violations), and publish entry serialization.
User documentation and OpenSpec artifacts
docs/module-system/installing-modules.md, docs/module-system/module-marketplace.md, openspec/changes/module-scope-version-diagnostics/
Documented specfact module doctor usage and recovery guidance; OpenSpec proposal, design, specs, TDD evidence, and CHANGE_ORDER reflect requirements and test outcomes.

HTTP Adapter Type Safety Hardening

Layer / File(s) Summary
GitHub adapter header and payload typing
src/specfact_cli/adapters/github.py
All GitHub API request methods now declare headers: dict[str, str | bytes] locally and wrap JSON payloads with cast(Any, ...) across issue search, creation, status, comments, snapshots, body updates, GraphQL, and backlog operations.
Azure DevOps adapter header and payload typing
src/specfact_cli/adapters/ado.py
ADO _auth_headers() return type and request method header parameters widened to dict[str, str | bytes]; JSON payloads wrapped with cast(Any, ...) in work item, proposal, comment, and backlog patch operations.
Bridge sync and proposal validator typing
src/specfact_cli/sync/bridge_sync.py, src/specfact_cli/validators/change_proposal_integration.py
Bridge sync and proposal integration now declare headers as dict[str, str | bytes] and add cast(Any, ...) to GitHub PATCH/POST JSON payloads.
Adapter test updates
tests/unit/adapters/test_ado_backlog_adapter.py
ADO auth tests coerce headers to str before assertion for compatibility with str | bytes return type.

Version Bump and Changelog

Layer / File(s) Summary
Version updates
pyproject.toml, setup.py, src/__init__.py, src/specfact_cli/__init__.py, src/specfact_cli/modules/module_registry/module-package.yaml, CHANGELOG.md
Version bumped 0.46.19 → 0.46.22 (module registry 0.1.24 → 0.1.26); changelog entries document module diagnostics, dependency enforcement, CI hardening, and signature PR verification fixes for versions 0.46.20–0.46.22.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR adds substantial new functionality (versioned dependency enforcement across install/registration, new diagnostic command) with widespread integration across multiple modules. The HTTP adapter type-safety changes, while numerous, are homogeneous boilerplate. The core logic density is high in module_installer.py and module_packages.py where version parsing, validation, and enforcement occur in multiple interdependent contexts. Comprehensive test coverage and OpenSpec traceability reduce risk but require careful review of edge cases (malformed specs, mixed-format dependency entries, registry ID collisions) and version constraint semantics using packaging library.

Possibly related PRs

  • nold-ai/specfact-cli#566: Directly related; both PRs add specfact module doctor and implement versioned module/bundle dependency enforcement in the same core modules (module_installer.py, module_packages.py).
  • nold-ai/specfact-cli#541: Overlapping changes to CLI lazy-delegation (_LazyDelegateGroup.get_command) in src/specfact_cli/cli.py.

Suggested labels

enhancement, documentation, QA

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR title follows Conventional Commits style with 'fix:' prefix and clearly summarizes the main change: adding module version handling.
Description check ✅ Passed PR description is comprehensive, including root cause, objectives, tracking info, and validation evidence. However, it does not follow the template structure with explicit Type of Change checkboxes or Contract-First Testing Evidence sections.
Linked Issues check ✅ Passed The PR addresses all objectives from linked issue #565: adds module doctor diagnostics, enforces versioned dependency checks at install/registration, surfaces effective vs shadowed modules, and preserves project-over-user precedence.
Out of Scope Changes check ✅ Passed All changes align with linked issue #565 scope: module diagnostics, versioned dependency enforcement, docs updates, OpenSpec artifacts, version bumps, and signature verifier fix all directly support the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@djm81 djm81 self-assigned this May 14, 2026
@djm81 djm81 added bug Something isn't working dependencies Dependency resolution and management labels May 14, 2026
@djm81 djm81 moved this from Todo to In Progress in SpecFact CLI May 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/specfact_cli/registry/module_installer.py (1)

871-911: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error message for fresh installs suggests "Reinstall" when the issue is marketplace version availability.

Lines 906-911 call _raise_if_dependency_version_mismatch after a fresh install_module call. If the marketplace version doesn't satisfy the constraint, the error message incorrectly suggests "Reinstall or upgrade the dependency" when the actual issue is that the available marketplace version doesn't meet the version requirement.

Consider using a context-aware message or a separate check for post-install validation that provides more actionable guidance (e.g., "The installed version X does not satisfy the required constraint Y. Check if a compatible version is available in the marketplace.").

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/publish-module.py`:
- Around line 393-403: The current loop coerces non-string dependency entries
(and dict id values) to strings, which allows invalid IDs like "None" or "123"
to be published; change the validation so that for dict entries the "id" field
must be an instance of str and non-empty (use raw_id and raw_dependencies to
locate the check) and for non-dict entries reject any entry that is not a str
with a ValueError instead of coercing it; ensure dependency_ids only ever
receives already-validated, stripped strings and update the error messages to
mention invalid types/empty values.

In `@src/specfact_cli/registry/module_installer.py`:
- Around line 245-260: The current _dependency_version_satisfies catches
InvalidSpecifier/InvalidVersion but logs "Ignoring..." then returns False which
triggers a hard failure downstream; change this to genuinely skip malformed
comparisons by returning True on exception and update the
get_bridge_logger(__name__).debug message in _dependency_version_satisfies to
state that the malformed version/specifier is being skipped (e.g., "Skipping
malformed bundle dependency version comparison...") so behavior and log align;
references: _dependency_version_satisfies and callers like
_raise_if_dependency_version_mismatch.

In `@src/specfact_cli/registry/module_packages.py`:
- Around line 548-585: The registration code
(_versioned_module_dependency_problem) swallows InvalidVersion/InvalidSpecifier
and treats malformed version comparisons as non-blocking; make
module_installer._dependency_version_satisfies mirror that tolerant behavior so
installer and registrar are consistent: catch InvalidVersion and
InvalidSpecifier in _dependency_version_satisfies, log a debug-level message
(similar to get_bridge_logger(...).debug usage in
_versioned_module_dependency_problem) and return True (treat as satisfied)
instead of False/raising, ensuring the function signature and callers remain
unchanged.

In `@src/specfact_cli/registry/module_state.py`:
- Around line 95-101: The current membership check uses a set union
{*module_dependencies, *versioned_dependencies} which requires items in
module_dependencies to be hashable and can raise TypeError for dict/list
entries; change the logic to perform safe string-based comparisons instead:
build a list of stringified module_dependencies (e.g., module_dependencies_str =
[str(dep) for dep in getattr(meta, "module_dependencies", [])]) reuse the
existing versioned_dependencies list, and replace the set-based check with a
safe condition like if module_id in module_dependencies_str or module_id in
versioned_dependencies to append to dependents; reference the symbols
module_dependencies, versioned_dependencies, module_id, meta, dependents, and
name when making the change.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 679026ca-fe14-4468-ade1-6adab329657d

📥 Commits

Reviewing files that changed from the base of the PR and between 775cba2 and 0c25c8a.

📒 Files selected for processing (37)
  • CHANGELOG.md
  • docs/module-system/installing-modules.md
  • docs/module-system/module-marketplace.md
  • openspec/CHANGE_ORDER.md
  • openspec/changes/module-scope-version-diagnostics/.openspec.yaml
  • openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • pyproject.toml
  • scripts/publish-module.py
  • scripts/verify-modules-signature.py
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/adapters/ado.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/cli.py
  • src/specfact_cli/modules/module_registry/module-package.yaml
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_installer.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/registry/test_module_installer.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/validators/test_bundle_dependency_install.py
💤 Files with no reviewable changes (1)
  • scripts/verify-modules-signature.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (21)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python-github-rules.mdc)

**/*.py: Maintain minimum 80% test coverage, with 100% coverage for critical paths in Python code
Use clear naming and self-documenting code, preferring clear names over comments
Ensure each function/class has a single clear purpose (Single Responsibility Principle)
Extract common patterns to avoid code duplication (DRY principle)
Apply SOLID object-oriented design principles in Python code
Use type hints everywhere in Python code and enable basedpyright strict mode
Use Pydantic models for data validation and serialization in Python
Use async/await for I/O operations in Python code
Use context managers for resource management in Python
Use dataclasses for simple data containers in Python
Enforce maximum line length of 120 characters in Python code
Use 4 spaces for indentation in Python code (no tabs)
Use 2 blank lines between classes and 1 blank line between methods in Python
Organize imports in order: Standard library → Third party → Local in Python files
Use snake_case for variables and functions in Python
Use PascalCase for class names in Python
Use UPPER_SNAKE_CASE for constants in Python
Use leading underscore (_) for private methods in Python classes
Use snake_case for Python file names
Enable basedpyright strict mode with strict type checking configuration in Python
Use Google-style docstrings for functions and classes in Python
Include comprehensive exception handling with specific exception types in Python code
Use logging with structured context (extra parameters) instead of print statements
Use retry logic with tenacity decorators (@retry) for operations that might fail
Use Pydantic BaseSettings for environment-based configuration in Python
Validate user input using Pydantic validators in Python models
Use @lru_cache and Redis-based caching for expensive calculations in Python
Run code formatting with Black (120 character line length) and isort in Python
Run type checking with basedpyright on all Python files
Run linting with ruff and pylint on all Pyth...

Files:

  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • setup.py
  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • scripts/publish-module.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/adapters/ado.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • src/specfact_cli/modules/module_registry/src/commands.py
  • tests/unit/registry/test_module_installer.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/registry/module_installer.py
{src/__init__.py,pyproject.toml,setup.py}

📄 CodeRabbit inference engine (.cursor/rules/python-github-rules.mdc)

{src/__init__.py,pyproject.toml,setup.py}: Update src/init.py first as primary source of truth for package version, then pyproject.toml and setup.py
Maintain version synchronization across src/init.py, pyproject.toml, and setup.py

Files:

  • src/__init__.py
  • pyproject.toml
  • setup.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/spec-fact-cli-rules.mdc)

src/**/*.py: All code changes must be followed by running the full test suite using the smart test system.
All Python files in src/ and tools/ directories must have corresponding test files in tests/ directory. If you modify src/common/logger_setup.py, you MUST have tests/unit/common/test_logger_setup.py. NO EXCEPTIONS - even small changes require tests.
All new Python runtime code files must have corresponding test files created BEFORE committing the code. NO EXCEPTIONS - no code without tests.
Test Coverage Validation: Run hatch run smart-test-unit for modified files, hatch run smart-test-folder for modified directories, and hatch run smart-test-full before committing. ALL TESTS MUST PASS.
All components must support TEST_MODE=true environment variable with test-specific behavior defined as: if os.environ.get('TEST_MODE') == 'true': # test-specific behavior
Use src/common/logger_setup.py for all logging via: from common.logger_setup import get_logger; logger = get_logger(name)
Use src/common/redis_client.py with fallback for Redis operations via: from common.redis_client import get_redis_client; redis_client = get_redis_client()
Type checking must pass with no errors using: mypy .
Test coverage must meet or exceed 80% total coverage. New code must have corresponding tests. Modified code must maintain or improve coverage. Critical paths must have 100% coverage.
Use Pydantic v2 validation for all context and data schemas.

Add/update contracts on new or modified public APIs, stateful classes and adapters using icontract decorators and beartype runtime type checks

src/**/*.py: Meaningful Naming — identifiers reveal intent; avoid abbreviations. Identifiers in src/ must use snake_case (modules/functions), PascalCase (classes), UPPER_SNAKE_CASE (constants). Avoid single-letter names outside short loop variables.
KISS — keep functions and modules small and single-purpose. Maximum function length: 120 lines (Phase A error threshold). Maximum cyclomati...

Files:

  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/adapters/ado.py
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/registry/module_installer.py
@(src|tests)/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/spec-fact-cli-rules.mdc)

Linting must pass with no errors using: pylint src tests

Files:

  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/adapters/ado.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • src/specfact_cli/modules/module_registry/src/commands.py
  • tests/unit/registry/test_module_installer.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/registry/module_installer.py
{pyproject.toml,setup.py,src/__init__.py}

📄 CodeRabbit inference engine (.cursor/rules/testing-and-build-guide.mdc)

Manually update version numbers in pyproject.toml, setup.py, and src/init.py when making a formal version change

Files:

  • src/__init__.py
  • pyproject.toml
  • setup.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{py,pyi}: After any code changes, follow these steps in order: (1) Apply linting and formatting to ensure code quality: hatch run format, (2) Type checking: hatch run type-check (basedpyright), (3) Contract-first approach: Run hatch run contract-test for contract validation, (4) Run full test suite: hatch test --cover -v, (5) Verify all tests pass and contracts are satisfied, (6) Fix any issues and repeat steps until all tests pass
All public APIs must have @icontract decorators and @beartype type checking
Use Pydantic models for all data structures with data validation
Only write high-value comments if at all. Avoid talking to the user through comments

Files:

  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • setup.py
  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • scripts/publish-module.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/adapters/ado.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • src/specfact_cli/modules/module_registry/src/commands.py
  • tests/unit/registry/test_module_installer.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/registry/module_installer.py
**/*.{md,mdc}

📄 CodeRabbit inference engine (.cursor/rules/markdown-rules.mdc)

**/*.{md,mdc}: Do not use more than one consecutive blank line anywhere in the document (MD012: No Multiple Consecutive Blank Lines)
Fenced code blocks should be surrounded by blank lines (MD031: Fenced Code Blocks)
Lists should be surrounded by blank lines (MD032: Lists)
Files must end with a single empty line (MD047: Files Must End With Single Newline)
Lines should not have trailing spaces (MD009: No Trailing Spaces)
Use asterisks (**) for strong emphasis, not underscores (__) (MD050: Strong Style)
Fenced code blocks must have a language specified (MD040: Fenced Code Language)
Headers should increment by one level at a time (MD001: Header Increment)
Headers should be surrounded by blank lines (MD022: Headers Should Be Surrounded By Blank Lines)
Only one top-level header (H1) is allowed per document (MD025: Single H1 Header)
Use consistent list markers, preferring dashes (-) for unordered lists (MD004: List Style)
Nested unordered list items should be indented consistently, typically by 2 spaces (MD007: Unordered List Indentation)
Use exactly one space after the list marker (e.g., -, *, +, 1.) (MD030: Spaces After List Markers)
Use incrementing numbers for ordered lists (MD029: Ordered List Item Prefix)
Enclose bare URLs in angle brackets or format them as links (MD034: Bare URLs)
Don't use spaces immediately inside code spans (MD038: Spaces Inside Code Spans)
Use consistent indentation (usually 2 or 4 spaces) throughout markdown files
Keep line length under 120 characters in markdown files
Use reference-style links for better readability in markdown files
Use a trailing slash for directory paths in markdown files
Ensure proper escaping of special characters in markdown files

Files:

  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/CHANGE_ORDER.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • docs/module-system/installing-modules.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
  • openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md
  • docs/module-system/module-marketplace.md
  • CHANGELOG.md
**/*.md

📄 CodeRabbit inference engine (.cursorrules)

Avoid markdown linting errors (refer to markdown-rules)

Files:

  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/CHANGE_ORDER.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • docs/module-system/installing-modules.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
  • openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md
  • docs/module-system/module-marketplace.md
  • CHANGELOG.md
openspec/changes/**/*.md

📄 CodeRabbit inference engine (.cursorrules)

For /opsx:archive (Archive change): Include module signing and cleanup in final tasks. Agents MUST run openspec archive <change-id> from repo root (no manual mv under openspec/changes/archive/)

Files:

  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
  • openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md
openspec/**/{proposal.md,tasks.md,design.md,spec.md}

📄 CodeRabbit inference engine (.cursor/rules/automatic-openspec-workflow.mdc)

openspec/**/{proposal.md,tasks.md,design.md,spec.md}: Apply openspec/config.yaml project context and per-artifact rules (for proposal, specs, design, tasks) when creating or updating any OpenSpec change artifact in the specfact-cli codebase
After implementation, validate the change with openspec validate <change-id> --strict; fix validation errors in proposal, specs, design, or tasks and re-validate until passing before considering the change complete

Files:

  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
openspec/**/*.md

⚙️ CodeRabbit configuration file

openspec/**/*.md: Treat as specification source of truth: proposal/tasks/spec deltas vs. code behavior,
CHANGE_ORDER consistency, and scenario coverage. Surface drift between OpenSpec and
implementation.

Files:

  • openspec/changes/module-scope-version-diagnostics/tasks.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md
  • openspec/CHANGE_ORDER.md
  • openspec/changes/module-scope-version-diagnostics/design.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md
  • openspec/changes/module-scope-version-diagnostics/proposal.md
  • openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md
  • openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md
src/specfact_cli/**/*.py

⚙️ CodeRabbit configuration file

src/specfact_cli/**/*.py: Focus on modular CLI architecture: lazy module loading, registry/bootstrap patterns, and
dependency direction. Flag breaking changes to public APIs, Pydantic models, and resource
bundling. Verify @icontract + @beartype on public surfaces; prefer centralized logging
(get_bridge_logger) over print().

Files:

  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • src/specfact_cli/validators/change_proposal_integration.py
  • src/specfact_cli/registry/module_state.py
  • src/specfact_cli/sync/bridge_sync.py
  • src/specfact_cli/adapters/ado.py
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/adapters/github.py
  • src/specfact_cli/registry/module_installer.py
**/*cli*.{py,pyi}

📄 CodeRabbit inference engine (.cursorrules)

Commands should follow typer patterns with rich console output

Files:

  • src/specfact_cli/cli.py
**/*.yaml

📄 CodeRabbit inference engine (.cursor/rules/spec-fact-cli-rules.mdc)

YAML files must pass linting using: hatch run yaml-lint with relaxed policy.

Files:

  • src/specfact_cli/modules/module_registry/module-package.yaml
**/*.{yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/testing-and-build-guide.mdc)

Validate YAML configuration files locally using hatch run yaml-lint before committing

**/*.{yml,yaml}: Format all YAML and workflow files using hatch run yaml-fix-all before committing
Use Prettier to fix whitespace, indentation, and final newline across YAML files
Use yamllint with the repo .yamllint configuration (line-length 140, trailing spaces and final newline enforced) to lint non-workflow YAML files

Files:

  • src/specfact_cli/modules/module_registry/module-package.yaml
pyproject.toml

📄 CodeRabbit inference engine (.cursorrules)

When updating the version in pyproject.toml, ensure it's newer than the latest PyPI version. The CI/CD pipeline will automatically publish to PyPI only if the new version is greater than the published version

Files:

  • pyproject.toml
docs/**/*.md

📄 CodeRabbit inference engine (.cursor/rules/spec-fact-cli-rules.mdc)

Update architecture documentation in docs/ for architecture changes, state machine documentation for FSM modifications, interface documentation for API changes, and configuration guides for configuration changes. DO NOT create internal docs in specfact-cli repo folder that should not be visible to end users; use the respective internal repository instead.

Files:

  • docs/module-system/installing-modules.md
  • docs/module-system/module-marketplace.md

⚙️ CodeRabbit configuration file

docs/**/*.md: User-facing accuracy: CLI examples match current behavior; preserve Jekyll front matter;
call out when README/docs index need sync.

Files:

  • docs/module-system/installing-modules.md
  • docs/module-system/module-marketplace.md
**/test_*.py

📄 CodeRabbit inference engine (.cursor/rules/python-github-rules.mdc)

**/test_*.py: Write tests first in test-driven development (TDD) using the Red-Green-Refactor cycle
Ensure each test is independent and repeatable with no shared state between tests
Organize Python imports in tests using unittest.mock for Mock and patch
Use setup_method() for test initialization and Arrange-Act-Assert pattern in test files
Use @pytest.mark.asyncio decorator for async test functions in Python
Organize test files in structure: tests/unit/, tests/integration/, tests/e2e/ by module

Files:

  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • tests/unit/registry/test_module_installer.py
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/spec-fact-cli-rules.mdc)

Tests must be meaningful and test actual functionality, cover both success and failure cases, be independent and repeatable, and have clear, descriptive names. NO EXCEPTIONS - no placeholder or empty tests.

tests/**/*.py: Trim low-value unit tests when a contract covers the same assertion (type/shape/raises on negative checks)
Delete tests that only assert input validation, datatype/shape enforcement, or raises on negative conditions now guarded by contracts and runtime typing
Convert repeated edge-case permutations into one Hypothesis property with contracts acting as oracles

Secret redaction via LoggerSetup.redact_secrets must be covered by unit tests

Files:

  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • tests/unit/registry/test_module_installer.py

⚙️ CodeRabbit configuration file

tests/**/*.py: Contract-first testing: meaningful scenarios, not redundant assertions already covered by
contracts. Flag flakiness, environment coupling, and missing coverage for changed behavior.

Files:

  • tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py
  • tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py
  • tests/unit/scripts/test_publish_module_bundle.py
  • tests/unit/specfact_cli/registry/test_module_packages.py
  • tests/unit/validators/test_bundle_dependency_install.py
  • tests/unit/adapters/test_ado_backlog_adapter.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_dependencies.py
  • tests/unit/registry/test_module_installer.py
CHANGELOG.md

📄 CodeRabbit inference engine (.cursor/rules/python-github-rules.mdc)

Include new version entries at the top of CHANGELOG.md when updating versions

Update CHANGELOG.md with all code changes as part of version control requirements.

Update CHANGELOG.md to document all significant changes under Added, Fixed, Changed, or Removed sections when making a version change

Files:

  • CHANGELOG.md
scripts/**/*.py

⚙️ CodeRabbit configuration file

scripts/**/*.py: Deterministic tooling: subprocess safety, Hatch integration, and parity with documented
quality gates (format, type-check, module signing).

Files:

  • scripts/publish-module.py
🪛 LanguageTool
openspec/changes/module-scope-version-diagnostics/design.md

[style] ~3-~3: This phrase is redundant. Consider writing “duplicates” or “copies”.
Context: ...e priority, but diagnostics will expose duplicate copies and version drift. Dependency enforceme...

(DUPLICATE_COPY)

openspec/changes/module-scope-version-diagnostics/proposal.md

[style] ~3-~3: To elevate your writing, try using a synonym here.
Context: ...d implicit development source roots are hard to see, and declared module dependency ...

(HARD_TO)

docs/module-system/installing-modules.md

[style] ~124-~124: This phrase is redundant. Consider writing “duplicates” or “copies”.
Context: ...-only and reports effective vs shadowed duplicate copies, exact manifest versions, paths, enable...

(DUPLICATE_COPY)

openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md

[grammar] ~28-~28: Ensure spelling is correct
Context: ... the passing review run. ## Real-World Tmp Smoke - Created a temporary git repo w...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[uncategorized] ~40-~40: The official name of this software platform is spelled with a capital “H”.
Context: ... in src/specfact_cli/adapters/ado.py, src/specfact_cli/adapters/github.py, and `src/specfact_cli/validators/c...

(GITHUB)

docs/module-system/module-marketplace.md

[style] ~59-~59: This phrase is redundant. Consider writing “duplicates” or “copies”.
Context: ...e doctor` additionally reports shadowed duplicate copies, exact manifest versions, paths, enable...

(DUPLICATE_COPY)

🔀 Multi-repo context nold-ai/specfact-cli-modules

[::nold-ai/specfact-cli-modules::] tools/validate_repo_manifests.py:256–275 — repository contains validation logic that expects manifest.bundle_dependencies to be a list and checks referenced module IDs exist in the registry. This aligns with the PR’s stricter parsing/validation of bundle_dependencies.

[::nold-ai/specfact-cli-modules::] .github/workflows/publish-modules.yml:393–394 — the publish workflow copies manifest["bundle_dependencies"] into registry entries; the PR’s scripts/publish-module.py change (normalizing/validating bundle_dependencies before writing registry entries) may affect or be redundant with this workflow behavior.

[::nold-ai/specfact-cli-modules::] registry/index.json — multiple registry entries already include a bundle_dependencies array (some empty, some populated). The PR’s enforcement of versioned bundle dependencies and installer behavior means registry entries and module-package.yaml bundle_dependencies must match expected formats; tests in this repo (below) enforce that.

[::nold-ai/specfact-cli-modules::] tests/unit/test_registry_manifest_bundle_dependencies.py & tests/unit/test_validate_repo_manifests_bundle_deps.py — unit tests in this repo assert registry.bundle_dependencies must be a list and must match each package module-package.yaml bundle_dependencies. The PR’s change to serialize bundle_dependencies as list-of-ids and to reject malformed objects should be compatible but will require registry/package alignment.

[::nold-ai/specfact-cli-modules::] packages/*/module-package.yaml (e.g., packages/specfact-code-review/module-package.yaml, packages/specfact-codebase/module-package.yaml, packages/specfact-project/module-package.yaml) — these manifest files define bundle_dependencies (some empty). The PR’s new handling (including possible version specifiers in bundle_dependencies entries) means manifests in this repo may need to include only allowed shapes (string ids or dicts with id) and, if versioned constraints are used, follow the expected syntax.

[::nold-ai/specfact-cli-modules::] docs/reference/module-security.md — documents module_dependencies_versioned and pip_dependencies_versioned; matches PR additions enforcing versioned dependency checks during registration/installation.

Summary: This repository contains module manifests, registry entries, CI publish workflow, and tests that validate bundle_dependencies shape and alignment between manifests and registry. The PR’s stricter parsing/validation and support for versioned bundle/module dependencies touches the same data and CI paths; ensure manifests and registry.index.json entries conform to the new normalized/list-of-ids shape (and that publish workflow produces matching registry entries). Tests in this repo already assert bundle_dependencies alignment and will be relevant when bumping module-package.yaml entries to include version specifiers.

🔇 Additional comments (34)
pyproject.toml (1)

7-7: LGTM!

setup.py (1)

10-10: LGTM!

src/__init__.py (1)

6-6: LGTM!

src/specfact_cli/__init__.py (1)

48-48: LGTM!

src/specfact_cli/modules/module_registry/module-package.yaml (1)

2-2: LGTM!

Also applies to: 20-21

CHANGELOG.md (1)

13-47: LGTM!

src/specfact_cli/adapters/github.py (1)

564-567: LGTM!

Also applies to: 1235-1238, 1501-1504, 1595-1598, 1604-1606, 1639-1642, 1663-1666, 1671-1671, 1681-1684, 1764-1767, 1974-1977, 1992-1994, 2630-2633, 2768-2771, 2983-2986, 2990-2990, 3252-3255

src/specfact_cli/adapters/ado.py (1)

1628-1628: LGTM!

Also applies to: 1942-1942, 1956-1956, 1976-1976, 2216-2216, 2673-2673, 3481-3481, 3785-3785, 3805-3805, 3823-3823, 3837-3839

src/specfact_cli/validators/change_proposal_integration.py (1)

19-19: LGTM!

Also applies to: 169-169, 191-191

src/specfact_cli/sync/bridge_sync.py (1)

2448-2451: LGTM!

Also applies to: 2684-2687

tests/unit/adapters/test_ado_backlog_adapter.py (1)

776-776: LGTM!

Also applies to: 785-785

tests/unit/modules/module_registry/test_commands.py (1)

12-12: LGTM!

Also applies to: 74-154, 565-565, 573-573

tests/unit/specfact_cli/registry/test_module_dependencies.py (1)

7-7: LGTM!

Also applies to: 10-10, 51-145

tests/unit/specfact_cli/registry/test_module_packages.py (1)

658-690: LGTM!

tests/unit/registry/test_module_installer.py (1)

143-143: LGTM!

Also applies to: 159-203, 205-242

tests/unit/scripts/test_publish_module_bundle.py (1)

105-122: LGTM!

Also applies to: 124-152

tests/unit/specfact_cli/registry/test_versioned_bundle_deps.py (1)

13-15: LGTM!

Also applies to: 78-95

tests/unit/specfact_cli/modules/test_module_upgrade_improvements.py (1)

259-260: LGTM!

tests/unit/validators/test_bundle_dependency_install.py (1)

11-11: LGTM!

Also applies to: 146-146

src/specfact_cli/registry/module_installer.py (3)

77-81: LGTM!


192-228: LGTM!


263-275: LGTM!

src/specfact_cli/registry/module_packages.py (5)

349-381: LGTM!


503-545: LGTM!


627-627: LGTM!

Also applies to: 634-637


657-660: LGTM!

Also applies to: 690-695


1619-1619: LGTM!

Also applies to: 1634-1636

openspec/changes/module-scope-version-diagnostics/.openspec.yaml (1)

1-2: LGTM!

openspec/changes/module-scope-version-diagnostics/specs/module-installation/spec.md (1)

1-21: LGTM!

openspec/changes/module-scope-version-diagnostics/specs/module-scope-diagnostics/spec.md (1)

1-21: LGTM!

openspec/changes/module-scope-version-diagnostics/specs/module-packages/spec.md (1)

1-21: LGTM!

openspec/changes/module-scope-version-diagnostics/TDD_EVIDENCE.md (1)

1-42: LGTM!

openspec/CHANGE_ORDER.md (1)

11-11: LGTM!

Also applies to: 28-28, 93-93

openspec/changes/module-scope-version-diagnostics/tasks.md (1)

1-25: LGTM!

Comment thread scripts/publish-module.py
Comment on lines +393 to +403
for entry in raw_dependencies:
if isinstance(entry, dict):
raw_id = entry.get("id")
if raw_id is None or not str(raw_id).strip():
raise ValueError(f"bundle_dependencies object entry must include non-empty 'id'; got {entry!r}")
dependency_ids.append(str(raw_id).strip())
continue
dependency_id = str(entry).strip()
if dependency_id:
dependency_ids.append(dependency_id)
return dependency_ids
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject non-string bundle dependency entries instead of coercing them

Line 400 coerces arbitrary values to strings. That can publish invalid IDs like "None"/"123" into registry metadata and break dependency enforcement downstream.

Proposed fix
     for entry in raw_dependencies:
         if isinstance(entry, dict):
             raw_id = entry.get("id")
             if raw_id is None or not str(raw_id).strip():
                 raise ValueError(f"bundle_dependencies object entry must include non-empty 'id'; got {entry!r}")
             dependency_ids.append(str(raw_id).strip())
             continue
-        dependency_id = str(entry).strip()
-        if dependency_id:
-            dependency_ids.append(dependency_id)
+        if not isinstance(entry, str):
+            raise ValueError(
+                "bundle_dependencies entries must be strings or objects with non-empty 'id'; "
+                f"got {type(entry).__name__}: {entry!r}"
+            )
+        dependency_id = entry.strip()
+        if not dependency_id:
+            raise ValueError("bundle_dependencies string entries must be non-empty")
+        dependency_ids.append(dependency_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/publish-module.py` around lines 393 - 403, The current loop coerces
non-string dependency entries (and dict id values) to strings, which allows
invalid IDs like "None" or "123" to be published; change the validation so that
for dict entries the "id" field must be an instance of str and non-empty (use
raw_id and raw_dependencies to locate the check) and for non-dict entries reject
any entry that is not a str with a ValueError instead of coercing it; ensure
dependency_ids only ever receives already-validated, stripped strings and update
the error messages to mention invalid types/empty values.

Comment on lines +245 to +260
@beartype
def _dependency_version_satisfies(installed_version: str, version_specifier: str) -> bool:
"""Return True when installed_version satisfies version_specifier or no specifier is declared."""
if not version_specifier:
return True
try:
return Version(installed_version) in SpecifierSet(version_specifier)
except (InvalidSpecifier, InvalidVersion) as exc:
get_bridge_logger(__name__).debug(
"Ignoring malformed bundle dependency version comparison: installed_version=%r, "
"version_specifier=%r, error=%s",
installed_version,
version_specifier,
exc,
)
return False
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Log message says "Ignoring" but behavior is to fail.

When InvalidSpecifier or InvalidVersion is caught, the log message says "Ignoring malformed bundle dependency version comparison" yet returns False, which causes _raise_if_dependency_version_mismatch to raise a ValueError. The message implies the check is skipped when it actually causes a hard failure.

Consider returning True to genuinely skip malformed comparisons (matching existing tolerance mentioned in the AI summary), or update the log message to reflect that this will cause a failure.

Option A: Truly ignore malformed comparisons (tolerant)
     except (InvalidSpecifier, InvalidVersion) as exc:
         get_bridge_logger(__name__).debug(
-            "Ignoring malformed bundle dependency version comparison: installed_version=%r, "
+            "Skipping malformed bundle dependency version comparison (treating as satisfied): installed_version=%r, "
             "version_specifier=%r, error=%s",
             installed_version,
             version_specifier,
             exc,
         )
-        return False
+        return True
Option B: Fix the log message to reflect actual behavior
     except (InvalidSpecifier, InvalidVersion) as exc:
-        get_bridge_logger(__name__).debug(
-            "Ignoring malformed bundle dependency version comparison: installed_version=%r, "
+        get_bridge_logger(__name__).warning(
+            "Malformed bundle dependency version comparison (treating as unsatisfied): installed_version=%r, "
             "version_specifier=%r, error=%s",
             installed_version,
             version_specifier,
             exc,
         )
         return False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/specfact_cli/registry/module_installer.py` around lines 245 - 260, The
current _dependency_version_satisfies catches InvalidSpecifier/InvalidVersion
but logs "Ignoring..." then returns False which triggers a hard failure
downstream; change this to genuinely skip malformed comparisons by returning
True on exception and update the get_bridge_logger(__name__).debug message in
_dependency_version_satisfies to state that the malformed version/specifier is
being skipped (e.g., "Skipping malformed bundle dependency version
comparison...") so behavior and log align; references:
_dependency_version_satisfies and callers like
_raise_if_dependency_version_mismatch.

Comment on lines +548 to +585
@beartype
def _versioned_module_dependency_problem(
dep: Any,
enabled_map: dict[str, bool],
module_versions: dict[str, str] | None,
) -> str | None:
"""Return the validation problem for one versioned dependency, if any."""
dep_name = str(getattr(dep, "name", "")).strip()
version_specifier = str(getattr(dep, "version_specifier", "") or "").strip()
if not dep_name:
return None
if dep_name not in enabled_map:
return f"{dep_name} (not found)"
if not enabled_map[dep_name]:
return f"{dep_name} (disabled)"
if not version_specifier:
return None
if module_versions is None:
return None
if dep_name not in module_versions:
return f"{dep_name} (requires {version_specifier}, found unknown)"
found_version = module_versions.get(dep_name)
if not found_version:
return f"{dep_name} (requires {version_specifier}, found unknown)"
try:
if Version(found_version) not in SpecifierSet(version_specifier):
return f"{dep_name} (requires {version_specifier}, found {found_version})"
except (InvalidVersion, InvalidSpecifier) as exc:
get_bridge_logger(__name__).debug(
"Ignoring malformed module dependency version comparison: dep_name=%r, "
"version_specifier=%r, found_version=%r, error=%s",
dep_name,
version_specifier,
found_version,
exc,
)
return None
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Inconsistent handling of malformed versions between installer and registration.

_versioned_module_dependency_problem returns None (no problem, passes validation) when InvalidVersion or InvalidSpecifier is caught (lines 575-584). However, _dependency_version_satisfies in module_installer.py returns False (fails validation) for the same scenario.

This inconsistency means:

  • Registration time: A module with a malformed version specifier will pass dependency validation and be registered.
  • Install time: The same module will fail with a ValueError when installing its bundle dependencies.

Both should behave consistently. Based on the AI summary ("treating malformed specifiers/versions as non-blocking debug logs to match existing tolerance"), returning None/True (tolerant) appears to be the intended behavior. Align module_installer._dependency_version_satisfies to return True on malformed comparisons, or vice versa.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/specfact_cli/registry/module_packages.py` around lines 548 - 585, The
registration code (_versioned_module_dependency_problem) swallows
InvalidVersion/InvalidSpecifier and treats malformed version comparisons as
non-blocking; make module_installer._dependency_version_satisfies mirror that
tolerant behavior so installer and registrar are consistent: catch
InvalidVersion and InvalidSpecifier in _dependency_version_satisfies, log a
debug-level message (similar to get_bridge_logger(...).debug usage in
_versioned_module_dependency_problem) and return True (treat as satisfied)
instead of False/raising, ensuring the function signature and callers remain
unchanged.

Comment on lines 95 to 101
module_dependencies = list(getattr(meta, "module_dependencies", []))
if module_id in module_dependencies:
versioned_dependencies = [
str(getattr(dependency, "name", ""))
for dependency in list(getattr(meta, "module_dependencies_versioned", []))
]
if module_id in {*module_dependencies, *versioned_dependencies}:
dependents.append(name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid hash-based membership on raw dependency entries

Line 100 now requires every module_dependencies item to be hashable. If metadata contains an unhashable item (e.g., dict/list), this throws TypeError and breaks dependent detection.

Proposed fix
-        module_dependencies = list(getattr(meta, "module_dependencies", []))
-        versioned_dependencies = [
-            str(getattr(dependency, "name", ""))
-            for dependency in list(getattr(meta, "module_dependencies_versioned", []))
-        ]
-        if module_id in {*module_dependencies, *versioned_dependencies}:
+        module_dependencies = [
+            str(dependency).strip()
+            for dependency in list(getattr(meta, "module_dependencies", []))
+            if str(dependency).strip()
+        ]
+        versioned_dependencies = [
+            str(getattr(dependency, "name", "")).strip()
+            for dependency in list(getattr(meta, "module_dependencies_versioned", []))
+            if str(getattr(dependency, "name", "")).strip()
+        ]
+        if module_id in module_dependencies or module_id in versioned_dependencies:
             dependents.append(name)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/specfact_cli/registry/module_state.py` around lines 95 - 101, The current
membership check uses a set union {*module_dependencies,
*versioned_dependencies} which requires items in module_dependencies to be
hashable and can raise TypeError for dict/list entries; change the logic to
perform safe string-based comparisons instead: build a list of stringified
module_dependencies (e.g., module_dependencies_str = [str(dep) for dep in
getattr(meta, "module_dependencies", [])]) reuse the existing
versioned_dependencies list, and replace the set-based check with a safe
condition like if module_id in module_dependencies_str or module_id in
versioned_dependencies to append to dependents; reference the symbols
module_dependencies, versioned_dependencies, module_id, meta, dependents, and
name when making the change.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

An unexpected error occurred while generating fixes: Repository rule violations found

Changes must be made through a pull request.

11 of 11 required status checks are expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Dependency resolution and management

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Module scope version diagnostics and dependency mismatch handling

1 participant