Skip to content

Commit 612cd4d

Browse files
feat(reqstool): add OpenSpec ↔ reqstool dogfooding layer (#174)
* feat(reqstool): add OpenSpec ↔ reqstool dogfooding layer Apply the reqstool-client#407 dogfooding pattern to this repo: tag RequirementsToolMojo's four behaviourally-distinct methods with @requirements, add a docs/reqstool SSOT (requirements.yml, software_verification_cases.yml, reqstool_config.yml), add matching thin OpenSpec capability specs, add .reqstool-ai.yaml, and close test-coverage gaps (attachArtifact and all three skip-flag paths were previously untested) so every SVC has a real verifying test. Wire CI to install reqstool, self-apply the plugin to produce the annotations dataset, and gate on `reqstool status --check-all-reqs-met` plus `openspec validate --specs --strict`. Required adding an explicit annotationProcessorPaths entry for reqstool-java-annotations: maven-compiler-plugin's non-forked javac invocation doesn't reliably auto-discover META-INF/services annotation processors from the plain compile classpath. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com> * Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com> --------- Signed-off-by: Jimisola Laursen <jimisola@jimisola.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 85c39ef commit 612cd4d

13 files changed

Lines changed: 455 additions & 16 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,48 @@ on:
1010
- reopened
1111
- synchronize
1212

13+
permissions:
14+
contents: read
15+
1316
jobs:
1417
linting:
1518
name: Check linting
1619
uses: ./.github/workflows/lint.yml
1720
build:
1821
runs-on: ubuntu-latest
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
reqstool-source: [pypi, main]
1926
steps:
2027
- uses: actions/checkout@v7
21-
- name: Build and run tests
28+
- name: Set up JDK
2229
uses: actions/setup-java@v5
2330
with:
2431
java-version: "21"
2532
distribution: "temurin"
26-
- run: mvn clean verify
33+
- name: Build and run tests
34+
run: mvn clean verify
35+
- name: Install plugin into local repository
36+
run: mvn -DskipTests install
37+
- name: Self-apply plugin to assemble dataset zip
38+
run: |
39+
VERSION=$(mvn -q help:evaluate -Dexpression=project.version -DforceStdout)
40+
mvn io.github.reqstool:reqstool-maven-plugin:${VERSION}:assemble-and-attach-zip-artifact \
41+
-Dreqstool.datasetPath=docs/reqstool \
42+
-Dreqstool.outputDirectory=target/reqstool
43+
- name: Install reqstool
44+
uses: reqstool/.github/.github/actions/install-reqstool@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22
45+
with:
46+
reqstool-source: ${{ matrix.reqstool-source }}
47+
- name: Validate reqstool spec completeness
48+
# not yet available in the latest PyPI release
49+
if: matrix.reqstool-source == 'main'
50+
uses: reqstool/.github/.github/actions/validate-reqstool@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22
51+
- name: Run reqstool status
52+
uses: reqstool/.github/.github/actions/reqstool-status@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22
53+
with:
54+
fail-if-incomplete: "true"
55+
56+
validate-openspec:
57+
uses: reqstool/.github/.github/workflows/common-validate-openspec.yml@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22

‎.reqstool-ai.yaml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# reqstool-ai configuration
2+
#
3+
# This file tells reqstool-ai skills where to find your reqstool files
4+
# and how to generate IDs for new requirements and SVCs.
5+
#
6+
# Place this file at: .reqstool-ai.yaml (project root)
7+
8+
# Project URN — matches the urn in your reqstool YAML files
9+
urn: reqstool-java-maven-plugin
10+
11+
# Revision string for new requirements and SVCs
12+
revision: "0.1.0"
13+
14+
# System-level reqstool directory (contains the SSOT requirements and SVCs)
15+
system:
16+
path: docs/reqstool
17+
18+
# Subproject modules — each module imports a subset of requirements/SVCs via filters
19+
#
20+
# Required fields per module:
21+
# path — path to the module's reqstool directory (contains filter files)
22+
# req_prefix — prefix for requirement IDs belonging to this module (e.g., CORE_)
23+
# svc_prefix — prefix for SVC IDs belonging to this module (e.g., SVC_CORE_)
24+
#
25+
# Add as many modules as your project has. The module name (key) is used in
26+
# commands like `/reqstool:status core` and `/reqstool:add-req core`.
27+
modules:
28+
# Matches the existing MAVEN_PLUGIN_NNN / SVC_MAVEN_PLUGIN_NNN ID convention.
29+
plugin:
30+
path: docs/reqstool
31+
req_prefix: "MAVEN_PLUGIN_"
32+
svc_prefix: "SVC_MAVEN_PLUGIN_"

‎docs/reqstool/reqstool_config.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/reqstool_config.schema.json
2+
3+
language: java
4+
build: maven
5+
resources:
6+
requirements: requirements.yml
7+
software_verification_cases: software_verification_cases.yml
8+
annotations: ../../target/reqstool/annotations.yml
9+
test_results:
10+
- ../../target/surefire-reports/*.xml

‎docs/reqstool/requirements.yml‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/requirements.schema.json
2+
3+
metadata:
4+
urn: reqstool-java-maven-plugin
5+
variant: microservice
6+
title: Reqstool Java Maven Plugin
7+
url: https://github.com/reqstool/reqstool-java-maven-plugin
8+
9+
requirements:
10+
- id: MAVEN_PLUGIN_001
11+
title: Combine requirement and SVC annotations into a single annotations.yml
12+
significance: shall
13+
description: >-
14+
The plugin shall combine the requirement-annotation and SVC-annotation
15+
files produced by the annotation processor (one from main sources, one
16+
from test sources) into a single annotations.yml, preserving any
17+
entries already present under each section.
18+
categories: [functional-suitability]
19+
revision: "0.1.0"
20+
- id: MAVEN_PLUGIN_002
21+
title: Assemble the reqstool zip artifact during the verify phase
22+
significance: shall
23+
description: >-
24+
During the verify phase, the plugin shall assemble a zip artifact
25+
containing the mandatory requirements.yml, the optional
26+
software_verification_cases.yml/manual_verification_results.yml/annotations.yml
27+
when present, any test result files matching the configured patterns,
28+
and a generated reqstool_config.yml describing the included resources.
29+
categories: [functional-suitability]
30+
revision: "0.1.0"
31+
- id: MAVEN_PLUGIN_003
32+
title: Attach the assembled zip artifact to the Maven project
33+
significance: shall
34+
description: >-
35+
The plugin shall attach the assembled zip artifact to the Maven
36+
project under the "reqstool" classifier, so it is installed/deployed
37+
alongside the project's other artifacts.
38+
categories: [functional-suitability]
39+
revision: "0.1.0"
40+
- id: MAVEN_PLUGIN_004
41+
title: Honor skip flags to bypass execution, zip assembly, or artifact attachment
42+
significance: shall
43+
description: >-
44+
The plugin shall support independent skip/skipAssembleZipArtifact/skipAttachZipArtifact
45+
flags, so the whole goal, just the zip assembly, or just the artifact
46+
attachment can be bypassed without modifying the rest of the build.
47+
categories: [functional-suitability]
48+
revision: "0.1.0"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/software_verification_cases.schema.json
2+
3+
cases:
4+
- id: SVC_MAVEN_PLUGIN_001
5+
requirement_ids: ["MAVEN_PLUGIN_001"]
6+
title: "Verify implementation and test annotation files are combined into one annotations.yml"
7+
verification: automated-test
8+
revision: "0.1.0"
9+
10+
- id: SVC_MAVEN_PLUGIN_002
11+
requirement_ids: ["MAVEN_PLUGIN_002"]
12+
title: "Verify the zip artifact is assembled with the mandatory and optional reqstool resources"
13+
verification: automated-test
14+
revision: "0.1.0"
15+
16+
- id: SVC_MAVEN_PLUGIN_003
17+
requirement_ids: ["MAVEN_PLUGIN_003"]
18+
title: "Verify the assembled zip is attached to the Maven project under the reqstool classifier"
19+
verification: automated-test
20+
revision: "0.1.0"
21+
22+
- id: SVC_MAVEN_PLUGIN_004
23+
requirement_ids: ["MAVEN_PLUGIN_004"]
24+
title: "Verify skip, skipAssembleZipArtifact, and skipAttachZipArtifact each bypass their respective step"
25+
verification: automated-test
26+
revision: "0.1.0"

‎openspec/openspecui.hooks.ts‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// @reqstool-openspec-hooks: 0.1.1
2+
import { spawn, ChildProcess } from "child_process";
3+
import type { OnReadDocumentHookV1 } from "openspecui/hooks";
4+
5+
// Minimal MCP client over stdio (JSON-RPC 2.0, newline-delimited).
6+
// Uses only Node.js built-ins — no npm packages required.
7+
class McpStdioClient {
8+
private proc: ChildProcess;
9+
private buf = "";
10+
private pending = new Map<
11+
number,
12+
{ resolve: (v: unknown) => void; reject: (e: Error) => void }
13+
>();
14+
private id = 1;
15+
readonly ready: Promise<void>;
16+
17+
constructor(cwd: string) {
18+
this.proc = spawn("reqstool", ["mcp"], {
19+
cwd,
20+
stdio: ["pipe", "pipe", "pipe"],
21+
});
22+
this.proc.stdout!.on("data", (chunk: Buffer) => {
23+
this.buf += chunk.toString();
24+
let nl: number;
25+
while ((nl = this.buf.indexOf("\n")) !== -1) {
26+
const line = this.buf.slice(0, nl).trim();
27+
this.buf = this.buf.slice(nl + 1);
28+
if (line) this.handle(line);
29+
}
30+
});
31+
this.ready = this.init();
32+
}
33+
34+
private handle(line: string) {
35+
try {
36+
const msg = JSON.parse(line) as { id?: number; result?: unknown; error?: { message: string } };
37+
if (msg.id !== undefined) {
38+
const p = this.pending.get(msg.id);
39+
if (p) {
40+
this.pending.delete(msg.id);
41+
msg.error ? p.reject(new Error(msg.error.message)) : p.resolve(msg.result);
42+
}
43+
}
44+
} catch (e) {
45+
console.warn("[reqstool-openspec] Skipping non-JSON line from reqstool mcp:", e instanceof Error ? e.message : e);
46+
}
47+
}
48+
49+
private send(method: string, params: unknown, expectReply = true): Promise<unknown> {
50+
if (!expectReply) {
51+
this.proc.stdin!.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
52+
return Promise.resolve();
53+
}
54+
const id = this.id++;
55+
return new Promise((resolve, reject) => {
56+
this.pending.set(id, { resolve, reject });
57+
this.proc.stdin!.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
58+
});
59+
}
60+
61+
private async init(): Promise<void> {
62+
await this.send("initialize", {
63+
protocolVersion: "2024-11-05",
64+
capabilities: { tools: {} },
65+
clientInfo: { name: "openspecui", version: "1.0" },
66+
});
67+
this.send("notifications/initialized", {}, false);
68+
}
69+
70+
async enrich(content: string, preset: string): Promise<string> {
71+
await this.ready;
72+
const result = (await this.send("tools/call", {
73+
name: "enrich_document",
74+
arguments: { content, preset },
75+
})) as { content: { text: string }[] };
76+
return result.content[0].text;
77+
}
78+
79+
close() {
80+
this.proc.stdin?.end();
81+
this.proc.kill();
82+
}
83+
}
84+
85+
let client: McpStdioClient | null = null;
86+
87+
export const onReadDocument: OnReadDocumentHookV1 = async (ctx, read) => {
88+
if (!client) {
89+
client = new McpStdioClient(ctx.projectDir);
90+
ctx.lifecycle.onDispose(() => {
91+
client?.close();
92+
client = null;
93+
});
94+
}
95+
96+
const result = await read();
97+
const preset = `openspec:${ctx.document.kind}`;
98+
99+
try {
100+
const enriched = await client.enrich(result.markdown, preset);
101+
return { ...result, markdown: enriched, sourceLabel: `reqstool ${preset}` };
102+
} catch (e) {
103+
return {
104+
...result,
105+
diagnostics: [{ level: "warning", message: `reqstool enrich failed: ${e}` }],
106+
};
107+
}
108+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Annotation Combination Specification
2+
3+
## Purpose
4+
5+
Requirement and SVC content is owned by reqstool (single source of truth). This spec references
6+
reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via
7+
`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`.
8+
9+
## Requirements
10+
11+
### Requirement: MAVEN_PLUGIN_001
12+
The system SHALL implement MAVEN_PLUGIN_001.
13+
14+
#### Scenario: SVC_MAVEN_PLUGIN_001
15+
The system SHALL pass SVC_MAVEN_PLUGIN_001.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Artifact Attachment Specification
2+
3+
## Purpose
4+
5+
Requirement and SVC content is owned by reqstool (single source of truth). This spec references
6+
reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via
7+
`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`.
8+
9+
## Requirements
10+
11+
### Requirement: MAVEN_PLUGIN_003
12+
The system SHALL implement MAVEN_PLUGIN_003.
13+
14+
#### Scenario: SVC_MAVEN_PLUGIN_003
15+
The system SHALL pass SVC_MAVEN_PLUGIN_003.

‎openspec/specs/skip-flags/spec.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Skip Flags Specification
2+
3+
## Purpose
4+
5+
Requirement and SVC content is owned by reqstool (single source of truth). This spec references
6+
reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via
7+
`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`.
8+
9+
## Requirements
10+
11+
### Requirement: MAVEN_PLUGIN_004
12+
The system SHALL implement MAVEN_PLUGIN_004.
13+
14+
#### Scenario: SVC_MAVEN_PLUGIN_004
15+
The system SHALL pass SVC_MAVEN_PLUGIN_004.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Zip Assembly Specification
2+
3+
## Purpose
4+
5+
Requirement and SVC content is owned by reqstool (single source of truth). This spec references
6+
reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via
7+
`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`.
8+
9+
## Requirements
10+
11+
### Requirement: MAVEN_PLUGIN_002
12+
The system SHALL implement MAVEN_PLUGIN_002.
13+
14+
#### Scenario: SVC_MAVEN_PLUGIN_002
15+
The system SHALL pass SVC_MAVEN_PLUGIN_002.

0 commit comments

Comments
 (0)