Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -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
}
}
41 changes: 39 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,54 @@ 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: |
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:
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ buildNumber.properties
### VisualStudioCode ###
.vscode/*

.claude/
.claude/*
!.claude/settings.json
32 changes: 32 additions & 0 deletions .reqstool-ai.yaml
Original file line number Diff line number Diff line change
@@ -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_"
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions docs/reqstool/reqstool_config.yml
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions docs/reqstool/requirements.yml
Original file line number Diff line number Diff line change
@@ -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"
20 changes: 20 additions & 0 deletions docs/reqstool/software_verification_cases.yml
Original file line number Diff line number Diff line change
@@ -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"
108 changes: 108 additions & 0 deletions openspec/openspecui.hooks.ts
Original file line number Diff line number Diff line change
@@ -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<void>;

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<unknown> {
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<void> {
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<string> {
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}` }],
};
}
};
15 changes: 15 additions & 0 deletions openspec/specs/annotation-definitions/spec.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions openspec/specs/annotation-processing/spec.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions openspec/specs/yaml-export/spec.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading