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
27 changes: 23 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ on:
- reopened
- synchronize

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
reqstool-source: [pypi, main]
steps:
- name: Check out source repository
uses: actions/checkout@v7
Expand All @@ -23,7 +30,19 @@ jobs:
distribution: "temurin"
- name: Build project
run: mvn clean verify
- name: Run reqstool status command
run: |
pip install reqstool
reqstool status local -p "$GITHUB_WORKSPACE"/docs/reqstool
- name: Install reqstool
uses: reqstool/.github/.github/actions/install-reqstool@5bdf4e5c4af98274c44c8fbaa5b54d605a6cf38a # 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@5bdf4e5c4af98274c44c8fbaa5b54d605a6cf38a # main 2026-06-22
- name: Run reqstool status
uses: reqstool/.github/.github/actions/reqstool-status@5bdf4e5c4af98274c44c8fbaa5b54d605a6cf38a # main 2026-06-22
with:
# this repo intentionally has incomplete requirements (it showcases every status outcome)
fail-if-incomplete: "false"

validate-openspec:
uses: reqstool/.github/.github/workflows/common-validate-openspec.yml@5bdf4e5c4af98274c44c8fbaa5b54d605a6cf38a # main 2026-06-22
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"reqstool": {
"command": "reqstool",
"args": ["mcp"]
}
}
}
35 changes: 35 additions & 0 deletions .reqstool-ai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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-demo

# 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:
# Domain-specific prefixes: this demo's requirement IDs are managed manually, so
# req_prefix is empty (no auto-prefix for new requirements). SVC IDs already use a
# literal "SVC_" + number convention (SVC_010, SVC_020, ...), so svc_prefix is set.
# (REQ_PASS, REQ_MANUAL_FAIL, REQ_NOT_IMPLEMENTED, ...; SVC_010, SVC_020, ...)
demo:
path: docs/reqstool
req_prefix: ""
svc_prefix: "SVC_"
12 changes: 6 additions & 6 deletions docs/reqstool/requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,39 @@ requirements:
description: The system shall display a personalized greeting message based on a given name parameter.
rationale: Users need to receive a personalized greeting to confirm their identity when interacting with the system.
categories: ["functional-suitability", "maintainability"]
revision: 0.0.1
revision: 0.1.0
- id: REQ_MANUAL_FAIL
title: Calculate item total
significance: shall
description: The system shall calculate the total price for a given quantity of items.
rationale: Accurate price calculation is essential for correct billing and invoicing.
categories: ["functional-suitability", "maintainability"]
revision: 0.0.1
revision: 0.1.0
- id: REQ_NOT_IMPLEMENTED
title: Export report as PDF
significance: may
description: The system should support exporting reports in PDF format.
rationale: PDF export enables users to share and archive reports in a portable format.
categories: ["functional-suitability", "maintainability"]
revision: 0.0.1
revision: 0.1.0
- id: REQ_FAILING_TEST
title: Validate email format
significance: shall
description: The system shall validate that a provided email address conforms to standard email format.
rationale: Ensuring valid email format prevents delivery failures and improves data quality.
categories: ["functional-suitability"]
revision: 0.0.1
revision: 0.1.0
- id: REQ_SKIPPED_TEST
title: Send notification via SMS
significance: may
description: The system should support sending notifications to users via SMS.
rationale: SMS notifications provide an alternative channel for time-sensitive alerts.
categories: ["functional-suitability"]
revision: 0.0.1
revision: 0.1.0
- id: REQ_MISSING_TEST
title: Generate audit log entry
significance: shall
description: The system shall generate an audit log entry for each user action.
rationale: Audit logging is required for compliance and security traceability.
categories: ["functional-suitability"]
revision: 0.0.1
revision: 0.1.0
16 changes: 8 additions & 8 deletions docs/reqstool/software_verification_cases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,46 @@ cases:
requirement_ids: ["REQ_PASS"]
title: "Verify greeting message contains the provided name"
verification: automated-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_020
requirement_ids: ["REQ_MANUAL_FAIL"]
title: "Verify total price calculation for given quantity"
verification: automated-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_021
requirement_ids: ["REQ_PASS"]
title: "Manually verify greeting is displayed correctly in UI"
verification: manual-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_022
requirement_ids: ["REQ_MANUAL_FAIL"]
title: "Manually verify total price is shown on invoice page"
verification: manual-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_030
requirement_ids: ["REQ_NOT_IMPLEMENTED"]
title: "Verify PDF export produces a valid document"
verification: automated-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_040
requirement_ids: ["REQ_FAILING_TEST"]
title: "Verify email validation rejects invalid formats"
verification: automated-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_050
requirement_ids: ["REQ_SKIPPED_TEST"]
title: "Verify SMS notification is sent successfully"
verification: automated-test
revision: "0.0.1"
revision: "0.1.0"

- id: SVC_060
requirement_ids: ["REQ_MISSING_TEST"]
title: "Verify audit log entry is created for user actions"
verification: automated-test
revision: "0.0.1"
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}` }],
};
}
};
17 changes: 17 additions & 0 deletions openspec/specs/audit-logging/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Audit Logging 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/`. This capability demonstrates a
requirement and SVC that exist but have no verifying test at all — the **missing-test** status
case.

## Requirements

### Requirement: REQ_MISSING_TEST
The system SHALL implement REQ_MISSING_TEST.

#### Scenario: SVC_060
The system SHALL pass SVC_060.
20 changes: 20 additions & 0 deletions openspec/specs/billing/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Billing 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/`. This capability demonstrates a
requirement that passes its automated test but fails manual verification — the **manual-fail**
status case.

## Requirements

### Requirement: REQ_MANUAL_FAIL
The system SHALL implement REQ_MANUAL_FAIL.

#### Scenario: SVC_020
The system SHALL pass SVC_020.

#### Scenario: SVC_022
The system SHALL pass SVC_022.
20 changes: 20 additions & 0 deletions openspec/specs/greeting/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Greeting 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/`. This capability demonstrates a
requirement that is fully implemented, automatically tested, and manually verified — the **passing**
status case.

## Requirements

### Requirement: REQ_PASS
The system SHALL implement REQ_PASS.

#### Scenario: SVC_010
The system SHALL pass SVC_010.

#### Scenario: SVC_021
The system SHALL pass SVC_021.
17 changes: 17 additions & 0 deletions openspec/specs/notifications/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Notifications 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/`. This capability demonstrates a
requirement whose implementation is intentionally unfinished, causing its automated test to be
skipped — the **skipped-test** status case.

## Requirements

### Requirement: REQ_SKIPPED_TEST
The system SHALL implement REQ_SKIPPED_TEST.

#### Scenario: SVC_050
The system SHALL pass SVC_050.
16 changes: 16 additions & 0 deletions openspec/specs/reporting/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Reporting 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/`. This capability demonstrates a
requirement with an SVC but no implementation — the **not-implemented** status case.

## Requirements

### Requirement: REQ_NOT_IMPLEMENTED
The system SHALL implement REQ_NOT_IMPLEMENTED.

#### Scenario: SVC_030
The system SHALL pass SVC_030.
Loading