requ-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/conductor.d.ts +46 -0
- package/dist/conductor.js +175 -0
- package/dist/conductor.js.map +1 -0
- package/dist/coverage.d.ts +109 -0
- package/dist/coverage.js +157 -0
- package/dist/coverage.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +684 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +19 -0
- package/dist/ingest.js +47 -0
- package/dist/ingest.js.map +1 -0
- package/dist/schema.d.ts +262 -0
- package/dist/schema.js +134 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage.d.ts +48 -0
- package/dist/storage.js +184 -0
- package/dist/storage.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nourreddine HOUARI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# requ-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/nouhouari/requ-mcp/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
An MCP server that tracks **requirements coverage** for a project, and how it
|
|
6
|
+
**evolves across phases/releases**. It gives AI agents structured tools to
|
|
7
|
+
maintain a living traceability graph:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Requirement → User Story → (descriptive acceptance criteria)
|
|
11
|
+
▲
|
|
12
|
+
│ link = an @US-xxx tag on a cucumber scenario
|
|
13
|
+
│
|
|
14
|
+
Phase (v1.0, v1.1, …) → Execution (a scenario result for a run)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
At the end of a working session — or for any release — you ask the server one
|
|
18
|
+
question — *"what's our coverage?"* — and get a precise, always-current answer
|
|
19
|
+
instead of a stale spreadsheet, plus the **trend** of how coverage changed
|
|
20
|
+
release over release.
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/nouhouari/requ-mcp.git
|
|
26
|
+
cd requ-mcp
|
|
27
|
+
npm install
|
|
28
|
+
npm run build # compiles to dist/
|
|
29
|
+
npm run smoke # optional: end-to-end self-test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Register it with your MCP client (e.g. Claude Code) — once, globally:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"requ": {
|
|
38
|
+
"command": "node",
|
|
39
|
+
"args": ["/absolute/path/to/requ-mcp/dist/index.js"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then a typical flow, all driven through the agent:
|
|
46
|
+
|
|
47
|
+
1. `init_project` — create `.requ/` and point at your Conductor project (`conductorPath`), optionally create a first phase.
|
|
48
|
+
2. `create_requirement` — import the requirements (with `components`).
|
|
49
|
+
3. `create_user_story` — PO authors stories, each tracing to ≥1 requirement.
|
|
50
|
+
4. **Tag scenarios** `@US-007` in your feature files — that *is* the test link.
|
|
51
|
+
5. `import_execution_report` — ingest a Conductor cucumber-json run into the active phase.
|
|
52
|
+
6. `coverage_report` / `find_gaps` / `coverage_trend` — see coverage now and how it evolves.
|
|
53
|
+
|
|
54
|
+
## Why
|
|
55
|
+
|
|
56
|
+
- **The server** serves the imported **requirements** (the upstream "what must be built").
|
|
57
|
+
- A **PO agent** reads them and authors **user stories**, each tracing to **≥1 requirement** (enforced) with **acceptance criteria**.
|
|
58
|
+
- A **tester agent** links **Conductor tests** (cucumber scenarios) to each criterion and records results per phase.
|
|
59
|
+
|
|
60
|
+
A requirement is only **verified** when it has a story *and* every acceptance
|
|
61
|
+
criterion of every linked story has a passing test **in that phase**. That
|
|
62
|
+
distinction — "has tests" vs. real **coverage** — is the whole point.
|
|
63
|
+
|
|
64
|
+
### Phases & executions
|
|
65
|
+
|
|
66
|
+
A **TestLink** is pure intent ("this scenario verifies this criterion").
|
|
67
|
+
Results are **Executions** owned by a **Phase**, so the same test can pass in
|
|
68
|
+
v1.0 and fail in v1.1 — and coverage reflects it. Coverage is computed in one of
|
|
69
|
+
two modes:
|
|
70
|
+
|
|
71
|
+
- **cumulative** (default) — the latest known result for each test *as of* the
|
|
72
|
+
phase, carried forward from earlier phases. The smooth evolution curve.
|
|
73
|
+
- **strict** — only runs recorded *in* that phase count. Honest release sign-off:
|
|
74
|
+
anything not re-run this phase is uncovered.
|
|
75
|
+
|
|
76
|
+
`coverage_trend` returns the summary at each phase in order — the evolution view.
|
|
77
|
+
|
|
78
|
+
### Components
|
|
79
|
+
|
|
80
|
+
Requirements carry a `components` array, so coverage can be sliced per
|
|
81
|
+
sub-system (the `byComponent` rollup in every report).
|
|
82
|
+
|
|
83
|
+
### Ingesting Conductor results
|
|
84
|
+
|
|
85
|
+
Conductor runs `cucumber-js --format json`. Point `import_execution_report` at
|
|
86
|
+
that file and it records one execution per scenario into a phase, then reports
|
|
87
|
+
how many results mapped onto a linked test.
|
|
88
|
+
|
|
89
|
+
It pairs with [Conductor](https://github.com/nouhouari/conductor): Conductor
|
|
90
|
+
owns the e2e *test definitions*; requ-mcp owns the *requirements and their
|
|
91
|
+
coverage*. A test reference is a cucumber scenario (feature + scenario name);
|
|
92
|
+
maestro-driven mobile tests run through cucumber step definitions and appear in
|
|
93
|
+
the report as scenarios too, so scenarios are the single unit of linkage.
|
|
94
|
+
References are validated by reading the Conductor project's
|
|
95
|
+
`features/**/*.feature` directly off disk — no runtime coupling between the two
|
|
96
|
+
servers.
|
|
97
|
+
|
|
98
|
+
## Storage
|
|
99
|
+
|
|
100
|
+
Everything is flat YAML under `.requ/`, so coverage is version-controlled and
|
|
101
|
+
reviewable in PRs:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
.requ/
|
|
105
|
+
config.yaml # project name, Conductor path, active phase
|
|
106
|
+
requirements/REQ-001.yaml
|
|
107
|
+
stories/US-001.yaml # story → criteria → linked tests
|
|
108
|
+
phases/PHASE-001.yaml # a phase / release
|
|
109
|
+
executions/PHASE-001.yaml # test results recorded against that phase
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Tools
|
|
113
|
+
|
|
114
|
+
| Tool | Actor | Purpose |
|
|
115
|
+
|------|-------|---------|
|
|
116
|
+
| `init_project` | setup | Create `.requ/`, record Conductor + report path, optional first phase |
|
|
117
|
+
| `create_requirement` / `list_requirements` / `get_requirement` / `update_requirement` | server | Manage imported requirements (with `components`) |
|
|
118
|
+
| `create_user_story` | PO | Author a story (rejects unless it links ≥1 existing requirement) |
|
|
119
|
+
| `update_user_story` / `add_acceptance_criterion` / `list_user_stories` / `get_user_story` | PO | Edit stories & criteria |
|
|
120
|
+
| `create_phase` / `list_phases` / `update_phase` / `set_active_phase` | release | Manage phases/releases |
|
|
121
|
+
| `list_links` | tester | Show which scenarios are tagged to which story; flag dangling `@US-xxx` tags and stories with no scenario |
|
|
122
|
+
| `record_execution` | tester | Record one scenario result against a phase |
|
|
123
|
+
| `import_execution_report` | tester | Ingest a Conductor cucumber-json file into a phase |
|
|
124
|
+
| `coverage_report` | reporting | Phase/mode rollup + per-component + summary % (json or markdown) |
|
|
125
|
+
| `coverage_trend` | reporting | Coverage summary at each phase — the evolution view |
|
|
126
|
+
| `find_gaps` | reporting | Requirements without stories, stories without scenarios, stories not covered (per phase) |
|
|
127
|
+
|
|
128
|
+
Every tool also accepts an optional `projectPath` (see below).
|
|
129
|
+
|
|
130
|
+
## Linking tests — `@US-xxx` tags
|
|
131
|
+
|
|
132
|
+
There is no manual link step. A scenario is linked to a story by tagging it in
|
|
133
|
+
the feature file:
|
|
134
|
+
|
|
135
|
+
```gherkin
|
|
136
|
+
@US-007
|
|
137
|
+
Scenario: Reset email is sent for a registered address
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
One feature file can hold scenarios for many stories. The server derives the
|
|
142
|
+
links by scanning `features/**/*.feature` (the same tags ride along in the
|
|
143
|
+
cucumber-JSON, so imported results map straight onto stories). `list_links`
|
|
144
|
+
shows the derived graph and flags `@US-xxx` tags that point at a story that
|
|
145
|
+
doesn't exist.
|
|
146
|
+
|
|
147
|
+
## Develop
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install
|
|
151
|
+
npm run build # tsc -> dist/
|
|
152
|
+
npm run smoke # end-to-end test against the built server over stdio
|
|
153
|
+
npm run dev # run from source with tsx
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Register with an MCP client
|
|
157
|
+
|
|
158
|
+
The server talks stdio. It can be installed **once at the user level** and serve
|
|
159
|
+
any project — it resolves the target project per call:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"mcpServers": {
|
|
164
|
+
"requ": {
|
|
165
|
+
"command": "node",
|
|
166
|
+
"args": ["/absolute/path/to/requ-mcp/dist/index.js"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### How it finds the project
|
|
173
|
+
|
|
174
|
+
For each tool call, the project root (the directory containing `.requ/`) is
|
|
175
|
+
resolved in this order:
|
|
176
|
+
|
|
177
|
+
1. The tool's explicit **`projectPath`** argument, if given (use this in monorepos).
|
|
178
|
+
2. The **`REQU_ROOT`** env var, if set at launch (a hard pin).
|
|
179
|
+
3. A **workspace root** advertised by the client (MCP `roots`) that contains `.requ/`.
|
|
180
|
+
4. The nearest **ancestor of the cwd** that contains `.requ/`.
|
|
181
|
+
5. Otherwise the first workspace root, else the cwd (used by `init_project`).
|
|
182
|
+
|
|
183
|
+
So a single global server works across projects: most clients (Claude Code, IDEs)
|
|
184
|
+
advertise the open workspace as a root, and you can always pass `projectPath`
|
|
185
|
+
explicitly. The Conductor `features/` location is read from `.requ/config.yaml`
|
|
186
|
+
relative to that resolved root.
|
|
187
|
+
|
|
188
|
+
## Releasing (npm)
|
|
189
|
+
|
|
190
|
+
Publishing is automated by `.github/workflows/publish.yml`, which runs on a
|
|
191
|
+
GitHub Release and ships to npm with provenance.
|
|
192
|
+
|
|
193
|
+
One-time setup:
|
|
194
|
+
|
|
195
|
+
1. Create an npm **Automation** access token at npmjs.com → Access Tokens.
|
|
196
|
+
2. Add it as a repo secret named `NPM_TOKEN`
|
|
197
|
+
(`gh secret set NPM_TOKEN`, or repo → Settings → Secrets → Actions).
|
|
198
|
+
|
|
199
|
+
To cut a release:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# bump the version in package.json (e.g. 0.1.0), commit, then:
|
|
203
|
+
git tag v0.1.0
|
|
204
|
+
git push origin v0.1.0
|
|
205
|
+
gh release create v0.1.0 --generate-notes
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The workflow verifies the tag matches `package.json`, builds, runs the smoke
|
|
209
|
+
test, and publishes. The release tag (`vX.Y.Z`) must match the `package.json`
|
|
210
|
+
version.
|
|
211
|
+
|
|
212
|
+
## Coverage metrics (story-level)
|
|
213
|
+
|
|
214
|
+
- **Story coverage** — % of active requirements that trace to ≥1 story.
|
|
215
|
+
- **Story tested** — the story has ≥1 scenario tagged `@US-xxx`.
|
|
216
|
+
- **Story covered** — every tagged scenario passes in the phase.
|
|
217
|
+
- **Verified** — % of active requirements where every linked story is covered.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { testKey } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads the Conductor project's feature files directly off disk — no runtime
|
|
4
|
+
* coupling to the Conductor MCP server.
|
|
5
|
+
*
|
|
6
|
+
* A test is a cucumber scenario (feature name + scenario name) in
|
|
7
|
+
* features/**\/*.feature. Scenario → user-story links are declared as `@US-xxx`
|
|
8
|
+
* tags on the scenario and derived here; the feature files are the single
|
|
9
|
+
* source of truth for linkage.
|
|
10
|
+
*/
|
|
11
|
+
export interface ConductorScenario {
|
|
12
|
+
feature: string;
|
|
13
|
+
name: string;
|
|
14
|
+
file: string;
|
|
15
|
+
/** All tags on the scenario (incl. inherited feature-level tags), e.g. "@US-007". */
|
|
16
|
+
tags: string[];
|
|
17
|
+
/** Story ids parsed from tags, e.g. ["US-007"]. */
|
|
18
|
+
stories: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface ConductorIndex {
|
|
21
|
+
scenarios: ConductorScenario[];
|
|
22
|
+
}
|
|
23
|
+
/** Build an index of all scenarios in a Conductor project. */
|
|
24
|
+
export declare function indexConductor(conductorRoot: string): Promise<ConductorIndex>;
|
|
25
|
+
export interface ValidationResult {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
suggestions?: string[];
|
|
29
|
+
}
|
|
30
|
+
/** Check that a test ref resolves to a real Conductor scenario. */
|
|
31
|
+
export declare function validateTestRef(ref: {
|
|
32
|
+
feature: string;
|
|
33
|
+
name: string;
|
|
34
|
+
}, index: ConductorIndex): ValidationResult;
|
|
35
|
+
/** Map of story id → the scenarios tagged with it. */
|
|
36
|
+
export declare function scenariosByStory(index: ConductorIndex): Map<string, ConductorScenario[]>;
|
|
37
|
+
/** testKeys of every scenario that carries at least one @US-xxx tag. */
|
|
38
|
+
export declare function linkedScenarioKeys(index: ConductorIndex): Set<string>;
|
|
39
|
+
/** Scenario tags that reference a story id not present in `knownStoryIds`. */
|
|
40
|
+
export declare function danglingStoryTags(index: ConductorIndex, knownStoryIds: Set<string>): {
|
|
41
|
+
feature: string;
|
|
42
|
+
name: string;
|
|
43
|
+
file: string;
|
|
44
|
+
story: string;
|
|
45
|
+
}[];
|
|
46
|
+
export { testKey };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { STORY_TAG_RE, testKey } from "./schema.js";
|
|
4
|
+
async function walk(dir, match) {
|
|
5
|
+
let entries;
|
|
6
|
+
try {
|
|
7
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (err?.code === "ENOENT")
|
|
11
|
+
return [];
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const e of entries) {
|
|
16
|
+
const full = path.join(dir, e.name);
|
|
17
|
+
if (e.isDirectory()) {
|
|
18
|
+
if (e.name === "node_modules" || e.name.startsWith("."))
|
|
19
|
+
continue;
|
|
20
|
+
out.push(...(await walk(full, match)));
|
|
21
|
+
}
|
|
22
|
+
else if (match(e.name)) {
|
|
23
|
+
out.push(full);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function parseTags(line) {
|
|
29
|
+
return line.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
30
|
+
}
|
|
31
|
+
function storiesFromTags(tags) {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const t of tags) {
|
|
34
|
+
const m = t.match(STORY_TAG_RE);
|
|
35
|
+
if (m && !out.includes(m[1]))
|
|
36
|
+
out.push(m[1]);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Lightweight gherkin scan: pull the Feature name, each Scenario name, and the
|
|
42
|
+
* tags attached to each (tag lines immediately above the Scenario, plus
|
|
43
|
+
* feature-level tags above the `Feature:` line, which apply to all scenarios).
|
|
44
|
+
*/
|
|
45
|
+
function parseFeatureFile(content, file) {
|
|
46
|
+
let feature = path.basename(file, ".feature");
|
|
47
|
+
let featureTags = [];
|
|
48
|
+
let pendingTags = [];
|
|
49
|
+
const scenarios = [];
|
|
50
|
+
for (const lineRaw of content.split(/\r?\n/)) {
|
|
51
|
+
const line = lineRaw.trim();
|
|
52
|
+
if (line === "" || line.startsWith("#"))
|
|
53
|
+
continue;
|
|
54
|
+
if (line.startsWith("@")) {
|
|
55
|
+
pendingTags.push(...parseTags(line));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const fm = line.match(/^Feature:\s*(.+)$/);
|
|
59
|
+
if (fm) {
|
|
60
|
+
feature = fm[1].trim();
|
|
61
|
+
featureTags = pendingTags;
|
|
62
|
+
pendingTags = [];
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const sm = line.match(/^Scenario(?:\s+Outline)?:\s*(.+)$/);
|
|
66
|
+
if (sm) {
|
|
67
|
+
const tags = [...featureTags, ...pendingTags];
|
|
68
|
+
scenarios.push({
|
|
69
|
+
feature,
|
|
70
|
+
name: sm[1].trim(),
|
|
71
|
+
file,
|
|
72
|
+
tags,
|
|
73
|
+
stories: storiesFromTags(tags),
|
|
74
|
+
});
|
|
75
|
+
pendingTags = [];
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Any other content line (steps, Background, Examples) ends a tag block.
|
|
79
|
+
pendingTags = [];
|
|
80
|
+
}
|
|
81
|
+
return scenarios;
|
|
82
|
+
}
|
|
83
|
+
/** Build an index of all scenarios in a Conductor project. */
|
|
84
|
+
export async function indexConductor(conductorRoot) {
|
|
85
|
+
const featureFiles = await walk(path.join(conductorRoot, "features"), (f) => f.endsWith(".feature"));
|
|
86
|
+
const scenarios = [];
|
|
87
|
+
for (const file of featureFiles) {
|
|
88
|
+
scenarios.push(...parseFeatureFile(await fs.readFile(file, "utf8"), file));
|
|
89
|
+
}
|
|
90
|
+
return { scenarios };
|
|
91
|
+
}
|
|
92
|
+
/** Check that a test ref resolves to a real Conductor scenario. */
|
|
93
|
+
export function validateTestRef(ref, index) {
|
|
94
|
+
const matches = index.scenarios.filter((s) => s.name === ref.name && s.feature === ref.feature);
|
|
95
|
+
if (matches.length > 0)
|
|
96
|
+
return { ok: true };
|
|
97
|
+
// Fall back to a name-only match to produce a precise hint.
|
|
98
|
+
const byName = index.scenarios.filter((s) => s.name === ref.name);
|
|
99
|
+
if (byName.length > 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: `Scenario "${ref.name}" exists, but not in feature "${ref.feature}".`,
|
|
103
|
+
suggestions: byName.map((s) => `${s.feature} :: ${s.name}`),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
reason: `No scenario "${ref.name}" found in feature "${ref.feature}".`,
|
|
109
|
+
suggestions: closest(ref.name, index.scenarios.map((s) => `${s.feature} :: ${s.name}`)),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Cheap fuzzy match for helpful "did you mean" suggestions. */
|
|
113
|
+
function closest(target, pool, limit = 5) {
|
|
114
|
+
const t = target.toLowerCase();
|
|
115
|
+
return pool
|
|
116
|
+
.map((c) => ({ c, d: levenshtein(t, c.toLowerCase()) }))
|
|
117
|
+
.sort((a, b) => a.d - b.d)
|
|
118
|
+
.slice(0, limit)
|
|
119
|
+
.map((x) => x.c);
|
|
120
|
+
}
|
|
121
|
+
function levenshtein(a, b) {
|
|
122
|
+
const m = a.length;
|
|
123
|
+
const n = b.length;
|
|
124
|
+
if (m === 0)
|
|
125
|
+
return n;
|
|
126
|
+
if (n === 0)
|
|
127
|
+
return m;
|
|
128
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
129
|
+
let curr = new Array(n + 1);
|
|
130
|
+
for (let i = 1; i <= m; i++) {
|
|
131
|
+
curr[0] = i;
|
|
132
|
+
for (let j = 1; j <= n; j++) {
|
|
133
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
134
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
135
|
+
}
|
|
136
|
+
[prev, curr] = [curr, prev];
|
|
137
|
+
}
|
|
138
|
+
return prev[n];
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Tag-derived linkage
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
/** Map of story id → the scenarios tagged with it. */
|
|
144
|
+
export function scenariosByStory(index) {
|
|
145
|
+
const map = new Map();
|
|
146
|
+
for (const sc of index.scenarios) {
|
|
147
|
+
for (const story of sc.stories) {
|
|
148
|
+
const arr = map.get(story) ?? [];
|
|
149
|
+
arr.push(sc);
|
|
150
|
+
map.set(story, arr);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return map;
|
|
154
|
+
}
|
|
155
|
+
/** testKeys of every scenario that carries at least one @US-xxx tag. */
|
|
156
|
+
export function linkedScenarioKeys(index) {
|
|
157
|
+
const set = new Set();
|
|
158
|
+
for (const sc of index.scenarios)
|
|
159
|
+
if (sc.stories.length)
|
|
160
|
+
set.add(testKey(sc));
|
|
161
|
+
return set;
|
|
162
|
+
}
|
|
163
|
+
/** Scenario tags that reference a story id not present in `knownStoryIds`. */
|
|
164
|
+
export function danglingStoryTags(index, knownStoryIds) {
|
|
165
|
+
const out = [];
|
|
166
|
+
for (const sc of index.scenarios) {
|
|
167
|
+
for (const story of sc.stories) {
|
|
168
|
+
if (!knownStoryIds.has(story))
|
|
169
|
+
out.push({ feature: sc.feature, name: sc.name, file: sc.file, story });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
export { testKey };
|
|
175
|
+
//# sourceMappingURL=conductor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conductor.js","sourceRoot":"","sources":["../src/conductor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type CoverageMode, type Execution, type Phase, type Requirement, type TestStatus, type UserStory } from "./schema.js";
|
|
2
|
+
import type { ConductorScenario } from "./conductor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Phase-aware, story-level coverage rollup.
|
|
5
|
+
*
|
|
6
|
+
* Links are derived from `@US-xxx` scenario tags (see conductor.ts), so a story
|
|
7
|
+
* owns the scenarios tagged with its id. A test's result is resolved from the
|
|
8
|
+
* Executions recorded against a phase:
|
|
9
|
+
*
|
|
10
|
+
* strict — only executions recorded *in* the target phase count.
|
|
11
|
+
* cumulative — the latest known result as of the target phase (carried
|
|
12
|
+
* forward from earlier phases, by phase order then ranAt).
|
|
13
|
+
*
|
|
14
|
+
* Coverage rules (story-level):
|
|
15
|
+
* - A story is COVERED when it has ≥1 tagged scenario and all of them pass.
|
|
16
|
+
* - A requirement is VERIFIED when it has ≥1 story and all its stories are
|
|
17
|
+
* covered.
|
|
18
|
+
* Acceptance criteria are descriptive and do not affect coverage.
|
|
19
|
+
*/
|
|
20
|
+
export type StatusMap = Map<string, TestStatus>;
|
|
21
|
+
export type ScenariosByStory = Map<string, ConductorScenario[]>;
|
|
22
|
+
export declare function resolveStatuses(executionsByPhase: Map<string, Execution[]>, phases: Phase[], targetPhaseId: string | null, mode: CoverageMode): StatusMap;
|
|
23
|
+
export interface ScenarioCoverage {
|
|
24
|
+
feature: string;
|
|
25
|
+
name: string;
|
|
26
|
+
status: TestStatus;
|
|
27
|
+
}
|
|
28
|
+
export interface StoryCoverage {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
status: string;
|
|
32
|
+
requirements: string[];
|
|
33
|
+
scenarios: ScenarioCoverage[];
|
|
34
|
+
passing: number;
|
|
35
|
+
failing: number;
|
|
36
|
+
pending: number;
|
|
37
|
+
/** Has ≥1 tagged scenario. */
|
|
38
|
+
tested: boolean;
|
|
39
|
+
/** Covered = tested AND every tagged scenario passes. */
|
|
40
|
+
covered: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface RequirementCoverage {
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
priority: string;
|
|
46
|
+
status: string;
|
|
47
|
+
components: string[];
|
|
48
|
+
storyIds: string[];
|
|
49
|
+
hasStory: boolean;
|
|
50
|
+
verified: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface ComponentCoverage {
|
|
53
|
+
component: string;
|
|
54
|
+
requirements: number;
|
|
55
|
+
withStory: number;
|
|
56
|
+
verified: number;
|
|
57
|
+
verifiedPct: number;
|
|
58
|
+
}
|
|
59
|
+
export interface CoverageSummary {
|
|
60
|
+
requirementsTotal: number;
|
|
61
|
+
requirementsWithStory: number;
|
|
62
|
+
requirementsVerified: number;
|
|
63
|
+
storiesTotal: number;
|
|
64
|
+
storiesTested: number;
|
|
65
|
+
storiesCovered: number;
|
|
66
|
+
scenariosLinked: number;
|
|
67
|
+
scenariosPassing: number;
|
|
68
|
+
storyCoveragePct: number;
|
|
69
|
+
verifiedPct: number;
|
|
70
|
+
testedStoryCoveragePct: number;
|
|
71
|
+
}
|
|
72
|
+
export interface CoverageReport {
|
|
73
|
+
phase: string | null;
|
|
74
|
+
mode: CoverageMode;
|
|
75
|
+
requirements: RequirementCoverage[];
|
|
76
|
+
stories: StoryCoverage[];
|
|
77
|
+
byComponent: ComponentCoverage[];
|
|
78
|
+
summary: CoverageSummary;
|
|
79
|
+
}
|
|
80
|
+
export declare function computeStoryCoverage(story: UserStory, scenariosByStory: ScenariosByStory, status: StatusMap): StoryCoverage;
|
|
81
|
+
export declare function buildReport(requirements: Requirement[], stories: UserStory[], scenariosByStory: ScenariosByStory, status: StatusMap, phaseId: string | null, mode: CoverageMode): CoverageReport;
|
|
82
|
+
export interface TrendPoint {
|
|
83
|
+
phase: string;
|
|
84
|
+
phaseName: string;
|
|
85
|
+
order: number;
|
|
86
|
+
summary: CoverageSummary;
|
|
87
|
+
}
|
|
88
|
+
export declare function buildTrend(requirements: Requirement[], stories: UserStory[], scenariosByStory: ScenariosByStory, executionsByPhase: Map<string, Execution[]>, phases: Phase[], mode: CoverageMode): TrendPoint[];
|
|
89
|
+
export interface Gaps {
|
|
90
|
+
phase: string | null;
|
|
91
|
+
mode: CoverageMode;
|
|
92
|
+
requirementsWithoutStory: {
|
|
93
|
+
id: string;
|
|
94
|
+
title: string;
|
|
95
|
+
priority: string;
|
|
96
|
+
components: string[];
|
|
97
|
+
}[];
|
|
98
|
+
storiesWithoutScenario: {
|
|
99
|
+
id: string;
|
|
100
|
+
title: string;
|
|
101
|
+
}[];
|
|
102
|
+
storiesNotCovered: {
|
|
103
|
+
id: string;
|
|
104
|
+
title: string;
|
|
105
|
+
failing: string[];
|
|
106
|
+
pending: string[];
|
|
107
|
+
}[];
|
|
108
|
+
}
|
|
109
|
+
export declare function findGaps(requirements: Requirement[], stories: UserStory[], scenariosByStory: ScenariosByStory, status: StatusMap, phaseId: string | null, mode: CoverageMode): Gaps;
|