eslint-plugin-traceability 1.10.1 → 1.11.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 (70) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +3 -2
  3. package/lib/src/maintenance/cli.js +12 -12
  4. package/lib/src/maintenance/detect.js +19 -19
  5. package/lib/src/maintenance/flags.js +111 -25
  6. package/lib/src/rules/helpers/require-story-core.d.ts +55 -9
  7. package/lib/src/rules/helpers/require-story-core.js +85 -62
  8. package/lib/src/rules/helpers/require-story-helpers.d.ts +27 -48
  9. package/lib/src/rules/helpers/require-story-helpers.js +154 -116
  10. package/lib/src/rules/helpers/require-story-io.js +51 -31
  11. package/lib/src/rules/helpers/require-story-visitors.js +47 -6
  12. package/lib/src/rules/helpers/valid-annotation-format-validators.js +5 -1
  13. package/lib/src/rules/helpers/valid-annotation-options.d.ts +9 -0
  14. package/lib/src/rules/helpers/valid-annotation-options.js +67 -20
  15. package/lib/src/rules/helpers/valid-annotation-utils.js +31 -31
  16. package/lib/src/rules/helpers/valid-story-reference-helpers.js +19 -19
  17. package/lib/src/rules/prefer-implements-annotation.js +29 -1
  18. package/lib/src/rules/require-story-annotation.js +15 -0
  19. package/lib/src/rules/require-test-traceability.js +1 -6
  20. package/lib/src/utils/annotation-checker.js +32 -8
  21. package/lib/src/utils/reqAnnotationDetection.js +36 -22
  22. package/lib/tests/cli-error-handling.test.js +1 -0
  23. package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
  24. package/lib/tests/config/eslint-config-validation.test.js +8 -0
  25. package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
  26. package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
  27. package/lib/tests/config/require-story-annotation-config.test.js +9 -0
  28. package/lib/tests/integration/cli-integration.test.js +9 -1
  29. package/lib/tests/maintenance/batch.test.js +1 -0
  30. package/lib/tests/maintenance/cli.test.js +1 -0
  31. package/lib/tests/maintenance/detect-isolated.test.js +1 -0
  32. package/lib/tests/maintenance/detect.test.js +1 -0
  33. package/lib/tests/maintenance/index.test.js +1 -0
  34. package/lib/tests/maintenance/report.test.js +1 -0
  35. package/lib/tests/maintenance/update-isolated.test.js +1 -0
  36. package/lib/tests/maintenance/update.test.js +1 -0
  37. package/lib/tests/perf/maintenance-cli-large-workspace.test.d.ts +1 -0
  38. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +130 -0
  39. package/lib/tests/perf/maintenance-large-workspace.test.d.ts +1 -0
  40. package/lib/tests/perf/maintenance-large-workspace.test.js +149 -0
  41. package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
  42. package/lib/tests/plugin-setup-error.test.d.ts +1 -0
  43. package/lib/tests/plugin-setup-error.test.js +1 -0
  44. package/lib/tests/plugin-setup.test.js +1 -1
  45. package/lib/tests/rules/auto-fix-behavior-008.test.js +39 -0
  46. package/lib/tests/rules/error-reporting.test.js +1 -0
  47. package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
  48. package/lib/tests/rules/require-branch-annotation.test.js +2 -0
  49. package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
  50. package/lib/tests/rules/require-story-core.autofix.test.js +10 -3
  51. package/lib/tests/rules/require-story-core.test.js +14 -7
  52. package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
  53. package/lib/tests/rules/require-story-helpers-edgecases.test.js +2 -1
  54. package/lib/tests/rules/require-story-helpers.test.js +18 -11
  55. package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
  56. package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
  57. package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
  58. package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
  59. package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
  60. package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
  61. package/lib/tests/rules/valid-story-reference.test.js +2 -0
  62. package/lib/tests/utils/annotation-checker.test.js +2 -1
  63. package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
  64. package/lib/tests/utils/require-story-core-test-helpers.d.ts +1 -1
  65. package/lib/tests/utils/require-story-core-test-helpers.js +16 -16
  66. package/lib/tests/utils/temp-dir-helpers.js +1 -1
  67. package/package.json +9 -2
  68. package/user-docs/api-reference.md +123 -12
  69. package/user-docs/examples.md +41 -0
  70. package/user-docs/migration-guide.md +36 -3
@@ -45,6 +45,9 @@ const rule = {
45
45
  uniqueItems: true,
46
46
  },
47
47
  exportPriority: { type: "string", enum: require_story_helpers_1.EXPORT_PRIORITY_VALUES },
48
+ annotationTemplate: { type: "string" },
49
+ methodAnnotationTemplate: { type: "string" },
50
+ autoFix: { type: "boolean" },
48
51
  },
49
52
  additionalProperties: false,
50
53
  },
@@ -63,6 +66,15 @@ const rule = {
63
66
  const opts = (context.options && context.options[0]) || {};
64
67
  const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
65
68
  const exportPriority = opts.exportPriority || "all";
69
+ const annotationTemplate = typeof opts.annotationTemplate === "string" &&
70
+ opts.annotationTemplate.trim().length > 0
71
+ ? opts.annotationTemplate.trim()
72
+ : undefined;
73
+ const methodAnnotationTemplate = typeof opts.methodAnnotationTemplate === "string" &&
74
+ opts.methodAnnotationTemplate.trim().length > 0
75
+ ? opts.methodAnnotationTemplate.trim()
76
+ : undefined;
77
+ const autoFix = typeof opts.autoFix === "boolean" ? opts.autoFix : true;
66
78
  /**
67
79
  * Optional debug logging for troubleshooting this rule.
68
80
  * Developers can temporarily uncomment the block below to log when the rule
@@ -84,6 +96,9 @@ const rule = {
84
96
  shouldProcessNode: should,
85
97
  scope,
86
98
  exportPriority,
99
+ annotationTemplate,
100
+ methodAnnotationTemplate,
101
+ autoFix,
87
102
  });
88
103
  },
89
104
  };
@@ -37,12 +37,7 @@ const rule = {
37
37
  testFilePatterns: {
38
38
  type: "array",
39
39
  items: { type: "string" },
40
- default: [
41
- "**/tests/**/*.test.{js,ts}",
42
- "**/tests/**/*.spec.{js,ts}",
43
- "**/__tests__/**/*.{js,ts}",
44
- "**/*.{test,spec}.{js,ts}",
45
- ],
40
+ default: ["/tests/", "/test/", "/__tests__", ".test.", ".spec."],
46
41
  },
47
42
  requireDescribeStory: {
48
43
  type: "boolean",
@@ -76,7 +76,7 @@ function getFixTargetNode(node) {
76
76
  * Returned function is a proper named function so no inline arrow is used.
77
77
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
78
78
  * @req REQ-ANNOTATION-AUTOFIX - Provide autofix for missing @req annotation
79
- * @implements docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
79
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-AUTOFIX REQ-ANNOTATION-REPORTING
80
80
  */
81
81
  function createMissingReqFix(node) {
82
82
  const target = getFixTargetNode(node);
@@ -90,6 +90,34 @@ function createMissingReqFix(node) {
90
90
  return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
91
91
  };
92
92
  }
93
+ /**
94
+ * Resolve the display name used when reporting a missing @req annotation.
95
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
96
+ * @req REQ-ANNOTATION-REPORTING - Use consistent naming when reporting missing @req
97
+ * @req REQ-ERROR-SPECIFIC - Derive a specific, human-readable name for the node
98
+ */
99
+ function getReportedName(contextNode, parentNode) {
100
+ const rawName = (0, require_story_utils_1.getNodeName)(contextNode) ?? (0, require_story_utils_1.getNodeName)(parentNode);
101
+ return rawName ?? "(anonymous)";
102
+ }
103
+ /**
104
+ * Determine the AST sub-node that should be used as the location for reporting.
105
+ * Prefers Identifier nodes (id or key) over the broader function-like node.
106
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
107
+ * @req REQ-ANNOTATION-REPORTING - Report missing @req on the most relevant node
108
+ * @req REQ-ERROR-SPECIFIC - Target the identifier when available for precise errors
109
+ */
110
+ function getNameNodeForReqReport(node) {
111
+ const candidateId = node.id;
112
+ if (candidateId && candidateId.type === "Identifier") {
113
+ return candidateId;
114
+ }
115
+ const candidateKey = node.key;
116
+ if (candidateKey && candidateKey.type === "Identifier") {
117
+ return candidateKey;
118
+ }
119
+ return node;
120
+ }
93
121
  /**
94
122
  * Helper to report a missing @req annotation via the ESLint context API.
95
123
  * Uses getNodeName to provide a readable name for the node.
@@ -102,13 +130,9 @@ function createMissingReqFix(node) {
102
130
  * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
103
131
  */
104
132
  function reportMissing(context, node, enableFix = true) {
105
- const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
106
- const name = rawName ?? "(anonymous)";
107
- const nameNode = (node && node.id && node.id.type === "Identifier"
108
- ? node.id
109
- : node && node.key && node.key.type === "Identifier"
110
- ? node.key
111
- : node) ?? node;
133
+ const parentNode = node?.parent;
134
+ const name = getReportedName(node, parentNode);
135
+ const nameNode = getNameNodeForReqReport(node);
112
136
  const reportOptions = {
113
137
  node: nameNode,
114
138
  messageId: "missingReq",
@@ -138,6 +138,38 @@ function fallbackTextBeforeHasReq(sourceCode, node) {
138
138
  }
139
139
  return false;
140
140
  }
141
+ /**
142
+ * Helper to combine advanced, location-based heuristics for requirement detection.
143
+ * Uses preceding lines, parent-chain comments, and fallback text windows to find @req/@supports.
144
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
145
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
146
+ * @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
147
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
148
+ */
149
+ function hasReqInAdvancedHeuristics(sourceCode, node) {
150
+ if (!sourceCode || !node) {
151
+ return false;
152
+ }
153
+ return (linesBeforeHasReq(sourceCode, node) ||
154
+ parentChainHasReq(sourceCode, node) ||
155
+ fallbackTextBeforeHasReq(sourceCode, node));
156
+ }
157
+ /**
158
+ * Helper to check JSDoc and nearby comments for requirement annotations.
159
+ * Accepts both @req and @supports markers as evidence of requirement coverage.
160
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
161
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
162
+ * @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation in JSDoc/comments
163
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Accept @supports as requirement coverage in JSDoc/comments
164
+ */
165
+ function hasReqInJsdocOrComments(jsdoc, comments) {
166
+ if (jsdoc &&
167
+ typeof jsdoc.value === "string" &&
168
+ (jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) {
169
+ return true;
170
+ }
171
+ return comments.some(commentContainsReq);
172
+ }
141
173
  /**
142
174
  * Helper to determine whether a JSDoc or any nearby comments contain a requirement annotation.
143
175
  * Treats both @req and @supports annotations as evidence of requirement coverage.
@@ -151,30 +183,12 @@ function hasReqAnnotation(jsdoc, comments, context, node) {
151
183
  const sourceCode = context && typeof context.getSourceCode === "function"
152
184
  ? context.getSourceCode()
153
185
  : undefined;
154
- // Prefer robust, location-based heuristics when sourceCode and node are available.
155
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
156
- // @req REQ-ANNOTATION-REQ-DETECTION - Use multiple heuristics to detect @req markers around the node
157
- // @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Use multiple heuristics to detect @supports markers around the node
158
- if (sourceCode && node) {
159
- if (linesBeforeHasReq(sourceCode, node) ||
160
- parentChainHasReq(sourceCode, node) ||
161
- fallbackTextBeforeHasReq(sourceCode, node)) {
162
- return true;
163
- }
186
+ if (hasReqInAdvancedHeuristics(sourceCode, node)) {
187
+ return true;
164
188
  }
165
189
  }
166
190
  catch {
167
- // Swallow detection errors and fall through to simple checks.
168
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
169
- // @req REQ-ANNOTATION-REQ-DETECTION - Fail gracefully when advanced detection heuristics throw
191
+ // swallow and fall through to simple checks
170
192
  }
171
- // BRANCH requirement detection on JSDoc or comments, accepting both @req and @supports.
172
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
173
- // @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
174
- // @req REQ-ANNOTATION-REQ-DETECTION
175
- // @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS
176
- return ((jsdoc &&
177
- typeof jsdoc.value === "string" &&
178
- (jsdoc.value.includes("@req") || jsdoc.value.includes("@supports"))) ||
179
- comments.some(commentContainsReq));
193
+ return hasReqInJsdocOrComments(jsdoc, comments);
180
194
  }
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  * Tests for CLI error handling when plugin loading fails
8
8
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
9
9
  * @req REQ-ERROR-HANDLING - Plugin CLI should exit with error on rule load failure
10
+ * @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-ERROR-HANDLING
10
11
  */
11
12
  const child_process_1 = require("child_process");
12
13
  const path_1 = __importDefault(require("path"));
@@ -1 +1,9 @@
1
+ /**
2
+ * Tests for ESLint config rule schemas.
3
+ *
4
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
5
+ * @req REQ-RULE-OPTIONS
6
+ * @req REQ-CONFIG-VALIDATION
7
+ * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
8
+ */
1
9
  export {};
@@ -1,4 +1,12 @@
1
1
  "use strict";
2
+ /**
3
+ * Tests for ESLint config rule schemas.
4
+ *
5
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
6
+ * @req REQ-RULE-OPTIONS
7
+ * @req REQ-CONFIG-VALIDATION
8
+ * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
9
+ */
2
10
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
11
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
12
  };
@@ -36,9 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  /**
37
37
  * Tests for: docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
38
38
  * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
39
- * @req REQ-CONFIG-PRESETS - Validate flat-config presets register traceability plugin and rules
40
- * @req REQ-FLAT-CONFIG - Ensure presets work with ESLint v9 flat config
41
- * @req REQ-PROJECT-INTEGRATION - Support seamless integration via documented preset usage
39
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-CONFIG-PRESETS REQ-FLAT-CONFIG REQ-PROJECT-INTEGRATION
42
40
  */
43
41
  const use_at_your_own_risk_1 = require("eslint/use-at-your-own-risk");
44
42
  const index_1 = __importStar(require("../../src/index"));
@@ -1 +1,10 @@
1
+ /**
2
+ * Tests for the require-story-annotation rule schema configuration.
3
+ *
4
+ * Verifies that the ESLint rule options for require-story-annotation
5
+ * define the expected schema properties and constraints.
6
+ *
7
+ * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
8
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-RULE-OPTIONS
9
+ */
1
10
  export {};
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ /**
3
+ * Tests for the require-story-annotation rule schema configuration.
4
+ *
5
+ * Verifies that the ESLint rule options for require-story-annotation
6
+ * define the expected schema properties and constraints.
7
+ *
8
+ * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
9
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-RULE-OPTIONS
10
+ */
2
11
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
13
  };
@@ -3,6 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * Tests for CLI integration of the traceability plugin.
8
+ * Validates that the plugin registers correctly and enforces
9
+ * traceability-related rules when invoked via the ESLint CLI.
10
+ *
11
+ * @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE
12
+ * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
13
+ */
6
14
  /**
7
15
  * Tests for CLI integration functionality
8
16
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
@@ -10,7 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
18
  */
11
19
  const child_process_1 = require("child_process");
12
20
  const path_1 = __importDefault(require("path"));
13
- describe("[docs/stories/001.0-DEV-PLUGIN-SETUP.story.md] CLI Integration (traceability plugin)", () => {
21
+ describe("CLI Integration (Story 001.0-DEV-PLUGIN-SETUP)", () => {
14
22
  const eslintPkgDir = path_1.default.dirname(require.resolve("eslint/package.json"));
15
23
  const eslintCliPath = path_1.default.join(eslintPkgDir, "bin", "eslint.js");
16
24
  const configPath = path_1.default.resolve(__dirname, "../../eslint.config.js");
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
38
38
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
39
39
  * @req REQ-MAINT-BATCH - Perform batch updates
40
40
  * @req REQ-MAINT-VERIFY - Verify annotation references
41
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-BATCH REQ-MAINT-VERIFY
41
42
  */
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  * @req REQ-MAINT-REPORT - CLI reporting of stale annotations
12
12
  * @req REQ-MAINT-UPDATE - CLI updating of annotation references
13
13
  * @req REQ-MAINT-SAFE - Clear exit codes and non-destructive dry-run
14
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-UPDATE REQ-MAINT-SAFE
14
15
  */
15
16
  const fs_1 = __importDefault(require("fs"));
16
17
  const path_1 = __importDefault(require("path"));
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  * Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
38
38
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
39
39
  * @req REQ-MAINT-DETECT - Detect stale annotation references
40
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT
40
41
  */
41
42
  const path = __importStar(require("path"));
42
43
  const os = __importStar(require("os"));
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  * Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
8
8
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
9
9
  * @req REQ-MAINT-DETECT - Detect stale annotation references
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT
10
11
  */
11
12
  const fs_1 = __importDefault(require("fs"));
12
13
  const path_1 = __importDefault(require("path"));
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  * Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
5
5
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
6
6
  * @req REQ-MAINT-SAFE - Ensure all maintenance tools are exported correctly
7
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT
7
8
  */
8
9
  const maintenance_1 = require("../../src/maintenance");
9
10
  describe("Maintenance Tools Index Exports (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
@@ -38,6 +38,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
38
38
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
39
39
  * @req REQ-MAINT-REPORT - Generate maintenance report
40
40
  * @req REQ-MAINT-SAFE - Ensure operations are safe and reversible
41
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
41
42
  */
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  * Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
38
38
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
39
39
  * @req REQ-MAINT-UPDATE - Update annotation references
40
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
40
41
  */
41
42
  const fs = __importStar(require("fs"));
42
43
  const path = __importStar(require("path"));
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  * Tests for: docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
8
8
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
9
9
  * @req REQ-MAINT-UPDATE - Update annotation references
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
10
11
  */
11
12
  const fs_1 = __importDefault(require("fs"));
12
13
  const os_1 = __importDefault(require("os"));
@@ -0,0 +1,130 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ /**
37
+ * CLI-level performance tests for maintenance tools on large workspaces.
38
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-REPORT REQ-MAINT-SAFE
39
+ */
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const perf_hooks_1 = require("perf_hooks");
44
+ const cli_1 = require("../../src/maintenance/cli");
45
+ function createCliLargeWorkspace() {
46
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-cli-large-"));
47
+ // Create a modestly sized workspace reusing the same shape as the core perf tests,
48
+ // but with fewer files to keep end-to-end CLI timing predictable.
49
+ for (let moduleIndex = 0; moduleIndex < 5; moduleIndex += 1) {
50
+ const moduleDir = path.join(root, `module-${moduleIndex.toString().padStart(3, "0")}`);
51
+ fs.mkdirSync(moduleDir);
52
+ for (let fileIndex = 0; fileIndex < 20; fileIndex += 1) {
53
+ const filePath = path.join(moduleDir, `file-${fileIndex.toString().padStart(3, "0")}.ts`);
54
+ const validStory = "cli-valid.story.md";
55
+ const staleStory = "cli-stale.story.md";
56
+ const content = `/**
57
+ * @story ${validStory}
58
+ * @story ${staleStory}
59
+ */
60
+ export function cli_example_${moduleIndex}_${fileIndex}() {}
61
+ `;
62
+ fs.writeFileSync(filePath, content, "utf8");
63
+ }
64
+ }
65
+ // Create the valid story file so that only the stale entries are reported.
66
+ fs.writeFileSync(path.join(root, "cli-valid.story.md"), "# cli valid", "utf8");
67
+ return {
68
+ root,
69
+ cleanup: () => {
70
+ fs.rmSync(root, { recursive: true, force: true });
71
+ },
72
+ };
73
+ }
74
+ describe("Maintenance CLI on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
75
+ let workspace;
76
+ let originalCwd;
77
+ beforeAll(() => {
78
+ originalCwd = process.cwd();
79
+ workspace = createCliLargeWorkspace();
80
+ process.chdir(workspace.root);
81
+ });
82
+ afterAll(() => {
83
+ process.chdir(originalCwd);
84
+ workspace.cleanup();
85
+ });
86
+ it("[REQ-MAINT-DETECT] detect --json completes within a generous time budget and returns JSON payload", () => {
87
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
88
+ const start = perf_hooks_1.performance.now();
89
+ const exitCode = (0, cli_1.runMaintenanceCli)([
90
+ "node",
91
+ "traceability-maint",
92
+ "detect",
93
+ "--root",
94
+ workspace.root,
95
+ "--json",
96
+ ]);
97
+ const durationMs = perf_hooks_1.performance.now() - start;
98
+ expect(exitCode === 0 || exitCode === 1).toBe(true);
99
+ expect(durationMs).toBeLessThan(5000);
100
+ expect(logSpy).toHaveBeenCalledTimes(1);
101
+ const payloadRaw = String(logSpy.mock.calls[0][0]);
102
+ const payload = JSON.parse(payloadRaw);
103
+ expect(payload.root).toBe(workspace.root);
104
+ expect(Array.isArray(payload.stale)).toBe(true);
105
+ expect(payload.stale.length).toBeGreaterThan(0);
106
+ logSpy.mockRestore();
107
+ });
108
+ it("[REQ-MAINT-REPORT] report --format=json completes within a generous time budget", () => {
109
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => { });
110
+ const start = perf_hooks_1.performance.now();
111
+ const exitCode = (0, cli_1.runMaintenanceCli)([
112
+ "node",
113
+ "traceability-maint",
114
+ "report",
115
+ "--root",
116
+ workspace.root,
117
+ "--format",
118
+ "json",
119
+ ]);
120
+ const durationMs = perf_hooks_1.performance.now() - start;
121
+ expect(exitCode).toBe(0);
122
+ expect(durationMs).toBeLessThan(5000);
123
+ expect(logSpy).toHaveBeenCalledTimes(1);
124
+ const payloadRaw = String(logSpy.mock.calls[0][0]);
125
+ const payload = JSON.parse(payloadRaw);
126
+ expect(payload.root).toBe(workspace.root);
127
+ expect(typeof payload.report).toBe("string");
128
+ logSpy.mockRestore();
129
+ });
130
+ });
@@ -0,0 +1,149 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ /**
37
+ * Performance and stress tests for maintenance tools on large workspaces.
38
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-UPDATE REQ-MAINT-BATCH
39
+ */
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const perf_hooks_1 = require("perf_hooks");
44
+ const detect_1 = require("../../src/maintenance/detect");
45
+ const batch_1 = require("../../src/maintenance/batch");
46
+ const report_1 = require("../../src/maintenance/report");
47
+ const update_1 = require("../../src/maintenance/update");
48
+ /**
49
+ * Shape of the synthetic large workspace:
50
+ * - 10 modules (module-000 .. module-009)
51
+ * - 50 files per module (file-000.ts .. file-049.ts)
52
+ * - Each file includes a mix of valid and stale @story references.
53
+ */
54
+ function createLargeWorkspace() {
55
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "traceability-large-"));
56
+ // Create a pool of story files that will be considered "valid".
57
+ const validStories = [];
58
+ for (let i = 0; i < 250; i += 1) {
59
+ const storyName = `valid-story-${i.toString().padStart(4, "0")}.story.md`;
60
+ const storyPath = path.join(root, storyName);
61
+ fs.writeFileSync(storyPath, `# ${storyName}`, "utf8");
62
+ validStories.push(storyName);
63
+ }
64
+ let validIndex = 0;
65
+ let staleIndex = 0;
66
+ for (let moduleIndex = 0; moduleIndex < 10; moduleIndex += 1) {
67
+ const moduleDir = path.join(root, `module-${moduleIndex.toString().padStart(3, "0")}`);
68
+ fs.mkdirSync(moduleDir);
69
+ for (let fileIndex = 0; fileIndex < 50; fileIndex += 1) {
70
+ const filePath = path.join(moduleDir, `file-${fileIndex.toString().padStart(3, "0")}.ts`);
71
+ const validStory = validStories[validIndex % validStories.length] ??
72
+ "valid-story-0000.story.md";
73
+ validIndex += 1;
74
+ const staleStory = `stale-story-${staleIndex
75
+ .toString()
76
+ .padStart(4, "0")}.story.md`;
77
+ staleIndex += 1;
78
+ const content = `/**
79
+ * @story ${validStory}
80
+ * @story ${staleStory}
81
+ */
82
+ export function example_${moduleIndex}_${fileIndex}() {}
83
+ `;
84
+ fs.writeFileSync(filePath, content, "utf8");
85
+ }
86
+ }
87
+ return {
88
+ root,
89
+ cleanup: () => {
90
+ fs.rmSync(root, { recursive: true, force: true });
91
+ },
92
+ };
93
+ }
94
+ describe("Maintenance tools on large workspaces (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
95
+ let workspace;
96
+ beforeAll(() => {
97
+ workspace = createLargeWorkspace();
98
+ });
99
+ afterAll(() => {
100
+ workspace.cleanup();
101
+ });
102
+ it("[REQ-MAINT-DETECT] detectStaleAnnotations completes within a generous time budget", () => {
103
+ const start = perf_hooks_1.performance.now();
104
+ const stale = (0, detect_1.detectStaleAnnotations)(workspace.root);
105
+ const durationMs = perf_hooks_1.performance.now() - start;
106
+ // Sanity check: we expect at least some stale entries due to the generated stale-story-* references.
107
+ expect(stale.length).toBeGreaterThan(0);
108
+ // Guardrail: this operation should remain comfortably under ~5 seconds on CI hardware.
109
+ expect(durationMs).toBeLessThan(5000);
110
+ });
111
+ it("[REQ-MAINT-VERIFY] verifyAnnotations remains fast on large workspaces", () => {
112
+ const start = perf_hooks_1.performance.now();
113
+ const result = (0, batch_1.verifyAnnotations)(workspace.root);
114
+ const durationMs = perf_hooks_1.performance.now() - start;
115
+ // With both valid and stale references, verification should report false.
116
+ expect(result).toBe(false);
117
+ expect(durationMs).toBeLessThan(5000);
118
+ });
119
+ it("[REQ-MAINT-REPORT] generateMaintenanceReport produces output within a generous time budget", () => {
120
+ const start = perf_hooks_1.performance.now();
121
+ const report = (0, report_1.generateMaintenanceReport)(workspace.root);
122
+ const durationMs = perf_hooks_1.performance.now() - start;
123
+ expect(report).not.toBe("");
124
+ expect(durationMs).toBeLessThan(5000);
125
+ });
126
+ it("[REQ-MAINT-UPDATE] updateAnnotationReferences and batchUpdateAnnotations remain tractable", () => {
127
+ const exampleOldPath = "stale-story-0000.story.md";
128
+ const exampleNewPath = "updated-story-0000.story.md";
129
+ const singleStart = perf_hooks_1.performance.now();
130
+ const updatedCount = (0, update_1.updateAnnotationReferences)(workspace.root, exampleOldPath, exampleNewPath);
131
+ const singleDuration = perf_hooks_1.performance.now() - singleStart;
132
+ expect(updatedCount).toBeGreaterThan(0);
133
+ expect(singleDuration).toBeLessThan(5000);
134
+ const batchStart = perf_hooks_1.performance.now();
135
+ const totalUpdated = (0, batch_1.batchUpdateAnnotations)(workspace.root, [
136
+ {
137
+ oldPath: "stale-story-0001.story.md",
138
+ newPath: "updated-story-0001.story.md",
139
+ },
140
+ {
141
+ oldPath: "stale-story-0002.story.md",
142
+ newPath: "updated-story-0002.story.md",
143
+ },
144
+ ]);
145
+ const batchDuration = perf_hooks_1.performance.now() - batchStart;
146
+ expect(totalUpdated).toBeGreaterThanOrEqual(2);
147
+ expect(batchDuration).toBeLessThan(5000);
148
+ });
149
+ });
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  /**
37
37
  * Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
38
+ * @supports docs/stories/001.0-DEV-PLUGIN-SETUP.story.md REQ-PLUGIN-STRUCTURE REQ-RULE-REGISTRY REQ-CONFIG-SYSTEM
39
+ * @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SEVERITY
38
40
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
39
41
  * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
40
42
  * @req REQ-PLUGIN-STRUCTURE - Validate plugin default export and configs in src/index.ts