eslint-plugin-traceability 1.24.0 → 1.26.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 +7 -2
- package/README.md +84 -14
- package/lib/maintenance/batch.d.ts +12 -3
- package/lib/maintenance/batch.js +14 -6
- package/lib/maintenance/commands.d.ts +3 -0
- package/lib/maintenance/commands.js +62 -24
- package/lib/maintenance/detect.d.ts +4 -1
- package/lib/maintenance/detect.js +5 -2
- package/lib/maintenance/flags.d.ts +1 -0
- package/lib/maintenance/flags.js +20 -0
- package/lib/maintenance/report.d.ts +3 -1
- package/lib/maintenance/report.js +166 -5
- package/lib/maintenance/update.d.ts +11 -5
- package/lib/maintenance/update.js +65 -44
- package/lib/maintenance/utils.d.ts +18 -1
- package/lib/maintenance/utils.js +35 -4
- package/package.json +1 -1
- package/user-docs/api-reference.md +22 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.26.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.25.0...v1.26.0) (2026-01-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **tests:** update performance test for new return types ([6db924f](https://github.com/voder-ai/eslint-plugin-traceability/commit/6db924f7af3c4feadf9528b557a3948a2e89bb19))
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
### Features
|
|
5
10
|
|
|
6
|
-
* add
|
|
11
|
+
* **maintenance:** add [@supports](https://github.com/supports) reference updates and malformed annotation detection ([845a8cf](https://github.com/voder-ai/eslint-plugin-traceability/commit/845a8cf55e5f0444cead557fdea2f9d1b10b588b)), closes [REQ-MAINT-UPDATE#1](https://github.com/REQ-MAINT-UPDATE/issues/1)
|
|
7
12
|
|
|
8
13
|
# Changelog
|
|
9
14
|
|
package/README.md
CHANGED
|
@@ -292,39 +292,109 @@ 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
|
-
|
|
301
|
-
- `
|
|
302
|
-
|
|
299
|
+
### Primary Command
|
|
300
|
+
|
|
301
|
+
- `update` – Batch update `@story` and `@supports` annotations when a story file is renamed or moved (the key feature ESLint cannot provide)
|
|
302
|
+
|
|
303
|
+
### Supporting Commands
|
|
304
|
+
|
|
305
|
+
The CLI also includes `detect`, `verify`, and `report` commands for historical compatibility, but these largely duplicate what ESLint already provides during normal linting:
|
|
306
|
+
|
|
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)
|
|
303
310
|
|
|
304
311
|
### Usage
|
|
305
312
|
|
|
306
|
-
|
|
313
|
+
**Primary use case - batch update references:**
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Update references when a story file is renamed
|
|
317
|
+
npx traceability-maint update \
|
|
318
|
+
--root . \
|
|
319
|
+
--from "stories/feature-authentication.story.md" \
|
|
320
|
+
--to "stories/feature-auth-v2.story.md"
|
|
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
|
+
|
|
329
|
+
# Update with ignore patterns to skip generated code
|
|
330
|
+
npx traceability-maint update \
|
|
331
|
+
--root . \
|
|
332
|
+
--from "stories/old.story.md" \
|
|
333
|
+
--to "stories/new.story.md" \
|
|
334
|
+
--ignore-pattern dist
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Supporting commands** (largely redundant with ESLint, but available):
|
|
307
338
|
|
|
308
339
|
```bash
|
|
309
340
|
# Show help and all options
|
|
310
341
|
npx traceability-maint --help
|
|
311
342
|
|
|
312
|
-
# Detect stale story references
|
|
343
|
+
# Detect stale story references (ESLint already does this during linting)
|
|
313
344
|
npx traceability-maint detect --root .
|
|
314
345
|
|
|
315
|
-
#
|
|
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)
|
|
316
350
|
npx traceability-maint verify --root .
|
|
317
351
|
|
|
318
|
-
# Generate a
|
|
352
|
+
# Generate a report (ESLint output already provides this information)
|
|
319
353
|
npx traceability-maint report --root . --format json
|
|
354
|
+
```
|
|
320
355
|
|
|
321
|
-
|
|
356
|
+
### Options
|
|
357
|
+
|
|
358
|
+
- `--root <path>` – Workspace root directory (defaults to current directory)
|
|
359
|
+
- `--json` – Output results in JSON format
|
|
360
|
+
- `--format <text|json>` – Report format (for `report` command)
|
|
361
|
+
- `--from <path>` – Source story path (for `update` command)
|
|
362
|
+
- `--to <path>` – Destination story path (for `update` command)
|
|
363
|
+
- `--dry-run` – Preview changes without modifying files (for `update` command)
|
|
364
|
+
- `--ignore-pattern <pattern>` – Path or directory to ignore (can be specified multiple times)
|
|
365
|
+
|
|
366
|
+
### ESLint Configuration Integration
|
|
367
|
+
|
|
368
|
+
The maintenance tools support `--ignore-pattern` flags to skip directories when performing batch updates:
|
|
369
|
+
|
|
370
|
+
- Skip generated code directories (e.g., `dist`, `build`)
|
|
371
|
+
- Ignore dependency folders (e.g., `node_modules`)
|
|
372
|
+
- Exclude test fixtures or temporary files
|
|
373
|
+
|
|
374
|
+
Multiple patterns can be specified:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
322
377
|
npx traceability-maint update \
|
|
323
|
-
--
|
|
324
|
-
--
|
|
325
|
-
--
|
|
378
|
+
--from old.story.md \
|
|
379
|
+
--to new.story.md \
|
|
380
|
+
--ignore-pattern node_modules \
|
|
381
|
+
--ignore-pattern dist \
|
|
382
|
+
--ignore-pattern coverage
|
|
326
383
|
```
|
|
327
384
|
|
|
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
|
|
395
|
+
|
|
396
|
+
The maintenance CLI is a manual refactoring aid, not an automated validation tool. ESLint handles validation automatically during your normal development workflow.
|
|
397
|
+
|
|
328
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.
|
|
329
399
|
|
|
330
400
|
## Plugin Validation
|
|
@@ -1,21 +1,30 @@
|
|
|
1
|
+
import { GetAllFilesOptions } from "./utils";
|
|
1
2
|
/**
|
|
2
3
|
* Batch update annotations and verify references
|
|
3
4
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
5
|
* @req REQ-MAINT-BATCH - Perform batch updates
|
|
5
6
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
7
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
8
|
+
* @req REQ-MAINT-UPDATE#1 - Update @supports references alongside @story
|
|
6
9
|
* @param codebasePath Absolute path to the workspace root where annotations will be updated.
|
|
7
10
|
* @param mappings Array of mapping objects describing path changes, each containing an oldPath and newPath.
|
|
8
|
-
* @
|
|
11
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
12
|
+
* @returns Object with total count of updated annotations and array of malformed annotation warnings
|
|
9
13
|
*/
|
|
10
14
|
export declare function batchUpdateAnnotations(codebasePath: string, mappings: {
|
|
11
15
|
oldPath: string;
|
|
12
16
|
newPath: string;
|
|
13
|
-
}[]):
|
|
17
|
+
}[], options?: GetAllFilesOptions): {
|
|
18
|
+
count: number;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
};
|
|
14
21
|
/**
|
|
15
22
|
* Verify annotation references in codebase after maintenance operations
|
|
16
23
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
17
24
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
25
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
18
26
|
* @param codebasePath Absolute path to the workspace root whose annotations should be verified.
|
|
27
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
19
28
|
* @returns Boolean indicating whether there are no stale annotations remaining (true if clean, false if any remain).
|
|
20
29
|
*/
|
|
21
|
-
export declare function verifyAnnotations(codebasePath: string): boolean;
|
|
30
|
+
export declare function verifyAnnotations(codebasePath: string, options?: GetAllFilesOptions): boolean;
|
package/lib/maintenance/batch.js
CHANGED
|
@@ -9,25 +9,33 @@ const detect_1 = require("./detect");
|
|
|
9
9
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
10
10
|
* @req REQ-MAINT-BATCH - Perform batch updates
|
|
11
11
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
12
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
13
|
+
* @req REQ-MAINT-UPDATE#1 - Update @supports references alongside @story
|
|
12
14
|
* @param codebasePath Absolute path to the workspace root where annotations will be updated.
|
|
13
15
|
* @param mappings Array of mapping objects describing path changes, each containing an oldPath and newPath.
|
|
14
|
-
* @
|
|
16
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
17
|
+
* @returns Object with total count of updated annotations and array of malformed annotation warnings
|
|
15
18
|
*/
|
|
16
|
-
function batchUpdateAnnotations(codebasePath, mappings) {
|
|
19
|
+
function batchUpdateAnnotations(codebasePath, mappings, options) {
|
|
17
20
|
let totalUpdated = 0;
|
|
21
|
+
const allWarnings = [];
|
|
18
22
|
for (const { oldPath, newPath } of mappings) {
|
|
19
|
-
|
|
23
|
+
const result = (0, update_1.updateAnnotationReferences)(codebasePath, oldPath, newPath, options);
|
|
24
|
+
totalUpdated += result.count;
|
|
25
|
+
allWarnings.push(...result.warnings);
|
|
20
26
|
}
|
|
21
|
-
return totalUpdated;
|
|
27
|
+
return { count: totalUpdated, warnings: allWarnings };
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* Verify annotation references in codebase after maintenance operations
|
|
25
31
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
26
32
|
* @req REQ-MAINT-VERIFY - Verify annotation references
|
|
33
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
27
34
|
* @param codebasePath Absolute path to the workspace root whose annotations should be verified.
|
|
35
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
28
36
|
* @returns Boolean indicating whether there are no stale annotations remaining (true if clean, false if any remain).
|
|
29
37
|
*/
|
|
30
|
-
function verifyAnnotations(codebasePath) {
|
|
31
|
-
const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath);
|
|
38
|
+
function verifyAnnotations(codebasePath, options) {
|
|
39
|
+
const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath, options);
|
|
32
40
|
return staleAnnotations.length === 0;
|
|
33
41
|
}
|
|
@@ -7,6 +7,7 @@ export declare const EXIT_USAGE = 2;
|
|
|
7
7
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
8
|
* @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
|
|
9
9
|
* @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
|
|
10
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
10
11
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
|
|
11
12
|
*/
|
|
12
13
|
export declare function handleDetect(normalized: NormalizedCliArgs): number;
|
|
@@ -15,6 +16,7 @@ export declare function handleDetect(normalized: NormalizedCliArgs): number;
|
|
|
15
16
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
16
17
|
* @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
|
|
17
18
|
* @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
|
|
19
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
18
20
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
|
|
19
21
|
*/
|
|
20
22
|
export declare function handleVerify(normalized: NormalizedCliArgs): number;
|
|
@@ -23,6 +25,7 @@ export declare function handleVerify(normalized: NormalizedCliArgs): number;
|
|
|
23
25
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
24
26
|
* @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
|
|
25
27
|
* @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
|
|
28
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
26
29
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
|
|
27
30
|
*/
|
|
28
31
|
export declare function handleReport(normalized: NormalizedCliArgs): number;
|
|
@@ -28,12 +28,16 @@ exports.EXIT_USAGE = 2;
|
|
|
28
28
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
29
29
|
* @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
|
|
30
30
|
* @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
|
|
31
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
31
32
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
|
|
32
33
|
*/
|
|
33
34
|
function handleDetect(normalized) {
|
|
34
35
|
const flags = (0, flags_1.parseFlags)(normalized);
|
|
35
36
|
const root = flags.root;
|
|
36
|
-
const
|
|
37
|
+
const options = flags.ignorePatterns
|
|
38
|
+
? { ignorePatterns: flags.ignorePatterns }
|
|
39
|
+
: undefined;
|
|
40
|
+
const stale = (0, detect_1.detectStaleAnnotations)(root, options);
|
|
37
41
|
if (flags.json) {
|
|
38
42
|
// Emit JSON output to support consumption by external tools and scripts.
|
|
39
43
|
console.log(JSON.stringify({ root, stale }));
|
|
@@ -55,12 +59,16 @@ Run 'traceability-maint report' for a structured summary.`);
|
|
|
55
59
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
56
60
|
* @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
|
|
57
61
|
* @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
|
|
62
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
58
63
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
|
|
59
64
|
*/
|
|
60
65
|
function handleVerify(normalized) {
|
|
61
66
|
const flags = (0, flags_1.parseFlags)(normalized);
|
|
62
67
|
const root = flags.root;
|
|
63
|
-
const
|
|
68
|
+
const options = flags.ignorePatterns
|
|
69
|
+
? { ignorePatterns: flags.ignorePatterns }
|
|
70
|
+
: undefined;
|
|
71
|
+
const valid = (0, batch_1.verifyAnnotations)(root, options);
|
|
64
72
|
if (valid) {
|
|
65
73
|
console.log(`All traceability annotations under ${root} are valid.`);
|
|
66
74
|
return exports.EXIT_OK;
|
|
@@ -73,13 +81,17 @@ function handleVerify(normalized) {
|
|
|
73
81
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
74
82
|
* @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
|
|
75
83
|
* @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
|
|
84
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
76
85
|
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
|
|
77
86
|
*/
|
|
78
87
|
function handleReport(normalized) {
|
|
79
88
|
const flags = (0, flags_1.parseFlags)(normalized);
|
|
80
89
|
const root = flags.root;
|
|
81
90
|
const format = flags.format ?? "text";
|
|
82
|
-
const
|
|
91
|
+
const options = flags.ignorePatterns
|
|
92
|
+
? { ignorePatterns: flags.ignorePatterns }
|
|
93
|
+
: undefined;
|
|
94
|
+
const report = (0, report_1.generateMaintenanceReport)(root, options);
|
|
83
95
|
if (format === "json") {
|
|
84
96
|
console.log(JSON.stringify({ root, report }));
|
|
85
97
|
}
|
|
@@ -94,6 +106,33 @@ function handleReport(normalized) {
|
|
|
94
106
|
}
|
|
95
107
|
return exports.EXIT_OK;
|
|
96
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
|
+
}
|
|
97
136
|
/**
|
|
98
137
|
* Handle the `update` subcommand to rewrite story annotation references.
|
|
99
138
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
@@ -104,6 +143,9 @@ function handleReport(normalized) {
|
|
|
104
143
|
function handleUpdate(normalized) {
|
|
105
144
|
const flags = (0, flags_1.parseFlags)(normalized);
|
|
106
145
|
const root = flags.root;
|
|
146
|
+
const options = flags.ignorePatterns
|
|
147
|
+
? { ignorePatterns: flags.ignorePatterns }
|
|
148
|
+
: undefined;
|
|
107
149
|
if (!flags.from || !flags.to) {
|
|
108
150
|
console.error("'update' requires --from <oldPath> and --to <newPath>.");
|
|
109
151
|
return exports.EXIT_USAGE;
|
|
@@ -111,32 +153,28 @@ function handleUpdate(normalized) {
|
|
|
111
153
|
const from = flags.from;
|
|
112
154
|
const to = flags.to;
|
|
113
155
|
if (flags.dryRun) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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({
|
|
119
169
|
root,
|
|
120
170
|
from,
|
|
121
171
|
to,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.log(JSON.stringify({ mode: "dry-run", ...summary }));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
console.log("Dry run: no files were modified.");
|
|
129
|
-
console.log(`Would update @story annotations from '${from}' to '${to}' under ${root}.`);
|
|
130
|
-
console.log(`Estimated stale annotations before update: ${summary.estimatedStaleCount}.`);
|
|
131
|
-
}
|
|
132
|
-
return exports.EXIT_OK;
|
|
133
|
-
}
|
|
134
|
-
const count = (0, update_1.updateAnnotationReferences)(root, from, to);
|
|
135
|
-
if (flags.json) {
|
|
136
|
-
console.log(JSON.stringify({ root, from, to, updated: count }));
|
|
172
|
+
updated: result.count,
|
|
173
|
+
warnings: result.warnings,
|
|
174
|
+
}));
|
|
137
175
|
}
|
|
138
176
|
else {
|
|
139
|
-
console.log(`Updated ${count}
|
|
177
|
+
console.log(`Updated ${result.count} annotation${result.count === 1 ? "" : "s"} (@story and @supports) from '${from}' to '${to}' under ${root}.`);
|
|
140
178
|
}
|
|
141
179
|
return exports.EXIT_OK;
|
|
142
180
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { GetAllFilesOptions } from "./utils";
|
|
1
2
|
/**
|
|
2
3
|
* Detect stale annotation references that point to moved or deleted story files
|
|
3
4
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
5
|
* @req REQ-MAINT-DETECT - Detect stale annotation references
|
|
6
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
5
7
|
* @param codebasePath Path to the codebase root, treated as a workspace root and resolved against process.cwd().
|
|
8
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
6
9
|
* @returns A de-duplicated array of stale @story paths (as strings) whose resolved targets no longer exist on disk.
|
|
7
10
|
*/
|
|
8
|
-
export declare function detectStaleAnnotations(codebasePath: string): string[];
|
|
11
|
+
export declare function detectStaleAnnotations(codebasePath: string, options?: GetAllFilesOptions): string[];
|
|
@@ -42,10 +42,12 @@ const storyReferenceUtils_1 = require("../utils/storyReferenceUtils");
|
|
|
42
42
|
* Detect stale annotation references that point to moved or deleted story files
|
|
43
43
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
44
44
|
* @req REQ-MAINT-DETECT - Detect stale annotation references
|
|
45
|
+
* @req REQ-MAINT-UPDATE - Integrate with ESLint configuration
|
|
45
46
|
* @param codebasePath Path to the codebase root, treated as a workspace root and resolved against process.cwd().
|
|
47
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
46
48
|
* @returns A de-duplicated array of stale @story paths (as strings) whose resolved targets no longer exist on disk.
|
|
47
49
|
*/
|
|
48
|
-
function detectStaleAnnotations(codebasePath) {
|
|
50
|
+
function detectStaleAnnotations(codebasePath, options) {
|
|
49
51
|
const cwd = process.cwd();
|
|
50
52
|
// @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
51
53
|
// @req REQ-MAINT-DETECT - Treat codebasePath as a workspace root resolved from process.cwd()
|
|
@@ -62,7 +64,8 @@ function detectStaleAnnotations(codebasePath) {
|
|
|
62
64
|
const stale = new Set();
|
|
63
65
|
// @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
64
66
|
// @req REQ-MAINT-DETECT - Iterate over all files in the isolated workspace root
|
|
65
|
-
|
|
67
|
+
// @req REQ-MAINT-UPDATE - Apply ESLint ignore patterns during file discovery
|
|
68
|
+
const files = (0, utils_1.getAllFiles)(workspaceRoot, options);
|
|
66
69
|
// @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
67
70
|
// @req REQ-MAINT-DETECT - Loop over each workspace file to inspect its @story annotations
|
|
68
71
|
for (const file of files) {
|
package/lib/maintenance/flags.js
CHANGED
|
@@ -152,6 +152,22 @@ function handleDryRunFlag(flags, args, index) {
|
|
|
152
152
|
flags.dryRun = true;
|
|
153
153
|
return index;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Handle the --ignore-pattern flag, collecting ignore patterns for ESLint integration
|
|
157
|
+
*
|
|
158
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
159
|
+
* @req REQ-MAINT-UPDATE - Support ESLint configuration integration
|
|
160
|
+
*/
|
|
161
|
+
function handleIgnorePatternFlag(flags, args, index) {
|
|
162
|
+
if (args[index] !== "--ignore-pattern" || !isNextValueString(args, index)) {
|
|
163
|
+
return index;
|
|
164
|
+
}
|
|
165
|
+
if (!flags.ignorePatterns) {
|
|
166
|
+
flags.ignorePatterns = [];
|
|
167
|
+
}
|
|
168
|
+
flags.ignorePatterns.push(args[index + 1]);
|
|
169
|
+
return index + 1;
|
|
170
|
+
}
|
|
155
171
|
/**
|
|
156
172
|
* Handle a single CLI argument and update the flags accordingly.
|
|
157
173
|
*
|
|
@@ -183,6 +199,10 @@ function applyFlag(flags, args, index) {
|
|
|
183
199
|
if (afterDryRun !== index) {
|
|
184
200
|
return afterDryRun;
|
|
185
201
|
}
|
|
202
|
+
const afterIgnorePattern = handleIgnorePatternFlag(flags, args, index);
|
|
203
|
+
if (afterIgnorePattern !== index) {
|
|
204
|
+
return afterIgnorePattern;
|
|
205
|
+
}
|
|
186
206
|
return index;
|
|
187
207
|
}
|
|
188
208
|
/**
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { GetAllFilesOptions } from "./utils";
|
|
1
2
|
/**
|
|
2
3
|
* Generate a report of maintenance operations performed
|
|
3
4
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
5
|
* @req REQ-MAINT-REPORT - Generate maintenance report
|
|
5
6
|
* @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
|
|
6
7
|
* @param codebasePath The workspace root to scan for stale maintenance annotations.
|
|
8
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
7
9
|
* @returns An empty string when no stale annotations are found, or a newline-separated list of stale `@story` paths.
|
|
8
10
|
*/
|
|
9
|
-
export declare function generateMaintenanceReport(codebasePath: string): string;
|
|
11
|
+
export declare function generateMaintenanceReport(codebasePath: string, options?: GetAllFilesOptions): string;
|
|
@@ -1,21 +1,182 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.generateMaintenanceReport = generateMaintenanceReport;
|
|
4
37
|
const detect_1 = require("./detect");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* Detect circular references in story annotations
|
|
42
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
43
|
+
* @req REQ-MAINT-REPORT - Handle circular reference edge cases
|
|
44
|
+
* @param codebasePath The workspace root to scan for circular references
|
|
45
|
+
* @returns Array of circular reference chains detected
|
|
46
|
+
*/
|
|
47
|
+
function detectCircularReferences(codebasePath) {
|
|
48
|
+
const circularChains = [];
|
|
49
|
+
const storyGraph = new Map();
|
|
50
|
+
// Build a graph of story file references
|
|
51
|
+
try {
|
|
52
|
+
buildStoryGraph(codebasePath, storyGraph);
|
|
53
|
+
// Detect cycles using DFS
|
|
54
|
+
const visited = new Set();
|
|
55
|
+
const recursionStack = new Set();
|
|
56
|
+
for (const storyPath of storyGraph.keys()) {
|
|
57
|
+
if (!visited.has(storyPath)) {
|
|
58
|
+
detectCycles(storyPath, {
|
|
59
|
+
graph: storyGraph,
|
|
60
|
+
visited,
|
|
61
|
+
recursionStack,
|
|
62
|
+
path: [],
|
|
63
|
+
circularChains,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Silently handle errors during circular reference detection
|
|
70
|
+
// to avoid breaking the main report generation
|
|
71
|
+
}
|
|
72
|
+
return circularChains;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a graph of story file cross-references
|
|
76
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
77
|
+
* @req REQ-MAINT-REPORT - Build dependency graph for circular detection
|
|
78
|
+
*/
|
|
79
|
+
function buildStoryGraph(codebasePath, graph) {
|
|
80
|
+
const storyFiles = findStoryFiles(codebasePath);
|
|
81
|
+
for (const storyFile of storyFiles) {
|
|
82
|
+
const content = fs.readFileSync(storyFile, "utf8");
|
|
83
|
+
const references = extractStoryReferences(content);
|
|
84
|
+
const relativePath = path.relative(codebasePath, storyFile);
|
|
85
|
+
if (!graph.has(relativePath)) {
|
|
86
|
+
graph.set(relativePath, new Set());
|
|
87
|
+
}
|
|
88
|
+
for (const ref of references) {
|
|
89
|
+
graph.get(relativePath)?.add(ref);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Find all story files in the codebase
|
|
95
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
96
|
+
* @req REQ-MAINT-REPORT - Locate story files for circular detection
|
|
97
|
+
*/
|
|
98
|
+
function findStoryFiles(dir) {
|
|
99
|
+
const storyFiles = [];
|
|
100
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
101
|
+
return storyFiles;
|
|
102
|
+
}
|
|
103
|
+
const entries = fs.readdirSync(dir);
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const fullPath = path.join(dir, entry);
|
|
106
|
+
const stat = fs.statSync(fullPath);
|
|
107
|
+
if (stat.isDirectory()) {
|
|
108
|
+
storyFiles.push(...findStoryFiles(fullPath));
|
|
109
|
+
}
|
|
110
|
+
else if (stat.isFile() && entry.endsWith(".story.md")) {
|
|
111
|
+
storyFiles.push(fullPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return storyFiles;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Extract story references from file content
|
|
118
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
119
|
+
* @req REQ-MAINT-REPORT - Parse story references for circular detection
|
|
120
|
+
*/
|
|
121
|
+
function extractStoryReferences(content) {
|
|
122
|
+
const references = [];
|
|
123
|
+
const storyPattern = /@story\s+([^\s]+\.story\.md)/g;
|
|
124
|
+
let match;
|
|
125
|
+
while ((match = storyPattern.exec(content)) !== null) {
|
|
126
|
+
references.push(match[1]);
|
|
127
|
+
}
|
|
128
|
+
return references;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Detect cycles in the story dependency graph using DFS
|
|
132
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
133
|
+
* @req REQ-MAINT-REPORT - Detect circular dependencies using graph traversal
|
|
134
|
+
*/
|
|
135
|
+
function detectCycles(node, options) {
|
|
136
|
+
const { graph, visited, recursionStack, path, circularChains } = options;
|
|
137
|
+
visited.add(node);
|
|
138
|
+
recursionStack.add(node);
|
|
139
|
+
path.push(node);
|
|
140
|
+
const neighbors = graph.get(node) || new Set();
|
|
141
|
+
for (const neighbor of neighbors) {
|
|
142
|
+
if (!visited.has(neighbor)) {
|
|
143
|
+
detectCycles(neighbor, options);
|
|
144
|
+
}
|
|
145
|
+
else if (recursionStack.has(neighbor)) {
|
|
146
|
+
// Found a cycle
|
|
147
|
+
const cycleStart = path.indexOf(neighbor);
|
|
148
|
+
const cycle = path.slice(cycleStart).concat(neighbor);
|
|
149
|
+
circularChains.push(`Circular reference: ${cycle.join(" -> ")}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
path.pop();
|
|
153
|
+
recursionStack.delete(node);
|
|
154
|
+
}
|
|
5
155
|
/**
|
|
6
156
|
* Generate a report of maintenance operations performed
|
|
7
157
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
8
158
|
* @req REQ-MAINT-REPORT - Generate maintenance report
|
|
9
159
|
* @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
|
|
10
160
|
* @param codebasePath The workspace root to scan for stale maintenance annotations.
|
|
161
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
11
162
|
* @returns An empty string when no stale annotations are found, or a newline-separated list of stale `@story` paths.
|
|
12
163
|
*/
|
|
13
|
-
function generateMaintenanceReport(codebasePath) {
|
|
14
|
-
const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath);
|
|
164
|
+
function generateMaintenanceReport(codebasePath, options) {
|
|
165
|
+
const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath, options);
|
|
166
|
+
const circularReferences = detectCircularReferences(codebasePath);
|
|
167
|
+
const reportSections = [];
|
|
15
168
|
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE
|
|
16
169
|
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT
|
|
17
|
-
if (staleAnnotations.length
|
|
18
|
-
|
|
170
|
+
if (staleAnnotations.length > 0) {
|
|
171
|
+
reportSections.push("Stale Annotations:");
|
|
172
|
+
reportSections.push(...staleAnnotations);
|
|
173
|
+
}
|
|
174
|
+
if (circularReferences.length > 0) {
|
|
175
|
+
if (reportSections.length > 0) {
|
|
176
|
+
reportSections.push("");
|
|
177
|
+
}
|
|
178
|
+
reportSections.push("Circular References:");
|
|
179
|
+
reportSections.push(...circularReferences);
|
|
19
180
|
}
|
|
20
|
-
return
|
|
181
|
+
return reportSections.join("\n");
|
|
21
182
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { GetAllFilesOptions } from "./utils";
|
|
1
2
|
/**
|
|
2
3
|
* Update annotation references when story files are moved or renamed
|
|
3
4
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
|
-
* @req REQ-MAINT-UPDATE
|
|
5
|
+
* @req REQ-MAINT-UPDATE
|
|
6
|
+
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
5
7
|
* @param codebasePath Absolute or workspace-root path whose files will be updated in-place.
|
|
6
|
-
* @param oldPath The original
|
|
7
|
-
* @param newPath The replacement
|
|
8
|
-
* @
|
|
8
|
+
* @param oldPath The original path to search for in annotations.
|
|
9
|
+
* @param newPath The replacement path.
|
|
10
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
11
|
+
* @returns Object with count of annotations updated and array of warnings
|
|
9
12
|
*/
|
|
10
|
-
export declare function updateAnnotationReferences(codebasePath: string, oldPath: string, newPath: string):
|
|
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,42 +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
|
|
73
|
-
* @
|
|
103
|
+
* @param oldPath The original path to search for in annotations.
|
|
104
|
+
* @param newPath The replacement path.
|
|
105
|
+
* @param options Optional configuration including ESLint ignore patterns
|
|
106
|
+
* @returns Object with count of annotations updated and array of warnings
|
|
74
107
|
*/
|
|
75
|
-
function updateAnnotationReferences(codebasePath, oldPath, newPath) {
|
|
76
|
-
|
|
77
|
-
* Check that the provided codebase path exists and is a directory.
|
|
78
|
-
* If not, abort early.
|
|
79
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
80
|
-
* @req REQ-MAINT-UPDATE
|
|
81
|
-
*/
|
|
108
|
+
function updateAnnotationReferences(codebasePath, oldPath, newPath, options) {
|
|
109
|
+
// Check that the provided codebase path exists and is a directory.
|
|
82
110
|
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
83
111
|
if (!fs.existsSync(codebasePath) ||
|
|
84
112
|
!fs.statSync(codebasePath).isDirectory()) {
|
|
85
|
-
return 0;
|
|
113
|
+
return { count: 0, warnings: [] };
|
|
86
114
|
}
|
|
87
|
-
const
|
|
115
|
+
const refs = { count: 0, warnings: [] };
|
|
88
116
|
const escapedOldPath = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*/
|
|
97
|
-
/**
|
|
98
|
-
* Loop over each discovered file path
|
|
99
|
-
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
100
|
-
* @req REQ-MAINT-UPDATE
|
|
101
|
-
* @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
102
|
-
*/
|
|
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");
|
|
121
|
+
const files = (0, utils_1.getAllFiles)(codebasePath, options);
|
|
122
|
+
// Loop over each discovered file path
|
|
123
|
+
// @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
|
|
103
124
|
for (const fullPath of files) {
|
|
104
|
-
processFileForAnnotationUpdates(fullPath,
|
|
125
|
+
processFileForAnnotationUpdates(fullPath, { story: storyRegex, supports: supportsRegex }, newPath, refs);
|
|
105
126
|
}
|
|
106
|
-
return
|
|
127
|
+
return { count: refs.count, warnings: refs.warnings };
|
|
107
128
|
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for file traversal with optional ignore patterns
|
|
3
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
|
+
* @req REQ-MAINT-UPDATE - Support ESLint configuration integration
|
|
5
|
+
*/
|
|
6
|
+
export interface GetAllFilesOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Array of glob patterns or absolute paths to ignore during traversal.
|
|
9
|
+
* Supports both directory paths (to skip entire directories) and file patterns.
|
|
10
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
11
|
+
* @req REQ-MAINT-UPDATE - Respect ESLint ignore patterns
|
|
12
|
+
*/
|
|
13
|
+
ignorePatterns?: string[];
|
|
14
|
+
}
|
|
1
15
|
/**
|
|
2
16
|
* Recursively retrieve all files in a directory.
|
|
3
17
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
4
18
|
* @req REQ-MAINT-UTILS - Extract common file traversal logic for maintenance tools
|
|
19
|
+
* @req REQ-MAINT-UPDATE - Support ESLint configuration integration
|
|
20
|
+
* @param dir Root directory to scan
|
|
21
|
+
* @param options Optional configuration including ignore patterns from ESLint config
|
|
5
22
|
*/
|
|
6
|
-
export declare function getAllFiles(dir: string): string[];
|
|
23
|
+
export declare function getAllFiles(dir: string, options?: GetAllFilesOptions): string[];
|
package/lib/maintenance/utils.js
CHANGED
|
@@ -40,9 +40,13 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
* Recursively retrieve all files in a directory.
|
|
41
41
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
42
42
|
* @req REQ-MAINT-UTILS - Extract common file traversal logic for maintenance tools
|
|
43
|
+
* @req REQ-MAINT-UPDATE - Support ESLint configuration integration
|
|
44
|
+
* @param dir Root directory to scan
|
|
45
|
+
* @param options Optional configuration including ignore patterns from ESLint config
|
|
43
46
|
*/
|
|
44
|
-
function getAllFiles(dir) {
|
|
47
|
+
function getAllFiles(dir, options) {
|
|
45
48
|
const fileList = [];
|
|
49
|
+
const ignorePatterns = options?.ignorePatterns ?? [];
|
|
46
50
|
/**
|
|
47
51
|
* Ensure the provided path exists and is a directory before traversal.
|
|
48
52
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
@@ -56,15 +60,34 @@ function getAllFiles(dir) {
|
|
|
56
60
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
57
61
|
return fileList;
|
|
58
62
|
}
|
|
59
|
-
traverseDirectory(dir, fileList);
|
|
63
|
+
traverseDirectory(dir, fileList, ignorePatterns);
|
|
60
64
|
return fileList;
|
|
61
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if a path should be ignored based on patterns
|
|
68
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
69
|
+
* @req REQ-MAINT-UPDATE - Respect ESLint ignore patterns
|
|
70
|
+
*/
|
|
71
|
+
function shouldIgnore(filePath, ignorePatterns) {
|
|
72
|
+
for (const pattern of ignorePatterns) {
|
|
73
|
+
// Support both exact path matches and directory prefix matches
|
|
74
|
+
if (filePath === pattern ||
|
|
75
|
+
filePath.startsWith(pattern + path.sep) ||
|
|
76
|
+
// Support common patterns like node_modules, dist, etc.
|
|
77
|
+
filePath.includes(path.sep + pattern + path.sep) ||
|
|
78
|
+
filePath.endsWith(path.sep + pattern)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
62
84
|
/**
|
|
63
85
|
* Recursively traverse a directory and collect file paths.
|
|
64
86
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
65
87
|
* @req REQ-MAINT-UTILS-TRAVERSE - Helper traversal function used by getAllFiles
|
|
88
|
+
* @req REQ-MAINT-UPDATE - Apply ignore patterns during traversal
|
|
66
89
|
*/
|
|
67
|
-
function traverseDirectory(currentDir, fileList) {
|
|
90
|
+
function traverseDirectory(currentDir, fileList, ignorePatterns) {
|
|
68
91
|
const entries = fs.readdirSync(currentDir);
|
|
69
92
|
/**
|
|
70
93
|
* Iterate over directory entries using a for-of loop.
|
|
@@ -73,6 +96,14 @@ function traverseDirectory(currentDir, fileList) {
|
|
|
73
96
|
*/
|
|
74
97
|
for (const entry of entries) {
|
|
75
98
|
const fullPath = path.join(currentDir, entry);
|
|
99
|
+
/**
|
|
100
|
+
* Skip ignored paths based on ESLint configuration
|
|
101
|
+
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
|
102
|
+
* @req REQ-MAINT-UPDATE - Respect ESLint ignore patterns
|
|
103
|
+
*/
|
|
104
|
+
if (shouldIgnore(fullPath, ignorePatterns)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
76
107
|
const stat = fs.statSync(fullPath);
|
|
77
108
|
/**
|
|
78
109
|
* Recurse into directories to continue traversal.
|
|
@@ -80,7 +111,7 @@ function traverseDirectory(currentDir, fileList) {
|
|
|
80
111
|
* @req REQ-MAINT-UTILS-TRAVERSE-DIR - Handle directory entries during traversal
|
|
81
112
|
*/
|
|
82
113
|
if (stat.isDirectory()) {
|
|
83
|
-
traverseDirectory(fullPath, fileList);
|
|
114
|
+
traverseDirectory(fullPath, fileList, ignorePatterns);
|
|
84
115
|
/**
|
|
85
116
|
* Collect regular file entries during traversal.
|
|
86
117
|
* @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-traceability",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.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",
|
|
@@ -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
|
|