eslint-plugin-traceability 1.6.5 → 1.7.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.
Files changed (38) hide show
  1. package/README.md +38 -1
  2. package/lib/src/index.d.ts +28 -25
  3. package/lib/src/index.js +49 -31
  4. package/lib/src/maintenance/cli.d.ts +12 -0
  5. package/lib/src/maintenance/cli.js +279 -0
  6. package/lib/src/maintenance/detect.js +27 -12
  7. package/lib/src/maintenance/update.js +42 -34
  8. package/lib/src/maintenance/utils.js +30 -30
  9. package/lib/src/rules/helpers/require-story-io.js +51 -15
  10. package/lib/src/rules/helpers/valid-annotation-options.d.ts +118 -0
  11. package/lib/src/rules/helpers/valid-annotation-options.js +167 -0
  12. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +68 -0
  13. package/lib/src/rules/helpers/valid-annotation-utils.js +103 -0
  14. package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +67 -0
  15. package/lib/src/rules/helpers/valid-story-reference-helpers.js +92 -0
  16. package/lib/src/rules/valid-annotation-format.js +168 -180
  17. package/lib/src/rules/valid-req-reference.js +139 -29
  18. package/lib/src/rules/valid-story-reference.d.ts +7 -0
  19. package/lib/src/rules/valid-story-reference.js +38 -80
  20. package/lib/src/utils/annotation-checker.js +2 -145
  21. package/lib/src/utils/branch-annotation-helpers.js +12 -3
  22. package/lib/src/utils/reqAnnotationDetection.d.ts +6 -0
  23. package/lib/src/utils/reqAnnotationDetection.js +152 -0
  24. package/lib/tests/maintenance/cli.test.d.ts +1 -0
  25. package/lib/tests/maintenance/cli.test.js +172 -0
  26. package/lib/tests/rules/require-branch-annotation.test.js +3 -2
  27. package/lib/tests/rules/require-req-annotation.test.js +57 -68
  28. package/lib/tests/rules/require-story-annotation.test.js +13 -28
  29. package/lib/tests/rules/require-story-core-edgecases.test.js +3 -58
  30. package/lib/tests/rules/require-story-core.autofix.test.js +5 -41
  31. package/lib/tests/rules/valid-annotation-format.test.js +328 -51
  32. package/lib/tests/utils/annotation-checker.test.d.ts +23 -0
  33. package/lib/tests/utils/annotation-checker.test.js +24 -17
  34. package/lib/tests/utils/require-story-core-test-helpers.d.ts +10 -0
  35. package/lib/tests/utils/require-story-core-test-helpers.js +75 -0
  36. package/lib/tests/utils/ts-language-options.d.ts +22 -0
  37. package/lib/tests/utils/ts-language-options.js +27 -0
  38. package/package.json +12 -3
package/README.md CHANGED
@@ -8,7 +8,7 @@ Created autonomously by [voder.ai](https://voder.ai).
8
8
 
9
9
  ## Installation
10
10
 
11
- Prerequisites: Node.js >=14 and ESLint v9+.
11
+ Prerequisites: Node.js >=18.18.0 and ESLint v9+.
12
12
 
13
13
  1. Using npm
14
14
  npm install --save-dev eslint-plugin-traceability
@@ -99,6 +99,43 @@ Detailed API specification and configuration options can be found in the [API Re
99
99
 
100
100
  Practical usage examples and sample configurations are available in the [Examples](user-docs/examples.md) document.
101
101
 
102
+ ## Maintenance CLI
103
+
104
+ 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.
105
+
106
+ ### Commands
107
+
108
+ - `detect` – Scan the workspace and detect `@story` annotations that reference missing story files.
109
+ - `verify` – Verify that no stale `@story` annotations exist under the workspace root.
110
+ - `report` – Generate a human-readable or JSON report of stale story references.
111
+ - `update` – Apply safe, scripted updates to `@story` annotations (e.g., when a story file is renamed).
112
+
113
+ ### Usage
114
+
115
+ All commands are run from your project root:
116
+
117
+ ```bash
118
+ # Show help and all options
119
+ npx traceability-maint --help
120
+
121
+ # Detect stale story references
122
+ npx traceability-maint detect --root .
123
+
124
+ # Verify that annotations are valid
125
+ npx traceability-maint verify --root .
126
+
127
+ # Generate a JSON report for CI pipelines
128
+ npx traceability-maint report --root . --format json
129
+
130
+ # Update references when a story file is renamed
131
+ npx traceability-maint update \
132
+ --root . \
133
+ --from "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md" \
134
+ --to "docs/stories/003.0-DEV-FN-ANNOTATIONS.story.md"
135
+ ```
136
+
137
+ 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.
138
+
102
139
  ## Plugin Validation
103
140
 
104
141
  You can validate the plugin by running ESLint CLI with the plugin on a sample file:
@@ -5,6 +5,11 @@
5
5
  * @req REQ-ERROR-HANDLING - Gracefully handles plugin loading errors and missing dependencies
6
6
  */
7
7
  import type { Rule } from "eslint";
8
+ /**
9
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
10
+ * @req REQ-MAINTENANCE-API-EXPORT - Expose maintenance utilities alongside core plugin exports
11
+ */
12
+ import { detectStaleAnnotations, updateAnnotationReferences, batchUpdateAnnotations, verifyAnnotations, generateMaintenanceReport } from "./maintenance";
8
13
  /**
9
14
  * @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
10
15
  * @req REQ-RULE-LIST - Enumerate supported rule file names for plugin discovery
@@ -24,12 +29,7 @@ declare const configs: {
24
29
  traceability: {};
25
30
  };
26
31
  rules: {
27
- "traceability/require-story-annotation": string;
28
- "traceability/require-req-annotation": string;
29
- "traceability/require-branch-annotation": string;
30
- "traceability/valid-annotation-format": string;
31
- "traceability/valid-story-reference": string;
32
- "traceability/valid-req-reference": string;
32
+ [x: string]: "error" | "warn";
33
33
  };
34
34
  }[];
35
35
  strict: {
@@ -37,16 +37,22 @@ declare const configs: {
37
37
  traceability: {};
38
38
  };
39
39
  rules: {
40
- "traceability/require-story-annotation": string;
41
- "traceability/require-req-annotation": string;
42
- "traceability/require-branch-annotation": string;
43
- "traceability/valid-annotation-format": string;
44
- "traceability/valid-story-reference": string;
45
- "traceability/valid-req-reference": string;
40
+ [x: string]: "error" | "warn";
46
41
  };
47
42
  }[];
48
43
  };
49
- export { rules, configs };
44
+ /**
45
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
46
+ * @req REQ-MAINTENANCE-API-EXPORT - Expose maintenance utilities alongside core plugin exports
47
+ */
48
+ declare const maintenance: {
49
+ detectStaleAnnotations: typeof detectStaleAnnotations;
50
+ updateAnnotationReferences: typeof updateAnnotationReferences;
51
+ batchUpdateAnnotations: typeof batchUpdateAnnotations;
52
+ verifyAnnotations: typeof verifyAnnotations;
53
+ generateMaintenanceReport: typeof generateMaintenanceReport;
54
+ };
55
+ export { rules, configs, maintenance };
50
56
  declare const _default: {
51
57
  rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference", Rule.RuleModule>;
52
58
  configs: {
@@ -55,12 +61,7 @@ declare const _default: {
55
61
  traceability: {};
56
62
  };
57
63
  rules: {
58
- "traceability/require-story-annotation": string;
59
- "traceability/require-req-annotation": string;
60
- "traceability/require-branch-annotation": string;
61
- "traceability/valid-annotation-format": string;
62
- "traceability/valid-story-reference": string;
63
- "traceability/valid-req-reference": string;
64
+ [x: string]: "error" | "warn";
64
65
  };
65
66
  }[];
66
67
  strict: {
@@ -68,14 +69,16 @@ declare const _default: {
68
69
  traceability: {};
69
70
  };
70
71
  rules: {
71
- "traceability/require-story-annotation": string;
72
- "traceability/require-req-annotation": string;
73
- "traceability/require-branch-annotation": string;
74
- "traceability/valid-annotation-format": string;
75
- "traceability/valid-story-reference": string;
76
- "traceability/valid-req-reference": string;
72
+ [x: string]: "error" | "warn";
77
73
  };
78
74
  }[];
79
75
  };
76
+ maintenance: {
77
+ detectStaleAnnotations: typeof detectStaleAnnotations;
78
+ updateAnnotationReferences: typeof updateAnnotationReferences;
79
+ batchUpdateAnnotations: typeof batchUpdateAnnotations;
80
+ verifyAnnotations: typeof verifyAnnotations;
81
+ generateMaintenanceReport: typeof generateMaintenanceReport;
82
+ };
80
83
  };
81
84
  export default _default;
package/lib/src/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configs = exports.rules = void 0;
3
+ exports.maintenance = exports.configs = exports.rules = void 0;
4
+ /**
5
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
6
+ * @req REQ-MAINTENANCE-API-EXPORT - Expose maintenance utilities alongside core plugin exports
7
+ */
8
+ const maintenance_1 = require("./maintenance");
4
9
  /**
5
10
  * @story docs/stories/002.0-DYNAMIC-RULE-LOADING.story.md
6
11
  * @req REQ-RULE-LIST - Enumerate supported rule file names for plugin discovery
@@ -73,37 +78,50 @@ RULE_NAMES.forEach(
73
78
  * The recommended and strict configs treat missing annotations and missing references as errors,
74
79
  * while formatting issues are reported as warnings, matching the story's severity conventions.
75
80
  */
76
- const configs = {
77
- recommended: [
78
- {
79
- plugins: {
80
- traceability: {},
81
- },
82
- rules: {
83
- "traceability/require-story-annotation": "error",
84
- "traceability/require-req-annotation": "error",
85
- "traceability/require-branch-annotation": "error",
86
- "traceability/valid-annotation-format": "warn",
87
- "traceability/valid-story-reference": "error",
88
- "traceability/valid-req-reference": "error",
89
- },
81
+ const TRACEABILITY_RULE_SEVERITIES = {
82
+ "traceability/require-story-annotation": "error",
83
+ "traceability/require-req-annotation": "error",
84
+ "traceability/require-branch-annotation": "error",
85
+ "traceability/valid-annotation-format": "warn",
86
+ "traceability/valid-story-reference": "error",
87
+ "traceability/valid-req-reference": "error",
88
+ };
89
+ /**
90
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
91
+ * @req REQ-PLUGIN-STRUCTURE - Provide foundational plugin export and registration
92
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
93
+ */
94
+ function createTraceabilityFlatConfig() {
95
+ return {
96
+ plugins: {
97
+ traceability: {},
90
98
  },
91
- ],
92
- strict: [
93
- {
94
- plugins: {
95
- traceability: {},
96
- },
97
- rules: {
98
- "traceability/require-story-annotation": "error",
99
- "traceability/require-req-annotation": "error",
100
- "traceability/require-branch-annotation": "error",
101
- "traceability/valid-annotation-format": "warn",
102
- "traceability/valid-story-reference": "error",
103
- "traceability/valid-req-reference": "error",
104
- },
99
+ rules: {
100
+ ...TRACEABILITY_RULE_SEVERITIES,
105
101
  },
106
- ],
102
+ };
103
+ }
104
+ /**
105
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
106
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
107
+ * The recommended and strict configs treat missing annotations and missing references as errors,
108
+ * while formatting issues are reported as warnings, matching the story's severity conventions.
109
+ */
110
+ const configs = {
111
+ recommended: [createTraceabilityFlatConfig()],
112
+ strict: [createTraceabilityFlatConfig()],
107
113
  };
108
114
  exports.configs = configs;
109
- exports.default = { rules, configs };
115
+ /**
116
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
117
+ * @req REQ-MAINTENANCE-API-EXPORT - Expose maintenance utilities alongside core plugin exports
118
+ */
119
+ const maintenance = {
120
+ detectStaleAnnotations: maintenance_1.detectStaleAnnotations,
121
+ updateAnnotationReferences: maintenance_1.updateAnnotationReferences,
122
+ batchUpdateAnnotations: maintenance_1.batchUpdateAnnotations,
123
+ verifyAnnotations: maintenance_1.verifyAnnotations,
124
+ generateMaintenanceReport: maintenance_1.generateMaintenanceReport,
125
+ };
126
+ exports.maintenance = maintenance;
127
+ exports.default = { rules, configs, maintenance };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintenance CLI entry point.
4
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
5
+ * @req REQ-MAINT-DETECT - CLI support for detection of stale annotations
6
+ * @req REQ-MAINT-VERIFY - CLI support for verification of annotations
7
+ * @req REQ-MAINT-REPORT - CLI support for human-readable reports
8
+ * @req REQ-MAINT-UPDATE - CLI support for updating annotation references
9
+ * @req REQ-MAINT-BATCH - CLI support for batch maintenance operations
10
+ * @req REQ-MAINT-SAFE - Provide clear exit codes and avoid unsafe defaults
11
+ */
12
+ export declare function runMaintenanceCli(rawArgv: string[]): number;
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.runMaintenanceCli = runMaintenanceCli;
8
+ const path_1 = __importDefault(require("path"));
9
+ const detect_1 = require("./detect");
10
+ const batch_1 = require("./batch");
11
+ const update_1 = require("./update");
12
+ const report_1 = require("./report");
13
+ const EXIT_OK = 0;
14
+ const EXIT_STALE = 1;
15
+ const EXIT_USAGE = 2;
16
+ /**
17
+ * Extract the subcommand and its arguments from a raw argv array.
18
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
19
+ * @req REQ-MAINT-SAFE - Centralize parsing of CLI command and arguments
20
+ */
21
+ function parseCliInput(rawArgv) {
22
+ const [, , command, ...rest] = rawArgv;
23
+ return { command, args: rest };
24
+ }
25
+ /**
26
+ * Maintenance CLI entry point.
27
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
28
+ * @req REQ-MAINT-DETECT - CLI support for detection of stale annotations
29
+ * @req REQ-MAINT-VERIFY - CLI support for verification of annotations
30
+ * @req REQ-MAINT-REPORT - CLI support for human-readable reports
31
+ * @req REQ-MAINT-UPDATE - CLI support for updating annotation references
32
+ * @req REQ-MAINT-BATCH - CLI support for batch maintenance operations
33
+ * @req REQ-MAINT-SAFE - Provide clear exit codes and avoid unsafe defaults
34
+ */
35
+ function runMaintenanceCli(rawArgv) {
36
+ const { command, args } = parseCliInput(rawArgv);
37
+ if (!command || command === "-h" || command === "--help") {
38
+ printHelp();
39
+ return EXIT_OK;
40
+ }
41
+ try {
42
+ switch (command) {
43
+ case "detect":
44
+ return handleDetect(args);
45
+ case "verify":
46
+ return handleVerify(args);
47
+ case "report":
48
+ return handleReport(args);
49
+ case "update":
50
+ return handleUpdate(args);
51
+ default:
52
+ console.error(`Unknown command: ${command}`);
53
+ printHelp();
54
+ return EXIT_USAGE;
55
+ }
56
+ }
57
+ catch (error) {
58
+ // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
59
+ // @req REQ-MAINT-SAFE - Catch unexpected errors and emit concise diagnostics
60
+ const message = error instanceof Error
61
+ ? error.message
62
+ : "Unknown error in maintenance CLI";
63
+ console.error(`traceability-maint failed: ${message}`);
64
+ return EXIT_USAGE;
65
+ }
66
+ }
67
+ /**
68
+ * Initialize default flags for the maintenance CLI.
69
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
70
+ * @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
71
+ */
72
+ function createDefaultFlags() {
73
+ return {
74
+ root: process.cwd(),
75
+ json: false,
76
+ };
77
+ }
78
+ /**
79
+ * Handle a single CLI argument and update the flags accordingly.
80
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
81
+ * @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
82
+ */
83
+ function applyFlag(flags, args, index) {
84
+ const arg = args[index];
85
+ if (arg === "--root" && typeof args[index + 1] === "string") {
86
+ flags.root = path_1.default.resolve(args[index + 1]);
87
+ return index + 1;
88
+ }
89
+ if (arg === "--json") {
90
+ flags.json = true;
91
+ return index;
92
+ }
93
+ if (arg === "--format" && typeof args[index + 1] === "string") {
94
+ const value = args[index + 1];
95
+ if (value === "text" || value === "json") {
96
+ flags.format = value;
97
+ }
98
+ else {
99
+ throw new Error(`Invalid format: ${value}. Expected 'text' or 'json'.`);
100
+ }
101
+ return index + 1;
102
+ }
103
+ if (arg === "--from" && typeof args[index + 1] === "string") {
104
+ flags.from = args[index + 1];
105
+ return index + 1;
106
+ }
107
+ if (arg === "--to" && typeof args[index + 1] === "string") {
108
+ flags.to = args[index + 1];
109
+ return index + 1;
110
+ }
111
+ if (arg === "--dry-run") {
112
+ flags.dryRun = true;
113
+ return index;
114
+ }
115
+ return index;
116
+ }
117
+ /**
118
+ * Basic flag parser for maintenance CLI subcommands.
119
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
120
+ * @req REQ-MAINT-SAFE - Provide predictable, minimal argument parsing
121
+ */
122
+ function parseFlags(args) {
123
+ const flags = createDefaultFlags();
124
+ for (let i = 0; i < args.length; i += 1) {
125
+ i = applyFlag(flags, args, i);
126
+ }
127
+ return flags;
128
+ }
129
+ /**
130
+ * Handle the `detect` subcommand for stale @story annotations.
131
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
132
+ * @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
133
+ * @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
134
+ */
135
+ function handleDetect(args) {
136
+ const flags = parseFlags(args);
137
+ const root = flags.root;
138
+ const stale = (0, detect_1.detectStaleAnnotations)(root);
139
+ if (flags.json) {
140
+ // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
141
+ // @req REQ-MAINT-REPORT - JSON-friendly output for tooling integration
142
+ console.log(JSON.stringify({ root, stale }));
143
+ }
144
+ else {
145
+ if (stale.length === 0) {
146
+ console.log("No stale @story annotations found.");
147
+ }
148
+ else {
149
+ stale.forEach((story) => {
150
+ console.log(story);
151
+ });
152
+ console.log(`Found ${stale.length} stale @story annotation${stale.length === 1 ? "" : "s"}.
153
+ Run 'traceability-maint report' for a structured summary.`);
154
+ }
155
+ }
156
+ return stale.length === 0 ? EXIT_OK : EXIT_STALE;
157
+ }
158
+ /**
159
+ * Handle the `verify` subcommand to validate traceability annotations.
160
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
161
+ * @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
162
+ * @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
163
+ */
164
+ function handleVerify(args) {
165
+ const flags = parseFlags(args);
166
+ const root = flags.root;
167
+ const valid = (0, batch_1.verifyAnnotations)(root);
168
+ if (valid) {
169
+ console.log(`All traceability annotations under ${root} are valid.`);
170
+ return EXIT_OK;
171
+ }
172
+ console.log(`Stale or invalid traceability annotations detected under ${root}.\nRun 'traceability-maint detect' or 'traceability-maint report' for details.`);
173
+ return EXIT_STALE;
174
+ }
175
+ /**
176
+ * Handle the `report` subcommand to generate a maintenance report.
177
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
178
+ * @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
179
+ * @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
180
+ */
181
+ function handleReport(args) {
182
+ const flags = parseFlags(args);
183
+ const root = flags.root;
184
+ const format = flags.format ?? "text";
185
+ const report = (0, report_1.generateMaintenanceReport)(root);
186
+ if (format === "json") {
187
+ console.log(JSON.stringify({ root, report }));
188
+ }
189
+ else {
190
+ if (!report) {
191
+ console.log("No stale @story annotations found. Nothing to report.");
192
+ }
193
+ else {
194
+ console.log(`# Traceability Maintenance Report for ${root}`);
195
+ console.log("");
196
+ console.log("Stale story references:");
197
+ console.log(report);
198
+ }
199
+ }
200
+ return EXIT_OK;
201
+ }
202
+ /**
203
+ * Handle the `update` subcommand to rewrite @story annotation references.
204
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
205
+ * @req REQ-MAINT-UPDATE - CLI surface for updating annotation references
206
+ * @req REQ-MAINT-SAFE - Provide dry-run mode and explicit parameter checks
207
+ */
208
+ function handleUpdate(args) {
209
+ const flags = parseFlags(args);
210
+ const root = flags.root;
211
+ if (!flags.from || !flags.to) {
212
+ console.error("'update' requires --from <oldPath> and --to <newPath>.");
213
+ printHelp();
214
+ return EXIT_USAGE;
215
+ }
216
+ const from = flags.from;
217
+ const to = flags.to;
218
+ if (flags.dryRun) {
219
+ // For now, we cannot get a per-file diff without changing the maintenance API.
220
+ // We conservatively reuse generateMaintenanceReport to indicate potential impact.
221
+ const beforeReport = (0, report_1.generateMaintenanceReport)(root);
222
+ const potentialChanges = beforeReport ? beforeReport.split("\n").length : 0;
223
+ const summary = {
224
+ root,
225
+ from,
226
+ to,
227
+ estimatedStaleCount: potentialChanges,
228
+ };
229
+ if (flags.json) {
230
+ console.log(JSON.stringify({ mode: "dry-run", ...summary }));
231
+ }
232
+ else {
233
+ console.log("Dry run: no files were modified.");
234
+ console.log(`Would update @story annotations from '${from}' to '${to}' under ${root}.`);
235
+ console.log(`Estimated stale annotations before update: ${summary.estimatedStaleCount}.`);
236
+ }
237
+ return EXIT_OK;
238
+ }
239
+ const count = (0, update_1.updateAnnotationReferences)(root, from, to);
240
+ if (flags.json) {
241
+ console.log(JSON.stringify({ root, from, to, updated: count }));
242
+ }
243
+ else {
244
+ console.log(`Updated ${count} @story annotation${count === 1 ? "" : "s"} from '${from}' to '${to}' under ${root}.`);
245
+ }
246
+ return EXIT_OK;
247
+ }
248
+ /**
249
+ * Print CLI usage help for the maintenance tools.
250
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
251
+ * @req REQ-MAINT-SAFE - Provide discoverable CLI usage information
252
+ */
253
+ function printHelp() {
254
+ // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
255
+ // @req REQ-MAINT-SAFE - Provide discoverable CLI usage information
256
+ console.log(`traceability-maint - Traceability annotation maintenance tools
257
+
258
+ Usage:
259
+ traceability-maint <command> [options]
260
+
261
+ Commands:
262
+ detect Detect stale @story annotations
263
+ verify Verify that traceability annotations are valid
264
+ report Generate a maintenance report
265
+ update Update @story annotation references
266
+
267
+ Options:
268
+ --root <dir> Workspace root to scan (defaults to current directory)
269
+ --json Output JSON where supported
270
+ --format <text|json> Output format for 'report' (default: text)
271
+ --from <oldPath> Old story path for 'update'
272
+ --to <newPath> New story path for 'update'
273
+ --dry-run Plan changes for 'update' without modifying files
274
+ -h, --help Show this help message
275
+ `);
276
+ }
277
+ if (require.main === module) {
278
+ process.exit(runMaintenanceCli(process.argv));
279
+ }
@@ -101,6 +101,25 @@ function handleStoryMatch(storyPath, workspaceRoot, cwd, stale) {
101
101
  const storyCodebaseCandidate = path.resolve(workspaceRoot, storyPath);
102
102
  // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
103
103
  // @req REQ-MAINT-DETECT - Enforce workspaceRoot as the project boundary for resolved story paths
104
+ const inProjectCandidates = getInProjectCandidates(storyProjectCandidate, storyCodebaseCandidate, workspaceRoot);
105
+ // If both candidates are out-of-project, do not mark as stale and skip FS checks
106
+ if (inProjectCandidates.length === 0) {
107
+ return;
108
+ }
109
+ // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
110
+ // @req REQ-MAINT-DETECT - Only check existence for in-project candidates
111
+ const anyExists = anyInProjectCandidateExists(inProjectCandidates);
112
+ // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
113
+ // @req REQ-MAINT-DETECT - Mark story as stale if any in-project candidate exists conceptually but none exist on disk
114
+ if (!anyExists) {
115
+ stale.add(storyPath);
116
+ }
117
+ }
118
+ /**
119
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
120
+ * @req REQ-MAINT-DETECT - Enforce project boundary and return in-project candidates
121
+ */
122
+ function getInProjectCandidates(storyProjectCandidate, storyCodebaseCandidate, workspaceRoot) {
104
123
  let projectBoundary;
105
124
  let codebaseBoundary;
106
125
  try {
@@ -128,16 +147,12 @@ function handleStoryMatch(storyPath, workspaceRoot, cwd, stale) {
128
147
  if (codebaseBoundary.isWithinProject) {
129
148
  inProjectCandidates.push(codebaseBoundary.candidate);
130
149
  }
131
- // If both candidates are out-of-project, do not mark as stale and skip FS checks
132
- if (inProjectCandidates.length === 0) {
133
- return;
134
- }
135
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
136
- // @req REQ-MAINT-DETECT - Only check existence for in-project candidates
137
- const anyExists = inProjectCandidates.some((p) => fs.existsSync(p));
138
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
139
- // @req REQ-MAINT-DETECT - Mark story as stale if any in-project candidate exists conceptually but none exist on disk
140
- if (!anyExists) {
141
- stale.add(storyPath);
142
- }
150
+ return inProjectCandidates;
151
+ }
152
+ /**
153
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
154
+ * @req REQ-MAINT-DETECT - Check on-disk existence of in-project candidates
155
+ */
156
+ function anyInProjectCandidateExists(inProjectCandidates) {
157
+ return inProjectCandidates.some((p) => fs.existsSync(p));
143
158
  }
@@ -36,6 +36,45 @@ 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
+ * Helper to process a single file for annotation reference updates
41
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
42
+ * @req REQ-MAINT-UPDATE
43
+ */
44
+ function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef) {
45
+ const stat = fs.statSync(fullPath);
46
+ /**
47
+ * Skip non-files in iteration
48
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
49
+ * @req REQ-MAINT-UPDATE
50
+ */
51
+ /**
52
+ * Skip entries that are not regular files (e.g., directories)
53
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
54
+ * @req REQ-MAINT-UPDATE
55
+ */
56
+ if (!stat.isFile())
57
+ return;
58
+ const content = fs.readFileSync(fullPath, "utf8");
59
+ const newContent = content.replace(regex,
60
+ /**
61
+ * Replacement callback to update annotation references
62
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
63
+ * @req REQ-MAINT-UPDATE
64
+ */
65
+ (match, p1) => {
66
+ replacementCountRef.count++;
67
+ return `${p1}${newPath}`;
68
+ });
69
+ /**
70
+ * Write file only if content changed
71
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
72
+ * @req REQ-MAINT-UPDATE
73
+ */
74
+ if (newContent !== content) {
75
+ fs.writeFileSync(fullPath, newContent, "utf8");
76
+ }
77
+ }
39
78
  /**
40
79
  * Update annotation references when story files are moved or renamed
41
80
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
@@ -54,7 +93,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
54
93
  !fs.statSync(codebasePath).isDirectory()) {
55
94
  return 0;
56
95
  }
57
- let replacementCount = 0;
96
+ const replacementCountRef = { count: 0 };
58
97
  const escapedOldPath = oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
59
98
  const regex = new RegExp(`(@story\\s*)${escapedOldPath}`, "g");
60
99
  const files = (0, utils_1.getAllFiles)(codebasePath);
@@ -69,38 +108,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
69
108
  * @req REQ-MAINT-UPDATE
70
109
  */
71
110
  for (const fullPath of files) {
72
- const stat = fs.statSync(fullPath);
73
- /**
74
- * Skip non-files in iteration
75
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
76
- * @req REQ-MAINT-UPDATE
77
- */
78
- /**
79
- * Skip entries that are not regular files (e.g., directories)
80
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
81
- * @req REQ-MAINT-UPDATE
82
- */
83
- if (!stat.isFile())
84
- continue;
85
- const content = fs.readFileSync(fullPath, "utf8");
86
- const newContent = content.replace(regex,
87
- /**
88
- * Replacement callback to update annotation references
89
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
90
- * @req REQ-MAINT-UPDATE
91
- */
92
- (match, p1) => {
93
- replacementCount++;
94
- return `${p1}${newPath}`;
95
- });
96
- /**
97
- * Write file only if content changed
98
- * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
99
- * @req REQ-MAINT-UPDATE
100
- */
101
- if (newContent !== content) {
102
- fs.writeFileSync(fullPath, newContent, "utf8");
103
- }
111
+ processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef);
104
112
  }
105
- return replacementCount;
113
+ return replacementCountRef.count;
106
114
  }