eslint-plugin-traceability 1.6.5 → 1.7.1

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 (48) hide show
  1. package/README.md +39 -1
  2. package/lib/src/index.d.ts +30 -27
  3. package/lib/src/index.js +51 -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-format-internal.d.ts +30 -0
  11. package/lib/src/rules/helpers/valid-annotation-format-internal.js +36 -0
  12. package/lib/src/rules/helpers/valid-annotation-options.d.ts +118 -0
  13. package/lib/src/rules/helpers/valid-annotation-options.js +167 -0
  14. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +68 -0
  15. package/lib/src/rules/helpers/valid-annotation-utils.js +103 -0
  16. package/lib/src/rules/helpers/valid-implements-utils.d.ts +75 -0
  17. package/lib/src/rules/helpers/valid-implements-utils.js +149 -0
  18. package/lib/src/rules/helpers/valid-story-reference-helpers.d.ts +67 -0
  19. package/lib/src/rules/helpers/valid-story-reference-helpers.js +92 -0
  20. package/lib/src/rules/prefer-implements-annotation.d.ts +39 -0
  21. package/lib/src/rules/prefer-implements-annotation.js +276 -0
  22. package/lib/src/rules/valid-annotation-format.js +255 -208
  23. package/lib/src/rules/valid-req-reference.js +210 -29
  24. package/lib/src/rules/valid-story-reference.d.ts +7 -0
  25. package/lib/src/rules/valid-story-reference.js +38 -80
  26. package/lib/src/utils/annotation-checker.js +2 -145
  27. package/lib/src/utils/branch-annotation-helpers.js +12 -3
  28. package/lib/src/utils/reqAnnotationDetection.d.ts +6 -0
  29. package/lib/src/utils/reqAnnotationDetection.js +152 -0
  30. package/lib/tests/maintenance/cli.test.d.ts +1 -0
  31. package/lib/tests/maintenance/cli.test.js +172 -0
  32. package/lib/tests/plugin-default-export-and-configs.test.js +3 -0
  33. package/lib/tests/rules/prefer-implements-annotation.test.d.ts +1 -0
  34. package/lib/tests/rules/prefer-implements-annotation.test.js +84 -0
  35. package/lib/tests/rules/require-branch-annotation.test.js +3 -2
  36. package/lib/tests/rules/require-req-annotation.test.js +57 -68
  37. package/lib/tests/rules/require-story-annotation.test.js +13 -28
  38. package/lib/tests/rules/require-story-core-edgecases.test.js +3 -58
  39. package/lib/tests/rules/require-story-core.autofix.test.js +5 -41
  40. package/lib/tests/rules/valid-annotation-format.test.js +395 -40
  41. package/lib/tests/rules/valid-req-reference.test.js +34 -0
  42. package/lib/tests/utils/annotation-checker.test.d.ts +23 -0
  43. package/lib/tests/utils/annotation-checker.test.js +24 -17
  44. package/lib/tests/utils/require-story-core-test-helpers.d.ts +10 -0
  45. package/lib/tests/utils/require-story-core-test-helpers.js +75 -0
  46. package/lib/tests/utils/ts-language-options.d.ts +22 -0
  47. package/lib/tests/utils/ts-language-options.js +27 -0
  48. 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
@@ -54,6 +54,7 @@ module.exports = [
54
54
  - `traceability/valid-annotation-format` Enforces correct format of traceability annotations. ([Documentation](docs/rules/valid-annotation-format.md))
55
55
  - `traceability/valid-story-reference` Validates that `@story` references point to existing story files. ([Documentation](docs/rules/valid-story-reference.md))
56
56
  - `traceability/valid-req-reference` Validates that `@req` references point to existing requirement IDs. ([Documentation](docs/rules/valid-req-reference.md))
57
+ - `traceability/prefer-implements-annotation` Recommends migration from legacy `@story`/`@req` annotations to `@implements` (disabled by default). ([Documentation](docs/rules/prefer-implements-annotation.md))
57
58
 
58
59
  Configuration options: For detailed per-rule options (such as scopes, branch types, and story directory settings), see the individual rule docs in `docs/rules/` and the consolidated [API Reference](user-docs/api-reference.md).
59
60
 
@@ -99,6 +100,43 @@ Detailed API specification and configuration options can be found in the [API Re
99
100
 
100
101
  Practical usage examples and sample configurations are available in the [Examples](user-docs/examples.md) document.
101
102
 
103
+ ## Maintenance CLI
104
+
105
+ 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.
106
+
107
+ ### Commands
108
+
109
+ - `detect` – Scan the workspace and detect `@story` annotations that reference missing story files.
110
+ - `verify` – Verify that no stale `@story` annotations exist under the workspace root.
111
+ - `report` – Generate a human-readable or JSON report of stale story references.
112
+ - `update` – Apply safe, scripted updates to `@story` annotations (e.g., when a story file is renamed).
113
+
114
+ ### Usage
115
+
116
+ All commands are run from your project root:
117
+
118
+ ```bash
119
+ # Show help and all options
120
+ npx traceability-maint --help
121
+
122
+ # Detect stale story references
123
+ npx traceability-maint detect --root .
124
+
125
+ # Verify that annotations are valid
126
+ npx traceability-maint verify --root .
127
+
128
+ # Generate a JSON report for CI pipelines
129
+ npx traceability-maint report --root . --format json
130
+
131
+ # Update references when a story file is renamed
132
+ npx traceability-maint update \
133
+ --root . \
134
+ --from "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md" \
135
+ --to "docs/stories/003.0-DEV-FN-ANNOTATIONS.story.md"
136
+ ```
137
+
138
+ 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.
139
+
102
140
  ## Plugin Validation
103
141
 
104
142
  You can validate the plugin by running ESLint CLI with the plugin on a sample file:
@@ -5,11 +5,16 @@
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
11
16
  */
12
- declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference"];
17
+ declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference", "prefer-implements-annotation"];
13
18
  type RuleName = (typeof RULE_NAMES)[number];
14
19
  declare const rules: Record<RuleName, Rule.RuleModule>;
15
20
  /**
@@ -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,30 +37,31 @@ 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
- rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference", Rule.RuleModule>;
57
+ rules: Record<"require-story-annotation" | "require-req-annotation" | "require-branch-annotation" | "valid-annotation-format" | "valid-story-reference" | "valid-req-reference" | "prefer-implements-annotation", Rule.RuleModule>;
52
58
  configs: {
53
59
  recommended: {
54
60
  plugins: {
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
@@ -12,6 +17,7 @@ const RULE_NAMES = [
12
17
  "valid-annotation-format",
13
18
  "valid-story-reference",
14
19
  "valid-req-reference",
20
+ "prefer-implements-annotation",
15
21
  ];
16
22
  const rules = {};
17
23
  exports.rules = rules;
@@ -73,37 +79,51 @@ RULE_NAMES.forEach(
73
79
  * The recommended and strict configs treat missing annotations and missing references as errors,
74
80
  * while formatting issues are reported as warnings, matching the story's severity conventions.
75
81
  */
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
- },
82
+ const TRACEABILITY_RULE_SEVERITIES = {
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
+ "traceability/prefer-implements-annotation": "warn",
90
+ };
91
+ /**
92
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
93
+ * @req REQ-PLUGIN-STRUCTURE - Provide foundational plugin export and registration
94
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
95
+ */
96
+ function createTraceabilityFlatConfig() {
97
+ return {
98
+ plugins: {
99
+ traceability: {},
90
100
  },
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
- },
101
+ rules: {
102
+ ...TRACEABILITY_RULE_SEVERITIES,
105
103
  },
106
- ],
104
+ };
105
+ }
106
+ /**
107
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
108
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
109
+ * The recommended and strict configs treat missing annotations and missing references as errors,
110
+ * while formatting issues are reported as warnings, matching the story's severity conventions.
111
+ */
112
+ const configs = {
113
+ recommended: [createTraceabilityFlatConfig()],
114
+ strict: [createTraceabilityFlatConfig()],
107
115
  };
108
116
  exports.configs = configs;
109
- exports.default = { rules, configs };
117
+ /**
118
+ * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
119
+ * @req REQ-MAINTENANCE-API-EXPORT - Expose maintenance utilities alongside core plugin exports
120
+ */
121
+ const maintenance = {
122
+ detectStaleAnnotations: maintenance_1.detectStaleAnnotations,
123
+ updateAnnotationReferences: maintenance_1.updateAnnotationReferences,
124
+ batchUpdateAnnotations: maintenance_1.batchUpdateAnnotations,
125
+ verifyAnnotations: maintenance_1.verifyAnnotations,
126
+ generateMaintenanceReport: maintenance_1.generateMaintenanceReport,
127
+ };
128
+ exports.maintenance = maintenance;
129
+ 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
  }