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 CHANGED
@@ -1,9 +1,14 @@
1
- # [1.24.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.23.1...v1.24.0) (2026-01-10)
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 auto-fix support to require-traceability rule ([ee35349](https://github.com/voder-ai/eslint-plugin-traceability/commit/ee353492bfbebbc18dba7ae8e4d32b8ec386bdf1))
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 helps you maintain and audit `@story` annotations outside of ESLint runs. It focuses on repository-wide checks for stale story references and safe batch updates.
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
- ### Commands
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
- - `detect` – Scan the workspace and detect `@story` annotations that reference missing story files.
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.
302
- - `update` – Apply safe, scripted updates to `@story` annotations (e.g., when a story file is renamed).
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
- All commands are run from your project root:
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
- # Verify that annotations are valid
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 JSON report for CI pipelines
352
+ # Generate a report (ESLint output already provides this information)
319
353
  npx traceability-maint report --root . --format json
354
+ ```
320
355
 
321
- # Update references when a story file is renamed
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
- --root . \
324
- --from "stories/feature-authentication.story.md" \
325
- --to "stories/feature-auth-v2.story.md"
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
- * @returns Total number of updated @story annotations across all mappings.
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
- }[]): number;
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;
@@ -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
- * @returns Total number of updated @story annotations across all mappings.
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
- totalUpdated += (0, update_1.updateAnnotationReferences)(codebasePath, oldPath, newPath);
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 stale = (0, detect_1.detectStaleAnnotations)(root);
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 valid = (0, batch_1.verifyAnnotations)(root);
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 report = (0, report_1.generateMaintenanceReport)(root);
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
- // For now, we cannot get a per-file diff without changing the maintenance API.
115
- // We conservatively reuse generateMaintenanceReport to indicate potential impact.
116
- const beforeReport = (0, report_1.generateMaintenanceReport)(root);
117
- const potentialChanges = beforeReport ? beforeReport.split("\n").length : 0;
118
- const summary = {
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
- estimatedStaleCount: potentialChanges,
123
- };
124
- if (flags.json) {
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} @story annotation${count === 1 ? "" : "s"} from '${from}' to '${to}' under ${root}.`);
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
- const files = (0, utils_1.getAllFiles)(workspaceRoot);
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) {
@@ -83,6 +83,7 @@ export interface ParsedFlags {
83
83
  from?: string;
84
84
  to?: string;
85
85
  dryRun?: boolean;
86
+ ignorePatterns?: string[];
86
87
  }
87
88
  /**
88
89
  * Basic flag parser for maintenance CLI subcommands.
@@ -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 === 0) {
18
- return "";
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 staleAnnotations.join("\n");
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 - Update annotation references
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 @story path to search for in annotation comments.
7
- * @param newPath The replacement @story path that will replace occurrences of oldPath.
8
- * @returns The number of @story annotations that were updated across the codebase.
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): number;
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, regex, newPath, replacementCountRef) {
46
- const content = fs.readFileSync(fullPath, "utf8"); // getAllFiles already returns regular files
47
- const newContent = content.replace(regex,
48
- /**
49
- * Replacement callback to update annotation references
50
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
51
- * @req REQ-MAINT-UPDATE
52
- */
53
- (match, p1) => {
54
- replacementCountRef.count++;
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
- * Write file only if content changed
59
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
60
- * @req REQ-MAINT-UPDATE
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 - Update annotation references
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 @story path to search for in annotation comments.
72
- * @param newPath The replacement @story path that will replace occurrences of oldPath.
73
- * @returns The number of @story annotations that were updated across the codebase.
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 replacementCountRef = { count: 0 };
115
+ const refs = { count: 0, warnings: [] };
88
116
  const escapedOldPath = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
89
- const regex = new RegExp(`(@story\\s*)${escapedOldPath}`, "g");
90
- const files = (0, utils_1.getAllFiles)(codebasePath);
91
- /**
92
- * Iterate over all files and replace annotation references
93
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
94
- * @req REQ-MAINT-UPDATE
95
- * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
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, regex, newPath, replacementCountRef);
125
+ processFileForAnnotationUpdates(fullPath, { story: storyRegex, supports: supportsRegex }, newPath, refs);
105
126
  }
106
- return replacementCountRef.count;
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[];
@@ -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.24.0",
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 small maintenance API and a companion CLI, `traceability-maint`, for bulk operations on `@story` annotations. These tools are intentionally minimal and focused on stale **story** references only; requirement-level maintenance and more advanced filtering are planned but **not yet implemented**. 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.
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 after `@story`.
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
- - `number` – The count of `@story` annotations that were updated.
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
- - Only `@story` annotations are modified; `@req` annotations are never changed.
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` without modifying anything.
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
- - `number` – The total number of `@story` annotations updated across all mappings.
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 sums the counts.
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 use in scripts and CI. It is typically available via `npx traceability-maint` or as an npm script.
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
- These tools are intentionally minimal and focused on stale **story** references only; requirement-level maintenance and more advanced filtering are planned but **not yet implemented**. The CLI currently focuses on stale `@story` annotations only. It does **not** build or consume a separate index file, and it does not yet support requirement-level maintenance.
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