TL;DR: SpecFact shipped a specfact requirements command group in the 0.50.x line: import, validate, list, and coverage over normalized requirement records. Useful, but manual — you had to hand-author a JSON/YAML records file. The next step is import-first: SpecFact reads your native OpenSpec change folders and Spec Kit feature folders, normalizes their scenarios into given/when/then rules with content-hash provenance, and turns validation into deterministic pass/fail gates. No new authoring schema. No LLM in the gate path. Your specs stay where they are.

Status at time of writing: The requirements module and its data model shipped with specfact-cli 0.49.0–0.50.2. The import-first extension is the rescoped OpenSpec change openspec-01-intent-trace, tracked as nold-ai/specfact-cli#350 (core contracts) and nold-ai/specfact-cli-modules#168 (runtime), currently in implementation. Commands marked "landing" below are from those changes.


The Gap: Shipped Commands, Manual Fuel

The 0.50.x releases delivered a working requirements evidence surface:

# Shipped today
specfact requirements import --from-file records.yaml --bundle .specfact/project
specfact requirements validate --bundle .specfact/project --profile enterprise
specfact requirements list --bundle .specfact/project --show-coverage
specfact requirements coverage --bundle .specfact/project --format json

Under the hood sits a deliberately narrow data model: a RequirementInput record with a stable ID, source references, given/when/then business rules, constraints, and links to downstream evidence such as tests and validation runs. Validation is profile-aware and exits non-zero on failure, so CI can consume it directly.

The problem was the first flag: --from-file. Someone had to write that records file by hand. For a tool whose whole thesis is deterministic evidence over manual ceremony, that was a contradiction we needed to remove.


The Trap We Avoided: Owning an Authoring Schema

The original plan for this change — dating back to March — was an optional ## Intent Trace YAML block that authors would hand-write into their OpenSpec proposals. SpecFact would define the schema, validate it strictly, and import it.

We retired that scope before implementing a single line of it.

The reason is accountability. OpenSpec and Spec Kit already own specification and planning authoring. They have their own formats, their own review flows, and their own communities. The moment SpecFact defines a metadata block that upstream authors must maintain, SpecFact becomes a second authoring system — and every proposal that skips the block becomes silently invisible to validation. That is exactly the "markdown islands" failure mode we keep writing about, recreated one layer up.

The principle we settled on instead:

OpenSpec and Spec Kit own authoring. SpecFact owns verification evidence.

SpecFact parses what those tools already produce. Upstream changes nothing. Nobody maintains a parallel schema. And imports are strictly read-only: the importer never creates or mutates a file inside openspec/ or a Spec Kit feature folder.


Import-First: Native Artifacts In, Normalized Evidence Out

The rescoped change makes upstream folders first-class import sources (landing with #350/#168):

# Landing with openspec-01 (in implementation)
specfact requirements import --from-openspec --bundle .specfact/project
specfact requirements import --from-speckit specs/042-payment-retry --bundle .specfact/project

What happens on import is deliberately boring:

  • OpenSpec change folders (proposal.md, specs/*/spec.md, tasks.md) and Spec Kit feature folders (spec.md, plan.md, tasks.md) are parsed with the deterministic parsers SpecFact already ships for its bridge workflows.
  • Every spec requirement becomes a RequirementInput with a stable derived ID such as openspec:<change>:<capability>:<slug>. Re-importing an unchanged folder is idempotent — no duplicates, ever.
  • Every GIVEN/WHEN/THEN scenario becomes a given/when/then business rule on that record. Your acceptance scenarios stop being prose and become checkable structure.
  • Every source reference records a sha256: content hash of the artifact at import time.

That last point is small and load-bearing. The hash is what turns "we imported this once" into "we can mechanically prove the evidence is still current."


Gates, Not Vibes

Import alone is bookkeeping. The product value is what validation can now assert. The rescoped change adds four deterministic gate categories to specfact requirements validate:

Gate Fires when Why you care
scenario-unverified A business rule has no linked test or validation evidence Acceptance criteria exist but nothing proves them.
stale-import The source content hash no longer matches the artifact on disk Someone edited the spec after you imported it. Your evidence is out of date.
source-missing The referenced upstream artifact no longer exists The requirement points at a deleted or moved spec.
ambiguous-mapping Two sources claim the same requirement identity Conflicting truth. Nothing gets silently overwritten.

Severity is profile-driven: a solo profile reports most findings as warnings; strict and enterprise profiles turn them into errors and a non-zero exit. Every finding ships as machine-readable JSON, so the same gate feeds CI, review bots, and AI IDE remediation loops.

Notice what is not in that list: no LLM judgment. Every gate is a mechanical check — a hash comparison, a link count, an identity collision. That is a deliberate constraint. AI can help you fix findings; it does not get to decide whether your build passes.


Fixing a Shipped Gap Along the Way

Rescoping forced us to re-read the shipped code against the shipped promises, and that surfaced a real misalignment: profile configuration layering (shipped earlier in the 0.5x line) writes per-tier validation settings — including requirements_schema.required_fields — into layered config, but requirements validate ignored all of it and hardcoded a startup default.

Run specfact init --profile enterprise today and requirements validation still quietly judges you at startup strictness. That fix is now folded into the same change: when --profile is omitted, the effective profile resolves from layered configuration (profile defaults, org baseline, repo overlay, developer local), and an explicit flag always wins.

This is the quiet benefit of spec-first work: the misalignment was found by rescoping a proposal against canonical specs, not by a production incident.


What Stays Out

Scope discipline is most visible in what we said no to:

  • Backlog import and drift detection (GitHub Issues, Jira, Azure DevOps, Linear) is parked, not dead. Local artifacts first; tracker integration when the import-first loop proves its value.
  • No authoring commands. SpecFact's own plan-authoring surface is repositioned as secondary. The documented flagship path is import.
  • No write-back. Upstream directories are byte-identical after every import, validate, and coverage run — and there is a contract test asserting exactly that.

The Loop This Enables

Put together, the target workflow looks like this:

# Author specs where you already author them (OpenSpec / Spec Kit)
# Then, in CI or locally:

specfact requirements import --from-openspec --bundle .specfact/project
specfact requirements validate --bundle .specfact/project --format json

# Exit code 0: every scenario has evidence, nothing stale, no conflicts.
# Exit code 1: deterministic findings, with requirement IDs and source paths.

Specs stay accountable upstream. Evidence stays verifiable downstream. And the thing in between — the import, the normalization, the gate — is deterministic enough that you can wire it into a merge check without arguing with it.

Further Reading