From 2f7cbaf707e25ea65aefe343f8f0a7798b2f4315 Mon Sep 17 00:00:00 2001 From: Dominikus Nold Date: Mon, 13 Jul 2026 21:19:53 +0200 Subject: [PATCH 1/6] docs(openspec): rescope openspec-01 to import-first evidence runtime Retire the Intent Trace authoring scope. The requirements module gains thin runtime wiring for import-first requirement evidence: --from-openspec and --from-speckit flags with conservative layout auto-detection, gate-finding surfacing in validate with CI-usable exit codes, and layered-config profile resolution when --profile is omitted. Parsing, hashing, and gate logic stay core-owned (nold-ai/specfact-cli#350). Reprioritize Track B: openspec-01 moves to order 2, requirements-03-backlog-sync is parked as of 2026-07-13. Co-Authored-By: Claude Fable 5 --- openspec/CHANGE_ORDER.md | 16 +- .../CHANGE_VALIDATION.md | 114 +------------ .../openspec-01-intent-trace/design.md | 102 +++++------ .../openspec-01-intent-trace/proposal.md | 63 ++++--- .../specs/openspec-bridge-adapter/spec.md | 22 --- .../openspec-bridge-intent-import/spec.md | 49 ------ .../openspec-intent-trace-schema/spec.md | 42 ----- .../specs/requirements-module/spec.md | 45 +++++ .../changes/openspec-01-intent-trace/tasks.md | 159 +++--------------- 9 files changed, 170 insertions(+), 442 deletions(-) delete mode 100644 openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-adapter/spec.md delete mode 100644 openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-intent-import/spec.md delete mode 100644 openspec/changes/openspec-01-intent-trace/specs/openspec-intent-trace-schema/spec.md create mode 100644 openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index 74fc3d3f..2ea28e52 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -67,10 +67,10 @@ AI IDE, rerun, and compare improved evidence. | Order | Change folder | GitHub # | Positioning | Blocked by | |---:|---|---|---|---| | 1 | `requirements-02-module-commands` | [#165](https://github.com/nold-ai/specfact-cli-modules/issues/165) | Import/normalize requirement context for evidence | core requirements input model | -| 2 | `sync-01-unified-kernel` | [#157](https://github.com/nold-ai/specfact-cli-modules/issues/157) | Preview/apply safety only where validation adapters need it | project/runtime safety specs | -| 3 | `requirements-03-backlog-sync` | [#166](https://github.com/nold-ai/specfact-cli-modules/issues/166) | Read-first backlog drift evidence; no write-back critical path | requirements-02, sync-01 | -| 4 | `architecture-01-solution-layer` | [#164](https://github.com/nold-ai/specfact-cli-modules/issues/164) | Architecture-boundary validation input | core architecture-boundary contracts | -| 5 | `openspec-01-intent-trace` | [#168](https://github.com/nold-ai/specfact-cli-modules/issues/168) | Optional OpenSpec and Spec Kit evidence import adapter | requirements input contracts | +| 2 | `openspec-01-intent-trace` | [#168](https://github.com/nold-ai/specfact-cli-modules/issues/168) | Import-first OpenSpec and Spec Kit requirement evidence runtime with gate surfacing (rescoped 2026-07-13) | requirements-02 runtime; core nold-ai/specfact-cli#350 contracts | +| 3 | `architecture-01-solution-layer` | [#164](https://github.com/nold-ai/specfact-cli-modules/issues/164) | Architecture-boundary validation input | core architecture-boundary contracts | +| 4 | `sync-01-unified-kernel` | [#157](https://github.com/nold-ai/specfact-cli-modules/issues/157) | Preview/apply safety only where validation adapters need it | project/runtime safety specs | +| Parked | `requirements-03-backlog-sync` | [#166](https://github.com/nold-ai/specfact-cli-modules/issues/166) | Read-first backlog drift evidence; no write-back critical path. Deprioritized 2026-07-13 behind openspec-01 | requirements-02, sync-01 | | Gated | `architecture-02-module-well-architected` | [#230](https://github.com/nold-ai/specfact-cli-modules/issues/230) | Architecture-boundary review findings | architecture-01 shipped plus one usage cycle | ### Track C - Supporting Docs @@ -89,7 +89,7 @@ AI IDE, rerun, and compare improved evidence. | `requirements-02-module-commands` | Drop requirement authoring as a flagship workflow. Keep import, normalization, validation, and coverage inspection. | | `requirements-03-backlog-sync` | Keep read-first drift evidence. Write-back remains preview-only and outside the validation critical path. | | `architecture-01-solution-layer` | Keep architecture-boundary input and validation hooks. Drop architecture generation. | -| `openspec-01-intent-trace` | Reframe as optional OpenSpec/Spec Kit adapter consuming upstream artifacts. | +| `openspec-01-intent-trace` | Done 2026-07-13: rescoped to import-first runtime for native OpenSpec/Spec Kit artifacts with deterministic gate surfacing. | | `architecture-02-module-well-architected` | Keep gated until architecture-01 ships and is used for one complete cycle. | | `sync-01-unified-kernel` | Keep only as safety infrastructure for validation/context adapters. | @@ -129,10 +129,10 @@ ceremony rather than validation evidence: ### Wave 3 - Context Adapters - `requirements-02-module-commands` -- `sync-01-unified-kernel` -- `requirements-03-backlog-sync` +- `openspec-01-intent-trace` (pulled forward 2026-07-13, import-first rescope) - `architecture-01-solution-layer` -- `openspec-01-intent-trace` +- `sync-01-unified-kernel` +- `requirements-03-backlog-sync` (parked 2026-07-13) ### Wave 4 - Gated Extensions diff --git a/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md b/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md index 52126942..1b2db9c1 100644 --- a/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md +++ b/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md @@ -1,110 +1,12 @@ # Change Validation Report: openspec-01-intent-trace -**Validation Date**: 2026-03-05 -**Change Proposal**: [proposal.md](./proposal.md) -**Validation Method**: Dry-run simulation — codebase interface analysis + temporary workspace -**Source Plan**: `specfact-cli-internal/docs/internal/implementation/2026-03-05-CLAUDE-RESEARCH-INTENT-DRIVEN-DEVELOPMENT.md` +**Status**: STALE — revalidation required before implementation. -## Executive Summary +The previous report (2026-03-05) validated the retired `## Intent Trace` +authoring scope. The change was rescoped on 2026-07-13 to import-first +requirement evidence runtime for native OpenSpec and Spec Kit artifacts (see +`proposal.md`, Rescope section, and core counterpart nold-ai/specfact-cli#350). -- Breaking Changes: 0 detected -- Dependent Files: 3 files affected (additive, non-breaking) -- Impact Level: Low-Medium (extends existing adapter with optional new capability) -- Validation Result: **Pass** -- User Decision: N/A -- Implementation Constraint Identified: 1 (beartype type-annotation — implementation note, not a blocker) - -## Breaking Changes Detected - -None. All interface extensions are additive and optional: -- `parse_change_proposal()` returns `dict[str, Any]` — adding optional `"intent_trace"` key is non-breaking -- `--import-intent` CLI flag has no default effect (opt-in) -- New files (`intent_trace_validator.py`, `intent-trace.schema.json`) have no existing callers - -## Implementation Constraint (Non-Breaking) - -**Constraint**: `_parse_proposal_content()` at `openspec_parser.py:335` has type annotation `dict[str, str]` (return type). If intent trace data (a nested dict) were added here, `@beartype` would raise a type error. - -**Required implementation approach**: Intent trace extraction MUST be done in `parse_change_proposal()` (returns `dict[str, Any]`) by: -1. Calling `_parse_proposal_content(content)` as usual → returns `dict[str, str]` -2. Separately extracting the YAML fenced block under `## Intent Trace` from `content` -3. Parsing with `yaml.safe_load()` and assigning to `result["intent_trace"]` -4. `parse_change_proposal()` return dict is `dict[str, Any]` — no type violation - -The task at step 5.2 ("Add `## Intent Trace` section parser") should be executed in `parse_change_proposal()`, not in `_parse_proposal_content()`. This is an implementation detail — recommend adding a note to `tasks.md`. - -## Dependencies Affected - -### Critical (hard blockers — must land before `--import-intent` write path) - -| Dependency | Issue | Status | -|---|---|---| -| `requirements-01-data-model` | [#238](https://github.com/nold-ai/specfact-cli/issues/238) | PENDING (Wave 5) | -| `requirements-02-module-commands` | [#239](https://github.com/nold-ai/specfact-cli/issues/239) | PENDING (Wave 5/6) | - -Note: The validation/parsing components (JSON Schema, `validate_intent_trace()`, parser extension) do NOT require requirements-01/02. Only the `--import-intent` write path (creating `.req.yaml` files) requires them. This means parsing and validation can be implemented ahead of Wave 5. - -### Recommended Updates (affected, not breaking) - -| File | Reason | Update Type | -|---|---|---| -| `src/specfact_cli/adapters/openspec_parser.py` | Extend `parse_change_proposal()` with intent trace extraction | Additive | -| `src/specfact_cli/adapters/openspec.py` | Extend `_import_change_proposal()` to pass intent trace to bundle; add `--import-intent` path | Additive | -| `src/specfact_cli/validators/change_proposal_integration.py` | May need to call `validate_intent_trace()` when strict mode enabled | Additive | - -## Impact Assessment - -- **Code Impact**: Low — 2 existing files extended (additive only), 2 new files created -- **Test Impact**: Low — new test files only; no existing test modifications required -- **Documentation Impact**: Medium — `docs/adapters/openspec.md` and `docs/guides/openspec-journey.md` need updates -- **Release Impact**: Minor version bump (new features, no breaking changes) - -## Format Validation - -- **proposal.md Format**: Pass - - `# Change:` title ✓ - - `## Why`, `## What Changes`, `## Capabilities`, `## Impact` sections ✓ - - NEW/EXTEND markers ✓ - - Capabilities linked to spec folders ✓ - - Source Tracking section ✓ -- **tasks.md Format**: Pass - - Hierarchical `## 1.`…`## 10.` structure ✓ - - Task 1 = git worktree creation ✓ - - Task 10 = PR creation (last) ✓ - - Post-merge cleanup section ✓ - - TDD / SDD order section at top ✓ - - Tests before implementation (Tasks 2-3 tests before Tasks 4-5 implementation) ✓ - - `TDD_EVIDENCE.md` recording tasks ✓ - - Quality gate tasks ✓ - - Documentation task included ✓ - - Version and changelog task ✓ - - GitHub issue creation task ✓ - - Module signing verification task: not included — this change has no module-package.yaml changes; acceptable -- **specs Format**: Pass - - `####` for scenario headers ✓ - - `## ADDED Requirements` / `## MODIFIED Requirements` delta format ✓ - - G/W/T format with THEN/AND ✓ - - Every requirement has ≥1 scenario ✓ -- **Config.yaml Compliance**: Pass - -## OpenSpec Validation - -- **Status**: Pass -- **Command**: `openspec validate openspec-01-intent-trace --strict` -- **Output**: `Change 'openspec-01-intent-trace' is valid` -- **Issues Found/Fixed**: 0 - -## Dependency Phasing Note - -Unlike ai-integration-04, this change has **two separable implementation phases**: - -1. **Phase A (can land with Wave 5/6)**: JSON Schema definition, `validate_intent_trace()`, parser extension to extract the intent trace block, `openspec validate --strict` hook. No dependency on requirements-01/02. -2. **Phase B (requires Wave 5 complete)**: `--import-intent` flag and `.req.yaml` artifact writer. Requires `BusinessOutcome`/`BusinessRule` Pydantic models. - -Tasks 2-4 can proceed as soon as the worktree is created. Task 5.3 (`--import-intent` flag) should wait for requirements-01 (#238). - -## Ownership Authority - -- **Intent trace schema** (`openspec/schemas/intent-trace.schema.json`): owned by this change; aligns with `requirements-01-data-model` field definitions (no conflict — requirements-01 owns the Pydantic model, this change owns the JSON Schema) -- **`parse_change_proposal()` return dict** (`openspec_parser.py`): existing authority is `openspec_parser.py` itself; this change adds the `"intent_trace"` key — no conflict -- **`--import-intent` write path** in `openspec.py`: this change is authoritative; no other pending change touches this code path +Re-run the change validation workflow against the rescoped proposal and +replace this file with the new report before starting implementation +(tracked as task 1.3 in `tasks.md`). diff --git a/openspec/changes/openspec-01-intent-trace/design.md b/openspec/changes/openspec-01-intent-trace/design.md index 22e7db53..8e90142e 100644 --- a/openspec/changes/openspec-01-intent-trace/design.md +++ b/openspec/changes/openspec-01-intent-trace/design.md @@ -1,82 +1,72 @@ -# Design: OpenSpec Intent Trace — Bridge Adapter Integration +# Design: OpenSpec and Spec Kit Import Runtime for Requirement Evidence ## Context -SpecFact's OpenSpec bridge adapter (`specfact sync bridge --adapter openspec`) reads proposal and task files from an OpenSpec change directory and imports them into SpecFact's project bundle. Currently it reads: title, description, tasks list, and spec references. It does not read any business intent context. This change adds an optional `## Intent Trace` YAML block to the OpenSpec proposal format and teaches the bridge adapter to import it into the `.specfact/requirements/` storage hierarchy used by requirements-01-data-model. - -The principle is: **"OpenSpec owns the intent. SpecFact owns the evidence."** OpenSpec authors write intent in a human-readable YAML block; SpecFact validates conformance and generates evidence. This separation keeps the intent format tool-agnostic. +The `specfact-requirements` module (`packages/specfact-requirements/`) already +wires `requirements import|validate|list|coverage` to core helpers via lazy +imports (`runtime.py` loads `specfact_cli.models.requirements` and +`specfact_cli.requirements.context`). Import currently accepts only +`--from-file`. The core counterpart change (nold-ai/specfact-cli#350) adds +OpenSpec and Spec Kit import normalizers and gate evaluation to core; this +change adds the command-surface runtime. ## Goals / Non-Goals **Goals:** -- Define the `## Intent Trace` section YAML schema and JSON Schema validator -- Extend the OpenSpec bridge adapter to parse and import intent artifacts when the section is present -- Keep the `## Intent Trace` section strictly optional — existing proposals without it are unaffected -- Validate the section against the JSON Schema during `openspec validate --strict` -- Produce `.specfact/requirements/{id}.req.yaml` artifacts from imported intent data - -**Non-Goals:** -- Forcing all existing OpenSpec proposals to add an `## Intent Trace` section -- Building a new proposal authoring tool — the section is hand-authored YAML in Markdown -- Replacing requirements-01/02 commands — the bridge adapter imports intent; the requirements module validates and traces it -## Decisions +- Add `--from-openspec [PATH]` and `--from-speckit [PATH]` to + `requirements import`, delegating all parsing/normalization/hashing to core. +- Auto-detect conventional source layouts when the path is omitted. +- Surface core gate findings in `validate` output with non-zero exit when a + profile treats a finding as an error; expose gate-relevant counts in + `list`/`coverage`. -### D1: YAML fenced block vs structured Markdown headings for Intent Trace - -**Decision**: YAML fenced block under `## Intent Trace` heading -```yaml -intent_trace: - business_outcomes: - - id: "BO-001" - description: "..." - persona: "..." - business_rules: - - id: "BR-001" - outcome_ref: "BO-001" - given: "..." - when: "..." - then: "..." -``` -**Rationale**: YAML is machine-parseable with a single `yaml.safe_load()` call and maps directly to Pydantic models. Structured Markdown headings require custom parsing logic that is brittle and hard to validate with JSON Schema. YAML fenced blocks are already used in GitHub Actions, Docker Compose, and Kubernetes manifests — authors are familiar with the pattern. -**Alternative rejected**: Structured `### Business Outcomes / ### Business Rules` Markdown sub-sections — readable but not JSON Schema validatable. +**Non-Goals:** -### D2: JSON Schema stored in SpecFact vs in OpenSpec format repo +- Any parsing, hashing, or gate logic in the module (core-owned). +- Writes into upstream OpenSpec or Spec Kit directories. +- Backlog import (deprioritized `requirements-03-backlog-sync` scope). -**Decision**: JSON Schema at `openspec/schemas/intent-trace.schema.json` within SpecFact's own repo -**Rationale**: SpecFact is the authority for validation. The schema living in SpecFact's repo means the bridge adapter always validates against the version it was built with. When OpenSpec is an external tool (not this repo), the schema reference is still resolvable locally. -**Alternative rejected**: Hosting schema at `openspec.dev/schemas/intent-trace` — external HTTP dependency violates offline-first constraint. +## Decisions -### D3: `--import-intent` flag vs automatic intent import +### D1: Flags on `requirements import` vs a new subcommand -**Decision**: `specfact sync bridge --adapter openspec --import-intent` — opt-in flag -**Rationale**: Not every team using the OpenSpec bridge wants `.specfact/requirements/` files created from every proposal import. The opt-in flag gives teams control. When `## Intent Trace` is present but `--import-intent` is not passed, the bridge still validates the section on `openspec validate --strict` but does not write requirements artifacts. -**Alternative rejected**: Automatic import when section is present — surprising side effect; could overwrite existing requirement files. +**Decision**: Extend the existing `import` command with mutually compatible +source flags (`--from-file`, `--from-openspec`, `--from-speckit`). +**Rationale**: One import surface, one sidecar persistence path, one merge +semantics (`merge_requirement_inputs`). A new subcommand would duplicate the +bundle/sidecar handling for no user benefit. -### D4: `requirement_refs` in tasks.md — free-form vs validated IDs +### D2: Lazy core-helper loading, same pattern as requirements-02 -**Decision**: Free-form string list (`["BR-001", "AC-002"]`) with advisory validation -**Rationale**: Task-level requirement refs are metadata for traceability, not for enforcement. Advisory-mode validation warns if a ref ID does not match any known `BusinessRule` or `ArchitecturalConstraint` in `.specfact/requirements/` but does not block import. Hard enforcement would break workflows where requirements are not yet captured. +**Decision**: Load the new core import normalizers via the existing +`_load_requirements_module` lazy-import pattern and raise +`RequirementsCoreUnavailableError` with the paired core change named when the +helpers are missing. +**Rationale**: Keeps the module installable against older cores with a clear +error instead of an ImportError traceback. -### D5: `evidence` field in archived changes +### D3: Auto-detection stays conservative -**Decision**: Optional string field in change archive metadata: `evidence: ".specfact/evidence/{timestamp}_{run_id}_evidence.json"` -**Rationale**: Minimal — just a file path reference. The evidence file itself is owned by governance-01-evidence-output. The archive metadata is a pointer, not a copy. This keeps the archive lightweight while enabling audit trail navigation. +**Decision**: When the source path is omitted, detect only conventional +layouts (`openspec/changes/` for OpenSpec; Spec Kit `specs/` feature folders) +relative to the current project root. Ambiguity or absence is a clear error +naming the expected layouts, never a guess. +**Rationale**: Deterministic behavior over convenience heuristics; misdetected +sources would poison evidence. ## Risks / Trade-offs -- **[Risk] YAML indentation errors in proposals** — Authors writing `## Intent Trace` blocks manually may introduce YAML syntax errors. Mitigation: `openspec validate --strict` catches YAML parse errors before import; error message shows the line number and suggests a fix. -- **[Risk] ID collision between imported BusinessOutcome IDs and existing requirements** — If a team runs `--import-intent` twice with the same proposal, duplicate `.req.yaml` files may result. Mitigation: bridge adapter checks for existing file with same ID before writing; uses `--overwrite` flag to allow update. -- **[Trade-off] Schema evolution** — As requirements-01-data-model evolves (new fields), the `intent-trace.schema.json` must stay in sync. Mitigation: schema versioning (`schema_version: "1.0"` in the YAML block); bridge adapter rejects unknown schema versions with a clear error. +- **[Risk] Core/module version skew** — flags exist but core helpers are old. + Mitigation: D2 error contract; documented minimum core version in + `module-package.yaml` dependency metadata. +- **[Trade-off] Auto-detection scope** — monorepos with multiple Spec Kit + roots need explicit paths. Accepted; explicit path flag covers it. ## Migration Plan -1. Land requirements-01-data-model (#238) — `BusinessOutcome` and `BusinessRule` Pydantic models must exist. -2. Define `intent-trace.schema.json` using the Pydantic model fields from requirements-01 as source of truth. -3. Extend bridge adapter parser to detect `## Intent Trace` section and extract the YAML block. -4. Implement `--import-intent` flag and requirements artifact writer. -5. Extend `openspec validate --strict` to call JSON Schema validator on intent trace section. -6. Update existing SpecFact dogfood proposals (this repo's `openspec/changes/`) with `## Intent Trace` sections as the team adopts the format. +1. No data migration; sidecar format unchanged. +2. Docs update the requirements module page with import-first examples. ## Open Questions diff --git a/openspec/changes/openspec-01-intent-trace/proposal.md b/openspec/changes/openspec-01-intent-trace/proposal.md index 075a50f4..da945a1e 100644 --- a/openspec/changes/openspec-01-intent-trace/proposal.md +++ b/openspec/changes/openspec-01-intent-trace/proposal.md @@ -1,39 +1,56 @@ -# Change: OpenSpec and Spec Kit Evidence Import Runtime +# Change: OpenSpec and Spec Kit Import Runtime for Requirement Evidence ## Why -OpenSpec and Spec Kit already own upstream planning artifacts. The modules repo -should provide optional import runtime that maps those artifacts into SpecFact -validation evidence when useful, without requiring upstream tools to adopt a new -intent schema. +OpenSpec and Spec Kit own upstream planning and specification authoring. The +modules repo provides the runtime that imports their native artifacts into +SpecFact requirement evidence, so the `specfact requirements` command group is +useful without hand-authored records files. Core owns the parsing, +normalization, hashing, and gate contracts; this module wires them to commands. -## Ownership Alignment (2026-06-06) +## Rescope (2026-07-13) -- Modules-owned scope retained here: bridge/import runtime, optional metadata - parsing, source attribution, and generated evidence inputs. -- Core-owned scope remains optional adapter contracts and validation behavior. +The previous scope (optional `## Intent Trace` YAML block, schema validation of +hand-authored metadata) is retired: SpecFact must not define an authoring +schema for upstream tools. New scope is **import-first**: parse native OpenSpec +change folders and Spec Kit feature folders as they exist today, read-only, +with deterministic pass/fail validation gates. See the core counterpart +proposal (nold-ai/specfact-cli#350) for contract details. -## What Changes +## Ownership Alignment (2026-07-13) -- **NEW**: Optional import support for OpenSpec proposals and Spec Kit feature - folders. -- **NEW**: Source-attributed mapping of tasks, spec deltas, acceptance checks, - requirement references, and evidence links when present. -- **NEW**: Strict metadata validation only when optional metadata exists. -- **EXTEND**: Project sync/import commands avoid duplicate planning artifacts and - instead feed validation evidence. +- Modules-owned scope retained here: `requirements import` runtime flags + (`--from-openspec`, `--from-speckit`), source auto-detection, command wiring, + and surfacing of gate findings in validate/list/coverage output. +- Core-owned scope remains: artifact parsers, normalization into + `RequirementInput`, content-hash staleness contract, and gate evaluation. +- Runtime MUST stay thin: no parsing, hashing, or gate logic in this module. +- Runtime MUST NOT create or mutate upstream OpenSpec or Spec Kit artifacts. -## Capabilities +## What Changes -### New Capabilities +- **NEW**: `specfact requirements import --from-openspec [PATH]` imports native + OpenSpec change folders through the core evidence adapter. +- **NEW**: `specfact requirements import --from-speckit [PATH]` imports native + Spec Kit feature folders through the core evidence adapter. +- **NEW**: Omitted paths auto-detect conventional layouts (`openspec/changes/` + and Spec Kit `specs/`) relative to the project root. +- **EXTEND**: `requirements validate` surfaces the core gate findings + (`scenario-unverified`, `stale-import`, `source-missing`, + `ambiguous-mapping`) with profile-driven severity and non-zero exit on + failure; `list`/`coverage` output includes gate-relevant counts. +- **EXTEND**: When `--profile` is omitted, the effective profile resolves from + the layered configuration shipped by `profile-01-config-layering` instead of + a hardcoded `startup` default; an explicit flag always wins. +- **UNCHANGED**: `--from-file` remains for generic records; existing sidecar + persistence and merge semantics are reused as-is. -- `openspec-speckit-evidence-import-runtime`: Optional runtime adapter for - OpenSpec and Spec Kit artifacts consumed by validation. +## Capabilities ### Modified Capabilities -- `openspec-bridge-adapter`: Extended to parse optional metadata and evidence - links without requiring them. +- `requirements-module`: Runtime commands gain OpenSpec and Spec Kit import + sources and gate-finding surfacing. --- diff --git a/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-adapter/spec.md b/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-adapter/spec.md deleted file mode 100644 index 2012e709..00000000 --- a/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-adapter/spec.md +++ /dev/null @@ -1,22 +0,0 @@ -## MODIFIED Requirements - -### Requirement: OpenSpec Bridge Adapter Import -The system SHALL import OpenSpec change proposals into SpecFact's project bundle with full backwards compatibility when the `## Intent Trace` section is absent, and include intent context when the section is present. - -#### Scenario: Proposal import without Intent Trace section is unchanged -- **GIVEN** an OpenSpec proposal that has no `## Intent Trace` section -- **WHEN** `specfact sync bridge --adapter openspec` is run -- **THEN** the import behaviour is identical to the pre-change behaviour -- **AND** no error, warning, or advisory is emitted related to missing intent trace - -#### Scenario: Proposal import includes intent context when section is present -- **GIVEN** an OpenSpec proposal with a valid `## Intent Trace` section -- **WHEN** `specfact sync bridge --adapter openspec` is run (without `--import-intent`) -- **THEN** the proposal's intent trace metadata is attached to the project bundle as read-only context -- **AND** `specfact project health-check` can report that intent context is available for the change - -#### Scenario: `openspec validate --strict` validates intent trace when present -- **GIVEN** an OpenSpec change with a proposal containing a `## Intent Trace` section -- **WHEN** `openspec validate --strict` is run -- **THEN** the validator checks the YAML block against `intent-trace.schema.json` -- **AND** any schema violations cause a non-zero exit code with descriptive error messages diff --git a/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-intent-import/spec.md b/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-intent-import/spec.md deleted file mode 100644 index 6d04cad0..00000000 --- a/openspec/changes/openspec-01-intent-trace/specs/openspec-bridge-intent-import/spec.md +++ /dev/null @@ -1,49 +0,0 @@ -## ADDED Requirements - -### Requirement: Bridge Adapter Intent Import -The system SHALL extend `specfact sync bridge --adapter openspec` with an `--import-intent` flag that reads the `## Intent Trace` YAML block from imported proposals and creates corresponding `.specfact/requirements/{id}.req.yaml` artifacts. - -#### Scenario: Intent import creates BusinessOutcome artifacts -- **GIVEN** an OpenSpec proposal with a `## Intent Trace` section containing at least one `business_outcomes` entry -- **WHEN** `specfact sync bridge --adapter openspec --import-intent` is run -- **THEN** a `.specfact/requirements/{id}.req.yaml` file is created for each `BusinessOutcome` in the intent trace -- **AND** each artifact validates against the `BusinessOutcome` Pydantic schema without errors - -#### Scenario: Intent import creates BusinessRule artifacts -- **GIVEN** an OpenSpec proposal with `business_rules` entries in the `## Intent Trace` section -- **WHEN** `specfact sync bridge --adapter openspec --import-intent` is run -- **THEN** each `BusinessRule` (id, outcome_ref, given, when, then) is stored in the corresponding `.req.yaml` artifact under its parent `BusinessOutcome` -- **AND** the `outcome_ref` is resolved to a valid `BusinessOutcome` ID in the imported requirements - -#### Scenario: Intent import skips existing artifacts without --overwrite -- **GIVEN** a `.specfact/requirements/BO-001.req.yaml` file already exists -- **WHEN** `specfact sync bridge --adapter openspec --import-intent` is run without `--overwrite` -- **THEN** the existing file is not modified -- **AND** the CLI output notes the skipped artifact with its ID - -#### Scenario: Intent import overwrites with --overwrite flag -- **GIVEN** a `.specfact/requirements/BO-001.req.yaml` file already exists -- **WHEN** `specfact sync bridge --adapter openspec --import-intent --overwrite` is run -- **THEN** the existing file is updated with the content from the proposal's intent trace section -- **AND** the CLI output confirms the overwritten artifact ID - -#### Scenario: Import without --import-intent ignores intent trace section -- **GIVEN** an OpenSpec proposal with a `## Intent Trace` section -- **WHEN** `specfact sync bridge --adapter openspec` is run without `--import-intent` -- **THEN** no `.specfact/requirements/` artifacts are created -- **AND** the section is validated but not imported - -### Requirement: Task-Level Requirement References -The system SHALL support an optional `requirement_refs` list field on individual tasks in OpenSpec `tasks.md` files, linking tasks to specific `BusinessRule` or `ArchitecturalConstraint` IDs. - -#### Scenario: Bridge adapter parses requirement_refs in tasks -- **GIVEN** a `tasks.md` file with a task containing `requirement_refs: ["BR-001", "AC-002"]` -- **WHEN** the bridge adapter imports the proposal -- **THEN** the imported task record includes the requirement ref IDs -- **AND** they are included in the project bundle's task metadata - -#### Scenario: Advisory validation warns on unresolved requirement refs -- **GIVEN** a task with `requirement_refs: ["BR-999"]` where BR-999 does not exist in `.specfact/requirements/` -- **WHEN** `specfact sync bridge --adapter openspec` is run -- **THEN** the CLI emits an advisory warning: `[ADVISORY] Task X: requirement_refs contains unknown ID BR-999` -- **AND** the import proceeds without failing diff --git a/openspec/changes/openspec-01-intent-trace/specs/openspec-intent-trace-schema/spec.md b/openspec/changes/openspec-01-intent-trace/specs/openspec-intent-trace-schema/spec.md deleted file mode 100644 index b9fa93ae..00000000 --- a/openspec/changes/openspec-01-intent-trace/specs/openspec-intent-trace-schema/spec.md +++ /dev/null @@ -1,42 +0,0 @@ -## ADDED Requirements - -### Requirement: Intent Trace Section Schema -The system SHALL define a JSON Schema at `openspec/schemas/intent-trace.schema.json` that validates the `## Intent Trace` YAML block in OpenSpec proposal files. - -#### Scenario: Valid intent trace section passes schema validation -- **GIVEN** an OpenSpec proposal with a correctly structured `## Intent Trace` YAML block -- **WHEN** `openspec validate --strict` is run -- **THEN** the intent trace section validates without errors -- **AND** the validation output confirms intent trace section is present and valid - -#### Scenario: Invalid intent trace section fails schema validation -- **GIVEN** an OpenSpec proposal with a `## Intent Trace` YAML block missing a required field (e.g., `id` on a `BusinessOutcome`) -- **WHEN** `openspec validate --strict` is run -- **THEN** the validation exits with a non-zero code -- **AND** the error message identifies the specific field violation and the line context - -#### Scenario: Missing intent trace section is valid (section is optional) -- **GIVEN** an OpenSpec proposal without any `## Intent Trace` section -- **WHEN** `openspec validate --strict` is run -- **THEN** the validation passes without intent-trace errors -- **AND** no warning about missing intent trace is emitted in normal mode - -#### Scenario: Intent trace schema includes schema version field -- **GIVEN** an intent trace YAML block with `schema_version: "1.0"` -- **WHEN** the bridge adapter reads the block -- **THEN** it accepts the artifact and records the schema version -- **AND** if the schema version is unknown the adapter emits a clear error with supported versions - -### Requirement: Intent Trace Evidence Field in Archive -The system SHALL support an optional `evidence` field in change archive metadata pointing to the evidence JSON envelope file produced during implementation. - -#### Scenario: Archive metadata includes evidence reference -- **GIVEN** an archived change that generated a governance evidence artifact -- **WHEN** the archive metadata is read -- **THEN** the `evidence` field contains a relative path to the `.specfact/evidence/` JSON file -- **AND** the path resolves to a readable file on disk - -#### Scenario: Archive without evidence field is valid -- **GIVEN** an archived change that did not produce governance evidence -- **WHEN** the archive metadata is validated -- **THEN** validation passes without errors related to the missing evidence field diff --git a/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md b/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md new file mode 100644 index 00000000..48de3a0f --- /dev/null +++ b/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md @@ -0,0 +1,45 @@ +## ADDED Requirements + +### Requirement: OpenSpec and Spec Kit Import Runtime + +The requirements module SHALL provide runtime command flags that import native +OpenSpec change folders and Spec Kit feature folders into requirement evidence +through the core evidence adapter, and SHALL surface core gate findings in +validation output. The runtime SHALL contain no parsing, hashing, or gate +logic of its own and SHALL never write into upstream artifact directories. + +#### Scenario: Import from an OpenSpec change folder + +- **GIVEN** a project bundle and an OpenSpec change folder +- **WHEN** `specfact requirements import --from-openspec --bundle ` runs +- **THEN** the core adapter normalizes the artifacts into requirement records +- **AND** merged records persist to the bundle requirements sidecar exactly like `--from-file` imports +- **AND** the command reports imported counts and diagnostics. + +#### Scenario: Import from a Spec Kit feature folder + +- **GIVEN** a project bundle and a Spec Kit feature folder +- **WHEN** `specfact requirements import --from-speckit --bundle ` runs +- **THEN** the core adapter normalizes the artifacts into requirement records +- **AND** merged records persist to the bundle requirements sidecar. + +#### Scenario: Omitted source paths auto-detect conventional layouts + +- **GIVEN** a project root containing an `openspec/changes/` directory +- **WHEN** `specfact requirements import --from-openspec` runs without an explicit path +- **THEN** the conventional layout is detected and imported +- **AND** a clear error names the expected layouts when detection finds no source. + +#### Scenario: Validate surfaces gate findings with CI-usable exit codes + +- **GIVEN** a bundle with imported requirements that trigger core gate findings + (`scenario-unverified`, `stale-import`, `source-missing`, `ambiguous-mapping`) +- **WHEN** `specfact requirements validate --bundle --profile ` runs +- **THEN** the report lists each gate finding with its category and affected requirement IDs +- **AND** the command exits non-zero when the profile treats any finding as an error. + +#### Scenario: Runtime never writes upstream + +- **GIVEN** import and validation runs against OpenSpec and Spec Kit sources +- **WHEN** the commands complete +- **THEN** the upstream source directories are byte-identical to before the run. diff --git a/openspec/changes/openspec-01-intent-trace/tasks.md b/openspec/changes/openspec-01-intent-trace/tasks.md index fc8d320c..3f32774c 100644 --- a/openspec/changes/openspec-01-intent-trace/tasks.md +++ b/openspec/changes/openspec-01-intent-trace/tasks.md @@ -1,151 +1,38 @@ -# Tasks: OpenSpec Intent Trace — Bridge Adapter Integration +# Tasks: OpenSpec and Spec Kit Import Runtime for Requirement Evidence ## TDD / SDD order (enforced) -Per `openspec/config.yaml`, tests MUST precede production code for any behavior-changing task. - -Order: -1. Spec deltas (already in `specs/`) -2. Tests derived from spec scenarios — run and expect failure -3. Production code — implement until tests pass - -Do not implement production code until tests exist and have been run (expecting failure). +Spec deltas first, failing tests second, production code third. Record +evidence in `TDD_EVIDENCE.md`. --- -## 1. Create git worktree for this change - -- [ ] 1.1 Fetch latest and create a worktree with a new branch from `origin/dev`. - - [ ] 1.1.1 `git fetch origin` - - [ ] 1.1.2 `git worktree add ../specfact-cli-worktrees/feature/openspec-01-intent-trace -b feature/openspec-01-intent-trace origin/dev` - - [ ] 1.1.3 `cd ../specfact-cli-worktrees/feature/openspec-01-intent-trace` - - [ ] 1.1.4 `python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"` - - [ ] 1.1.5 `git branch --show-current` (verify `feature/openspec-01-intent-trace`) - -## 2. Define Intent Trace JSON Schema - -- [ ] 2.1 Create `openspec/schemas/intent-trace.schema.json`: - - [ ] 2.1.1 Root object with `schema_version` (required string), `intent_trace` object - - [ ] 2.1.2 `business_outcomes` array: each item requires `id` (string), `description` (string), `persona` (string) - - [ ] 2.1.3 `business_rules` array: each item requires `id`, `outcome_ref`, `given`, `when`, `then` - - [ ] 2.1.4 `architectural_constraints` array: each item requires `id`, `outcome_ref`, `constraint` - - [ ] 2.1.5 `requirement_refs` optional array of strings - - [ ] 2.1.6 `additionalProperties: false` on all objects for strict validation -- [ ] 2.2 Write schema unit tests in `tests/unit/specfact_cli/test_intent_trace_schema.py`: - - [ ] 2.2.1 Test valid complete intent trace block passes validation - - [ ] 2.2.2 Test missing required `id` field on BusinessOutcome fails - - [ ] 2.2.3 Test missing intent trace section (None) passes (optional) - - [ ] 2.2.4 Test unknown `schema_version` raises descriptive error -- [ ] 2.3 Run schema tests — expect failure: `hatch test -- tests/unit/specfact_cli/test_intent_trace_schema.py -v` -- [ ] 2.4 Record failing test evidence in `TDD_EVIDENCE.md` - -## 3. Write bridge adapter tests (TDD — expect failure) - -- [ ] 3.1 Review existing OpenSpec bridge adapter tests in `tests/` -- [ ] 3.2 Add `tests/unit/specfact_cli/test_openspec_bridge_intent.py`: - - [ ] 3.2.1 Test bridge import of proposal with `## Intent Trace` creates `.req.yaml` files (with `--import-intent`) - - [ ] 3.2.2 Test bridge import without `## Intent Trace` section is unchanged (backwards compatible) - - [ ] 3.2.3 Test `--import-intent` without `--overwrite` skips existing artifacts - - [ ] 3.2.4 Test `--import-intent --overwrite` updates existing artifacts - - [ ] 3.2.5 Test advisory warning on unresolved `requirement_refs` - - [ ] 3.2.6 Test `requirement_refs` parsed into imported task metadata -- [ ] 3.3 Add `tests/integration/test_openspec_intent_trace_e2e.py`: - - [ ] 3.3.1 End-to-end test: proposal with intent trace → bridge import → `.req.yaml` exists and validates -- [ ] 3.4 Run bridge tests — expect failure: `hatch test -- tests/unit/specfact_cli/test_openspec_bridge_intent.py -v` -- [ ] 3.5 Record failing test evidence in `TDD_EVIDENCE.md` - -## 4. Implement Intent Trace schema validator - -- [ ] 4.1 Add `src/specfact_cli/validators/intent_trace_validator.py`: - - [ ] 4.1.1 `validate_intent_trace(yaml_block: dict | None) -> ValidationResult` with `@require` and `@beartype` - - [ ] 4.1.2 Load `openspec/schemas/intent-trace.schema.json` (bundled resource) - - [ ] 4.1.3 Use `jsonschema.validate()` for schema check - - [ ] 4.1.4 Return structured errors with field path, message, and suggestion -- [ ] 4.2 Register schema file in `pyproject.toml` `[tool.hatch.build.targets.wheel]` force-include - -## 5. Extend OpenSpec bridge adapter with intent import - -> **Implementation constraint (from CHANGE_VALIDATION.md)**: `_parse_proposal_content()` in `openspec_parser.py` has return type `dict[str, str]`. Intent trace extraction MUST be done in `parse_change_proposal()` (returns `dict[str, Any]`) — NOT in `_parse_proposal_content()` — to avoid a `@beartype` type violation. - -- [ ] 5.1 Locate OpenSpec bridge adapter in `src/specfact_cli/adapters/` -- [ ] 5.2 Add `## Intent Trace` section parser: - - [ ] 5.2.1 Extract YAML fenced block under `## Intent Trace` heading from proposal Markdown - - [ ] 5.2.2 Parse YAML with `yaml.safe_load()` - - [ ] 5.2.3 Run `validate_intent_trace()` on the parsed block -- [ ] 5.3 Add `--import-intent` flag to `specfact sync bridge --adapter openspec` command: - - [ ] 5.3.1 `@require`: intent import requires requirements-01 module is installed (advisory check) - - [ ] 5.3.2 Write `.specfact/requirements/{id}.req.yaml` for each `BusinessOutcome` - - [ ] 5.3.3 Embed `BusinessRule` entries in parent `.req.yaml` files - - [ ] 5.3.4 Respect `--overwrite` flag; skip existing files otherwise -- [ ] 5.4 Add `requirement_refs` parsing from `tasks.md` task entries - - [ ] 5.4.1 Parse optional `requirement_refs:` YAML field on task lines - - [ ] 5.4.2 Include in imported task metadata in project bundle - - [ ] 5.4.3 Advisory warning for unresolved IDs -- [ ] 5.5 Extend `openspec validate --strict` hook to call `validate_intent_trace()` when section present -- [ ] 5.6 Add `@require`, `@ensure`, `@beartype` decorators to all new public API functions - -## 6. Passing tests and quality gates - -- [ ] 6.1 Run all new tests — expect passing: `hatch test -- tests/unit/specfact_cli/test_intent_trace*.py tests/unit/specfact_cli/test_openspec_bridge*.py tests/integration/test_openspec_intent_trace*.py -v` -- [ ] 6.2 Record passing test evidence in `TDD_EVIDENCE.md` -- [ ] 6.3 `hatch run format` -- [ ] 6.4 `hatch run type-check` -- [ ] 6.5 `hatch run lint` -- [ ] 6.6 `hatch run yaml-lint` -- [ ] 6.7 `hatch run contract-test` -- [ ] 6.8 `hatch run smart-test` - -## 7. Documentation +## 1. Branch and dependency guardrails -- [ ] 7.1 Update `docs/adapters/openspec.md` (or equivalent): - - [ ] 7.1.1 Document `## Intent Trace` section format with full YAML example - - [ ] 7.1.2 Document `--import-intent` and `--overwrite` flags - - [ ] 7.1.3 Document `requirement_refs` field on tasks -- [ ] 7.2 Update `docs/guides/openspec-journey.md` — add Intent Trace section -- [ ] 7.3 Ensure front-matter on all updated/new doc pages is valid (layout, title, permalink, description) -- [ ] 7.4 Update `docs/_layouts/default.html` sidebar navigation if new pages are added +- [ ] 1.1 Create worktree branch `feature/openspec-01-intent-trace` from `dev`. +- [ ] 1.2 Verify the paired core change (nold-ai/specfact-cli#350) import normalizers and gate helpers are merged, or coordinate parallel implementation with explicit core version pinning. +- [ ] 1.3 Re-run change validation for the rescoped proposal and refresh `CHANGE_VALIDATION.md`. -## 8. Version and changelog +## 2. Spec-first and test-first preparation -- [ ] 8.1 Bump minor version in `pyproject.toml`, `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py` -- [ ] 8.2 Add CHANGELOG.md entry under new `[X.Y.Z] - 2026-XX-XX` with Added/Changed sections +- [ ] 2.1 Finalize the `requirements-module` spec delta and cross-check scenario completeness. +- [ ] 2.2 Add tests: `--from-openspec`/`--from-speckit` import against fixtures, auto-detection success and clear-error cases, gate findings surfaced in `validate` with non-zero exit, and upstream directories untouched after runs. +- [ ] 2.3 Run targeted tests, capture failing-first output in `TDD_EVIDENCE.md`. -## 9. GitHub issue creation +## 3. Implementation -- [ ] 9.1 Create GitHub issue: - ```bash - gh issue create \ - --repo nold-ai/specfact-cli \ - --title "[Change] OpenSpec Intent Trace — Bridge Adapter Integration" \ - --body-file /tmp/github-issue-openspec-01.md \ - --label "enhancement" \ - --label "change-proposal" - ``` -- [ ] 9.2 Link issue to project: `gh project item-add 1 --owner nold-ai --url ` -- [ ] 9.3 Update `proposal.md` Source Tracking section with issue number and URL -- [ ] 9.4 Link branch to issue: `gh issue develop --repo nold-ai/specfact-cli --name feature/openspec-01-intent-trace` +- [ ] 3.1 Extend `packages/specfact-requirements/.../commands.py` import command with `--from-openspec [PATH]` and `--from-speckit [PATH]` options. +- [ ] 3.2 Extend `runtime.py` with thin delegation to the new core normalizers via the existing `_load_requirements_module` pattern, including `RequirementsCoreUnavailableError` messaging. +- [ ] 3.3 Surface gate findings in `validate` output and gate-relevant counts in `list`/`coverage`. +- [ ] 3.4 Contract decorators (`@beartype`, `@require`, `@ensure`) on all new public APIs. -## 10. Pull request +## 4. Validation and documentation -- [ ] 10.1 `git add` all changed files; commit with `feat: add OpenSpec Intent Trace section and bridge adapter import` -- [ ] 10.2 `git push -u origin feature/openspec-01-intent-trace` -- [ ] 10.3 Create PR: - ```bash - gh pr create \ - --repo nold-ai/specfact-cli \ - --base dev \ - --head feature/openspec-01-intent-trace \ - --title "feat: OpenSpec Intent Trace bridge adapter integration" \ - --body-file /tmp/pr-body-openspec-01.md - ``` -- [ ] 10.4 Link PR to project: `gh project item-add 1 --owner nold-ai --url ` -- [ ] 10.5 Set project status to "In Progress" +- [ ] 4.1 Re-run tests and quality gates until green; record passing evidence in `TDD_EVIDENCE.md`. +- [ ] 4.2 Update module docs and command overview with import-first examples and gate categories. +- [ ] 4.3 Run `openspec validate openspec-01-intent-trace --strict` and resolve all issues. -## Post-merge cleanup (after PR is merged) +## 5. Delivery -- [ ] Return to primary checkout: `cd .../specfact-cli` -- [ ] `git fetch origin` -- [ ] `git worktree remove ../specfact-cli-worktrees/feature/openspec-01-intent-trace` -- [ ] `git branch -d feature/openspec-01-intent-trace` -- [ ] `git worktree prune` -- [ ] (Optional) `git push origin --delete feature/openspec-01-intent-trace` +- [ ] 5.1 Open PR to `dev` with spec/test/code/docs evidence. +- [ ] 5.2 Sync GitHub issue #168 title/body with the rescoped proposal. From 10bafdd77992fc55468de5a2be8f169d1c9c8a62 Mon Sep 17 00:00:00 2001 From: Dominikus Nold Date: Mon, 13 Jul 2026 21:23:23 +0200 Subject: [PATCH 2/6] chore(openspec): archive requirements-02-module-commands Runtime shipped via PR #326 (issue #165 closed 2026-07-08). Archive merges delta specs into canonical openspec/specs (requirements-module, backlog-adapter, module-io-contract); the two deltas targeting not-yet-existing canonical specs were converted from MODIFIED to ADDED to seed those specs. Co-Authored-By: Claude Fable 5 --- openspec/CHANGE_ORDER.md | 2 +- .../.openspec.yaml | 0 .../CHANGE_VALIDATION.md | 0 .../TDD_EVIDENCE.md | 0 .../design.md | 0 .../proposal.md | 0 .../specs/backlog-adapter/spec.md | 2 +- .../specs/module-io-contract/spec.md | 2 +- .../specs/requirements-module/spec.md | 0 .../tasks.md | 0 openspec/specs/backlog-adapter/spec.md | 24 ++++++++++++ openspec/specs/module-io-contract/spec.md | 24 ++++++++++++ openspec/specs/requirements-module/spec.md | 39 +++++++++++++++++++ 13 files changed, 90 insertions(+), 3 deletions(-) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/.openspec.yaml (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/CHANGE_VALIDATION.md (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/TDD_EVIDENCE.md (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/design.md (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/proposal.md (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/specs/backlog-adapter/spec.md (96%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/specs/module-io-contract/spec.md (97%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/specs/requirements-module/spec.md (100%) rename openspec/changes/{requirements-02-module-commands => archive/2026-07-13-requirements-02-module-commands}/tasks.md (100%) create mode 100644 openspec/specs/backlog-adapter/spec.md create mode 100644 openspec/specs/module-io-contract/spec.md create mode 100644 openspec/specs/requirements-module/spec.md diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index 2ea28e52..d179da82 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -66,7 +66,7 @@ AI IDE, rerun, and compare improved evidence. | Order | Change folder | GitHub # | Positioning | Blocked by | |---:|---|---|---|---| -| 1 | `requirements-02-module-commands` | [#165](https://github.com/nold-ai/specfact-cli-modules/issues/165) | Import/normalize requirement context for evidence | core requirements input model | +| 1 | `requirements-02-module-commands` | [#165](https://github.com/nold-ai/specfact-cli-modules/issues/165) | Import/normalize requirement context for evidence (shipped via PR #326, archived 2026-07-13) | core requirements input model | | 2 | `openspec-01-intent-trace` | [#168](https://github.com/nold-ai/specfact-cli-modules/issues/168) | Import-first OpenSpec and Spec Kit requirement evidence runtime with gate surfacing (rescoped 2026-07-13) | requirements-02 runtime; core nold-ai/specfact-cli#350 contracts | | 3 | `architecture-01-solution-layer` | [#164](https://github.com/nold-ai/specfact-cli-modules/issues/164) | Architecture-boundary validation input | core architecture-boundary contracts | | 4 | `sync-01-unified-kernel` | [#157](https://github.com/nold-ai/specfact-cli-modules/issues/157) | Preview/apply safety only where validation adapters need it | project/runtime safety specs | diff --git a/openspec/changes/requirements-02-module-commands/.openspec.yaml b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/.openspec.yaml similarity index 100% rename from openspec/changes/requirements-02-module-commands/.openspec.yaml rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/.openspec.yaml diff --git a/openspec/changes/requirements-02-module-commands/CHANGE_VALIDATION.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/CHANGE_VALIDATION.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/CHANGE_VALIDATION.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/CHANGE_VALIDATION.md diff --git a/openspec/changes/requirements-02-module-commands/TDD_EVIDENCE.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/TDD_EVIDENCE.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/TDD_EVIDENCE.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/TDD_EVIDENCE.md diff --git a/openspec/changes/requirements-02-module-commands/design.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/design.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/design.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/design.md diff --git a/openspec/changes/requirements-02-module-commands/proposal.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/proposal.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/proposal.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/proposal.md diff --git a/openspec/changes/requirements-02-module-commands/specs/backlog-adapter/spec.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/backlog-adapter/spec.md similarity index 96% rename from openspec/changes/requirements-02-module-commands/specs/backlog-adapter/spec.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/backlog-adapter/spec.md index dae70768..af018730 100644 --- a/openspec/changes/requirements-02-module-commands/specs/backlog-adapter/spec.md +++ b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/backlog-adapter/spec.md @@ -1,4 +1,4 @@ -## MODIFIED Requirements +## ADDED Requirements ### Requirement: Backlog Adapter diff --git a/openspec/changes/requirements-02-module-commands/specs/module-io-contract/spec.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/module-io-contract/spec.md similarity index 97% rename from openspec/changes/requirements-02-module-commands/specs/module-io-contract/spec.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/module-io-contract/spec.md index 28f851e8..e35d612f 100644 --- a/openspec/changes/requirements-02-module-commands/specs/module-io-contract/spec.md +++ b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/module-io-contract/spec.md @@ -1,4 +1,4 @@ -## MODIFIED Requirements +## ADDED Requirements ### Requirement: Module Io Contract diff --git a/openspec/changes/requirements-02-module-commands/specs/requirements-module/spec.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/requirements-module/spec.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/specs/requirements-module/spec.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/specs/requirements-module/spec.md diff --git a/openspec/changes/requirements-02-module-commands/tasks.md b/openspec/changes/archive/2026-07-13-requirements-02-module-commands/tasks.md similarity index 100% rename from openspec/changes/requirements-02-module-commands/tasks.md rename to openspec/changes/archive/2026-07-13-requirements-02-module-commands/tasks.md diff --git a/openspec/specs/backlog-adapter/spec.md b/openspec/specs/backlog-adapter/spec.md new file mode 100644 index 00000000..853d0970 --- /dev/null +++ b/openspec/specs/backlog-adapter/spec.md @@ -0,0 +1,24 @@ +# backlog-adapter Specification + +## Purpose +TBD - created by archiving change requirements-02-module-commands. Update Purpose after archive. +## Requirements +### Requirement: Backlog Adapter + +The system SHALL expose source-attributed backlog requirement snippets to +requirements import workflows. + +#### Scenario: Adapter returns acceptance criteria payload for import + +- **GIVEN** a backlog item selected for requirement context import +- **WHEN** requirements import receives adapter source fields +- **THEN** title, description, acceptance-criteria text, and item identity are available +- **AND** normalization proceeds without provider-specific parsing in command handlers. + +#### Scenario: Missing acceptance criteria is surfaced explicitly + +- **GIVEN** a backlog item with no acceptance criteria +- **WHEN** normalization runs +- **THEN** the item is reported as incomplete input +- **AND** command output includes the backlog item identifier. + diff --git a/openspec/specs/module-io-contract/spec.md b/openspec/specs/module-io-contract/spec.md new file mode 100644 index 00000000..6dfcf33e --- /dev/null +++ b/openspec/specs/module-io-contract/spec.md @@ -0,0 +1,24 @@ +# module-io-contract Specification + +## Purpose +TBD - created by archiving change requirements-02-module-commands. Update Purpose after archive. +## Requirements +### Requirement: Module Io Contract + +The requirements module SHALL consume core requirements context adapter helpers +through the existing `ModuleIOContract` boundary. + +#### Scenario: Import operation maps backlog items to requirements + +- **GIVEN** source-attributed requirement records imported by a module command +- **WHEN** the runtime stores normalized requirements on a `ProjectBundle` +- **THEN** requirements are added under the `requirements.inputs` extension with stable IDs +- **AND** parse diagnostics remain available to the module runtime for partial failures. + +#### Scenario: Validate operation enforces profile schema + +- **GIVEN** a requirements bundle and active validation profile +- **WHEN** the runtime delegates to core requirements context validation +- **THEN** missing evidence links and weak context are reported +- **AND** validation severity respects the selected evidence strictness profile. + diff --git a/openspec/specs/requirements-module/spec.md b/openspec/specs/requirements-module/spec.md new file mode 100644 index 00000000..3edb4b99 --- /dev/null +++ b/openspec/specs/requirements-module/spec.md @@ -0,0 +1,39 @@ +# requirements-module Specification + +## Purpose +TBD - created by archiving change requirements-02-module-commands. Update Purpose after archive. +## Requirements +### Requirement: Requirements Runtime Commands + +The system SHALL provide module-owned `specfact requirements ...` commands for +importing, validating, listing, and inspecting upstream requirement context as +validation evidence. + +#### Scenario: Import local requirement records into bundle extensions + +- **GIVEN** a local JSON or YAML file containing source-attributed requirement records +- **WHEN** `specfact requirements import --from-file --bundle ` runs +- **THEN** valid records are stored under the bundle's `requirements.inputs` extension +- **AND** invalid records are returned as bounded diagnostics. + +#### Scenario: Validate requirement context by profile + +- **GIVEN** a project bundle with normalized requirement inputs +- **WHEN** `specfact requirements validate --bundle --profile enterprise` runs +- **THEN** validation delegates to the core profile-aware requirements context helper +- **AND** missing downstream evidence links are reported as failed validation. + +#### Scenario: List requirements with coverage summary + +- **GIVEN** requirement inputs are present on a project bundle +- **WHEN** `specfact requirements list --bundle --show-coverage --format json` runs +- **THEN** each requirement ID and title is returned +- **AND** the output includes a machine-readable coverage summary. + +#### Scenario: No authoring command is exposed + +- **GIVEN** the requirements module is installed +- **WHEN** the user inspects `specfact requirements --help` +- **THEN** import, validate, list, and coverage commands are visible +- **AND** requirement authoring commands are not exposed. + From f7a4d69426340a1b9cadfcdde5ce8e64401406ca Mon Sep 17 00:00:00 2001 From: Dominikus Nold Date: Mon, 13 Jul 2026 21:39:37 +0200 Subject: [PATCH 3/6] docs(openspec): fold profile-resolution fix into openspec-01 runtime tasks Validate command switches from a hardcoded startup profile default to core layered-config resolution when --profile is omitted; explicit flag wins. Paired with core scope in nold-ai/specfact-cli#350. Co-Authored-By: Claude Fable 5 --- openspec/changes/openspec-01-intent-trace/tasks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openspec/changes/openspec-01-intent-trace/tasks.md b/openspec/changes/openspec-01-intent-trace/tasks.md index 3f32774c..839377ca 100644 --- a/openspec/changes/openspec-01-intent-trace/tasks.md +++ b/openspec/changes/openspec-01-intent-trace/tasks.md @@ -17,6 +17,7 @@ evidence in `TDD_EVIDENCE.md`. - [ ] 2.1 Finalize the `requirements-module` spec delta and cross-check scenario completeness. - [ ] 2.2 Add tests: `--from-openspec`/`--from-speckit` import against fixtures, auto-detection success and clear-error cases, gate findings surfaced in `validate` with non-zero exit, and upstream directories untouched after runs. +- [ ] 2.2b Add tests: omitted `--profile` resolves the effective profile from layered configuration instead of a hardcoded `startup` default; explicit `--profile` overrides. - [ ] 2.3 Run targeted tests, capture failing-first output in `TDD_EVIDENCE.md`. ## 3. Implementation @@ -24,6 +25,7 @@ evidence in `TDD_EVIDENCE.md`. - [ ] 3.1 Extend `packages/specfact-requirements/.../commands.py` import command with `--from-openspec [PATH]` and `--from-speckit [PATH]` options. - [ ] 3.2 Extend `runtime.py` with thin delegation to the new core normalizers via the existing `_load_requirements_module` pattern, including `RequirementsCoreUnavailableError` messaging. - [ ] 3.3 Surface gate findings in `validate` output and gate-relevant counts in `list`/`coverage`. +- [ ] 3.3b Change the `validate` command's profile default from hardcoded `startup` to core layered-config resolution when the flag is omitted (delegated to the core helper; no config parsing in the module). - [ ] 3.4 Contract decorators (`@beartype`, `@require`, `@ensure`) on all new public APIs. ## 4. Validation and documentation From c6b7893ffa56a7d951da3ba36d0fe23ed11022b6 Mon Sep 17 00:00:00 2001 From: Dom <39115308+djm81@users.noreply.github.com> Date: Tue, 14 Jul 2026 21:46:35 +0200 Subject: [PATCH 4/6] feat(requirements): import OpenSpec requirement evidence (#342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - implement #168 using the shipped core #350 native OpenSpec and Spec Kit import helpers - add fail-closed schema handling, conservative source auto-detection, profile-aware validation, and core gate-count reporting - update `docs/Gemfile.lock` to concurrent-ruby 1.3.7 ## Scope This PR delivers the requirements-module adapter work for #168. It also includes a small companion `specfact-code-review` correction: the KISS parameter-count analyzer now identifies actual Typer applications before exempting their callbacks. That prevents the requirements CLI from producing a false-positive warning while retaining checks for unrelated `*.command` and `*.callback` decorators. ## Bundle impact - `nold-ai/specfact-requirements` released as `0.2.3`, with synchronized registry artifact and integrity metadata. - `nold-ai/specfact-code-review` released as `0.47.51`, with synchronized registry artifact and integrity metadata. ## Validation - `hatch run test` — 884 passed, with 2 third-party deprecation warnings. - `hatch run smart-test` — 884 passed. - `hatch run contract-test` — 28 passed. - `openspec validate openspec-01-intent-trace --strict` — passed. - Changed-code review — 0 findings. - Module signature/version-bump verification — passed. ## Documentation - Updated the Requirements bundle overview, generated command references, and `llms.txt` for optional native-source paths. - Recorded failing-before and passing-after evidence in the active OpenSpec change. ## CI and checklist - [x] Formatter, lint, type, import-boundary, manifest, and signature checks pass. - [x] Requirements and code-review regression coverage added. - [x] Registry artifacts match the released manifest versions. - [x] No CI-skip markers are used. Closes #168 --------- Co-authored-by: Dominikus Nold Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/Gemfile.lock | 2 +- docs/bundles/requirements/overview.md | 32 ++- docs/reference/commands.generated.json | 195 +++++++++++++++--- docs/reference/commands.generated.md | 44 ++-- llms.txt | 44 ++-- .../CHANGE_VALIDATION.md | 38 +++- .../openspec-01-intent-trace/TDD_EVIDENCE.md | 78 +++++++ .../openspec-01-intent-trace/design.md | 28 ++- .../openspec-01-intent-trace/proposal.md | 13 +- .../specs/requirements-module/spec.md | 27 ++- .../changes/openspec-01-intent-trace/tasks.md | 12 +- .../specfact-code-review/module-package.yaml | 6 +- .../tools/radon_runner.py | 97 ++++++--- .../specfact-requirements/module-package.yaml | 8 +- .../requirements/commands.py | 98 +++++++-- .../requirements/runtime.py | 125 ++++++++++- registry/index.json | 15 +- .../specfact-code-review-0.47.51.tar.gz | Bin 0 -> 62832 bytes ...specfact-code-review-0.47.51.tar.gz.sha256 | 1 + .../specfact-requirements-0.2.3.tar.gz | Bin 0 -> 5697 bytes .../specfact-requirements-0.2.3.tar.gz.sha256 | 1 + scripts/generate-command-overview.py | 12 +- .../test_command_apps.py | 130 ++++++++++++ .../docs/test_bundle_overview_cli_examples.py | 16 ++ .../unit/docs/test_llms_overview_freshness.py | 10 + .../tools/test_radon_runner.py | 83 +++++--- .../test_requirements_runtime.py | 124 +++++++++++ 27 files changed, 1044 insertions(+), 195 deletions(-) create mode 100644 openspec/changes/openspec-01-intent-trace/TDD_EVIDENCE.md create mode 100644 registry/modules/specfact-code-review-0.47.51.tar.gz create mode 100644 registry/modules/specfact-code-review-0.47.51.tar.gz.sha256 create mode 100644 registry/modules/specfact-requirements-0.2.3.tar.gz create mode 100644 registry/modules/specfact-requirements-0.2.3.tar.gz.sha256 diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 783241ae..82206bde 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -5,7 +5,7 @@ GEM public_suffix (>= 2.0.2, < 8.0) base64 (0.3.0) colorator (1.1.0) - concurrent-ruby (1.3.6) + concurrent-ruby (1.3.7) csv (3.3.5) em-websocket (0.5.3) eventmachine (>= 0.12.9) diff --git a/docs/bundles/requirements/overview.md b/docs/bundles/requirements/overview.md index 7a43e94f..4d7076ee 100644 --- a/docs/bundles/requirements/overview.md +++ b/docs/bundles/requirements/overview.md @@ -18,7 +18,7 @@ planning or product-management system. ## Prerequisites -- SpecFact CLI with core requirements context helpers +- SpecFact CLI `>=0.52.0,<1.0.0` - Bundle installed: `specfact module install nold-ai/specfact-requirements` - A project bundle directory, usually created by the [Project](/bundles/project/overview/) bundle - Local JSON or YAML requirement records with source attribution @@ -30,10 +30,10 @@ group. | Command | Purpose | |--------|---------| -| `import` | Import local JSON/YAML requirement records into a project bundle | -| `validate` | Validate attached requirement context against a profile | -| `list` | List attached requirement records, optionally with coverage | -| `coverage` | Print coverage counts for downstream evidence links | +| `import` | Import local records or native OpenSpec/Spec Kit evidence into a project bundle | +| `validate` | Validate attached requirement context against the selected or layered profile | +| `list` | List attached requirement records with optional coverage and core gate counts | +| `coverage` | Print coverage and core gate-finding counts | ## Input shape @@ -69,11 +69,32 @@ Each record follows the core `RequirementInput` model and must include ```bash specfact requirements import --from-file requirements.json --bundle .specfact/projects/shop --format json +specfact requirements import --from-openspec openspec/changes/widget-evidence --bundle .specfact/projects/shop --format json +specfact requirements import --from-speckit specs/001-widget-rendering --bundle .specfact/projects/shop --format json specfact requirements list --bundle .specfact/projects/shop --show-coverage --format json specfact requirements validate --bundle .specfact/projects/shop --profile enterprise --format json specfact requirements coverage --bundle .specfact/projects/shop --format json ``` +For native imports, a source path is optional. `--from-openspec` discovers a +single conventional change below `openspec/changes/`; `--from-speckit` +discovers a single feature below `specs/`. Multiple or missing candidates are a +clear error so the command never guesses. Pass a path whenever the project has +more than one candidate. + +Native imports are read-only and supported only for core's default, +fixture-backed OpenSpec and Spec Kit Markdown profiles. A core +`unsupported-source-schema` diagnostic is returned unchanged, and no partial +requirements sidecar is written. The bundle does not infer upstream tool +versions, fall back to another parser, or add upstream metadata. + +When `--profile` is omitted, validation uses the effective layered core +configuration. Explicit `--profile` still wins. Core findings such as +`scenario-unverified`, `stale-import`, `source-missing`, +`ambiguous-mapping`, and `unsupported-profile-field` remain machine-readable; +the list and coverage commands include their counts without re-evaluating the +gates. + ## Storage The command runtime rehydrates the core `requirements.inputs` extension before @@ -87,6 +108,7 @@ The bundle is read-first and evidence-focused. - It imports and normalizes source-attributed requirement context. - It reports validation findings and coverage gaps. +- It delegates parsing, hashing, compatibility, and gate evaluation to core. - It does not expose authoring templates. - It does not perform bidirectional backlog sync or ceremony automation. diff --git a/docs/reference/commands.generated.json b/docs/reference/commands.generated.json index 21294e57..8a7d726d 100644 --- a/docs/reference/commands.generated.json +++ b/docs/reference/commands.generated.json @@ -190,7 +190,13 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog ceremony flow", "deprecated": false, @@ -206,7 +212,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog ceremony pi-summary", "deprecated": false, @@ -222,7 +234,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog ceremony planning", "deprecated": false, @@ -238,7 +256,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog ceremony refinement", "deprecated": false, @@ -252,7 +276,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog ceremony standup", "deprecated": false, @@ -268,7 +298,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog daily", "deprecated": false, @@ -359,7 +395,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ITEM_ID", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog delta impact", "deprecated": false, @@ -396,7 +438,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER_ARG", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact backlog delta status", "deprecated": false, @@ -500,7 +548,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "ADAPTER", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact backlog refine", "deprecated": false, @@ -665,7 +719,13 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact code drift detect", "deprecated": false, @@ -683,7 +743,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact code import", "deprecated": false, @@ -737,7 +803,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact code import from-code", "deprecated": false, @@ -975,7 +1047,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "FILES", + "nargs": -1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact code review review run", "deprecated": false, @@ -1043,7 +1121,18 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE_NAME", + "nargs": 1, + "required": true + }, + { + "name": "REPO_PATH", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact code validate sidecar init", "deprecated": false, @@ -1057,7 +1146,18 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE_NAME", + "nargs": 1, + "required": true + }, + { + "name": "REPO_PATH", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact code validate sidecar run", "deprecated": false, @@ -1113,7 +1213,13 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact govern enforce sdd", "deprecated": false, @@ -1164,7 +1270,13 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "PATCH_FILE", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact govern patch apply", "deprecated": false, @@ -1566,7 +1678,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "BUNDLE", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact project sync intelligent", "deprecated": false, @@ -1735,7 +1853,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "SOURCE_PATH", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact requirements import", "deprecated": false, @@ -1744,7 +1868,9 @@ "options": [ "--bundle", "--format", - "--from-file" + "--from-file", + "--from-openspec", + "--from-speckit" ], "owner_package": "nold-ai/specfact-requirements", "owner_repo": "nold-ai/specfact-cli-modules", @@ -1811,7 +1937,18 @@ ] }, { - "arguments": [], + "arguments": [ + { + "name": "OLD_SPEC", + "nargs": 1, + "required": true + }, + { + "name": "NEW_SPEC", + "nargs": 1, + "required": true + } + ], "bare_invocation": "executes", "command": "specfact spec backward-compat", "deprecated": false, @@ -1825,7 +1962,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "SPEC_PATH", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact spec generate-tests", "deprecated": false, @@ -1865,7 +2008,13 @@ "subcommands": [] }, { - "arguments": [], + "arguments": [ + { + "name": "SPEC_PATH", + "nargs": 1, + "required": false + } + ], "bare_invocation": "executes", "command": "specfact spec validate", "deprecated": false, diff --git a/docs/reference/commands.generated.md b/docs/reference/commands.generated.md index 315987f9..44291b6e 100644 --- a/docs/reference/commands.generated.md +++ b/docs/reference/commands.generated.md @@ -21,32 +21,32 @@ This file is generated from the current module command trees. Do not edit by han | `specfact backlog auth github` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --base-url, --client-id, --scopes; args: - | - | | | `specfact backlog auth status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | - | | | `specfact backlog ceremony` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | flow, pi-summary, planning, refinement, standup | | -| `specfact backlog ceremony flow` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony pi-summary` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony planning` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony refinement` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | - | | -| `specfact backlog ceremony standup` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog daily` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --annotations, --assignee, --blockers, --blockers-first, --comments, --copilot-export, --first-comments, --first-issues, --github-token, --id, --interactive, --iteration, --labels, --last-comments, --last-issues, --limit, --mode, --no-show-unassigned, --patch, --post, --release, --repo-name, --repo-owner, --search, --show-unassigned, --sprint, --state, --suggest-next, --summarize, --summarize-to, --tags, --today, --unassigned-only, --yesterday; args: - | - | | +| `specfact backlog ceremony flow` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony pi-summary` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony planning` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony refinement` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: ADAPTER (required) | - | | +| `specfact backlog ceremony standup` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog daily` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --annotations, --assignee, --blockers, --blockers-first, --comments, --copilot-export, --first-comments, --first-issues, --github-token, --id, --interactive, --iteration, --labels, --last-comments, --last-issues, --limit, --mode, --no-show-unassigned, --patch, --post, --release, --repo-name, --repo-owner, --search, --show-unassigned, --sprint, --state, --suggest-next, --summarize, --summarize-to, --tags, --today, --unassigned-only, --yesterday; args: ADAPTER (required) | - | | | `specfact backlog delta` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | cost-estimate, impact, rollback-analysis, status | | | `specfact backlog delta cost-estimate` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | -| `specfact backlog delta impact` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --template; args: - | - | | +| `specfact backlog delta impact` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --template; args: ITEM_ID (required) | - | | | `specfact backlog delta rollback-analysis` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | -| `specfact backlog delta status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --repo-name, --repo-owner, --since, --template; args: - | - | | +| `specfact backlog delta status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --repo-name, --repo-owner, --since, --template; args: ADAPTER_ARG | - | | | `specfact backlog diff` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | | `specfact backlog init-config` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --force; args: - | - | | | `specfact backlog map-fields` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-base-url, --ado-framework, --ado-org, --ado-project, --ado-token, --github-project-id, --github-project-v2-id, --github-type-field-id, --github-type-option, --non-interactive, --provider, --reset; args: - | - | | | `specfact backlog promote` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --item-id, --project-id, --template, --to-status; args: - | - | | -| `specfact backlog refine` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --assignee, --auto-accept-high-confidence, --auto-bundle, --bundle, --check-dor, --custom-field-mapping, --export-to-tmp, --first-comments, --first-issues, --framework, --github-token, --id, --ignore-refined, --import-from-tmp, --iteration, --labels, --last-comments, --last-issues, --limit, --no-ignore-refined, --no-preview, --openspec-comment, --persona, --preview, --release, --repo-name, --repo-owner, --search, --sprint, --state, --tags, --template, --tmp-file, --write; args: - | - | | +| `specfact backlog refine` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --assignee, --auto-accept-high-confidence, --auto-bundle, --bundle, --check-dor, --custom-field-mapping, --export-to-tmp, --first-comments, --first-issues, --framework, --github-token, --id, --ignore-refined, --import-from-tmp, --iteration, --labels, --last-comments, --last-issues, --limit, --no-ignore-refined, --no-preview, --openspec-comment, --persona, --preview, --release, --repo-name, --repo-owner, --search, --sprint, --state, --tags, --template, --tmp-file, --write; args: ADAPTER (required) | - | | | `specfact backlog sync` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --force-baseline-overwrite, --output-format, --project-id, --template; args: - | - | | | `specfact backlog verify-readiness` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --target-items, --template; args: - | - | | | `specfact code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --install-completion, --show-completion; args: - | analyze, drift, import, repro, validate | | | `specfact code analyze` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | contracts | | | `specfact code analyze contracts` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --bundle, --repo; args: - | - | | | `specfact code drift` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | detect | | -| `specfact code drift detect` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --format, --out, --repo; args: - | - | | -| `specfact code import` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: - | from-bridge, from-code | | +| `specfact code drift detect` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --format, --out, --repo; args: BUNDLE | - | | +| `specfact code import` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: BUNDLE | from-bridge, from-code | | | `specfact code import from-bridge` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --adapter, --dry-run, --force, --out-branch, --repo, --report, --write; args: - | - | | -| `specfact code import from-code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: - | - | | +| `specfact code import from-code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: BUNDLE | - | | | `specfact code repro` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --budget, --crosshair-per-path-timeout, --crosshair-required, --fail-fast, --fix, --out, --repo, --sidecar, --sidecar-bundle, --verbose; args: - | setup | | | `specfact code repro setup` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --install-crosshair, --repo; args: - | - | | | `specfact code review` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --install-completion, --show-completion; args: - | review | | @@ -59,17 +59,17 @@ This file is generated from the current module command trees. Do not edit by han | `specfact code review review rules init` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --ide; args: - | - | | | `specfact code review review rules show` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | -; args: - | - | | | `specfact code review review rules update` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --ide; args: - | - | | -| `specfact code review review run` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --bug-hunt, --enforcement, --exclude-tests, --fix, --focus, --include-noise, --include-tests, --instructions, --interactive, --json, --level, --mode, --no-tests, --out, --path, --preview-fixes, --scope, --score-only, --suppress-noise, --with-mutation; args: - | - | | +| `specfact code review review run` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --bug-hunt, --enforcement, --exclude-tests, --fix, --focus, --include-noise, --include-tests, --instructions, --interactive, --json, --level, --mode, --no-tests, --out, --path, --preview-fixes, --scope, --score-only, --suppress-noise, --with-mutation; args: FILES | - | | | `specfact code validate` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | sidecar | | | `specfact code validate sidecar` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | init, run | | -| `specfact code validate sidecar init` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | - | | -| `specfact code validate sidecar run` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --no-run-crosshair, --no-run-specmatic, --run-crosshair, --run-specmatic; args: - | - | | +| `specfact code validate sidecar init` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: BUNDLE_NAME (required), REPO_PATH (required) | - | | +| `specfact code validate sidecar run` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --no-run-crosshair, --no-run-specmatic, --run-crosshair, --run-specmatic; args: BUNDLE_NAME (required), REPO_PATH (required) | - | | | `specfact govern` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --install-completion, --show-completion; args: - | enforce, patch | | | `specfact govern enforce` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | -; args: - | sdd, stage | | -| `specfact govern enforce sdd` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --no-interactive, --out, --output-format, --sdd; args: - | - | | +| `specfact govern enforce sdd` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --no-interactive, --out, --output-format, --sdd; args: BUNDLE | - | | | `specfact govern enforce stage` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --preset; args: - | - | | | `specfact govern patch` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | -; args: - | apply | | -| `specfact govern patch apply` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --dry-run, --write, --yes; args: - | - | | +| `specfact govern patch apply` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --dry-run, --write, --yes; args: PATCH_FILE (required) | - | | | `specfact project` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --install-completion, --show-completion; args: - | devops-flow, export, export-roadmap, health-check, import, init-personas, link-backlog, lock, locks, merge, regenerate, resolve-conflict, snapshot, sync, unlock, version | | | `specfact project devops-flow` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --action, --bundle, --no-interactive, --project-name, --repo, --stage, --verbose; args: - | - | | | `specfact project export` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --list-personas, --no-interactive, --out, --output, --output-dir, --persona, --repo, --stdout, --template; args: - | - | | @@ -86,7 +86,7 @@ This file is generated from the current module command trees. Do not edit by han | `specfact project snapshot` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --no-interactive, --output, --project-name, --repo; args: - | - | | | `specfact project sync` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | -; args: - | bridge, intelligent, repository | | | `specfact project sync bridge` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --adapter, --add-progress-comment, --ado-base-url, --ado-org, --ado-project, --ado-token, --ado-work-item-type, --all, --backlog-ids, --backlog-ids-file, --bidirectional, --bundle, --change-ids, --code-repo, --ensure-compliance, --export-to-tmp, --external-base-path, --feature, --github-token, --import-from-tmp, --include-archived, --interactive, --interval, --mode, --no-add-progress-comment, --no-gh-cli, --no-include-archived, --no-sanitize, --no-track-code-changes, --no-update-existing, --overwrite, --repo, --repo-name, --repo-owner, --sanitize, --target-repo, --tmp-file, --track-code-changes, --update-existing, --use-gh-cli, --watch; args: - | - | | -| `specfact project sync intelligent` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --code-to-spec, --repo, --spec-to-code, --tests, --watch; args: - | - | | +| `specfact project sync intelligent` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --code-to-spec, --repo, --spec-to-code, --tests, --watch; args: BUNDLE | - | | | `specfact project sync repository` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --confidence, --interval, --repo, --target, --watch; args: - | - | | | `specfact project unlock` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --no-interactive, --repo, --section; args: - | - | | | `specfact project version` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | -; args: - | bump, check, set | | @@ -95,11 +95,11 @@ This file is generated from the current module command trees. Do not edit by han | `specfact project version set` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --repo, --version; args: - | - | | | `specfact requirements` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --install-completion, --show-completion; args: - | coverage, import, list, validate | | | `specfact requirements coverage` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format; args: - | - | | -| `specfact requirements import` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --from-file; args: - | - | | +| `specfact requirements import` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --from-file, --from-openspec, --from-speckit; args: SOURCE_PATH | - | | | `specfact requirements list` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --show-coverage; args: - | - | | | `specfact requirements validate` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --profile; args: - | - | | | `specfact spec` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --install-completion, --show-completion; args: - | backward-compat, generate-tests, mock, validate | | -| `specfact spec backward-compat` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | -; args: - | - | | -| `specfact spec generate-tests` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --out, --output; args: - | - | | +| `specfact spec backward-compat` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | -; args: OLD_SPEC (required), NEW_SPEC (required) | - | | +| `specfact spec generate-tests` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --out, --output; args: SPEC_PATH | - | | | `specfact spec mock` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --examples, --no-interactive, --port, --spec, --strict; args: - | - | | -| `specfact spec validate` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --no-interactive, --previous; args: - | - | | +| `specfact spec validate` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --no-interactive, --previous; args: SPEC_PATH | - | | diff --git a/llms.txt b/llms.txt index 2cd332d2..f213369b 100644 --- a/llms.txt +++ b/llms.txt @@ -25,32 +25,32 @@ This file is generated from the current module command trees. Do not edit by han | `specfact backlog auth github` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --base-url, --client-id, --scopes; args: - | - | | | `specfact backlog auth status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | - | | | `specfact backlog ceremony` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | flow, pi-summary, planning, refinement, standup | | -| `specfact backlog ceremony flow` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony pi-summary` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony planning` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog ceremony refinement` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | - | | -| `specfact backlog ceremony standup` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: - | - | | -| `specfact backlog daily` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --annotations, --assignee, --blockers, --blockers-first, --comments, --copilot-export, --first-comments, --first-issues, --github-token, --id, --interactive, --iteration, --labels, --last-comments, --last-issues, --limit, --mode, --no-show-unassigned, --patch, --post, --release, --repo-name, --repo-owner, --search, --show-unassigned, --sprint, --state, --suggest-next, --summarize, --summarize-to, --tags, --today, --unassigned-only, --yesterday; args: - | - | | +| `specfact backlog ceremony flow` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony pi-summary` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony planning` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog ceremony refinement` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: ADAPTER (required) | - | | +| `specfact backlog ceremony standup` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --mode; args: ADAPTER (required) | - | | +| `specfact backlog daily` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --annotations, --assignee, --blockers, --blockers-first, --comments, --copilot-export, --first-comments, --first-issues, --github-token, --id, --interactive, --iteration, --labels, --last-comments, --last-issues, --limit, --mode, --no-show-unassigned, --patch, --post, --release, --repo-name, --repo-owner, --search, --show-unassigned, --sprint, --state, --suggest-next, --summarize, --summarize-to, --tags, --today, --unassigned-only, --yesterday; args: ADAPTER (required) | - | | | `specfact backlog delta` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | -; args: - | cost-estimate, impact, rollback-analysis, status | | | `specfact backlog delta cost-estimate` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | -| `specfact backlog delta impact` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --template; args: - | - | | +| `specfact backlog delta impact` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --template; args: ITEM_ID (required) | - | | | `specfact backlog delta rollback-analysis` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | -| `specfact backlog delta status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --repo-name, --repo-owner, --since, --template; args: - | - | | +| `specfact backlog delta status` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --repo-name, --repo-owner, --since, --template; args: ADAPTER_ARG | - | | | `specfact backlog diff` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --project-id, --template; args: - | - | | | `specfact backlog init-config` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --force; args: - | - | | | `specfact backlog map-fields` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-base-url, --ado-framework, --ado-org, --ado-project, --ado-token, --github-project-id, --github-project-v2-id, --github-type-field-id, --github-type-option, --non-interactive, --provider, --reset; args: - | - | | | `specfact backlog promote` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --item-id, --project-id, --template, --to-status; args: - | - | | -| `specfact backlog refine` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --assignee, --auto-accept-high-confidence, --auto-bundle, --bundle, --check-dor, --custom-field-mapping, --export-to-tmp, --first-comments, --first-issues, --framework, --github-token, --id, --ignore-refined, --import-from-tmp, --iteration, --labels, --last-comments, --last-issues, --limit, --no-ignore-refined, --no-preview, --openspec-comment, --persona, --preview, --release, --repo-name, --repo-owner, --search, --sprint, --state, --tags, --template, --tmp-file, --write; args: - | - | | +| `specfact backlog refine` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --ado-org, --ado-project, --ado-team, --ado-token, --assignee, --auto-accept-high-confidence, --auto-bundle, --bundle, --check-dor, --custom-field-mapping, --export-to-tmp, --first-comments, --first-issues, --framework, --github-token, --id, --ignore-refined, --import-from-tmp, --iteration, --labels, --last-comments, --last-issues, --limit, --no-ignore-refined, --no-preview, --openspec-comment, --persona, --preview, --release, --repo-name, --repo-owner, --search, --sprint, --state, --tags, --template, --tmp-file, --write; args: ADAPTER (required) | - | | | `specfact backlog sync` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --baseline-file, --force-baseline-overwrite, --output-format, --project-id, --template; args: - | - | | | `specfact backlog verify-readiness` | nold-ai/specfact-backlog | `specfact module install nold-ai/specfact-backlog` | --adapter, --project-id, --target-items, --template; args: - | - | | | `specfact code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --install-completion, --show-completion; args: - | analyze, drift, import, repro, validate | | | `specfact code analyze` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | contracts | | | `specfact code analyze contracts` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --bundle, --repo; args: - | - | | | `specfact code drift` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | detect | | -| `specfact code drift detect` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --format, --out, --repo; args: - | - | | -| `specfact code import` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: - | from-bridge, from-code | | +| `specfact code drift detect` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --format, --out, --repo; args: BUNDLE | - | | +| `specfact code import` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: BUNDLE | from-bridge, from-code | | | `specfact code import from-bridge` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --adapter, --dry-run, --force, --out-branch, --repo, --report, --write; args: - | - | | -| `specfact code import from-code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: - | - | | +| `specfact code import from-code` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --confidence, --enrich-for-speckit, --enrichment, --entry-point, --exclude-tests, --force, --include-tests, --key-format, --no-enrich-for-speckit, --no-revalidate-features, --repo, --report, --revalidate-features, --shadow-only; args: BUNDLE | - | | | `specfact code repro` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --budget, --crosshair-per-path-timeout, --crosshair-required, --fail-fast, --fix, --out, --repo, --sidecar, --sidecar-bundle, --verbose; args: - | setup | | | `specfact code repro setup` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --install-crosshair, --repo; args: - | - | | | `specfact code review` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --install-completion, --show-completion; args: - | review | | @@ -63,17 +63,17 @@ This file is generated from the current module command trees. Do not edit by han | `specfact code review review rules init` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --ide; args: - | - | | | `specfact code review review rules show` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | -; args: - | - | | | `specfact code review review rules update` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --ide; args: - | - | | -| `specfact code review review run` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --bug-hunt, --enforcement, --exclude-tests, --fix, --focus, --include-noise, --include-tests, --instructions, --interactive, --json, --level, --mode, --no-tests, --out, --path, --preview-fixes, --scope, --score-only, --suppress-noise, --with-mutation; args: - | - | | +| `specfact code review review run` | nold-ai/specfact-code-review | `specfact module install nold-ai/specfact-code-review` | --bug-hunt, --enforcement, --exclude-tests, --fix, --focus, --include-noise, --include-tests, --instructions, --interactive, --json, --level, --mode, --no-tests, --out, --path, --preview-fixes, --scope, --score-only, --suppress-noise, --with-mutation; args: FILES | - | | | `specfact code validate` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | sidecar | | | `specfact code validate sidecar` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | init, run | | -| `specfact code validate sidecar init` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: - | - | | -| `specfact code validate sidecar run` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --no-run-crosshair, --no-run-specmatic, --run-crosshair, --run-specmatic; args: - | - | | +| `specfact code validate sidecar init` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | -; args: BUNDLE_NAME (required), REPO_PATH (required) | - | | +| `specfact code validate sidecar run` | nold-ai/specfact-codebase | `specfact module install nold-ai/specfact-codebase` | --no-run-crosshair, --no-run-specmatic, --run-crosshair, --run-specmatic; args: BUNDLE_NAME (required), REPO_PATH (required) | - | | | `specfact govern` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --install-completion, --show-completion; args: - | enforce, patch | | | `specfact govern enforce` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | -; args: - | sdd, stage | | -| `specfact govern enforce sdd` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --no-interactive, --out, --output-format, --sdd; args: - | - | | +| `specfact govern enforce sdd` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --no-interactive, --out, --output-format, --sdd; args: BUNDLE | - | | | `specfact govern enforce stage` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --preset; args: - | - | | | `specfact govern patch` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | -; args: - | apply | | -| `specfact govern patch apply` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --dry-run, --write, --yes; args: - | - | | +| `specfact govern patch apply` | nold-ai/specfact-govern | `specfact module install nold-ai/specfact-govern` | --dry-run, --write, --yes; args: PATCH_FILE (required) | - | | | `specfact project` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --install-completion, --show-completion; args: - | devops-flow, export, export-roadmap, health-check, import, init-personas, link-backlog, lock, locks, merge, regenerate, resolve-conflict, snapshot, sync, unlock, version | | | `specfact project devops-flow` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --action, --bundle, --no-interactive, --project-name, --repo, --stage, --verbose; args: - | - | | | `specfact project export` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --list-personas, --no-interactive, --out, --output, --output-dir, --persona, --repo, --stdout, --template; args: - | - | | @@ -90,7 +90,7 @@ This file is generated from the current module command trees. Do not edit by han | `specfact project snapshot` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --no-interactive, --output, --project-name, --repo; args: - | - | | | `specfact project sync` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | -; args: - | bridge, intelligent, repository | | | `specfact project sync bridge` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --adapter, --add-progress-comment, --ado-base-url, --ado-org, --ado-project, --ado-token, --ado-work-item-type, --all, --backlog-ids, --backlog-ids-file, --bidirectional, --bundle, --change-ids, --code-repo, --ensure-compliance, --export-to-tmp, --external-base-path, --feature, --github-token, --import-from-tmp, --include-archived, --interactive, --interval, --mode, --no-add-progress-comment, --no-gh-cli, --no-include-archived, --no-sanitize, --no-track-code-changes, --no-update-existing, --overwrite, --repo, --repo-name, --repo-owner, --sanitize, --target-repo, --tmp-file, --track-code-changes, --update-existing, --use-gh-cli, --watch; args: - | - | | -| `specfact project sync intelligent` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --code-to-spec, --repo, --spec-to-code, --tests, --watch; args: - | - | | +| `specfact project sync intelligent` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --code-to-spec, --repo, --spec-to-code, --tests, --watch; args: BUNDLE | - | | | `specfact project sync repository` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --confidence, --interval, --repo, --target, --watch; args: - | - | | | `specfact project unlock` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --no-interactive, --repo, --section; args: - | - | | | `specfact project version` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | -; args: - | bump, check, set | | @@ -99,11 +99,11 @@ This file is generated from the current module command trees. Do not edit by han | `specfact project version set` | nold-ai/specfact-project | `specfact module install nold-ai/specfact-project` | --bundle, --repo, --version; args: - | - | | | `specfact requirements` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --install-completion, --show-completion; args: - | coverage, import, list, validate | | | `specfact requirements coverage` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format; args: - | - | | -| `specfact requirements import` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --from-file; args: - | - | | +| `specfact requirements import` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --from-file, --from-openspec, --from-speckit; args: SOURCE_PATH | - | | | `specfact requirements list` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --show-coverage; args: - | - | | | `specfact requirements validate` | nold-ai/specfact-requirements | `specfact module install nold-ai/specfact-requirements` | --bundle, --format, --profile; args: - | - | | | `specfact spec` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --install-completion, --show-completion; args: - | backward-compat, generate-tests, mock, validate | | -| `specfact spec backward-compat` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | -; args: - | - | | -| `specfact spec generate-tests` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --out, --output; args: - | - | | +| `specfact spec backward-compat` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | -; args: OLD_SPEC (required), NEW_SPEC (required) | - | | +| `specfact spec generate-tests` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --out, --output; args: SPEC_PATH | - | | | `specfact spec mock` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --examples, --no-interactive, --port, --spec, --strict; args: - | - | | -| `specfact spec validate` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --no-interactive, --previous; args: - | - | | +| `specfact spec validate` | nold-ai/specfact-spec | `specfact module install nold-ai/specfact-spec` | --bundle, --force, --no-interactive, --previous; args: SPEC_PATH | - | | diff --git a/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md b/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md index 1b2db9c1..a290c4c4 100644 --- a/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md +++ b/openspec/changes/openspec-01-intent-trace/CHANGE_VALIDATION.md @@ -1,12 +1,34 @@ # Change Validation Report: openspec-01-intent-trace -**Status**: STALE — revalidation required before implementation. +**Status**: VALIDATED — 2026-07-14 (Europe/Berlin) -The previous report (2026-03-05) validated the retired `## Intent Trace` -authoring scope. The change was rescoped on 2026-07-13 to import-first -requirement evidence runtime for native OpenSpec and Spec Kit artifacts (see -`proposal.md`, Rescope section, and core counterpart nold-ai/specfact-cli#350). +## Scope and dependency check -Re-run the change validation workflow against the rescoped proposal and -replace this file with the new report before starting implementation -(tracked as task 1.3 in `tasks.md`). +- The modules change remains a thin command runtime: it delegates native + OpenSpec and Spec Kit imports to core, and does not parse, hash, enrich, or + write upstream artifacts. +- Core counterpart `nold-ai/specfact-cli#350` shipped in merged PR + `nold-ai/specfact-cli#646` on 2026-07-13. Its exported helpers are + `import_openspec_change`, `import_speckit_feature`, and + `validate_requirement_context` with optional profile resolution. +- The module requires core `>=0.52.0,<1.0.0`; the validated local core is + `0.52.2`. +- Core's `unsupported-source-schema` and `unsupported-profile-field` outcomes + remain core-owned and are passed through by the module without fallback + parsing, metadata enrichment, or partial persistence. + +## Change validity evidence + +`openspec validate openspec-01-intent-trace --strict` passed on 2026-07-14. + +The active delta covers the command flags, conservative auto-detection, +sidecar merge behavior, profile delegation, core diagnostic pass-through, and +read-only upstream behavior. No scope change is required beyond the existing +delta updates that record the core compatibility outcomes. + +## Implementation constraints + +1. Add or update scenario-mapped tests before runtime code. +2. Record failing-before and passing-after evidence in `TDD_EVIDENCE.md`. +3. Bump the additive module release and align manifest compatibility metadata. +4. Re-sign the changed module manifest and run the full quality gate sequence. diff --git a/openspec/changes/openspec-01-intent-trace/TDD_EVIDENCE.md b/openspec/changes/openspec-01-intent-trace/TDD_EVIDENCE.md new file mode 100644 index 00000000..889e3493 --- /dev/null +++ b/openspec/changes/openspec-01-intent-trace/TDD_EVIDENCE.md @@ -0,0 +1,78 @@ +# TDD Evidence: openspec-01-intent-trace + +## Failing-before + +- Date: 2026-07-14 (Europe/Berlin) +- Command: + + ```bash + hatch run test tests/unit/specfact_requirements/test_requirements_runtime.py tests/integration/specfact_requirements/test_command_apps.py + ``` + +- Result: failed during collection, as expected before runtime implementation. +- Evidence: `ImportError: cannot import name 'import_native_requirements_to_bundle' from 'specfact_requirements.requirements.runtime'`. + +The new tests cover OpenSpec and Spec Kit import delegation, read-only source +behavior, no partial persistence for core `unsupported-source-schema`, +layered-profile delegation, core gate-count presentation, and CLI explicit and +auto-detected OpenSpec sources. + +## Passing-after + +- Date: 2026-07-14 (Europe/Berlin) +- Command: + + ```bash + hatch run pytest tests/unit/specfact_requirements/test_requirements_runtime.py tests/integration/specfact_requirements/test_command_apps.py -q + ``` + +- Result: `21 passed`. +- The passing suite proves core-delegated OpenSpec and Spec Kit imports, + read-only source handling, fail-closed schema rejection without a sidecar, + layered-profile delegation, core gate-count presentation, and command-level + explicit and auto-detected OpenSpec import behavior. + +## Review-regression evidence + +- Date: 2026-07-14 (Europe/Berlin) +- Command: + + ```bash + hatch run pytest tests/unit/specfact_requirements/test_requirements_runtime.py tests/integration/specfact_requirements/test_command_apps.py -q + ``` + +- Failing-before result: 4 failures. OpenSpec auto-detection treated + `openspec/changes/archive/` as an import candidate; the CLI did not expose an + optional positional source path; and a regular-file source raised a runtime + contract violation instead of a CLI usage error. +- Passing-after result: `21 passed` after archive exclusion, typed optional + source-path parsing, explicit Spec Kit CLI coverage, and generated-command + metadata support were added. + +## Code-review regression evidence + +- Date: 2026-07-14 (Europe/Berlin) +- Command: + + ```bash + hatch run pytest tests/unit/specfact_code_review/tools/test_radon_runner.py -q + ``` + +- Failing-before result: `1 failed, 7 passed`; a Typer command without a + `ctx` parameter still produced `kiss.parameter-count.warning`. +- Passing-after result: `9 passed`; commands are exempt only when their + decorator is owned by a statically identified Typer application, regardless + of whether they accept a context. + +## Final quality gates + +- `hatch run type-check -- packages/specfact-requirements/src/specfact_requirements/requirements packages/specfact-code-review/src/specfact_code_review/tools/radon_runner.py tests/unit/specfact_requirements tests/integration/specfact_requirements/test_command_apps.py tests/unit/specfact_code_review/tools/test_radon_runner.py` — passed with 0 errors. +- `hatch run lint` — passed. +- `hatch run yaml-lint` — passed. +- `hatch run check-bundle-imports` — passed. +- `hatch run verify-modules-signature --payload-from-filesystem --enforce-version-bump --allow-missing-public-key` — passed for all manifests. +- `hatch run contract-test` — passed (28 tests). +- `hatch run smart-test` — passed (884 tests). +- `hatch run test` — passed (884 tests, 2 third-party deprecation warnings). +- `openspec validate openspec-01-intent-trace --strict` — passed. +- `hatch run specfact code review run --enforcement changed --bug-hunt --json --out /private/tmp/specfact-code-review-final.json` — passed with no findings. diff --git a/openspec/changes/openspec-01-intent-trace/design.md b/openspec/changes/openspec-01-intent-trace/design.md index 8e90142e..9e22bd08 100644 --- a/openspec/changes/openspec-01-intent-trace/design.md +++ b/openspec/changes/openspec-01-intent-trace/design.md @@ -48,13 +48,33 @@ error instead of an ImportError traceback. ### D3: Auto-detection stays conservative -**Decision**: When the source path is omitted, detect only conventional -layouts (`openspec/changes/` for OpenSpec; Spec Kit `specs/` feature folders) -relative to the current project root. Ambiguity or absence is a clear error -naming the expected layouts, never a guess. +**Decision**: When the source path is omitted, detect only conventional active +layouts (`openspec/changes/`, excluding its `archive/` directory, for OpenSpec; +Spec Kit `specs/` feature folders) relative to the current project root. +Ambiguity or absence is a clear error naming the expected layouts, never a +guess. **Rationale**: Deterministic behavior over convenience heuristics; misdetected sources would poison evidence. +### D4: Preserve core required-field findings unchanged + +**Decision**: The runtime delegates profile resolution and completeness +evaluation to core, including the evidence-compatible aliases (`id`, `title`, +`acceptance`, and `trace_links`) and `unsupported-profile-field` advisories. +It renders those findings without translating, filtering, or supplementing +them with module-owned metadata. +**Rationale**: The module must remain a thin command adapter. Adding owner, +risk, or exception fields here would make the module an authoring surface and +break the core import-first contract. + +### D5: Preserve core compatibility failures unchanged + +**Decision**: The runtime surfaces `unsupported-source-schema` returned by the +core adapter and does not persist records from that import result. +**Rationale**: Source-profile detection, version policy, fallback parsing, and +partial-import prevention belong to core #350. Reimplementing any of them in +the module would create divergent compatibility behavior. + ## Risks / Trade-offs - **[Risk] Core/module version skew** — flags exist but core helpers are old. diff --git a/openspec/changes/openspec-01-intent-trace/proposal.md b/openspec/changes/openspec-01-intent-trace/proposal.md index da945a1e..8e3458f1 100644 --- a/openspec/changes/openspec-01-intent-trace/proposal.md +++ b/openspec/changes/openspec-01-intent-trace/proposal.md @@ -37,11 +37,18 @@ proposal (nold-ai/specfact-cli#350) for contract details. and Spec Kit `specs/`) relative to the project root. - **EXTEND**: `requirements validate` surfaces the core gate findings (`scenario-unverified`, `stale-import`, `source-missing`, - `ambiguous-mapping`) with profile-driven severity and non-zero exit on - failure; `list`/`coverage` output includes gate-relevant counts. + `ambiguous-mapping`) with profile-driven severity and non-zero exit on failure; + `list`/`coverage` output includes gate-relevant counts. - **EXTEND**: When `--profile` is omitted, the effective profile resolves from the layered configuration shipped by `profile-01-config-layering` instead of a hardcoded `startup` default; an explicit flag always wins. +- **EXTEND**: Preserve the core adapter's evidence-compatible required-field + mapping (`id`, `title`, `acceptance`, and `trace_links`) and surface its + `unsupported-profile-field` advisories unchanged. The module does not add + owner, risk, or exception metadata to imported records. +- **EXTEND**: Surface core `unsupported-source-schema` errors unchanged and do + not persist any partial import when core rejects an untested OpenSpec schema + or a customized Spec Kit template profile. - **UNCHANGED**: `--from-file` remains for generic records; existing sidecar persistence and merge semantics are reused as-is. @@ -60,5 +67,5 @@ proposal (nold-ai/specfact-cli#350) for contract details. - **GitHub Issue**: #168 - **Issue URL**: - **Core Counterpart**: nold-ai/specfact-cli#350 -- **Last Synced Status**: proposed +- **Last Synced Status**: open / Todo (aligned 2026-07-13) - **Sanitized**: false diff --git a/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md b/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md index 48de3a0f..81bb814f 100644 --- a/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md +++ b/openspec/changes/openspec-01-intent-trace/specs/requirements-module/spec.md @@ -23,11 +23,13 @@ logic of its own and SHALL never write into upstream artifact directories. - **THEN** the core adapter normalizes the artifacts into requirement records - **AND** merged records persist to the bundle requirements sidecar. -#### Scenario: Omitted source paths auto-detect conventional layouts +#### Scenario: Omitted source paths auto-detect active conventional layouts -- **GIVEN** a project root containing an `openspec/changes/` directory +- **GIVEN** a project root containing one active OpenSpec change and an + `openspec/changes/archive/` directory - **WHEN** `specfact requirements import --from-openspec` runs without an explicit path -- **THEN** the conventional layout is detected and imported +- **THEN** the active conventional change layout is detected and imported +- **AND** the archive directory is not considered an import source - **AND** a clear error names the expected layouts when detection finds no source. #### Scenario: Validate surfaces gate findings with CI-usable exit codes @@ -38,6 +40,25 @@ logic of its own and SHALL never write into upstream artifact directories. - **THEN** the report lists each gate finding with its category and affected requirement IDs - **AND** the command exits non-zero when the profile treats any finding as an error. +#### Scenario: Runtime preserves core required-field advisories + +- **GIVEN** core validation returns an `unsupported-profile-field` advisory + for a profile field not represented by `RequirementInput` +- **WHEN** `specfact requirements validate` renders the validation report +- **THEN** the advisory is present in the machine-readable and human-readable + output unchanged +- **AND** the module does not add owner, risk, or exception metadata to the + imported record. + +#### Scenario: Runtime blocks unsupported source profiles + +- **GIVEN** an OpenSpec schema or Spec Kit template profile rejected by the + core adapter with `unsupported-source-schema` +- **WHEN** an import command delegates to core +- **THEN** the command surfaces that diagnostic unchanged +- **AND** it does not create or persist partial requirement records +- **AND** it does not attempt version detection or fallback parsing. + #### Scenario: Runtime never writes upstream - **GIVEN** import and validation runs against OpenSpec and Spec Kit sources diff --git a/openspec/changes/openspec-01-intent-trace/tasks.md b/openspec/changes/openspec-01-intent-trace/tasks.md index 839377ca..f46ff159 100644 --- a/openspec/changes/openspec-01-intent-trace/tasks.md +++ b/openspec/changes/openspec-01-intent-trace/tasks.md @@ -17,15 +17,17 @@ evidence in `TDD_EVIDENCE.md`. - [ ] 2.1 Finalize the `requirements-module` spec delta and cross-check scenario completeness. - [ ] 2.2 Add tests: `--from-openspec`/`--from-speckit` import against fixtures, auto-detection success and clear-error cases, gate findings surfaced in `validate` with non-zero exit, and upstream directories untouched after runs. -- [ ] 2.2b Add tests: omitted `--profile` resolves the effective profile from layered configuration instead of a hardcoded `startup` default; explicit `--profile` overrides. +- [ ] 2.2b Add tests: omitted `--profile` resolves the effective profile from layered configuration instead of a hardcoded `startup` default; explicit `--profile` overrides; core's four supported aliases and `unsupported-profile-field` advisories pass through unchanged. +- [ ] 2.2c Add tests: core `unsupported-source-schema` diagnostics surface unchanged and produce no partial sidecar persistence. - [ ] 2.3 Run targeted tests, capture failing-first output in `TDD_EVIDENCE.md`. ## 3. Implementation -- [ ] 3.1 Extend `packages/specfact-requirements/.../commands.py` import command with `--from-openspec [PATH]` and `--from-speckit [PATH]` options. -- [ ] 3.2 Extend `runtime.py` with thin delegation to the new core normalizers via the existing `_load_requirements_module` pattern, including `RequirementsCoreUnavailableError` messaging. -- [ ] 3.3 Surface gate findings in `validate` output and gate-relevant counts in `list`/`coverage`. -- [ ] 3.3b Change the `validate` command's profile default from hardcoded `startup` to core layered-config resolution when the flag is omitted (delegated to the core helper; no config parsing in the module). +- [x] 3.1 Extend `packages/specfact-requirements/.../commands.py` import command with `--from-openspec [PATH]` and `--from-speckit [PATH]` options. +- [x] 3.2 Extend `runtime.py` with thin delegation to the new core normalizers via the existing `_load_requirements_module` pattern, including `RequirementsCoreUnavailableError` messaging. +- [x] 3.3 Surface gate findings in `validate` output and gate-relevant counts in `list`/`coverage`. +- [x] 3.3b Change the `validate` command's profile default from hardcoded `startup` to core layered-config resolution when the flag is omitted, and render core required-field advisories unchanged (delegated to the core helper; no config parsing or metadata enrichment in the module). +- [x] 3.3c Surface core `unsupported-source-schema` results without fallback parsing or partial persistence. - [ ] 3.4 Contract decorators (`@beartype`, `@require`, `@ensure`) on all new public APIs. ## 4. Validation and documentation diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index 0e62afec..afdf906d 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.48 +version: 0.47.51 commands: - code tier: official @@ -23,5 +23,5 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:666caa2524a6c569dbecc9450ef690035960de5232e6547904edd813bede2745 - signature: Yn9yF3FxljA4nzTnN8XtWph9v/K6jeg8mAkeBN/yDjx5sCeyKDQ9DWCHJhLtMeNbfW1cQESdAlC+P2KFx1fgCw== + checksum: sha256:f85ae00bc406952621b590367370ef7a9d28acbc26703befa5b1b1a76a6ef516 + signature: fJJEEZliDUcVlOmmjMLAqrYAjHz+r1jVLU8JB09SDeKTVM0yrilkWCqnGpKKMAwat4dw/s0ispT+brZogMqoAQ== diff --git a/packages/specfact-code-review/src/specfact_code_review/tools/radon_runner.py b/packages/specfact-code-review/src/specfact_code_review/tools/radon_runner.py index 7955dba4..2ac30b6d 100644 --- a/packages/specfact-code-review/src/specfact_code_review/tools/radon_runner.py +++ b/packages/specfact-code-review/src/specfact_code_review/tools/radon_runner.py @@ -106,13 +106,14 @@ def _kiss_metric_findings(file_path: Path) -> list[ReviewFinding]: except (OSError, SyntaxError) as exc: return _tool_error(file_path, f"Unable to parse source for KISS metrics: {exc}") + typer_command_targets = _typer_command_targets(tree) findings: list[ReviewFinding] = [] for function_node in ast.walk(tree): if not isinstance(function_node, ast.FunctionDef | ast.AsyncFunctionDef): continue findings.extend(_kiss_loc_findings(function_node, file_path)) findings.extend(_kiss_nesting_findings(function_node, file_path)) - findings.extend(_kiss_parameter_findings(function_node, file_path)) + findings.extend(_kiss_parameter_findings(function_node, file_path, typer_command_targets)) return findings @@ -165,14 +166,10 @@ def _kiss_nesting_findings( return findings -def _typer_cli_entrypoint_exempt(function_node: ast.FunctionDef | ast.AsyncFunctionDef, file_path: Path) -> bool: +def _typer_cli_entrypoint_exempt( + function_node: ast.FunctionDef | ast.AsyncFunctionDef, file_path: Path, typer_command_targets: set[str] +) -> bool: """Typer command callbacks legitimately take many injected options; skip parameter-count KISS on them.""" - args0 = function_node.args.args - if not args0: - return False - first = args0[0] - if first.arg != "ctx": - return False normalized = str(file_path).replace("\\", "/") # Stable path suffix: matches in-repo and user-scoped installs (~/.specfact/modules/.../src/...). # Typer CLI handler `run(ctx: Context, ...)` in review.commands injects many option parameters by @@ -180,16 +177,7 @@ def _typer_cli_entrypoint_exempt(function_node: ast.FunctionDef | ast.AsyncFunct # elsewhere still get complexity checks. if function_node.name == "run" and normalized.endswith("specfact_code_review/review/commands.py"): return True - if not _has_typer_command_decorator(function_node): - return False - ann = first.annotation - if ann is None: - return False - try: - rendered = ast.unparse(ann) - except AttributeError: - return False - return rendered.endswith("Context") + return _has_typer_command_decorator(function_node, typer_command_targets) def _decorator_name_parts(decorator: ast.expr) -> tuple[str, ...]: @@ -202,21 +190,82 @@ def _decorator_name_parts(decorator: ast.expr) -> tuple[str, ...]: return () -def _has_typer_command_decorator(function_node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool: +def _typer_command_targets(tree: ast.Module) -> set[str]: + """Return names that own Typer command or callback decorators in a module.""" + typer_module_names = _typer_module_names(tree) + typer_constructor_names = _typer_constructor_names(tree) + return typer_module_names | _typer_application_names(tree, typer_module_names, typer_constructor_names) + + +def _typer_module_names(tree: ast.Module) -> set[str]: + names: set[str] = set() + for node in tree.body: + if not isinstance(node, ast.Import): + continue + for alias in node.names: + if alias.name == "typer": + names.add(alias.asname or alias.name) + return names + + +def _typer_constructor_names(tree: ast.Module) -> set[str]: + names: set[str] = set() + for node in tree.body: + if not isinstance(node, ast.ImportFrom) or node.module != "typer": + continue + for alias in node.names: + if alias.name == "Typer": + names.add(alias.asname or alias.name) + return names + + +def _typer_application_names( + tree: ast.Module, typer_module_names: set[str], typer_constructor_names: set[str] +) -> set[str]: + names: set[str] = set() + for node in tree.body: + if isinstance(node, ast.Assign | ast.AnnAssign) and _is_typer_constructor( + node.value, typer_module_names, typer_constructor_names + ): + names.update(_assignment_target_names(node)) + return names + + +def _is_typer_constructor( + value: ast.expr | None, typer_module_names: set[str], typer_constructor_names: set[str] +) -> bool: + if not isinstance(value, ast.Call): + return False + if isinstance(value.func, ast.Name): + return value.func.id in typer_constructor_names + return ( + isinstance(value.func, ast.Attribute) + and isinstance(value.func.value, ast.Name) + and value.func.value.id in typer_module_names + and value.func.attr == "Typer" + ) + + +def _assignment_target_names(node: ast.Assign | ast.AnnAssign) -> set[str]: + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + return {target.id for target in targets if isinstance(target, ast.Name)} + + +def _has_typer_command_decorator( + function_node: ast.FunctionDef | ast.AsyncFunctionDef, typer_command_targets: set[str] +) -> bool: for decorator in function_node.decorator_list: parts = _decorator_name_parts(decorator) - if parts == ("command",) or parts[-1:] == ("command",): - return True - if parts[-1:] == ("callback",): + if len(parts) == 2 and parts[0] in typer_command_targets and parts[1] in {"command", "callback"}: return True return False def _kiss_parameter_findings( - function_node: ast.FunctionDef | ast.AsyncFunctionDef, file_path: Path + function_node: ast.FunctionDef | ast.AsyncFunctionDef, file_path: Path, typer_command_targets: set[str] ) -> list[ReviewFinding]: findings: list[ReviewFinding] = [] - if _typer_cli_entrypoint_exempt(function_node, file_path): + if _typer_cli_entrypoint_exempt(function_node, file_path, typer_command_targets): return findings parameter_count = len(function_node.args.posonlyargs) parameter_count += len(function_node.args.args) diff --git a/packages/specfact-requirements/module-package.yaml b/packages/specfact-requirements/module-package.yaml index e465cb26..a5ab3c8c 100644 --- a/packages/specfact-requirements/module-package.yaml +++ b/packages/specfact-requirements/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-requirements -version: 0.1.5 +version: 0.2.3 commands: - requirements tier: official @@ -14,10 +14,10 @@ pip_dependencies: - pydantic - pyyaml - typer -core_compatibility: '>=0.50.0,<1.0.0' +core_compatibility: '>=0.52.0,<1.0.0' description: Official SpecFact requirements evidence runtime bundle package. category: project bundle_group_command: requirements integrity: - checksum: sha256:d200aaecff7760ee660dd984d70d417b56960879385ebd745ddf48f08cee1b0d - signature: 3cXSjTAnhQHUcvDsM4pYwp8OxJ/gAvNtZD3H0zCMqOyuHW5axSeCJcpGDqjno1YLAq+v4B1PL5HYuJkAUeZQAQ== + checksum: sha256:02ae009efbaba529ba0b4abc14a0ca11c0d08d6451dff094236ce3e850f046a7 + signature: 2WfMBL5+ISTzij7cpqX25K2blP7XXG52jcWc43xE8lPEJE8AKnMsYzZfArd75VVPjTS1kPqzwKxmGlGwodpaDg== diff --git a/packages/specfact-requirements/src/specfact_requirements/requirements/commands.py b/packages/specfact-requirements/src/specfact_requirements/requirements/commands.py index 2064958f..a4af0297 100644 --- a/packages/specfact-requirements/src/specfact_requirements/requirements/commands.py +++ b/packages/specfact-requirements/src/specfact_requirements/requirements/commands.py @@ -9,13 +9,19 @@ import typer from beartype import beartype +from click import Context from icontract import ensure, require from specfact_requirements.requirements.runtime import ( + auto_detect_openspec_change, + auto_detect_speckit_feature, + import_native_requirements_to_bundle, import_requirements_file_to_bundle, + import_result_has_errors, inspect_requirements_bundle_coverage, is_requirement_context_profile_supported, list_requirements_with_coverage, + requirements_gate_finding_counts, validate_requirements_bundle, ) @@ -49,27 +55,84 @@ def _emit_payload(payload: dict[str, Any], output_format: OutputFormat) -> None: typer.echo(f"{key}: {value}") -@app.command("import", help="Import local requirement records into a project bundle.") +def _selected_import_source(from_file: Path | None, from_openspec: bool, from_speckit: bool) -> str: + selected = [ + name + for name, enabled in ( + ("file", from_file is not None), + ("openspec", from_openspec), + ("speckit", from_speckit), + ) + if enabled + ] + if len(selected) != 1: + raise typer.BadParameter("choose exactly one of --from-file, --from-openspec, or --from-speckit") + return selected[0] + + +@app.command("import", help="Import local, OpenSpec, or Spec Kit requirement evidence into a project bundle.") @beartype -@require(lambda from_file: from_file.is_file(), "from_file must exist") @require(lambda bundle: bundle.is_dir(), "bundle must exist") @require(_format_supported, "output format must be supported") @ensure(lambda result: result is None) def import_command( - from_file: Annotated[ - Path, - typer.Option( - "--from-file", exists=True, file_okay=True, dir_okay=False, readable=True, help="JSON/YAML records." - ), - ], + ctx: Context, bundle: Annotated[ Path, typer.Option("--bundle", exists=True, file_okay=False, dir_okay=True, readable=True, writable=True), ], + source_path: Annotated[ + Path | None, + typer.Argument( + exists=True, + file_okay=False, + dir_okay=True, + readable=True, + help="Optional OpenSpec change or Spec Kit feature directory.", + ), + ] = None, + from_file: Annotated[ + Path | None, + typer.Option( + "--from-file", exists=True, file_okay=True, dir_okay=False, readable=True, help="JSON/YAML records." + ), + ] = None, + from_openspec: Annotated[ + bool, + typer.Option( + "--from-openspec", help="Import one OpenSpec change; an optional positional path overrides auto-detection." + ), + ] = False, + from_speckit: Annotated[ + bool, + typer.Option( + "--from-speckit", help="Import one Spec Kit feature; an optional positional path overrides auto-detection." + ), + ] = False, output_format: Annotated[OutputFormat, typer.Option("--format", help="Output format.")] = OutputFormat.TEXT, ) -> None: - """Import local requirement records into a project bundle.""" - result = import_requirements_file_to_bundle(from_file, bundle) + """Import one source of requirement evidence into a project bundle.""" + del ctx # Typer injects the Click context so the command remains compatible with context-aware tooling. + source_name = _selected_import_source(from_file, from_openspec, from_speckit) + if source_name == "file": + if source_path is not None: + raise typer.BadParameter("--from-file does not accept a positional source path") + if from_file is None: + raise typer.BadParameter("--from-file requires a path") + result = import_requirements_file_to_bundle(from_file, bundle) + else: + source_dir = source_path + if source_dir is None: + source_dir = ( + auto_detect_openspec_change(Path.cwd()) + if source_name == "openspec" + else auto_detect_speckit_feature(Path.cwd()) + ) + result = import_native_requirements_to_bundle( + source_name, + source_dir, + bundle, + ) _emit_payload( { "imported": len(result.requirements), @@ -77,12 +140,17 @@ def import_command( }, output_format, ) + if import_result_has_errors(result): + raise typer.Exit(1) @app.command("validate", help="Validate requirement context evidence usefulness.") @beartype @require(lambda bundle: bundle.is_dir(), "bundle must exist") -@require(is_requirement_context_profile_supported, "profile must be a known requirement context profile") +@require( + lambda profile: profile is None or is_requirement_context_profile_supported(profile), + "profile must be a known requirement context profile when provided", +) @require(_format_supported, "output format must be supported") @ensure(lambda result: result is None) def validate_command( @@ -90,7 +158,9 @@ def validate_command( Path, typer.Option("--bundle", exists=True, file_okay=False, dir_okay=True, readable=True), ], - profile: Annotated[str, typer.Option("--profile", help="Validation profile.")] = "startup", + profile: Annotated[ + str | None, typer.Option("--profile", help="Validation profile; omit to use layered configuration.") + ] = None, output_format: Annotated[OutputFormat, typer.Option("--format", help="Output format.")] = OutputFormat.TEXT, ) -> None: """Validate requirement context evidence usefulness.""" @@ -131,4 +201,6 @@ def coverage_command( ) -> None: """Inspect requirement context coverage.""" coverage = inspect_requirements_bundle_coverage(bundle) - _emit_payload(coverage.model_dump(mode="json"), output_format) + payload = coverage.model_dump(mode="json") + payload["gate_finding_counts"] = requirements_gate_finding_counts(bundle) + _emit_payload(payload, output_format) diff --git a/packages/specfact-requirements/src/specfact_requirements/requirements/runtime.py b/packages/specfact-requirements/src/specfact_requirements/requirements/runtime.py index 0d140d55..0cffe6e3 100644 --- a/packages/specfact-requirements/src/specfact_requirements/requirements/runtime.py +++ b/packages/specfact-requirements/src/specfact_requirements/requirements/runtime.py @@ -20,6 +20,11 @@ _REQUIREMENTS_INPUTS_DIR = "requirements" _REQUIREMENTS_MODELS_MODULE = "specfact_cli.models.requirements" _REQUIREMENTS_CONTEXT_MODULE = "specfact_cli.requirements.context" +_REQUIREMENTS_PUBLIC_MODULE = "specfact_cli.requirements" +_NATIVE_IMPORT_HELPERS = { + "openspec": "import_openspec_change", + "speckit": "import_speckit_feature", +} class RequirementsCoreUnavailableError(RuntimeError): @@ -32,7 +37,7 @@ def _load_requirements_module(module_name: str, purpose: str) -> Any: except ImportError as exc: msg = ( f"specfact-requirements requires the paired specfact-cli {purpose} " - "from core change requirements-02-module-commands" + "from core change #350 (specfact-cli >=0.52.0)" ) raise RequirementsCoreUnavailableError(msg) from exc @@ -49,6 +54,30 @@ def _has_attributes(result: Any, *attributes: str) -> bool: return all(hasattr(result, attribute) for attribute in attributes) +def _diagnostic_is_error(diagnostic: Any) -> bool: + return str(getattr(diagnostic, "severity", "")) == "error" + + +@beartype +@require( + lambda import_result: all(hasattr(import_result, attribute) for attribute in ("requirements", "diagnostics")), + "import_result must expose requirements and diagnostics", +) +@ensure(lambda result: isinstance(result, bool)) +def import_result_has_errors(import_result: Any) -> bool: + """Return whether a core import result contains an error diagnostic.""" + return any(_diagnostic_is_error(diagnostic) for diagnostic in import_result.diagnostics) + + +def _core_import_helper(helper_name: str) -> Any: + core_requirements = _load_requirements_module(_REQUIREMENTS_PUBLIC_MODULE, "native requirement import helpers") + helper = getattr(core_requirements, helper_name, None) + if callable(helper): + return helper + msg = f"specfact-requirements requires specfact-cli >=0.52.0; missing core helper '{helper_name}' from change #350" + raise RequirementsCoreUnavailableError(msg) + + def _profile_aliases(profiles: frozenset[str]) -> frozenset[str]: aliases = set(profiles) aliases.update(profile.replace("_", "-") for profile in profiles) @@ -129,6 +158,15 @@ def _write_requirements_sidecar(bundle_dir: Path, records: Sequence[Any]) -> Non temporary_sidecar.unlink() +def _persist_imported_requirements(bundle_dir: Path, imported: Sequence[Any]) -> None: + context_helpers = _load_requirements_module(_REQUIREMENTS_CONTEXT_MODULE, "requirements context helpers") + bundle = _load_bundle_with_requirements(bundle_dir) + merged = merge_requirement_inputs(context_helpers.load_requirements_from_bundle(bundle), imported) + context_helpers.attach_requirements_to_bundle(bundle, merged) + save_project_bundle(bundle, bundle_dir, atomic=True) + _write_requirements_sidecar(bundle_dir, merged) + + @beartype @require(lambda source_file: source_file.is_file(), "source_file must exist") @ensure(_records_are_supported, "all loaded records must be RequirementInput or mapping values") @@ -175,23 +213,91 @@ def import_requirements_file_to_bundle(source_file: Path, bundle_dir: Path) -> A records = load_requirement_records(source_file) context_helpers = _load_requirements_module(_REQUIREMENTS_CONTEXT_MODULE, "requirements context helpers") result = context_helpers.normalize_requirement_records(records, source_locator=source_file.as_posix()) - bundle = _load_bundle_with_requirements(bundle_dir) - merged = merge_requirement_inputs(context_helpers.load_requirements_from_bundle(bundle), result.requirements) - context_helpers.attach_requirements_to_bundle(bundle, merged) - save_project_bundle(bundle, bundle_dir, atomic=True) - _write_requirements_sidecar(bundle_dir, merged) + _persist_imported_requirements(bundle_dir, result.requirements) return result @beartype +@require(lambda source_kind: source_kind in _NATIVE_IMPORT_HELPERS, "source_kind must be openspec or speckit") +@require(lambda source_dir: source_dir.is_dir(), "source_dir must exist") @require(lambda bundle_dir: bundle_dir.is_dir(), "bundle_dir must exist") -@require(is_requirement_context_profile_supported, "profile must be a known requirement context profile") +@ensure(lambda result: _has_attributes(result, "requirements", "diagnostics")) +def import_native_requirements_to_bundle(source_kind: str, source_dir: Path, bundle_dir: Path) -> Any: + """Delegate a native source import to core and persist only valid records.""" + result = _core_import_helper(_NATIVE_IMPORT_HELPERS[source_kind])(source_dir) + if not import_result_has_errors(result): + _persist_imported_requirements(bundle_dir, result.requirements) + return result + + +def _sole_source(candidates: list[Path], expected_layout: str) -> Path: + if len(candidates) == 1: + return candidates[0] + if not candidates: + raise ValueError(f"No import source found; expected {expected_layout}.") + names = ", ".join(candidate.name for candidate in candidates) + raise ValueError(f"Multiple import sources found ({names}); pass an explicit path instead of {expected_layout}.") + + +@beartype +@require(lambda project_root: project_root.is_dir(), "project_root must exist") +@ensure(lambda result: result.is_dir()) +def auto_detect_openspec_change(project_root: Path) -> Path: + """Return the single conventional OpenSpec change source below a project root.""" + changes_dir = project_root / "openspec" / "changes" + candidates = ( + [path for path in sorted(changes_dir.iterdir()) if path.is_dir() and path.name != "archive"] + if changes_dir.is_dir() + else [] + ) + return _sole_source(candidates, "openspec/changes/") + + +@beartype +@require(lambda project_root: project_root.is_dir(), "project_root must exist") +@ensure(lambda result: result.is_dir()) +def auto_detect_speckit_feature(project_root: Path) -> Path: + """Return the single conventional Spec Kit feature source below a project root.""" + specs_dir = project_root / "specs" + candidates = ( + [path for path in sorted(specs_dir.iterdir()) if path.is_dir() and (path / "spec.md").is_file()] + if specs_dir.is_dir() + else [] + ) + return _sole_source(candidates, "specs//") + + +@beartype +@require(lambda bundle_dir: bundle_dir.is_dir(), "bundle_dir must exist") +@require( + lambda profile: profile is None or is_requirement_context_profile_supported(profile), + "profile must be a known requirement context profile when provided", +) @ensure(lambda result: isinstance(result, ValidationReport)) -def validate_requirements_bundle(bundle_dir: Path, *, profile: str = "startup") -> ValidationReport: +def validate_requirements_bundle(bundle_dir: Path, *, profile: str | None = None) -> ValidationReport: """Validate requirement context evidence usefulness for a bundle.""" bundle = _load_bundle_with_requirements(bundle_dir) context_helpers = _load_requirements_module(_REQUIREMENTS_CONTEXT_MODULE, "requirements context helpers") - return context_helpers.validate_requirement_context(bundle, profile=normalize_requirement_context_profile(profile)) + normalized_profile = normalize_requirement_context_profile(profile) if profile is not None else None + return context_helpers.validate_requirement_context(bundle, profile=normalized_profile, project_root=Path.cwd()) + + +@beartype +@require(lambda bundle_dir: bundle_dir.is_dir(), "bundle_dir must exist") +@require( + lambda profile: profile is None or is_requirement_context_profile_supported(profile), + "profile must be a known requirement context profile when provided", +) +@ensure(lambda result: all(isinstance(code, str) and isinstance(count, int) for code, count in result.items())) +def requirements_gate_finding_counts(bundle_dir: Path, *, profile: str | None = None) -> dict[str, int]: + """Count core validation findings without reimplementing their gate logic.""" + report = validate_requirements_bundle(bundle_dir, profile=profile) + counts: dict[str, int] = {} + for violation in report.violations: + code = violation.get("code") + if isinstance(code, str): + counts[code] = counts.get(code, 0) + 1 + return counts @beartype @@ -225,4 +331,5 @@ def list_requirements_with_coverage(bundle_dir: Path, *, show_coverage: bool = F } if show_coverage: payload["coverage"] = context_helpers.inspect_requirement_context_coverage(requirements).model_dump(mode="json") + payload["gate_finding_counts"] = requirements_gate_finding_counts(bundle_dir) return payload diff --git a/registry/index.json b/registry/index.json index 9844978d..9f3fb0f9 100644 --- a/registry/index.json +++ b/registry/index.json @@ -78,9 +78,9 @@ }, { "id": "nold-ai/specfact-code-review", - "latest_version": "0.47.48", - "download_url": "modules/specfact-code-review-0.47.48.tar.gz", - "checksum_sha256": "dc41ee6a479bbc07f74670ff99f995ae75246da0ad94678957b8efbde1717501", + "latest_version": "0.47.51", + "download_url": "modules/specfact-code-review-0.47.51.tar.gz", + "checksum_sha256": "ee0b2a366293ecf7f3d08331287397322cea35cda5cbd3af7b9df01bd3c00ab1", "core_compatibility": ">=0.44.0,<1.0.0", "tier": "official", "publisher": { @@ -94,9 +94,9 @@ }, { "id": "nold-ai/specfact-requirements", - "latest_version": "0.1.5", - "download_url": "modules/specfact-requirements-0.1.5.tar.gz", - "checksum_sha256": "fd3f684be7d8ba76c16296c00e44f7ab05dbddf00b90bb716d5b9882bbc6745c", + "latest_version": "0.2.3", + "download_url": "modules/specfact-requirements-0.2.3.tar.gz", + "checksum_sha256": "98e343595ef4b2a63e8f5375a7f546774c3c3aee918a0f24370a52ba3369e4c6", "tier": "official", "publisher": { "name": "nold-ai", @@ -105,7 +105,8 @@ "bundle_dependencies": [ "nold-ai/specfact-project" ], - "description": "Official SpecFact requirements evidence runtime bundle package." + "description": "Official SpecFact requirements evidence runtime bundle package.", + "core_compatibility": ">=0.52.0,<1.0.0" } ] } diff --git a/registry/modules/specfact-code-review-0.47.51.tar.gz b/registry/modules/specfact-code-review-0.47.51.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..c7c51b386fd775b5971099389276bfb8e1dbdcc1 GIT binary patch literal 62832 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 literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-code-review-0.47.51.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.51.tar.gz.sha256 new file mode 100644 index 00000000..6ffe8932 --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.51.tar.gz.sha256 @@ -0,0 +1 @@ +ee0b2a366293ecf7f3d08331287397322cea35cda5cbd3af7b9df01bd3c00ab1 diff --git a/registry/modules/specfact-requirements-0.2.3.tar.gz b/registry/modules/specfact-requirements-0.2.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..bf100e18124d57617985f016528f1f5645c9d86b GIT binary patch literal 5697 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 literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-requirements-0.2.3.tar.gz.sha256 b/registry/modules/specfact-requirements-0.2.3.tar.gz.sha256 new file mode 100644 index 00000000..8a8eceac --- /dev/null +++ b/registry/modules/specfact-requirements-0.2.3.tar.gz.sha256 @@ -0,0 +1 @@ +98e343595ef4b2a63e8f5375a7f546774c3c3aee918a0f24370a52ba3369e4c6 diff --git a/scripts/generate-command-overview.py b/scripts/generate-command-overview.py index e8419bde..65d867fc 100644 --- a/scripts/generate-command-overview.py +++ b/scripts/generate-command-overview.py @@ -154,8 +154,8 @@ def validate_official_mount_inventory() -> None: def _command_options(command: click.Command) -> list[str]: options: set[str] = set() for param in command.params: - if hasattr(param, "opts"): - secondary_opts = getattr(param, "secondary_opts", ()) + if isinstance(param, click.Option): + secondary_opts = param.secondary_opts options.update(opt for opt in [*param.opts, *secondary_opts] if opt.startswith("--")) return sorted(options) @@ -163,7 +163,7 @@ def _command_options(command: click.Command) -> list[str]: def _command_arguments(command: click.Command) -> list[dict[str, Any]]: arguments: list[dict[str, Any]] = [] for param in command.params: - if not hasattr(param, "opts") and hasattr(param, "human_readable_name"): + if isinstance(param, click.Argument): arguments.append( { "name": param.human_readable_name, @@ -208,10 +208,10 @@ def _has_bare_business_parameters(command: click.Command) -> bool: "--show-completion", } for param in command.params: - if not hasattr(param, "opts") and hasattr(param, "human_readable_name"): + if isinstance(param, click.Argument): return True - if hasattr(param, "opts"): - opts = set(param.opts) | set(getattr(param, "secondary_opts", ())) + if isinstance(param, click.Option): + opts = set(param.opts) | set(param.secondary_opts) if opts and opts.isdisjoint(ignored_options): return True return False diff --git a/tests/integration/specfact_requirements/test_command_apps.py b/tests/integration/specfact_requirements/test_command_apps.py index 320e5928..4eefe7af 100644 --- a/tests/integration/specfact_requirements/test_command_apps.py +++ b/tests/integration/specfact_requirements/test_command_apps.py @@ -49,6 +49,53 @@ def _source_file(tmp_path: Path) -> Path: return source +def _openspec_change(project_root: Path) -> Path: + change_dir = project_root / "openspec" / "changes" / "widget-evidence" + spec_path = change_dir / "specs" / "widgets" / "spec.md" + spec_path.parent.mkdir(parents=True) + spec_path.write_text( + """## ADDED Requirements + +### Requirement: Widget rendering + +The system SHALL render a widget. + +#### Scenario: Render a valid widget + +- **GIVEN** a valid widget request +- **WHEN** rendering runs +- **THEN** the widget is returned +""", + encoding="utf-8", + ) + return change_dir + + +def _speckit_feature(project_root: Path) -> Path: + feature_dir = project_root / "specs" / "001-widget-rendering" + feature_dir.mkdir(parents=True) + (feature_dir / "spec.md").write_text( + """# Feature Specification: Widget rendering + +## User Scenarios & Testing + +### User Story 1 - Render widgets (Priority: P1) + +As a user, I want widgets rendered so that I can see them. + +**Acceptance Scenarios**: + +1. **Given** a valid widget request, **When** rendering runs, **Then** the widget is returned + +## Requirements + +- **FR-001**: System MUST render a widget +""", + encoding="utf-8", + ) + return feature_dir + + @pytest.mark.integration def test_command_module_exposes_typer_app() -> None: assert app is not None @@ -119,3 +166,86 @@ def test_requirements_help_exposes_no_author_command() -> None: assert "validate" in result.output assert "coverage" in result.output assert "author" not in result.output + + +@pytest.mark.integration +def test_requirements_import_help_exposes_optional_native_source_path() -> None: + result = runner.invoke(app, ["import", "--help"]) + + assert result.exit_code == 0 + assert "[SOURCE_PATH]" in result.output + + +@pytest.mark.integration +def test_requirements_import_openspec_accepts_explicit_and_auto_detected_sources( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + explicit_bundle = _bundle_dir(tmp_path / "explicit") + + explicit_result = runner.invoke( + app, + ["import", "--from-openspec", str(change_dir), "--bundle", str(explicit_bundle), "--format", "json"], + ) + + assert explicit_result.exit_code == 0, explicit_result.output + assert json.loads(explicit_result.output)["imported"] == 1 + + auto_bundle = _bundle_dir(tmp_path / "auto") + (project_root / "openspec" / "changes" / "archive" / "2026-07-14-widget-evidence").mkdir(parents=True) + monkeypatch.chdir(project_root) + auto_result = runner.invoke( + app, + ["import", "--from-openspec", "--bundle", str(auto_bundle), "--format", "json"], + ) + + assert auto_result.exit_code == 0, auto_result.output + assert json.loads(auto_result.output)["imported"] == 1 + + +@pytest.mark.integration +def test_requirements_import_speckit_accepts_explicit_source(tmp_path: Path) -> None: + project_root = tmp_path / "project" + feature_dir = _speckit_feature(project_root) + bundle_dir = _bundle_dir(tmp_path) + + result = runner.invoke( + app, + ["import", "--from-speckit", str(feature_dir), "--bundle", str(bundle_dir), "--format", "json"], + ) + + assert result.exit_code == 0, result.output + assert json.loads(result.output)["imported"] == 1 + + +@pytest.mark.integration +def test_requirements_import_rejects_non_directory_native_source_path(tmp_path: Path) -> None: + bundle_dir = _bundle_dir(tmp_path) + source_file = tmp_path / "source.md" + source_file.write_text("not a source directory\n", encoding="utf-8") + + result = runner.invoke( + app, + ["import", "--from-openspec", str(source_file), "--bundle", str(bundle_dir), "--format", "json"], + ) + + assert result.exit_code == 2 + assert "Invalid value" in result.output + + +@pytest.mark.integration +def test_requirements_import_surfaces_core_schema_rejection_without_persistence(tmp_path: Path) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + (project_root / "openspec" / "config.yaml").write_text("schema: company-custom\n", encoding="utf-8") + bundle_dir = _bundle_dir(tmp_path) + + result = runner.invoke( + app, + ["import", "--from-openspec", str(change_dir), "--bundle", str(bundle_dir), "--format", "json"], + ) + + assert result.exit_code == 1 + assert json.loads(result.output)["diagnostics"][0]["code"] == "unsupported-source-schema" + assert not (bundle_dir / "reports" / "requirements" / "inputs.yaml").exists() diff --git a/tests/unit/docs/test_bundle_overview_cli_examples.py b/tests/unit/docs/test_bundle_overview_cli_examples.py index aa3ce5c5..63b8f878 100644 --- a/tests/unit/docs/test_bundle_overview_cli_examples.py +++ b/tests/unit/docs/test_bundle_overview_cli_examples.py @@ -60,6 +60,22 @@ "import", "--help", ], + ( + "specfact requirements import --from-openspec openspec/changes/widget-evidence " + "--bundle .specfact/projects/shop --format json" + ): [ + "requirements", + "import", + "--help", + ], + ( + "specfact requirements import --from-speckit specs/001-widget-rendering " + "--bundle .specfact/projects/shop --format json" + ): [ + "requirements", + "import", + "--help", + ], "specfact requirements list --bundle .specfact/projects/shop --show-coverage --format json": [ "requirements", "list", diff --git a/tests/unit/docs/test_llms_overview_freshness.py b/tests/unit/docs/test_llms_overview_freshness.py index c04585b5..5a3e0784 100644 --- a/tests/unit/docs/test_llms_overview_freshness.py +++ b/tests/unit/docs/test_llms_overview_freshness.py @@ -13,6 +13,7 @@ import sys from pathlib import Path +import click import pytest from tests.unit._script_test_utils import load_module_from_path @@ -52,6 +53,15 @@ def test_llms_and_command_overview_are_current() -> None: ) +def test_command_overview_records_optional_click_arguments() -> None: + generator = load_module_from_path("generate_command_overview_arguments", GENERATOR) + command = click.Command("import", params=[click.Argument(["source_path"], required=False)]) + + assert generator._command_arguments(command) == [ # pylint: disable=protected-access + {"name": "SOURCE_PATH", "required": False, "nargs": 1} + ] + + def test_command_overview_rejects_unrepresented_official_inventory(tmp_path: Path, monkeypatch) -> None: generator = load_module_from_path("generate_command_overview_inventory", GENERATOR) manifest = tmp_path / "packages" / "specfact-example" / "module-package.yaml" diff --git a/tests/unit/specfact_code_review/tools/test_radon_runner.py b/tests/unit/specfact_code_review/tools/test_radon_runner.py index e3c32e56..3c8affe1 100644 --- a/tests/unit/specfact_code_review/tools/test_radon_runner.py +++ b/tests/unit/specfact_code_review/tools/test_radon_runner.py @@ -5,19 +5,34 @@ from pathlib import Path from unittest.mock import Mock +import pytest from pytest import MonkeyPatch from specfact_code_review.tools.radon_runner import run_radon from tests.unit.specfact_code_review.tools.helpers import assert_tool_run, completed_process, create_noisy_file +def _parameter_count_rules(tmp_path: Path, monkeypatch: MonkeyPatch, source: str) -> set[str]: + file_path = tmp_path / "commands.py" + file_path.write_text(source, encoding="utf-8") + monkeypatch.setattr( + subprocess, + "run", + Mock(return_value=completed_process("radon", stdout=json.dumps({str(file_path): []}))), + ) + return {finding.rule for finding in run_radon([file_path])} + + def test_run_radon_returns_empty_when_only_non_python_paths(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: manifest = tmp_path / "module-package.yaml" manifest.write_text("name: example\n", encoding="utf-8") run_mock = Mock() monkeypatch.setattr(subprocess, "run", run_mock) - assert run_radon([manifest]) == [] + result = run_radon([manifest]) + + assert isinstance(result, list) + assert not result run_mock.assert_not_called() @@ -60,7 +75,8 @@ def test_run_radon_returns_no_findings_for_complexity_twelve_or_below(tmp_path: findings = run_radon([file_path]) - assert findings == [] + assert isinstance(findings, list) + assert not findings def test_run_radon_returns_tool_error_on_parse_error(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: @@ -109,44 +125,45 @@ def test_run_radon_uses_dedicated_tool_identifier_for_kiss_findings(tmp_path: Pa assert {finding.tool for finding in kiss_findings} == {"radon-kiss"} -def test_run_radon_requires_typer_decorator_for_context_parameter_exemption( - tmp_path: Path, monkeypatch: MonkeyPatch -) -> None: - file_path = tmp_path / "commands.py" - file_path.write_text( - """ +@pytest.mark.parametrize( + ("source", "expects_parameter_count_warning"), + [ + ( + """ def callback(ctx: typer.Context, a: str, b: str, c: str, d: str, e: str) -> None: return None """, - encoding="utf-8", - ) - monkeypatch.setattr( - subprocess, - "run", - Mock(return_value=completed_process("radon", stdout=json.dumps({str(file_path): []}))), - ) - - findings = run_radon([file_path]) - - assert "kiss.parameter-count.warning" in {finding.rule for finding in findings} + True, + ), + ( + """ +import typer +app = typer.Typer() -def test_run_radon_exempts_typer_command_context_parameters(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: - file_path = tmp_path / "commands.py" - file_path.write_text( - """ @app.command("run") -def callback(ctx: typer.Context, a: str, b: str, c: str, d: str, e: str) -> None: +def callback(a: str, b: str, c: str, d: str, e: str, f: str) -> None: return None """, - encoding="utf-8", - ) - monkeypatch.setattr( - subprocess, - "run", - Mock(return_value=completed_process("radon", stdout=json.dumps({str(file_path): []}))), + False, + ), + ( + """ +@custom.command("run") +def callback(a: str, b: str, c: str, d: str, e: str, f: str) -> None: + return None +""", + True, + ), + ], +) +def test_run_radon_applies_parameter_count_rule_to_cli_decorators( + tmp_path: Path, monkeypatch: MonkeyPatch, source: str, expects_parameter_count_warning: bool +) -> None: + findings = _parameter_count_rules( + tmp_path, + monkeypatch, + source, ) - findings = run_radon([file_path]) - - assert "kiss.parameter-count.warning" not in {finding.rule for finding in findings} + assert ("kiss.parameter-count.warning" in findings) is expects_parameter_count_warning diff --git a/tests/unit/specfact_requirements/test_requirements_runtime.py b/tests/unit/specfact_requirements/test_requirements_runtime.py index 10592288..3f54daea 100644 --- a/tests/unit/specfact_requirements/test_requirements_runtime.py +++ b/tests/unit/specfact_requirements/test_requirements_runtime.py @@ -15,8 +15,11 @@ from specfact_requirements.requirements import runtime as requirements_runtime from specfact_requirements.requirements.runtime import ( RequirementsCoreUnavailableError, + auto_detect_openspec_change, + import_native_requirements_to_bundle, import_requirements_file_to_bundle, list_requirements_with_coverage, + requirements_gate_finding_counts, validate_requirements_bundle, ) @@ -46,6 +49,55 @@ def _bundle_dir(tmp_path: Path) -> Path: return bundle_dir +def _openspec_change(project_root: Path) -> Path: + change_dir = project_root / "openspec" / "changes" / "widget-evidence" + spec_path = change_dir / "specs" / "widgets" / "spec.md" + spec_path.parent.mkdir(parents=True) + (change_dir / "proposal.md").write_text("# Change: Widget evidence\n", encoding="utf-8") + (change_dir / "tasks.md").write_text("# Tasks: Widget evidence\n", encoding="utf-8") + spec_path.write_text( + """## ADDED Requirements + +### Requirement: Widget rendering + +The system SHALL render a widget. + +#### Scenario: Render a valid widget + +- **GIVEN** a valid widget request +- **WHEN** rendering runs +- **THEN** the widget is returned +""", + encoding="utf-8", + ) + return change_dir + + +def _speckit_feature(project_root: Path) -> Path: + feature_dir = project_root / "specs" / "001-widget-rendering" + feature_dir.mkdir(parents=True) + (feature_dir / "spec.md").write_text( + """# Feature Specification: Widget rendering + +## User Scenarios & Testing + +### User Story 1 - Render widgets (Priority: P1) + +As a user, I want widgets rendered so that I can see them. + +**Acceptance Scenarios**: + +1. **Given** a valid widget request, **When** rendering runs, **Then** the widget is returned + +## Requirements + +- **FR-001**: System MUST render a widget +""", + encoding="utf-8", + ) + return feature_dir + + def _block_runtime_import(module_name: str) -> Callable[[str], ModuleType]: def blocked_import(name: str) -> ModuleType: if name == module_name: @@ -156,3 +208,75 @@ def test_list_requirements_with_coverage_is_machine_readable(tmp_path: Path) -> assert listing["requirements"][0]["title"] == "Requirement context is imported as validation evidence" assert listing["coverage"]["total_requirements"] == 1 assert listing["coverage"]["with_test_links"] == 1 + + +def test_import_openspec_change_persists_core_records_without_mutating_source(tmp_path: Path) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + before = {path.relative_to(change_dir): path.read_bytes() for path in change_dir.rglob("*") if path.is_file()} + bundle_dir = _bundle_dir(project_root) + + result = import_native_requirements_to_bundle("openspec", change_dir, bundle_dir) + + assert [record.requirement_id for record in result.requirements] == [ + "openspec:widget-evidence:widgets:widget-rendering" + ] + assert result.diagnostics == [] + assert list_requirements_with_coverage(bundle_dir)["requirements"][0]["requirement_id"] == ( + "openspec:widget-evidence:widgets:widget-rendering" + ) + after = {path.relative_to(change_dir): path.read_bytes() for path in change_dir.rglob("*") if path.is_file()} + assert after == before + + +def test_import_speckit_feature_persists_core_records(tmp_path: Path) -> None: + project_root = tmp_path / "project" + feature_dir = _speckit_feature(project_root) + bundle_dir = _bundle_dir(project_root) + + result = import_native_requirements_to_bundle("speckit", feature_dir, bundle_dir) + + assert [record.requirement_id for record in result.requirements] == ["speckit:001-widget-rendering:render-a-widget"] + assert result.diagnostics == [] + + +def test_auto_detect_openspec_change_ignores_archive_directory(tmp_path: Path) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + (project_root / "openspec" / "changes" / "archive" / "2026-07-14-widget-evidence").mkdir(parents=True) + + detected = auto_detect_openspec_change(project_root) + + assert detected == change_dir + + +def test_import_rejected_by_core_does_not_persist_partial_sidecar(tmp_path: Path) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + (project_root / "openspec" / "config.yaml").write_text("schema: company-custom\n", encoding="utf-8") + bundle_dir = _bundle_dir(project_root) + + result = import_native_requirements_to_bundle("openspec", change_dir, bundle_dir) + + assert result.requirements == [] + assert [diagnostic.code for diagnostic in result.diagnostics] == ["unsupported-source-schema"] + assert not (bundle_dir / "reports" / "requirements" / "inputs.yaml").exists() + + +def test_profile_resolution_and_gate_counts_delegate_to_core(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + project_root = tmp_path / "project" + change_dir = _openspec_change(project_root) + bundle_dir = _bundle_dir(project_root) + import_native_requirements_to_bundle("openspec", change_dir, bundle_dir) + config_dir = project_root / ".specfact" + config_dir.mkdir() + (config_dir / "config.yaml").write_text("profile: enterprise\n", encoding="utf-8") + monkeypatch.chdir(project_root) + + configured_report = validate_requirements_bundle(bundle_dir) + explicit_report = validate_requirements_bundle(bundle_dir, profile="solo") + gate_counts = requirements_gate_finding_counts(bundle_dir) + + assert configured_report.status == "failed" + assert explicit_report.status == "warnings" + assert gate_counts["scenario-unverified"] == 1 From 596c93a013115069854916419839a4aef2660fba Mon Sep 17 00:00:00 2001 From: Dom <39115308+djm81@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:07:51 +0200 Subject: [PATCH 5/6] chore(modules): recover signed module publication (#343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Prepare corrected releases for the requirements and code-review modules: - `nold-ai/specfact-requirements`: `0.2.3` → `0.2.4` - `nold-ai/specfact-code-review`: `0.47.51` → `0.47.52` ## Root cause The prior feature PR committed registry entries and tarballs before the canonical `publish-modules` workflow ran. The source manifests were later signed by CI, while the committed tarballs still embedded unsigned manifests. Because `dev` already recorded the same versions, a manual publish dispatch selected no corrective release. ## Release path This change intentionally leaves `registry/**` untouched and keeps local manifests checksum-only. After merge to `dev`, CI must generate the signed manifest and registry publication PRs for these new versions. ## Validation - `hatch run yaml-lint` - `hatch run verify-modules-signature --payload-from-filesystem --enforce-version-bump --allow-missing-public-key` - pre-commit format, YAML, import-boundary, command-reference, documentation, and manifest-signature checks --------- Co-authored-by: Dominikus Nold Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/specfact-code-review/module-package.yaml | 6 +++--- packages/specfact-requirements/module-package.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/specfact-code-review/module-package.yaml b/packages/specfact-code-review/module-package.yaml index afdf906d..f625bf58 100644 --- a/packages/specfact-code-review/module-package.yaml +++ b/packages/specfact-code-review/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-code-review -version: 0.47.51 +version: 0.47.52 commands: - code tier: official @@ -23,5 +23,5 @@ description: Official SpecFact code review bundle package. category: codebase bundle_group_command: code integrity: - checksum: sha256:f85ae00bc406952621b590367370ef7a9d28acbc26703befa5b1b1a76a6ef516 - signature: fJJEEZliDUcVlOmmjMLAqrYAjHz+r1jVLU8JB09SDeKTVM0yrilkWCqnGpKKMAwat4dw/s0ispT+brZogMqoAQ== + checksum: sha256:2fc7b2456493245328c294008e17a72a224edfeb7c3ca2fc581b5de94fe941ca + signature: QJB9dXYvdTTzboXBRGGqMXoEKvoVdVwK/B9NVZ/xk2F8QwusMkXMGVqmQzF+biH4xIaujqK94wqw30s2yIUMDQ== diff --git a/packages/specfact-requirements/module-package.yaml b/packages/specfact-requirements/module-package.yaml index a5ab3c8c..93cf2735 100644 --- a/packages/specfact-requirements/module-package.yaml +++ b/packages/specfact-requirements/module-package.yaml @@ -1,5 +1,5 @@ name: nold-ai/specfact-requirements -version: 0.2.3 +version: 0.2.4 commands: - requirements tier: official @@ -19,5 +19,5 @@ description: Official SpecFact requirements evidence runtime bundle package. category: project bundle_group_command: requirements integrity: - checksum: sha256:02ae009efbaba529ba0b4abc14a0ca11c0d08d6451dff094236ce3e850f046a7 - signature: 2WfMBL5+ISTzij7cpqX25K2blP7XXG52jcWc43xE8lPEJE8AKnMsYzZfArd75VVPjTS1kPqzwKxmGlGwodpaDg== + checksum: sha256:ed53370ebcfb4a0667e5fc7e0af097d0dd4896fc70b583965664ccc833d1e8b3 + signature: piqKk1AXxSRms2k+hGRn8IHFQE4g60M14qU/rDdChn++sq/HBCj7S9fNq1LfhyKD0TfendIbz01Is+dIoB1cDw== From 7f120bd415bc14546ad8e4f5dc5d91de9410979d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:08:43 +0200 Subject: [PATCH 6/6] chore(registry): publish changed modules (#344) Automated registry publish update from workflow run 29364507530. Bundle selection reasons: - `specfact-code-review`: changed, registry-outdated - `specfact-requirements`: changed, registry-outdated Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- registry/index.json | 12 ++++++------ .../modules/specfact-code-review-0.47.52.tar.gz | Bin 0 -> 65421 bytes .../specfact-code-review-0.47.52.tar.gz.sha256 | 1 + .../modules/specfact-requirements-0.2.4.tar.gz | Bin 0 -> 6021 bytes .../specfact-requirements-0.2.4.tar.gz.sha256 | 1 + .../specfact-code-review-0.47.52.tar.sig | 1 + .../specfact-requirements-0.2.4.tar.sig | 1 + 7 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 registry/modules/specfact-code-review-0.47.52.tar.gz create mode 100644 registry/modules/specfact-code-review-0.47.52.tar.gz.sha256 create mode 100644 registry/modules/specfact-requirements-0.2.4.tar.gz create mode 100644 registry/modules/specfact-requirements-0.2.4.tar.gz.sha256 create mode 100644 registry/signatures/specfact-code-review-0.47.52.tar.sig create mode 100644 registry/signatures/specfact-requirements-0.2.4.tar.sig diff --git a/registry/index.json b/registry/index.json index 9f3fb0f9..c350f2f4 100644 --- a/registry/index.json +++ b/registry/index.json @@ -78,9 +78,9 @@ }, { "id": "nold-ai/specfact-code-review", - "latest_version": "0.47.51", - "download_url": "modules/specfact-code-review-0.47.51.tar.gz", - "checksum_sha256": "ee0b2a366293ecf7f3d08331287397322cea35cda5cbd3af7b9df01bd3c00ab1", + "latest_version": "0.47.52", + "download_url": "modules/specfact-code-review-0.47.52.tar.gz", + "checksum_sha256": "b946e680f38672f443312821a511d4ce05f3b4385c1f0432604c4cef652f8547", "core_compatibility": ">=0.44.0,<1.0.0", "tier": "official", "publisher": { @@ -94,9 +94,9 @@ }, { "id": "nold-ai/specfact-requirements", - "latest_version": "0.2.3", - "download_url": "modules/specfact-requirements-0.2.3.tar.gz", - "checksum_sha256": "98e343595ef4b2a63e8f5375a7f546774c3c3aee918a0f24370a52ba3369e4c6", + "latest_version": "0.2.4", + "download_url": "modules/specfact-requirements-0.2.4.tar.gz", + "checksum_sha256": "3d8751f3b15624262c24079900056d681b0668f24d5f69f9041c5b8096d370b3", "tier": "official", "publisher": { "name": "nold-ai", diff --git a/registry/modules/specfact-code-review-0.47.52.tar.gz b/registry/modules/specfact-code-review-0.47.52.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..bda2639108df62a44006a9c1d0ef2563a827bc5a GIT binary patch literal 65421 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

Hgzy_YcX%0KF5MZ5h5qhPjkLD#ywc z#Ylc__uyBQj^;(2OCuz71}dfB!tip0W&~D%AGz+8rtO%1Lq}qHVDMvn#gKnvC;5jCZRQi!EI`^&7IhUjYW)Jf{8b<`p*OsL@ zs0j`8)WCv&e2XO6TPOYA&iFbIVy#($%&&y0P^GdV+TJ?3wkxt_)ry)_s8SV0T4~u< zt!fIqP_?OotN#PmPsS%{3H7(hQR+frWF$l|j`p5RV%N)q*Pb0F^^DrSAI)wTvCNRK z8A}d9D6=q1c)1`(a(F-)ys#*-XognKfQ~%@sV&0T=>-PD<+WtHQZ^)I49AKn?MJ>A zp(!0eF}e)R1AaC1Wws02I)#dwIfB>}4p>BwJC9JbERUs(7Nw|f=yf!~@$furHLE>` zc7#U>Oi!w&Cy4Xhx!LFV^$czQDk)n?>Vs{e7&tLVseys*n`z&N^X0$Mb1Oh;?X|c$ znuC;pRoCNY1K{=5wNyZfNE6I%jBvw%kL%4$`hjsQM^QkSLd6cQQw+6(@@Pfg@$*;hfE%l!$+Pw zo6V1#d+Zka&K{A?$)z)?Q}v~0<{khxH?INljltIIVMxAS1LUc};5|TG4lk+NJri>3 zxknr)k}Ek*{|0A3q@?J?6uI|jn88-ar=q%oxVpFP0&I|N!!Xz$>8orOnDGYQ$u?A8{8pQ{*{R)fJZX6#*;Mo7)lfu~#S2jWIu@5exg Gc%w; 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 literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-code-review-0.47.52.tar.gz.sha256 b/registry/modules/specfact-code-review-0.47.52.tar.gz.sha256 new file mode 100644 index 00000000..983fdf92 --- /dev/null +++ b/registry/modules/specfact-code-review-0.47.52.tar.gz.sha256 @@ -0,0 +1 @@ +b946e680f38672f443312821a511d4ce05f3b4385c1f0432604c4cef652f8547 diff --git a/registry/modules/specfact-requirements-0.2.4.tar.gz b/registry/modules/specfact-requirements-0.2.4.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..da413ab834915f9210c5390f4838075c6374783a GIT binary patch literal 6021 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 literal 0 HcmV?d00001 diff --git a/registry/modules/specfact-requirements-0.2.4.tar.gz.sha256 b/registry/modules/specfact-requirements-0.2.4.tar.gz.sha256 new file mode 100644 index 00000000..5069ba93 --- /dev/null +++ b/registry/modules/specfact-requirements-0.2.4.tar.gz.sha256 @@ -0,0 +1 @@ +3d8751f3b15624262c24079900056d681b0668f24d5f69f9041c5b8096d370b3 diff --git a/registry/signatures/specfact-code-review-0.47.52.tar.sig b/registry/signatures/specfact-code-review-0.47.52.tar.sig new file mode 100644 index 00000000..61144493 --- /dev/null +++ b/registry/signatures/specfact-code-review-0.47.52.tar.sig @@ -0,0 +1 @@ +QJB9dXYvdTTzboXBRGGqMXoEKvoVdVwK/B9NVZ/xk2F8QwusMkXMGVqmQzF+biH4xIaujqK94wqw30s2yIUMDQ== diff --git a/registry/signatures/specfact-requirements-0.2.4.tar.sig b/registry/signatures/specfact-requirements-0.2.4.tar.sig new file mode 100644 index 00000000..c6c23f98 --- /dev/null +++ b/registry/signatures/specfact-requirements-0.2.4.tar.sig @@ -0,0 +1 @@ +piqKk1AXxSRms2k+hGRn8IHFQE4g60M14qU/rDdChn++sq/HBCj7S9fNq1LfhyKD0TfendIbz01Is+dIoB1cDw==