eslint-plugin-traceability 1.25.0 → 1.27.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/CHANGELOG.md +3 -2
- package/README.md +53 -24
- package/lib/maintenance/batch.d.ts +6 -2
- package/lib/maintenance/batch.js +7 -3
- package/lib/maintenance/commands.js +44 -21
- package/lib/maintenance/update.d.ts +9 -5
- package/lib/maintenance/update.js +62 -42
- package/lib/rules/helpers/valid-annotation-options.d.ts +13 -0
- package/lib/rules/helpers/valid-annotation-options.js +56 -13
- package/package.json +3 -2
- package/user-docs/api-reference.md +24 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.27.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.26.0...v1.27.0) (2026-01-12)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Features
|
|
5
5
|
|
|
6
|
-
* **
|
|
6
|
+
* **rules:** add storyDirectories support to valid-annotation-format ([50ac131](https://github.com/voder-ai/eslint-plugin-traceability/commit/50ac131f24accf3009899939ababc91c0f1c83ad)), closes [#now-1](https://github.com/voder-ai/eslint-plugin-traceability/issues/now-1)
|
|
7
|
+
* **rules:** complete storyDirectories example derivation ([ac0ed9c](https://github.com/voder-ai/eslint-plugin-traceability/commit/ac0ed9c48c5312c502ae07d18b1ab0ca36e6e2c0))
|
|
7
8
|
|
|
8
9
|
# Changelog
|
|
9
10
|
|
package/README.md
CHANGED
|
@@ -292,41 +292,40 @@ Practical usage examples and sample configurations are available in the [Example
|
|
|
292
292
|
|
|
293
293
|
## Maintenance CLI
|
|
294
294
|
|
|
295
|
-
The `traceability-maint` CLI
|
|
295
|
+
The `traceability-maint` CLI provides a batch update tool for maintaining `@story` and `@supports` annotation references when you reorganize story files.
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
**Note**: Detection and verification of stale references are already handled by ESLint rules (`valid-story-reference`, `valid-req-reference`) during normal linting. The maintenance CLI's primary value is the **update** command, which can batch-update references across your codebase when story files are moved or renamed - something ESLint's auto-fix cannot do.
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
- `verify` – Verify that no stale `@story` annotations exist under the workspace root.
|
|
301
|
-
- `report` – Generate a human-readable or JSON report of stale story references and circular dependencies.
|
|
302
|
-
- `update` – Apply safe, scripted updates to `@story` annotations (e.g., when a story file is renamed).
|
|
299
|
+
### Primary Command
|
|
303
300
|
|
|
304
|
-
|
|
301
|
+
- `update` – Batch update `@story` and `@supports` annotations when a story file is renamed or moved (the key feature ESLint cannot provide)
|
|
305
302
|
|
|
306
|
-
|
|
303
|
+
### Supporting Commands
|
|
307
304
|
|
|
308
|
-
|
|
309
|
-
# Show help and all options
|
|
310
|
-
npx traceability-maint --help
|
|
305
|
+
The CLI also includes `detect`, `verify`, and `report` commands for historical compatibility, but these largely duplicate what ESLint already provides during normal linting:
|
|
311
306
|
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
- `detect` – Scan for `@story` annotations referencing missing files (ESLint rules already do this)
|
|
308
|
+
- `verify` – Check for stale annotations (ESLint rules already do this)
|
|
309
|
+
- `report` – Generate reports of stale references (ESLint output already provides this)
|
|
314
310
|
|
|
315
|
-
|
|
316
|
-
npx traceability-maint detect --root . --ignore-pattern node_modules --ignore-pattern dist
|
|
317
|
-
|
|
318
|
-
# Verify that annotations are valid
|
|
319
|
-
npx traceability-maint verify --root .
|
|
311
|
+
### Usage
|
|
320
312
|
|
|
321
|
-
|
|
322
|
-
npx traceability-maint report --root . --format json
|
|
313
|
+
**Primary use case - batch update references:**
|
|
323
314
|
|
|
315
|
+
```bash
|
|
324
316
|
# Update references when a story file is renamed
|
|
325
317
|
npx traceability-maint update \
|
|
326
318
|
--root . \
|
|
327
319
|
--from "stories/feature-authentication.story.md" \
|
|
328
320
|
--to "stories/feature-auth-v2.story.md"
|
|
329
321
|
|
|
322
|
+
# Preview changes first with --dry-run
|
|
323
|
+
npx traceability-maint update \
|
|
324
|
+
--root . \
|
|
325
|
+
--from "stories/old.story.md" \
|
|
326
|
+
--to "stories/new.story.md" \
|
|
327
|
+
--dry-run
|
|
328
|
+
|
|
330
329
|
# Update with ignore patterns to skip generated code
|
|
331
330
|
npx traceability-maint update \
|
|
332
331
|
--root . \
|
|
@@ -335,6 +334,25 @@ npx traceability-maint update \
|
|
|
335
334
|
--ignore-pattern dist
|
|
336
335
|
```
|
|
337
336
|
|
|
337
|
+
**Supporting commands** (largely redundant with ESLint, but available):
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Show help and all options
|
|
341
|
+
npx traceability-maint --help
|
|
342
|
+
|
|
343
|
+
# Detect stale story references (ESLint already does this during linting)
|
|
344
|
+
npx traceability-maint detect --root .
|
|
345
|
+
|
|
346
|
+
# Detect with ESLint-style ignore patterns
|
|
347
|
+
npx traceability-maint detect --root . --ignore-pattern node_modules --ignore-pattern dist
|
|
348
|
+
|
|
349
|
+
# Verify that annotations are valid (ESLint already does this during linting)
|
|
350
|
+
npx traceability-maint verify --root .
|
|
351
|
+
|
|
352
|
+
# Generate a report (ESLint output already provides this information)
|
|
353
|
+
npx traceability-maint report --root . --format json
|
|
354
|
+
```
|
|
355
|
+
|
|
338
356
|
### Options
|
|
339
357
|
|
|
340
358
|
- `--root <path>` – Workspace root directory (defaults to current directory)
|
|
@@ -347,7 +365,7 @@ npx traceability-maint update \
|
|
|
347
365
|
|
|
348
366
|
### ESLint Configuration Integration
|
|
349
367
|
|
|
350
|
-
The maintenance tools support
|
|
368
|
+
The maintenance tools support `--ignore-pattern` flags to skip directories when performing batch updates:
|
|
351
369
|
|
|
352
370
|
- Skip generated code directories (e.g., `dist`, `build`)
|
|
353
371
|
- Ignore dependency folders (e.g., `node_modules`)
|
|
@@ -356,15 +374,26 @@ The maintenance tools support integration with ESLint configuration through `--i
|
|
|
356
374
|
Multiple patterns can be specified:
|
|
357
375
|
|
|
358
376
|
```bash
|
|
359
|
-
npx traceability-maint
|
|
377
|
+
npx traceability-maint update \
|
|
378
|
+
--from old.story.md \
|
|
379
|
+
--to new.story.md \
|
|
360
380
|
--ignore-pattern node_modules \
|
|
361
381
|
--ignore-pattern dist \
|
|
362
382
|
--ignore-pattern coverage
|
|
363
383
|
```
|
|
364
384
|
|
|
365
|
-
###
|
|
385
|
+
### When to Use the Maintenance CLI vs ESLint
|
|
386
|
+
|
|
387
|
+
**Use the maintenance CLI** when:
|
|
388
|
+
- You've renamed or moved story files and need to update all code references
|
|
389
|
+
- You're reorganizing your story file structure
|
|
390
|
+
|
|
391
|
+
**Use ESLint** for:
|
|
392
|
+
- Detecting stale or invalid references during development
|
|
393
|
+
- Validating annotation format
|
|
394
|
+
- Ongoing verification of traceability compliance
|
|
366
395
|
|
|
367
|
-
The
|
|
396
|
+
The maintenance CLI is a manual refactoring aid, not an automated validation tool. ESLint handles validation automatically during your normal development workflow.
|
|
368
397
|
|
|
369
398
|
For a full description of options and JSON payloads, see the [Maintenance API and CLI](user-docs/api-reference.md#maintenance-api-and-cli) section in the API Reference.
|
|
370
399
|
|
|
@@ -5,15 +5,19 @@ import { GetAllFilesOptions } from "./utils";
|
|
|
5
5
|
* @req REQ-MAINT-BATCH - Perform batch updates
|
|
6
6
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
7
7
|
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
8
|
+
* @req REQ-MAINT-UPDATE#1 - Update @supports references alongside @story
|
|
8
9
|
* @param codebasePath Absolute path to the workspace root where annotations will be updated.
|
|
9
10
|
* @param mappings Array of mapping objects describing path changes, each containing an oldPath and newPath.
|
|
10
11
|
* @param options Optional configuration including ESLint ignore patterns
|
|
11
|
-
* @returns
|
|
12
|
+
* @returns Object with total count of updated annotations and array of malformed annotation warnings
|
|
12
13
|
*/
|
|
13
14
|
export declare function batchUpdateAnnotations(codebasePath: string, mappings: {
|
|
14
15
|
oldPath: string;
|
|
15
16
|
newPath: string;
|
|
16
|
-
}[], options?: GetAllFilesOptions):
|
|
17
|
+
}[], options?: GetAllFilesOptions): {
|
|
18
|
+
count: number;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
};
|
|
17
21
|
/**
|
|
18
22
|
* Verify annotation references in codebase after maintenance operations
|
|
19
23
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
package/lib/maintenance/batch.js
CHANGED
|
@@ -10,17 +10,21 @@ const detect_1 = require("./detect");
|
|
|
10
10
|
* @req REQ-MAINT-BATCH - Perform batch updates
|
|
11
11
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
12
12
|
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
13
|
+
* @req REQ-MAINT-UPDATE#1 - Update @supports references alongside @story
|
|
13
14
|
* @param codebasePath Absolute path to the workspace root where annotations will be updated.
|
|
14
15
|
* @param mappings Array of mapping objects describing path changes, each containing an oldPath and newPath.
|
|
15
16
|
* @param options Optional configuration including ESLint ignore patterns
|
|
16
|
-
* @returns
|
|
17
|
+
* @returns Object with total count of updated annotations and array of malformed annotation warnings
|
|
17
18
|
*/
|
|
18
19
|
function batchUpdateAnnotations(codebasePath, mappings, options) {
|
|
19
20
|
let totalUpdated = 0;
|
|
21
|
+
const allWarnings = [];
|
|
20
22
|
for (const { oldPath, newPath } of mappings) {
|
|
21
|
-
|
|
23
|
+
const result = (0, update_1.updateAnnotationReferences)(codebasePath, oldPath, newPath, options);
|
|
24
|
+
totalUpdated += result.count;
|
|
25
|
+
allWarnings.push(...result.warnings);
|
|
22
26
|
}
|
|
23
|
-
return totalUpdated;
|
|
27
|
+
return { count: totalUpdated, warnings: allWarnings };
|
|
24
28
|
}
|
|
25
29
|
/**
|
|
26
30
|
* Verify annotation references in codebase after maintenance operations
|
|
@@ -106,6 +106,33 @@ function handleReport(normalized) {
|
|
|
106
106
|
}
|
|
107
107
|
return exports.EXIT_OK;
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Handle dry-run mode for update command
|
|
111
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
112
|
+
* @req REQ-MAINT-SAFE
|
|
113
|
+
*/
|
|
114
|
+
function handleUpdateDryRun(root, from, to, flags) {
|
|
115
|
+
const options = flags.ignorePatterns
|
|
116
|
+
? { ignorePatterns: flags.ignorePatterns }
|
|
117
|
+
: undefined;
|
|
118
|
+
const beforeReport = (0, report_1.generateMaintenanceReport)(root, options);
|
|
119
|
+
const potentialChanges = beforeReport ? beforeReport.split("\n").length : 0;
|
|
120
|
+
const summary = {
|
|
121
|
+
root,
|
|
122
|
+
from,
|
|
123
|
+
to,
|
|
124
|
+
estimatedStaleCount: potentialChanges,
|
|
125
|
+
};
|
|
126
|
+
if (flags.json) {
|
|
127
|
+
console.log(JSON.stringify({ mode: "dry-run", ...summary }));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log("Dry run: no files were modified.");
|
|
131
|
+
console.log(`Would update @story and @supports annotations from '${from}' to '${to}' under ${root}.`);
|
|
132
|
+
console.log(`Estimated stale annotations before update: ${summary.estimatedStaleCount}.`);
|
|
133
|
+
}
|
|
134
|
+
return exports.EXIT_OK;
|
|
135
|
+
}
|
|
109
136
|
/**
|
|
110
137
|
* Handle the `update` subcommand to rewrite story annotation references.
|
|
111
138
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
@@ -126,32 +153,28 @@ function handleUpdate(normalized) {
|
|
|
126
153
|
const from = flags.from;
|
|
127
154
|
const to = flags.to;
|
|
128
155
|
if (flags.dryRun) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
return handleUpdateDryRun(root, from, to, {
|
|
157
|
+
ignorePatterns: flags.ignorePatterns,
|
|
158
|
+
json: flags.json,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const result = (0, update_1.updateAnnotationReferences)(root, from, to, options);
|
|
162
|
+
// Report malformed annotations if any were found
|
|
163
|
+
if (result.warnings.length > 0) {
|
|
164
|
+
console.error("\nWarnings - malformed annotations detected:");
|
|
165
|
+
result.warnings.forEach((warning) => console.error(` ${warning}`));
|
|
166
|
+
}
|
|
167
|
+
if (flags.json) {
|
|
168
|
+
console.log(JSON.stringify({
|
|
134
169
|
root,
|
|
135
170
|
from,
|
|
136
171
|
to,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
console.log(JSON.stringify({ mode: "dry-run", ...summary }));
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
console.log("Dry run: no files were modified.");
|
|
144
|
-
console.log(`Would update @story annotations from '${from}' to '${to}' under ${root}.`);
|
|
145
|
-
console.log(`Estimated stale annotations before update: ${summary.estimatedStaleCount}.`);
|
|
146
|
-
}
|
|
147
|
-
return exports.EXIT_OK;
|
|
148
|
-
}
|
|
149
|
-
const count = (0, update_1.updateAnnotationReferences)(root, from, to, options);
|
|
150
|
-
if (flags.json) {
|
|
151
|
-
console.log(JSON.stringify({ root, from, to, updated: count }));
|
|
172
|
+
updated: result.count,
|
|
173
|
+
warnings: result.warnings,
|
|
174
|
+
}));
|
|
152
175
|
}
|
|
153
176
|
else {
|
|
154
|
-
console.log(`Updated ${count}
|
|
177
|
+
console.log(`Updated ${result.count} annotation${result.count === 1 ? "" : "s"} (@story and @supports) from '${from}' to '${to}' under ${root}.`);
|
|
155
178
|
}
|
|
156
179
|
return exports.EXIT_OK;
|
|
157
180
|
}
|
|
@@ -2,11 +2,15 @@ import { GetAllFilesOptions } from "./utils";
|
|
|
2
2
|
/**
|
|
3
3
|
* Update annotation references when story files are moved or renamed
|
|
4
4
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
5
|
-
* @req REQ-MAINT-UPDATE
|
|
5
|
+
* @req REQ-MAINT-UPDATE
|
|
6
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
6
7
|
* @param codebasePath Absolute or workspace-root path whose files will be updated in-place.
|
|
7
|
-
* @param oldPath The original
|
|
8
|
-
* @param newPath The replacement
|
|
8
|
+
* @param oldPath The original path to search for in annotations.
|
|
9
|
+
* @param newPath The replacement path.
|
|
9
10
|
* @param options Optional configuration including ESLint ignore patterns
|
|
10
|
-
* @returns
|
|
11
|
+
* @returns Object with count of annotations updated and array of warnings
|
|
11
12
|
*/
|
|
12
|
-
export declare function updateAnnotationReferences(codebasePath: string, oldPath: string, newPath: string, options?: GetAllFilesOptions):
|
|
13
|
+
export declare function updateAnnotationReferences(codebasePath: string, oldPath: string, newPath: string, options?: GetAllFilesOptions): {
|
|
14
|
+
count: number;
|
|
15
|
+
warnings: string[];
|
|
16
|
+
};
|
|
@@ -36,29 +36,60 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.updateAnnotationReferences = updateAnnotationReferences;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const utils_1 = require("./utils");
|
|
39
|
+
/**
|
|
40
|
+
* Detect malformed annotations in file content
|
|
41
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
42
|
+
* @req REQ-MAINT-UPDATE - Detect and report malformed annotations
|
|
43
|
+
*/
|
|
44
|
+
function detectMalformedAnnotations(content, filePath) {
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
lines.forEach((line, idx) => {
|
|
48
|
+
const lineNum = idx + 1;
|
|
49
|
+
/* eslint-disable traceability/valid-annotation-format */
|
|
50
|
+
// Detect @story without a path
|
|
51
|
+
if (/@story\s*$/.test(line.trim()) || /@story\s*\*\//.test(line)) {
|
|
52
|
+
warnings.push(`${filePath}:${lineNum}: @story annotation without path`);
|
|
53
|
+
}
|
|
54
|
+
// Detect @supports without a path or requirements
|
|
55
|
+
if (/@supports\s*$/.test(line.trim()) || /@supports\s*\*\//.test(line)) {
|
|
56
|
+
warnings.push(`${filePath}:${lineNum}: @supports annotation without path/requirements`);
|
|
57
|
+
}
|
|
58
|
+
// Detect @req without a requirement ID
|
|
59
|
+
if (/@req\s*$/.test(line.trim()) ||
|
|
60
|
+
/@req\s*\*\//.test(line) ||
|
|
61
|
+
/@req\s+-\s/.test(line)) {
|
|
62
|
+
warnings.push(`${filePath}:${lineNum}: @req annotation without requirement ID`);
|
|
63
|
+
}
|
|
64
|
+
/* eslint-enable traceability/valid-annotation-format */
|
|
65
|
+
});
|
|
66
|
+
return warnings;
|
|
67
|
+
}
|
|
39
68
|
/**
|
|
40
69
|
* Helper to process a single file for annotation reference updates
|
|
41
70
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
42
71
|
* @req REQ-MAINT-UPDATE
|
|
43
72
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
44
73
|
*/
|
|
45
|
-
function processFileForAnnotationUpdates(fullPath,
|
|
46
|
-
const content = fs.readFileSync(fullPath, "utf8");
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
(match, p1) => {
|
|
54
|
-
|
|
74
|
+
function processFileForAnnotationUpdates(fullPath, regexes, newPath, refs) {
|
|
75
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
76
|
+
// Detect malformed annotations before processing
|
|
77
|
+
const malformedWarnings = detectMalformedAnnotations(content, fullPath);
|
|
78
|
+
refs.warnings.push(...malformedWarnings);
|
|
79
|
+
let newContent = content;
|
|
80
|
+
/* eslint-disable traceability/valid-annotation-format */
|
|
81
|
+
// Update @story references
|
|
82
|
+
newContent = newContent.replace(regexes.story, (match, p1) => {
|
|
83
|
+
refs.count++;
|
|
55
84
|
return `${p1}${newPath}`;
|
|
56
85
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
// Update @supports references
|
|
87
|
+
newContent = newContent.replace(regexes.supports, (match, prefix, suffix) => {
|
|
88
|
+
refs.count++;
|
|
89
|
+
return `${prefix}${newPath}${suffix}`;
|
|
90
|
+
});
|
|
91
|
+
/* eslint-enable traceability/valid-annotation-format */
|
|
92
|
+
// Write file only if content changed
|
|
62
93
|
if (newContent !== content) {
|
|
63
94
|
fs.writeFileSync(fullPath, newContent, "utf8");
|
|
64
95
|
}
|
|
@@ -66,43 +97,32 @@ function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCo
|
|
|
66
97
|
/**
|
|
67
98
|
* Update annotation references when story files are moved or renamed
|
|
68
99
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
69
|
-
* @req REQ-MAINT-UPDATE
|
|
100
|
+
* @req REQ-MAINT-UPDATE
|
|
101
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
70
102
|
* @param codebasePath Absolute or workspace-root path whose files will be updated in-place.
|
|
71
|
-
* @param oldPath The original
|
|
72
|
-
* @param newPath The replacement
|
|
103
|
+
* @param oldPath The original path to search for in annotations.
|
|
104
|
+
* @param newPath The replacement path.
|
|
73
105
|
* @param options Optional configuration including ESLint ignore patterns
|
|
74
|
-
* @returns
|
|
106
|
+
* @returns Object with count of annotations updated and array of warnings
|
|
75
107
|
*/
|
|
76
108
|
function updateAnnotationReferences(codebasePath, oldPath, newPath, options) {
|
|
77
|
-
|
|
78
|
-
* Check that the provided codebase path exists and is a directory.
|
|
79
|
-
* If not, abort early.
|
|
80
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
81
|
-
* @req REQ-MAINT-UPDATE
|
|
82
|
-
*/
|
|
109
|
+
// Check that the provided codebase path exists and is a directory.
|
|
83
110
|
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
84
111
|
if (!fs.existsSync(codebasePath) ||
|
|
85
112
|
!fs.statSync(codebasePath).isDirectory()) {
|
|
86
|
-
return 0;
|
|
113
|
+
return { count: 0, warnings: [] };
|
|
87
114
|
}
|
|
88
|
-
const
|
|
115
|
+
const refs = { count: 0, warnings: [] };
|
|
89
116
|
const escapedOldPath = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
90
|
-
|
|
117
|
+
// Create regex patterns for both story and supports references
|
|
118
|
+
const storyRegex = new RegExp(`(@story\\s*)${escapedOldPath}`, "g");
|
|
119
|
+
// Match supports with old path, capturing prefix and suffix requirements
|
|
120
|
+
const supportsRegex = new RegExp(`(@supports\\s+)${escapedOldPath}(\\s+[A-Z][A-Z0-9_-]*(?:#\\d+)?(?:\\s+[A-Z][A-Z0-9_-]*(?:#\\d+)?)*)`, "g");
|
|
91
121
|
const files = (0, utils_1.getAllFiles)(codebasePath, options);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
95
|
-
* @req REQ-MAINT-UPDATE
|
|
96
|
-
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
97
|
-
*/
|
|
98
|
-
/**
|
|
99
|
-
* Loop over each discovered file path
|
|
100
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
101
|
-
* @req REQ-MAINT-UPDATE
|
|
102
|
-
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
103
|
-
*/
|
|
122
|
+
// Loop over each discovered file path
|
|
123
|
+
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
104
124
|
for (const fullPath of files) {
|
|
105
|
-
processFileForAnnotationUpdates(fullPath,
|
|
125
|
+
processFileForAnnotationUpdates(fullPath, { story: storyRegex, supports: supportsRegex }, newPath, refs);
|
|
106
126
|
}
|
|
107
|
-
return
|
|
127
|
+
return { count: refs.count, warnings: refs.warnings };
|
|
108
128
|
}
|
|
@@ -43,6 +43,13 @@ export interface AnnotationRuleOptions {
|
|
|
43
43
|
* Human-readable example requirement ID used in error messages.
|
|
44
44
|
*/
|
|
45
45
|
requirementIdExample?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Story directories to validate against. When provided, derives storyPathPattern
|
|
48
|
+
* to match files within these directories if no explicit pattern is configured.
|
|
49
|
+
* Aligns with valid-story-reference configuration.
|
|
50
|
+
* Default: ["docs/stories", "stories"]
|
|
51
|
+
*/
|
|
52
|
+
storyDirectories?: string[];
|
|
46
53
|
/**
|
|
47
54
|
* Global toggle for auto-fix behavior in valid-annotation-format.
|
|
48
55
|
* When false, no automatic suffix-normalization fixes are applied.
|
|
@@ -140,6 +147,12 @@ export declare function getRuleSchema(): {
|
|
|
140
147
|
requirementIdExample: {
|
|
141
148
|
type: string;
|
|
142
149
|
};
|
|
150
|
+
storyDirectories: {
|
|
151
|
+
type: string;
|
|
152
|
+
items: {
|
|
153
|
+
type: string;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
143
156
|
autoFix: {
|
|
144
157
|
type: string;
|
|
145
158
|
};
|
|
@@ -16,6 +16,29 @@ exports.getRuleSchema = getRuleSchema;
|
|
|
16
16
|
* @req REQ-SCHEMA-VALIDATION - Use JSON Schema to validate configuration options
|
|
17
17
|
*/
|
|
18
18
|
const pattern_validators_1 = require("./pattern-validators");
|
|
19
|
+
/**
|
|
20
|
+
* Derive a story path pattern from configured story directories.
|
|
21
|
+
* Creates a pattern that matches files within any of the provided directories.
|
|
22
|
+
*
|
|
23
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
24
|
+
*/
|
|
25
|
+
function deriveStoryPatternFromDirectories(dirs) {
|
|
26
|
+
// Escape special regex characters in directory paths
|
|
27
|
+
const escapedDirs = dirs.map((dir) => dir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
28
|
+
// Create alternation pattern: (dir1|dir2|...)/filename.story.md
|
|
29
|
+
const dirsPattern = escapedDirs.length === 1 ? escapedDirs[0] : `(${escapedDirs.join("|")})`;
|
|
30
|
+
return new RegExp(String.raw `^${dirsPattern}/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derive a story path example from configured story directories.
|
|
34
|
+
* Uses the first directory in the list to create an example path.
|
|
35
|
+
*
|
|
36
|
+
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
37
|
+
*/
|
|
38
|
+
function deriveStoryExampleFromDirectories(dirs) {
|
|
39
|
+
const firstDir = dirs[0] || "docs/stories";
|
|
40
|
+
return `${firstDir}/005.0-DEV-EXAMPLE.story.md`;
|
|
41
|
+
}
|
|
19
42
|
/**
|
|
20
43
|
* Get the default regular expression used to validate story paths.
|
|
21
44
|
*
|
|
@@ -98,21 +121,31 @@ function getOptionErrors() {
|
|
|
98
121
|
/**
|
|
99
122
|
* Resolve the story path pattern from nested or flat configuration
|
|
100
123
|
* fields, validating and falling back to the default as needed.
|
|
124
|
+
* If storyDirectories is provided but no explicit pattern, derives pattern from directories.
|
|
101
125
|
*
|
|
102
126
|
* @story docs/stories/010.1-DEV-CONFIGURABLE-PATTERNS.story.md
|
|
103
127
|
* @req REQ-PATTERN-CONFIG - Allow configurable story path patterns
|
|
104
128
|
* @req REQ-REGEX-VALIDATION - Validate story path regex options
|
|
105
129
|
* @req REQ-BACKWARD-COMPAT - Use a default when no pattern is provided
|
|
106
130
|
*/
|
|
107
|
-
function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
function resolveStoryPattern(nestedStoryPattern, flatStoryPattern, storyDirectories) {
|
|
132
|
+
// If an explicit pattern is provided (nested or flat), use it
|
|
133
|
+
if (nestedStoryPattern || flatStoryPattern) {
|
|
134
|
+
return (0, pattern_validators_1.resolvePattern)({
|
|
135
|
+
nestedPattern: nestedStoryPattern,
|
|
136
|
+
nestedFieldName: "story.pattern",
|
|
137
|
+
flatPattern: flatStoryPattern,
|
|
138
|
+
flatFieldName: "storyPathPattern",
|
|
139
|
+
defaultPattern: getDefaultStoryPattern(),
|
|
140
|
+
errors: optionErrors,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// If storyDirectories is provided, derive pattern from it
|
|
144
|
+
if (storyDirectories && storyDirectories.length > 0) {
|
|
145
|
+
return deriveStoryPatternFromDirectories(storyDirectories);
|
|
146
|
+
}
|
|
147
|
+
// Otherwise, use the default pattern
|
|
148
|
+
return getDefaultStoryPattern();
|
|
116
149
|
}
|
|
117
150
|
/**
|
|
118
151
|
* Resolve the requirement ID pattern from nested or flat configuration
|
|
@@ -141,8 +174,17 @@ function resolveReqPattern(nestedReqPattern, flatReqPattern) {
|
|
|
141
174
|
* @req REQ-EXAMPLE-MESSAGES - Allow custom story examples in messages
|
|
142
175
|
* @req REQ-BACKWARD-COMPAT - Use a default story example when omitted
|
|
143
176
|
*/
|
|
144
|
-
function resolveStoryExample(nestedStoryExample, flatStoryExample) {
|
|
145
|
-
|
|
177
|
+
function resolveStoryExample(nestedStoryExample, flatStoryExample, storyDirectories) {
|
|
178
|
+
// If an explicit example is provided, use it
|
|
179
|
+
if (nestedStoryExample || flatStoryExample) {
|
|
180
|
+
return (0, pattern_validators_1.resolveExample)(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
181
|
+
}
|
|
182
|
+
// If storyDirectories is provided, derive example from it
|
|
183
|
+
if (storyDirectories && storyDirectories.length > 0) {
|
|
184
|
+
return deriveStoryExampleFromDirectories(storyDirectories);
|
|
185
|
+
}
|
|
186
|
+
// Otherwise, use the default example
|
|
187
|
+
return getDefaultStoryExample();
|
|
146
188
|
}
|
|
147
189
|
/**
|
|
148
190
|
* Resolve the requirement ID example string from nested or flat configuration
|
|
@@ -227,9 +269,9 @@ function resolveOptionsInternal(user) {
|
|
|
227
269
|
const { nestedReqExample, flatReqExample } = getReqExampleInputs(user);
|
|
228
270
|
const autoFixFlag = user?.autoFix;
|
|
229
271
|
const autoFix = typeof autoFixFlag === "boolean" ? autoFixFlag : true;
|
|
230
|
-
const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern);
|
|
272
|
+
const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern, user?.storyDirectories);
|
|
231
273
|
const reqPattern = resolveReqPattern(nestedReqPattern, flatReqPattern);
|
|
232
|
-
const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample);
|
|
274
|
+
const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample, user?.storyDirectories);
|
|
233
275
|
const reqExample = resolveReqExample(nestedReqExample, flatReqExample);
|
|
234
276
|
return {
|
|
235
277
|
storyPattern,
|
|
@@ -290,6 +332,7 @@ function getRuleSchema() {
|
|
|
290
332
|
storyPathExample: { type: "string" },
|
|
291
333
|
requirementIdPattern: { type: "string" },
|
|
292
334
|
requirementIdExample: { type: "string" },
|
|
335
|
+
storyDirectories: { type: "array", items: { type: "string" } },
|
|
293
336
|
autoFix: { type: "boolean" },
|
|
294
337
|
},
|
|
295
338
|
additionalProperties: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
"lint:require-built-plugin": "npm run lint-plugin-guard",
|
|
29
29
|
"lint": "eslint --config eslint.config.js \"src/**/*.{js,ts}\" \"tests/**/*.{js,ts}\" --max-warnings=0",
|
|
30
30
|
"test": "jest --ci --bail",
|
|
31
|
-
"test:unit": "jest --ci --bail --testPathPatterns='tests/(rules|maintenance|utils|unit)' --testPathIgnorePatterns='integration'",
|
|
31
|
+
"test:unit": "jest --ci --bail --testPathPatterns='tests/(rules|maintenance|utils|unit)' --testPathIgnorePatterns='integration|e2e'",
|
|
32
32
|
"test:integration": "jest --ci --bail --testPathPatterns='tests/integration'",
|
|
33
|
+
"test:e2e": "jest --ci --bail --testPathPatterns='tests/e2e'",
|
|
33
34
|
"ci-verify": "npm run type-check && npm run lint && npm run format:check && npm run duplication && npm run check:traceability && npm test && npm run audit:ci && npm run safety:deps",
|
|
34
35
|
"ci-verify:full": "npm run check:traceability && npm run safety:deps && npm run audit:ci && npm run build && npm run smoke:runtime && npm run type-check && npm run lint-plugin-check && npm run lint -- --max-warnings=0 && npm run duplication && npm run test -- --coverage && npm run format:check && npm audit --omit=dev --audit-level=high && npm run audit:dev-high && npm run check:ci-artifacts",
|
|
35
36
|
"ci-verify:fast": "npm run type-check && npm run check:traceability && npm run duplication && jest --ci --bail --passWithNoTests --testPathPatterns 'tests/(rules|maintenance)'",
|
|
@@ -551,7 +551,11 @@ export default [js.configs.recommended, traceability.configs.strict];
|
|
|
551
551
|
|
|
552
552
|
## Maintenance API and CLI
|
|
553
553
|
|
|
554
|
-
The plugin exposes a
|
|
554
|
+
The plugin exposes a maintenance API and CLI, `traceability-maint`, primarily for the **batch update** operation when story files are moved or renamed.
|
|
555
|
+
|
|
556
|
+
**Note**: The CLI also includes `detect`, `verify`, and `report` commands for historical reasons, but these largely duplicate functionality already provided by ESLint rules (`valid-story-reference`, `valid-req-reference`) during normal linting. The primary value of the maintenance tools is the **update** command, which can perform bulk reference updates across your codebase - something ESLint's auto-fix cannot do.
|
|
557
|
+
|
|
558
|
+
These tools update both `@story` and `@supports` references when story files are moved or renamed. All maintenance functions operate only on the local filesystem under the provided root directory; they do not make any network calls or interact with external services. These are manual developer tools, not intended for automated pipelines (ESLint rules handle validation during development and builds).
|
|
555
559
|
|
|
556
560
|
### Programmatic Maintenance API
|
|
557
561
|
|
|
@@ -603,23 +607,27 @@ Scans the workspace for `@story` annotations that point to missing or out-of-pro
|
|
|
603
607
|
|
|
604
608
|
#### `updateAnnotationReferences(rootDir, oldPath, newPath)`
|
|
605
609
|
|
|
606
|
-
Performs a targeted text replacement of `@story` values across the workspace.
|
|
610
|
+
Performs a targeted text replacement of `@story` and `@supports` values across the workspace, and detects malformed annotations.
|
|
607
611
|
|
|
608
612
|
**Parameters:**
|
|
609
613
|
|
|
610
614
|
- `rootDir` (string, required) – Workspace root to update in-place.
|
|
611
|
-
- `oldPath` (string, required) – The story path to search for
|
|
615
|
+
- `oldPath` (string, required) – The story path to search for in `@story` and `@supports` annotations.
|
|
612
616
|
- `newPath` (string, required) – The replacement story path.
|
|
613
617
|
|
|
614
618
|
**Returns:**
|
|
615
619
|
|
|
616
|
-
-
|
|
620
|
+
- Object with:
|
|
621
|
+
- `count` (number) – The count of annotations (`@story` and `@supports`) that were updated.
|
|
622
|
+
- `warnings` (string[]) – Array of malformed annotation warnings found during processing.
|
|
617
623
|
|
|
618
624
|
**Behavior notes:**
|
|
619
625
|
|
|
620
|
-
-
|
|
626
|
+
- Both `@story` and `@supports` annotations are updated when they reference the old path.
|
|
627
|
+
- For `@supports` annotations, the story path is updated while preserving the requirement IDs.
|
|
628
|
+
- Malformed annotations (missing paths or requirement IDs) are detected and reported in warnings.
|
|
621
629
|
- Files are only written when the content actually changes.
|
|
622
|
-
- If `rootDir` does not exist or is not a directory, the function returns `0
|
|
630
|
+
- If `rootDir` does not exist or is not a directory, the function returns `{ count: 0, warnings: [] }`.
|
|
623
631
|
|
|
624
632
|
#### `batchUpdateAnnotations(rootDir, mappings)`
|
|
625
633
|
|
|
@@ -632,12 +640,14 @@ Runs multiple `updateAnnotationReferences` operations in sequence.
|
|
|
632
640
|
|
|
633
641
|
**Returns:**
|
|
634
642
|
|
|
635
|
-
-
|
|
643
|
+
- Object with:
|
|
644
|
+
- `count` (number) – The total number of annotations updated across all mappings.
|
|
645
|
+
- `warnings` (string[]) – Combined array of all malformed annotation warnings found.
|
|
636
646
|
|
|
637
647
|
**Behavior notes:**
|
|
638
648
|
|
|
639
649
|
- There is no special batching logic; this helper simply loops over the provided mappings.
|
|
640
|
-
- For each mapping, it calls `updateAnnotationReferences(rootDir, oldPath, newPath)` and
|
|
650
|
+
- For each mapping, it calls `updateAnnotationReferences(rootDir, oldPath, newPath)` and aggregates counts and warnings.
|
|
641
651
|
|
|
642
652
|
#### `verifyAnnotations(rootDir)`
|
|
643
653
|
|
|
@@ -675,9 +685,11 @@ Generates a simple, text-only report of stale `@story` annotations.
|
|
|
675
685
|
|
|
676
686
|
### `traceability-maint` CLI
|
|
677
687
|
|
|
678
|
-
The `traceability-maint` CLI wraps the maintenance API for
|
|
688
|
+
The `traceability-maint` CLI wraps the maintenance API for manual developer invocation when reorganizing story files. It is typically available via `npx traceability-maint`.
|
|
689
|
+
|
|
690
|
+
**Important**: This CLI is designed for **manual developer execution only** when you need to batch-update references after moving or renaming story files. It should **not** be integrated into automated pipelines - ESLint rules (`valid-story-reference`, `valid-req-reference`) already handle validation during development and builds.
|
|
679
691
|
|
|
680
|
-
|
|
692
|
+
The CLI's **primary value** is the `update` command, which performs bulk reference updates that ESLint cannot do. The `detect`, `verify`, and `report` commands are included for historical compatibility but largely duplicate what ESLint already provides during normal linting.
|
|
681
693
|
|
|
682
694
|
#### General usage
|
|
683
695
|
|
|
@@ -754,7 +766,7 @@ Generates a plain-text or JSON report of stale story references.
|
|
|
754
766
|
# Human-readable text report (default)
|
|
755
767
|
traceability-maint report --root .
|
|
756
768
|
|
|
757
|
-
# JSON
|
|
769
|
+
# Machine-readable JSON output for further analysis
|
|
758
770
|
traceability-maint report --root . --format json
|
|
759
771
|
```
|
|
760
772
|
|
|
@@ -835,7 +847,7 @@ If `--from` or `--to` is missing, the CLI prints an error, shows the help text,
|
|
|
835
847
|
}
|
|
836
848
|
```
|
|
837
849
|
|
|
838
|
-
|
|
850
|
+
Manual verification:
|
|
839
851
|
|
|
840
852
|
```bash
|
|
841
853
|
npm run traceability:verify
|