diff --git a/.cursor/rules/clean-code-principles.mdc b/.cursor/rules/clean-code-principles.mdc index ea98c82c..6a5e780f 100644 --- a/.cursor/rules/clean-code-principles.mdc +++ b/.cursor/rules/clean-code-principles.mdc @@ -1,148 +1,109 @@ --- -description: Enforce clean-code principles and enforcements across the repository. +description: Enforce the 7-principle clean-code charter across the repository. globs: alwaysApply: true --- -# Rule: Clean Code Principles and Enforcements - -Description: -This rule enforces concrete clean-code practices across the repository. It complements existing high-level rules (TDD, linting, formatting, coverage) by adding behavioral and API-consistency checks, with deterministic commands and examples. - -Why: - -- Reduce cognitive load and debugging time by standardizing error handling, return types, logging, and small, single-purpose functions. -- Make automated checks (pre-commit/CI) able to catch non-style problems (bare-excepts, prints, mixed return semantics, state-machine/config mismatches). - -Scope: -Applies to all python files under src/ and tests/ and any state-machine generation artifacts. - -Enforcement checklist (machine-checkable where possible): - -1. No bare except or broad except Exception without re-raising or logging full context - - Rationale: Broad excepts hide real failures. - - Check: run pylint with a custom plugin or flake8 rule to detect "except:" or "except Exception:" that either: - - swallow the exception without re-raising, or - - do not call logger.exception(...) (stack trace). - - Command (CI): flake8 --select E722,B001 (or custom check) - - Example (bad): - try: - ... - except Exception: - pass - - Example (good): - except SpecificError as e: - logger.error("...: %s", e) - raise - -2. No direct print() calls in library modules - - Rationale: Use LoggerSetup for structured logs and session IDs. - - Check: grep for "print(" in src/ and tests/ (fail in src/) - - Command (CI): git grep -n -- '^\s*print\(' -- src/ && fail - - Fix: Replace print with LoggerSetup.get_logger(...) or setup_logger(...) - -3. Enforce consistent return types (avoid implicit None/False mixing) - - Rationale: Functions exposed as API should have typed, predictable returns. - - Rules: - - Publishing/IO methods must return bool (True/False), not None. If underlying library returned int, normalize. - - connect() should return bool only. - - Check: static analysis rule or unit test that asserts function annotations and runtime contract on a selection of public functions (RedisClient.publish/connect, MessagingStandardization.*) - - Example (good): - def publish(...) -> bool: ... - return True/False - -4. Prefer specific exceptions & re-raise after logging - - Rationale: Keep failure semantics explicit for callers and tests. - - Rule: Do not swallow exceptions. If a function handles and cannot continue, raise a domain-specific exception or re-raise. - -5. Limit line / function complexity - - Rationale: Keep functions short and single-responsibility. - - Rules: - - Maximum function length: 120 lines (configurable) - - Maximum cyclomatic complexity: 12 - - Check: use radon cc --total-average and flake8-ext or pylint thresholds - - Command (CI): radon cc -nc -s src/ && fail if any > 12 - -6. Use centralized logging, never ad-hoc formatting - - Rationale: Keep consistent format and redaction. - - Rules: - - Use LoggerSetup.get_logger or setup_logger to obtain logger. - - No module-level printing of messages; message flow logs should go via agent_flow logger. - - Check: grep for 'logging.getLogger(' and verify consistent usage, grep for 'print(' (see rule 2). - -7. Avoid filesystem operations with overly permissive modes - - Rationale: 0o777 should not be used by default. - - Rule: mkdir/os.makedirs must not be invoked with mode=0o777. Use 0o755 or use environment-controlled mode. - - Check: grep for '0o777' and require explicit justification in code comments; CI should flag occurrences. - -8. State machine / YAML consistency check - - Rationale: Generated state machines must match canonical YAML config. - - Rule: Add a unit/regression test that: - - reloads src/common/*.py generated state machine enums and compares state names and transitions to autodev_config.yaml and productplan_config.yaml - - fails the build if mismatch or case differences are present - - Check: add tests/unit/test_state_machine_config_sync.py - - Command (CI): hatch test tests/unit/test_state_machine_config_sync.py - -9. No side-effects at import time - - Rationale: Module imports should be safe and predictable for tests and tools. - - Rule: Avoid heavy I/O or network calls on import. get_runtime_logs_dir may create directories — allowed only if idempotent and documented; avoid network calls or spawn threads. - - Check: grep for network or redis connection calls at module-level (e.g., RedisSDK(...)). - -10. Async / signal safety - - Rationale: Signal handlers must not call non-signal-safe operations. - - Rule: Signal handlers may only set a flag or call a thread-safe routine; do not call asyncio.create_task directly from a POSIX signal handler. - - Check: grep for "signal.signal(" and assert handler functions only set flags or use loop.call_soon_threadsafe. - - Suggested fix: replace create_task calls in handler with loop.call_soon_threadsafe(lambda: asyncio.create_task(...)) - -11. Secure secret redaction guaranteed - - Rationale: Sensitive keys must be masked in logs. - - Rule: LoggerSetup.redact_secrets must be covered by unit tests for nested dicts and strings. - - Check: add tests/unit/test_logger_redaction.py - -12. Messaging coercion & strict validation - - Rationale: Legacy message coercion is useful but must be exercised and tested. - - Rule: Any place that uses coercion (MessagingStandardization.process_standardized_message) must have tests that validate coercion success/failure and metrics increments. - - Check: add unit tests that assert contextschema_coercion_success/failed metrics behave as expected. - -13. Enforce pre-commit & CI gating - - Add pre-commit config that runs: black, isort, mypy, flake8 (with B/C plugins), radon, tests for state machine sync. - - CI job must fail on: - - lint failures - - radon/cyclomatic complexity - - state-machine config mismatch test - - flake8 error codes for prints and bare excepts - -14. Documentation/commit habits - - Rule: Any code change that modifies public API, state machine YAMLs, or generated state machine outputs MUST: - - include/modify a unit test covering new behavior - - update docs/ and CHANGELOG.md - - include 'BREAKING' section in PR if public API changed - - Check: CI script that rejects PRs lacking test files changed when src/ is modified (heuristic). - -Implementation guidance (how to add tests / checks quickly) - -- Add a lightweight pytest module for state-machine YAML<->enum sync (example included in docs). -- Add flake8 plugin rules or configure pylint to error on 'print' and bare excepts; enable in CI config. -- Add radon complexity check step to pipeline; fail if thresholds exceeded. -- Add a small assertion test for RedisClient.publish return normalization. - -Mapping to existing rules (what to augment) - -- docs/rules/python-github-rules.md: Add explicit "no-bare-except" and "no-print" items and function complexity thresholds. -- docs/rules/spec-fact-cli-rules.md: Add "state-machine YAML / generated code sync check" and "normalize public API return types". -- docs/rules/testing-and-build-guide.md: Add specific test files to run (state-machine sync test, logger redaction tests) and mention radon/complexity checks. -- .cursor/rules/testing-and-build-guide.mdc and .cursor/rules/python-github-rules.mdc: include exact CLI commands and required test files (see enforcement checklist). - -Developer notes (priorities) - -1. Add the state-machine sync unit test (high value, low effort). -2. Add flake8/pylint rule to detect print/bare-except (medium effort). -3. Normalize RedisClient.publish/connect return values & add tests (low-to-medium effort). -4. Add radon step to CI and fix top offenders iteratively (ongoing). -5. Replace print() in AutoDevStateMachine.log with LoggerSetup.get_logger (small change, run tests). +# Rule: Clean-Code Principles and Review Gate ---- +## Charter source of truth + +The canonical 7-principle clean-code charter lives in: +- **Policy-pack**: `specfact/clean-code-principles` (shipped by `specfact-cli-modules`) +- **Skill file**: `skills/specfact-code-review/SKILL.md` in `nold-ai/specfact-cli-modules` + +This file is an **alias surface** for the specfact-cli repository. It maps each +principle to its review category and records the Phase A gate thresholds so that +contributors, reviewers, and AI coding agents operate from the same reference +without duplicating the full charter text. + +## The 7 clean-code principles + +| # | Principle | Review category | +|---|-----------|-----------------| +| 1 | **Meaningful Naming** — identifiers reveal intent; avoid abbreviations | `naming` | +| 2 | **KISS** — keep functions and modules small and single-purpose | `kiss` | +| 3 | **YAGNI** — do not add functionality until it is needed | `yagni` | +| 4 | **DRY** — single source of truth; eliminate copy-paste duplication | `dry` | +| 5 | **SOLID** — single responsibility, open/closed, Liskov, interface segregation, dependency inversion | `solid` | +| 6 | **Small, focused functions** — each function does exactly one thing (subsumed by KISS metrics) | `kiss` | +| 7 | **Self-documenting code** — prefer expressive code over inline comments; comments explain *why* not *what* | `naming` | + +## Active gate: Phase A KISS metrics + +The following thresholds are **enforced** through the `specfact code review run` +gate and the pre-commit hook (`scripts/pre_commit_code_review.py`): + +| Metric | Warning | Error | +|--------|---------|-------| +| Lines of code per function (LOC) | > 80 (warning) | > 120 (error) | +| Nesting depth | configurable (phase A) | configurable (phase A) | +| Parameter count | configurable (phase A) | configurable (phase A) | + +Nesting-depth and parameter-count checks are **active** in Phase A. + +### Phase B (deferred) + +Phase B thresholds (`> 40` warning / `> 80` error for LOC) are planned for a +future cleanup change once the initial Phase A remediation is complete. +**Phase B is not yet a hard gate.** Do not silently promote Phase B thresholds +when configuring or extending the review gate. + +## Review categories consumed by this repo + +When `specfact code review run` executes against this codebase the following +clean-code categories from the expanded review module are included: + +- `naming` — semgrep naming and exception-pattern rules +- `kiss` — radon LOC/nesting/parameter findings under Phase A thresholds +- `yagni` — AST-based unused-abstraction detection +- `dry` — AST clone-detection and duplication findings +- `solid` — AST dependency-role and single-responsibility checks + +Zero regressions in these categories are required before a PR is merge-ready. + +## Per-principle enforcement notes + +### Naming (`naming`) + +- Identifiers in `src/` must use `snake_case` (modules/functions), `PascalCase` (classes), `UPPER_SNAKE_CASE` (constants). +- Avoid single-letter names outside short loop variables. +- Exception patterns must not use broad `except Exception` without re-raising or logging. + +### KISS (`kiss`) + +- Maximum function length: **120 lines** (Phase A error threshold). +- Maximum cyclomatic complexity: **12** (radon `cc` threshold; `>= 16` is error band). +- Phase A LOC warning at > 80, error at > 120. + +### YAGNI (`yagni`) + +- Do not add configuration options, flags, or extension points until a concrete use-case drives them. +- Remove dead code paths rather than commenting them out. + +### DRY (`dry`) + +- Do not copy-paste logic; extract to a shared function or module. +- Single source of truth for schemas (Pydantic `BaseModel`), constants (`UPPER_SNAKE_CASE`), and templates (`resources/templates/`). + +### SOLID (`solid`) + +- Each module/class has a single clearly named responsibility. +- Extend via dependency injection (adapters pattern) rather than modifying existing classes. +- Use `@icontract` (`@require`/`@ensure`) and `@beartype` on all public APIs. + +## Additional behavioral rules (complement SOLID/KISS) + +- No `print()` calls in `src/` — use `from specfact_cli.common import get_bridge_logger`. + *(Note: T20 is currently ignored in pyproject.toml and not enforced by the review gate; this is aspirational.)* +- No broad `except Exception` without re-raising or logging. + *(Note: W0718 is disabled in .pylintrc; broad-exception checks are aspirational.)* +- No side-effects at import time (no network calls, no file I/O on module load). +- Signal handlers must only set a flag or use `loop.call_soon_threadsafe`. +- Filesystem modes must not use `0o777`; use `0o755` or environment-controlled mode. +- Secret redaction via `LoggerSetup.redact_secrets` must be covered by unit tests. -Changelog / Expected follow-up +## Pre-commit and CI enforcement -- After adding these rules, update CI to run the new checks and add sample unit tests that fail on violations. -- Run hatch test and the new linters locally; fix top offenders in an iterative PR with small, focused commits. +The `specfact-code-review-gate` pre-commit hook and the `hatch run specfact code +review run --json --out .specfact/code-review.json` command both enforce these +principles. Run the review before submitting a PR and resolve every finding. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..ad6cb5f3 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,23 @@ +# GitHub Copilot Instructions — specfact-cli + +## Clean-Code Charter + +This repository enforces the **7-principle clean-code charter** defined in: +- `skills/specfact-code-review/SKILL.md` (`nold-ai/specfact-cli-modules`) +- Policy-pack: `specfact/clean-code-principles` + +Review categories checked on every PR: **naming · kiss · yagni · dry · solid** + +Phase A KISS thresholds: LOC > 80 warning / > 120 error per function. +Nesting-depth and parameter-count checks are active. Phase B (>40/80) is deferred. + +Run `hatch run specfact code review run --json --out .specfact/code-review.json` before submitting. + +## Key conventions + +- Python 3.11+, Typer CLI, Pydantic models, `@icontract` + `@beartype` on all public APIs +- No `print()` in `src/` — use `get_bridge_logger()` +- Branch protection: work on `feature/*`, `bugfix/*`, `hotfix/*` branches; PRs to `dev` +- Pre-commit checklist: `hatch run format` → `type-check` → `lint` → `yaml-lint` → `contract-test` → `smart-test` + +See `AGENTS.md` and `.cursor/rules/` for the full contributor guide. diff --git a/AGENTS.md b/AGENTS.md index 988e01cf..24c320fb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -205,6 +205,26 @@ hatch run specfact code review run --json --out .specfact/code-review.json - OpenSpec change **`tasks.md`** should include explicit tasks for generating/updating this file and clearing findings (see `openspec/config.yaml` → `rules.tasks` → “SpecFact code review JSON”). Agent runs should treat those tasks and this section as the same bar. +### Clean-Code Review Gate + +specfact-cli enforces the 7-principle clean-code charter through the `specfact code review run` gate. The canonical charter lives in `skills/specfact-code-review/SKILL.md` (in `nold-ai/specfact-cli-modules`). This repo consumes the expanded clean-code categories from that review module: + +| Category | Principle covered | +|----------|-------------------| +| `naming` | Meaningful naming, exception-pattern rules | +| `kiss` | Keep It Simple: LOC, nesting-depth, parameter-count (Phase A: >80 warning / >120 error) | +| `yagni` | You Aren't Gonna Need It: unused-abstraction detection | +| `dry` | Don't Repeat Yourself: clone-detection and duplication checks | +| `solid` | SOLID principles: dependency-role and single-responsibility checks | + +Zero regressions in any of these categories are required before merge. Run the review gate with: + +```bash +hatch run specfact code review run --json --out .specfact/code-review.json +``` + +**Phase A thresholds are active.** Phase B thresholds (>40 / >80 LOC) are deferred to a later cleanup change and are not yet enforced. + ### Module Signature Gate (Required for Change Finalization) Before PR creation, every change MUST pass bundled module signature verification: diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d282fa..244f307d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,25 @@ All notable changes to this project will be documented in this file. --- +## [0.44.0] - 2026-03-31 + +### Added + +- **Clean-code principle gates** (`clean-code-01-principle-gates`): + - `.cursor/rules/clean-code-principles.mdc` restructured as a canonical alias for the + 7-principle clean-code charter (`naming`, `kiss`, `yagni`, `dry`, `solid`) defined in + `nold-ai/specfact-cli-modules` (`skills/specfact-code-review/SKILL.md`). + - Phase A KISS metric thresholds documented: LOC > 80 warning / > 120 error per function; + nesting-depth and parameter-count checks active. Phase B (> 40 / > 80) explicitly deferred. + - `AGENTS.md` and `CLAUDE.md` extended with a **Clean-Code Review Gate** section listing + the 5 expanded review categories and the Phase A thresholds that gate every PR. + - `.github/copilot-instructions.md` created as a lightweight alias surface that references + the canonical charter without duplicating it inline. + - Unit tests: `tests/unit/specfact_cli/test_clean_code_principle_gates.py` covering all + three spec scenarios (charter references, compliance gate, LOC/nesting check). + +--- + ## [0.43.3] - 2026-03-30 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 4f155662..971f9f99 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -157,6 +157,26 @@ Run all steps in order before committing. Every step must pass with no errors. 5. `hatch run contract-test` # contract-first validation 6. `hatch run smart-test` # targeted test run (use `smart-test-full` for larger modifications) +### Clean-Code Review Gate + +specfact-cli enforces the 7-principle clean-code charter through the `specfact code review run` gate. The canonical charter lives in `skills/specfact-code-review/SKILL.md` (in `nold-ai/specfact-cli-modules`). This repo consumes the expanded clean-code categories from that review module: + +| Category | Principle covered | +|----------|-------------------| +| `naming` | Meaningful naming, exception-pattern rules | +| `kiss` | Keep It Simple: LOC, nesting-depth, parameter-count (Phase A: >80 warning / >120 error) | +| `yagni` | You Aren't Gonna Need It: unused-abstraction detection | +| `dry` | Don't Repeat Yourself: clone-detection and duplication checks | +| `solid` | SOLID principles: dependency-role and single-responsibility checks | + +Zero regressions in any of these categories are required before merge. Run the review gate with: + +```bash +hatch run specfact code review run --json --out .specfact/code-review.json +``` + +**Phase A thresholds are active.** Phase B thresholds (>40 / >80 LOC) are deferred to a later cleanup change and are not yet enforced. + ### OpenSpec Workflow Before modifying application code, **always** verify that an active OpenSpec change in `openspec/changes/` **explicitly covers the requested modification**. This is the spec-driven workflow defined in `openspec/config.yaml`. Skip only when the user explicitly says `"skip openspec"` or `"implement without openspec change"`. diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index 2d4d4b80..97f38955 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -67,6 +67,12 @@ Only changes that are **archived**, shown as **✓ Complete** by `openspec list` Entries in the tables below are pending unless explicitly marked as implemented (archived). +## Clean-code enforcement + +| Module | Order | Change folder | GitHub # | Blocked by | +|--------|-------|---------------|----------|------------| +| clean-code | 01 | clean-code-01-principle-gates | [#434](https://github.com/nold-ai/specfact-cli/issues/434) | code-review-zero-findings ✅; clean-code-02-expanded-review-module (modules repo) ✅ | + ## Dogfooding | Module | Order | Change folder | GitHub # | Blocked by | diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/TDD_EVIDENCE.md diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/design.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/design.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/design.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/design.md diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/proposal.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/proposal.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/proposal.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/proposal.md diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/specs/backlog-adapter/spec.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/specs/backlog-adapter/spec.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/specs/backlog-adapter/spec.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/specs/backlog-adapter/spec.md diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/specs/devops-sync/spec.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/specs/devops-sync/spec.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/specs/devops-sync/spec.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/specs/devops-sync/spec.md diff --git a/openspec/changes/bugfix-02-ado-import-payload-slugging/tasks.md b/openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/tasks.md similarity index 100% rename from openspec/changes/bugfix-02-ado-import-payload-slugging/tasks.md rename to openspec/changes/archive/2026-03-31-bugfix-02-ado-import-payload-slugging/tasks.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/CHANGE_VALIDATION.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/TDD_EVIDENCE.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/design.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/design.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/design.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/design.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/proposal.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/proposal.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/proposal.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/proposal.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/specs/trustworthy-green-checks/spec.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/specs/trustworthy-green-checks/spec.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/specs/trustworthy-green-checks/spec.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/specs/trustworthy-green-checks/spec.md diff --git a/openspec/changes/ci-02-trustworthy-green-checks/tasks.md b/openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/tasks.md similarity index 100% rename from openspec/changes/ci-02-trustworthy-green-checks/tasks.md rename to openspec/changes/archive/2026-03-31-ci-02-trustworthy-green-checks/tasks.md diff --git a/openspec/changes/clean-code-01-principle-gates/.openspec.yaml b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/.openspec.yaml similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/.openspec.yaml rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/.openspec.yaml diff --git a/openspec/changes/clean-code-01-principle-gates/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/CHANGE_VALIDATION.md diff --git a/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/TDD_EVIDENCE.md new file mode 100644 index 00000000..144f5f54 --- /dev/null +++ b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/TDD_EVIDENCE.md @@ -0,0 +1,86 @@ +# TDD Evidence: clean-code-01-principle-gates + +## Red phase — failing tests before implementation + +**Timestamp**: 2026-03-31T~10:30 UTC (worktree session) + +**Command**: +```bash +hatch test -- tests/unit/specfact_cli/test_clean_code_principle_gates.py -v +``` + +**Result**: 10 failed, 1 skipped + +``` +FAILED test_agents_md_references_clean_code_categories +FAILED test_claude_md_references_clean_code_categories +FAILED test_clean_code_mdc_references_seven_principles +FAILED test_clean_code_mdc_references_canonical_skill +FAILED test_copilot_instructions_exists_and_references_charter +FAILED test_agents_md_documents_clean_code_compliance_gate +FAILED test_claude_md_documents_clean_code_compliance_gate +FAILED test_clean_code_mdc_documents_phase_a_loc_thresholds +FAILED test_clean_code_mdc_mentions_nesting_and_parameter_checks +FAILED test_clean_code_mdc_documents_phase_b_as_deferred +SKIPPED test_copilot_instructions_does_not_duplicate_full_charter +``` + +**Failure summary**: AGENTS.md and CLAUDE.md had no references to `naming`, `kiss`, +`yagni`, `dry`, or `solid`. `.cursor/rules/clean-code-principles.mdc` did not mention +the 7-principle charter categories, Phase A thresholds, or Phase B deferral. +`.github/copilot-instructions.md` did not exist. + +## Implementation + +```text +Files changed: + +1. `.cursor/rules/clean-code-principles.mdc` — rewrote as an alias surface referencing + the canonical charter in `nold-ai/specfact-cli-modules`; added principle-to-category + table, Phase A LOC thresholds (>80 / >120), nesting-depth and parameter-count notes, + explicit Phase B deferral. + +2. `AGENTS.md` — added **Clean-Code Review Gate** section listing all 5 review categories + (`naming`, `kiss`, `yagni`, `dry`, `solid`) with Phase A threshold table. + +3. `CLAUDE.md` — added identical **Clean-Code Review Gate** section. + +4. `.github/copilot-instructions.md` — created as a lightweight alias (≤ 30 lines) + referencing the canonical charter without duplicating it. +``` + +## Green phase — passing tests after implementation + +**Timestamp**: 2026-03-31T~10:35 UTC + +**Command**: +```bash +hatch test -- tests/unit/specfact_cli/test_clean_code_principle_gates.py -v +``` + +**Result**: 11 passed in 0.23s + +``` +PASSED test_agents_md_references_clean_code_categories +PASSED test_claude_md_references_clean_code_categories +PASSED test_clean_code_mdc_references_seven_principles +PASSED test_clean_code_mdc_references_canonical_skill +PASSED test_copilot_instructions_exists_and_references_charter +PASSED test_copilot_instructions_does_not_duplicate_full_charter +PASSED test_agents_md_documents_clean_code_compliance_gate +PASSED test_claude_md_documents_clean_code_compliance_gate +PASSED test_clean_code_mdc_documents_phase_a_loc_thresholds +PASSED test_clean_code_mdc_mentions_nesting_and_parameter_checks +PASSED test_clean_code_mdc_documents_phase_b_as_deferred +``` + +## Quality gates + +All pre-commit gates passed: + +- `hatch run format` — 1 file reformatted (test file), 0 remaining errors +- `hatch run type-check` — 0 errors, warnings only (pre-existing) +- `hatch run lint` — 10.00/10 pylint, all checks passed +- `hatch run yaml-lint` — clean +- `hatch run contract-test` — no modified files (no production code changes; cached pass) +- `hatch run smart-test` — no changed files mapped to tests (instruction-surface-only change) diff --git a/openspec/changes/clean-code-01-principle-gates/design.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/design.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/design.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/design.md diff --git a/openspec/changes/clean-code-01-principle-gates/proposal.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/proposal.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/proposal.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/proposal.md diff --git a/openspec/changes/clean-code-01-principle-gates/specs/agent-instruction-clean-code-charter/spec.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/agent-instruction-clean-code-charter/spec.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/specs/agent-instruction-clean-code-charter/spec.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/agent-instruction-clean-code-charter/spec.md diff --git a/openspec/changes/clean-code-01-principle-gates/specs/clean-code-compliance-gate/spec.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/clean-code-compliance-gate/spec.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/specs/clean-code-compliance-gate/spec.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/clean-code-compliance-gate/spec.md diff --git a/openspec/changes/clean-code-01-principle-gates/specs/clean-code-loc-nesting-check/spec.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/clean-code-loc-nesting-check/spec.md similarity index 100% rename from openspec/changes/clean-code-01-principle-gates/specs/clean-code-loc-nesting-check/spec.md rename to openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/specs/clean-code-loc-nesting-check/spec.md diff --git a/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/tasks.md b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/tasks.md new file mode 100644 index 00000000..0e596d48 --- /dev/null +++ b/openspec/changes/archive/2026-03-31-clean-code-01-principle-gates/tasks.md @@ -0,0 +1,40 @@ +# Tasks: clean-code-01-principle-gates + +## 1. Branch and dependency guardrails + +- [x] 1.1 Create dedicated worktree branch `feature/clean-code-01-principle-gates` from `dev` before implementation work: `scripts/worktree.sh create feature/clean-code-01-principle-gates`. +- [x] 1.2 Confirm `code-review-zero-findings` has recorded a zero-finding self-review baseline and that the modules repo change `clean-code-02-expanded-review-module` is available for consumption. +- [x] 1.3 Reconfirm scope against the 2026-03-22 clean-code implementation plan and the updated `openspec/CHANGE_ORDER.md`. + +## 2. Spec-first and test-first preparation + +- [x] 2.1 Finalize `specs/` deltas for clean-code charter references, clean-code compliance gating, and staged LOC/nesting checks. +- [x] 2.2 Add or update tests derived from the new scenarios before touching production code. +- [x] 2.3 Run targeted tests and capture failing-first evidence in `TDD_EVIDENCE.md`. + +## 3. Implementation + +- [x] 3.1 Update instruction surfaces (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/clean-code-principles.mdc`, `.github/copilot-instructions.md`, relevant `.codex` skill entry points) to reference the canonical clean-code charter. +- [x] 3.2 Wire specfact-cli review and CI flows to consume the expanded clean-code categories from the modules repo without introducing a second clean-code configuration model. +- [x] 3.3 Adopt Phase A LOC, nesting-depth, and parameter-count checks through the review integration path and preserve the Phase B thresholds as a later change. + +## 4. Validation and documentation + +- [x] 4.1 Re-run targeted tests, review flows, and quality gates until all changed scenarios pass. +- [x] 4.2 Update contributor-facing docs that explain AI instruction files, repo review rules, and clean-code governance. +- [x] 4.3 Run `openspec validate clean-code-01-principle-gates --strict` and resolve all issues. + +## 5. Delivery + +- [x] 5.1 Update `openspec/CHANGE_ORDER.md` dependency notes if implementation sequencing changes again. +- [x] 5.2 Open a PR from `feature/clean-code-01-principle-gates` to `dev` with spec/test/code/docs evidence. + +## 6. Review findings remediation (post-implementation) + +- [x] 6.1 Fix coderabbitai review findings: + - Updated `.cursor/rules/clean-code-principles.mdc` to clarify T20 and W0718 are aspirational + - Added language specifier to fenced code block in `TDD_EVIDENCE.md` + - Updated test to check for all 7 canonical principles + - Made LOC threshold assertion more specific with exact strings +- [x] 6.2 Verify CHANGELOG.md is updated with version 0.44.0 and all change details +- [x] 6.3 Update task status to reflect completion diff --git a/openspec/changes/code-review-zero-findings/.openspec.yaml b/openspec/changes/archive/2026-03-31-code-review-zero-findings/.openspec.yaml similarity index 100% rename from openspec/changes/code-review-zero-findings/.openspec.yaml rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/.openspec.yaml diff --git a/openspec/changes/code-review-zero-findings/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/code-review-zero-findings/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/CHANGE_VALIDATION.md diff --git a/openspec/changes/code-review-zero-findings/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/code-review-zero-findings/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/TDD_EVIDENCE.md diff --git a/openspec/changes/code-review-zero-findings/design.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/design.md similarity index 100% rename from openspec/changes/code-review-zero-findings/design.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/design.md diff --git a/openspec/changes/code-review-zero-findings/proposal.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/proposal.md similarity index 100% rename from openspec/changes/code-review-zero-findings/proposal.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/proposal.md diff --git a/openspec/changes/code-review-zero-findings/specs/code-review-module/spec.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/code-review-module/spec.md similarity index 100% rename from openspec/changes/code-review-zero-findings/specs/code-review-module/spec.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/code-review-module/spec.md diff --git a/openspec/changes/code-review-zero-findings/specs/debug-logging/spec.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/debug-logging/spec.md similarity index 100% rename from openspec/changes/code-review-zero-findings/specs/debug-logging/spec.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/debug-logging/spec.md diff --git a/openspec/changes/code-review-zero-findings/specs/dogfood-self-review/spec.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/dogfood-self-review/spec.md similarity index 100% rename from openspec/changes/code-review-zero-findings/specs/dogfood-self-review/spec.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/dogfood-self-review/spec.md diff --git a/openspec/changes/code-review-zero-findings/specs/review-cli-contracts/spec.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/review-cli-contracts/spec.md similarity index 100% rename from openspec/changes/code-review-zero-findings/specs/review-cli-contracts/spec.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/review-cli-contracts/spec.md diff --git a/openspec/changes/code-review-zero-findings/specs/review-run-command/spec.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/review-run-command/spec.md similarity index 100% rename from openspec/changes/code-review-zero-findings/specs/review-run-command/spec.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/specs/review-run-command/spec.md diff --git a/openspec/changes/code-review-zero-findings/tasks.md b/openspec/changes/archive/2026-03-31-code-review-zero-findings/tasks.md similarity index 100% rename from openspec/changes/code-review-zero-findings/tasks.md rename to openspec/changes/archive/2026-03-31-code-review-zero-findings/tasks.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/.openspec.yaml b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/.openspec.yaml similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/.openspec.yaml rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/.openspec.yaml diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/CHANGE_VALIDATION.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/TDD_EVIDENCE.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/design.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/design.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/design.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/design.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/proposal.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/proposal.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/proposal.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/proposal.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/specs/docs-review-gate/spec.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/specs/docs-review-gate/spec.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/specs/docs-review-gate/spec.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/specs/docs-review-gate/spec.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/specs/documentation-alignment/spec.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/specs/documentation-alignment/spec.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/specs/documentation-alignment/spec.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/specs/documentation-alignment/spec.md diff --git a/openspec/changes/docs-04-docs-review-gate-and-link-integrity/tasks.md b/openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/tasks.md similarity index 100% rename from openspec/changes/docs-04-docs-review-gate-and-link-integrity/tasks.md rename to openspec/changes/archive/2026-03-31-docs-04-docs-review-gate-and-link-integrity/tasks.md diff --git a/openspec/changes/docs-05-core-site-ia-restructure/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/docs-05-core-site-ia-restructure/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/CHANGE_VALIDATION.md diff --git a/openspec/changes/docs-05-core-site-ia-restructure/proposal.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/proposal.md similarity index 100% rename from openspec/changes/docs-05-core-site-ia-restructure/proposal.md rename to openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/proposal.md diff --git a/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md new file mode 100644 index 00000000..d499182c --- /dev/null +++ b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md @@ -0,0 +1,29 @@ +# Capability: core-cli-reference + +Dedicated reference pages for each core CLI command (init, module, upgrade). + +## ADDED Requirements + +### Requirement: Core CLI reference pages exist + +The system SHALL provide dedicated reference pages for core CLI commands. + +#### Scenario: Init reference page documents all subcommands and options + +- **GIVEN** the docs/core-cli/init.md page exists +- **WHEN** a user reads the page +- **THEN** it documents: specfact init, init --profile, init --install, init ide, init --install-deps +- **AND** all documented commands match the actual --help output + +#### Scenario: Module reference page documents all subcommands + +- **GIVEN** the docs/core-cli/module.md page exists +- **WHEN** a user reads the page +- **THEN** it documents: module install, module uninstall, module list, module show, module search, module upgrade, module alias, module add-registry, module list-registries, module remove-registry, module enable, module disable +- **AND** all documented commands match the actual --help output + +#### Scenario: Upgrade reference page documents the command + +- **GIVEN** the docs/core-cli/upgrade.md page exists +- **WHEN** a user reads the page +- **THEN** it documents the specfact upgrade command and its options diff --git a/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md new file mode 100644 index 00000000..971ea9c9 --- /dev/null +++ b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md @@ -0,0 +1,34 @@ +# Capability: core-docs-progressive-nav + +Core docs site sidebar provides a 6-section progressive navigation structure. + +## ADDED Requirements + +### Requirement: Sidebar provides 6-section progressive navigation + +The system SHALL provide a sidebar with 6 sections in a specific order. + +#### Scenario: Sidebar renders 6 sections in correct order + +- **GIVEN** the core docs site is built with Jekyll +- **WHEN** a user visits any page on docs.specfact.io +- **THEN** the sidebar displays sections in order: Getting Started, Core CLI, Module System, Architecture, Reference, Migration + +#### Scenario: Getting Started section contains beginner-friendly entries + +- **GIVEN** the sidebar Getting Started section +- **WHEN** a user expands the section +- **THEN** it contains: Installation, 5-Minute Quickstart, Profiles & IDE Setup +- **AND** does NOT contain module-specific tutorials (backlog quickstart, standup, refine) + +#### Scenario: Core CLI section links to command reference pages + +- **GIVEN** the sidebar Core CLI section +- **WHEN** a user expands the section +- **THEN** it contains links to: specfact init, specfact module, specfact upgrade, Operational Modes, Debug Logging + +#### Scenario: Moved files redirect to new locations + +- **GIVEN** a file was moved from its old location +- **WHEN** a user visits the old URL +- **THEN** they are redirected to the new URL via jekyll-redirect-from diff --git a/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md new file mode 100644 index 00000000..c2847364 --- /dev/null +++ b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md @@ -0,0 +1,31 @@ +# Delta: documentation-alignment + +Extends the existing `documentation-alignment` capability with core-only site focus and module redirect policy. + +## MODIFIED Requirements + +### Requirement: Live docs reflect lean-core and grouped bundle command topology + +The live authored documentation set SHALL use command examples and migration guidance that match the currently shipped core and bundle command groups, and SHALL NOT present removed or transitional command families as current syntax. The core docs site SHALL focus exclusively on core platform concerns and SHALL redirect module-specific workflow content to modules.specfact.io. + +#### Scenario: Core docs site excludes module-specific workflow content + +- **GIVEN** the docs.specfact.io landing page (index.md) +- **WHEN** a reader arrives at the docs home +- **THEN** the page clearly separates core platform concerns from module-specific workflows +- **AND** provides direct links to modules.specfact.io for bundle-specific guidance + +#### Scenario: Core site landing page delineates core vs modules + +- **GIVEN** the docs.specfact.io landing page (index.md) +- **WHEN** a reader arrives at the docs home +- **THEN** the page clearly separates core platform concerns from module-specific workflows +- **AND** provides direct links to modules.specfact.io for bundle-specific guidance + +#### Scenario: Getting Started section focuses on platform bootstrap + +- **GIVEN** the Getting Started section of the core docs +- **WHEN** a new user follows the getting started path +- **THEN** it covers installation, quickstart, and profiles/IDE setup +- **AND** does NOT include module-specific tutorials as inline content +- **AND** links to modules.specfact.io for module workflow tutorials diff --git a/openspec/changes/docs-05-core-site-ia-restructure/tasks.md b/openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/tasks.md similarity index 100% rename from openspec/changes/docs-05-core-site-ia-restructure/tasks.md rename to openspec/changes/archive/2026-03-31-docs-05-core-site-ia-restructure/tasks.md diff --git a/openspec/changes/docs-07-core-handoff-conversion/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/docs-07-core-handoff-conversion/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/CHANGE_VALIDATION.md diff --git a/openspec/changes/docs-07-core-handoff-conversion/proposal.md b/openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/proposal.md similarity index 100% rename from openspec/changes/docs-07-core-handoff-conversion/proposal.md rename to openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/proposal.md diff --git a/openspec/changes/docs-07-core-handoff-conversion/specs/documentation-alignment/spec.md b/openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/specs/documentation-alignment/spec.md similarity index 100% rename from openspec/changes/docs-07-core-handoff-conversion/specs/documentation-alignment/spec.md rename to openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/specs/documentation-alignment/spec.md diff --git a/openspec/changes/docs-07-core-handoff-conversion/tasks.md b/openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/tasks.md similarity index 100% rename from openspec/changes/docs-07-core-handoff-conversion/tasks.md rename to openspec/changes/archive/2026-03-31-docs-07-core-handoff-conversion/tasks.md diff --git a/openspec/changes/docs-12-docs-validation-ci/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/CHANGE_VALIDATION.md diff --git a/openspec/changes/docs-12-docs-validation-ci/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/TDD_EVIDENCE.md diff --git a/openspec/changes/docs-12-docs-validation-ci/proposal.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/proposal.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/proposal.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/proposal.md diff --git a/openspec/changes/docs-12-docs-validation-ci/specs/docs-command-validation/spec.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/specs/docs-command-validation/spec.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/specs/docs-command-validation/spec.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/specs/docs-command-validation/spec.md diff --git a/openspec/changes/docs-12-docs-validation-ci/specs/docs-cross-site-link-check/spec.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/specs/docs-cross-site-link-check/spec.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/specs/docs-cross-site-link-check/spec.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/specs/docs-cross-site-link-check/spec.md diff --git a/openspec/changes/docs-12-docs-validation-ci/tasks.md b/openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/tasks.md similarity index 100% rename from openspec/changes/docs-12-docs-validation-ci/tasks.md rename to openspec/changes/archive/2026-03-31-docs-12-docs-validation-ci/tasks.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/CHANGE_VALIDATION.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/TDD_EVIDENCE.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/design.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/design.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/design.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/design.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/proposal.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/proposal.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/proposal.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/proposal.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-client-search/spec.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-client-search/spec.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-client-search/spec.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-client-search/spec.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-data-driven-nav/spec.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-data-driven-nav/spec.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-data-driven-nav/spec.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-data-driven-nav/spec.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-expertise-paths/spec.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-expertise-paths/spec.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-expertise-paths/spec.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-expertise-paths/spec.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-theme-toggle/spec.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-theme-toggle/spec.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/specs/core-docs-theme-toggle/spec.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/specs/core-docs-theme-toggle/spec.md diff --git a/openspec/changes/docs-13-core-nav-search-theme-roles/tasks.md b/openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/tasks.md similarity index 100% rename from openspec/changes/docs-13-core-nav-search-theme-roles/tasks.md rename to openspec/changes/archive/2026-03-31-docs-13-core-nav-search-theme-roles/tasks.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/.openspec.yaml b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/.openspec.yaml similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/.openspec.yaml rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/.openspec.yaml diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/TDD_EVIDENCE.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/design.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/design.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/design.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/design.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/proposal.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/proposal.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/proposal.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/proposal.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/specs/documentation-alignment/spec.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/documentation-alignment/spec.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/specs/documentation-alignment/spec.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/documentation-alignment/spec.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/specs/entrypoint-onboarding/spec.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/entrypoint-onboarding/spec.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/specs/entrypoint-onboarding/spec.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/entrypoint-onboarding/spec.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/specs/first-contact-story/spec.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/first-contact-story/spec.md similarity index 100% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/specs/first-contact-story/spec.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/specs/first-contact-story/spec.md diff --git a/openspec/changes/docs-14-first-contact-story-and-onboarding/tasks.md b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/tasks.md similarity index 98% rename from openspec/changes/docs-14-first-contact-story-and-onboarding/tasks.md rename to openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/tasks.md index 04c39dcf..fe0823ec 100644 --- a/openspec/changes/docs-14-first-contact-story-and-onboarding/tasks.md +++ b/openspec/changes/archive/2026-03-31-docs-14-first-contact-story-and-onboarding/tasks.md @@ -85,4 +85,4 @@ evidence in `TDD_EVIDENCE.md`. - [x] 8.1 Update `openspec/CHANGE_ORDER.md` with implementation status when work begins/lands. - [x] 8.2 Stage and commit with a Conventional Commit message. - [x] 8.3 Push the feature branch and open a PR to `dev`. -- [ ] 8.4 After merge to `dev`, remove the worktree and delete the feature branch locally/remotely. +- [x] 8.4 After merge to `dev`, remove the worktree and delete the feature branch locally/remotely. diff --git a/openspec/changes/init-ide-prompt-source-selection/.openspec.yaml b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/.openspec.yaml similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/.openspec.yaml rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/.openspec.yaml diff --git a/openspec/changes/init-ide-prompt-source-selection/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/CHANGE_VALIDATION.md diff --git a/openspec/changes/init-ide-prompt-source-selection/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/TDD_EVIDENCE.md diff --git a/openspec/changes/init-ide-prompt-source-selection/design.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/design.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/design.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/design.md diff --git a/openspec/changes/init-ide-prompt-source-selection/proposal.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/proposal.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/proposal.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/proposal.md diff --git a/openspec/changes/init-ide-prompt-source-selection/specs/init-ide-prompt-source-selection/spec.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/specs/init-ide-prompt-source-selection/spec.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/specs/init-ide-prompt-source-selection/spec.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/specs/init-ide-prompt-source-selection/spec.md diff --git a/openspec/changes/init-ide-prompt-source-selection/tasks.md b/openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/tasks.md similarity index 100% rename from openspec/changes/init-ide-prompt-source-selection/tasks.md rename to openspec/changes/archive/2026-03-31-init-ide-prompt-source-selection/tasks.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/.openspec.yaml b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/.openspec.yaml similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/.openspec.yaml rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/.openspec.yaml diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/CHANGE_VALIDATION.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/TDD_EVIDENCE.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/design.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/design.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/design.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/design.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/proposal.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/proposal.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/proposal.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/proposal.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/specs/module-owned-ide-prompts/spec.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/specs/module-owned-ide-prompts/spec.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/specs/module-owned-ide-prompts/spec.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/specs/module-owned-ide-prompts/spec.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/specs/runtime-portability/spec.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/specs/runtime-portability/spec.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/specs/runtime-portability/spec.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/specs/runtime-portability/spec.md diff --git a/openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/tasks.md b/openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/tasks.md similarity index 100% rename from openspec/changes/packaging-02-cross-platform-runtime-and-module-resources/tasks.md rename to openspec/changes/archive/2026-03-31-packaging-02-cross-platform-runtime-and-module-resources/tasks.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/.openspec.yaml b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/.openspec.yaml similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/.openspec.yaml rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/.openspec.yaml diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/CHANGE_VALIDATION.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/TDD_EVIDENCE.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/TDD_EVIDENCE.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/design.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/design.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/design.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/design.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/proposal.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/proposal.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/proposal.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/proposal.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md similarity index 61% rename from openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md index ee6f6cdd..d435e0cf 100644 --- a/openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md +++ b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-adapter/spec.md @@ -1,8 +1,29 @@ ## MODIFIED Requirements -### Requirement: SpecKitAdapter get_capabilities returns tool metadata +### Requirement: Spec-Kit Adapter Implementation -The system SHALL return comprehensive tool capabilities including extension metadata, preset information, and hook events when detecting a spec-kit installation. +The system SHALL provide a `SpecKitAdapter` class that encapsulates all Spec-Kit-specific logic, including comprehensive tool capabilities detection for v0.4.x features. The `ToolCapabilities` dataclass SHALL include optional fields for extensions, extension commands, presets, hook events, and version detection source. + +#### Scenario: ToolCapabilities with extension metadata + +- **GIVEN** a `ToolCapabilities` instance created for a spec-kit v0.4.x repository +- **WHEN** the instance is constructed with extension data +- **THEN** `extensions` is a `list[str]` of extension names (e.g., `["reconcile", "sync", "verify"]`) +- **AND** `extension_commands` is a `dict[str, list[str]]` mapping extension names to command lists +- **AND** `presets` is a `list[str]` of active preset names +- **AND** `hook_events` is a `list[str]` of detected hook event types +- **AND** `detected_version_source` is a `str` with value `"cli"` or `"heuristic"` + +#### Scenario: ToolCapabilities backward compatibility + +- **GIVEN** a `ToolCapabilities` instance created without the new optional fields +- **WHEN** the instance is constructed with only the existing fields (`tool`, `version`, `layout`, `specs_dir`, `has_external_config`, `has_custom_hooks`, `supported_sync_modes`) +- **THEN** `extensions` defaults to `None` +- **AND** `extension_commands` defaults to `None` +- **AND** `presets` defaults to `None` +- **AND** `hook_events` defaults to `None` +- **AND** `detected_version_source` defaults to `None` +- **AND** all existing adapter code continues to work without modification #### Scenario: Get capabilities for spec-kit v0.4.x repository diff --git a/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md new file mode 100644 index 00000000..ac71a475 --- /dev/null +++ b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md @@ -0,0 +1,27 @@ +## ADDED Requirements + +### Requirement: BridgeConfig spec-kit presets include all slash commands + +The `BridgeConfig` spec-kit presets SHALL map all 7 core spec-kit slash commands. + +#### Scenario: Classic preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_classic()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains entries for: `"specify"`, `"plan"`, `"tasks"`, `"implement"`, `"constitution"`, `"clarify"`, `"analyze"` +- **AND** each entry has a `trigger` matching the corresponding `/speckit.*` slash command +- **AND** each entry has appropriate `input_ref` and `output_ref` fields + +#### Scenario: Specify preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_specify()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains the same 7 entries as the classic preset +- **AND** artifact path patterns use `.specify/specs/` prefix + +#### Scenario: Modern preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_modern()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains the same 7 entries as the classic preset +- **AND** artifact path patterns use `docs/specs/` prefix diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/specs/speckit-extension-catalog/spec.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/speckit-extension-catalog/spec.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/specs/speckit-extension-catalog/spec.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/speckit-extension-catalog/spec.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/specs/speckit-version-detection/spec.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/speckit-version-detection/spec.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/specs/speckit-version-detection/spec.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/specs/speckit-version-detection/spec.md diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/tasks.md b/openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/tasks.md similarity index 100% rename from openspec/changes/speckit-02-v04-adapter-alignment/tasks.md rename to openspec/changes/archive/2026-03-31-speckit-02-v04-adapter-alignment/tasks.md diff --git a/openspec/changes/clean-code-01-principle-gates/tasks.md b/openspec/changes/clean-code-01-principle-gates/tasks.md deleted file mode 100644 index 39c4e84b..00000000 --- a/openspec/changes/clean-code-01-principle-gates/tasks.md +++ /dev/null @@ -1,30 +0,0 @@ -# Tasks: clean-code-01-principle-gates - -## 1. Branch and dependency guardrails - -- [ ] 1.1 Create dedicated worktree branch `feature/clean-code-01-principle-gates` from `dev` before implementation work: `scripts/worktree.sh create feature/clean-code-01-principle-gates`. -- [ ] 1.2 Confirm `code-review-zero-findings` has recorded a zero-finding self-review baseline and that the modules repo change `clean-code-02-expanded-review-module` is available for consumption. -- [ ] 1.3 Reconfirm scope against the 2026-03-22 clean-code implementation plan and the updated `openspec/CHANGE_ORDER.md`. - -## 2. Spec-first and test-first preparation - -- [ ] 2.1 Finalize `specs/` deltas for clean-code charter references, clean-code compliance gating, and staged LOC/nesting checks. -- [ ] 2.2 Add or update tests derived from the new scenarios before touching production code. -- [ ] 2.3 Run targeted tests and capture failing-first evidence in `TDD_EVIDENCE.md`. - -## 3. Implementation - -- [ ] 3.1 Update instruction surfaces (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/clean-code-principles.mdc`, `.github/copilot-instructions.md`, relevant `.codex` skill entry points) to reference the canonical clean-code charter. -- [ ] 3.2 Wire specfact-cli review and CI flows to consume the expanded clean-code categories from the modules repo without introducing a second clean-code configuration model. -- [ ] 3.3 Adopt Phase A LOC, nesting-depth, and parameter-count checks through the review integration path and preserve the Phase B thresholds as a later change. - -## 4. Validation and documentation - -- [ ] 4.1 Re-run targeted tests, review flows, and quality gates until all changed scenarios pass. -- [ ] 4.2 Update contributor-facing docs that explain AI instruction files, repo review rules, and clean-code governance. -- [ ] 4.3 Run `openspec validate clean-code-01-principle-gates --strict` and resolve all issues. - -## 5. Delivery - -- [ ] 5.1 Update `openspec/CHANGE_ORDER.md` dependency notes if implementation sequencing changes again. -- [ ] 5.2 Open a PR from `feature/clean-code-01-principle-gates` to `dev` with spec/test/code/docs evidence. diff --git a/openspec/changes/docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md b/openspec/changes/docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md deleted file mode 100644 index ba029ad9..00000000 --- a/openspec/changes/docs-05-core-site-ia-restructure/specs/core-cli-reference/spec.md +++ /dev/null @@ -1,25 +0,0 @@ -# Capability: core-cli-reference - -Dedicated reference pages for each core CLI command (init, module, upgrade). - -## Scenarios - -### Scenario: Init reference page documents all subcommands and options - -Given the docs/core-cli/init.md page exists -When a user reads the page -Then it documents: specfact init, init --profile, init --install, init ide, init --install-deps -And all documented commands match the actual --help output - -### Scenario: Module reference page documents all subcommands - -Given the docs/core-cli/module.md page exists -When a user reads the page -Then it documents: module install, module uninstall, module list, module show, module search, module upgrade, module alias, module add-registry, module list-registries, module remove-registry, module enable, module disable -And all documented commands match the actual --help output - -### Scenario: Upgrade reference page documents the command - -Given the docs/core-cli/upgrade.md page exists -When a user reads the page -Then it documents the specfact upgrade command and its options diff --git a/openspec/changes/docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md b/openspec/changes/docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md deleted file mode 100644 index 4a126c22..00000000 --- a/openspec/changes/docs-05-core-site-ia-restructure/specs/core-docs-progressive-nav/spec.md +++ /dev/null @@ -1,30 +0,0 @@ -# Capability: core-docs-progressive-nav - -Core docs site sidebar provides a 6-section progressive navigation structure. - -## Scenarios - -### Scenario: Sidebar renders 6 sections in correct order - -Given the core docs site is built with Jekyll -When a user visits any page on docs.specfact.io -Then the sidebar displays sections in order: Getting Started, Core CLI, Module System, Architecture, Reference, Migration - -### Scenario: Getting Started section contains beginner-friendly entries - -Given the sidebar Getting Started section -When a user expands the section -Then it contains: Installation, 5-Minute Quickstart, Profiles & IDE Setup -And does NOT contain module-specific tutorials (backlog quickstart, standup, refine) - -### Scenario: Core CLI section links to command reference pages - -Given the sidebar Core CLI section -When a user expands the section -Then it contains links to: specfact init, specfact module, specfact upgrade, Operational Modes, Debug Logging - -### Scenario: Moved files redirect to new locations - -Given a file was moved from its old location -When a user visits the old URL -Then they are redirected to the new URL via jekyll-redirect-from diff --git a/openspec/changes/docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md b/openspec/changes/docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md deleted file mode 100644 index 72bf13e2..00000000 --- a/openspec/changes/docs-05-core-site-ia-restructure/specs/documentation-alignment/spec.md +++ /dev/null @@ -1,27 +0,0 @@ -# Delta: documentation-alignment - -Extends the existing `documentation-alignment` capability with core-only site focus and module redirect policy. - -## New Scenarios - -### Scenario: Core docs site excludes module-specific workflow content - -- **GIVEN** the core docs site at docs.specfact.io -- **WHEN** a reader browses guides and tutorials -- **THEN** module-specific tutorials (backlog quickstart, standup, refine) are NOT present in the core site -- **AND** those topics link to the canonical modules site at modules.specfact.io - -### Scenario: Core site landing page delineates core vs modules - -- **GIVEN** the docs.specfact.io landing page (index.md) -- **WHEN** a reader arrives at the docs home -- **THEN** the page clearly separates core platform concerns from module-specific workflows -- **AND** provides direct links to modules.specfact.io for bundle-specific guidance - -### Scenario: Getting Started section focuses on platform bootstrap - -- **GIVEN** the Getting Started section of the core docs -- **WHEN** a new user follows the getting started path -- **THEN** it covers installation, quickstart, and profiles/IDE setup -- **AND** does NOT include module-specific tutorials as inline content -- **AND** links to modules.specfact.io for module workflow tutorials diff --git a/openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md b/openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md deleted file mode 100644 index 86b31fc8..00000000 --- a/openspec/changes/speckit-02-v04-adapter-alignment/specs/bridge-registry/spec.md +++ /dev/null @@ -1,52 +0,0 @@ -## MODIFIED Requirements - -### Requirement: ToolCapabilities supports extension and preset metadata - -The `ToolCapabilities` dataclass SHALL include optional fields for extensions, extension commands, presets, hook events, and version detection source. - -#### Scenario: ToolCapabilities with extension metadata - -- **GIVEN** a `ToolCapabilities` instance created for a spec-kit v0.4.x repository -- **WHEN** the instance is constructed with extension data -- **THEN** `extensions` is a `list[str]` of extension names (e.g., `["reconcile", "sync", "verify"]`) -- **AND** `extension_commands` is a `dict[str, list[str]]` mapping extension names to command lists -- **AND** `presets` is a `list[str]` of active preset names -- **AND** `hook_events` is a `list[str]` of detected hook event types -- **AND** `detected_version_source` is a `str` with value `"cli"` or `"heuristic"` - -#### Scenario: ToolCapabilities backward compatibility - -- **GIVEN** a `ToolCapabilities` instance created without the new optional fields -- **WHEN** the instance is constructed with only the existing fields (`tool`, `version`, `layout`, `specs_dir`, `has_external_config`, `has_custom_hooks`, `supported_sync_modes`) -- **THEN** `extensions` defaults to `None` -- **AND** `extension_commands` defaults to `None` -- **AND** `presets` defaults to `None` -- **AND** `hook_events` defaults to `None` -- **AND** `detected_version_source` defaults to `None` -- **AND** all existing adapter code continues to work without modification - -### Requirement: BridgeConfig spec-kit presets include all slash commands - -The `BridgeConfig` spec-kit presets SHALL map all 7 core spec-kit slash commands. - -#### Scenario: Classic preset includes full command set - -- **GIVEN** `BridgeConfig.preset_speckit_classic()` is called -- **WHEN** the preset is constructed -- **THEN** `commands` dict contains entries for: `"specify"`, `"plan"`, `"tasks"`, `"implement"`, `"constitution"`, `"clarify"`, `"analyze"` -- **AND** each entry has a `trigger` matching the corresponding `/speckit.*` slash command -- **AND** each entry has appropriate `input_ref` and `output_ref` fields - -#### Scenario: Specify preset includes full command set - -- **GIVEN** `BridgeConfig.preset_speckit_specify()` is called -- **WHEN** the preset is constructed -- **THEN** `commands` dict contains the same 7 entries as the classic preset -- **AND** artifact path patterns use `.specify/specs/` prefix - -#### Scenario: Modern preset includes full command set - -- **GIVEN** `BridgeConfig.preset_speckit_modern()` is called -- **WHEN** the preset is constructed -- **THEN** `commands` dict contains the same 7 entries as the classic preset -- **AND** artifact path patterns use `docs/specs/` prefix diff --git a/openspec/specs/agent-instruction-clean-code-charter/spec.md b/openspec/specs/agent-instruction-clean-code-charter/spec.md new file mode 100644 index 00000000..89e8b424 --- /dev/null +++ b/openspec/specs/agent-instruction-clean-code-charter/spec.md @@ -0,0 +1,20 @@ +# agent-instruction-clean-code-charter Specification + +## Purpose +TBD - created by archiving change clean-code-01-principle-gates. Update Purpose after archive. +## Requirements +### Requirement: Agent Instruction Clean-Code Charter +The repository SHALL expose the 7-principle clean-code charter consistently across its instruction surfaces without creating drift-prone duplicate sources of truth. + +#### Scenario: Core instruction surfaces reference the charter consistently +- **GIVEN** a contributor opens `AGENTS.md`, `CLAUDE.md`, `.cursor/rules/clean-code-principles.mdc`, or `.github/copilot-instructions.md` +- **WHEN** they inspect clean-code guidance +- **THEN** each surface points to the same clean-code charter semantics +- **AND** any shorter alias surface references the canonical charter rather than redefining it independently + +#### Scenario: Generated IDE aliases stay lightweight +- **GIVEN** platform-specific instruction files are generated from `ai-integration-03-instruction-files` +- **WHEN** clean-code guidance is included +- **THEN** the generated file contains a short clean-code alias reference +- **AND** the full charter text is not duplicated into every generated alias + diff --git a/openspec/specs/backlog-adapter/spec.md b/openspec/specs/backlog-adapter/spec.md index d633ac3e..6a925861 100644 --- a/openspec/specs/backlog-adapter/spec.md +++ b/openspec/specs/backlog-adapter/spec.md @@ -7,39 +7,22 @@ TBD - created by archiving change add-generic-backlog-abstraction. Update Purpos The system SHALL provide a standard `BacklogAdapter` interface that all backlog sources (GitHub, ADO, JIRA, GitLab, etc.) must implement. -#### Scenario: Case-insensitive filter matching - -- **GIVEN** filters for state or assignee -- **WHEN** an adapter applies those filters -- **THEN** comparisons are case-insensitive and whitespace-normalized -- **AND** the adapter does not drop items due to case differences. - -#### Scenario: Adapter-specific assignee normalization - -- **GIVEN** an ADO work item with `System.AssignedTo` values (displayName, uniqueName, or mail) -- **WHEN** a user filters by assignee -- **THEN** the adapter matches against any of those identity fields (case-insensitive). - -- **GIVEN** a GitHub issue with assignee login -- **WHEN** a user filters by assignee with or without leading `@` -- **THEN** the adapter matches login and display name when available (case-insensitive) and falls back to login-only. - -#### Scenario: Sprint disambiguation for ADO - -- **GIVEN** multiple iteration paths that contain the same sprint name -- **WHEN** a user filters with a name-only `--sprint` -- **THEN** the adapter reports ambiguity and prompts for a full iteration path -- **AND** does not default to the earliest matching sprint. - -#### Scenario: Default to current iteration for ADO when sprint omitted - -- **GIVEN** an ADO adapter with org/project/team context -- **WHEN** `--sprint` is not provided -- **THEN** the adapter resolves the current active iteration via the team iterations API -- **AND** uses the `$timeframe=current` query for the team iterations endpoint -- **AND** uses that iteration path for filtering when available. -- **AND** the team is taken from `--ado-team` when provided, otherwise defaults to the project team name. -- **AND** the team iterations endpoint format follows `/{org}/{project}/{team}/_apis/work/teamsettings/iterations?$timeframe=current`. +#### Scenario: Selective proposal import preserves provider-native payload + +- **GIVEN** an adapter supports selective backlog import by explicit item reference +- **WHEN** bridge sync fetches one item through `fetch_backlog_item()` and passes the result into proposal import +- **THEN** the fetched artifact preserves the provider-native fields required by `extract_change_proposal_data()` or `import_artifact()` +- **AND** adapter-specific convenience fields may be added without discarding the native structure +- **AND** contract tests cover the `fetch_backlog_item()` to `import_artifact()` round trip for supported adapters + +#### Scenario: Imported proposal IDs normalize title-first across adapters + +- **GIVEN** an imported backlog artifact has no embedded OpenSpec change ID metadata +- **AND** the source artifact has a usable human-readable title +- **WHEN** the adapter or shared backlog import path constructs the proposal change ID +- **THEN** the change ID is derived from a normalized title slug +- **AND** a numeric provider ID is used only as source tracking metadata or as a deterministic suffix when needed for uniqueness +- **AND** the system does not default to a numeric-only change name while a usable title is available ### Requirement: Adapter Extensibility diff --git a/openspec/specs/bridge-adapter/spec.md b/openspec/specs/bridge-adapter/spec.md index 6801b9d9..07e0db96 100644 --- a/openspec/specs/bridge-adapter/spec.md +++ b/openspec/specs/bridge-adapter/spec.md @@ -270,86 +270,65 @@ The system SHALL use a plugin-based adapter registry pattern for all tool integr ### Requirement: Spec-Kit Adapter Implementation -The system SHALL provide a `SpecKitAdapter` class that encapsulates all Spec-Kit-specific logic. +The system SHALL provide a `SpecKitAdapter` class that encapsulates all Spec-Kit-specific logic, including comprehensive tool capabilities detection for v0.4.x features. The `ToolCapabilities` dataclass SHALL include optional fields for extensions, extension commands, presets, hook events, and version detection source. -#### Scenario: Spec-Kit Detection +#### Scenario: ToolCapabilities with extension metadata -- **GIVEN** a repository with Spec-Kit structure -- **WHEN** `SpecKitAdapter.detect()` is called -- **THEN** checks for `.specify/` directory (indicates Spec-Kit project) -- **AND** checks for `specs/` directory (classic format) or `docs/specs/` directory (modern format) -- **AND** checks for `.specify/memory/constitution.md` file -- **AND** returns True if Spec-Kit structure is detected (`.specify/` directory exists) -- **AND** supports cross-repo detection via `bridge_config.external_base_path` +- **GIVEN** a `ToolCapabilities` instance created for a spec-kit v0.4.x repository +- **WHEN** the instance is constructed with extension data +- **THEN** `extensions` is a `list[str]` of extension names (e.g., `["reconcile", "sync", "verify"]`) +- **AND** `extension_commands` is a `dict[str, list[str]]` mapping extension names to command lists +- **AND** `presets` is a `list[str]` of active preset names +- **AND** `hook_events` is a `list[str]` of detected hook event types +- **AND** `detected_version_source` is a `str` with value `"cli"` or `"heuristic"` -#### Scenario: Spec-Kit Capabilities +#### Scenario: ToolCapabilities backward compatibility -- **GIVEN** Spec-Kit is detected -- **WHEN** `SpecKitAdapter.get_capabilities()` is called +- **GIVEN** a `ToolCapabilities` instance created without the new optional fields +- **WHEN** the instance is constructed with only the existing fields (`tool`, `version`, `layout`, `specs_dir`, `has_external_config`, `has_custom_hooks`, `supported_sync_modes`) +- **THEN** `extensions` defaults to `None` +- **AND** `extension_commands` defaults to `None` +- **AND** `presets` defaults to `None` +- **AND** `hook_events` defaults to `None` +- **AND** `detected_version_source` defaults to `None` +- **AND** all existing adapter code continues to work without modification + +#### Scenario: Get capabilities for spec-kit v0.4.x repository + +- **GIVEN** a repository with `.specify/` directory, `extensions/catalog.community.json`, and `presets/` directory +- **WHEN** `SpecKitAdapter.get_capabilities(repo_path)` is called - **THEN** returns `ToolCapabilities` with: - - `tool="speckit"` - - `specs_dir` set to detected format (`specs/` for classic, `docs/specs/` for modern) - - `has_custom_hooks` flag based on constitution presence and validation (non-minimal constitution) - - `layout` set to "standard" (Spec-Kit uses standard layout) -- **AND** validates constitution exists and is not minimal (empty or template-only) -- **AND** supports cross-repo paths via bridge_config - -#### Scenario: Spec-Kit Artifact Import - -- **GIVEN** Spec-Kit artifacts exist in repository -- **WHEN** `SpecKitAdapter.import_artifact()` is called -- **THEN** uses `SpecKitScanner` and `SpecKitConverter` internally -- **AND** maps Spec-Kit artifacts (spec.md, plan.md, tasks.md) to SpecFact models -- **AND** stores Spec-Kit paths in `source_tracking.source_metadata` -- **AND** supports both modern (`.specify/`) and classic (`specs/`) formats - -#### Scenario: Spec-Kit Artifact Export - -- **GIVEN** SpecFact project bundle with features -- **WHEN** `SpecKitAdapter.export_artifact()` is called -- **THEN** uses `SpecKitConverter.convert_to_speckit()` internally -- **AND** exports SpecFact features to Spec-Kit format (spec.md, plan.md, tasks.md) -- **AND** supports overwrite mode and conflict resolution -- **AND** writes to correct format based on detected Spec-Kit structure - -#### Scenario: Spec-Kit Bridge Config Generation - -- **GIVEN** Spec-Kit is detected -- **WHEN** `SpecKitAdapter.generate_bridge_config()` is called -- **THEN** returns `BridgeConfig` using existing preset methods: - - `BridgeConfig.preset_speckit_classic()` if classic format detected (`specs/` directory at root) - - `BridgeConfig.preset_speckit_modern()` if modern format detected (`docs/specs/` directory) - - Artifact mappings include: `specification`, `plan`, `tasks`, `contracts` - - Constitution path: `.specify/memory/constitution.md` (checked for both formats) -- **AND** includes `external_base_path` if cross-repo detected -- **AND** auto-detects format based on directory structure (classic: `specs/` at root, modern: `docs/specs/`) - -#### Scenario: Spec-Kit Bidirectional Sync - -- **GIVEN** Spec-Kit adapter is used for bidirectional sync -- **WHEN** `BridgeSync.sync_bidirectional()` is called with Spec-Kit adapter -- **THEN** adapter's `import_artifact()` and `export_artifact()` methods handle change detection internally -- **AND** adapter detects changes in Spec-Kit artifacts (via internal `_detect_speckit_changes()` helper) -- **AND** adapter detects changes in SpecFact artifacts (via internal `_detect_specfact_changes()` helper) -- **AND** adapter merges changes and detects conflicts (via internal `_merge_changes()` and `_detect_conflicts()` helpers) -- **AND** conflicts are resolved using priority rules (SpecFact > Spec-Kit for artifacts) - -#### Scenario: Spec-Kit Constitution Validation - -- **GIVEN** Spec-Kit adapter is used -- **WHEN** `SpecKitAdapter.get_capabilities()` is called -- **THEN** checks for constitution file (`.specify/memory/constitution.md` or classic format) -- **AND** sets `has_custom_hooks` flag based on constitution presence -- **AND** validates constitution is not minimal (if present) -- **AND** returns `ToolCapabilities` with constitution validation status - -#### Scenario: Constitution Command Location - -- **GIVEN** Spec-Kit constitution management commands exist -- **WHEN** user wants to manage constitution -- **THEN** commands are available via `specfact sdd constitution` (not `specfact bridge constitution`) -- **AND** `specfact bridge` command does not exist (bridge adapters are internal connectors, no user-facing commands) -- **AND** constitution commands (bootstrap, enrich, validate) are under SDD command group (Spec-Kit is an SDD tool) + - `tool` equals `"speckit"` + - `version` populated from CLI or heuristic detection + - `layout` equals `"modern"` + - `extensions` contains list of detected extension names + - `extension_commands` contains dict mapping extension names to their commands + - `presets` contains list of detected preset names + - `hook_events` contains list of detected hook event types (e.g., `["before_task", "after_task"]`) + - `detected_version_source` equals `"cli"` or `"heuristic"` + - `supported_sync_modes` includes `"bidirectional"` and `"unidirectional"` + +#### Scenario: Get capabilities for legacy spec-kit repository + +- **GIVEN** a repository with only `specs/` directory at root (no `.specify/`, no `extensions/`, no `presets/`) +- **WHEN** `SpecKitAdapter.get_capabilities(repo_path)` is called +- **THEN** returns `ToolCapabilities` with: + - `tool` equals `"speckit"` + - `version` equals `None` + - `layout` equals `"classic"` + - `extensions` equals `None` + - `extension_commands` equals `None` + - `presets` equals `None` + - `hook_events` equals `None` + - `detected_version_source` equals `None` +- **AND** behavior is identical to the pre-change adapter + +#### Scenario: Get capabilities with cross-repo bridge config + +- **GIVEN** a bridge config with `external_base_path` pointing to a spec-kit repository +- **WHEN** `SpecKitAdapter.get_capabilities(repo_path, bridge_config)` is called +- **THEN** extension and preset detection uses the `external_base_path` as base +- **AND** CLI version detection is skipped for cross-repo scenarios (filesystem-only) ### Requirement: Backlog Adapter Extensibility Pattern diff --git a/openspec/specs/bridge-registry/spec.md b/openspec/specs/bridge-registry/spec.md index 963080e5..856c8d44 100644 --- a/openspec/specs/bridge-registry/spec.md +++ b/openspec/specs/bridge-registry/spec.md @@ -61,3 +61,29 @@ The system SHALL support bridge registration and local converter resolution with - **THEN** bridge registration SHALL complete using local module manifests and local Python imports - **AND** SHALL NOT require external API or registry calls. +### Requirement: BridgeConfig spec-kit presets include all slash commands + +The `BridgeConfig` spec-kit presets SHALL map all 7 core spec-kit slash commands. + +#### Scenario: Classic preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_classic()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains entries for: `"specify"`, `"plan"`, `"tasks"`, `"implement"`, `"constitution"`, `"clarify"`, `"analyze"` +- **AND** each entry has a `trigger` matching the corresponding `/speckit.*` slash command +- **AND** each entry has appropriate `input_ref` and `output_ref` fields + +#### Scenario: Specify preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_specify()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains the same 7 entries as the classic preset +- **AND** artifact path patterns use `.specify/specs/` prefix + +#### Scenario: Modern preset includes full command set + +- **GIVEN** `BridgeConfig.preset_speckit_modern()` is called +- **WHEN** the preset is constructed +- **THEN** `commands` dict contains the same 7 entries as the classic preset +- **AND** artifact path patterns use `docs/specs/` prefix + diff --git a/openspec/specs/clean-code-compliance-gate/spec.md b/openspec/specs/clean-code-compliance-gate/spec.md new file mode 100644 index 00000000..6caeaa24 --- /dev/null +++ b/openspec/specs/clean-code-compliance-gate/spec.md @@ -0,0 +1,20 @@ +# clean-code-compliance-gate Specification + +## Purpose +TBD - created by archiving change clean-code-01-principle-gates. Update Purpose after archive. +## Requirements +### Requirement: Clean-Code Compliance Gate +The repository SHALL consume the expanded review-module clean-code categories and block clean-code regressions before merge. + +#### Scenario: Repo review includes expanded clean-code categories +- **GIVEN** the review module exposes clean-code categories `naming`, `kiss`, `yagni`, `dry`, and `solid` +- **WHEN** `specfact review` runs against specfact-cli in CI or local gated mode +- **THEN** those categories are included in the review result +- **AND** regressions in blocking clean-code rules fail the gated run + +#### Scenario: Zero-finding dogfood baseline stays a prerequisite +- **GIVEN** `code-review-zero-findings` has not yet reached its zero-finding proof +- **WHEN** implementation work for this change is evaluated +- **THEN** clean-code gating cannot be considered complete +- **AND** the change remains blocked until the prerequisite evidence exists + diff --git a/openspec/specs/clean-code-loc-nesting-check/spec.md b/openspec/specs/clean-code-loc-nesting-check/spec.md new file mode 100644 index 00000000..123419bd --- /dev/null +++ b/openspec/specs/clean-code-loc-nesting-check/spec.md @@ -0,0 +1,20 @@ +# clean-code-loc-nesting-check Specification + +## Purpose +TBD - created by archiving change clean-code-01-principle-gates. Update Purpose after archive. +## Requirements +### Requirement: Staged LOC, Nesting, and Parameter Checks +The repository SHALL adopt the expanded KISS metrics through a staged rollout that starts with the Phase A thresholds from the 2026-03-22 plan. + +#### Scenario: Phase A thresholds are enforced first +- **GIVEN** the clean-code review checks are enabled for specfact-cli +- **WHEN** LOC-per-function findings are evaluated +- **THEN** warning and error thresholds start at `>80` and `>120` +- **AND** nesting-depth and parameter-count checks are active in the same review run + +#### Scenario: Phase B remains deferred until cleanup is complete +- **GIVEN** stricter LOC thresholds of `>40` and `>80` are planned +- **WHEN** this change is implemented +- **THEN** Phase B remains documented as a future tightening step +- **AND** the current change does not silently promote Phase B to a hard gate + diff --git a/openspec/specs/code-review-module/spec.md b/openspec/specs/code-review-module/spec.md index 3169e394..e2ecf931 100644 --- a/openspec/specs/code-review-module/spec.md +++ b/openspec/specs/code-review-module/spec.md @@ -28,3 +28,29 @@ The `nold-ai/specfact-code-review` module SHALL be installable and extend `specf - **WHEN** the user installs it again - **THEN** no duplicate `review` entries appear in `specfact code --help` +### Requirement: Self-referential scan — review module can scan itself without errors +The `specfact-code-review` module SHALL be able to review its own source files (including the files that implement the reviewer) without infinite loops, false positives from meta-scanning, or unhandled exceptions. + +#### Scenario: Review run on specfact-cli repo completes without tool_error findings +- **WHEN** `specfact review` is run with the specfact-cli repo as the target +- **THEN** no finding with `tool` equal to `code-review-module` or `category` equal to `tool_error` is produced +- **AND** the run exits with code 0 (assuming all other findings are resolved) + +#### Scenario: Tool error finding is surfaced as error severity +- **WHEN** any configured tool fails to invoke (e.g., missing binary) +- **THEN** a finding with `category="tool_error"` and `severity="error"` is produced +- **AND** the finding message includes the tool name and failure reason + +### Requirement: CI gate integration — review must be runnable non-interactively +The review module SHALL support a `--ci` or equivalent non-interactive flag that suppresses prompts, writes machine-readable output to `.specfact/code-review.json`, and exits with code 1 on any finding at severity `error` or higher. + +#### Scenario: Non-interactive CI run writes JSON report and exits non-zero on errors +- **WHEN** `specfact review run --ci` is executed and error-severity findings exist +- **THEN** `.specfact/code-review.json` is written with `overall_verdict: "FAIL"` and `ci_exit_code: 1` +- **AND** the process exits with code 1 + +#### Scenario: Non-interactive CI run exits zero on clean codebase +- **WHEN** `specfact review run --ci` is executed and no findings exist +- **THEN** `.specfact/code-review.json` is written with `overall_verdict: "PASS"` and `ci_exit_code: 0` +- **AND** the process exits with code 0 + diff --git a/openspec/specs/core-cli-reference/spec.md b/openspec/specs/core-cli-reference/spec.md new file mode 100644 index 00000000..a7e25be0 --- /dev/null +++ b/openspec/specs/core-cli-reference/spec.md @@ -0,0 +1,29 @@ +# core-cli-reference Specification + +## Purpose +TBD - created by archiving change docs-05-core-site-ia-restructure. Update Purpose after archive. +## Requirements +### Requirement: Core CLI reference pages exist + +The system SHALL provide dedicated reference pages for core CLI commands. + +#### Scenario: Init reference page documents all subcommands and options + +- **GIVEN** the docs/core-cli/init.md page exists +- **WHEN** a user reads the page +- **THEN** it documents: specfact init, init --profile, init --install, init ide, init --install-deps +- **AND** all documented commands match the actual --help output + +#### Scenario: Module reference page documents all subcommands + +- **GIVEN** the docs/core-cli/module.md page exists +- **WHEN** a user reads the page +- **THEN** it documents: module install, module uninstall, module list, module show, module search, module upgrade, module alias, module add-registry, module list-registries, module remove-registry, module enable, module disable +- **AND** all documented commands match the actual --help output + +#### Scenario: Upgrade reference page documents the command + +- **GIVEN** the docs/core-cli/upgrade.md page exists +- **WHEN** a user reads the page +- **THEN** it documents the specfact upgrade command and its options + diff --git a/openspec/specs/core-docs-client-search/spec.md b/openspec/specs/core-docs-client-search/spec.md new file mode 100644 index 00000000..cf6dcdd7 --- /dev/null +++ b/openspec/specs/core-docs-client-search/spec.md @@ -0,0 +1,23 @@ +# core-docs-client-search Specification + +## Purpose +TBD - created by archiving change docs-13-core-nav-search-theme-roles. Update Purpose after archive. +## Requirements +### Requirement: Core docs pages SHALL be searchable from a client-side index + +Core docs pages SHALL be searchable from the site experience using a client-side index built from repository content. + +#### Scenario: Search returns matching core CLI reference pages + +- **GIVEN** the core docs site search is available +- **WHEN** a user searches for a known core term such as `init`, `module`, or `architecture` +- **THEN** the results include the corresponding core docs pages +- **AND** the results use core docs metadata and content excerpts from the built site + +#### Scenario: Search stays within core-owned docs scope + +- **GIVEN** the core docs site search index is generated +- **WHEN** the index is built +- **THEN** it only includes pages owned by the core docs site +- **AND** it does not present module-site pages as if they were local core pages + diff --git a/openspec/specs/core-docs-data-driven-nav/spec.md b/openspec/specs/core-docs-data-driven-nav/spec.md new file mode 100644 index 00000000..39d65b56 --- /dev/null +++ b/openspec/specs/core-docs-data-driven-nav/spec.md @@ -0,0 +1,22 @@ +# core-docs-data-driven-nav Specification + +## Purpose +TBD - created by archiving change docs-13-core-nav-search-theme-roles. Update Purpose after archive. +## Requirements +### Requirement: Core docs navigation SHALL render from structured navigation data + +Core docs navigation SHALL be rendered from a structured data source rather than duplicated as hardcoded sidebar markup. + +#### Scenario: Sidebar renders from structured core navigation data + +- **GIVEN** the core docs site is built +- **WHEN** a user visits a page on `docs.specfact.io` +- **THEN** the sidebar navigation is rendered from a structured data source +- **AND** the rendered sections still reflect the core IA owned by the core docs site + +#### Scenario: Navigation updates do not require hardcoded layout edits + +- **GIVEN** a core docs section link changes +- **WHEN** the navigation data source is updated +- **THEN** the sidebar rendering reflects the change without duplicating the link structure in hardcoded template markup + diff --git a/openspec/specs/core-docs-expertise-paths/spec.md b/openspec/specs/core-docs-expertise-paths/spec.md new file mode 100644 index 00000000..3480a307 --- /dev/null +++ b/openspec/specs/core-docs-expertise-paths/spec.md @@ -0,0 +1,23 @@ +# core-docs-expertise-paths Specification + +## Purpose +TBD - created by archiving change docs-13-core-nav-search-theme-roles. Update Purpose after archive. +## Requirements +### Requirement: Core docs SHALL expose expertise-aware or role-aware entry paths + +Core docs SHALL expose expertise-aware or role-aware paths that help users find the right entry points for their current level. + +#### Scenario: Expertise filter narrows visible navigation options + +- **GIVEN** the core docs navigation includes expertise-aware metadata +- **WHEN** a user selects a specific expertise level +- **THEN** the visible navigation emphasizes pages relevant to that level +- **AND** the selection persists across page loads + +#### Scenario: Landing page offers clear entry paths without reintroducing module-owned tutorials + +- **GIVEN** the core docs landing page is updated +- **WHEN** a new or returning user arrives at the site +- **THEN** the page highlights clear core-docs starting paths by task or audience +- **AND** any module-specific depth continues to link out to the modules site rather than being duplicated in core + diff --git a/openspec/specs/core-docs-progressive-nav/spec.md b/openspec/specs/core-docs-progressive-nav/spec.md new file mode 100644 index 00000000..bcf9ae5a --- /dev/null +++ b/openspec/specs/core-docs-progressive-nav/spec.md @@ -0,0 +1,34 @@ +# core-docs-progressive-nav Specification + +## Purpose +TBD - created by archiving change docs-05-core-site-ia-restructure. Update Purpose after archive. +## Requirements +### Requirement: Sidebar provides 6-section progressive navigation + +The system SHALL provide a sidebar with 6 sections in a specific order. + +#### Scenario: Sidebar renders 6 sections in correct order + +- **GIVEN** the core docs site is built with Jekyll +- **WHEN** a user visits any page on docs.specfact.io +- **THEN** the sidebar displays sections in order: Getting Started, Core CLI, Module System, Architecture, Reference, Migration + +#### Scenario: Getting Started section contains beginner-friendly entries + +- **GIVEN** the sidebar Getting Started section +- **WHEN** a user expands the section +- **THEN** it contains: Installation, 5-Minute Quickstart, Profiles & IDE Setup +- **AND** does NOT contain module-specific tutorials (backlog quickstart, standup, refine) + +#### Scenario: Core CLI section links to command reference pages + +- **GIVEN** the sidebar Core CLI section +- **WHEN** a user expands the section +- **THEN** it contains links to: specfact init, specfact module, specfact upgrade, Operational Modes, Debug Logging + +#### Scenario: Moved files redirect to new locations + +- **GIVEN** a file was moved from its old location +- **WHEN** a user visits the old URL +- **THEN** they are redirected to the new URL via jekyll-redirect-from + diff --git a/openspec/specs/core-docs-theme-toggle/spec.md b/openspec/specs/core-docs-theme-toggle/spec.md new file mode 100644 index 00000000..d193f7a6 --- /dev/null +++ b/openspec/specs/core-docs-theme-toggle/spec.md @@ -0,0 +1,23 @@ +# core-docs-theme-toggle Specification + +## Purpose +TBD - created by archiving change docs-13-core-nav-search-theme-roles. Update Purpose after archive. +## Requirements +### Requirement: Core docs SHALL support a persisted light/dark theme toggle + +Core docs SHALL support a persisted light/dark theme toggle for the docs site shell. + +#### Scenario: User toggles theme and preference persists + +- **GIVEN** the core docs site is loaded +- **WHEN** a user switches between light and dark theme +- **THEN** the site updates its color theme accordingly +- **AND** the selected preference persists across page reloads + +#### Scenario: Theme styling preserves readability for core docs content + +- **GIVEN** the core docs include command examples, reference pages, and long-form explanatory content +- **WHEN** the user reads the site in either theme +- **THEN** text, navigation, and code blocks remain readable +- **AND** the shell styling does not obscure canonical links or content hierarchy + diff --git a/openspec/specs/debug-logging/spec.md b/openspec/specs/debug-logging/spec.md index c2d5762e..a6ed8be9 100644 --- a/openspec/specs/debug-logging/spec.md +++ b/openspec/specs/debug-logging/spec.md @@ -154,3 +154,25 @@ The system SHALL keep raw internal runtime diagnostics out of normal command out - **WHEN** internal bridge or registry code records non-actionable informational or warning diagnostics through the shared logger - **THEN** those raw logger lines do not appear in the user's console output - **AND** only explicitly formatted user-facing warnings remain visible when the user must take action. + +### Requirement: Bridge logger used in all production source paths +Every production code path in `src/specfact_cli/` SHALL use `get_bridge_logger()` from `specfact_cli.common` for all diagnostic output, replacing any remaining `print()` builtin calls. + +#### Scenario: Adapter module writes diagnostic output via bridge logger +- **WHEN** an adapter module (e.g., `adapters/ado.py`, `adapters/github.py`) performs a network call or state change +- **THEN** diagnostic messages are written via `logger = get_bridge_logger(__name__)` and `logger.debug(...)` / `logger.info(...)` +- **AND** no `print()` call appears in the adapter module + +#### Scenario: Sync module writes diagnostic output via bridge logger +- **WHEN** `sync/bridge_sync.py` or `sync/spec_to_code.py` processes a file +- **THEN** all progress and error messages are routed through `get_bridge_logger(__name__)` +- **AND** no `print()` call appears in the sync module + +### Requirement: Script-layer logging uses stdlib or Rich, not print() +Scripts in `scripts/` and `tools/` that run as standalone CLI programs SHALL use `logging.getLogger(__name__)` with a `StreamHandler` for progress output, or `rich.console.Console()` for formatted terminal output. The stdlib `print()` builtin SHALL NOT be used. + +#### Scenario: Standalone script writes progress via logging +- **WHEN** a script in `scripts/` needs to write a status message to stdout +- **THEN** it calls `logging.getLogger(__name__).info(...)` or `console.print(...)` from a Rich Console instance +- **AND** semgrep `print-in-src` reports zero findings for that script + diff --git a/openspec/specs/devops-sync/spec.md b/openspec/specs/devops-sync/spec.md index 962b4dfc..b2e85c73 100644 --- a/openspec/specs/devops-sync/spec.md +++ b/openspec/specs/devops-sync/spec.md @@ -609,18 +609,29 @@ The system SHALL detect code changes related to change proposals and add progres The system SHALL support Azure DevOps work items as a backlog adapter in the DevOps sync workflow. -#### Scenario: Export-only sync to ADO +#### Scenario: Selective ADO import preserves native payload for proposal import -- **WHEN** the user runs `specfact sync bridge --adapter ado --mode export-only` -- **THEN** change proposals are exported to ADO work items -- **AND** no ADO import operations are attempted +- **GIVEN** a user runs `specfact project sync bridge --adapter ado --mode bidirectional --backlog-ids 123456` +- **WHEN** bridge sync fetches that single ADO work item for import as an OpenSpec change proposal +- **THEN** the adapter returns the provider-native work item payload with a populated `fields` object +- **AND** the payload may include convenience keys such as `title`, `state`, or `description` without removing the native `fields` structure +- **AND** proposal import does not fail for a valid work item with `ADO work item must have fields` -#### Scenario: Bidirectional sync with ADO +#### Scenario: Selective ADO import derives a human-readable change ID when metadata is absent -- **WHEN** the user runs `specfact sync bridge --adapter ado --mode bidirectional` -- **THEN** change proposals are exported to ADO work items -- **AND** ADO work items are imported as OpenSpec change proposals -- **AND** status synchronization is applied in both directions +- **GIVEN** an imported ADO work item has no existing OpenSpec change ID embedded in its description or comments +- **AND** the work item title is `Selective import keeps ADO payload` +- **WHEN** the adapter generates the OpenSpec change proposal during import +- **THEN** the resulting change ID is derived from the title as kebab-case +- **AND** the work item numeric ID remains in source tracking metadata instead of becoming the entire change name + +#### Scenario: Duplicate title slug appends deterministic source suffix + +- **GIVEN** a title-derived slug already exists in `openspec/changes/` +- **AND** another imported ADO work item with ID `123456` resolves to the same title slug +- **WHEN** the second proposal is created +- **THEN** the final change ID keeps the readable title slug and appends a deterministic suffix such as `-123456` +- **AND** the system does not fall back to using only the raw numeric work item ID as the change name ### Requirement: Azure DevOps Sync Configuration diff --git a/openspec/specs/docs-command-validation/spec.md b/openspec/specs/docs-command-validation/spec.md new file mode 100644 index 00000000..2ca6372a --- /dev/null +++ b/openspec/specs/docs-command-validation/spec.md @@ -0,0 +1,25 @@ +# docs-command-validation Specification + +## Purpose +TBD - created by archiving change docs-12-docs-validation-ci. Update Purpose after archive. +## Requirements +### Requirement: Docs command examples resolve to a valid CLI path + +Documentation under `docs/` SHALL include `specfact …` examples in fenced code blocks only when some prefix of the command tokens matches a command path that accepts `--help` in the current CLI (or is a bundle-only group that reports “not installed” when bundles are absent). + +#### Scenario: CI runs command validation on docs changes + +- **WHEN** the docs-review workflow runs on a branch that touches docs or validation scripts +- **THEN** it executes `hatch run check-docs-commands` +- **AND** the step fails the job when an example cannot be resolved to a valid command path + +### Requirement: Historical migration docs are excluded from strict command parity + +Content under `docs/migration/` and other explicitly listed illustrative pages MAY retain historical or placeholder command lines that no longer exist in the CLI; those paths SHALL be excluded from automated command validation so the check targets current user-facing docs. + +#### Scenario: Migration pages are skipped + +- **WHEN** `check-docs-commands` scans `docs/` +- **THEN** it skips `docs/migration/**` and other configured exclusions +- **AND** it does not fail on removed commands documented only for historical context + diff --git a/openspec/specs/docs-cross-site-link-check/spec.md b/openspec/specs/docs-cross-site-link-check/spec.md new file mode 100644 index 00000000..522074ae --- /dev/null +++ b/openspec/specs/docs-cross-site-link-check/spec.md @@ -0,0 +1,25 @@ +# docs-cross-site-link-check Specification + +## Purpose +TBD - created by archiving change docs-12-docs-validation-ci. Update Purpose after archive. +## Requirements +### Requirement: Cross-site modules URLs are discoverable from Markdown + +The repository SHALL provide a script that extracts `https://modules.specfact.io/…` URLs from `docs/**/*.md`, performs HTTP HEAD/GET checks with redirects allowed, and reports source file context for failures. + +#### Scenario: Link check runs in docs-review with warn-only mode + +- **WHEN** the docs-review workflow runs +- **THEN** it executes `hatch run check-cross-site-links --warn-only` +- **AND** failures are printed but do not fail the job while the live site may lag content deploys + +### Requirement: Handoff map URLs MUST be verifiable with opt-in live checks + +The handoff migration map SHALL be covered by opt-in HTTP tests that verify each listed modules URL is reachable when `SPECFACT_RUN_HANDOFF_URL_CHECK=1`; the default test run SHALL skip those checks to avoid flaky network or deploy lag in CI. + +#### Scenario: Opt-in network test + +- **WHEN** a maintainer sets `SPECFACT_RUN_HANDOFF_URL_CHECK=1` +- **THEN** pytest runs the handoff map URL reachability test against production +- **AND** the default CI run skips that test to avoid flaky or lagging deploy noise + diff --git a/openspec/specs/docs-review-gate/spec.md b/openspec/specs/docs-review-gate/spec.md new file mode 100644 index 00000000..e7530f97 --- /dev/null +++ b/openspec/specs/docs-review-gate/spec.md @@ -0,0 +1,41 @@ +# docs-review-gate Specification + +## Purpose +TBD - created by archiving change docs-04-docs-review-gate-and-link-integrity. Update Purpose after archive. +## Requirements +### Requirement: Docs review validates published route integrity + +The docs review gate SHALL derive the published route for authored docs pages from Jekyll front matter and site defaults, and SHALL fail when an internal docs link points to a route that is not published by the current docs source tree. + +#### Scenario: Sidebar or landing page links point to an unpublished route + +- **WHEN** the docs review gate evaluates links from `docs/index.md` or `docs/_layouts/default.html` +- **THEN** every internal docs route resolves to exactly one published docs page +- **AND** the failure output identifies the authored source and the unresolved route when a link is broken + +#### Scenario: Authored markdown links drift from the published permalink + +- **WHEN** the docs review gate evaluates internal Markdown links inside published docs pages +- **THEN** links to docs pages resolve by published route rather than only by source-file existence +- **AND** a page with a mismatched permalink fails validation even if the Markdown file still exists on disk + +### Requirement: Docs review validates required front matter for published docs targets + +The docs review gate SHALL fail when a published docs page that is linked from navigation or another authored docs page is missing required front matter fields needed for publishing and navigation: `layout`, `title`, and `permalink`. + +#### Scenario: Linked docs page is missing required metadata + +- **WHEN** the docs review gate evaluates a navigation-linked or authored-link target page +- **THEN** the page must declare `layout`, `title`, and `permalink` in front matter +- **AND** the failure output identifies the page and the missing keys + +### Requirement: Docs-only pull requests run a dedicated docs review workflow + +A dedicated docs review workflow SHALL run the docs review gate for pull requests or pushes that change docs or Markdown content, even when no Python source files changed. + +#### Scenario: Docs-only change triggers docs review validation + +- **WHEN** a pull request changes only `docs/**` or Markdown files +- **THEN** the dedicated docs review workflow runs the targeted docs review suite +- **AND** docs validation does not wait for the full code-oriented PR orchestrator to complete + diff --git a/openspec/specs/documentation-alignment/spec.md b/openspec/specs/documentation-alignment/spec.md index d31e6319..cf1469a4 100644 --- a/openspec/specs/documentation-alignment/spec.md +++ b/openspec/specs/documentation-alignment/spec.md @@ -94,21 +94,29 @@ Any stated performance or timing in the docs SHALL reflect current benchmarks or ### Requirement: Live docs reflect lean-core and grouped bundle command topology -The live authored documentation set SHALL use command examples and migration guidance that match the currently shipped core and bundle command groups, and SHALL NOT present removed or transitional command families as current syntax. +The live authored documentation set SHALL use command examples and migration guidance that match the currently shipped core and bundle command groups, and SHALL NOT present removed or transitional command families as current syntax. The core docs site SHALL focus exclusively on core platform concerns and SHALL redirect module-specific workflow content to modules.specfact.io. -#### Scenario: Reader checks command examples and navigation +#### Scenario: Core docs site excludes module-specific workflow content -- **WHEN** a reader follows command examples in README or published docs -- **THEN** core commands are shown as always available from `specfact-cli` -- **AND** bundle commands are shown through grouped command paths and marketplace installation context -- **AND** the top-level docs navigation exposes clear entry points for `Docs Home`, `Core CLI`, and `Modules`. +- **GIVEN** the docs.specfact.io landing page (index.md) +- **WHEN** a reader arrives at the docs home +- **THEN** the page clearly separates core platform concerns from module-specific workflows +- **AND** provides direct links to modules.specfact.io for bundle-specific guidance -#### Scenario: Reader copies a documented command after the split +#### Scenario: Core site landing page delineates core vs modules -- **WHEN** a reader copies a command from `README.md` or authored docs under `docs/` -- **THEN** the command path matches a currently shipped surface from the active CLI release -- **AND** removed or transitional syntax such as `specfact project plan ...`, `specfact project import from-bridge ...`, `specfact backlog policy ...`, or retired `specfact spec ...` subgroup trees is replaced, removed, or clearly labeled as historical context -- **AND** command examples route readers through the correct current group for that workflow area (`backlog`, `code`, `govern`, `project`, or `spec`) +- **GIVEN** the docs.specfact.io landing page (index.md) +- **WHEN** a reader arrives at the docs home +- **THEN** the page clearly separates core platform concerns from module-specific workflows +- **AND** provides direct links to modules.specfact.io for bundle-specific guidance + +#### Scenario: Getting Started section focuses on platform bootstrap + +- **GIVEN** the Getting Started section of the core docs +- **WHEN** a new user follows the getting started path +- **THEN** it covers installation, quickstart, and profiles/IDE setup +- **AND** does NOT include module-specific tutorials as inline content +- **AND** links to modules.specfact.io for module workflow tutorials ### Requirement: Marketplace guidance is discoverable and non-duplicative @@ -158,3 +166,102 @@ Authored links from `specfact-cli` docs to `https://modules.specfact.io/...` SHA - **THEN** the path segment matches the target file’s `permalink` in `specfact-cli-modules` or the URL contract reference - **AND** contributors can discover rules from `docs/reference/documentation-url-contract.md` on the core site +### Requirement: Navigation-owned docs links match published permalinks + +The docs landing page and sidebar navigation SHALL link to the actual published permalinks for their target pages, and SHALL NOT assume a section-prefixed route when the page publishes elsewhere. + +#### Scenario: Reader opens a navigation-linked reference page + +- **WHEN** a reader selects a reference or guide link from `docs/index.md` or `docs/_layouts/default.html` +- **THEN** the route resolves on `docs.specfact.io` +- **AND** the link target matches the page permalink declared in the authored docs source + +### Requirement: Broken published docs routes are corrected in authored source + +When docs review identifies a broken published route caused by authored permalink drift, the authored page or link source SHALL be corrected in the same remediation change so the published docs site remains internally consistent. + +#### Scenario: Existing docs page has a mismatched permalink + +- **WHEN** an authored docs page exists but the linked published route does not resolve because the page permalink differs +- **THEN** the remediation updates the authored permalink or the authored link source to restore route integrity +- **AND** the corrected route remains covered by docs review validation + +### Requirement: Core handoff pages are thin summaries with a canonical modules link + +Core docs pages that previously duplicated module-owned guides SHALL contain only a short summary, prerequisites, and a prominent link to the canonical URL on `modules.specfact.io` (per `permalink` in `specfact-cli-modules`), not the full guide body. + +#### Scenario: Handoff page structure + +- **WHEN** a reader opens a converted handoff page on `docs.specfact.io` +- **THEN** the page includes a brief summary of the topic +- **AND** it includes a prerequisites note +- **AND** it includes a prominent link to the full guide on the canonical modules docs site +- **AND** it does not include the duplicated long-form guide content owned by modules + +### Requirement: Legacy URLs remain reachable + +Handoff pages that previously published under alternate paths SHALL preserve `redirect_from` entries so old bookmarks do not 404. + +#### Scenario: Redirect metadata preserved where applicable + +- **WHEN** a handoff page had `redirect_from` for legacy paths +- **THEN** those entries remain in front matter after conversion +- **AND** the published URL still serves the thin handoff page + +### Requirement: Canonical link targets match modules permalinks + +Each converted page’s canonical link SHALL match the modules documentation `permalink` for that topic (which may be `/bundles/.../`, `/guides/.../`, `/integrations/.../`, or a root path), not an assumed mirror of the core `/guides//` path. + +#### Scenario: URL contract compliance + +- **WHEN** authors map core handoff pages to modules URLs +- **THEN** they use the checklist and `documentation-url-contract` rules +- **AND** each link targets the verified modules canonical URL for that guide + +### Requirement: Entry-point messaging hierarchy is documented + +Contributor-facing documentation SHALL define the required messaging hierarchy for first-contact +surfaces so README and homepage edits preserve the same structure over time. + +#### Scenario: Contributor updates an entry-point page + +- **WHEN** a contributor edits `README.md`, `docs/index.md`, or other designated entry-point copy +- **THEN** the guidance SHALL require them to preserve the ordering of: + - product identity + - why it exists + - user value + - how to start + - deeper topology and branching guidance +- **AND** the guidance SHALL define validation/alignment as the product core, with “keep backlog, + specs, tests, and code in sync” expressed as the user-visible result + +### Requirement: Cross-site handoff copy stays aligned + +Documentation alignment rules SHALL require core-docs and modules-docs entry points to describe the +same ownership split and onboarding handoff. + +#### Scenario: Contributor edits core or modules landing copy + +- **WHEN** a contributor updates landing-page copy that references `docs.specfact.io` or + `modules.specfact.io` +- **THEN** the wording SHALL preserve the same explanation of what belongs to the core docs versus + the modules docs +- **AND** cross-site links SHALL direct users to the intended next step rather than only the raw site + URL + +### Requirement: First-contact copy encodes the key user questions + +Contributor guidance SHALL require entry-point copy to answer the key first-contact questions +explicitly enough that maintainers can review the page against them. + +#### Scenario: Maintainer reviews a rewritten entry-point page + +- **WHEN** a maintainer reviews changes to an entry-point page +- **THEN** they SHALL be able to verify that the page clearly answers: + - what SpecFact is + - why it exists + - why a user should use it + - what the user gets + - how the user gets started +- **AND** the page SHALL not bury those answers underneath topology or implementation details + diff --git a/openspec/specs/dogfood-self-review/spec.md b/openspec/specs/dogfood-self-review/spec.md new file mode 100644 index 00000000..ed991848 --- /dev/null +++ b/openspec/specs/dogfood-self-review/spec.md @@ -0,0 +1,97 @@ +# dogfood-self-review Specification + +## Purpose +TBD - created by archiving change code-review-zero-findings. Update Purpose after archive. +## Requirements +### Requirement: Self-review policy — specfact-cli runs specfact review on itself +The specfact-cli repository SHALL be subject to `specfact review` as a first-class CI gate, enforcing the same zero-finding standard we recommend to customers. + +#### Scenario: Review run on own repo produces zero findings +- **WHEN** `specfact review` is executed against the specfact-cli repository root +- **THEN** `overall_verdict` is `PASS` +- **AND** the findings array is empty +- **AND** the process exits with code 0 + +#### Scenario: Review run failure blocks CI +- **WHEN** `specfact review` exits with code non-zero on a PR targeting `dev` or `main` +- **THEN** the CI pipeline marks the PR check as failed +- **AND** the PR cannot be merged until findings are resolved + +#### Scenario: Review result is machine-readable +- **WHEN** `specfact review --format json` is run in CI +- **THEN** a JSON report is written to `.specfact/code-review.json` +- **AND** the report schema_version is `1.0` +- **AND** `overall_verdict`, `score`, `findings`, and `ci_exit_code` fields are present + +#### Scenario: Expanded clean-code categories stay at zero findings +- **GIVEN** the expanded clean-code pack is available from the review module +- **WHEN** `specfact review` runs against the specfact-cli repository root with clean-code categories enabled +- **THEN** categories `naming`, `kiss`, `yagni`, `dry`, and `solid` each report zero findings +- **AND** the zero-finding proof is recorded in `TDD_EVIDENCE.md` + +### Requirement: Type-safe codebase — zero basedpyright findings in strict mode +All public API class members and function signatures in `src/specfact_cli/` SHALL be explicitly typed so that `basedpyright` strict mode reports zero `reportUnknownMemberType`, `reportAttributeAccessIssue`, and `reportUnsupportedDunderAll` findings. + +#### Scenario: basedpyright strict mode passes on src/ +- **WHEN** `hatch run type-check` is executed +- **THEN** basedpyright reports zero errors and zero warnings for files under `src/specfact_cli/` + +#### Scenario: Untyped class member introduced in PR fails CI +- **WHEN** a PR introduces a class member without a type annotation +- **THEN** `hatch run type-check` exits non-zero +- **AND** CI marks the type-check step as failed + +#### Scenario: TypedDict used for structured dict shapes +- **WHEN** a function accepts or returns a dict with a known schema +- **THEN** a `TypedDict` or Pydantic model is used rather than `dict[str, Any]` +- **AND** basedpyright infers the member types without `reportUnknownMemberType` + +### Requirement: Print-free source — all production logging via bridge logger +No `print()` builtin calls SHALL appear in files under `src/specfact_cli/`, `scripts/`, or `tools/`, as detected by the semgrep `print-in-src` rule. + +#### Scenario: Logging call replaces print in adapter layer +- **WHEN** `get_bridge_logger()` is called in an adapter module (e.g., `adapters/ado.py`) +- **THEN** structured log messages are routed to the debug log file when `--debug` is active +- **AND** no `print()` call remains in the file +- **AND** semgrep `print-in-src` reports zero findings for that file + +#### Scenario: Script-layer progress output uses Rich console or stdlib logging +- **WHEN** a script in `scripts/` or `tools/` needs to write progress to stdout +- **THEN** it uses `rich.console.Console().print()` or `logging.getLogger(__name__)`, not the stdlib `print` builtin +- **AND** semgrep `print-in-src` reports zero findings for that file + +### Requirement: Full contract coverage — all public APIs carry icontract decorators +Every public function (non-underscore-prefixed) in `src/specfact_cli/` SHALL have at least one `@require` or `@ensure` decorator from icontract, and a `@beartype` decorator for runtime type enforcement. + +#### Scenario: Public function without @require fails contract_runner check +- **WHEN** `contract_runner` scans a file with a public function lacking `@require`/`@ensure` +- **THEN** a `MISSING_ICONTRACT` finding is produced + +#### Scenario: Decorated public function produces no missing-contract finding +- **WHEN** a public function has both `@require` (or `@ensure`) and `@beartype` +- **THEN** `contract_runner` produces zero `MISSING_ICONTRACT` findings for that function + +#### Scenario: Minimal meaningful contract per function +- **WHEN** a `@require` precondition is added to a public function +- **THEN** the precondition checks a domain-meaningful invariant (e.g., path exists, non-empty string, valid enum) +- **AND** the precondition is NOT a trivial `lambda x: x is not None` that merely restates the type + +#### Scenario: Utility contract exploration handles pathological strings gracefully +- **WHEN** CrossHair or unit tests exercise utility helpers with pathological string inputs such as + control characters or malformed package names +- **THEN** the helpers SHALL return a safe fallback value instead of raising unexpected exceptions +- **AND** `hatch run contract-test` SHALL not report uncaught exceptions for those utility paths + +### Requirement: Complexity budget — no function exceeds CC15 +No function in `src/specfact_cli/`, `scripts/`, or `tools/` SHALL have cyclomatic complexity >=16, as measured by radon. + +#### Scenario: High-complexity function split into helpers passes complexity check +- **WHEN** a function with CC>=16 is refactored into a top-level function and one or more private helpers +- **THEN** `hatch run lint` (radon check) reports no CC>=16 findings for that function +- **AND** each extracted helper has CC<10 + +#### Scenario: New code written during this change stays below threshold +- **WHEN** any new function is introduced during this change +- **THEN** its cyclomatic complexity is <10 as measured by radon +- **AND** no CC>=13 warning is raised for the new function + diff --git a/openspec/specs/entrypoint-onboarding/spec.md b/openspec/specs/entrypoint-onboarding/spec.md new file mode 100644 index 00000000..b3c39d27 --- /dev/null +++ b/openspec/specs/entrypoint-onboarding/spec.md @@ -0,0 +1,63 @@ +# entrypoint-onboarding Specification + +## Purpose +TBD - created by archiving change docs-14-first-contact-story-and-onboarding. Update Purpose after archive. +## Requirements +### Requirement: One primary fast-start path + +The central entry points SHALL provide one primary “start here now” path before branching into more +specialized persona or workflow guidance. + +#### Scenario: User wants to try SpecFact immediately + +- **WHEN** a first-time visitor reaches the primary getting-started section +- **THEN** the page SHALL provide one recommended install-and-first-run path +- **AND** that path SHALL appear before alternative personas, workflow branches, or topology details +- **AND** the path SHALL make the first value explicit rather than only listing commands + +### Requirement: Choose-your-path guidance follows the first-run path + +After the primary fast-start path, entry points SHALL route users into the most relevant next step +for their intent. + +#### Scenario: User needs the right next path + +- **WHEN** the user completes or reviews the first-run path +- **THEN** the entry point SHALL offer clear next-step options for at least: + - greenfield or AI-assisted development that needs stronger validation + - brownfield or legacy code understanding and reverse-engineering + - backlog/spec/code alignment workflows +- **AND** each option SHALL describe the user outcome, not just the internal command group + +### Requirement: Brownfield path explains the spec-first handoff + +Brownfield onboarding SHALL explain that SpecFact helps extract trustworthy understanding from +existing systems and feed that understanding into spec-first workflows. + +#### Scenario: User evaluates the brownfield path + +- **WHEN** a user reads the brownfield or existing-codebase onboarding path +- **THEN** the path SHALL explain that SpecFact analyzes the codebase and sidecar context to produce + structured insight +- **AND** it SHALL explain that this insight can be handed into spec-first tools such as OpenSpec or + Spec-Kit to create accurate specs and reduce drift + +### Requirement: Core-versus-modules handoff is explicit + +The entry points SHALL explain that `docs.specfact.io` is the default starting point and +`modules.specfact.io` is the deeper module- and workflow-specific documentation surface. + +#### Scenario: New user lands on modules docs + +- **WHEN** a first-time visitor reaches `modules.specfact.io` +- **THEN** the page SHALL explain that module docs are the deeper workflow layer +- **AND** it SHALL direct users back to the core docs if they still need orientation or the initial + getting-started flow +- **AND** it SHALL clarify that bundle-deep workflows build on the same validation/alignment story + +#### Scenario: Core docs hand off to module-deep guidance + +- **WHEN** a user outgrows the core landing guidance and needs workflow- or bundle-specific help +- **THEN** the core docs SHALL provide a clear, explicit handoff to `modules.specfact.io` +- **AND** the handoff SHALL explain what extra value the modules docs provide + diff --git a/openspec/specs/first-contact-story/spec.md b/openspec/specs/first-contact-story/spec.md new file mode 100644 index 00000000..93aedb63 --- /dev/null +++ b/openspec/specs/first-contact-story/spec.md @@ -0,0 +1,91 @@ +# first-contact-story Specification + +## Purpose +TBD - created by archiving change docs-14-first-contact-story-and-onboarding. Update Purpose after archive. +## Requirements +### Requirement: Canonical first-contact product story + +The repository and documentation entry points SHALL present one canonical product story that answers +the first-contact questions in a consistent order: + +- what SpecFact is +- why it exists +- why a user should care +- what value the user gets +- how to start + +The canonical answer to “what is SpecFact?” SHALL define it as a validation and alignment layer for +software delivery, not merely as a collection of commands or integrations. + +#### Scenario: User reads the README hero + +- **WHEN** a first-time visitor lands on `README.md` +- **THEN** the page SHALL answer “what is SpecFact?” in one concise identity statement +- **AND** the answer SHALL appear before topology, module ownership, or migration detail +- **AND** the identity statement SHALL make validation/alignment central and present “keep things in + sync” as the outcome rather than the only definition + +#### Scenario: User compares repo and docs entry points + +- **WHEN** a user reads the repo README and the core docs homepage +- **THEN** both entry points SHALL describe the same core product identity +- **AND** they SHALL not give conflicting first impressions about whether SpecFact is primarily a CLI, + a module platform, an AI tool, or a backlog tool + +### Requirement: First-contact story explains the four product pressures + +The first-contact story SHALL explain why SpecFact exists by grounding it in the main delivery +pressures it addresses. + +#### Scenario: User asks why SpecFact exists + +- **WHEN** a first-time visitor reads the “why” section of the README or core docs landing page +- **THEN** the page SHALL explain that SpecFact addresses: + + - AI-assisted or vibe-coded changes that need stronger validation + - brownfield systems that need reverse-engineered understanding + - backlog/spec/code drift that causes “I wanted X but got Y” + - team and enterprise policy inconsistency across developers and CI +- **AND** the wording SHALL present those pressures as reasons the product exists, not as an + unstructured feature list + +### Requirement: Headline and proof-point separation + +First-contact surfaces SHALL keep the primary identity statement separate from supporting proof +points such as greenfield/brownfield support, SDD/TDD/contracts, AI-copilot compatibility, +reverse-engineering support, and module extensibility. + +#### Scenario: User scans the first screen + +- **WHEN** a user scans the first screen of the README or docs homepage +- **THEN** the primary message SHALL fit in a short headline/subheadline structure +- **AND** secondary capability claims SHALL appear as proof points rather than headline overload + +### Requirement: Future enterprise direction reinforces seriousness without narrowing adoption + +First-contact messaging SHALL describe centralized policy management as a scale-up path for teams and +enterprises without implying that SpecFact only makes sense for large organizations. + +#### Scenario: User scans enterprise and governance messaging + +- **WHEN** a visitor reads first-contact copy that mentions policy enforcement or future account/back-end support +- **THEN** the copy SHALL present that capability as an extension of the same validation/alignment story +- **AND** it SHALL preserve the message that solo developers and smaller teams can adopt SpecFact immediately + +### Requirement: Repo metadata reinforces the same story + +GitHub-facing repository metadata SHALL reinforce the same first-contact story used in the README and +docs landing pages. + +#### Scenario: User sees the repository before opening the README + +- **WHEN** a visitor sees the repository description, topics, badges, and other above-the-fold repo + cues +- **THEN** those cues SHALL reinforce the same core identity as the README hero +- **AND** they SHALL not emphasize internal topology ahead of user value + +Cross-repo traceability note: `modules.specfact.io` and the +`nold-ai/specfact-cli-modules` `docs/index.md` SHALL either present the same first-contact story or +provide an explicit handoff to the core docs. See `documentation-alignment/spec.md` for ownership +and cross-site wording guidance. + diff --git a/openspec/specs/init-ide-prompt-source-selection/spec.md b/openspec/specs/init-ide-prompt-source-selection/spec.md new file mode 100644 index 00000000..4d2d4459 --- /dev/null +++ b/openspec/specs/init-ide-prompt-source-selection/spec.md @@ -0,0 +1,70 @@ +# init-ide-prompt-source-selection Specification + +## Purpose +TBD - created by archiving change init-ide-prompt-source-selection. Update Purpose after archive. +## Requirements +### Requirement: Init IDE Must Export All Prompt Sources By Default + +`specfact init ide` SHALL export all available prompt sources by default. + +#### Scenario: Default export includes core and installed modules across effective roots +- **WHEN** a user runs `specfact init ide` without restricting prompt sources +- **THEN** prompt export includes core prompts +- **AND** prompt export includes prompts from installed and enabled modules that provide prompt resources +- **AND** the catalog is built from the effective built-in, project, user, and configured custom module roots for that repository context. + +### Requirement: Init IDE Must Discover Sources From Installed Module Roots Only + +`specfact init ide` SHALL discover prompt and related module-owned resources from installed module roots and packaged resource directories. It SHALL not fetch module archives or treat the modules source repository as a runtime extraction source. + +#### Scenario: Installed project-scope bundle contributes prompt resources +- **WHEN** a repository has an installed module under `/.specfact/modules` +- **THEN** `specfact init ide` can discover that module's packaged prompt resources for export in that repository. + +#### Scenario: Installed user-scope bundle contributes prompt resources +- **WHEN** a user has installed a module under `~/.specfact/modules` +- **AND** no overriding project-scope copy shadows it +- **THEN** `specfact init ide` can discover that module's packaged prompt resources for export. + +#### Scenario: Missing selected source does not trigger install work +- **WHEN** a selected prompt source is not installed or does not expose the required packaged resources +- **THEN** `specfact init ide` fails or warns with actionable guidance +- **AND** the guidance names the relevant scope and install/bootstrap command such as `specfact module init --scope project` or `specfact module install --scope user` +- **AND** the command does not download, install, or extract the module itself. + +### Requirement: Init IDE Must Support Interactive Prompt Source Selection + +Interactive `specfact init ide` SHALL allow users to choose prompt sources from installed options. + +#### Scenario: Interactive picker shows available sources +- **WHEN** `specfact init ide` runs in interactive mode +- **THEN** it shows a multi-select source picker containing `core` and installed module ids with prompt resources +- **AND** the selected sources determine which prompt resources are copied. + +### Requirement: Init IDE Must Support Non-Interactive Prompt Source Selection + +Non-interactive `specfact init ide` SHALL accept a comma-separated prompt source selector. + +#### Scenario: Non-interactive selector accepts core and module ids +- **WHEN** a user runs `specfact init ide --prompts core,nold-ai/specfact-backlog` +- **THEN** core prompts and the selected installed module prompts are copied +- **AND** unrelated prompt sources are not copied. + +#### Scenario: Invalid or unavailable module source is rejected +- **WHEN** a user passes a prompt source token that is not `all`, not `core`, and not an installed module id with prompt resources +- **THEN** the command fails with actionable guidance describing the invalid token and the available prompt sources. + +### Requirement: Exported Prompt Files Must Be IDE-Discoverable With Deterministic Single Sourcing + +Exported prompts for VS Code / Copilot (under ``.github/prompts/``) and other multi-source IDE targets SHALL use a **flat** layout (no per-source subfolders) so editors and agents can discover ``specfact*.prompt.md`` (or equivalent) at the export root. + +#### Scenario: Core defers to modules on overlapping template basenames +- **WHEN** `core` and one or more installed modules expose the same source filename (e.g. ``specfact.01-import.md``) +- **THEN** the prompt catalog SHALL list that basename only under the owning module source +- **AND** `core` SHALL NOT duplicate that basename so exports are single-sourced. + +#### Scenario: Multiple module sources expose the same basename +- **WHEN** two installed modules expose the same template basename +- **THEN** the merged export uses a deterministic last-wins rule by sorted source id (later id overwrites earlier) +- **AND** the flat export contains exactly one file per output basename. + diff --git a/openspec/specs/module-owned-ide-prompts/spec.md b/openspec/specs/module-owned-ide-prompts/spec.md new file mode 100644 index 00000000..739bc3d5 --- /dev/null +++ b/openspec/specs/module-owned-ide-prompts/spec.md @@ -0,0 +1,44 @@ +# module-owned-ide-prompts Specification + +## Purpose +TBD - created by archiving change packaging-02-cross-platform-runtime-and-module-resources. Update Purpose after archive. +## Requirements +### Requirement: IDE prompt export SHALL use installed module resources +`specfact init ide` SHALL discover prompt templates from installed module packages and their packaged resource directories. The export flow SHALL not depend on workflow prompt files stored under the core CLI package for bundle-owned commands. + +#### Scenario: Installed bundle contributes prompt resources +- **WHEN** an installed module exposes packaged prompt resources for IDE export +- **THEN** `specfact init ide` discovers that module's prompt directory from the installed module location +- **AND** copies the prompt files from that module-owned resource path into the selected IDE folder + +#### Scenario: Core package does not masquerade as owner of bundle prompts +- **WHEN** workflow prompts exist only for bundle/module-owned commands +- **THEN** the export catalog excludes equivalent core-owned fallback prompt files +- **AND** prompt provenance remains attributable to the owning module + +### Requirement: Missing prompt assets SHALL fail clearly +If a selected or installed module is expected to provide prompt resources but no packaged prompt directory is available, `specfact init ide` SHALL report an actionable error or warning that identifies the owning module and the missing resource path. + +#### Scenario: Selected module has no packaged prompt directory +- **WHEN** `specfact init ide` evaluates an installed module that should contribute prompts but its packaged prompt resource directory is absent +- **THEN** the command reports which module is incomplete +- **AND** the message explains that prompt resources must ship with the owning module package + +#### Scenario: Prompt discovery feeds later source selection +- **WHEN** the prompt export catalog is built for a repository with multiple installed modules +- **THEN** the discovered prompt sources are available for later interactive or non-interactive source selection features +- **AND** the catalog preserves module-level provenance for each exported prompt + +### Requirement: Core init flows SHALL use installed module-owned template resources +When a setup or install flow needs a non-prompt resource that is owned by an extracted bundle, the core CLI SHALL resolve that asset from the installed bundle package instead of from a core-owned fallback directory. + +#### Scenario: Backlog field mapping templates resolve from installed backlog bundle +- **WHEN** a core init or setup flow needs backlog field mapping templates +- **THEN** the CLI resolves those templates from the installed backlog bundle resource path +- **AND** the flow does not require a canonical source copy under the core CLI repository + +#### Scenario: Missing module-owned template asset fails clearly +- **WHEN** a required installed bundle resource path for a module-owned template is absent +- **THEN** the CLI reports which bundle-owned asset is missing +- **AND** the message directs the user toward installing or updating the owning bundle + diff --git a/openspec/specs/review-cli-contracts/spec.md b/openspec/specs/review-cli-contracts/spec.md index a0b6a576..bc357e23 100644 --- a/openspec/specs/review-cli-contracts/spec.md +++ b/openspec/specs/review-cli-contracts/spec.md @@ -26,3 +26,21 @@ The system SHALL provide cli-val-01 compliant scenario YAML files for `specfact - **WHEN** validated against the cli-val-01 behavior contract schema - **THEN** no validation errors are reported +### Requirement: Review CLI commands carry icontract and beartype decorators +All public command functions in the review module (`specfact code review run`, `ledger`, `rules`) SHALL have `@require` / `@ensure` decorators (icontract) and `@beartype` on their signatures, consistent with the project-wide contract-first standard. + +#### Scenario: review run command has precondition on repo_path +- **WHEN** `specfact code review run` is invoked with an invalid `repo_path` +- **THEN** an icontract `ViolationError` is raised before any tool runner is invoked +- **AND** the error message references the violated precondition + +#### Scenario: review CLI contracts are consistent with typed signatures +- **WHEN** `hatch run contract-test` is executed after type annotations are applied to the review CLI module +- **THEN** `contract_runner` reports zero `MISSING_ICONTRACT` findings for review command functions +- **AND** `basedpyright` reports zero type errors for the review CLI module + +#### Scenario: Contract validation scenarios cover review run with CI flag +- **GIVEN** `tests/cli-contracts/specfact-code-review-run.scenarios.yaml` exists +- **WHEN** a scenario exercising `review run --ci` with a clean target is added +- **THEN** the scenario validates exit code 0 and presence of `.specfact/code-review.json` + diff --git a/openspec/specs/review-run-command/spec.md b/openspec/specs/review-run-command/spec.md index 9e9e2e95..bfb28c56 100644 --- a/openspec/specs/review-run-command/spec.md +++ b/openspec/specs/review-run-command/spec.md @@ -4,35 +4,14 @@ TBD - created by archiving change code-review-08-review-run-integration. Update Purpose after archive. ## Requirements ### Requirement: End-to-End specfact code review run Command -The system SHALL provide a fully wired `specfact code review run` command that orchestrates all tool runners and returns a `ReviewReport` with correct exit codes (0=PASS/WARN, 1=BLOCK). - -#### Scenario: Run on clean fixture produces PASS and exit 0 -- **GIVEN** `tests/fixtures/review/clean_module.py` with no violations and passing tests -- **WHEN** `specfact code review run tests/fixtures/review/clean_module.py` is called -- **THEN** `overall_verdict` equals `"PASS"` and exit code is `0` - -#### Scenario: Run on dirty fixture produces BLOCK and exit 1 -- **GIVEN** `tests/fixtures/review/dirty_module.py` with violations and missing test file -- **WHEN** `specfact code review run tests/fixtures/review/dirty_module.py` is called -- **THEN** `overall_verdict` equals `"FAIL"` and exit code is `1` - -#### Scenario: --json outputs valid ReviewReport JSON -- **GIVEN** any set of files -- **WHEN** `specfact code review run --json` is called -- **THEN** stdout contains valid JSON parseable as `ReviewReport` with all governance fields present - -#### Scenario: --score-only prints only reward_delta integer -- **GIVEN** a run with `reward_delta=-5` -- **WHEN** `specfact code review run --score-only` is called -- **THEN** stdout contains exactly `-5` followed by a newline - -#### Scenario: --fix applies ruff autofix then re-runs -- **GIVEN** files with auto-fixable ruff violations -- **WHEN** `specfact code review run --fix` is called -- **THEN** `ruff --fix` is applied and the review runs again on the fixed files - -#### Scenario: No files provided uses git diff HEAD -- **GIVEN** no `FILES` argument is provided -- **WHEN** `specfact code review run` is called -- **THEN** changed files are determined from `git diff HEAD --name-only` and the run proceeds +The `specfact code review run` workflow SHALL support the dogfood self-review proof for the SpecFact CLI repository and emit a governed zero-finding report when remediation is complete. + +#### Scenario: Dogfood self-review on SpecFact CLI reaches zero tracked findings +- **GIVEN** the SpecFact CLI repository under the `code-review-zero-findings` remediation branch +- **AND** the dogfood self-review tests in `tests/unit/specfact_cli/test_dogfood_self_review.py` +- **WHEN** `specfact code review run --scope full --json --out ` is executed in an environment where the `code` bundle is installed +- **THEN** the generated report has `overall_verdict` equal to `"PASS"` +- **AND** the report contains zero findings with rules `reportUnknownMemberType`, `print-in-src`, and `MISSING_ICONTRACT` +- **AND** the report contains zero `clean_code` findings with rules `CC16` or higher +- **AND** the report contains zero findings in category `tool_error` diff --git a/openspec/specs/runtime-portability/spec.md b/openspec/specs/runtime-portability/spec.md new file mode 100644 index 00000000..980122de --- /dev/null +++ b/openspec/specs/runtime-portability/spec.md @@ -0,0 +1,31 @@ +# runtime-portability Specification + +## Purpose +TBD - created by archiving change packaging-02-cross-platform-runtime-and-module-resources. Update Purpose after archive. +## Requirements +### Requirement: CLI output SHALL degrade safely on non-UTF-8 terminals +The SpecFact CLI SHALL render help, startup, and other common command output without raising encoding exceptions on supported Windows, Linux, and macOS terminals. When the active output stream cannot encode configured Unicode glyphs, the CLI SHALL switch to an ASCII-safe fallback for affected symbols instead of crashing. + +#### Scenario: Windows help rendering on a legacy code page +- **WHEN** a user runs a help or startup command in a terminal whose output encoding cannot represent the configured Unicode icons +- **THEN** the CLI completes successfully +- **AND** the rendered output uses encoding-safe fallback symbols for the unsupported glyphs + +#### Scenario: UTF-8 terminal preserves rich symbols +- **WHEN** a user runs the same help or startup command in a UTF-8-capable terminal +- **THEN** the CLI completes successfully +- **AND** the configured rich Unicode symbols remain enabled + +### Requirement: Runtime mismatch diagnostics SHALL be actionable +When automation or programmatic invocation reaches a SpecFact installation whose runtime, module path, or compiled dependencies are incompatible with the calling environment, the CLI SHALL fail with a compatibility error that identifies the failing component and the resolved installation context. + +#### Scenario: External interpreter cannot load installed SpecFact module runtime +- **WHEN** a caller invokes backlog automation from a different interpreter environment than the one hosting the installed SpecFact stack +- **THEN** the CLI fails with a compatibility error instead of a raw low-level import traceback +- **AND** the error reports the unresolved module or compiled dependency +- **AND** the error explains which interpreter or installation boundary must be used + +#### Scenario: Compatible installation resolves without manual path injection +- **WHEN** a caller invokes a supported SpecFact workflow from the interpreter that hosts the installed SpecFact runtime and module resources +- **THEN** the CLI resolves the required runtime and module paths without requiring manual `.specfact/modules/...` injection + diff --git a/openspec/specs/speckit-extension-catalog/spec.md b/openspec/specs/speckit-extension-catalog/spec.md new file mode 100644 index 00000000..2501060e --- /dev/null +++ b/openspec/specs/speckit-extension-catalog/spec.md @@ -0,0 +1,72 @@ +# speckit-extension-catalog Specification + +## Purpose +TBD - created by archiving change speckit-02-v04-adapter-alignment. Update Purpose after archive. +## Requirements +### Requirement: Extension catalog detection + +The system SHALL detect spec-kit extension catalogs in a target repository by scanning for `extensions/catalog.community.json` and `extensions/catalog.core.json` files relative to the repository root or `.specify/` directory. + +#### Scenario: Detect community extension catalog + +- **GIVEN** a repository with `.specify/` directory and `extensions/catalog.community.json` +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** the scanner returns a list of extension metadata objects parsed from the catalog JSON +- **AND** each extension object contains at minimum `name`, `commands`, and `version` fields + +#### Scenario: Detect core extension catalog + +- **GIVEN** a repository with `extensions/catalog.core.json` +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** the scanner parses both core and community catalogs +- **AND** core extensions are included alongside community extensions in the result + +#### Scenario: No extension catalog present + +- **GIVEN** a repository with `.specify/` directory but no `extensions/` directory +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** the scanner returns an empty list +- **AND** no error is raised + +#### Scenario: Malformed extension catalog + +- **GIVEN** a repository with `extensions/catalog.community.json` containing invalid JSON +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** the scanner logs a warning +- **AND** returns an empty list for that catalog +- **AND** does not raise an exception + +### Requirement: Extension command extraction + +The system SHALL extract slash commands provided by each detected extension, making them available in `ToolCapabilities.extension_commands`. + +#### Scenario: Extract commands from extension metadata + +- **GIVEN** a parsed extension catalog with entries containing `commands` arrays +- **WHEN** `SpecKitAdapter.get_capabilities()` processes extension metadata +- **THEN** `ToolCapabilities.extension_commands` contains a dict mapping extension name to its command list +- **AND** each command is a string (e.g., `"/speckit.reconcile.run"`, `"/speckit.sync.detect"`) + +#### Scenario: Extension with no commands + +- **GIVEN** a parsed extension catalog with an entry that has an empty `commands` array +- **WHEN** extension commands are extracted +- **THEN** that extension is included in `ToolCapabilities.extensions` but has an empty command list in `extension_commands` + +### Requirement: Extension ignore support + +The system SHALL respect `.extensionignore` files when reporting active extensions. + +#### Scenario: Extension excluded by extensionignore + +- **GIVEN** a repository with `extensions/catalog.community.json` containing extension "verify" +- **AND** a `.extensionignore` file containing the line "verify" +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** the "verify" extension is excluded from the returned list + +#### Scenario: No extensionignore file + +- **GIVEN** a repository with extensions but no `.extensionignore` file +- **WHEN** `SpecKitScanner.scan_extensions()` is called +- **THEN** all extensions from the catalogs are included in the result + diff --git a/openspec/specs/speckit-version-detection/spec.md b/openspec/specs/speckit-version-detection/spec.md new file mode 100644 index 00000000..b2fe8e68 --- /dev/null +++ b/openspec/specs/speckit-version-detection/spec.md @@ -0,0 +1,83 @@ +# speckit-version-detection Specification + +## Purpose +TBD - created by archiving change speckit-02-v04-adapter-alignment. Update Purpose after archive. +## Requirements +### Requirement: CLI-based version detection + +The system SHALL attempt to detect the installed spec-kit version by invoking the `specify` CLI when available on PATH. + +#### Scenario: CLI available and returns version + +- **GIVEN** the `specify` CLI is installed and available on the system PATH +- **WHEN** `SpecKitAdapter._detect_version_from_cli(repo_path)` is called +- **THEN** the method runs `specify --version` as a subprocess +- **AND** parses the version string from stdout +- **AND** returns the version string (e.g., `"0.4.3"`) +- **AND** sets `ToolCapabilities.detected_version_source` to `"cli"` + +#### Scenario: CLI not available + +- **GIVEN** the `specify` CLI is not installed or not on PATH +- **WHEN** `SpecKitAdapter._detect_version_from_cli(repo_path)` is called +- **THEN** the method returns `None` +- **AND** does not raise an exception +- **AND** the detection falls through to heuristic detection + +#### Scenario: CLI invocation times out + +- **GIVEN** the `specify` CLI is on PATH but hangs or takes longer than 5 seconds +- **WHEN** `SpecKitAdapter._detect_version_from_cli(repo_path)` is called +- **THEN** the subprocess is terminated after the timeout +- **AND** the method returns `None` +- **AND** logs a debug-level warning + +### Requirement: Heuristic version detection + +The system SHALL estimate the spec-kit version from directory structure when CLI detection is unavailable. + +#### Scenario: Presets directory implies version >= 0.3.0 + +- **GIVEN** a repository with `.specify/` and a `presets/` directory +- **AND** CLI-based version detection returned `None` +- **WHEN** `SpecKitAdapter._detect_version_from_heuristics(repo_path)` is called +- **THEN** the method returns `">=0.3.0"` +- **AND** sets `ToolCapabilities.detected_version_source` to `"heuristic"` + +#### Scenario: Extensions directory implies version >= 0.2.0 + +- **GIVEN** a repository with `.specify/` and `extensions/` directory but no `presets/` directory +- **AND** CLI-based version detection returned `None` +- **WHEN** `SpecKitAdapter._detect_version_from_heuristics(repo_path)` is called +- **THEN** the method returns `">=0.2.0"` +- **AND** sets `ToolCapabilities.detected_version_source` to `"heuristic"` + +#### Scenario: Only specify directory implies version >= 0.1.0 + +- **GIVEN** a repository with `.specify/` directory but no `extensions/` or `presets/` +- **AND** CLI-based version detection returned `None` +- **WHEN** `SpecKitAdapter._detect_version_from_heuristics(repo_path)` is called +- **THEN** the method returns `">=0.1.0"` +- **AND** sets `ToolCapabilities.detected_version_source` to `"heuristic"` + +#### Scenario: No version detectable + +- **GIVEN** a repository with only `specs/` at root (classic layout, no `.specify/`) +- **AND** CLI-based version detection returned `None` +- **WHEN** `SpecKitAdapter._detect_version_from_heuristics(repo_path)` is called +- **THEN** the method returns `None` +- **AND** `ToolCapabilities.detected_version_source` remains `None` + +### Requirement: Version detection integration in get_capabilities + +The system SHALL integrate version detection into the existing `SpecKitAdapter.get_capabilities()` flow, trying CLI first then heuristics. + +#### Scenario: Full version detection flow + +- **GIVEN** a spec-kit repository +- **WHEN** `SpecKitAdapter.get_capabilities(repo_path)` is called +- **THEN** the adapter tries CLI detection first +- **AND** if CLI returns `None`, falls back to heuristic detection +- **AND** populates `ToolCapabilities.version` with the result +- **AND** populates `ToolCapabilities.detected_version_source` with the detection method used + diff --git a/openspec/specs/trustworthy-green-checks/spec.md b/openspec/specs/trustworthy-green-checks/spec.md new file mode 100644 index 00000000..d2ef352a --- /dev/null +++ b/openspec/specs/trustworthy-green-checks/spec.md @@ -0,0 +1,109 @@ +# trustworthy-green-checks Specification + +## Purpose +TBD - created by archiving change ci-02-trustworthy-green-checks. Update Purpose after archive. +## Requirements +### Requirement: Required CI jobs fail on required tool failures + +Workflow jobs classified as required merge gates SHALL exit non-zero when their underlying required tool fails, and SHALL NOT suppress the failure behind warn-only shell patterns or broad continue-on-error handling. + +#### Scenario: Required compatibility or contract job encounters a tool failure + +- **WHEN** a required job in `pr-orchestrator.yml` runs a compatibility, contract, lint, or equivalent required validation command +- **AND** the underlying command exits non-zero +- **THEN** the workflow job exits non-zero +- **AND** the GitHub check does not report success for that job + +### Requirement: Advisory jobs are explicit and non-deceptive + +Non-blocking validation jobs SHALL be labeled and documented as advisory so maintainers can distinguish useful warnings from required merge gates. + +#### Scenario: Repository keeps a warn-only validation job + +- **WHEN** a workflow contains a non-blocking validation step or job +- **THEN** the job name or step output clearly marks it as advisory +- **AND** branch protection guidance does not rely on that advisory check as evidence that required validation passed + +### Requirement: Main-bound release PRs only skip tests when parity is provable + +`dev -> main` pull requests SHALL skip the full required validation set only when the workflow can prove the PR head is already-covered by the validated `dev` commit set without additional follow-up commits affecting release safety. + +#### Scenario: Release PR contains follow-up commits after the last validated dev merge tip + +- **WHEN** a `dev -> main` pull request includes additional commits beyond the already-validated merge tip from `dev` +- **THEN** the workflow does not use the fast-path skip +- **AND** the required validation set runs again for the release PR + +### Requirement: Workflow changes trigger mandatory workflow lint validation + +Changes under `.github/workflows/**` SHALL trigger mandatory CI validation for workflow syntax and supported shell/static checks so workflow regressions cannot rely solely on local tooling or bot review. + +#### Scenario: Pull request modifies a GitHub Actions workflow + +- **WHEN** a pull request changes one or more files under `.github/workflows/` +- **THEN** CI runs mandatory workflow validation for those changes +- **AND** a workflow-lint failure blocks the required check from reporting success + +### Requirement: Required branch-protection checks always report on PR head commits + +Any check selected as a required branch-protection gate SHALL emit a success or failure status for +every new pull-request head commit. Required checks SHALL NOT disappear for docs-only or otherwise +out-of-scope follow-up commits because the entire workflow was skipped by top-level `paths` or +`paths-ignore` filtering. + +#### Scenario: Docs-only follow-up push updates an existing pull request + +- **WHEN** a pull request receives a new head commit that only changes docs, markdown, or other + files outside a required validation's relevance scope +- **AND** branch protection still expects the required validation check on the new head SHA +- **THEN** the workflow still triggers and emits a status for that required check +- **AND** the job may exit early with a clear no-op success message +- **AND** GitHub does not leave the PR waiting on a missing required check status + +### Requirement: Required checks use canonical names across workflows + +The repository SHALL standardize on one canonical emitted check name for any logical required gate +that is emitted by more than one workflow or exposed through related dedicated and orchestrated +forms. + +#### Scenario: Signature validation exists in both orchestrator and dedicated workflow form + +- **WHEN** the repository exposes module-signature validation through multiple workflow entry points +- **THEN** the emitted required check names use one canonical spelling/casing per logical gate +- **AND** branch protection guidance does not depend on subtly different names that can drift apart + +### Requirement: Supported local pre-commit installation matches core CI gate semantics + +The repository-supported pre-commit installation path SHALL enforce the same core gate semantics +that CI relies on for changed files, rather than leaving stronger checks only in an optional +wrapper unknown to standard contributors. + +#### Scenario: Contributor installs the documented local hooks + +- **WHEN** a contributor follows the repository-supported hook installation path +- **THEN** staged Python, workflow, Markdown, and module-signature relevant changes receive the documented local gate coverage +- **AND** the contributor is not relying on a weaker default `.pre-commit` path than the one CI and maintainer guidance assume + +### Requirement: Automatic PR review coverage includes both active protected targets + +Automatic repository review configuration SHALL cover pull requests targeting both `dev` and +`main` when both branches are active protected integration targets. + +#### Scenario: Release-forward pull request targets main + +- **WHEN** a pull request targets `main` +- **THEN** the configured automatic review system applies the same target-branch auto-review policy used for `dev` +- **AND** release PRs do not silently lose their default automated review pass + +### Requirement: Merge-blocking and advisory outputs are documented distinctly + +Contributor-facing CI and hook documentation SHALL distinguish required merge gates from advisory +signals so maintainers can interpret a green PR consistently across local hooks, GitHub Actions, +and automated review outputs. + +#### Scenario: Contributor reviews the repository quality-gate guidance + +- **WHEN** a contributor reads the documented pre-commit, CI, or PR-review guidance +- **THEN** the documentation names which checks are merge-blocking required gates +- **AND** the documentation names which outputs remain advisory warnings or review assistance + diff --git a/pyproject.toml b/pyproject.toml index d5fa9c78..8466bb8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "specfact-cli" -version = "0.43.3" +version = "0.44.0" description = "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with validation and contract enforcement for new projects and long-lived codebases." readme = "README.md" requires-python = ">=3.11" diff --git a/setup.py b/setup.py index d9ca5c03..f2d563d2 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ if __name__ == "__main__": _setup = setup( name="specfact-cli", - version="0.43.3", + version="0.44.0", description=( "The swiss knife CLI for agile DevOps teams. Keep backlog, specs, tests, and code in sync with " "validation and contract enforcement for new projects and long-lived codebases." diff --git a/src/specfact_cli/__init__.py b/src/specfact_cli/__init__.py index 6f918cd3..a4a9b8c3 100644 --- a/src/specfact_cli/__init__.py +++ b/src/specfact_cli/__init__.py @@ -45,6 +45,6 @@ def _bootstrap_bundle_paths() -> None: _bootstrap_bundle_paths() -__version__ = "0.43.3" +__version__ = "0.44.0" __all__ = ["__version__"] diff --git a/src/specfact_cli/adapters/speckit.py b/src/specfact_cli/adapters/speckit.py index 2564aecb..23b283ff 100644 --- a/src/specfact_cli/adapters/speckit.py +++ b/src/specfact_cli/adapters/speckit.py @@ -426,7 +426,7 @@ def _resolve_feature_title(self, spec_data: dict[str, Any], spec_path: Path) -> ) if title_match: feature_title = title_match.group(1).strip() - except Exception: + except (OSError, UnicodeDecodeError): pass if not feature_title or feature_title.strip() == "": return "Unknown Feature" diff --git a/src/specfact_cli/agents/analyze_agent.py b/src/specfact_cli/agents/analyze_agent.py index 6cd51add..ee02653e 100644 --- a/src/specfact_cli/agents/analyze_agent.py +++ b/src/specfact_cli/agents/analyze_agent.py @@ -262,7 +262,7 @@ def _read_dependency_snippets(repo_path: Path) -> list[str]: try: content = dep_file.read_text(encoding="utf-8")[:500] dependencies.append(f"{dep_file.name}: {content[:100]}...") - except Exception: + except (OSError, UnicodeDecodeError): pass return dependencies diff --git a/src/specfact_cli/analyzers/ambiguity_scanner.py b/src/specfact_cli/analyzers/ambiguity_scanner.py index e7db1aa0..d5cc6412 100644 --- a/src/specfact_cli/analyzers/ambiguity_scanner.py +++ b/src/specfact_cli/analyzers/ambiguity_scanner.py @@ -772,7 +772,7 @@ def _personas_from_readme(self) -> set[str]: continue if user_clean and len(user_clean) > 2 and user_lower not in excluded and len(user_clean.split()) <= 3: result.add(user_clean.title()) - except Exception: + except (OSError, UnicodeDecodeError, re.error): pass return result @@ -898,9 +898,9 @@ def _personas_from_codebase(self) -> set[str]: continue try: self._personas_from_py_file(py_file, result, excluded) - except Exception: + except (OSError, UnicodeDecodeError, re.error): continue - except Exception: + except (OSError, UnicodeDecodeError, re.error): pass return result diff --git a/src/specfact_cli/analyzers/code_analyzer.py b/src/specfact_cli/analyzers/code_analyzer.py index f121b1f3..76428901 100644 --- a/src/specfact_cli/analyzers/code_analyzer.py +++ b/src/specfact_cli/analyzers/code_analyzer.py @@ -1794,14 +1794,14 @@ def _analyze_commit_history(self) -> None: for commit in commits: try: self._process_commit_for_feature_bounds(commit) - except Exception: + except (OSError, ValueError): # Skip individual commits that fail (corrupted, etc.) continue except ImportError: # GitPython not available, skip pass - except Exception: + except OSError: # Git operations failed, skip gracefully pass diff --git a/src/specfact_cli/analyzers/contract_extractor.py b/src/specfact_cli/analyzers/contract_extractor.py index e5b33390..0652fe3d 100644 --- a/src/specfact_cli/analyzers/contract_extractor.py +++ b/src/specfact_cli/analyzers/contract_extractor.py @@ -240,7 +240,7 @@ def _ast_to_type_string(self, node: ast.AST | None) -> str: if hasattr(ast, "unparse"): try: return ast.unparse(node) - except Exception: + except (ValueError, TypeError): pass # Fallback: manual conversion @@ -282,7 +282,7 @@ def _ast_to_value_string(self, node: ast.AST) -> str: if hasattr(ast, "unparse"): try: return ast.unparse(node) - except Exception: + except (ValueError, TypeError): pass return "..." @@ -295,7 +295,7 @@ def _ast_to_condition_string(self, node: ast.AST) -> str: if hasattr(ast, "unparse"): try: return ast.unparse(node) - except Exception: + except (ValueError, TypeError): pass # Fallback: basic conversion diff --git a/src/specfact_cli/analyzers/graph_analyzer.py b/src/specfact_cli/analyzers/graph_analyzer.py index 4be7da57..4846bf15 100644 --- a/src/specfact_cli/analyzers/graph_analyzer.py +++ b/src/specfact_cli/analyzers/graph_analyzer.py @@ -178,7 +178,7 @@ def process_imports(file_path: Path) -> list[tuple[str, str]]: edges = future.result() for module_name, matching_module in edges: graph.add_edge(module_name, matching_module) - except Exception: + except (OSError, RuntimeError): pass completed += 1 if progress_callback: @@ -211,7 +211,7 @@ def _build_call_graph_edges( callee_module = self._resolve_module_from_function(callee, python_files) if callee_module and callee_module in graph: graph.add_edge(module_name, callee_module) - except Exception: + except (OSError, RuntimeError): pass completed += 1 if progress_callback: diff --git a/src/specfact_cli/analyzers/relationship_mapper.py b/src/specfact_cli/analyzers/relationship_mapper.py index 480dda46..8991677c 100644 --- a/src/specfact_cli/analyzers/relationship_mapper.py +++ b/src/specfact_cli/analyzers/relationship_mapper.py @@ -306,7 +306,7 @@ def _analyze_file_parallel(self, file_path: Path) -> tuple[str, dict[str, Any]]: if file_hash: self.analysis_cache[file_hash] = empty_result return (file_key, empty_result) - except Exception: + except (OSError, PermissionError): pass try: @@ -354,7 +354,7 @@ def _collect_parallel_results( if not f.done(): f.cancel() raise - except Exception: + except (OSError, ValueError): pass completed_count += 1 if progress_callback: diff --git a/src/specfact_cli/common/logger_setup.py b/src/specfact_cli/common/logger_setup.py index 60ef3da8..355aee85 100644 --- a/src/specfact_cli/common/logger_setup.py +++ b/src/specfact_cli/common/logger_setup.py @@ -426,11 +426,11 @@ def _attach_file_output_pipeline( log_file_path = os.path.join(logs_dir, log_file) log_file_dir = os.path.dirname(log_file_path) - os.makedirs(log_file_dir, mode=0o777, exist_ok=True) + os.makedirs(log_file_dir, mode=0o755, exist_ok=True) try: with open(log_file_path, "a", encoding="utf-8"): pass - except Exception: + except OSError: pass try: diff --git a/src/specfact_cli/enrichers/constitution_enricher.py b/src/specfact_cli/enrichers/constitution_enricher.py index f2a4c750..4d35479b 100644 --- a/src/specfact_cli/enrichers/constitution_enricher.py +++ b/src/specfact_cli/enrichers/constitution_enricher.py @@ -145,7 +145,7 @@ def _analyze_pyproject(self, pyproject_path: Path) -> dict[str, Any]: # Simple dependency name without version constraints result["technology_stack"].append(dep) - except Exception: + except (OSError, UnicodeDecodeError, ValueError): pass # If parsing fails, return empty result return result @@ -190,7 +190,7 @@ def _analyze_package_json(self, package_json_path: Path) -> dict[str, Any]: else: result["technology_stack"].append(dep) - except Exception: + except (OSError, UnicodeDecodeError, ValueError): pass return result @@ -235,7 +235,7 @@ def _analyze_readme(self, readme_path: Path) -> dict[str, Any]: users = [u.strip() for u in re.split(r"[,;]", users_text)] result["target_users"] = users[:5] - except Exception: + except (OSError, UnicodeDecodeError, ValueError): pass return result @@ -265,7 +265,7 @@ def _analyze_cursor_rules(self, rules_dir: Path) -> list[dict[str, str]]: # Extract principles from headings and key sections extracted = self._extract_principles_from_markdown(content, rule_file) principles.extend(extracted) - except Exception: + except (OSError, UnicodeDecodeError, ValueError): pass return principles @@ -293,7 +293,7 @@ def _analyze_docs_rules(self, rules_dir: Path) -> list[str]: # Extract quality standards extracted = self._extract_quality_standards(content) standards.extend(extracted) - except Exception: + except (OSError, UnicodeDecodeError, ValueError): pass return standards diff --git a/src/specfact_cli/generators/persona_exporter.py b/src/specfact_cli/generators/persona_exporter.py index 3d6bce55..ba68ba11 100644 --- a/src/specfact_cli/generators/persona_exporter.py +++ b/src/specfact_cli/generators/persona_exporter.py @@ -242,7 +242,7 @@ def _load_bundle_protocols(self, bundle_dir: Path) -> dict[str, Any]: protocol_data = load_structured_file(protocol_file) protocol_name = protocol_file.stem.replace(".protocol", "") protocols[protocol_name] = protocol_data - except Exception: + except (OSError, ValueError): pass return protocols @@ -267,7 +267,7 @@ def _load_bundle_contracts(self, bundle_dir: Path) -> dict[str, Any]: contract_data = load_structured_file(contract_file) contract_name = contract_file.stem.replace(".openapi", "").replace(".asyncapi", "") contracts[contract_name] = contract_data - except Exception: + except (OSError, ValueError): pass return contracts diff --git a/src/specfact_cli/modules/init/module-package.yaml b/src/specfact_cli/modules/init/module-package.yaml index 966023a6..79bfa74d 100644 --- a/src/specfact_cli/modules/init/module-package.yaml +++ b/src/specfact_cli/modules/init/module-package.yaml @@ -1,5 +1,5 @@ name: init -version: 0.1.19 +version: 0.1.21 commands: - init category: core @@ -17,5 +17,5 @@ publisher: description: Initialize SpecFact workspace and bootstrap local configuration. license: Apache-2.0 integrity: - checksum: sha256:a0ca0fb136f278a11a113be78047c3c7037de9a393c27f8e677a26c4ab2ba659 - signature: r3czsyG/tinaxMTd/lNkhjXQqRUU0ecLywXt1iDWPO0QuExVM+msp5tuAud03QPnuCsMXNdyBWWSDBovPeY+Aw== + checksum: sha256:925fd303b581441618597b5fa5d7f308cbc31405455ae7811243c072616974bf + signature: uMDekXAxcKEDb8V1rqEeL8X806bP4otT5rT5ShXFlUNjcJ06c90s7xg1KqTNYj/eVsuh07xKSGnWKo1BG6juAw== diff --git a/src/specfact_cli/modules/init/src/commands.py b/src/specfact_cli/modules/init/src/commands.py index 6ac7f90a..057c5d0c 100644 --- a/src/specfact_cli/modules/init/src/commands.py +++ b/src/specfact_cli/modules/init/src/commands.py @@ -13,7 +13,6 @@ from rich.console import Console from rich.panel import Panel from rich.rule import Rule -from rich.table import Table from specfact_cli import __version__ from specfact_cli.contracts.module_interface import ModuleIOContract @@ -222,21 +221,6 @@ def _questionary_style() -> Any: ) -def _render_modules_table(modules_list: list[dict[str, Any]]) -> None: - """Render discovered modules with effective enabled/disabled state.""" - table = Table(title="Installed Modules") - table.add_column("Module", style="cyan") - table.add_column("Version", style="magenta") - table.add_column("State", style="green") - for module in modules_list: - module_id = str(module.get("id", "")) - version = str(module.get("version", "")) - enabled = bool(module.get("enabled", True)) - state = "enabled" if enabled else "disabled" - table.add_row(module_id, version, state) - console.print(table) - - def _module_checkbox_rows(candidates: list[dict[str, Any]]) -> tuple[dict[str, str], list[str]]: display_to_id: dict[str, str] = {} choices: list[str] = [] @@ -274,40 +258,6 @@ def _run_module_checkbox_prompt( return [display_to_id[s] for s in selected if s in display_to_id] -def _select_module_ids_interactive(action: str, modules_list: list[dict[str, Any]]) -> list[str]: - """Select one or more module IDs interactively via arrow-key checkbox menu.""" - try: - import questionary # type: ignore[reportMissingImports] - except ImportError as e: - console.print( - "[red]Interactive module selection requires 'questionary'. Install with: pip install questionary[/red]" - ) - raise typer.Exit(1) from e - - target_enabled = action == "disable" - candidates = [m for m in modules_list if bool(m.get("enabled", True)) is target_enabled] - if not candidates: - console.print(f"[yellow]No modules available to {action}.[/yellow]") - return [] - - action_title = "Enable" if action == "enable" else "Disable" - current_state = "disabled" if action == "enable" else "enabled" - console.print() - console.print( - Panel( - f"[bold cyan]{action_title} Modules[/bold cyan]\n" - f"Choose one or more currently [bold]{current_state}[/bold] modules.", - border_style="cyan", - ) - ) - console.print( - "[dim]Controls: ↑↓ navigate • Space toggle • Enter confirm • Type to search/filter • Ctrl+C cancel[/dim]" - ) - - display_to_id, choices = _module_checkbox_rows(candidates) - return _run_module_checkbox_prompt(action, display_to_id, choices, questionary) - - def _resolve_templates_dir(repo_path: Path) -> Path | None: """Resolve a representative templates directory from installed modules or a dev repo checkout.""" prompt_files = discover_prompt_template_files(repo_path, include_package_fallback=True) diff --git a/src/specfact_cli/modules/module_io_shim.py b/src/specfact_cli/modules/module_io_shim.py index d9cee884..da21461a 100644 --- a/src/specfact_cli/modules/module_io_shim.py +++ b/src/specfact_cli/modules/module_io_shim.py @@ -17,10 +17,6 @@ def _import_source_exists(source: Path) -> bool: return source.exists() -def _export_target_exists(target: Path) -> bool: - return target.exists() - - def _external_source_nonempty(external_source: str) -> bool: return len(external_source.strip()) > 0 diff --git a/src/specfact_cli/registry/bootstrap.py b/src/specfact_cli/registry/bootstrap.py index 1c453e71..c9ad6351 100644 --- a/src/specfact_cli/registry/bootstrap.py +++ b/src/specfact_cli/registry/bootstrap.py @@ -41,7 +41,7 @@ def _get_category_grouping_enabled() -> bool: return val if isinstance(val, str): return val.strip().lower() in ("1", "true", "yes") - except Exception: + except (OSError, ValueError): pass return True diff --git a/src/specfact_cli/registry/module_packages.py b/src/specfact_cli/registry/module_packages.py index 6b6a95d6..ed782a77 100644 --- a/src/specfact_cli/registry/module_packages.py +++ b/src/specfact_cli/registry/module_packages.py @@ -742,25 +742,6 @@ def _check_protocol_compliance(module_class: Any) -> list[str]: return operations -@beartype -@require(lambda package_name: cast(str, package_name).strip() != "", "Package name must not be empty") -@ensure(lambda result: result is not None, "Protocol inspection target must be resolved") -def _resolve_protocol_target(module_obj: Any, package_name: str) -> Any: - """Resolve runtime interface used for protocol inspection.""" - runtime_interface = getattr(module_obj, "runtime_interface", None) - if runtime_interface is not None: - return runtime_interface - commands_interface = getattr(module_obj, "commands", None) - if commands_interface is not None: - return commands_interface - # Module app entrypoints often only expose `app`; load module-local commands for protocol detection. - try: - return importlib.import_module(f"specfact_cli.modules.{package_name}.src.commands") - except Exception: - pass - return module_obj - - def _resolve_protocol_source_paths( package_dir: Path, package_name: str, diff --git a/src/specfact_cli/sync/bridge_sync.py b/src/specfact_cli/sync/bridge_sync.py index f7911303..0e82eb98 100644 --- a/src/specfact_cli/sync/bridge_sync.py +++ b/src/specfact_cli/sync/bridge_sync.py @@ -67,7 +67,7 @@ def _code_repo_from_cwd(repo_name: str) -> Path | None: ) if result.returncode == 0 and repo_name in result.stdout: return cwd - except Exception: + except (OSError, ValueError): pass return None @@ -79,7 +79,7 @@ def _code_repo_from_parent(repo_name: str) -> Path | None: repo_path = cwd.parent / repo_name if repo_path.exists() and (repo_path / ".git").exists(): return repo_path - except Exception: + except (OSError, ValueError): pass return None @@ -94,7 +94,7 @@ def _code_repo_from_grandparent_siblings(repo_name: str) -> Path | None: for sibling in grandparent.iterdir(): if sibling.is_dir() and sibling.name == repo_name and (sibling / ".git").exists(): return sibling - except Exception: + except (OSError, ValueError): pass return None @@ -1498,7 +1498,7 @@ def _enrich_source_tracking_entry_repo(self, entry: dict[str, Any]) -> None: parsed_hostname: str | None = cast(str | None, parsed.hostname) if parsed_hostname and parsed_hostname.lower() == "dev.azure.com": pass - except Exception: + except ValueError: pass def _collect_source_tracking_entries_from_proposal_text(self, proposal_content: str) -> list[dict[str, Any]]: diff --git a/src/specfact_cli/sync/spec_to_code.py b/src/specfact_cli/sync/spec_to_code.py index 25cd55ec..83f6a001 100644 --- a/src/specfact_cli/sync/spec_to_code.py +++ b/src/specfact_cli/sync/spec_to_code.py @@ -220,7 +220,7 @@ def _read_requirements(self, repo_path: Path) -> list[str]: data = cast(dict[str, Any], _tomli.load(f)) if "project" in data and "dependencies" in data["project"]: dependencies.extend(data["project"]["dependencies"]) - except Exception: + except (ImportError, OSError, ValueError): pass # Ignore parsing errors return dependencies diff --git a/src/specfact_cli/utils/context_detection.py b/src/specfact_cli/utils/context_detection.py index 823ae2c5..96e0d068 100644 --- a/src/specfact_cli/utils/context_detection.py +++ b/src/specfact_cli/utils/context_detection.py @@ -208,7 +208,7 @@ def _detect_python_framework(repo_path: Path, context: ProjectContext) -> None: context.framework = "flask" elif "fastapi" in content: context.framework = "fastapi" - except Exception: + except (OSError, UnicodeDecodeError): pass @@ -225,7 +225,7 @@ def _detect_js_framework(repo_path: Path, context: ProjectContext) -> None: context.framework = "react" elif "vue" in deps: context.framework = "vue" - except Exception: + except (OSError, json.JSONDecodeError): pass diff --git a/src/specfact_cli/utils/progress.py b/src/specfact_cli/utils/progress.py index 0665fecc..501bd237 100644 --- a/src/specfact_cli/utils/progress.py +++ b/src/specfact_cli/utils/progress.py @@ -45,7 +45,7 @@ def _safe_progress_display(display_console: Console) -> bool: # Rich stores active Live displays in Console._live if hasattr(display_console, "_live") and display_console._live is not None: return False - except Exception: + except AttributeError: pass return True @@ -149,7 +149,7 @@ def load_bundle_with_progress( # Brief pause to show completion time.sleep(0.1) return bundle - except Exception: + except (RuntimeError, OSError): # If Progress creation fails (e.g., LiveError), fall back to direct load pass @@ -222,7 +222,7 @@ def save_bundle_with_progress( # Brief pause to show completion time.sleep(0.1) return - except Exception: + except (RuntimeError, OSError): # If Progress creation fails (e.g., LiveError), fall back to direct save pass diff --git a/src/specfact_cli/utils/progressive_disclosure.py b/src/specfact_cli/utils/progressive_disclosure.py index bd7e685d..a7633733 100644 --- a/src/specfact_cli/utils/progressive_disclosure.py +++ b/src/specfact_cli/utils/progressive_disclosure.py @@ -82,22 +82,6 @@ def intercept_help_advanced() -> None: sys.argv[:] = normalized_args -@beartype -def _is_help_context(ctx: ClickContext | None) -> bool: - """Check if this context is for showing help.""" - if ctx is None: - return False - # Check if help was requested by looking at params or info_name - if hasattr(ctx, "params") and ctx.params: - # Check if any help option is in params - for param in ctx.params.values() if isinstance(ctx.params, dict) else []: - param_name = getattr(param, "name", None) - if param_name and param_name in ("--help", "-h", "--help-advanced", "-ha"): - return True - # Check info_name for help indicators - return bool(hasattr(ctx, "info_name") and ctx.info_name and "help" in str(ctx.info_name).lower()) - - @beartype def _is_advanced_help_context(ctx: ClickContext | None) -> bool: """Check if this context is for showing advanced help.""" diff --git a/src/specfact_cli/utils/source_scanner.py b/src/specfact_cli/utils/source_scanner.py index e0489726..6f925bf4 100644 --- a/src/specfact_cli/utils/source_scanner.py +++ b/src/specfact_cli/utils/source_scanner.py @@ -512,7 +512,7 @@ def _index_impl_file_for_link_cache( source_tracking = SourceTracking() source_tracking.update_hash(file_path) file_hashes_cache[rel_path] = source_tracking.file_hashes.get(rel_path, "") - except Exception: + except (OSError, ValueError): pass def _index_test_file_for_link_cache( @@ -552,7 +552,7 @@ def _index_test_file_for_link_cache( source_tracking = SourceTracking() source_tracking.update_hash(file_path) file_hashes_cache[rel_path] = source_tracking.file_hashes.get(rel_path, "") - except Exception: + except (OSError, ValueError): pass def _run_parallel_feature_linking( diff --git a/src/specfact_cli/utils/structure.py b/src/specfact_cli/utils/structure.py index b02460b6..98a7941a 100644 --- a/src/specfact_cli/utils/structure.py +++ b/src/specfact_cli/utils/structure.py @@ -252,7 +252,7 @@ def get_default_plan_path( bundle_dir = base_path / cls.PROJECTS / active_bundle if bundle_dir.exists() and (bundle_dir / "bundle.manifest.yaml").exists(): return bundle_dir - except Exception: + except (OSError, ValueError): # Fallback if config read fails pass @@ -302,7 +302,7 @@ def get_active_bundle_name(cls, base_path: Path | None = None) -> str | None: active_bundle = config.get(cls.ACTIVE_BUNDLE_CONFIG_KEY) if active_bundle: return active_bundle - except Exception: + except (OSError, ValueError): # Fallback if config read fails pass diff --git a/src/specfact_cli/validators/sidecar/dependency_installer.py b/src/specfact_cli/validators/sidecar/dependency_installer.py index f4dbbd63..502c8f4f 100644 --- a/src/specfact_cli/validators/sidecar/dependency_installer.py +++ b/src/specfact_cli/validators/sidecar/dependency_installer.py @@ -55,7 +55,7 @@ def create_sidecar_venv(venv_path: Path, repo_path: Path) -> bool: if result.returncode == 0: # Venv exists and works, skip recreation return True - except Exception: + except (OSError, subprocess.SubprocessError): # Venv exists but Python can't run (e.g., libpython issue) # Delete and recreate pass diff --git a/src/specfact_cli/validators/sidecar/orchestrator.py b/src/specfact_cli/validators/sidecar/orchestrator.py index 09cc4304..34db91c4 100644 --- a/src/specfact_cli/validators/sidecar/orchestrator.py +++ b/src/specfact_cli/validators/sidecar/orchestrator.py @@ -51,7 +51,7 @@ def _should_use_progress(console: Console) -> bool: try: if hasattr(console, "_live") and console._live is not None: return False - except Exception: + except AttributeError: pass return True diff --git a/tests/unit/specfact_cli/test_clean_code_principle_gates.py b/tests/unit/specfact_cli/test_clean_code_principle_gates.py new file mode 100644 index 00000000..c8b52d6c --- /dev/null +++ b/tests/unit/specfact_cli/test_clean_code_principle_gates.py @@ -0,0 +1,168 @@ +"""Tests for clean-code-01-principle-gates. + +Validates that specfact-cli instruction surfaces expose the 7-principle +clean-code charter consistently and that the review gate is configured +for the expanded clean-code categories under Phase A thresholds. + +Spec scenarios from: + openspec/changes/clean-code-01-principle-gates/specs/ +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[3] + +# Instruction surface paths +AGENTS_MD = REPO_ROOT / "AGENTS.md" +CLAUDE_MD = REPO_ROOT / "CLAUDE.md" +CLEAN_CODE_MDC = REPO_ROOT / ".cursor" / "rules" / "clean-code-principles.mdc" +COPILOT_INSTRUCTIONS = REPO_ROOT / ".github" / "copilot-instructions.md" + + +# --------------------------------------------------------------------------- +# Spec: agent-instruction-clean-code-charter +# Scenario: Core instruction surfaces reference the charter consistently +# --------------------------------------------------------------------------- + + +def test_agents_md_references_clean_code_categories() -> None: + """AGENTS.md must reference clean-code review categories so contributors + know which categories the gate checks.""" + text = AGENTS_MD.read_text(encoding="utf-8") + for category in ("naming", "kiss", "yagni", "dry", "solid"): + assert category in text.lower(), ( + f"AGENTS.md missing clean-code category reference: {category!r}. " + "Add a clean-code review section that lists all 5 expanded categories." + ) + + +def test_claude_md_references_clean_code_categories() -> None: + """CLAUDE.md must reference clean-code review categories.""" + text = CLAUDE_MD.read_text(encoding="utf-8") + for category in ("naming", "kiss", "yagni", "dry", "solid"): + assert category in text.lower(), ( + f"CLAUDE.md missing clean-code category reference: {category!r}. " + "Add a clean-code review section that lists all 5 expanded categories." + ) + + +def test_clean_code_mdc_references_seven_principles() -> None: + """clean-code-principles.mdc must reference all 7 principles by canonical name.""" + text = CLEAN_CODE_MDC.read_text(encoding="utf-8") + # 7 canonical principles + for category in ("naming", "kiss", "yagni", "dry", "solid", "small", "self"): + assert category in text.lower(), ( + f".cursor/rules/clean-code-principles.mdc missing principle: {category!r}. " + "Update this file to reference the canonical 7-principle charter." + ) + + +def test_clean_code_mdc_references_canonical_skill() -> None: + """clean-code-principles.mdc must reference the canonical charter source + (skills/specfact-code-review/SKILL.md or the policy-pack name) so that + generated alias surfaces do not duplicate the full charter text.""" + text = CLEAN_CODE_MDC.read_text(encoding="utf-8") + assert "specfact-code-review" in text or "clean-code-principles" in text, ( + ".cursor/rules/clean-code-principles.mdc must point to the canonical charter source " + "(e.g. 'specfact/clean-code-principles' policy-pack or 'skills/specfact-code-review/SKILL.md')." + ) + + +# --------------------------------------------------------------------------- +# Spec: agent-instruction-clean-code-charter +# Scenario: Generated IDE aliases stay lightweight +# --------------------------------------------------------------------------- + + +def test_copilot_instructions_exists_and_references_charter() -> None: + """GITHUB copilot-instructions.md must exist and contain a clean-code alias + reference without duplicating the full charter inline.""" + assert COPILOT_INSTRUCTIONS.exists(), ( + f"{COPILOT_INSTRUCTIONS} is missing. Create a lightweight alias file " + "that references the canonical clean-code charter." + ) + text = COPILOT_INSTRUCTIONS.read_text(encoding="utf-8") + assert "clean-code" in text.lower() or "clean_code" in text.lower(), ( + ".github/copilot-instructions.md must contain a clean-code alias reference." + ) + + +def test_copilot_instructions_does_not_duplicate_full_charter() -> None: + """copilot-instructions.md must be a short alias, not a full charter copy. + If the file is longer than 80 lines it likely duplicates the charter verbatim.""" + if not COPILOT_INSTRUCTIONS.exists(): + pytest.skip("copilot-instructions.md not yet created") + lines = COPILOT_INSTRUCTIONS.read_text(encoding="utf-8").splitlines() + assert len(lines) <= 80, ( + f".github/copilot-instructions.md is {len(lines)} lines — too long for an alias. " + "Keep it concise and reference the canonical charter rather than duplicating it." + ) + + +# --------------------------------------------------------------------------- +# Spec: clean-code-compliance-gate +# Scenario: Repo review includes expanded clean-code categories +# --------------------------------------------------------------------------- + + +def test_agents_md_documents_clean_code_compliance_gate() -> None: + """AGENTS.md must document that the SpecFact review gate checks clean-code + categories so contributors know regressions will block merges.""" + text = AGENTS_MD.read_text(encoding="utf-8") + assert "clean-code" in text.lower() or "clean_code" in text.lower(), ( + "AGENTS.md must document the clean-code compliance gate so contributors " + "know the review gate enforces clean-code categories." + ) + + +def test_claude_md_documents_clean_code_compliance_gate() -> None: + """CLAUDE.md must document that the review gate checks clean-code categories.""" + text = CLAUDE_MD.read_text(encoding="utf-8") + assert "clean-code" in text.lower() or "clean_code" in text.lower(), ( + "CLAUDE.md must document the clean-code compliance gate." + ) + + +# --------------------------------------------------------------------------- +# Spec: clean-code-loc-nesting-check +# Scenario: Phase A thresholds are enforced first +# --------------------------------------------------------------------------- + + +def test_clean_code_mdc_documents_phase_a_loc_thresholds() -> None: + """clean-code-principles.mdc must document the Phase A LOC thresholds + (>80 warning, >120 error) so reviewers and tools know the active limits.""" + text = CLEAN_CODE_MDC.read_text(encoding="utf-8") + assert "> 80 (warning)" in text and "> 120 (error)" in text, ( + ".cursor/rules/clean-code-principles.mdc must document Phase A LOC thresholds: " + "'> 80 (warning)' and '> 120 (error)'. These are the active KISS metric limits." + ) + + +def test_clean_code_mdc_mentions_nesting_and_parameter_checks() -> None: + """clean-code-principles.mdc must mention nesting-depth and parameter-count + checks alongside the LOC thresholds (all three are Phase A KISS metrics).""" + text = CLEAN_CODE_MDC.read_text(encoding="utf-8") + assert "nesting" in text.lower(), ".cursor/rules/clean-code-principles.mdc must mention nesting-depth checks." + assert "parameter" in text.lower(), ".cursor/rules/clean-code-principles.mdc must mention parameter-count checks." + + +# --------------------------------------------------------------------------- +# Spec: clean-code-loc-nesting-check +# Scenario: Phase B remains deferred until cleanup is complete +# --------------------------------------------------------------------------- + + +def test_clean_code_mdc_documents_phase_b_as_deferred() -> None: + """clean-code-principles.mdc must note that Phase B thresholds (>40 / >80) + are deferred so no tool silently promotes them to a hard gate.""" + text = CLEAN_CODE_MDC.read_text(encoding="utf-8") + assert "phase b" in text.lower() or "phase-b" in text.lower(), ( + ".cursor/rules/clean-code-principles.mdc must document that Phase B thresholds " + "(>40 / >80 LOC) are deferred — not yet active as a hard gate." + )