Skip to content

Commit da80932

Browse files
authored
feat(cli): category groups and flat shims using real module Typer (#331)
* feat(cli): category groups and flat shims using real module Typer - Add category groups (code, backlog, project, spec, govern) with flatten same-name member - Sort commands under backlog/project groups A–Z - Fix flat shims to expose real module Typer so 'specfact sync bridge' and 'specfact plan update-idea' work - Add first-run init, module grouping, OpenSpec change for 0.40.x remove-flat-shims - Bump version to 0.39.0, CHANGELOG and OpenSpec updates Made-with: Cursor * Fix signature * fix: resolve module grouping regressions and stabilize CI tests * fix: keep uncategorized modules flat during grouped registration --------- Co-authored-by: Dominikus Nold <djm81@users.noreply.github.com>
1 parent 1e743b5 commit da80932

File tree

66 files changed

+2591
-252
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2591
-252
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ All notable changes to this project will be documented in this file.
88
**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.
99

1010

11+
---
12+
13+
## [0.39.0] - 2026-02-28
14+
15+
### Added
16+
17+
- **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).
18+
- **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.
19+
- **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.
20+
21+
### Fixed
22+
23+
- `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.
24+
1125
---
1226

1327
## [0.38.2] - 2026-02-27

modules/backlog-core/module-package.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
name: backlog-core
2-
version: 0.1.5
2+
version: 0.1.6
33
commands:
44
- backlog
5+
category: backlog
6+
bundle: specfact-backlog
7+
bundle_group_command: backlog
8+
bundle_sub_command: core
59
command_help:
610
backlog: Backlog dependency analysis, delta workflows, and release readiness
711
pip_dependencies: []
@@ -20,10 +24,10 @@ schema_extensions:
2024
publisher:
2125
name: nold-ai
2226
url: https://github.com/nold-ai/specfact-cli-modules
23-
email: oss@nold.ai
27+
email: hello@noldai.com
2428
integrity:
25-
checksum: sha256:c6ae56b1e5f3cf4d4bc0d9d256f24e6377f08e4e82a1f8bead935c0e7cee7431
26-
signature: FpTzbqYcR+6jiRUXjqvzfmqoLGeam7lLyLLc/ZfT7AokzRPz4cl5F/KO0b3XZmXQfHWfT+GFTJi5T/POkobJCg==
29+
checksum: sha256:786a67c54f70930208265217499634ccd5e04cb8404d00762bce2e01904c55e4
30+
signature: Q8CweUicTL/btp9p5QYTlBuXF3yoKvz9ZwaGK0yw3QSM72nni28ZBJ+FivGkmBfcH5zXWAGtASbqC4ry8m5DDQ==
2731
dependencies: []
2832
description: Provide advanced backlog analysis and readiness capabilities.
2933
license: Apache-2.0

modules/bundle-mapper/module-package.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: bundle-mapper
2-
version: 0.1.2
2+
version: 0.1.3
33
commands: []
4+
category: core
45
pip_dependencies: []
56
module_dependencies: []
67
core_compatibility: '>=0.28.0,<1.0.0'
@@ -17,10 +18,10 @@ schema_extensions:
1718
publisher:
1819
name: nold-ai
1920
url: https://github.com/nold-ai/specfact-cli-modules
20-
email: oss@nold.ai
21+
email: hello@noldai.com
2122
integrity:
22-
checksum: sha256:1012f453bc4ae83b22e2cfabce13e5e324d9b4cdf454ce0159b5c5e17dd36f77
23-
signature: LlPqbIH6uD70AInX28PpVurOEv+W/Ztarj5yQhZ3MkC3yORcQrh6ISvJsQeFHFiV1cmnYck7RfDipl4FJyzDAA==
23+
checksum: sha256:359763f8589be35f00b53a996d76ccec32789508d0a2d7dae7e3cdb039a92fc3
24+
signature: OmAp12Rdk79IewQYiKRqvvAm8UgM6onL52Y2/ixSgX3X7onoc9FBKzBYuPmynEVgmJWAI2AX2gdujo/bKH5nAg==
2425
dependencies: []
2526
description: Map backlog items to best-fit modules using scoring heuristics.
2627
license: Apache-2.0

openspec/CHANGE_ORDER.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ These are derived extensions of the same 2026-02-15 plan and are required to ope
8585
| module-migration | 01 | module-migration-01-categorize-and-group | TBD | #215 (marketplace-02) |
8686
| module-migration | 02 | module-migration-02-bundle-extraction | TBD | module-migration-01 |
8787
| module-migration | 03 | module-migration-03-core-slimming | TBD | module-migration-02 |
88+
| module-migration | 04 | module-migration-04-remove-flat-shims | TBD | module-migration-01 |
8889

8990
### Cross-cutting foundations (no hard dependencies — implement early)
9091

@@ -324,6 +325,7 @@ Dependencies flow left-to-right; a wave may start once all its hard blockers are
324325
- backlog-scrum-01 ✅ (needs backlog-core-01; benefits from policy-engine-01 + patch-mode-01)
325326
- backlog-safe-02 (needs backlog-safe-01; integrates with scrum/kanban via bridge registry)
326327
- module-migration-01-categorize-and-group (needs marketplace-02; adds category metadata + group commands)
328+
- module-migration-04-remove-flat-shims (0.40.x; needs module-migration-01; removes flat shims, category-only CLI)
327329
- module-migration-02-bundle-extraction (needs module-migration-01; moves module source to bundle packages, publishes to marketplace registry)
328330
- marketplace-03-publisher-identity (needs marketplace-02; can run parallel with module-migration-01/02/03)
329331
- marketplace-04-revocation (needs marketplace-03; must land before external publisher onboarding)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Change Validation: module-migration-01-categorize-and-group
2+
3+
- **Validated on (UTC):** 2026-02-28T01:02:00Z
4+
- **Workflow:** /wf-validate-change (implementation update re-validation)
5+
- **Strict command:** `openspec validate module-migration-01-categorize-and-group --strict`
6+
- **Status command:** `openspec status --change "module-migration-01-categorize-and-group" --json`
7+
- **Result:** PASS
8+
9+
## Scope Summary
10+
11+
- **Capabilities touched by this update:** `category-command-groups`, `first-run-selection`
12+
- **Regression fixes validated:**
13+
- grouped registration preserves duplicate-command extension merging (no loader overwrite)
14+
- first-run detection treats workspace-local `project` source modules as installed
15+
- **Code paths reviewed:**
16+
- `src/specfact_cli/registry/module_packages.py`
17+
- `src/specfact_cli/modules/init/src/first_run_selection.py`
18+
- `tests/unit/specfact_cli/registry/test_module_packages.py`
19+
- `tests/unit/modules/init/test_first_run_selection.py`
20+
21+
## Breaking-Change Analysis
22+
23+
- No public CLI command names or argument signatures were changed.
24+
- Behavior is a compatibility restoration:
25+
- grouped mode now matches prior extension semantics for duplicate command groups
26+
- `specfact init` first-run suppression now correctly includes project-scoped installed bundles
27+
- No downstream migration is required.
28+
29+
## Dependency and Interface Impact
30+
31+
- Registry impact is internal to loader composition for duplicate command names.
32+
- Init impact is internal to module discovery source filtering.
33+
- No additional OpenSpec change scope expansion was required.
34+
35+
## Validation Outcome
36+
37+
- OpenSpec strict validation passed for this change.
38+
- `openspec status` reports required artifacts present and complete (`proposal`, `design`, `specs`, `tasks`).
39+
- Note: local environment emitted non-blocking OpenSpec telemetry network errors while flushing analytics; validation result remained PASS.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# TDD Evidence: module-migration-01-categorize-and-group
2+
3+
## Phase 3 — First-run module selection in `specfact init`
4+
5+
### 5.1 Failing tests (pre-implementation)
6+
7+
Tests were written first in `tests/unit/modules/init/test_first_run_selection.py`. Initial run (before implementation) would fail on:
8+
9+
- Profile resolution and install parsing (no `resolve_profile_bundles`, `resolve_install_bundles`, `is_first_run`, or `install_bundles_for_init`).
10+
- CLI tests would fail due to missing `--profile`/`--install` and missing first_run_selection integration.
11+
12+
(Exact failing run not captured; implementation followed immediately after test creation.)
13+
14+
### 5.3 Passing tests (post-implementation)
15+
16+
**Timestamp:** 2026-02-28
17+
**Command:** `hatch test -- tests/unit/modules/init/test_first_run_selection.py -v`
18+
**Result:** 16 passed
19+
20+
**Summary:**
21+
22+
- `test_profile_solo_developer_resolves_to_specfact_codebase_only` — profile preset resolution.
23+
- `test_profile_enterprise_full_stack_resolves_to_all_five_bundles` — enterprise preset.
24+
- `test_profile_nonexistent_raises_with_valid_list` — invalid profile raises with valid list.
25+
- `test_install_backlog_codebase_resolves_to_two_bundles``--install` parsing.
26+
- `test_install_all_resolves_to_all_five_bundles``--install all`.
27+
- `test_install_unknown_bundle_raises` — unknown bundle raises.
28+
- `test_is_first_run_true_when_no_category_bundle_installed` — first-run detection (no category bundle).
29+
- `test_is_first_run_false_when_category_bundle_installed` — first-run false when bundle present.
30+
- `test_init_profile_solo_developer_calls_installer_with_specfact_codebase` — CLI `--profile solo-developer`.
31+
- `test_init_profile_enterprise_full_stack_calls_installer_with_all_five` — CLI `--profile enterprise-full-stack`.
32+
- `test_init_profile_nonexistent_exits_nonzero_and_lists_valid_profiles` — CLI invalid profile exits non-zero.
33+
- `test_init_install_backlog_codebase_calls_installer_with_two_bundles` — CLI `--install backlog,codebase`.
34+
- `test_init_install_all_calls_installer_with_five_bundles` — CLI `--install all`.
35+
- `test_init_install_widgets_exits_nonzero` — CLI unknown bundle exits non-zero.
36+
- `test_init_second_run_skips_first_run_flow` — second run does not call installer when no `--profile`/`--install`.
37+
- `test_spec_bundle_install_includes_project_dep``install_bundles_for_init(["specfact-spec"])` installs project dep.
38+
39+
Implementation: `src/specfact_cli/modules/init/src/first_run_selection.py` and `commands.py` (--profile, --install, first_run_selection integration).
40+
41+
### Phase 3 follow-up (5.2.3, 5.2.7)
42+
43+
**Interactive first-run UI (5.2.3):**
44+
- `_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.
45+
- `BUNDLE_DISPLAY` and `PROFILE_DISPLAY_ORDER` in first_run_selection.py for UI labels.
46+
47+
**Graceful degradation (5.2.7):**
48+
- 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.
49+
50+
**Additional tests:**
51+
- `test_init_first_run_interactive_with_selection_calls_installer`: first run + interactive, mock selection returns ["specfact-codebase"], assert install called.
52+
- `test_init_first_run_interactive_no_selection_shows_tip`: first run + interactive, mock selection returns [], assert no install and "Tip" / "module install" in output.
53+
54+
**Run:** `hatch test -- tests/unit/modules/init/test_first_run_selection.py -v` — 18 passed.
55+
56+
## Section 6 — Integration and E2E
57+
58+
**Timestamp:** 2026-02-28
59+
**Commands:** `hatch test -- tests/integration/test_category_group_routing.py tests/e2e/test_first_run_init.py -v`
60+
**Result:** 5 passed (3 integration + 2 e2e).
61+
62+
**Integration:** `test_code_analyze_help_exits_zero`, `test_backlog_help_lists_subcommands`, `test_validate_shim_help_exits_zero`.
63+
**E2E:** `test_init_profile_solo_developer_completes_in_temp_workspace`, `test_after_solo_developer_init_code_analyze_help_available` (install_bundles_for_init mocked).
64+
65+
## Phase 4 — Regression fixes from review (grouped extension merge + project-scoped first-run)
66+
67+
### 4.1 Failing tests (pre-implementation)
68+
69+
**Timestamp:** 2026-02-28 01:00 UTC
70+
**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`
71+
**Result:** 2 failed.
72+
73+
**Failure summary:**
74+
75+
- `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.
76+
- `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.
77+
78+
### 4.2 Passing tests (post-implementation)
79+
80+
**Timestamp:** 2026-02-28 01:01 UTC
81+
**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`
82+
**Result:** 2 passed.
83+
84+
**Implementation summary:**
85+
86+
- 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.
87+
- Updated `is_first_run()` source filter to include `project` modules in first-run detection.
88+
89+
## Phase 5 — Regression fix from PR 331 (trust failure should not block unaffected legacy module registration)
90+
91+
### 5.1 Failing test (pre-implementation)
92+
93+
**Timestamp:** 2026-02-28 21:07 local
94+
**Command:** `hatch test -- tests/unit/specfact_cli/registry/test_module_packages.py::test_unaffected_modules_register_when_one_fails_trust -v`
95+
**Result:** 1 failed.
96+
97+
**Failure summary:**
98+
99+
- 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.
100+
101+
### 5.2 Passing tests (post-implementation)
102+
103+
**Timestamp:** 2026-02-28 21:09 local
104+
**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`
105+
**Result:** 3 passed.
106+
107+
**Implementation summary:**
108+
109+
- Updated `register_module_package_commands()` to use grouped registration only when `category_grouping_enabled` is true and module metadata declares `category`.
110+
- 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.

openspec/changes/module-migration-01-categorize-and-group/proposal.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ This mirrors the VS Code model: ship a lean core, present workflow-domain groups
6363
- **GitHub Issue**: #315
6464
- **Issue URL**: <https://github.com/nold-ai/specfact-cli/issues/315>
6565
- **Repository**: nold-ai/specfact-cli
66-
- **Last Synced Status**: proposed
66+
- **Last Synced Status**: open
6767
- **Sanitized**: false

openspec/changes/module-migration-01-categorize-and-group/specs/category-command-groups/spec.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ Each category group SHALL expose its member modules as sub-commands, preserving
2626
- **THEN** the command SHALL execute identically to the original `specfact analyze contracts`
2727
- **AND** the exit code, output format, and side effects SHALL be identical
2828

29+
#### Scenario: Grouped registration preserves command extensions for duplicate command names
30+
31+
- **GIVEN** `category_grouping_enabled` is `true`
32+
- **AND** a base module provides command group `backlog`
33+
- **AND** an extension module also declares command group `backlog`
34+
- **WHEN** module package commands are registered
35+
- **THEN** the registry SHALL merge extension subcommands into the existing `backlog` command tree
36+
- **AND** SHALL NOT replace the existing loader with only the extension loader
37+
- **AND** both base and extension subcommands SHALL remain accessible under `specfact backlog ...`
38+
2939
#### Scenario: Category group command is absent when bundle not installed
3040

3141
- **GIVEN** the `govern` bundle is NOT installed

openspec/changes/module-migration-01-categorize-and-group/specs/first-run-selection/spec.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ On a fresh install where no bundles are installed, `specfact init` SHALL present
4949
- **THEN** the CLI SHALL NOT show the bundle selection UI
5050
- **AND** SHALL run the standard workspace re-initialisation flow
5151

52+
#### Scenario: Workspace-local project-scoped modules suppress first-run flow
53+
54+
- **GIVEN** a repository already contains category bundle modules under workspace-local `.specfact/modules`
55+
- **AND** those modules are discovered with source `project`
56+
- **WHEN** the user runs `specfact init`
57+
- **THEN** first-run detection SHALL treat the workspace as already initialized
58+
- **AND** the CLI SHALL NOT show first-run bundle selection again
59+
- **AND** SHALL run the standard workspace re-initialisation flow
60+
5261
### Requirement: `specfact init --profile <name>` installs a named preset non-interactively
5362

5463
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.

0 commit comments

Comments
 (0)