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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ All notable changes to this project will be documented in this file.
- Marketplace package loader now resolves namespaced command entrypoints (`src/<package>/<command>/app.py`) for installed modules.
- Installed bundle detection now infers `specfact-*` bundle IDs from namespaced module names when manifest `bundle` metadata is absent.

### Removed

- **BREAKING**: Removed flat root command shims (OpenSpec change `module-migration-04-remove-flat-shims`, issue [#330](https://github.com/nold-ai/specfact-cli/issues/330)). Use grouped commands only, for example `specfact code validate` instead of `specfact validate`.

### Deprecated

- Legacy flat import paths under `specfact_cli.modules.*` are deprecated in favor of bundle namespaces (`specfact_project.*`, `specfact_backlog.*`, `specfact_codebase.*`, `specfact_spec.*`, `specfact_govern.*`) and are planned for removal in the next major release.
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ specfact code validate sidecar init my-project /path/to/repo
specfact code validate sidecar run my-project /path/to/repo
```

### Migration Note (Flat Commands Removed)

As of `0.40.0`, flat root commands are removed. Use grouped commands:

- `specfact validate ...` -> `specfact code validate ...`
- `specfact plan ...` -> `specfact project plan ...`
- `specfact policy ...` -> `specfact backlog policy ...`

### Backlog Bridge (60 seconds)

SpecFact's USP is closing the drift gap between **backlog -> specs -> code**.
Expand All @@ -72,7 +80,7 @@ specfact backlog daily ado --ado-org <org> --ado-project "<project>" --state any
specfact backlog refine ado --ado-org <org> --ado-project "<project>" --id <work-item-id> --preview

# 3) Keep backlog + spec intent aligned (avoid silent drift)
specfact policy validate --group-by-item
specfact backlog policy validate --group-by-item
```

For GitHub, replace adapter/org/project with:
Expand Down Expand Up @@ -140,18 +148,18 @@ Most tools help **either** coders **or** agile teams. SpecFact does both:
Recommended command entrypoints:
- `specfact backlog ceremony standup ...`
- `specfact backlog ceremony refinement ...`
- `specfact policy validate ...`
- `specfact policy suggest ...`
- `specfact backlog policy validate ...`
- `specfact backlog policy suggest ...`

What the Policy Engine does in practice:
- Turns team agreements (DoR, DoD, flow checks) into executable checks against your real backlog data.
- Shows exactly what is missing per item (for example missing acceptance criteria or definition of done).
- Generates patch-ready suggestions so teams can fix policy gaps quickly without guessing.

Start with:
- `specfact policy init --template scrum`
- `specfact policy validate --group-by-item`
- `specfact policy suggest --group-by-item --limit 5`
- `specfact backlog policy init --template scrum`
- `specfact backlog policy validate --group-by-item`
- `specfact backlog policy suggest --group-by-item --limit 5`

**Try it now**

Expand Down
12 changes: 10 additions & 2 deletions docs/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ SpecFact runs on a lifecycle-managed module system.

```bash
# CLI-only mode (works with uvx, no installation needed)
uvx specfact-cli@latest import from-code my-project --repo .
uvx specfact-cli@latest project import from-code my-project --repo .

# Interactive AI Assistant mode (requires pip install + specfact init)
# See First Steps guide for IDE integration setup
Expand All @@ -35,14 +35,22 @@ uvx specfact-cli@latest import from-code my-project --repo .

```bash
# CLI-only mode (bundle name as positional argument)
uvx specfact-cli@latest plan init my-project --interactive
uvx specfact-cli@latest project plan init my-project --interactive

# Interactive AI Assistant mode (recommended for better results)
# Requires: pip install specfact-cli && specfact init
```

**Note**: Interactive AI Assistant mode provides better feature detection and semantic understanding, but requires `pip install specfact-cli` and IDE setup. CLI-only mode works immediately with `uvx` but may show 0 features for simple test cases.

### Migration Note (0.40.0)

Flat root commands were removed. Use grouped command forms:

- `specfact validate ...` -> `specfact code validate ...`
- `specfact plan ...` -> `specfact project plan ...`
- `specfact policy ...` -> `specfact backlog policy ...`

First-run bundle selection examples:

```bash
Expand Down
17 changes: 9 additions & 8 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ permalink: /reference/commands/
# Command Reference

SpecFact CLI now ships a lean core. Workflow commands are installed from marketplace bundles.
Flat root-level compatibility shims were removed in `0.40.0`; use category-group commands only.

## Top-Level Commands

Fresh install includes only:
Root command surface includes core commands and installed category groups only:

- `specfact init`
- `specfact backlog auth`
- `specfact auth`
- `specfact module`
- `specfact upgrade`
- `specfact code ...`
- `specfact backlog ...`
- `specfact project ...`
- `specfact spec ...`
- `specfact govern ...`

Use `specfact init --profile <name>` (or `--install <list>`) to install workflow bundles.

Expand All @@ -39,7 +45,7 @@ After bundle install, command groups are mounted by category:
| `nold-ai/specfact-spec` | `spec` | `contract`, `api`, `sdd`, `generate` |
| `nold-ai/specfact-govern` | `govern` | `enforce`, `patch` |

## Removed Flat Commands
## Migration: Removed Flat Commands

Flat compatibility shims were removed in this change. Use grouped commands.

Expand All @@ -61,11 +67,6 @@ Flat compatibility shims were removed in this change. Use grouped commands.
| `specfact enforce ...` | `specfact govern enforce ...` |
| `specfact patch ...` | `specfact govern patch ...` |

Legacy reference kept for release-doc parity:

- `specfact patch apply --dry-run`
- `specfact patch apply --write`

## Common Flows

```bash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# TDD Evidence: module-migration-04-remove-flat-shims

## Pre-Implementation Failing Run

- Timestamp: 2026-03-04T20:23:10+01:00
- Command:

```bash
PYTHONPATH=/home/dom/git/nold-ai/specfact-cli-worktrees/feature/module-migration-04-remove-flat-shims/src \
/home/dom/git/nold-ai/specfact-cli/.venv/bin/python -m pytest \
tests/unit/specfact_cli/registry/test_module_packages.py \
-k grouped_registration_does_not_register_flat_shim_commands -v
```

- Result: **FAILED** (expected red phase)
- Failure summary: `validate` was still registered at root (`{'code', 'validate'}`), proving flat shim machinery was active.

## Post-Implementation Passing Run

- Timestamp: 2026-03-04T20:24:03+01:00
- Commands:

```bash
PYTHONPATH=/home/dom/git/nold-ai/specfact-cli-worktrees/feature/module-migration-04-remove-flat-shims/src \
/home/dom/git/nold-ai/specfact-cli/.venv/bin/python -m pytest \
tests/unit/specfact_cli/registry/test_module_packages.py \
-k grouped_registration_does_not_register_flat_shim_commands -v

PYTHONPATH=/home/dom/git/nold-ai/specfact-cli-worktrees/feature/module-migration-04-remove-flat-shims/src \
/home/dom/git/nold-ai/specfact-cli/.venv/bin/python -m pytest \
tests/unit/registry/test_category_groups.py \
-k "flat_validate_is_not_found_in_copilot_mode or flat_validate_is_not_found_in_cicd_mode" -v

PYTHONPATH=/home/dom/git/nold-ai/specfact-cli-worktrees/feature/module-migration-04-remove-flat-shims/src \
/home/dom/git/nold-ai/specfact-cli/.venv/bin/python -m pytest \
tests/integration/test_category_group_routing.py \
-k validate_flat_command_is_not_available -v
```

- Result: **PASSED**
- Passing summary: flat `validate` is no longer registered as a root command; category-only behavior is enforced for this shim-removal scope.

## Scope Note

- This change intentionally runs shim-removal-focused tests only.
- Broader suite migration/cleanup debt remains out of scope for this change and is deferred per migration planning.
41 changes: 21 additions & 20 deletions openspec/changes/module-migration-04-remove-flat-shims/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,39 @@ TDD/SDD order enforced. Version series: **0.40.x**.

## 1. Branch and prep

- [ ] 1.1 Create feature branch from `dev`: `feature/module-migration-04-remove-flat-shims`
- [ ] 1.2 Ensure module-migration-01 is merged to dev (category groups and shims exist)
- [x] 1.1 Create feature branch from `dev`: `feature/module-migration-04-remove-flat-shims`
- [x] 1.2 Ensure module-migration-01 is merged to dev (category groups and shims exist)

## 2. Spec and tests first

- [ ] 2.1 Add spec delta under `specs/category-command-groups/`: when `category_grouping_enabled` is true, root CLI SHALL list only core commands (init, auth, module, upgrade) and the five category groups (code, backlog, project, spec, govern). No flat shim commands.
- [ ] 2.2 Update or add tests that assert root help contains only core + groups when grouping enabled; remove or rewrite tests that assert flat shim deprecation or `specfact validate --help` success for shim.
- [ ] 2.3 Run tests and capture **failing** result (shims still present) in `TDD_EVIDENCE.md`.
- [ ] 2.4 Scope note: restrict to shim-removal-focused tests in `specfact-cli`; do **not** absorb broad suite migration/cleanup failures here.
- [x] 2.1 Add spec delta under `specs/category-command-groups/`: when `category_grouping_enabled` is true, root CLI SHALL list only core commands (init, auth, module, upgrade) and the five category groups (code, backlog, project, spec, govern). No flat shim commands.
- [x] 2.2 Update or add tests that assert root help contains only core + groups when grouping enabled; remove or rewrite tests that assert flat shim deprecation or `specfact validate --help` success for shim.
- [x] 2.3 Run tests and capture **failing** result (shims still present) in `TDD_EVIDENCE.md`.
- [x] 2.4 Scope note: restrict to shim-removal-focused tests in `specfact-cli`; do **not** absorb broad suite migration/cleanup failures here.

## 3. Implementation

- [ ] 3.1 In `module_packages.py`: remove the loop that registers shims from `FLAT_TO_GROUP`; keep only category group registration. Rename `_register_category_groups_and_shims` → `_register_category_groups` (or equivalent).
- [ ] 3.2 Remove `FLAT_TO_GROUP` and `_make_shim_loader()` (and any code only used by shims).
- [ ] 3.4 Run tests; capture **passing** result in `TDD_EVIDENCE.md`.
- [x] 3.1 In `module_packages.py`: remove the loop that registers shims from `FLAT_TO_GROUP`; keep only category group registration. Rename `_register_category_groups_and_shims` → `_register_category_groups` (or equivalent).
- [x] 3.2 Remove `FLAT_TO_GROUP` and `_make_shim_loader()` (and any code only used by shims).
- [x] 3.4 Run tests; capture **passing** result in `TDD_EVIDENCE.md`.

## 4. Quality gates

- [ ] 4.1 `hatch run format` and fix
- [ ] 4.2 `hatch run type-check` and fix
- [ ] 4.3 `hatch run lint` and fix
- [ ] 4.4 `hatch run contract-test` and fix
- [ ] 4.5 `hatch run smart-test` for this change scope; if `smart-test-full` exposes unrelated migration debt, record and defer to follow-up change(s) per migration-03 phase 20.
- [x] 4.1 `hatch run format` and fix
- [x] 4.2 `hatch run type-check` and fix
- [x] 4.3 `hatch run lint` and fix
- Deferred: remaining repository-wide pylint debt is tracked for follow-up changes `module-migration-06` / `module-migration-07`.
- [x] 4.4 `hatch run contract-test` and fix
- [x] 4.5 `hatch run smart-test` for this change scope; if `smart-test-full` exposes unrelated migration debt, record and defer to follow-up change(s) per migration-03 phase 20.

## 5. Documentation and release

- [ ] 5.1 Update `docs/reference/commands.md`: command topology is category-only (no flat commands).
- [ ] 5.2 Update `docs/guides/getting-started.md` and `README.md`: command list shows only core + categories; add migration note for users of flat commands.
- [ ] 5.3 Bump version to **0.40.0** in `pyproject.toml`, `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py`.
- [ ] 5.4 Add CHANGELOG.md entry for 0.40.0: **BREAKING** — removed flat command shims; use `specfact <group> <sub>` (e.g. `specfact code validate`).
- [x] 5.1 Update `docs/reference/commands.md`: command topology is category-only (no flat commands).
- [x] 5.2 Update `docs/guides/getting-started.md` and `README.md`: command list shows only core + categories; add migration note for users of flat commands.
- [x] 5.3 Bump version to **0.40.0** in `pyproject.toml`, `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py`.
- [x] 5.4 Add CHANGELOG.md entry for 0.40.0: **BREAKING** — removed flat command shims; use `specfact <group> <sub>` (e.g. `specfact code validate`).

## 6. PR

- [ ] 6.1 Create GitHub issue for change (title: `[Change] Remove flat shims — category-only CLI (0.40.x)`); link in proposal Source Tracking.
- [ ] 6.2 Open PR to `dev`; reference this change and breaking-change migration path.
- [x] 6.1 Create GitHub issue for change (title: `[Change] Remove flat shims — category-only CLI (0.40.x)`); link in proposal Source Tracking.
- [x] 6.2 Open PR to `dev`; reference this change and breaking-change migration path.
61 changes: 2 additions & 59 deletions src/specfact_cli/registry/module_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,44 +844,6 @@ def merge_module_state(
return merged


# Flat command name -> (group_command, sub_command) for compat shims when category grouping is enabled.
FLAT_TO_GROUP: dict[str, tuple[str, str]] = {
"analyze": ("code", "analyze"),
"drift": ("code", "drift"),
"validate": ("code", "validate"),
"repro": ("code", "repro"),
"backlog": ("backlog", "backlog"),
"policy": ("backlog", "policy"),
"project": ("project", "project"),
"plan": ("project", "plan"),
"import": ("project", "import"),
"sync": ("project", "sync"),
"migrate": ("project", "migrate"),
"contract": ("spec", "contract"),
"spec": ("spec", "api"),
"sdd": ("spec", "sdd"),
"generate": ("spec", "generate"),
"enforce": ("govern", "enforce"),
"patch": ("govern", "patch"),
}


def _make_shim_loader(
flat_name: str,
group_name: str,
sub_name: str,
help_str: str,
) -> Any:
"""Return a loader that returns the real module Typer so flat invocations like
'specfact sync bridge' work (subcommands come from the real module).
"""

def loader() -> Any:
return CommandRegistry.get_module_typer(flat_name)

return loader


@beartype
def get_installed_bundles(
packages: list[tuple[Path, ModulePackageMetadata]],
Expand Down Expand Up @@ -932,13 +894,12 @@ def _mount_installed_category_groups(
packages: list[tuple[Path, ModulePackageMetadata]],
enabled_map: dict[str, bool],
) -> None:
"""Register category groups and compat shims only for installed bundles."""
"""Register category groups only for installed bundles."""
installed = get_installed_bundles(packages, enabled_map)
bundle_to_group = _build_bundle_to_group()
module_entries_by_name = {
entry.get("name"): entry for entry in getattr(CommandRegistry, "_module_entries", []) if entry.get("name")
}
module_meta_by_name = {name: entry.get("metadata") for name, entry in module_entries_by_name.items()}
seen_groups: set[str] = set()
for bundle in installed:
group_info = bundle_to_group.get(bundle)
Expand Down Expand Up @@ -972,24 +933,6 @@ def _group_loader(_fn: Any = fn) -> Any:
)
CommandRegistry.register(group_name, loader, cmd_meta)

for flat_name, (group_name, sub_name) in FLAT_TO_GROUP.items():
if group_name not in {bundle_to_group[b][0] for b in installed if b in bundle_to_group}:
continue
if flat_name == group_name:
continue
meta = module_meta_by_name.get(flat_name)
if meta is None:
continue
help_str = meta.help
shim_loader = _make_shim_loader(flat_name, group_name, sub_name, help_str)
cmd_meta = CommandMetadata(
name=flat_name,
help=help_str + " (deprecated; use specfact " + group_name + " " + sub_name + ")",
tier=meta.tier,
addon_id=meta.addon_id,
)
CommandRegistry.register(flat_name, shim_loader, cmd_meta)


def register_module_package_commands(
enable_ids: list[str] | None = None,
Expand All @@ -1002,7 +945,7 @@ def register_module_package_commands(

Call after register_builtin_commands(). enable_ids/disable_ids from CLI (--enable-module/--disable-module).
allow_unsigned: If True, allow modules without integrity metadata. Default from SPECFACT_ALLOW_UNSIGNED env.
category_grouping_enabled: If True, register category groups (code, backlog, project, spec, govern) and compat shims.
category_grouping_enabled: If True, register category groups (code, backlog, project, spec, govern).
"""
enable_ids = enable_ids or []
disable_ids = disable_ids or []
Expand Down
13 changes: 6 additions & 7 deletions tests/integration/test_category_group_routing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Integration tests for category group routing (code, backlog, validate shim)."""
"""Integration tests for category group routing when grouping is enabled."""

from __future__ import annotations

Expand Down Expand Up @@ -49,10 +49,9 @@ def test_backlog_help_lists_subcommands() -> None:
assert "policy" in out or "ceremony" in out


def test_validate_shim_help_exits_zero() -> None:
"""Deprecated flat command specfact validate --help still returns help without error."""
def test_validate_flat_command_is_not_available() -> None:
"""Flat command `specfact validate --help` is unavailable after shim removal."""
result = runner.invoke(app, ["validate", "--help"])
assert result.exit_code == 0, (
f"Expected exit 0, got {result.exit_code}\nstdout: {result.stdout}\nstderr: {result.stderr}"
)
assert "validate" in (result.stdout or "").lower() or "usage" in (result.stdout or "").lower()
assert result.exit_code != 0
output = ((result.stdout or "") + (result.output or "")).lower()
assert "not installed" in output or "no such command" in output
Loading
Loading