Skip to content

[codex] stabilize module install and init state#535

Merged
djm81 merged 5 commits intodevfrom
bugfix/module-install-state-consistency
Apr 28, 2026
Merged

[codex] stabilize module install and init state#535
djm81 merged 5 commits intodevfrom
bugfix/module-install-state-consistency

Conversation

@djm81
Copy link
Copy Markdown
Collaborator

@djm81 djm81 commented Apr 28, 2026

What changed

This PR fixes the inconsistent module state behavior around install, upgrade, uninstall, and init --profile.

Root cause

Module availability checks were mixing discovery results with state writes, and the install repair path could overwrite unrelated modules.json rows. That made repeated runs across different repos or environments behave unreliably.

Impact

  • specfact module install now repairs disabled installed modules without dropping unrelated state.
  • Missing-command detection now distinguishes absent, disabled, skipped, shadowed, and ambiguous cases more precisely.
  • specfact init --repo ... --profile/--install ... refreshes module state using repo-aware discovery while preserving unrelated rows.
  • Reality-test coverage now exercises module install, upgrade, uninstall, and profile-init flows against a real project fixture.
  • Version sources were bumped together and documented in CHANGELOG.md.

Validation

  • hatch run lint
  • hatch run pytest on the touched-scope suite
  • hatch run specfact code review run --json --out .specfact/code-review.changed.json --scope changed
  • hatch run specfact code review run --bug-hunt --json --out .specfact/code-review.bug-hunt.json --scope changed
  • openspec validate marketplace-07-module-install-state-consistency --strict
  • real CLI reality test with hatch run specfact

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a metadata-only module availability classifier, repo-scoped discovery and preserve-merge semantics for init, install-time enable/repair of disabled modules, enhanced missing-command diagnostics, associated OpenSpec docs/tests, and bumps package/module versions to 0.46.7.

Changes

Cohort / File(s) Summary
Version & Release
CHANGELOG.md, pyproject.toml, setup.py, src/__init__.py, src/specfact_cli/__init__.py
Bumped project and package versions to 0.46.7 and prepended changelog entries for 0.46.5–0.46.7.
Docs & OpenSpec
docs/core-cli/init.md, docs/module-system/installing-modules.md, openspec/changes/marketplace-07-module-install-state-consistency/...
Added/updated docs, design, proposal, specs, tasks, and TDD evidence for module install-state consistency and diagnostics behavior.
CLI UX (missing-command)
src/specfact_cli/cli.py, tests/unit/specfact_cli/test_module_not_found_error.py
Missing-command path now queries availability classifier and emits targeted remediation for DISABLED/SKIPPED/SHADOWED; tests adapted to new output approach.
Availability Classifier
src/specfact_cli/registry/module_availability.py, tests/unit/specfact_cli/registry/test_module_availability.py
New enum/dataclass and classify_module_availability() returning ABSENT/AVAILABLE/DISABLED/SKIPPED/SHADOWED/AMBIGUOUS with reason/recovery metadata; unit tests added.
Discovery / Packages / Merge
src/specfact_cli/registry/module_discovery.py, src/specfact_cli/registry/module_packages.py, tests/unit/specfact_cli/registry/test_init_module_state.py
Refactored discovery with centralized root/options and project-scoped entrypoints; discovery/package functions accept base_path and preserve_existing to retain unseen persisted rows during merges.
Init command & bundles
src/specfact_cli/modules/init/src/commands.py, src/specfact_cli/modules/init/module-package.yaml, tests/unit/modules/init/test_first_run_selection.py
Refactored profile/bundle flow to return enabled marketplace IDs; init uses repo-scoped discovery and _refresh_init_module_state(..., preserve_existing=True) and persists only when modules returned; module-package metadata bumped; test added.
Module Registry Install
src/specfact_cli/modules/module_registry/src/commands.py, src/specfact_cli/modules/module_registry/module-package.yaml, tests/unit/modules/module_registry/test_commands.py
Install logic now resolves canonical module id, enables/repairs disabled lifecycle state (with discovery/state refresh and persistence), normalizes project --repo to workspace root for discovery, and reports canonical ids in output; tests added.
Module package manifests
src/specfact_cli/modules/init/module-package.yaml, src/specfact_cli/modules/module_registry/module-package.yaml
Bumped module-package versions and updated integrity checksums; removed legacy signature fields.
Semgrep / Review env
scripts/pre_commit_code_review.py, src/specfact_cli/analyzers/code_analyzer.py, tests/unit/.../test_pre_commit_code_review.py, tests/unit/analyzers/test_code_analyzer.py
Review and Semgrep subprocess environments now use deterministic repo-local .specfact dirs and set XDG/SEMGREP env vars; helpers added and tests updated to assert env and directory creation.
Tests & Test harness updates
tests/unit/... (multiple files)
Added/updated unit tests for availability classification, init lifecycle merging, install re-enable, and adjusted fixtures/monkeypatch signatures and adapter-context usage.
Misc / Refactors
src/specfact_cli/modules/init/src/commands.py, scripts/pre_commit_code_review.py
Tightened exception handling, refactored helper return types, added review runtime-dir helper, and updated pre-commit env construction.

Sequence Diagram

sequenceDiagram
    autonumber
    participant User as "CLI (specfact)"
    participant CLI as "Missing-Command Handler"
    participant Classifier as "Module Availability Classifier"
    participant Discover as "Module Discovery"
    participant Packages as "Package/State Merge"
    participant State as "Persisted State (modules.json)"
    rect rgba(200,200,255,0.5)
    User->>CLI: invoke command for group X
    CLI->>Classifier: classify_module_availability(module_id?, command_name?, base_path?)
    Classifier-->>CLI: ModuleAvailability(status, module_id, package_dir, reason, recovery_command)
    alt status == DISABLED or SKIPPED or SHADOWED
        CLI-->>User: print targeted remediation (enable/repair/skip reason/shadow info)
    else status == ABSENT
        CLI-->>User: print generic install/init guidance
    end
    end
    rect rgba(200,255,200,0.5)
    User->>CLI: run init --repo <path> --profile <id>
    CLI->>Discover: discover_all_modules_for_project(base_path=<path>)
    Discover-->>Packages: discovered_modules
    CLI->>Packages: get_discovered_modules_for_state(enable_ids=[profile ids], base_path=<path>, preserve_existing=true)
    Packages->>State: read_modules_state()
    State-->>Packages: existing_state
    Packages->>Packages: merge(discovered, existing_state, preserve_existing=true)
    Packages-->>State: write_modules_state(merged_module_list)
    State-->>CLI: success
    CLI-->>User: output (enabled modules + preserved unrelated state)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Title check ⚠️ Warning Title does not follow Conventional Commits style; missing prefix like 'fix:', 'feat:', or 'chore:'. Adopt Conventional Commits format: 'fix: stabilize module install and init state' or 'chore: stabilize module install and init state'.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive Description provides clear changes and validation but lacks formal structure matching the template sections and omits contract validation evidence required for CLI command changes. Expand description to include 'Type of Change' checklist, 'Contract-First Testing Evidence' section, 'How Has This Been Tested?' details, and 'Quality Gates Status' checklist to match template requirements.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/module-install-state-consistency

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

@djm81 djm81 self-assigned this Apr 28, 2026
@djm81 djm81 added bug Something isn't working enhancement New feature or request dependencies Dependency resolution and management labels Apr 28, 2026
@djm81 djm81 moved this from Todo to In Progress in SpecFact CLI Apr 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c92dbff0c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/specfact_cli/modules/module_registry/src/commands.py Outdated
Comment thread src/specfact_cli/registry/module_availability.py
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: 8

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/modules/module_registry/src/commands.py (1)

208-239: ⚠️ Potential issue | 🟠 Major

Use canonical availability here, not requested_name lookup.

Line 232 looks up discovered_by_name with requested_name, but that map is keyed by entry.metadata.name. For canonical manifests such as nold-ai/specfact-codebase, both specfact module install specfact-codebase and specfact module install nold-ai/specfact-codebase miss the existing local copy and fall through to bundled/marketplace install. Even when the lookup does hit, this branch still treats every discovered local copy as “already available”, so disabled/skipped/shadowed cases bypass the new repair and recovery behavior. Resolve the canonical manifest id first and drive this short-circuit from the shared availability classifier.

As per coding guidelines, openspec/**/*.md: Treat as specification source of truth: proposal/tasks/spec deltas vs. code behavior, CHANGE_ORDER consistency, and scenario coverage.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 15-17: Expand the 0.46.5 entry so the changelog documents all
significant changes instead of one compressed "Fixed" bullet: keep the existing
Fixed line for "Module install and init" (the current bullet text) and add
separate bullets under "Changed" and/or "Added" to explicitly mention the
diagnostics behavior changes and the new reality-test coverage, using clear
short titles like "Changed: diagnostics behavior" and "Added: reality-test
coverage" so downstream users can trace the updates.
- Line 17: The markdown bullet starting with "**Module install and init**" is
over 120 characters; reflow that single-line bullet into multiple wrapped lines
(preserving the bold header and sentence content) so no line exceeds 120 chars,
breaking at word boundaries and keeping continuation lines as plain wrapped text
under the same bullet; ensure the final wrapped lines read naturally and keep
the original meaning.

In `@src/specfact_cli/modules/module_registry/src/commands.py`:
- Around line 198-205: The helper _enable_if_disabled currently calls
get_discovered_modules_for_state without passing the selected repo/base path, so
modules that exist only under a non-default repo (e.g., invoked via module
install --scope project --repo /other/repo) are not rediscovered and the state
stays unchanged; update _enable_if_disabled(module_id: str) to accept and thread
a base_path (or repo) through to get_discovered_modules_for_state (i.e., call
get_discovered_modules_for_state(enable_ids=[module_id], disable_ids=[],
preserve_existing=True, base_path=base_path)) and ensure callers pass the
selected repo/base path when invoking _enable_if_disabled so the disabled-state
repair actually discovers modules in the target repository before writing state
and returning True.

In `@src/specfact_cli/registry/module_availability.py`:
- Around line 14-18: The availability classifier imports two private helpers
(_check_core_compatibility and _validate_module_dependencies) from
module_packages, creating tight coupling; extract these helpers into a new
shared module (e.g., module_checks.py) with public names
(check_core_compatibility, validate_module_dependencies), update
module_availability to import the new public functions, and adjust
module_packages to either import and re-export the shared functions or call them
from module_checks to preserve behavior; ensure all references to the
underscore-prefixed names in module_availability, module_packages, and tests are
updated to the new public names and that module_checks has clear public API
contracts and importable symbols.
- Around line 88-95: _add a clear docstring on the _skip_reason function stating
it performs metadata-only classification (core compatibility and dependency
checks) and intentionally omits deeper schema and integrity validations for
performance; mention that schema/integrity checks are performed elsewhere for
requested install/command paths. Reference the function name _skip_reason and
the types DiscoveredModule/enabled_map in the docstring so reviewers can find
the intended behavior quickly. Ensure the docstring is concise and placed
immediately above the _skip_reason definition.
- Around line 22-30: The ModuleAvailabilityStatus enum defines an unused value
AMBIGUOUS; update the enum or its documentation to remove ambiguity: either
delete the AMBIGUOUS member from ModuleAvailabilityStatus so the enum only
contains the actual returned states, or retain AMBIGUOUS but add an explicit
comment on ModuleAvailabilityStatus explaining it is reserved for future use
(and why) and add a TODO or contract note near classify_module_availability to
indicate why it currently doesn't return AMBIGUOUS; reference
ModuleAvailabilityStatus and classify_module_availability when making the
change.

In `@src/specfact_cli/registry/module_packages.py`:
- Around line 1427-1428: Add an icontract precondition to the function that
declares the parameters base_path and preserve_existing to enforce semantics:
require that if preserve_existing is True then base_path is not None (e.g.
`@icontract.require`(lambda preserve_existing, base_path: not preserve_existing or
base_path is not None, "When preserve_existing is True, base_path must be
provided")). Ensure icontract is imported and update the function docstring to
state this contract.

In `@tests/unit/modules/module_registry/test_commands.py`:
- Around line 119-126: The mock uses a lambda with an "or" side-effect trick
(enabled.append(... ) or [...]) which is terse but odd; replace the lambda with
a clearly named helper function (e.g., mock_list_modules or _mock_list_modules)
that accepts the same keyword args (enable_ids, disable_ids, preserve_existing),
performs enabled.append(list(enable_ids)), and then returns the list of module
dicts, and use that function in place of the lambda to make the side-effect
explicit and readable.
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: c43fa3df-7f4a-4bb7-a7fe-fa9d2d498181

📥 Commits

Reviewing files that changed from the base of the PR and between ce3bbb4 and c92dbff.

📒 Files selected for processing (28)
  • CHANGELOG.md
  • docs/core-cli/init.md
  • docs/module-system/installing-modules.md
  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • pyproject.toml
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/cli.py
  • src/specfact_cli/modules/init/module-package.yaml
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/modules/module_registry/module-package.yaml
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/registry/module_packages.py
  • tests/unit/modules/init/test_first_run_selection.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_init_module_state.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/specfact_cli/test_module_not_found_error.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Compatibility (Python 3.11)
  • GitHub Check: Linting (ruff, pylint, safe-write guard)
  • GitHub Check: Tests (Python 3.12)
  • GitHub Check: Socket Security: Pull Request Alerts
🧰 Additional context used
📓 Path-based instructions (20)
**/*.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/cli.py
  • tests/unit/specfact_cli/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • src/specfact_cli/__init__.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • setup.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/registry/module_discovery.py
  • tests/unit/specfact_cli/test_module_not_found_error.py
  • src/specfact_cli/modules/module_registry/src/commands.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/cli.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.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/cli.py
  • tests/unit/specfact_cli/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • src/specfact_cli/__init__.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/registry/module_discovery.py
  • tests/unit/specfact_cli/test_module_not_found_error.py
  • src/specfact_cli/modules/module_registry/src/commands.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/cli.py
  • tests/unit/specfact_cli/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • src/specfact_cli/__init__.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • setup.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/registry/module_discovery.py
  • tests/unit/specfact_cli/test_module_not_found_error.py
  • src/specfact_cli/modules/module_registry/src/commands.py
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
**/*.{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:

  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • CHANGELOG.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.md
  • docs/module-system/installing-modules.md
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/core-cli/init.md
  • docs/module-system/installing-modules.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/core-cli/init.md
  • docs/module-system/installing-modules.md
**/*.md

📄 CodeRabbit inference engine (.cursorrules)

Avoid markdown linting errors (refer to markdown-rules)

Files:

  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • CHANGELOG.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.md
  • docs/module-system/installing-modules.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/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.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/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.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/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.md
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
**/*cli*.{py,pyi}

📄 CodeRabbit inference engine (.cursorrules)

Commands should follow typer patterns with rich console output

Files:

  • src/specfact_cli/cli.py
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/cli.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_packages.py
  • src/specfact_cli/modules/init/src/commands.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.py
**/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/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/specfact_cli/test_module_not_found_error.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/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/specfact_cli/test_module_not_found_error.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/registry/test_init_module_state.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/modules/init/test_first_run_selection.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/specfact_cli/test_module_not_found_error.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/init/module-package.yaml
  • 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/init/module-package.yaml
  • src/specfact_cli/modules/module_registry/module-package.yaml
🧠 Learnings (22)
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to openspec/changes/**/*.md : 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/`)

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • openspec/changes/marketplace-07-module-install-state-consistency/proposal.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: Before implementing code changes, run `openspec validate <change-id> --strict` to validate the OpenSpec change and optionally run `/wf-validate-change <change-id>` for breaking-change and dependency analysis; do not proceed to implementation until validation passes or user explicitly overrides

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-04-10T22:42:04.372Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/session_startup_instructions.mdc:0-0
Timestamp: 2026-04-10T22:42:04.372Z
Learning: For OpenSpec changes when a sibling `specfact-cli-internal/` checkout exists, read internal wiki guidance in `docs/agent-rules/40-openspec-and-tdd.md` and consult `wiki/hot.md`, `wiki/graph.md`, and relevant `wiki/concepts/*.md` files

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • docs/module-system/installing-modules.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: When implementation is complete and merged, run `openspec archive <change-id>` from the repository root to merge delta specs into openspec/specs/ and move the change under openspec/changes/archive/; do not manually move folders or use --skip-specs or --no-validate without explicit user confirmation

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: Applies to 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

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml
  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to {pyproject.toml,setup.py,src/__init__.py} : Manually update version numbers in pyproject.toml, setup.py, and src/__init__.py when making a formal version change

Applied to files:

  • src/__init__.py
  • pyproject.toml
  • src/specfact_cli/__init__.py
  • setup.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {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

Applied to files:

  • src/__init__.py
  • pyproject.toml
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {src/__init__.py,pyproject.toml,setup.py} : Maintain version synchronization across src/__init__.py, pyproject.toml, and setup.py

Applied to files:

  • src/__init__.py
  • pyproject.toml
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to @(pyproject.toml|setup.py|src/__init__.py|src/specfact_cli/__init__.py) : Maintain synchronized versions across pyproject.toml, setup.py, src/__init__.py, and src/specfact_cli/__init__.py.

Applied to files:

  • src/__init__.py
  • pyproject.toml
  • src/specfact_cli/__init__.py
  • setup.py
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to pyproject.toml : 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

Applied to files:

  • pyproject.toml
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to docs/**/*.md : 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.

Applied to files:

  • docs/core-cli/init.md
  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/specs/init-module-state/spec.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
  • openspec/changes/marketplace-07-module-install-state-consistency/design.md
  • docs/module-system/installing-modules.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: If `openspec` command is not found or if specfact-cli workspace or openspec/ directory is not accessible, do not modify application code unless the user explicitly confirms to proceed without OpenSpec

Applied to files:

  • docs/core-cli/init.md
  • src/specfact_cli/modules/init/module-package.yaml
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: All development work MUST use git worktrees per AGENTS.md Git Worktree Policy. Never create branches with `git checkout -b` in the primary checkout. Create worktree from origin/dev: `git worktree add ../specfact-cli-worktrees/<type>/<slug> -b <branch-name> origin/dev` where allowed types are: `feature/`, `bugfix/`, `hotfix/`, `chore/`. Forbidden in worktrees: `dev`, `main`. After creating worktree: `cd ../specfact-cli-worktrees/<type>/<slug>`. Bootstrap Hatch in worktree: `hatch env create`. Run pre-flight checks: `hatch run smart-test-status` and `hatch run contract-test-status`. Do all implementation work from the worktree, never from primary checkout. After PR merge: cleanup with `git worktree remove`, `git branch -d`, `git worktree prune`

Applied to files:

  • docs/core-cli/init.md
  • src/specfact_cli/modules/init/module-package.yaml
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: For `/opsx:ff` (Fast-Forward Change Creation): OPSX provides change scaffolding and artifact templates. AGENTS.md requires explicitly adding: Worktree creation task as Step 1 in tasks.md (not just 'create branch'), TDD_EVIDENCE.md tracking task in section 2 (Tests), Documentation research task per `openspec/config.yaml`, Module signing quality gate if applicable, Worktree cleanup steps in final section

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: When creating implementation plans, task lists, or OpenSpec tasks.md, ALWAYS explicitly verify and include: (1) Worktree creation from `origin/dev` (not `git checkout -b`), (2) `hatch env create` in the worktree, (3) Pre-flight checks (`hatch run smart-test-status`, `hatch run contract-test-status`), (4) Worktree cleanup steps post-merge, (5) Self-check: 'Have I followed AGENTS.md Git Worktree Policy section?'

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: Applies to openspec/**/{proposal.md,tasks.md,design.md,spec.md} : 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

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Before executing ANY workflow command (`/opsx:ff`, `/opsx:apply`, `/opsx:continue`, etc.), perform the Pre-Execution Checklist: (1) Git Worktree - create if task creates branches/modifies code, (2) TDD Evidence - create `TDD_EVIDENCE.md` if behavior changes, (3) Documentation - include documentation research if changes affect user-facing behavior, (4) Module Signing - include signature verification if changes modify bundled modules, (5) Confirmation - state clearly that pre-execution checklist is complete and AGENTS.md compliance is confirmed

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-04-10T22:41:54.436Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/automatic-openspec-workflow.mdc:0-0
Timestamp: 2026-04-10T22:41:54.436Z
Learning: Applies to {src,tools}/**/*.{js,ts,jsx,tsx,py},**/*.test.{js,ts,jsx,tsx,py},**/*.spec.{js,ts,jsx,tsx,py} : Do not add, modify, or delete any application code in src/, tools/, tests, or significant docs until an OpenSpec change (new or delta) is created and validated, unless the user explicitly opts out with 'skip openspec', 'direct implementation', 'simple fix', or 'just fix it'

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-04-10T22:42:21.860Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T22:42:21.860Z
Learning: Perform `spec -> tests -> failing evidence -> code -> passing evidence` in that order for behavior changes

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
  • openspec/changes/marketplace-07-module-install-state-consistency/TDD_EVIDENCE.md
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: For `/opsx:apply` (Implementation): OPSX provides task iteration and progress tracking. AGENTS.md requires verification before each task: Confirm you are IN a worktree (not primary checkout) before modifying code, Record failing test evidence in `TDD_EVIDENCE.md` BEFORE implementing, Record passing test evidence AFTER implementation, Run quality gates from worktree (format, type-check, contract-test), GPG-signed commits (`git commit -S`), PR to `dev` branch (never direct push)

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/tasks.md
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG.md to document all significant changes under Added, Fixed, Changed, or Removed sections when making a version change

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-04-10T22:42:21.860Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T22:42:21.860Z
Learning: Enforce module signatures and version bumps when signed module assets or manifests are affected

Applied to files:

  • openspec/changes/marketplace-07-module-install-state-consistency/specs/module-installation/spec.md
  • src/specfact_cli/modules/module_registry/module-package.yaml
🪛 LanguageTool
openspec/changes/marketplace-07-module-install-state-consistency/design.md

[style] ~19-~19: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ses when they can be derived locally. - Make `specfact init --repo ... --profile ......

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~46-~46: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... specfact module enable <id> command. If skipped for integrity, compatibility, s...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~75-~75: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...h and merge state deterministically. 5. Update user-facing docs for module install/lis...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

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

[::nold-ai/specfact-cli-modules::] docs/guides/installing-modules.md: references specfact module install usage and repo/scope flags; documentation consumer of the CLI behaviors fixed by this PR (install/enable/repair messaging and repo-scoped --repo behavior).

[::nold-ai/specfact-cli-modules::] docs/reference/commands.md: documents specfact init --profile/--install and specfact module install CLIs; these docs are consumers of the UX/diagnostic changes (absent/disabled/skipped/shadowed messaging) introduced by the PR.

[::nold-ai/specfact-cli-modules::] docs/guides/marketplace.md and docs/getting-started/choose-your-modules.md: reference marketplace module ids and init/profile workflows; relevant to profile-driven enabling and discovery semantics changed by the PR.

[::nold-ai/specfact-cli-modules::] docs/team-and-enterprise/multi-repo.md and similar team docs (many files): show examples using specfact init --repo and profile flags; these content files depend on the repo-scoped discovery/preserve-existing behavior fixed here.

Summary assessment:

  • No code-level consumers (no executable code) were found in nold-ai/specfact-cli-modules — this repository primarily contains documentation that references the CLI commands and expected behaviors changed by the PR.
  • The repository’s docs are aligned with the PR scope (install/init/profile/repo behaviors); reviewers should confirm documentation examples/diagnostic text remain accurate after the PR’s messaging and recovery-command changes.
🔇 Additional comments (27)
src/specfact_cli/modules/module_registry/module-package.yaml (1)

2-2: Version bump and integrity refresh look consistent.

The manifest update at Line 2 and checksum refresh at Line 20 align with the release scope.

Also applies to: 20-20

openspec/changes/marketplace-07-module-install-state-consistency/.openspec.yaml (1)

1-2: OpenSpec registration metadata is clean and traceable.

schema and concrete created date are correctly declared for the new change entry.

src/__init__.py (1)

6-6: Version source update is consistent.

At Line 6, the package version aligns with the coordinated release bump.

src/specfact_cli/__init__.py (1)

48-48: Public package version export stays aligned.

The update at Line 48 keeps runtime-reported version in sync with release metadata.

setup.py (1)

10-10: Setup package version is correctly synchronized.

At Line 10, the version matches the coordinated 0.46.5 release metadata.

pyproject.toml (1)

7-7: Version 0.46.5 correctly supersedes PyPI release 0.46.4—release is gated for publish.

Line 7 confirms the local version is ahead of the current PyPI publication, enabling the CI/CD pipeline to proceed with publication.

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

20-20: Clarify when module signatures are required: checksum-only acceptable on PR branches per policy, but signature required for main.

At Line 20, the init module uses checksum-only integrity. This is acceptable on non-main branches (where the policy defers signing to CI and requires only version bumps), but strict verification (checksum + signature) is required on main.

However, note two structural concerns:

  1. Tier classification: The module is tier: community, not "core" (the category: core field refers to command bundling, not module tier).

  2. Architectural inconsistency: The upgrade module (also tier: community) has a signature in its manifest, while init and module_registry do not—all three changed in the same commit. The branch-aware policy (git-branch-module-signature-flag.sh) defers signature verification to CI on PR branches and requires strict checks on main. Confirm whether bundled community-tier modules should have uniform signing expectations.

The version was bumped (0.1.31), satisfying the version-increment requirement from the signing policy, but signature alignment across bundled modules may need review before main merge.

docs/core-cli/init.md (1)

55-56: LGTM! Documentation accurately reflects repo-scoped discovery and state preservation.

The added paragraph correctly documents the --profile and --install enable semantics along with the repo-aware discovery behavior. This aligns with the implementation in get_discovered_modules_for_state(..., preserve_existing=True) shown in the relevant context snippets.

src/specfact_cli/cli.py (2)

69-69: LGTM! Import introduces metadata-only availability classification.

This import enables the CLI to differentiate module availability states (disabled, skipped, shadowed, absent) without eagerly loading module command code—a key architectural decision for lazy module loading and deterministic diagnostics.


128-149: Well-structured availability-based diagnostic routing.

The branching logic correctly prioritizes DISABLED → SKIPPED → SHADOWED before falling through to the "not installed" path. This aligns with the OpenSpec design decision to differentiate lifecycle states in missing-command UX.

Minor observation: The availability.module_id or module_id fallback pattern appears in all three branches. This is safe given the classifier always populates module_id for non-ABSENT statuses, but the consistency is good for defensive coding.

tests/unit/modules/init/test_first_run_selection.py (1)

166-200: Excellent coverage of repo-scoped discovery contract.

This test validates the critical architectural invariant from the OpenSpec init-module-state spec: get_discovered_modules_for_state must receive base_path from --repo, preserve_existing=True, and the profile-resolved enable_ids. The captured kwargs pattern cleanly verifies the contract without coupling to implementation details.

The assertion at line 195 using tmp_path.resolve() correctly handles path normalization, matching how the implementation resolves the repo path.

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

72-86: Targeted test for merge-based state preservation.

This test directly validates the OpenSpec init-module-state requirement that repo-scoped refresh must preserve lifecycle state for modules outside the discovery view. The explicit dict assertions at lines 84-85 verify both:

  1. Discovered modules appear with default enabled: True
  2. Pre-existing unseen modules retain their stored state (enabled: False, version: 9.9.9)

The mock at line 78-80 cleanly isolates discovery to a controlled base_path scope.

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

97-146: Comprehensive test for disabled-module enable-on-install flow.

This test validates the OpenSpec module-installation spec requirement that specfact module install must repair/enable disabled modules rather than reporting only "already installed." The test captures:

  1. enable_ids passed to discovery (line 138)
  2. Exact state payload written to modules.json including unrelated modules (lines 139-144)
  3. User-facing "enabled" feedback in stdout (line 145)
openspec/changes/marketplace-07-module-install-state-consistency/specs/user-module-root/spec.md (1)

1-29: Well-structured scope precedence specification.

The three scenarios comprehensively cover the project-before-user shadowing semantics:

  1. Install satisfaction evaluated against target scope (user), with runtime governance warning
  2. Missing-command diagnostics identifying the controlling project origin while acknowledging shadowed user copy
  3. User-scope fallback when no project copy exists

These requirements directly map to the ModuleAvailabilityStatus.SHADOWED handling in cli.py and the _shadowed_availability helper in module_availability.py. The GIVEN/WHEN/THEN structure makes the scenarios testable.

tests/unit/specfact_cli/test_module_not_found_error.py (2)

23-34: Clean fixture for mocking availability classification.

The absent_module fixture correctly patches classify_module_availability at the call site (specfact_cli.cli) rather than the definition site, ensuring the mock takes effect. The lambda extracts module_id from kwargs to construct a realistic ModuleAvailability with dynamic recovery command.


80-102: Validates disabled-module messaging contract.

This test directly validates the OpenSpec requirement that missing-command UX must distinguish disabled modules from uninstalled ones. The assertions at lines 100-102 verify:

  1. Positive: "is installed but disabled" appears
  2. Positive: Recovery command specfact module enable ... appears
  3. Negative: "is not installed" does not appear

The normalized_output on line 98 handles Rich console formatting for reliable assertions.

openspec/changes/marketplace-07-module-install-state-consistency/tasks.md (1)

1-45: Comprehensive task tracking with TDD evidence requirements.

The tasks document aligns with the PR implementation:

  • Section 2.4 preserves the lazy-loading architectural constraint: classifier tests must not eagerly import module command code
  • Section 4.2 directly maps to the preserve_existing=True parameter
  • Section 6.2 includes openspec validate --strict as a quality gate

All tasks are marked complete ([x]), and TDD evidence tasks (1.7, 6.4) ensure behavioral changes have before/after validation.

src/specfact_cli/modules/init/src/commands.py (1)

483-492: Good merge-based, repo-aware state refresh.

Passing base_path=repo_path with preserve_existing=True, then threading the selected module ids into the refresh, fixes the earlier state-clobbering pattern without duplicating discovery logic.

Also applies to: 726-744

src/specfact_cli/registry/module_discovery.py (1)

166-215: Good consolidation of project-aware discovery.

The new wrapper keeps workspace-root selection repo-aware without duplicating precedence and shadowing logic across callers.

openspec/changes/marketplace-07-module-install-state-consistency/proposal.md (1)

1-39: Well-structured proposal with clear traceability.

The proposal correctly identifies the root cause (mixing discovery results with state writes) and enumerates concrete behavioral changes that align with the implementation in module_availability.py and module_packages.py. The GitHub issue linkage (#533 bug → #534 user story → #353 parent feature) provides solid traceability for OpenSpec validation.

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

1445-1458: Solid merge-based state preservation implementation.

The logic correctly extends newly discovered modules with persisted state rows that weren't in the current discovery view. The sorted(state.items()) ensures deterministic ordering, and the defensive str()/bool() casts handle potential schema drift gracefully. This aligns with the design spec's "merge-based" refresh requirement that preserves user-disabled choices.


198-203: Clean conditional routing for repo-scoped discovery.

The lazy import of discover_all_modules_for_project inside the function body maintains the file's pattern of avoiding eager module loading. The routing logic correctly delegates to the project-scoped discovery when base_path is provided, implementing the design spec's "repo-aware" requirement without breaking existing callers.

openspec/changes/marketplace-07-module-install-state-consistency/design.md (2)

1-84: Design decisions align well with implementation.

The five decisions map directly to the code changes:

  1. Availability classification helper → ModuleAvailabilityStatus enum and classify_module_availability() in module_availability.py
  2. Canonicalize module identity → _module_id_matches() and _module_id_tail() functions
  3. Install no-op as lifecycle reconciliation → referenced in PR objectives for commands.py
  4. Repo-aware merge-based init → get_discovered_modules_for_state(base_path=..., preserve_existing=True) in module_packages.py
  5. Shadowing behavior surfaced → _shadowed_availability() with shadowed_by field

The open questions (auto-enable vs explicit enable, list/origin exposure) are appropriately flagged for implementation-time decisions with test documentation.


62-68: Risk mitigations are practical and well-scoped.

The four risks identified (classification duplication, stale state preservation, enable-on-install surprise, startup integrity cost) each have proportionate mitigations. Particularly noteworthy: the mitigation for stale entries ("preserve only rows with explicit disabled state") aligns with the preserve_existing implementation that only carries forward rows not in the current discovery view.

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

160-187: Exemplary contract-first public API design.

The classify_module_availability function demonstrates proper contract usage:

  • @beartype for runtime type enforcement
  • @require with clear precondition ("module_id or command_name required")
  • @ensure validating the return type invariant

The logic flow (discover → match → shadowing check → disabled check → skip check → available) correctly implements the six-state classification without importing module command code, keeping CLI startup fast.


107-113: Shadowing detection correctly scoped to the documented confusion case.

The _shadowed_duplicate function only flags project-scope shadowing user/marketplace modules, which matches the design spec's focus: "When the selected command or module is unavailable because a project copy shadows a user copy, diagnostics should identify both origins." Other shadow combinations (e.g., user shadowing custom) are less confusing and correctly excluded.


46-56: Module ID canonicalization handles bare name resolution correctly.

The _module_id_tail and _module_id_matches functions implement the design spec's identity canonicalization: a request like specfact-codebase correctly matches the discovered nold-ai/specfact-codebase by comparing final path segments. The strip() calls provide defensive handling for whitespace in user input.

Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread src/specfact_cli/modules/module_registry/src/commands.py Outdated
Comment thread src/specfact_cli/registry/module_availability.py
Comment thread src/specfact_cli/registry/module_availability.py
Comment thread src/specfact_cli/registry/module_availability.py
Comment thread src/specfact_cli/registry/module_packages.py
Comment thread tests/unit/modules/module_registry/test_commands.py Outdated
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.

Caution

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

⚠️ Outside diff range comments (1)
tests/unit/commands/test_backlog_config.py (1)

89-97: ⚠️ Potential issue | 🟠 Major

Isolate precedence tests from ambient SPECFACT_* environment variables.

These tests validate fallback resolution (config/inferred), but they don’t clear relevant env vars first. If CI/local env already sets SPECFACT_GITHUB_* or SPECFACT_ADO_*, assertions can become nondeterministic.

Proposed fix
-    def test_github_uses_config_when_args_none(self) -> None:
+    def test_github_uses_config_when_args_none(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """When repo_owner/repo_name not passed, values from config are used."""
+        monkeypatch.delenv("SPECFACT_GITHUB_REPO_OWNER", raising=False)
+        monkeypatch.delenv("SPECFACT_GITHUB_REPO_NAME", raising=False)
+        monkeypatch.delenv("SPECFACT_GITHUB_TOKEN", raising=False)
         with patch(
             "specfact_backlog.backlog.commands._load_backlog_config",
             return_value={"github": {"repo_owner": "myorg", "repo_name": "myrepo"}},
         ):
             kwargs = _build_adapter_kwargs(_AdapterContext(adapter="github"))
         assert kwargs.get("repo_owner") == "myorg"
         assert kwargs.get("repo_name") == "myrepo"

-    def test_ado_uses_config_when_args_none(self) -> None:
+    def test_ado_uses_config_when_args_none(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """When ado_org/ado_project not passed, values from config are used."""
+        monkeypatch.delenv("SPECFACT_ADO_ORG", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_PROJECT", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_TEAM", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_TOKEN", raising=False)
         with patch(
             "specfact_backlog.backlog.commands._load_backlog_config",
             return_value={
                 "ado": {"org": "myorg", "project": "MyProject", "team": "My Team"},
             },
         ):
             kwargs = _build_adapter_kwargs(_AdapterContext(adapter="ado"))
         assert kwargs.get("org") == "myorg"
         assert kwargs.get("project") == "MyProject"
         assert kwargs.get("team") == "My Team"

-    def test_ado_uses_inferred_when_args_none(self) -> None:
+    def test_ado_uses_inferred_when_args_none(self, monkeypatch: pytest.MonkeyPatch) -> None:
         """When ado_org/ado_project not passed, inferred from git is used."""
+        monkeypatch.delenv("SPECFACT_ADO_ORG", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_PROJECT", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_TEAM", raising=False)
+        monkeypatch.delenv("SPECFACT_ADO_TOKEN", raising=False)
         with (
             patch(
                 "specfact_backlog.backlog.commands._load_backlog_config",
                 return_value={},
             ),

As per coding guidelines "Ensure each test is independent and repeatable with no shared state between tests" and "Tests must be meaningful and test actual functionality... be independent and repeatable".

Also applies to: 111-123, 210-224

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/commands/test_backlog_config.py` around lines 89 - 97, The tests
(e.g., test_github_uses_config_when_args_none and the other mentioned cases) can
be influenced by existing SPECFACT_* env vars; before calling
_build_adapter_kwargs with an _AdapterContext(adapter="github") ensure any
relevant environment variables (like SPECFACT_GITHUB_* and SPECFACT_ADO_*) are
cleared or isolated so the config fallback is deterministic—use the test fixture
(monkeypatch.delenv or patch.dict on os.environ) to remove
SPECFACT_GITHUB_REPO_OWNER, SPECFACT_GITHUB_REPO_NAME and any SPECFACT_ADO_*
entries at the start of these tests and restore/limit scope to the test only,
then assert repo_owner/repo_name come from the mocked _load_backlog_config as
intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@tests/unit/commands/test_backlog_config.py`:
- Around line 89-97: The tests (e.g., test_github_uses_config_when_args_none and
the other mentioned cases) can be influenced by existing SPECFACT_* env vars;
before calling _build_adapter_kwargs with an _AdapterContext(adapter="github")
ensure any relevant environment variables (like SPECFACT_GITHUB_* and
SPECFACT_ADO_*) are cleared or isolated so the config fallback is
deterministic—use the test fixture (monkeypatch.delenv or patch.dict on
os.environ) to remove SPECFACT_GITHUB_REPO_OWNER, SPECFACT_GITHUB_REPO_NAME and
any SPECFACT_ADO_* entries at the start of these tests and restore/limit scope
to the test only, then assert repo_owner/repo_name come from the mocked
_load_backlog_config as intended.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 473d9566-922a-4616-b520-eb9b5b930673

📥 Commits

Reviewing files that changed from the base of the PR and between c92dbff and 32048bb.

📒 Files selected for processing (2)
  • tests/unit/commands/test_backlog_config.py
  • tests/unit/specfact_cli/registry/test_init_module_lifecycle_ux.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests (Python 3.12)
🧰 Additional context used
📓 Path-based instructions (5)
**/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/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.py
**/*.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:

  • tests/unit/specfact_cli/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.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/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.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/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.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:

  • tests/unit/specfact_cli/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.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:

  • tests/unit/specfact_cli/registry/test_init_module_lifecycle_ux.py
  • tests/unit/commands/test_backlog_config.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to openspec/changes/**/*.md : 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/`)
🔀 Multi-repo context nold-ai/specfact-cli-modules

nold-ai/specfact-cli-modules

  • Docs referencing specfact init/profile/--install and module install CLI flows (potential consumers of CLI UX/diagnostic changes; may need wording updates to match new messaging/recovery commands):

    • docs/guides/installing-modules.md — multiple examples and guidance for specfact module install and module enable behavior. [::nold-ai/specfact-cli-modules::]
    • docs/getting-started/installation.md — profile/install examples and guidance for init/install flows. [::nold-ai/specfact-cli-modules::]
    • docs/getting-started/choose-your-modules.md — marketplace install examples and profile/init guidance. [::nold-ai/specfact-cli-modules::]
    • docs/guides/marketplace.md — multiple specfact module install examples and init profile usage. [::nold-ai/specfact-cli-modules::]
    • docs/guides/workflows.md — references specfact init --profile usage. [::nold-ai/specfact-cli-modules::]
    • docs/reference/commands.md — documents specfact init and specfact module install CLI signatures. [::nold-ai/specfact-cli-modules::]
    • docs/team-and-enterprise/multi-repo.md — examples using specfact init --repo (relevant to repo-scoped discovery/preserve_existing behavior). [::nold-ai/specfact-cli-modules::]
    • docs/guides/ai-ide-workflow.md and many bundle overview docs (backlog, codebase, code-review, spec, govern) — reference specfact init ide and recommend re-running init after upgrades/installs (relevant to init behavior and discovery of bundle resources). [::nold-ai/specfact-cli-modules::]
  • Tests/docs review harness referencing CLI commands:

    • tests/unit/docs/test_docs_review.py contains assertions expecting strings like "specfact init ide" (may need update if user-facing help text changes). [::nold-ai/specfact-cli-modules::]

Summary assessment: This repository contains documentation (and documentation tests) that reference the CLI commands and UX changed by the PR (init/profile, --repo behavior, module install/enable messages). No executable code consumers were found. Reviewers should verify doc examples and recovery/enable guidance (e.g., exact enable command strings) remain accurate after the PR's messaging and recovery-command changes. [::nold-ai/specfact-cli-modules::]

🔇 Additional comments (3)
tests/unit/specfact_cli/registry/test_init_module_lifecycle_ux.py (2)

62-62: Good signature sync for discovery monkeypatches.

Adding base_path and preserve_existing to both stubs correctly tracks the updated discovery API and avoids false failures in these Typer CLI UX tests.

Also applies to: 86-86


60-65: Consider extracting a shared discovery stub to reduce signature drift risk.

These two identical lambdas duplicate the same integration seam. A shared helper will make future API changes in init/discovery less brittle across UX tests.

[ suggest_optional_refactor ]

Refactor sketch
+def _stub_discovered_modules_for_state(
+    enable_ids=None, disable_ids=None, base_path=None, preserve_existing=False
+):
+    return [{"id": "sync", "version": "0.1.0", "enabled": True}]
+
 monkeypatch.setattr(
     "specfact_cli.modules.init.src.commands.get_discovered_modules_for_state",
-    lambda enable_ids=None, disable_ids=None, base_path=None, preserve_existing=False: [
-        {"id": "sync", "version": "0.1.0", "enabled": True},
-    ],
+    _stub_discovered_modules_for_state,
 )
As per coding guidelines, "Extract common patterns to avoid code duplication (DRY principle)".

Also applies to: 84-89

tests/unit/commands/test_backlog_config.py (1)

19-23: Good adapter-contract migration in tests.

Switching these paths to _AdapterContext(...) keeps the tests aligned with the new CLI adapter boundary and validates precedence behavior through the same context object shape used by the command layer.

Also applies to: 80-85, 133-137

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: 2

♻️ Duplicate comments (2)
CHANGELOG.md (2)

21-26: ⚠️ Potential issue | 🟡 Minor

Expand 0.46.5 to explicitly capture all significant change types.

Line 21-Line 26 compresses multiple release-level behaviors into a single Fixed bullet. Please add explicit Changed/Added bullets for diagnostics behavior updates and new reality-test coverage so downstream users can trace impact accurately.

Suggested changelog structure update
 ## [0.46.5] - 2026-04-28
 
 ### Fixed
 
 - **Module install and init**: repair module availability, install, upgrade, uninstall, and profile-init state handling so repeated runs across repos and envs stay consistent.
+
+### Changed
+
+- **Module diagnostics behavior**: improved missing-command classification and recovery guidance for absent, disabled, skipped, shadowed, and ambiguous module states.
+
+### Added
+
+- **Reality-test coverage**: added lifecycle reality tests for install, upgrade, uninstall, and `init --profile` flows against real project fixtures.

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

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 21 - 26, Update the [0.46.5] entry so that changes
are split into explicit categories instead of a single "Fixed" bullet: keep the
existing "Fixed" note about module install/init, add a "Changed" bullet
describing diagnostics behavior updates, and add an "Added" bullet noting new
reality-test coverage; target the header "[0.46.5]" and replace the single Fixed
list item with three separate bullets (Added, Changed, Fixed) that each
succinctly capture the distinct changes.

25-25: ⚠️ Potential issue | 🟡 Minor

Wrap the overlong bullet for markdown line-length compliance.

Line 25 exceeds the 120-character guideline and should be wrapped at word boundaries.

Suggested line wrap
-- **Module install and init**: repair module availability, install, upgrade, uninstall, and profile-init state handling so repeated runs across repos and envs stay consistent.
+- **Module install and init**: repair module availability, install, upgrade,
+  uninstall, and profile-init state handling so repeated runs across repos and
+  envs stay consistent.

As per coding guidelines: "Keep line length under 120 characters in markdown files"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 25, The long bullet in CHANGELOG.md ("**Module install
and init**: repair module availability, install, upgrade, uninstall, and
profile-init state handling so repeated runs across repos and envs stay
consistent.") exceeds 120 chars; break it into multiple lines at word boundaries
(e.g., split after "install and init" or between comma-separated phrases) so
each Markdown line is ≤120 chars while preserving the exact wording and bold
label "**Module install and init**".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/specfact_cli/modules/module_registry/src/commands.py`:
- Around line 445-452: Normalize the repository path once and reuse it for both
discovery and install-root construction: compute a single normalized project
root (e.g., normalized_repo or repo_root) by resolving the workspace root
(nearest .git) and use that value when calling
discover_all_modules_for_project(normalized_repo) and when building target_root
and the _InstallOneParams(repo=...) argument so discovery and install point at
the same root; update references to repo in discover_all_modules_for_project,
target_root construction, and the _InstallOneParams call to use this normalized
variable.

In `@src/specfact_cli/registry/module_availability.py`:
- Around line 51-77: _module_id_matches currently treats "namespace/name" the
same as short ids by using tail matching; change it so fully-qualified ids
(those containing '/') require exact equality with discovered_id. Specifically,
in _module_id_matches, after normalizing requested (requested_clean),
short-circuit to return requested_clean == discovered_id if '/' in
requested_clean (or otherwise only allow exact match for qualified ids);
otherwise preserve the existing behavior that also checks
_module_id_tail(requested_clean) == _module_id_tail(discovered_id). Keep
_entry_matches and _availability_matches logic unchanged so they benefit from
the stricter matching.

---

Duplicate comments:
In `@CHANGELOG.md`:
- Around line 21-26: Update the [0.46.5] entry so that changes are split into
explicit categories instead of a single "Fixed" bullet: keep the existing
"Fixed" note about module install/init, add a "Changed" bullet describing
diagnostics behavior updates, and add an "Added" bullet noting new reality-test
coverage; target the header "[0.46.5]" and replace the single Fixed list item
with three separate bullets (Added, Changed, Fixed) that each succinctly capture
the distinct changes.
- Line 25: The long bullet in CHANGELOG.md ("**Module install and init**: repair
module availability, install, upgrade, uninstall, and profile-init state
handling so repeated runs across repos and envs stay consistent.") exceeds 120
chars; break it into multiple lines at word boundaries (e.g., split after
"install and init" or between comma-separated phrases) so each Markdown line is
≤120 chars while preserving the exact wording and bold label "**Module install
and init**".
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: a9ef6a67-11c4-4a4d-b7ba-784f3154d69a

📥 Commits

Reviewing files that changed from the base of the PR and between 32048bb and a519a0d.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • pyproject.toml
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/modules/module_registry/module-package.yaml
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/registry/module_discovery.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Tests (Python 3.12)
  • GitHub Check: Type Checking (basedpyright)
  • GitHub Check: Linting (ruff, pylint, safe-write guard)
  • GitHub Check: Contract-First CI
  • GitHub Check: Compatibility (Python 3.11)
  • GitHub Check: Docs Review
🧰 Additional context used
📓 Path-based instructions (15)
{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:

  • pyproject.toml
  • setup.py
  • src/__init__.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:

  • pyproject.toml
  • setup.py
  • src/__init__.py
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
**/*.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:

  • setup.py
  • src/__init__.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.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:

  • setup.py
  • src/__init__.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.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/registry/module_availability.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.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
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.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
**/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/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.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/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.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/modules/module_registry/test_commands.py
  • tests/unit/specfact_cli/registry/test_module_availability.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:

  • CHANGELOG.md
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
**/*.md

📄 CodeRabbit inference engine (.cursorrules)

Avoid markdown linting errors (refer to markdown-rules)

Files:

  • CHANGELOG.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/registry/module_availability.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/registry/module_discovery.py
  • src/specfact_cli/modules/module_registry/src/commands.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to openspec/changes/**/*.md : 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/`)
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to {pyproject.toml,setup.py,src/__init__.py} : Manually update version numbers in pyproject.toml, setup.py, and src/__init__.py when making a formal version change

Applied to files:

  • pyproject.toml
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to pyproject.toml : 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

Applied to files:

  • pyproject.toml
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to @(pyproject.toml|setup.py|src/__init__.py|src/specfact_cli/__init__.py) : Maintain synchronized versions across pyproject.toml, setup.py, src/__init__.py, and src/specfact_cli/__init__.py.

Applied to files:

  • pyproject.toml
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/modules/module_registry/src/commands.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {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

Applied to files:

  • pyproject.toml
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {src/__init__.py,pyproject.toml,setup.py} : Maintain version synchronization across src/__init__.py, pyproject.toml, and setup.py

Applied to files:

  • pyproject.toml
  • src/__init__.py
📚 Learning: 2026-04-10T22:42:21.860Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T22:42:21.860Z
Learning: Enforce module signatures and version bumps when signed module assets or manifests are affected

Applied to files:

  • src/specfact_cli/modules/module_registry/module-package.yaml
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG.md to document all significant changes under Added, Fixed, Changed, or Removed sections when making a version change

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG.md with all code changes as part of version control requirements.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to openspec/changes/**/*.md : 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/`)

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to docs/**/*.md : 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.

Applied to files:

  • src/specfact_cli/__init__.py
🔀 Multi-repo context nold-ai/specfact-cli-modules

Linked repositories findings

nold-ai/specfact-cli-modules

  • Documentation references specfact init --profile / --install and repo-scoped init usage in many places (examples that may need wording or recovery-command updates):

    • docs/guides/workflows.md: lines showing "specfact init --profile solo-developer" and "specfact init ide --repo . --ide cursor" [::nold-ai/specfact-cli-modules::]
    • docs/guides/marketplace.md: multiple profile/install examples (e.g., lines with "specfact init --profile solo-developer", "specfact init --install backlog,codebase") [::nold-ai/specfact-cli-modules::]
    • docs/getting-started/installation.md: numerous first-run/init examples and guidance including "specfact init --profile ..." and "specfact init ide" [::nold-ai/specfact-cli-modules::]
    • docs/getting-started/choose-your-modules.md: profile/install examples and module install guidance (multiple occurrences) [::nold-ai/specfact-cli-modules::]
    • docs/reference/commands.md & docs/reference/module-categories.md: specfact init and specfact module install CLI usage examples (may cite exact enable/repair commands) [::nold-ai/specfact-cli-modules::]
  • Documentation references specfact module install and module enable/disable usage and repo/scope flags (may need to align messages with new classifier/repair behavior):

    • docs/guides/installing-modules.md: full install examples and notes including "--scope project --repo /path/to/repo" and "If a module is already available locally ... install is skipped with a clear message." (relevant to changed install/enable semantics) [::nold-ai/specfact-cli-modules::]
    • docs/reference/dependency-resolution.md and docs/guides/module-marketplace.md: multiple install examples and flags (relevant to availability messaging) [::nold-ai/specfact-cli-modules::]
    • docs/guides/installing-modules.md: examples showing "specfact module enable backlog" (recovery guidance string appears in docs) [::nold-ai/specfact-cli-modules::]
  • docs/team-and-enterprise/multi-repo.md and other team docs include recommendations to run specfact init --profile per-repo (relevant to repo-scoped discovery/preserve_existing behavior) [::nold-ai/specfact-cli-modules::]

  • Tests that assert documentation/help strings:

    • tests/unit/docs/test_docs_review.py contains an assertion expecting "specfact init ide" in combined docs output (this test may need adjustment if user-facing help text or examples change) [::nold-ai/specfact-cli-modules::]

Summary assessment: This repository contains documentation and a docs-review test that reference the CLI commands and example recovery/enable guidance changed by the PR (init/profile/--repo behavior, module install/enable messaging). Reviewers should verify documentation examples and the docs-test remain accurate with the PR's updated messaging and exact enable/repair command strings. [::nold-ai/specfact-cli-modules::]

🔇 Additional comments (9)
src/specfact_cli/__init__.py (1)

48-48: Version source stays aligned at the CLI package boundary.

__version__ is consistent with the other updated version sources in this PR.

Based on learnings: "Maintain synchronized versions across pyproject.toml, setup.py, src/init.py, and src/specfact_cli/init.py."

src/__init__.py (1)

6-6: Primary version source is correctly bumped.

This keeps the root package version aligned with the rest of the release metadata.

As per coding guidelines: "Update src/init.py first as primary source of truth for package version, then pyproject.toml and setup.py."

setup.py (1)

10-10: Setup metadata version remains in lockstep with project version sources.

Good alignment for build/publish consistency.

Based on learnings: "Maintain synchronized versions across pyproject.toml, setup.py, src/init.py, and src/specfact_cli/init.py."

pyproject.toml (1)

7-7: Version gate satisfied—no action required.

Local versions are synchronized across all sources (pyproject.toml, src/init.py, src/specfact_cli/init.py, setup.py), and 0.46.6 is ahead of the published PyPI version (0.46.4), ensuring release automation will proceed correctly.

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

19-20: This manifest revision is correct; signatures are applied post-merge by the sign-modules workflow.

The bundled module signing architecture uses a staged approach: PRs verify checksums and version bumps, while signatures are added post-merge by sign-modules.yml. The version bumps are present (init 0.1.31, module_registry 0.1.22), satisfying the requirement to enforce version bumps for manifests. Unsigned manifests during PR review are expected and by design—the workflow applies VERIFY_MODULES_PR (omitting --require-signature) for pull requests and reserves strict signature verification for protected-branch pushes.

			> Likely an incorrect or invalid review comment.
CHANGELOG.md (1)

13-19: 0.46.6 entry placement and scope look correct.

Top-of-file placement and concise Fixed classification are aligned with changelog ordering and structure expectations.

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

97-145: Good regression coverage for the repair path.

These cases pin both the preserve-merge state write and the repo-aware base_path threading, which are the two easiest places for this flow to regress.

Also applies to: 148-195

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

109-144: Nice shadowing regression.

This locks in the exact package_dir/shadowed_by contract, which is the detail most likely to drift when discovery ordering changes.

src/specfact_cli/registry/module_discovery.py (1)

176-242: Good consolidation of the project-aware discovery variants.

Routing all three entrypoints through _discover_modules() keeps root priority and duplicate handling in one place, which should make the repo-aware behavior much harder to regress.

Comment thread src/specfact_cli/modules/module_registry/src/commands.py Outdated
Comment thread src/specfact_cli/registry/module_availability.py
@djm81 djm81 force-pushed the bugfix/module-install-state-consistency branch from f1ebc20 to b52c9c1 Compare April 28, 2026 20:59
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: 6

Caution

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

⚠️ Outside diff range comments (1)
tests/unit/specfact_cli/registry/test_module_availability.py (1)

1-168: 🧹 Nitpick | 🔵 Trivial

Solid regression coverage for the availability classifier.

These five tests nail the critical state transitions—disabled/enabled repair, explicit module_id precedence over shared bundle groups, namespace isolation, compatibility skip, and project-vs-user shadowing. The monkeypatch approach keeps tests fast and deterministic without touching real filesystem state.

A few architectural notes:

  1. Missing trailing newline at line 168 (PEP 8 / linters will flag this).
  2. Consider adding @pytest.mark.unit or similar markers per coding guidelines to support hatch run smart-test-unit filtering.
📝 Suggested fix for trailing newline
     assert availability.status is ModuleAvailabilityStatus.SHADOWED
     assert availability.shadowed_by == project_entry.package_dir
     assert availability.package_dir == user_entry.package_dir
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/specfact_cli/registry/test_module_availability.py` around lines 1
- 168, The file is missing a trailing newline at EOF—add a final newline
character to the end of
tests/unit/specfact_cli/registry/test_module_availability.py; optionally, to
support unit test filtering, import pytest and either add `@pytest.mark.unit` to
the existing test functions (e.g.,
test_classify_installed_disabled_module_reports_disabled,
test_classify_prioritizes_requested_module_id_over_shared_command_group,
test_classify_fully_qualified_id_does_not_tail_match_other_namespace,
test_classify_skipped_module_reports_compatibility_reason,
test_project_scope_shadow_reports_shadowing_and_available_project_copy) or set
module-level pytestmark = [pytest.mark.unit].
♻️ Duplicate comments (3)
CHANGELOG.md (1)

27-27: ⚠️ Potential issue | 🟡 Minor

Wrap the 0.46.6 fixed bullet to satisfy markdown max line length.

This line is over the 120-character markdown limit and should be wrapped for lint compliance.

✂️ Proposed wrap
-- **PR follow-up**: fix project-scope module re-enable to honor `--repo` and restore reachable shadowed-module diagnostics during availability checks.
+- **PR follow-up**: fix project-scope module re-enable to honor `--repo`
+  and restore reachable shadowed-module diagnostics during availability
+  checks.

As per coding guidelines: "Keep line length under 120 characters in markdown files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 27, The CHANGELOG.md bullet "- **PR follow-up**: fix
project-scope module re-enable to honor `--repo` and restore reachable
shadowed-module diagnostics during availability checks." exceeds 120 chars; wrap
this single bullet into multiple lines (keeping the leading "- **PR
follow-up**:" marker and markdown formatting) so each line is under 120
characters—break after a natural clause (e.g., after "honor `--repo`," or "and
restore reachable shadowed-module diagnostics") to preserve readability and lint
compliance.
src/specfact_cli/registry/module_availability.py (2)

14-18: 🧹 Nitpick | 🔵 Trivial

Private helper imports create coupling—acceptable but note for future.

Importing _check_core_compatibility and _validate_module_dependencies couples this classifier to module_packages internals. As noted in prior review, if these helpers evolve independently, consider promoting them to a shared module_checks.py with public contracts.

For now, the coupling is acceptable given the helpers are stable and the alternative (duplicating logic) is worse.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/specfact_cli/registry/module_availability.py` around lines 14 - 18, The
file imports private helpers (_check_core_compatibility and
_validate_module_dependencies) from module_packages which creates tight
coupling; to address this, extract these helpers into a new shared module (e.g.,
module_checks.py) with public function names, move the implementations from
module_packages to that module, update references in this file to import the
public functions from module_checks, and keep merge_module_state import as-is if
it remains module_packages-specific; ensure all call sites use the new public
symbols and run tests to confirm no behavior change.

22-30: 🧹 Nitpick | 🔵 Trivial

AMBIGUOUS enum value still unused—consider documenting or removing.

As noted in prior review, classify_module_availability has five return paths (ABSENT, SHADOWED, DISABLED, SKIPPED, AVAILABLE) but never returns AMBIGUOUS. This violates DRY principles and reduces contract clarity.

If AMBIGUOUS is reserved for future multi-match scenarios (e.g., when multiple modules provide the same command), add a comment documenting this reservation. Otherwise, remove it to keep the enum honest about actual states.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/specfact_cli/registry/module_availability.py` around lines 22 - 30,
ModuleAvailabilityStatus currently defines AMBIGUOUS but
classify_module_availability never returns it; either remove AMBIGUOUS from the
enum or explicitly document its intended future use. Update the enum declaration
(ModuleAvailabilityStatus) to drop the AMBIGUOUS member if it’s unused, or add a
brief doc comment above AMBIGUOUS explaining the reserved scenario (e.g.,
"reserved for future multi-provider/multi-match resolution when multiple modules
claim the same command") and update any tests/docs to reflect the change; ensure
classify_module_availability and any callers remain consistent with the revised
enum.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/pre_commit_code_review.py`:
- Around line 163-175: The conditional uses an empty if/pass then else block
around checking env.get("SPECFACT_MODULES_REPO"); change it to an inverted
conditional for clarity by checking the negative condition (e.g., if not
env.get("SPECFACT_MODULES_REPO", "").strip():) and then run the
discover_specfact_modules_repo() logic to set env["SPECFACT_MODULES_REPO"],
leaving the subsequent env.setdefault(...) calls unchanged; reference symbols:
repo_root, env, "SPECFACT_MODULES_REPO", discover_specfact_modules_repo(), and
_repo_root().

In `@src/specfact_cli/registry/module_availability.py`:
- Line 191: The file ends without a trailing newline; add a single newline
character at EOF so the final line "return
_available_or_skipped_availability(primary, enabled_map)" is followed by a
newline to satisfy PEP 8 and POSIX expectations (ensure the file terminates with
a newline).

In
`@tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py`:
- Line 41: The line assigning local_version is too long; split the file read and
parse steps to keep each line under 120 chars. For example, first read the
pyproject content into a variable (e.g., pyproject_text = (repo_root /
"pyproject.toml").read_text(encoding="utf-8")), then call
tomllib.loads(pyproject_text)["project"]["version"].strip() to set
local_version; update references to local_version accordingly.
- Around line 14-15: Replace the duplicated TOML parsing helper in the test by
importing and using the production parser: remove the local
_project_version_from_pyproject_bytes definition and import
read_project_version_from_pyproject_bytes from the script module that defines
it, then update any test usages to call
read_project_version_from_pyproject_bytes(content) so the test and production
code share the same parsing logic; also add the import at the top of the test
file and remove the unused tomllib import.
- Around line 49-53: Add a unit test that patches read_local_version to "1.0.0",
pyproject_version_at_git_revision to "0.9.0", and fetch_latest_pypi_version to
"0.8.0" and then calls main(["--skip-when-version-unchanged-vs", "HEAD"])
asserting it returns 0 and that fetch_latest_pypi_version was called once; use
patch.object on the module functions (read_local_version,
pyproject_version_at_git_revision, fetch_latest_pypi_version) and the test
fixture name used in other tests (e.g., mod) to mirror the provided example so
the differing-version path exercises lines 224–229 and verifies PyPI is queried.

In `@tests/unit/scripts/test_pre_commit_code_review.py`:
- Around line 341-354: Extend the
test_build_review_subprocess_env_preserves_explicit_xdg_paths to also set
explicit environment values for SEMGREP_VERSION_CACHE_PATH and SEMGREP_LOG_FILE
before calling build_review_subprocess_env, then assert that
env["SEMGREP_VERSION_CACHE_PATH"] and env["SEMGREP_LOG_FILE"] equal the explicit
values; this verifies that the setdefault-based defaults do not override
caller-provided SEMGREP_* paths.

---

Outside diff comments:
In `@tests/unit/specfact_cli/registry/test_module_availability.py`:
- Around line 1-168: The file is missing a trailing newline at EOF—add a final
newline character to the end of
tests/unit/specfact_cli/registry/test_module_availability.py; optionally, to
support unit test filtering, import pytest and either add `@pytest.mark.unit` to
the existing test functions (e.g.,
test_classify_installed_disabled_module_reports_disabled,
test_classify_prioritizes_requested_module_id_over_shared_command_group,
test_classify_fully_qualified_id_does_not_tail_match_other_namespace,
test_classify_skipped_module_reports_compatibility_reason,
test_project_scope_shadow_reports_shadowing_and_available_project_copy) or set
module-level pytestmark = [pytest.mark.unit].

---

Duplicate comments:
In `@CHANGELOG.md`:
- Line 27: The CHANGELOG.md bullet "- **PR follow-up**: fix project-scope module
re-enable to honor `--repo` and restore reachable shadowed-module diagnostics
during availability checks." exceeds 120 chars; wrap this single bullet into
multiple lines (keeping the leading "- **PR follow-up**:" marker and markdown
formatting) so each line is under 120 characters—break after a natural clause
(e.g., after "honor `--repo`," or "and restore reachable shadowed-module
diagnostics") to preserve readability and lint compliance.

In `@src/specfact_cli/registry/module_availability.py`:
- Around line 14-18: The file imports private helpers (_check_core_compatibility
and _validate_module_dependencies) from module_packages which creates tight
coupling; to address this, extract these helpers into a new shared module (e.g.,
module_checks.py) with public function names, move the implementations from
module_packages to that module, update references in this file to import the
public functions from module_checks, and keep merge_module_state import as-is if
it remains module_packages-specific; ensure all call sites use the new public
symbols and run tests to confirm no behavior change.
- Around line 22-30: ModuleAvailabilityStatus currently defines AMBIGUOUS but
classify_module_availability never returns it; either remove AMBIGUOUS from the
enum or explicitly document its intended future use. Update the enum declaration
(ModuleAvailabilityStatus) to drop the AMBIGUOUS member if it’s unused, or add a
brief doc comment above AMBIGUOUS explaining the reserved scenario (e.g.,
"reserved for future multi-provider/multi-match resolution when multiple modules
claim the same command") and update any tests/docs to reflect the change; ensure
classify_module_availability and any callers remain consistent with the revised
enum.
🪄 Autofix (Beta)

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: ASSERTIVE

Plan: Pro

Run ID: 357ebeb0-cba7-4784-86de-0bc8cd0154f7

📥 Commits

Reviewing files that changed from the base of the PR and between a519a0d and f1ebc20.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • pyproject.toml
  • scripts/pre_commit_code_review.py
  • setup.py
  • src/__init__.py
  • src/specfact_cli/__init__.py
  • src/specfact_cli/analyzers/code_analyzer.py
  • src/specfact_cli/modules/module_registry/module-package.yaml
  • src/specfact_cli/modules/module_registry/src/commands.py
  • src/specfact_cli/registry/module_availability.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • tests/unit/modules/module_registry/test_commands.py
  • tests/unit/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Compatibility (Python 3.11)
  • GitHub Check: Type Checking (basedpyright)
  • GitHub Check: Contract-First CI
  • GitHub Check: Security Audit (pip-audit)
  • GitHub Check: Tests (Python 3.12)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.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/specfact_cli/__init__.py
  • src/__init__.py
  • setup.py
  • tests/unit/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • scripts/pre_commit_code_review.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/analyzers/code_analyzer.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/modules/module_registry/src/commands.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/specfact_cli/__init__.py
  • src/__init__.py
  • src/specfact_cli/analyzers/code_analyzer.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/modules/module_registry/src/commands.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/specfact_cli/__init__.py
  • src/__init__.py
  • tests/unit/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/analyzers/code_analyzer.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/modules/module_registry/src/commands.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/specfact_cli/__init__.py
  • src/__init__.py
  • setup.py
  • tests/unit/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • scripts/pre_commit_code_review.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • src/specfact_cli/analyzers/code_analyzer.py
  • tests/unit/modules/module_registry/test_commands.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/modules/module_registry/src/commands.py
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/analyzers/code_analyzer.py
  • src/specfact_cli/registry/module_availability.py
  • src/specfact_cli/modules/module_registry/src/commands.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
{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
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
**/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/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/modules/module_registry/test_commands.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/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/modules/module_registry/test_commands.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/scripts/test_pre_commit_code_review.py
  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • tests/unit/analyzers/test_code_analyzer.py
  • tests/unit/specfact_cli/registry/test_module_availability.py
  • tests/unit/modules/module_registry/test_commands.py
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/pre_commit_code_review.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
**/*.{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:

  • CHANGELOG.md
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
**/*.md

📄 CodeRabbit inference engine (.cursorrules)

Avoid markdown linting errors (refer to markdown-rules)

Files:

  • CHANGELOG.md
🧠 Learnings (16)
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to @(pyproject.toml|setup.py|src/__init__.py|src/specfact_cli/__init__.py) : Maintain synchronized versions across pyproject.toml, setup.py, src/__init__.py, and src/specfact_cli/__init__.py.

Applied to files:

  • src/specfact_cli/__init__.py
  • src/__init__.py
  • pyproject.toml
  • setup.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
  • src/specfact_cli/modules/module_registry/src/commands.py
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to {pyproject.toml,setup.py,src/__init__.py} : Manually update version numbers in pyproject.toml, setup.py, and src/__init__.py when making a formal version change

Applied to files:

  • src/specfact_cli/__init__.py
  • src/__init__.py
  • pyproject.toml
  • setup.py
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {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

Applied to files:

  • src/specfact_cli/__init__.py
  • src/__init__.py
  • pyproject.toml
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to {src/__init__.py,pyproject.toml,setup.py} : Maintain version synchronization across src/__init__.py, pyproject.toml, and setup.py

Applied to files:

  • src/specfact_cli/__init__.py
  • src/__init__.py
  • pyproject.toml
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to pyproject.toml : 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

Applied to files:

  • pyproject.toml
  • tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py
📚 Learning: 2026-03-25T21:32:29.182Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/python-github-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:29.182Z
Learning: Applies to **/test_*.py : Organize Python imports in tests using unittest.mock for Mock and patch

Applied to files:

  • tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py
📚 Learning: 2026-03-31T22:38:25.299Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/clean-code-principles.mdc:0-0
Timestamp: 2026-03-31T22:38:25.299Z
Learning: Applies to tests/**/*.py : Secret redaction via `LoggerSetup.redact_secrets` must be covered by unit tests

Applied to files:

  • tests/unit/analyzers/test_code_analyzer.py
📚 Learning: 2026-04-10T22:41:26.528Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-10T22:41:26.528Z
Learning: Enforce the clean-code review gate through `hatch run specfact code review run --json --out .specfact/code-review.json`

Applied to files:

  • scripts/pre_commit_code_review.py
📚 Learning: 2026-04-10T22:42:04.372Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/session_startup_instructions.mdc:0-0
Timestamp: 2026-04-10T22:42:04.372Z
Learning: After merges shipping OpenSpec- or GitHub-related work with a sibling `specfact-cli-internal` checkout present, run wiki scripts from that sibling repo's working directory (e.g., `cd ../specfact-cli-internal && python3 scripts/wiki_openspec_gh_status.py`), not from `specfact-cli` or other directories

Applied to files:

  • scripts/pre_commit_code_review.py
  • src/specfact_cli/modules/module_registry/src/commands.py
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: Applies to openspec/changes/**/*.md : 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/`)

Applied to files:

  • scripts/pre_commit_code_review.py
  • CHANGELOG.md
📚 Learning: 2026-04-10T22:42:21.860Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T22:42:21.860Z
Learning: Enforce module signatures and version bumps when signed module assets or manifests are affected

Applied to files:

  • src/specfact_cli/modules/module_registry/module-package.yaml
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG.md with all code changes as part of version control requirements.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-03-25T21:33:15.296Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/testing-and-build-guide.mdc:0-0
Timestamp: 2026-03-25T21:33:15.296Z
Learning: Applies to CHANGELOG.md : Update CHANGELOG.md to document all significant changes under Added, Fixed, Changed, or Removed sections when making a version change

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-03-25T21:32:57.944Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/spec-fact-cli-rules.mdc:0-0
Timestamp: 2026-03-25T21:32:57.944Z
Learning: Applies to docs/**/*.md : 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.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2026-04-10T22:41:19.090Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursorrules:0-0
Timestamp: 2026-04-10T22:41:19.090Z
Learning: All development work MUST use git worktrees per AGENTS.md Git Worktree Policy. Never create branches with `git checkout -b` in the primary checkout. Create worktree from origin/dev: `git worktree add ../specfact-cli-worktrees/<type>/<slug> -b <branch-name> origin/dev` where allowed types are: `feature/`, `bugfix/`, `hotfix/`, `chore/`. Forbidden in worktrees: `dev`, `main`. After creating worktree: `cd ../specfact-cli-worktrees/<type>/<slug>`. Bootstrap Hatch in worktree: `hatch env create`. Run pre-flight checks: `hatch run smart-test-status` and `hatch run contract-test-status`. Do all implementation work from the worktree, never from primary checkout. After PR merge: cleanup with `git worktree remove`, `git branch -d`, `git worktree prune`

Applied to files:

  • src/specfact_cli/modules/module_registry/src/commands.py
📚 Learning: 2026-04-10T22:42:04.372Z
Learnt from: CR
Repo: nold-ai/specfact-cli PR: 0
File: .cursor/rules/session_startup_instructions.mdc:0-0
Timestamp: 2026-04-10T22:42:04.372Z
Learning: Detect repository root, branch, and worktree state before implementation

Applied to files:

  • src/specfact_cli/modules/module_registry/src/commands.py
🔀 Multi-repo context nold-ai/specfact-cli-modules

Findings (repo: nold-ai/specfact-cli-modules)

  • docs/guides/installing-modules.md contains examples and recovery guidance (e.g., "specfact module install ...", "specfact module enable backlog", "--scope project --repo") that must be reviewed to ensure wording and exact enable/repair command strings match the PR’s new canonical id / enable/repair behavior. [::nold-ai/specfact-cli-modules::]

  • docs/getting-started/installation.md includes many "specfact init --profile ..." and "specfact init --repo ..." examples and notes about interactive vs repo-scoped init; verify these examples and the wording about auto-enable vs preserve-disabled behavior align with the PR changes. [::nold-ai/specfact-cli-modules::]

  • docs/guides/marketplace.md and docs/getting-started/choose-your-modules.md have profile/install examples (e.g., "specfact init --profile solo-developer", "specfact init --install backlog,codebase") that should be checked for any instructions that assume prior install/no-op semantics. [::nold-ai/specfact-cli-modules::]

  • docs/team-and-enterprise/multi-repo.md and related team docs explicitly recommend using --repo for per-repo init and module actions; these should be validated against the PR’s repo-root normalization behavior to ensure guidance is accurate. [::nold-ai/specfact-cli-modules::]

  • tests/unit/docs/test_docs_review.py references CLI examples (e.g., expects "specfact init ide"); if documentation example wording or exact recovery/enable command strings change, this test will likely need updating. [::nold-ai/specfact-cli-modules::]

Overall recommendation: review and update documentation examples and the docs-review test to reflect the new availability messages, canonical module id usage, and init/install repo-scoped behavior introduced by the PR. [::nold-ai/specfact-cli-modules::]

🔇 Additional comments (29)
src/specfact_cli/modules/module_registry/module-package.yaml (1)

2-2: LGTM — Community-tier manifest correctly drops optional signature.

The integrity.signature removal is valid because tier: community does not require signatures per crypto_validator.py (official-tier manifests would fail validation without one). The version bump to 0.1.23 and updated checksum align with the behavioral changes in this PR.

Also applies to: 11-11, 19-20

src/specfact_cli/analyzers/code_analyzer.py (3)

35-50: Solid architectural choice for deterministic Semgrep isolation.

The repo-local runtime directories under .specfact/ ensure CLI behavior remains consistent across environments and CI runs. The setdefault pattern correctly preserves any explicit overrides from the calling environment.

One operational consideration: verify .specfact/cache/ and .specfact/logs/ are in .gitignore to avoid committing transient Semgrep artifacts.


478-479: LGTM — Version check now uses isolated environment.

Passing the repo-local env to the --version check ensures even the availability probe doesn't pollute or depend on global Semgrep state.


583-589: LGTM — Pattern detection subprocess inherits isolated environment.

Consistent with the version check, the actual Semgrep execution now uses deterministic paths. This prevents cross-contamination between repositories analyzed in the same session.

src/__init__.py (1)

6-6: Version bump to 0.46.7 — primary source of truth updated.

Per coding guidelines, src/__init__.py is the authoritative version source. Verified this aligns with the PR's behavioral fixes for module install state consistency.

pyproject.toml (1)

7-7: Version synchronized with primary source.

The 0.46.7 version matches src/__init__.py and satisfies the CI/CD requirement for PyPI publication (newer than previous).

src/specfact_cli/__init__.py (1)

48-48: Runtime package version synchronized.

All four version locations now report 0.46.7:

  • src/__init__.py
  • pyproject.toml
  • src/specfact_cli/__init__.py
  • setup.py (verified next)
setup.py (1)

10-10: Version synchronization complete.

All four canonical version sources now declare 0.46.7, satisfying the cross-file version consistency requirement. Based on learnings: "Maintain synchronized versions across pyproject.toml, setup.py, src/init.py, and src/specfact_cli/init.py."

tests/unit/analyzers/test_code_analyzer.py (2)

48-59: Solid unit test for Semgrep environment builder.

This test validates both the environment variable configuration and the side effect of directory creation. Good use of tmp_path for test isolation.


61-79: Good integration test for env propagation to subprocess.

The mock validates that the constructed environment flows through to the Semgrep subprocess. Minor observation: the assertion only checks XDG_CONFIG_HOME and XDG_CACHE_HOME, not SEMGREP_VERSION_CACHE_PATH or SEMGREP_LOG_FILE. Consider adding those for completeness, though the companion unit test already covers them.

tests/unit/specfact_cli/modules/test_multi_module_install_uninstall.py (2)

24-25: Fixture rename removes unnecessary underscore prefix.

The autouse=True makes this fixture apply automatically to all tests in the module, so the leading underscore convention for "private" wasn't adding value. Good cleanup.


184-185: Mock signature updated to match refactored function.

The *args approach correctly extracts requested_name from args[1], matching the actual _install_skip_if_already_satisfied signature documented in the context snippet. The str() wrapper handles any edge cases where the argument might not be a plain string.

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

323-329: LGTM! Solid coverage for the new XDG/Semgrep runtime directory behavior.

The assertions verify all four environment variables and confirm that the created directories actually exist. This aligns well with the _build_semgrep_env reference implementation in src/specfact_cli/analyzers/code_analyzer.py:35-50.

One minor gap: consider adding an assertion that Path(env["SEMGREP_LOG_FILE"]).parent.is_dir() to confirm the logs directory was created (the log file itself won't exist yet, but its parent should).

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

97-146: Well-structured test for the disabled-module re-enable flow.

This test validates a critical PR objective: ensuring that install on an already-installed-but-disabled module correctly enables it without dropping unrelated state. The captured state assertions at lines 139-144 verify the preserve-merge semantics.

The or expression in the mock lambda (lines 119-125) is syntactically valid though unconventional; a named helper would be more explicit but this is acceptable for test mocks.


148-195: Good coverage for project-scoped re-enable with explicit --repo.

This test validates that the --scope project --repo flow correctly passes the repo path to discovery and persists the enabled state. The base_paths capture at line 171 is a clean way to verify the discovery call received the expected argument.


224-253: Critical test for repo-path normalization behavior.

This validates an important invariant: when --repo points to a nested subdirectory, the implementation should normalize to the actual repository root (detected via .git). The assertions at lines 251-252 confirm both discovery and install paths are correctly normalized.

This has cross-repo impact per the linked repository findings—documentation in specfact-cli-modules references this normalization behavior, so this test serves as a regression guard.


464-472: Good isolation improvement for the uninstall guidance test.

Adding tmp_path and monkeypatch.chdir(tmp_path) isolates this test from the actual working directory, preventing potential flakiness or cross-test interference. This aligns with the coding guideline that tests should be independent and repeatable.

scripts/pre_commit_code_review.py (2)

114-118: Consistent with the reference implementation in code_analyzer.py.

This helper mirrors _ensure_semgrep_runtime_dir from src/specfact_cli/analyzers/code_analyzer.py:35-38. The duplication is acceptable here since scripts/ is meant to be standalone and shouldn't import from src/specfact_cli.

Consider adding a brief comment noting the intentional parallel with code_analyzer.py for future maintainers.


148-176: Contract coverage is appropriate for this public API.

build_review_subprocess_env() correctly uses @beartype and @ensure to validate the return type. Since the function has no caller-provided arguments requiring validation, the absence of @require is acceptable. The postcondition at line 149-152 ensures all returned keys and values are strings.

src/specfact_cli/modules/module_registry/src/commands.py (6)

185-193: Workspace root normalization looks correct; edge case note.

The .git traversal correctly resolves subdirectory invocations to the workspace root. One architectural consideration: if a user runs --repo /some/path where no .git exists anywhere in the ancestry, the function falls back to the original resolved path (line 193). This is reasonable—monorepo subdirs and detached worktrees are handled—but consider logging a debug-level notice when fallback occurs for troubleshooting.


196-206: Defensive manifest read with graceful fallback—good resilience.

The error handling for OSError and yaml.YAMLError ensures corrupt or missing manifests don't crash the install repair path. The fallback to fallback_name maintains forward progress.


209-221: Enable-if-disabled repair now correctly threads base_path—past review addressed.

The get_discovered_modules_for_state(..., base_path=base_path, preserve_existing=True) call ensures repo-scoped modules are discovered before state is written. The verification at line 221 confirms the enable actually persisted before returning True.

One architectural note: the helper assumes module_id is the canonical manifest name (e.g., nold-ai/specfact-codebase). Callers at lines 234-235 and 251-253 correctly resolve this from manifest or metadata before calling—good contract adherence.


224-261: Install-skip logic now performs lifecycle-aware enable repair—architecturally sound.

The two code paths (already-installed at target root vs. discovered elsewhere) both:

  1. Resolve the canonical module_id from manifest/metadata
  2. Thread base_path appropriately (repo for project scope, None for user)
  3. Report whether state was enabled in the user message

This ensures specfact module install backlog --scope project --repo /other/repo repairs disabled state for that specific repo's modules rather than the default discovery context.


455-472: Single-point repo normalization before discovery and install—past review fully addressed.

The flow now:

  1. Normalizes repo to workspace root once (line 455)
  2. Builds target_root from normalized path (line 456)
  3. Runs discovery against normalized path (lines 457-459)
  4. Threads normalized_repo through _InstallOneParams (line 465)

This eliminates the previous inconsistency where discovery could resolve to repo-root while install wrote to a subdirectory. Clean architectural fix.


457-460: The key mismatch concern is unfounded; keying and lookups are consistent.

discovered_by_name is keyed by entry.metadata.name, which contains only the short module name (e.g., "specfact-codebase"), not the full canonical ID. The lookup at line 249 uses requested_name, which is also the short name extracted from the user input via _normalize_install_module_id. Both dictionary keys and lookups reference the same short-name format; no mismatch exists. The same pattern is used consistently in the uninstall path (line 591).

			> Likely an incorrect or invalid review comment.
src/specfact_cli/registry/module_availability.py (4)

51-58: Namespace-qualified ID matching now requires exact equality—past review addressed.

The fix at lines 56-57 ensures "owner-a/review" no longer incorrectly matches "owner-b/review". Short names like "review" still benefit from tail matching, which is the intended UX.


70-81: Match prioritization logic is architecturally correct.

The three-tier fallback:

  1. Exact/tail module_id matches → return immediately
  2. Qualified module_id with no matches → return empty (no command fallback)
  3. Unqualified name → try command_name matching

This ensures classify_module_availability(module_id="nold-ai/specfact-codebase") never accidentally picks up other-org/specfact-codebase via command matching.


164-191: Contract-first public API with proper @beartype and @icontract decorators.

The classifier follows the coding guidelines for public APIs:

  • @beartype for runtime type checking
  • @require precondition ensuring at least one lookup key
  • @ensure postcondition validating return type

The discovery-match-classify flow is clean and testable. The enabled_map merge at line 183 correctly integrates manifest data with persisted modules.json state.

One suggestion: consider adding a docstring with example usage since this is a public API consumed by cli.py for diagnostics.


111-117: Shadowing detection asymmetry is intentional—only project→user/marketplace is marked SHADOWED.

The function correctly implements the documented module precedence hierarchy: built-in > marketplace > user scope, with project scope overriding user/marketplace during discovery. Project-scoped modules shadowing user or marketplace modules are flagged as SHADOWED because this is the case where a user's global install becomes invisible within a repository context—an actionable distinction that diagnostics should surface.

Other shadow directions (e.g., user shadowing marketplace) don't require explicit detection because lower-priority sources are naturally occluded by the precedence order. This design is confirmed in OpenSpec (user-module-root/spec.md, marketplace-07-module-install-state-consistency/design.md) and aligns with the module registry's documented precedence. The implementation matches the specification intent.

Comment thread scripts/pre_commit_code_review.py Outdated
Comment thread src/specfact_cli/registry/module_availability.py
Comment thread tests/integration/scripts/test_check_local_version_ahead_of_pypi_integration.py Outdated
Comment thread tests/unit/scripts/test_pre_commit_code_review.py Outdated
@djm81 djm81 merged commit f48b156 into dev Apr 28, 2026
22 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in SpecFact CLI Apr 28, 2026
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 enhancement New feature or request

Projects

Status: Done

1 participant