Skip to content

feat(ci): add reusable workflow to validate OpenSpec specs - #40

Merged
jimisola merged 1 commit into
mainfrom
feat/common-validate-openspec
Jun 21, 2026
Merged

feat(ci): add reusable workflow to validate OpenSpec specs#40
jimisola merged 1 commit into
mainfrom
feat/common-validate-openspec

Conversation

@jimisola

Copy link
Copy Markdown
Member

What

Adds common-validate-openspec.yml, a reusable workflow that runs openspec validate --specs --strict against a repo's openspec/ directory.

Why

The OpenSpec ↔ reqstool dogfooding rollout (tracked in reqstool/PLAN_dog_fooding.md) is adding the same openspec validate --specs --strict CI step to every participating repo's build.yml (first one: reqstool-demo#104). Rather than copy-pasting the same npm-install-and-validate steps into ~10 more repos, this extracts it once, following the same common-*/reusable-workflow pattern already used for common-validate-renovate.yml, java-build-maven.yml, etc.

This step is fully build-system-agnostic (pure Node, no language toolchain dependency) — unlike the accompanying reqstool status/reqstool validate steps in each repo, which depend on per-language build artifacts and stay inline per-repo for now (those aren't safely generalizable across Java/Python/TypeScript without more design work).

Usage

jobs:
  validate-openspec:
    uses: reqstool/.github/.github/workflows/common-validate-openspec.yml@<pinned-sha> # main <date>

🤖 Generated with Claude Code

Extracts the openspec validate --specs --strict step that the OpenSpec ↔
reqstool dogfooding rollout (see reqstool/PLAN_dog_fooding.md) is adding
to every participating repo's build.yml, so it lives once instead of
being copy-pasted into each one. This step is fully build-system-agnostic
(pure Node, no language toolchain dependency), unlike the reqstool
status/validate steps which depend on per-language build artifacts and
stay inline per-repo for now.

First consumer: reqstool-demo#104.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
@jimisola
jimisola merged commit 054e9ca into main Jun 21, 2026
22 checks passed
@jimisola
jimisola deleted the feat/common-validate-openspec branch June 21, 2026 16:57
jimisola added a commit that referenced this pull request Jun 21, 2026
* feat(ci): add composite actions for reqstool validate/status

Two separate composite actions for the OpenSpec dogfooding rollout
(reqstool/PLAN_dog_fooding.md), each installing reqstool from either
PyPI or reqstool-client@main:

- validate-reqstool: runs `reqstool validate --strict` (spec completeness
  — every requirement has SVCs, manual SVCs have MVRs).
- reqstool-status: runs `reqstool status --verbosity compact`, optionally
  gated on `--check-all-reqs-met` via fail-if-incomplete (left off by
  default for repos that intentionally have incomplete requirements, e.g.
  demo/fixture repos).

These are composite actions, not reusable workflows (unlike
common-validate-openspec.yml, #40): they run as steps within the calling
job, after that job's own build step, since reqstool status/validate
needs build-time artifacts (annotations.yml, test results) that a
separate reusable-workflow job wouldn't have access to.

First consumer: reqstool-demo#104.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* refactor(ci): extract shared install-reqstool composite action

validate-reqstool and reqstool-status both duplicated the same
"set up Python + pip install reqstool from pypi/main" logic. Extract it
into a third composite action both now call via a nested
`uses: ./.github/actions/install-reqstool`, so the install logic lives in
one place.

Found via self-review on #41.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(security): pass action inputs via env instead of inline shell interpolation

CodeQL flagged code-injection risk: ${{ inputs.x }} interpolated directly
into run: shell blocks substitutes literal script text before execution,
so a malicious input value could inject arbitrary shell commands. Pass
all inputs used in run: blocks via env: instead and reference them as
shell variables, which the shell expands safely without re-parsing as
script.

Affects install-reqstool, validate-reqstool, reqstool-status.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

---------

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
jimisola added a commit to reqstool/reqstool-demo that referenced this pull request Jun 21, 2026
Replaces the inline reqstool install/status/validate steps and the
inline openspec install/validate step with the reusable building blocks
just added to reqstool/.github for this rollout:

- reqstool/.github/.github/actions/validate-reqstool (reqstool-client#412
  follow-up notwithstanding, runs reqstool validate --strict; only called
  for the main matrix leg since that subcommand isn't on PyPI yet)
- reqstool/.github/.github/actions/reqstool-status (runs reqstool status,
  fail-if-incomplete left false since this repo intentionally has
  incomplete requirements)
- reqstool/.github/.github/workflows/common-validate-openspec.yml (now a
  separate job, since it's a reusable *workflow* rather than a composite
  action)

Pinned to reqstool/.github@cd3b5e8 (main, 2026-06-21) per
reqstool/.github#40 and #41.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
jimisola added a commit to reqstool/reqstool-demo that referenced this pull request Jun 21, 2026
…howcase (#104)

* feat(openspec): bootstrap OpenSpec spec layer for the demo's status showcase

Adds an OpenSpec layer mirroring reqstool-client#407's dogfooding pattern,
scoped to this repo's actual purpose: showcasing every reqstool status
outcome (pass, manual-fail, not-implemented, failing-test, skipped-test,
missing-test) across six small feature capabilities.

- openspec/specs/{greeting,billing,reporting,validation,notifications,
  audit-logging}/spec.md — one capability per status outcome, in thin
  ID-reference form against the existing requirements.yml/SVC IDs
  (REQ_PASS, REQ_MANUAL_FAIL, ...). IDs are kept as-is (no capability
  prefix) since they're already domain-named and intentionally demonstrate
  non-passing states, unlike reqstool-client's renamed/100%-passing set.
- openspec/openspecui.hooks.ts — reqstool-ai's openspecui enrichment hook.
- .reqstool-ai.yaml — single "demo" module, domain-specific (no) prefix.
- .mcp.json — project-scoped reqstool MCP server entry.

Validated: openspec validate --specs --strict (6/6 pass), reqstool
validate --strict (pass), reqstool status (1/6 complete, by design),
mvn clean verify (build succeeds; one intentionally failing test, by
design), and CLI vs MCP get_requirements_status now agree exactly across
all 6 requirements (confirmed after reqstool-client#411's fix).

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* ci(build): validate OpenSpec specs, clarify reqstool-ai prefix comment

- Add an openspec validate --specs --strict step to build.yml so spec/SSOT
  drift fails CI instead of relying on the manual run documented in #104's
  PR description; build-docs.yml's docs/** path filter doesn't cover
  openspec/**, so nothing else was catching this.
- Expand the .reqstool-ai.yaml comment explaining why req_prefix is empty
  while svc_prefix isn't.

Found by /x:full-pr-review on #104. reqstool validate --strict was
intentionally not added yet — not in the currently published PyPI release.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* ci(build): matrix reqstool CI across PyPI and reqstool-client@main

Adds an openspec validate --specs --strict step to build.yml so spec/SSOT
drift fails CI instead of relying on the manual run documented in #104's
PR description.

Runs reqstool status/validate against both the latest PyPI release and
reqstool-client's main branch in a matrix, since reqstool-client is
deliberately holding off its next release until the org-wide OpenSpec
dogfooding rollout is complete, and main already has fixes (#411) and
commands (validate) not yet published. reqstool validate --strict only
runs on the main leg since that subcommand isn't on PyPI yet. CI continues
to run the latest PyPI release as its baseline; see reqstool-demo#105 for
tracking divergence between the two legs and collapsing back to PyPI-only
once reqstool-client cuts its next release.

Also expands the .reqstool-ai.yaml comment explaining why req_prefix is
empty while svc_prefix isn't.

Found by /x:full-pr-review on #104.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(ci): pin Python 3.13 for reqstool install step

build (main) failed: the runner's default Python (3.12) doesn't satisfy
reqstool-client@main's reqstool-python-decorators>=0.1.0 dependency, which
requires Python >=3.13. Add actions/setup-python@v6 pinned to 3.13,
matching reqstool-client's own CI convention (build.yml, lint.yml).

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* ci(build): use shared reqstool/openspec workflows from reqstool/.github

Replaces the inline reqstool install/status/validate steps and the
inline openspec install/validate step with the reusable building blocks
just added to reqstool/.github for this rollout:

- reqstool/.github/.github/actions/validate-reqstool (reqstool-client#412
  follow-up notwithstanding, runs reqstool validate --strict; only called
  for the main matrix leg since that subcommand isn't on PyPI yet)
- reqstool/.github/.github/actions/reqstool-status (runs reqstool status,
  fail-if-incomplete left false since this repo intentionally has
  incomplete requirements)
- reqstool/.github/.github/workflows/common-validate-openspec.yml (now a
  separate job, since it's a reusable *workflow* rather than a composite
  action)

Pinned to reqstool/.github@cd3b5e8 (main, 2026-06-21) per
reqstool/.github#40 and #41.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(ci): call install-reqstool explicitly, per reqstool/.github#48

validate-reqstool/reqstool-status no longer install reqstool themselves
(reqstool/.github#48 removed their broken nested
./.github/actions/install-reqstool reference, which only resolved when
called from within reqstool/.github itself, not from a consuming repo
like this one). Call install-reqstool explicitly as its own step first.

Pinned to reqstool/.github@11a00fc (main, 2026-06-21).

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* ci(build): re-pin reqstool/.github to 5bdf4e5

Picks up reqstool/.github#59 (drop --verbosity compact from
reqstool-status, also not yet on PyPI — same situation as the validate
subcommand fixed in #48), found via this PR's pypi matrix leg failing
with "invalid choice: 'compact'".

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(reqstool): use 0.1.0 as the first revision, per semver

requirements.yml and software_verification_cases.yml used 0.0.1 for every
requirement/SVC. Per semver, 0.0.x is reserved for pre-release/unstable
work before any real first version — the first published revision of a
thing should be 0.1.0, which also matches reqstool-ai's own
.reqstool-ai.yaml.template default ("Revision string for new requirements
and SVCs. Default: 0.1.0"). My earlier .reqstool-ai.yaml matched the
existing (non-conventional) 0.0.1 instead of fixing it forward.

manual_verification_results.yml has no revision field in its schema, so
nothing to change there.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(security): add explicit permissions block to build.yml

CodeQL flagged this 3 times across commits: the workflow didn't limit
GITHUB_TOKEN permissions. Add permissions: contents: read at the
workflow root, matching the existing convention in this repo
(build-docs.yml, check-semantic-pr.yml).

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

---------

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant