From b7bac8736afa3156d804bac950ff0f05d211a51f Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Tue, 23 Jun 2026 18:13:35 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(reqstool):=20dogfood=20OpenSpec=20?= =?UTF-8?q?=E2=86=94=20reqstool=20traceability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bootstraps the OpenSpec spec layer and reqstool SSOT for this library, mirroring reqstool-client#407's pattern: - 3 capability specs in openspec/specs/ (annotation-definitions, annotation-processing, yaml-export); docs/reqstool/ SSOT with ANNOTATIONS_001-003 / SVC_ANNOTATIONS_001-003 - Self-applies this library's own @Requirements/@SVCs annotations to its own Requirements/SVCs declarations and AbstractAnnotationsProcessor's process()/exportToYAML() methods -- via a new `self-apply` Maven profile (two-pass: install, then re-compile main+test with annotationProcessorPaths pointing at the just-installed jar) - Fixes a real bug along the way: SVCsTests.java's testMultipleSVCs instantiated Requirements instead of SVCs - CI (build.yml): [pypi, main] matrix via shared reqstool/.github actions; self-apply + yq-merge step (main/test annotation outputs land in separate generated-sources dirs with no existing mojo to combine them, unlike the Maven/Gradle plugins) producing target/reqstool/annotations.yml; validate-openspec job - .claude/settings.json (auto-enables reqstool/reqstool-openspec plugins) and CONTRIBUTING.md prerequisites added from the start, per the pattern fixed in reqstool-java-gradle-plugin#69/ reqstool-java-maven-plugin#176/reqstool-python-poetry-plugin#132 Found and fixed upstream along the way: reqstool-client#418/#419 -- annotations.schema.json's elementKind enum (and a second, independent CHECK constraint in storage/schema.py) was missing ANNOTATION_TYPE, the ElementKind Java reports for @interface declarations. The CHECK constraint gap was silently swallowing inserts via `INSERT OR IGNORE` with no warning logged, the kind of silent-drift bug this dogfooding rollout's CLI-vs-MCP/schema validation step exists to catch. Validated: reqstool status (3/3 PASS), reqstool validate --strict, openspec validate --specs --strict (3 passed), full test suite green. CLI-only validation performed (no MCP parity check) -- low risk given no skipped/missing-test scenarios are present here, the known CLI/MCP divergence area (reqstool-client#411). Signed-off-by: Jimisola Laursen --- .claude/settings.json | 15 +++ .github/workflows/build.yml | 44 ++++++- .gitignore | 3 +- .reqstool-ai.yaml | 32 ++++++ CONTRIBUTING.md | 14 +++ docs/reqstool/reqstool_config.yml | 10 ++ docs/reqstool/requirements.yml | 42 +++++++ docs/reqstool/software_verification_cases.yml | 20 ++++ openspec/openspecui.hooks.ts | 108 ++++++++++++++++++ openspec/specs/annotation-definitions/spec.md | 15 +++ openspec/specs/annotation-processing/spec.md | 15 +++ openspec/specs/yaml-export/spec.md | 15 +++ pom.xml | 32 ++++++ .../reqstool/annotations/Requirements.java | 1 + .../io/github/reqstool/annotations/SVCs.java | 1 + .../AbstractAnnotationsProcessor.java | 3 + .../annotations/RequirementsTests.java | 2 + .../reqstool/annotations/SVCsTests.java | 6 +- .../processor/AnnotationsProcessorTests.java | 3 + 19 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .reqstool-ai.yaml create mode 100644 docs/reqstool/reqstool_config.yml create mode 100644 docs/reqstool/requirements.yml create mode 100644 docs/reqstool/software_verification_cases.yml create mode 100644 openspec/openspecui.hooks.ts create mode 100644 openspec/specs/annotation-definitions/spec.md create mode 100644 openspec/specs/annotation-processing/spec.md create mode 100644 openspec/specs/yaml-export/spec.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..876c06b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "extraKnownMarketplaces": { + "reqstool-ai": { + "source": { + "source": "github", + "repo": "reqstool/reqstool-ai" + }, + "autoUpdate": true + } + }, + "enabledPlugins": { + "reqstool@reqstool-ai": true, + "reqstool-openspec@reqstool-ai": true + } +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 683af92..a5f4140 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,17 +10,57 @@ on: - reopened - synchronize +permissions: + contents: read + jobs: linting: name: Check linting uses: ./.github/workflows/lint.yml build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + reqstool-source: [pypi, main] steps: - uses: actions/checkout@v7 - - name: Build and run tests + - name: Set up JDK uses: actions/setup-java@v5 with: java-version: "21" distribution: "temurin" - - run: mvn clean verify + - name: Build and run tests + run: mvn clean verify + - name: Install into local repository + run: mvn -DskipTests install + - name: Self-apply own annotation processor to own sources + run: mvn compiler:compile compiler:testCompile -Pself-apply + - name: Combine main/test annotation processor output + # The processor's main/test annotation outputs land in separate + # generated-sources directories (no shared mojo to combine them, unlike + # the Maven/Gradle plugins) -- merge them the same way those plugins' + # combineOutput() would, so reqstool status has real, non-fabricated data. + run: | + mkdir -p target/reqstool + { + echo "# yaml-language-server: \$schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/annotations.schema.json" + yq eval-all '. as $item ireduce ({}; . * $item )' \ + target/generated-sources/annotations/resources/annotations.yml \ + target/generated-test-sources/test-annotations/resources/annotations.yml | grep -v "^#" + } > target/reqstool/annotations.yml + - name: Install reqstool + uses: reqstool/.github/.github/actions/install-reqstool@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22 + with: + reqstool-source: ${{ matrix.reqstool-source }} + - name: Validate reqstool spec completeness + # not yet available in the latest PyPI release + if: matrix.reqstool-source == 'main' + uses: reqstool/.github/.github/actions/validate-reqstool@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22 + - name: Run reqstool status + uses: reqstool/.github/.github/actions/reqstool-status@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22 + with: + fail-if-incomplete: "true" + + validate-openspec: + uses: reqstool/.github/.github/workflows/common-validate-openspec.yml@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22 diff --git a/.gitignore b/.gitignore index 1c7c3d1..670f108 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ buildNumber.properties ### VisualStudioCode ### .vscode/* -.claude/ +.claude/* +!.claude/settings.json diff --git a/.reqstool-ai.yaml b/.reqstool-ai.yaml new file mode 100644 index 0000000..9b4cdf2 --- /dev/null +++ b/.reqstool-ai.yaml @@ -0,0 +1,32 @@ +# reqstool-ai configuration +# +# This file tells reqstool-ai skills where to find your reqstool files +# and how to generate IDs for new requirements and SVCs. +# +# Place this file at: .reqstool-ai.yaml (project root) + +# Project URN — matches the urn in your reqstool YAML files +urn: reqstool-java-annotations + +# Revision string for new requirements and SVCs +revision: "0.1.0" + +# System-level reqstool directory (contains the SSOT requirements and SVCs) +system: + path: docs/reqstool + +# Subproject modules — each module imports a subset of requirements/SVCs via filters +# +# Required fields per module: +# path — path to the module's reqstool directory (contains filter files) +# req_prefix — prefix for requirement IDs belonging to this module (e.g., CORE_) +# svc_prefix — prefix for SVC IDs belonging to this module (e.g., SVC_CORE_) +# +# Add as many modules as your project has. The module name (key) is used in +# commands like `/reqstool:status core` and `/reqstool:add-req core`. +modules: + # Matches the existing ANNOTATIONS_NNN / SVC_ANNOTATIONS_NNN ID convention. + annotations: + path: docs/reqstool + req_prefix: "ANNOTATIONS_" + svc_prefix: "SVC_ANNOTATIONS_" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d28784..a4978ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ For DCO sign-off, commit conventions, and code review process, see the organizat - Java 21+ - Maven 3.9+ +- [reqstool](https://github.com/reqstool/reqstool-client) (`pipx install reqstool`) +- [OpenSpec](https://github.com/Fission-AI/OpenSpec) (`npm install -g @fission-ai/openspec`) ## Setup @@ -16,6 +18,18 @@ git clone https://github.com/reqstool/reqstool-java-annotations.git cd reqstool-java-annotations ``` +If using Claude Code, opening this repo will prompt you to confirm adding the `reqstool-ai` +marketplace and enabling the `reqstool`/`reqstool-openspec` plugins (configured in +`.claude/settings.json`) — accept the prompt. + +Then regenerate the `opsx` slash commands and OpenSpec skills +(`.claude/commands/opsx/`, `.claude/skills/openspec-*`) — they're CLI-generated tool scaffolding, +not committed to the repo: + +```bash +openspec update # or: openspec init --tools claude --force +``` + ## Build & Test ```bash diff --git a/docs/reqstool/reqstool_config.yml b/docs/reqstool/reqstool_config.yml new file mode 100644 index 0000000..2688a39 --- /dev/null +++ b/docs/reqstool/reqstool_config.yml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/reqstool_config.schema.json + +language: java +build: maven +resources: + requirements: requirements.yml + software_verification_cases: software_verification_cases.yml + annotations: ../../target/reqstool/annotations.yml + test_results: + - ../../target/surefire-reports/*.xml diff --git a/docs/reqstool/requirements.yml b/docs/reqstool/requirements.yml new file mode 100644 index 0000000..202ac4a --- /dev/null +++ b/docs/reqstool/requirements.yml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/requirements.schema.json +--- +metadata: + urn: reqstool-java-annotations + variant: microservice + title: Reqstool Java Annotations + url: https://github.com/reqstool/reqstool-java-annotations + +requirements: + - id: ANNOTATIONS_001 + title: Annotation definitions + significance: shall + description: >- + The library shall define @Requirements (targeting types, methods, and fields) and + @SVCs (targeting types and methods) annotations, each retaining a string array of + IDs at source level only. + categories: + - functional-suitability + revision: "0.1.0" + + - id: ANNOTATIONS_002 + title: Annotation element collection + significance: shall + description: >- + The annotation processor shall collect every element annotated with a supported + annotation across all compilation rounds, accumulating multiple element locations + against the same requirement or SVC ID. + categories: + - functional-suitability + revision: "0.1.0" + + - id: ANNOTATIONS_003 + title: YAML export + significance: shall + description: >- + The annotation processor shall serialize collected annotation locations to an + annotations.yml resource at the standard SOURCE_OUTPUT location, with a JSON + schema header comment and the appropriate implementations/tests element key for + @Requirements and @SVCs respectively. + categories: + - functional-suitability + revision: "0.1.0" diff --git a/docs/reqstool/software_verification_cases.yml b/docs/reqstool/software_verification_cases.yml new file mode 100644 index 0000000..140c98d --- /dev/null +++ b/docs/reqstool/software_verification_cases.yml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/software_verification_cases.schema.json + +cases: + - id: SVC_ANNOTATIONS_001 + requirement_ids: ["ANNOTATIONS_001"] + title: "Verify @Requirements and @SVCs retain single and multiple string values" + verification: automated-test + revision: "0.1.0" + + - id: SVC_ANNOTATIONS_002 + requirement_ids: ["ANNOTATIONS_002"] + title: "Verify the processor collects multiple element locations for the same ID, end to end" + verification: automated-test + revision: "0.1.0" + + - id: SVC_ANNOTATIONS_003 + requirement_ids: ["ANNOTATIONS_003"] + title: "Verify the exported YAML matches the expected schema-tagged implementations/tests structure" + verification: automated-test + revision: "0.1.0" diff --git a/openspec/openspecui.hooks.ts b/openspec/openspecui.hooks.ts new file mode 100644 index 0000000..49a96ee --- /dev/null +++ b/openspec/openspecui.hooks.ts @@ -0,0 +1,108 @@ +// @reqstool-openspec-hooks: 0.1.1 +import { spawn, ChildProcess } from "child_process"; +import type { OnReadDocumentHookV1 } from "openspecui/hooks"; + +// Minimal MCP client over stdio (JSON-RPC 2.0, newline-delimited). +// Uses only Node.js built-ins — no npm packages required. +class McpStdioClient { + private proc: ChildProcess; + private buf = ""; + private pending = new Map< + number, + { resolve: (v: unknown) => void; reject: (e: Error) => void } + >(); + private id = 1; + readonly ready: Promise; + + constructor(cwd: string) { + this.proc = spawn("reqstool", ["mcp"], { + cwd, + stdio: ["pipe", "pipe", "pipe"], + }); + this.proc.stdout!.on("data", (chunk: Buffer) => { + this.buf += chunk.toString(); + let nl: number; + while ((nl = this.buf.indexOf("\n")) !== -1) { + const line = this.buf.slice(0, nl).trim(); + this.buf = this.buf.slice(nl + 1); + if (line) this.handle(line); + } + }); + this.ready = this.init(); + } + + private handle(line: string) { + try { + const msg = JSON.parse(line) as { id?: number; result?: unknown; error?: { message: string } }; + if (msg.id !== undefined) { + const p = this.pending.get(msg.id); + if (p) { + this.pending.delete(msg.id); + msg.error ? p.reject(new Error(msg.error.message)) : p.resolve(msg.result); + } + } + } catch (e) { + console.warn("[reqstool-openspec] Skipping non-JSON line from reqstool mcp:", e instanceof Error ? e.message : e); + } + } + + private send(method: string, params: unknown, expectReply = true): Promise { + if (!expectReply) { + this.proc.stdin!.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n"); + return Promise.resolve(); + } + const id = this.id++; + return new Promise((resolve, reject) => { + this.pending.set(id, { resolve, reject }); + this.proc.stdin!.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); + }); + } + + private async init(): Promise { + await this.send("initialize", { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + clientInfo: { name: "openspecui", version: "1.0" }, + }); + this.send("notifications/initialized", {}, false); + } + + async enrich(content: string, preset: string): Promise { + await this.ready; + const result = (await this.send("tools/call", { + name: "enrich_document", + arguments: { content, preset }, + })) as { content: { text: string }[] }; + return result.content[0].text; + } + + close() { + this.proc.stdin?.end(); + this.proc.kill(); + } +} + +let client: McpStdioClient | null = null; + +export const onReadDocument: OnReadDocumentHookV1 = async (ctx, read) => { + if (!client) { + client = new McpStdioClient(ctx.projectDir); + ctx.lifecycle.onDispose(() => { + client?.close(); + client = null; + }); + } + + const result = await read(); + const preset = `openspec:${ctx.document.kind}`; + + try { + const enriched = await client.enrich(result.markdown, preset); + return { ...result, markdown: enriched, sourceLabel: `reqstool ${preset}` }; + } catch (e) { + return { + ...result, + diagnostics: [{ level: "warning", message: `reqstool enrich failed: ${e}` }], + }; + } +}; diff --git a/openspec/specs/annotation-definitions/spec.md b/openspec/specs/annotation-definitions/spec.md new file mode 100644 index 0000000..064039d --- /dev/null +++ b/openspec/specs/annotation-definitions/spec.md @@ -0,0 +1,15 @@ +# Annotation Definitions Specification + +## Purpose + +Requirement and SVC content is owned by reqstool (single source of truth). This spec references +reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via +`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`. + +## Requirements + +### Requirement: ANNOTATIONS_001 +The system SHALL implement ANNOTATIONS_001. + +#### Scenario: SVC_ANNOTATIONS_001 +The system SHALL pass SVC_ANNOTATIONS_001. diff --git a/openspec/specs/annotation-processing/spec.md b/openspec/specs/annotation-processing/spec.md new file mode 100644 index 0000000..95bc762 --- /dev/null +++ b/openspec/specs/annotation-processing/spec.md @@ -0,0 +1,15 @@ +# Annotation Processing Specification + +## Purpose + +Requirement and SVC content is owned by reqstool (single source of truth). This spec references +reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via +`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`. + +## Requirements + +### Requirement: ANNOTATIONS_002 +The system SHALL implement ANNOTATIONS_002. + +#### Scenario: SVC_ANNOTATIONS_002 +The system SHALL pass SVC_ANNOTATIONS_002. diff --git a/openspec/specs/yaml-export/spec.md b/openspec/specs/yaml-export/spec.md new file mode 100644 index 0000000..13401ea --- /dev/null +++ b/openspec/specs/yaml-export/spec.md @@ -0,0 +1,15 @@ +# YAML Export Specification + +## Purpose + +Requirement and SVC content is owned by reqstool (single source of truth). This spec references +reqstool requirement and SVC IDs only; titles and descriptions are injected at read time via +`reqstool enrich` (or the openspecui hook). See `docs/reqstool/`. + +## Requirements + +### Requirement: ANNOTATIONS_003 +The system SHALL implement ANNOTATIONS_003. + +#### Scenario: SVC_ANNOTATIONS_003 +The system SHALL pass SVC_ANNOTATIONS_003. diff --git a/pom.xml b/pom.xml index 3a77ff4..939b926 100644 --- a/pom.xml +++ b/pom.xml @@ -391,5 +391,37 @@ + + + + self-apply + + + + maven-compiler-plugin + + + + com.google.auto.service + auto-service + 1.1.1 + + + io.github.reqstool + reqstool-java-annotations + ${project.version} + + + + + + + diff --git a/src/main/java/io/github/reqstool/annotations/Requirements.java b/src/main/java/io/github/reqstool/annotations/Requirements.java index 574f265..e122ab1 100644 --- a/src/main/java/io/github/reqstool/annotations/Requirements.java +++ b/src/main/java/io/github/reqstool/annotations/Requirements.java @@ -13,6 +13,7 @@ @Documented @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.SOURCE) +@Requirements({ "ANNOTATIONS_001" }) public @interface Requirements { String[] value() default { diff --git a/src/main/java/io/github/reqstool/annotations/SVCs.java b/src/main/java/io/github/reqstool/annotations/SVCs.java index fd9db6f..2bcc1c5 100644 --- a/src/main/java/io/github/reqstool/annotations/SVCs.java +++ b/src/main/java/io/github/reqstool/annotations/SVCs.java @@ -12,6 +12,7 @@ @Documented @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.SOURCE) +@Requirements({ "ANNOTATIONS_001" }) public @interface SVCs { String[] value() default {}; diff --git a/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java b/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java index fc88561..7c85b3c 100644 --- a/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java +++ b/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import io.github.reqstool.annotations.Requirements; public abstract class AbstractAnnotationsProcessor extends AbstractProcessor { @@ -68,6 +69,7 @@ public SourceVersion getSupportedSourceVersion() { } @Override + @Requirements({ "ANNOTATIONS_002" }) public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { return exportToYAML(); @@ -106,6 +108,7 @@ public boolean process(Set annotations, RoundEnvironment abstract List getAnnotationStrings(Element element); + @Requirements({ "ANNOTATIONS_003" }) private boolean exportToYAML() { Map result = new LinkedHashMap<>(); diff --git a/src/test/java/io/github/reqstool/annotations/RequirementsTests.java b/src/test/java/io/github/reqstool/annotations/RequirementsTests.java index 4b4e983..dabf937 100644 --- a/src/test/java/io/github/reqstool/annotations/RequirementsTests.java +++ b/src/test/java/io/github/reqstool/annotations/RequirementsTests.java @@ -9,6 +9,7 @@ class RequirementsTests { @Test + @SVCs({ "SVC_ANNOTATIONS_001" }) void testOneRequirement() { Requirements requirements = new Requirements() { @@ -28,6 +29,7 @@ public Class annotationType() { } @Test + @SVCs({ "SVC_ANNOTATIONS_001" }) void testMultipleRequirements() { Requirements requirements = new Requirements() { diff --git a/src/test/java/io/github/reqstool/annotations/SVCsTests.java b/src/test/java/io/github/reqstool/annotations/SVCsTests.java index e484729..b0def90 100644 --- a/src/test/java/io/github/reqstool/annotations/SVCsTests.java +++ b/src/test/java/io/github/reqstool/annotations/SVCsTests.java @@ -9,6 +9,7 @@ class SVCsTests { @Test + @SVCs({ "SVC_ANNOTATIONS_001" }) void testOneSVC() { SVCs svcs = new SVCs() { @@ -28,9 +29,10 @@ public Class annotationType() { } @Test + @SVCs({ "SVC_ANNOTATIONS_001" }) void testMultipleSVCs() { - Requirements svcs = new Requirements() { + SVCs svcs = new SVCs() { @Override public String[] value() { @@ -39,7 +41,7 @@ public String[] value() { @Override public Class annotationType() { - return Requirements.class; + return SVCs.class; } }; diff --git a/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java b/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java index 1c8d7a0..e847339 100644 --- a/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java +++ b/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java @@ -16,6 +16,7 @@ import javax.tools.ToolProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import io.github.reqstool.annotations.SVCs; /** * Test custom AnnotationsProcessorTests @@ -31,6 +32,7 @@ class AnnotationsProcessorTests { Path tempDir; @Test + @SVCs({ "SVC_ANNOTATIONS_002", "SVC_ANNOTATIONS_003" }) void testRequirementsAnnotations() throws IOException { String javaFileResourceName = "java/RequirementsExample.java"; @@ -42,6 +44,7 @@ void testRequirementsAnnotations() throws IOException { } @Test + @SVCs({ "SVC_ANNOTATIONS_002", "SVC_ANNOTATIONS_003" }) void testSVCsAnnotations() throws IOException { String javaFileResourceName = "java/SVCsExample.java"; From 51d7b47551555e840328c01982c85dc22150c515 Mon Sep 17 00:00:00 2001 From: Jimisola Laursen Date: Tue, 23 Jun 2026 23:24:08 +0200 Subject: [PATCH 2/2] refactor(reqstool): address full-pr-review findings on #166 - Extract the main/test annotations.yml merge logic from build.yml into scripts/combine-annotations.sh, so it's testable/runnable outside CI rather than only living as inline workflow YAML - Fix import ordering in AbstractAnnotationsProcessor.java and AnnotationsProcessorTests.java (io.github.reqstool.* grouped with other imports, not appended out of order) Other findings from the review were judged out of scope for this PR: the openspecui.hooks.ts MCP client and .claude/settings.json auto-enable pattern are byte-identical to what's already merged across reqstool-client and the other three dogfooded repos this rollout, and the self-apply Maven profile being CI-only (no isolated Invoker IT) matches the Maven/Gradle plugins' existing pattern. Signed-off-by: Jimisola Laursen --- .github/workflows/build.yml | 11 ++++------ scripts/combine-annotations.sh | 20 +++++++++++++++++++ .../AbstractAnnotationsProcessor.java | 2 +- .../processor/AnnotationsProcessorTests.java | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) create mode 100755 scripts/combine-annotations.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5f4140..304b823 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,13 +42,10 @@ jobs: # the Maven/Gradle plugins) -- merge them the same way those plugins' # combineOutput() would, so reqstool status has real, non-fabricated data. run: | - mkdir -p target/reqstool - { - echo "# yaml-language-server: \$schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/annotations.schema.json" - yq eval-all '. as $item ireduce ({}; . * $item )' \ - target/generated-sources/annotations/resources/annotations.yml \ - target/generated-test-sources/test-annotations/resources/annotations.yml | grep -v "^#" - } > target/reqstool/annotations.yml + scripts/combine-annotations.sh \ + target/generated-sources/annotations/resources/annotations.yml \ + target/generated-test-sources/test-annotations/resources/annotations.yml \ + target/reqstool/annotations.yml - name: Install reqstool uses: reqstool/.github/.github/actions/install-reqstool@9c6feaab046f4782f430dd2527fdb82c2a5cd926 # main 2026-06-22 with: diff --git a/scripts/combine-annotations.sh b/scripts/combine-annotations.sh new file mode 100755 index 0000000..204de8d --- /dev/null +++ b/scripts/combine-annotations.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Combines this library's own main/test annotation processor output into a +# single annotations.yml, the way the Maven/Gradle plugins' combineOutput() +# would -- but this library has no shared mojo to do it, since main and test +# annotation processing run in separate javac invocations and write to +# separate generated-sources directories. +# +# Usage: scripts/combine-annotations.sh +set -euo pipefail + +main_file="$1" +test_file="$2" +output_file="$3" + +mkdir -p "$(dirname "$output_file")" + +{ + echo '# yaml-language-server: $schema=https://raw.githubusercontent.com/reqstool/reqstool-client/main/src/reqstool/resources/schemas/v1/annotations.schema.json' + yq eval-all '. as $item ireduce ({}; . * $item )' "$main_file" "$test_file" | grep -v "^#" +} > "$output_file" diff --git a/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java b/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java index 7c85b3c..0a87485 100644 --- a/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java +++ b/src/main/java/io/github/reqstool/processor/AbstractAnnotationsProcessor.java @@ -1,6 +1,7 @@ // Copyright © LFV package io.github.reqstool.processor; +import io.github.reqstool.annotations.Requirements; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -26,7 +27,6 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import io.github.reqstool.annotations.Requirements; public abstract class AbstractAnnotationsProcessor extends AbstractProcessor { diff --git a/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java b/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java index e847339..455800f 100644 --- a/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java +++ b/src/test/java/io/github/reqstool/processor/AnnotationsProcessorTests.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import io.github.reqstool.annotations.SVCs; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -16,7 +17,6 @@ import javax.tools.ToolProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import io.github.reqstool.annotations.SVCs; /** * Test custom AnnotationsProcessorTests