Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ All notable changes to this project will be documented in this file.
**Important:** Changes need to be documented below this block as this is the header section. Each section should be separated by a horizontal rule. Newer changelog entries need to be added on top of prior ones to keep the history chronological with most recent changes first.


---

## [0.39.0] - 2026-02-28

### Added

- **Category group commands** (OpenSpec change `module-migration-01-categorize-and-group`): Category grouping mounts commands under `code`, `backlog`, `project`, `spec`, and `govern`. Use `specfact code analyze`, `specfact backlog --help`, etc. Flat shims (e.g. `specfact validate`) remain with deprecation notice in Copilot mode. Configurable via `category_grouping_enabled` (default true).
- **First-run module selection in `specfact init`**: `--profile solo-developer` and `--profile enterprise-full-stack`, plus `--install <bundles>` and interactive bundle selection on first run when no category bundle is installed.
- **Integration and E2E tests**: `tests/integration/test_category_group_routing.py` and `tests/e2e/test_first_run_init.py` for category routing and init profile flows.

### Fixed

- `test_module_grouping.py` now imports `group_modules_by_category` from `module_grouping` instead of `module_packages`, fixing collection errors in the full test suite.

---

## [0.38.2] - 2026-02-27
Expand Down
12 changes: 8 additions & 4 deletions modules/backlog-core/module-package.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
name: backlog-core
version: 0.1.5
version: 0.1.6
commands:
- backlog
category: backlog
bundle: specfact-backlog
bundle_group_command: backlog
bundle_sub_command: core
command_help:
backlog: Backlog dependency analysis, delta workflows, and release readiness
pip_dependencies: []
Expand All @@ -20,10 +24,10 @@ schema_extensions:
publisher:
name: nold-ai
url: https://github.com/nold-ai/specfact-cli-modules
email: oss@nold.ai
email: hello@noldai.com
integrity:
checksum: sha256:c6ae56b1e5f3cf4d4bc0d9d256f24e6377f08e4e82a1f8bead935c0e7cee7431
signature: FpTzbqYcR+6jiRUXjqvzfmqoLGeam7lLyLLc/ZfT7AokzRPz4cl5F/KO0b3XZmXQfHWfT+GFTJi5T/POkobJCg==
checksum: sha256:786a67c54f70930208265217499634ccd5e04cb8404d00762bce2e01904c55e4
signature: Q8CweUicTL/btp9p5QYTlBuXF3yoKvz9ZwaGK0yw3QSM72nni28ZBJ+FivGkmBfcH5zXWAGtASbqC4ry8m5DDQ==
dependencies: []
description: Provide advanced backlog analysis and readiness capabilities.
license: Apache-2.0
9 changes: 5 additions & 4 deletions modules/bundle-mapper/module-package.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: bundle-mapper
version: 0.1.2
version: 0.1.3
commands: []
category: core
pip_dependencies: []
module_dependencies: []
core_compatibility: '>=0.28.0,<1.0.0'
Expand All @@ -17,10 +18,10 @@ schema_extensions:
publisher:
name: nold-ai
url: https://github.com/nold-ai/specfact-cli-modules
email: oss@nold.ai
email: hello@noldai.com
integrity:
checksum: sha256:1012f453bc4ae83b22e2cfabce13e5e324d9b4cdf454ce0159b5c5e17dd36f77
signature: LlPqbIH6uD70AInX28PpVurOEv+W/Ztarj5yQhZ3MkC3yORcQrh6ISvJsQeFHFiV1cmnYck7RfDipl4FJyzDAA==
checksum: sha256:359763f8589be35f00b53a996d76ccec32789508d0a2d7dae7e3cdb039a92fc3
signature: OmAp12Rdk79IewQYiKRqvvAm8UgM6onL52Y2/ixSgX3X7onoc9FBKzBYuPmynEVgmJWAI2AX2gdujo/bKH5nAg==
dependencies: []
description: Map backlog items to best-fit modules using scoring heuristics.
license: Apache-2.0
2 changes: 2 additions & 0 deletions openspec/CHANGE_ORDER.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ These are derived extensions of the same 2026-02-15 plan and are required to ope
| module-migration | 01 | module-migration-01-categorize-and-group | TBD | #215 (marketplace-02) |
| module-migration | 02 | module-migration-02-bundle-extraction | TBD | module-migration-01 |
| module-migration | 03 | module-migration-03-core-slimming | TBD | module-migration-02 |
| module-migration | 04 | module-migration-04-remove-flat-shims | TBD | module-migration-01 |

### Cross-cutting foundations (no hard dependencies β€” implement early)

Expand Down Expand Up @@ -324,6 +325,7 @@ Dependencies flow left-to-right; a wave may start once all its hard blockers are
- backlog-scrum-01 βœ… (needs backlog-core-01; benefits from policy-engine-01 + patch-mode-01)
- backlog-safe-02 (needs backlog-safe-01; integrates with scrum/kanban via bridge registry)
- module-migration-01-categorize-and-group (needs marketplace-02; adds category metadata + group commands)
- module-migration-04-remove-flat-shims (0.40.x; needs module-migration-01; removes flat shims, category-only CLI)
- module-migration-02-bundle-extraction (needs module-migration-01; moves module source to bundle packages, publishes to marketplace registry)
- marketplace-03-publisher-identity (needs marketplace-02; can run parallel with module-migration-01/02/03)
- marketplace-04-revocation (needs marketplace-03; must land before external publisher onboarding)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Change Validation: module-migration-01-categorize-and-group

- **Validated on (UTC):** 2026-02-28T01:02:00Z
- **Workflow:** /wf-validate-change (implementation update re-validation)
- **Strict command:** `openspec validate module-migration-01-categorize-and-group --strict`
- **Status command:** `openspec status --change "module-migration-01-categorize-and-group" --json`
- **Result:** PASS

## Scope Summary

- **Capabilities touched by this update:** `category-command-groups`, `first-run-selection`
- **Regression fixes validated:**
- grouped registration preserves duplicate-command extension merging (no loader overwrite)
- first-run detection treats workspace-local `project` source modules as installed
- **Code paths reviewed:**
- `src/specfact_cli/registry/module_packages.py`
- `src/specfact_cli/modules/init/src/first_run_selection.py`
- `tests/unit/specfact_cli/registry/test_module_packages.py`
- `tests/unit/modules/init/test_first_run_selection.py`

## Breaking-Change Analysis

- No public CLI command names or argument signatures were changed.
- Behavior is a compatibility restoration:
- grouped mode now matches prior extension semantics for duplicate command groups
- `specfact init` first-run suppression now correctly includes project-scoped installed bundles
- No downstream migration is required.

## Dependency and Interface Impact

- Registry impact is internal to loader composition for duplicate command names.
- Init impact is internal to module discovery source filtering.
- No additional OpenSpec change scope expansion was required.

## Validation Outcome

- OpenSpec strict validation passed for this change.
- `openspec status` reports required artifacts present and complete (`proposal`, `design`, `specs`, `tasks`).
- Note: local environment emitted non-blocking OpenSpec telemetry network errors while flushing analytics; validation result remained PASS.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# TDD Evidence: module-migration-01-categorize-and-group

## Phase 3 β€” First-run module selection in `specfact init`

### 5.1 Failing tests (pre-implementation)

Tests were written first in `tests/unit/modules/init/test_first_run_selection.py`. Initial run (before implementation) would fail on:

- Profile resolution and install parsing (no `resolve_profile_bundles`, `resolve_install_bundles`, `is_first_run`, or `install_bundles_for_init`).
- CLI tests would fail due to missing `--profile`/`--install` and missing first_run_selection integration.

(Exact failing run not captured; implementation followed immediately after test creation.)

### 5.3 Passing tests (post-implementation)

**Timestamp:** 2026-02-28
**Command:** `hatch test -- tests/unit/modules/init/test_first_run_selection.py -v`
**Result:** 16 passed

**Summary:**

- `test_profile_solo_developer_resolves_to_specfact_codebase_only` β€” profile preset resolution.
- `test_profile_enterprise_full_stack_resolves_to_all_five_bundles` β€” enterprise preset.
- `test_profile_nonexistent_raises_with_valid_list` β€” invalid profile raises with valid list.
- `test_install_backlog_codebase_resolves_to_two_bundles` β€” `--install` parsing.
- `test_install_all_resolves_to_all_five_bundles` β€” `--install all`.
- `test_install_unknown_bundle_raises` β€” unknown bundle raises.
- `test_is_first_run_true_when_no_category_bundle_installed` β€” first-run detection (no category bundle).
- `test_is_first_run_false_when_category_bundle_installed` β€” first-run false when bundle present.
- `test_init_profile_solo_developer_calls_installer_with_specfact_codebase` β€” CLI `--profile solo-developer`.
- `test_init_profile_enterprise_full_stack_calls_installer_with_all_five` β€” CLI `--profile enterprise-full-stack`.
- `test_init_profile_nonexistent_exits_nonzero_and_lists_valid_profiles` β€” CLI invalid profile exits non-zero.
- `test_init_install_backlog_codebase_calls_installer_with_two_bundles` β€” CLI `--install backlog,codebase`.
- `test_init_install_all_calls_installer_with_five_bundles` β€” CLI `--install all`.
- `test_init_install_widgets_exits_nonzero` β€” CLI unknown bundle exits non-zero.
- `test_init_second_run_skips_first_run_flow` β€” second run does not call installer when no `--profile`/`--install`.
- `test_spec_bundle_install_includes_project_dep` β€” `install_bundles_for_init(["specfact-spec"])` installs project dep.

Implementation: `src/specfact_cli/modules/init/src/first_run_selection.py` and `commands.py` (--profile, --install, first_run_selection integration).

### Phase 3 follow-up (5.2.3, 5.2.7)

**Interactive first-run UI (5.2.3):**
- `_interactive_first_run_bundle_selection()` in commands.py: welcome banner (Panel), questionary.select for profile or "Choose bundles manually", questionary.checkbox for manual bundle selection. When first run and interactive and no --profile/--install, init() calls it and installs selected bundles or shows tip if none.
- `BUNDLE_DISPLAY` and `PROFILE_DISPLAY_ORDER` in first_run_selection.py for UI labels.

**Graceful degradation (5.2.7):**
- In `install_bundles_for_init`, each `install_bundled_module` call wrapped in try/except; on exception log warning "Dependency resolver may be unavailable" and re-raise so errors are surfaced.

**Additional tests:**
- `test_init_first_run_interactive_with_selection_calls_installer`: first run + interactive, mock selection returns ["specfact-codebase"], assert install called.
- `test_init_first_run_interactive_no_selection_shows_tip`: first run + interactive, mock selection returns [], assert no install and "Tip" / "module install" in output.

**Run:** `hatch test -- tests/unit/modules/init/test_first_run_selection.py -v` β€” 18 passed.

## Section 6 β€” Integration and E2E

**Timestamp:** 2026-02-28
**Commands:** `hatch test -- tests/integration/test_category_group_routing.py tests/e2e/test_first_run_init.py -v`
**Result:** 5 passed (3 integration + 2 e2e).

**Integration:** `test_code_analyze_help_exits_zero`, `test_backlog_help_lists_subcommands`, `test_validate_shim_help_exits_zero`.
**E2E:** `test_init_profile_solo_developer_completes_in_temp_workspace`, `test_after_solo_developer_init_code_analyze_help_available` (install_bundles_for_init mocked).

## Phase 4 β€” Regression fixes from review (grouped extension merge + project-scoped first-run)

### 4.1 Failing tests (pre-implementation)

**Timestamp:** 2026-02-28 01:00 UTC
**Command:** `hatch test -- tests/unit/specfact_cli/registry/test_module_packages.py::test_grouped_registration_merges_duplicate_command_extensions tests/unit/modules/init/test_first_run_selection.py::test_is_first_run_false_when_project_scoped_category_bundle_installed -v`
**Result:** 2 failed.

**Failure summary:**

- `test_grouped_registration_merges_duplicate_command_extensions` failed because grouped registration replaced the earlier `backlog` loader; observed commands were only `('ext_cmd',)` and `base_cmd` was missing.
- `test_is_first_run_false_when_project_scoped_category_bundle_installed` failed because `is_first_run()` ignored modules discovered with source `project`, returning `True` for an already-initialized workspace.

### 4.2 Passing tests (post-implementation)

**Timestamp:** 2026-02-28 01:01 UTC
**Command:** `hatch test -- tests/unit/specfact_cli/registry/test_module_packages.py::test_grouped_registration_merges_duplicate_command_extensions tests/unit/modules/init/test_first_run_selection.py::test_is_first_run_false_when_project_scoped_category_bundle_installed -v`
**Result:** 2 passed.

**Implementation summary:**

- Updated `register_module_package_commands()` grouped path to merge duplicate command loaders via `_make_extending_loader` for module entries (and core root entries), instead of unconditional overwrite.
- Updated `is_first_run()` source filter to include `project` modules in first-run detection.

## Phase 5 β€” Regression fix from PR 331 (trust failure should not block unaffected legacy module registration)

### 5.1 Failing test (pre-implementation)

**Timestamp:** 2026-02-28 21:07 local
**Command:** `hatch test -- tests/unit/specfact_cli/registry/test_module_packages.py::test_unaffected_modules_register_when_one_fails_trust -v`
**Result:** 1 failed.

**Failure summary:**

- In grouped mode, a module without `category` metadata was routed into grouped registration, so `good_cmd` was not mounted as flat top-level despite warning text indicating flat mounting.

### 5.2 Passing tests (post-implementation)

**Timestamp:** 2026-02-28 21:09 local
**Command:** `hatch test -- tests/unit/specfact_cli/registry/test_module_packages.py::test_unaffected_modules_register_when_one_fails_trust tests/unit/specfact_cli/registry/test_module_packages.py::test_grouped_registration_merges_duplicate_command_extensions tests/unit/registry/test_module_grouping.py::test_module_package_yaml_without_category_mounts_ungrouped_warning_logged -v`
**Result:** 3 passed.

**Implementation summary:**

- Updated `register_module_package_commands()` to use grouped registration only when `category_grouping_enabled` is true and module metadata declares `category`.
- Updated grouped-extension unit fixture metadata to include `category="backlog"` so the test reflects migration-era grouped manifests and remains aligned with category-driven grouping semantics.
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@ This mirrors the VS Code model: ship a lean core, present workflow-domain groups
- **GitHub Issue**: #315
- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/315>
- **Repository**: nold-ai/specfact-cli
- **Last Synced Status**: proposed
- **Last Synced Status**: open
- **Sanitized**: false
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ Each category group SHALL expose its member modules as sub-commands, preserving
- **THEN** the command SHALL execute identically to the original `specfact analyze contracts`
- **AND** the exit code, output format, and side effects SHALL be identical

#### Scenario: Grouped registration preserves command extensions for duplicate command names

- **GIVEN** `category_grouping_enabled` is `true`
- **AND** a base module provides command group `backlog`
- **AND** an extension module also declares command group `backlog`
- **WHEN** module package commands are registered
- **THEN** the registry SHALL merge extension subcommands into the existing `backlog` command tree
- **AND** SHALL NOT replace the existing loader with only the extension loader
- **AND** both base and extension subcommands SHALL remain accessible under `specfact backlog ...`

#### Scenario: Category group command is absent when bundle not installed

- **GIVEN** the `govern` bundle is NOT installed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ On a fresh install where no bundles are installed, `specfact init` SHALL present
- **THEN** the CLI SHALL NOT show the bundle selection UI
- **AND** SHALL run the standard workspace re-initialisation flow

#### Scenario: Workspace-local project-scoped modules suppress first-run flow

- **GIVEN** a repository already contains category bundle modules under workspace-local `.specfact/modules`
- **AND** those modules are discovered with source `project`
- **WHEN** the user runs `specfact init`
- **THEN** first-run detection SHALL treat the workspace as already initialized
- **AND** the CLI SHALL NOT show first-run bundle selection again
- **AND** SHALL run the standard workspace re-initialisation flow

### Requirement: `specfact init --profile <name>` installs a named preset non-interactively

The system SHALL accept a `--profile <name>` argument on `specfact init` and MUST install the canonical bundle set for that profile without prompting, whether in CI/CD mode or interactive mode.
Expand Down
Loading
Loading