eslint-plugin-traceability 1.6.1 → 1.6.3

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.
@@ -12,6 +12,12 @@ import type { Rule } from "eslint";
12
12
  declare const RULE_NAMES: readonly ["require-story-annotation", "require-req-annotation", "require-branch-annotation", "valid-annotation-format", "valid-story-reference", "valid-req-reference"];
13
13
  type RuleName = (typeof RULE_NAMES)[number];
14
14
  declare const rules: Record<RuleName, Rule.RuleModule>;
15
+ /**
16
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
17
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
18
+ * The recommended and strict configs treat missing annotations and missing references as errors,
19
+ * while formatting issues are reported as warnings, matching the story's severity conventions.
20
+ */
15
21
  declare const configs: {
16
22
  recommended: {
17
23
  plugins: {
package/lib/src/index.js CHANGED
@@ -67,6 +67,12 @@ RULE_NAMES.forEach(
67
67
  };
68
68
  }
69
69
  });
70
+ /**
71
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
72
+ * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
73
+ * The recommended and strict configs treat missing annotations and missing references as errors,
74
+ * while formatting issues are reported as warnings, matching the story's severity conventions.
75
+ */
70
76
  const configs = {
71
77
  recommended: [
72
78
  {
@@ -100,9 +100,11 @@ declare function shouldProcessNode(node: any, scope: string[], exportPriority?:
100
100
  * Report a missing @story annotation for a function-like node
101
101
  * Provides a suggestion to add the annotation.
102
102
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
103
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
103
104
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
104
105
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
105
106
  * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
107
+ * @req REQ-ERROR-SPECIFIC - Error reports must include both name and functionName in the data payload for specific function context
106
108
  * @param {Rule.RuleContext} context - ESLint rule context used to report
107
109
  * @param {any} sourceCode - ESLint sourceCode object
108
110
  * @param {any} node - AST node that is missing the annotation
@@ -112,10 +114,15 @@ declare function reportMissing(context: Rule.RuleContext, sourceCode: any, node:
112
114
  /**
113
115
  * Report a missing @story annotation for a method-like node
114
116
  * Provides a suggestion to update the method/interface with the annotation.
117
+ * The error data payload uses both name and functionName for consistent, specific error context.
115
118
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
116
119
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
120
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
117
121
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
118
122
  * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
123
+ * @req REQ-ERROR-SPECIFIC - Method error reports must include both name and functionName in the data payload for specific function context
124
+ * @req REQ-ERROR-LOCATION - Method error reports must use the method name node to anchor error location
125
+ * @req REQ-ERROR-CONTEXT - Method error reports must include functionName data for consistent error context
119
126
  * @param {Rule.RuleContext} context - ESLint rule context to report
120
127
  * @param {any} sourceCode - ESLint sourceCode object
121
128
  * @param {any} node - AST node that is missing the annotation
@@ -238,9 +238,11 @@ function shouldProcessNode(node, scope, exportPriority = "all") {
238
238
  * Report a missing @story annotation for a function-like node
239
239
  * Provides a suggestion to add the annotation.
240
240
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
241
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
241
242
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
242
243
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
243
244
  * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
245
+ * @req REQ-ERROR-SPECIFIC - Error reports must include both name and functionName in the data payload for specific function context
244
246
  * @param {Rule.RuleContext} context - ESLint rule context used to report
245
247
  * @param {any} sourceCode - ESLint sourceCode object
246
248
  * @param {any} node - AST node that is missing the annotation
@@ -260,7 +262,7 @@ function reportMissing(context, sourceCode, node, passedTarget) {
260
262
  context.report({
261
263
  node: nameNode,
262
264
  messageId: "missingStory",
263
- data: { name },
265
+ data: { name, functionName: name },
264
266
  fix: (0, require_story_core_1.createAddStoryFix)(resolvedTarget),
265
267
  suggest: [
266
268
  {
@@ -277,10 +279,15 @@ function reportMissing(context, sourceCode, node, passedTarget) {
277
279
  /**
278
280
  * Report a missing @story annotation for a method-like node
279
281
  * Provides a suggestion to update the method/interface with the annotation.
282
+ * The error data payload uses both name and functionName for consistent, specific error context.
280
283
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
281
284
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
285
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
282
286
  * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
283
287
  * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
288
+ * @req REQ-ERROR-SPECIFIC - Method error reports must include both name and functionName in the data payload for specific function context
289
+ * @req REQ-ERROR-LOCATION - Method error reports must use the method name node to anchor error location
290
+ * @req REQ-ERROR-CONTEXT - Method error reports must include functionName data for consistent error context
284
291
  * @param {Rule.RuleContext} context - ESLint rule context to report
285
292
  * @param {any} sourceCode - ESLint sourceCode object
286
293
  * @param {any} node - AST node that is missing the annotation
@@ -297,7 +304,7 @@ function reportMethod(context, sourceCode, node, passedTarget) {
297
304
  context.report({
298
305
  node: nameNode,
299
306
  messageId: "missingStory",
300
- data: { name },
307
+ data: { name, functionName: name },
301
308
  fix: (0, require_story_core_1.createMethodFix)(resolvedTarget),
302
309
  suggest: [
303
310
  {
@@ -16,7 +16,11 @@ const rule = {
16
16
  },
17
17
  fixable: "code",
18
18
  messages: {
19
- missingAnnotation: "Missing {{missing}} annotation on code branch",
19
+ /**
20
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
21
+ * @req REQ-ERROR-CONSISTENCY - Use shared branch error message convention with {{missing}} placeholder
22
+ */
23
+ missingAnnotation: "Branch is missing required annotation: {{missing}}.",
20
24
  },
21
25
  schema: [
22
26
  {
@@ -11,7 +11,19 @@ const rule = {
11
11
  recommended: "error",
12
12
  },
13
13
  messages: {
14
- missingReq: "Missing @req annotation for function '{{name}}' (REQ-ANNOTATION-REQUIRED)",
14
+ /**
15
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
16
+ * @req REQ-ERROR-CONSISTENCY - Align missing @req function error with cross-rule conventions
17
+ * @req REQ-ERROR-SPECIFIC - Provide specific function name in error message
18
+ * @req REQ-ERROR-SUGGESTION - Suggest adding a @req annotation with an example identifier
19
+ * @req REQ-ERROR-CONTEXT - Include @req format guidance in the error text
20
+ * @req REQ-ERROR-LOCATION - Report the error at the function identifier location
21
+ * @req REQ-ERROR-SEVERITY - Use ESLint severity level "error" for missing @req annotations
22
+ *
23
+ * This rule uses ESLint's message data placeholders for the function name,
24
+ * specifically the {{name}} placeholder populated via context.report.
25
+ */
26
+ missingReq: "Function '{{functionName}}' is missing a required @req annotation. Add a JSDoc or line comment with @req (for example, '@req REQ-EXAMPLE') referencing the appropriate requirement from the story file.",
15
27
  },
16
28
  schema: [
17
29
  {
@@ -16,9 +16,13 @@ import type { Rule } from "eslint";
16
16
  * ESLint rule to require @story annotations on functions/methods.
17
17
  *
18
18
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
19
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
19
20
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
20
21
  * @req REQ-ANNOTATION-REQUIRED
21
22
  * @req REQ-AUTOFIX-MISSING - This rule participates in auto-fix for missing @story annotations.
23
+ * @req REQ-ERROR-MSG-CONTENT - Error message instructs adding an explicit @story annotation that points to the implementing story file.
24
+ * @req REQ-ERROR-MSG-PLACEHOLDER - Error message retains the {{name}} placeholder while also providing functionName in the data payload for cross-rule consistency.
25
+ * @req REQ-ERROR-MSG-ACTIONABLE - Error message text is concise, imperative, and describes the required remediation.
22
26
  */
23
27
  declare const rule: Rule.RuleModule;
24
28
  export default rule;
@@ -6,9 +6,13 @@ const require_story_helpers_1 = require("./helpers/require-story-helpers");
6
6
  * ESLint rule to require @story annotations on functions/methods.
7
7
  *
8
8
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
9
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
9
10
  * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
10
11
  * @req REQ-ANNOTATION-REQUIRED
11
12
  * @req REQ-AUTOFIX-MISSING - This rule participates in auto-fix for missing @story annotations.
13
+ * @req REQ-ERROR-MSG-CONTENT - Error message instructs adding an explicit @story annotation that points to the implementing story file.
14
+ * @req REQ-ERROR-MSG-PLACEHOLDER - Error message retains the {{name}} placeholder while also providing functionName in the data payload for cross-rule consistency.
15
+ * @req REQ-ERROR-MSG-ACTIONABLE - Error message text is concise, imperative, and describes the required remediation.
12
16
  */
13
17
  const rule = {
14
18
  meta: {
@@ -29,7 +33,7 @@ const rule = {
29
33
  */
30
34
  fixable: "code",
31
35
  messages: {
32
- missingStory: "Missing @story annotation for function '{{name}}' (REQ-ANNOTATION-REQUIRED)",
36
+ missingStory: "Function '{{name}}' must have an explicit @story annotation. Add a JSDoc or line comment with @story that points to the implementing story file (for example, docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md).",
33
37
  },
34
38
  schema: [
35
39
  {
@@ -324,8 +324,20 @@ exports.default = {
324
324
  recommended: "error",
325
325
  },
326
326
  messages: {
327
- invalidStoryFormat: "{{details}}",
328
- invalidReqFormat: "{{details}}",
327
+ /**
328
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
329
+ * @req REQ-ERROR-SPECIFIC - Provide specific details about invalid @story annotation format
330
+ * @req REQ-ERROR-CONTEXT - Include human-readable details about the expected @story annotation format
331
+ * @req REQ-ERROR-CONSISTENCY - Use shared "Invalid annotation format: {{details}}." message pattern across rules
332
+ */
333
+ invalidStoryFormat: "Invalid annotation format: {{details}}.",
334
+ /**
335
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
336
+ * @req REQ-ERROR-SPECIFIC - Provide specific details about invalid @req annotation format
337
+ * @req REQ-ERROR-CONTEXT - Include human-readable details about the expected @req annotation format
338
+ * @req REQ-ERROR-CONSISTENCY - Use shared "Invalid annotation format: {{details}}." message pattern across rules
339
+ */
340
+ invalidReqFormat: "Invalid annotation format: {{details}}.",
329
341
  },
330
342
  schema: [],
331
343
  /**
@@ -170,7 +170,21 @@ exports.default = {
170
170
  recommended: "error",
171
171
  },
172
172
  messages: {
173
+ /**
174
+ * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
175
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
176
+ * @req REQ-ERROR-SPECIFIC - Provide specific diagnostics when a referenced requirement ID cannot be found in a story
177
+ * @req REQ-ERROR-CONTEXT - Include both the missing requirement ID and the story path in the message
178
+ * @req REQ-ERROR-CONSISTENCY - Use a consistent message template for requirement lookup failures
179
+ */
173
180
  reqMissing: "Requirement '{{reqId}}' not found in '{{storyPath}}'",
181
+ /**
182
+ * @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
183
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
184
+ * @req REQ-ERROR-SPECIFIC - Indicate that the story path associated with a @req annotation is invalid
185
+ * @req REQ-ERROR-CONTEXT - Include the problematic storyPath value so the developer can correct it
186
+ * @req REQ-ERROR-CONSISTENCY - Reuse the same storyPath placeholder convention used by other rules
187
+ */
174
188
  invalidPath: "Invalid story path '{{storyPath}}'",
175
189
  },
176
190
  schema: [],
@@ -36,18 +36,37 @@ function validateStoryPath(opts) {
36
36
  });
37
37
  }
38
38
  /**
39
- * Report any problems related to the existence or accessibility of the
40
- * referenced story file. Filesystem and I/O errors are surfaced with a
41
- * dedicated diagnostic that differentiates them from missing files.
39
+ * Analyze candidate paths against the project boundary, returning whether any
40
+ * are within the project and whether any are outside.
41
+ *
42
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
43
+ * @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
44
+ * @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
45
+ */
46
+ function analyzeCandidateBoundaries(candidates, cwd) {
47
+ let hasInProjectCandidate = false;
48
+ let hasOutOfProjectCandidate = false;
49
+ for (const candidate of candidates) {
50
+ const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(candidate, cwd);
51
+ if (boundary.isWithinProject) {
52
+ hasInProjectCandidate = true;
53
+ }
54
+ else {
55
+ hasOutOfProjectCandidate = true;
56
+ }
57
+ }
58
+ return { hasInProjectCandidate, hasOutOfProjectCandidate };
59
+ }
60
+ /**
61
+ * Handle existence status and report appropriate diagnostics for missing
62
+ * or filesystem-error conditions, assuming project-boundary checks have
63
+ * already been applied.
42
64
  *
43
65
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
44
66
  * @req REQ-FILE-EXISTENCE - Ensure referenced files exist
45
67
  * @req REQ-ERROR-HANDLING - Differentiate missing files from filesystem errors
46
68
  */
47
- function reportExistenceProblems(opts) {
48
- const { storyPath, commentNode, context, cwd, storyDirs } = opts;
49
- const result = (0, storyReferenceUtils_1.normalizeStoryPath)(storyPath, cwd, storyDirs);
50
- const existenceResult = result.existence;
69
+ function reportExistenceStatus(existenceResult, storyPath, commentNode, context) {
51
70
  if (!existenceResult || existenceResult.status === "exists") {
52
71
  return;
53
72
  }
@@ -81,6 +100,48 @@ function reportExistenceProblems(opts) {
81
100
  });
82
101
  }
83
102
  }
103
+ /**
104
+ * Report any problems related to the existence or accessibility of the
105
+ * referenced story file. Filesystem and I/O errors are surfaced with a
106
+ * dedicated diagnostic that differentiates them from missing files.
107
+ *
108
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
109
+ * @req REQ-FILE-EXISTENCE - Ensure referenced files exist
110
+ * @req REQ-ERROR-HANDLING - Differentiate missing files from filesystem errors
111
+ * @req REQ-PROJECT-BOUNDARY - Ensure resolved candidate paths remain within the project root
112
+ * @req REQ-CONFIGURABLE-PATHS - Respect configured storyDirectories while enforcing project boundaries
113
+ */
114
+ function reportExistenceProblems(opts) {
115
+ const { storyPath, commentNode, context, cwd, storyDirs } = opts;
116
+ const result = (0, storyReferenceUtils_1.normalizeStoryPath)(storyPath, cwd, storyDirs);
117
+ const existenceResult = result.existence;
118
+ const candidates = result.candidates || [];
119
+ if (candidates.length > 0) {
120
+ const { hasInProjectCandidate, hasOutOfProjectCandidate } = analyzeCandidateBoundaries(candidates, cwd);
121
+ if (hasOutOfProjectCandidate && !hasInProjectCandidate) {
122
+ context.report({
123
+ node: commentNode,
124
+ messageId: "invalidPath",
125
+ data: { path: storyPath },
126
+ });
127
+ return;
128
+ }
129
+ }
130
+ if (existenceResult &&
131
+ existenceResult.status === "exists" &&
132
+ existenceResult.matchedPath) {
133
+ const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(existenceResult.matchedPath, cwd);
134
+ if (!boundary.isWithinProject) {
135
+ context.report({
136
+ node: commentNode,
137
+ messageId: "invalidPath",
138
+ data: { path: storyPath },
139
+ });
140
+ return;
141
+ }
142
+ }
143
+ reportExistenceStatus(existenceResult, storyPath, commentNode, context);
144
+ }
84
145
  /**
85
146
  * Process and validate the story path for security, extension, and existence.
86
147
  * Filesystem and I/O errors are handled inside the underlying utilities
@@ -103,8 +164,10 @@ function processStoryPath(opts) {
103
164
  messageId: "invalidPath",
104
165
  data: { path: storyPath },
105
166
  });
167
+ return;
106
168
  }
107
- return;
169
+ // When absolute paths are allowed, we still enforce extension and
170
+ // project-boundary checks below via the existence phase.
108
171
  }
109
172
  // Path traversal check
110
173
  if ((0, storyReferenceUtils_1.containsPathTraversal)(storyPath)) {
@@ -180,8 +243,29 @@ exports.default = {
180
243
  recommended: "error",
181
244
  },
182
245
  messages: {
246
+ /**
247
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
248
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
249
+ * @req REQ-ERROR-SPECIFIC - Provide specific diagnostics when a referenced story file cannot be found
250
+ * @req REQ-ERROR-CONTEXT - Include the original story path in the error message for troubleshooting
251
+ * @req REQ-ERROR-CONSISTENCY - Use consistent file-related error wording and placeholders across rules
252
+ */
183
253
  fileMissing: "Story file '{{path}}' not found",
254
+ /**
255
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
256
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
257
+ * @req REQ-ERROR-SPECIFIC - Indicate that the story file extension is invalid and what is expected
258
+ * @req REQ-ERROR-CONTEXT - Include the provided path so developers can see which reference is wrong
259
+ * @req REQ-ERROR-CONSISTENCY - Reuse the same pattern of "{{path}}" placeholder across file validation messages
260
+ */
184
261
  invalidExtension: "Invalid story file extension for '{{path}}', expected '.story.md'",
262
+ /**
263
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
264
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
265
+ * @req REQ-ERROR-SPECIFIC - Explain that the story path is invalid due to absolute or unsafe traversal
266
+ * @req REQ-ERROR-CONTEXT - Surface the offending path to assist with correcting the reference
267
+ * @req REQ-ERROR-CONSISTENCY - Maintain a consistent template for invalid path diagnostics across rules
268
+ */
185
269
  invalidPath: "Invalid story path '{{path}}'",
186
270
  /**
187
271
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
@@ -202,7 +286,7 @@ exports.default = {
202
286
  ],
203
287
  },
204
288
  create(context) {
205
- const cwd = process.cwd();
289
+ const cwd = context.cwd ?? process.cwd();
206
290
  const opts = context.options[0];
207
291
  const storyDirs = opts?.storyDirectories || defaultStoryDirs;
208
292
  const allowAbsolute = opts?.allowAbsolutePaths || false;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkReqAnnotation = checkReqAnnotation;
4
4
  const require_story_utils_1 = require("../rules/helpers/require-story-utils");
5
+ const require_story_io_1 = require("../rules/helpers/require-story-io");
5
6
  /**
6
7
  * Helper to retrieve the JSDoc comment for a node.
7
8
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -42,12 +43,94 @@ function combineComments(leading, before) {
42
43
  function commentContainsReq(c) {
43
44
  return c && typeof c.value === "string" && c.value.includes("@req");
44
45
  }
46
+ /**
47
+ * Line-based helper adapted from linesBeforeHasStory to detect @req.
48
+ */
49
+ function linesBeforeHasReq(sourceCode, node) {
50
+ const lines = sourceCode && sourceCode.lines;
51
+ const startLine = node && node.loc && typeof node.loc.start?.line === "number"
52
+ ? node.loc.start.line
53
+ : null;
54
+ if (!Array.isArray(lines) || typeof startLine !== "number") {
55
+ return false;
56
+ }
57
+ const from = Math.max(0, startLine - 1 - require_story_io_1.LOOKBACK_LINES);
58
+ const to = Math.max(0, startLine - 1);
59
+ for (let i = from; i < to; i++) {
60
+ const text = lines[i];
61
+ if (typeof text === "string" && text.includes("@req")) {
62
+ return true;
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ /**
68
+ * Parent-chain helper adapted from parentChainHasStory to detect @req.
69
+ */
70
+ function parentChainHasReq(sourceCode, node) {
71
+ let p = node && node.parent;
72
+ while (p) {
73
+ const pComments = typeof sourceCode?.getCommentsBefore === "function"
74
+ ? sourceCode.getCommentsBefore(p) || []
75
+ : [];
76
+ if (Array.isArray(pComments) &&
77
+ pComments.some((c) => typeof c.value === "string" && c.value.includes("@req"))) {
78
+ return true;
79
+ }
80
+ const pLeading = p.leadingComments || [];
81
+ if (Array.isArray(pLeading) &&
82
+ pLeading.some((c) => typeof c.value === "string" && c.value.includes("@req"))) {
83
+ return true;
84
+ }
85
+ p = p.parent;
86
+ }
87
+ return false;
88
+ }
89
+ /**
90
+ * Fallback text window helper adapted from fallbackTextBeforeHasStory to detect @req.
91
+ */
92
+ function fallbackTextBeforeHasReq(sourceCode, node) {
93
+ if (typeof sourceCode?.getText !== "function" ||
94
+ !Array.isArray((node && node.range) || [])) {
95
+ return false;
96
+ }
97
+ const range = node.range;
98
+ if (!Array.isArray(range) || typeof range[0] !== "number") {
99
+ return false;
100
+ }
101
+ try {
102
+ const start = Math.max(0, range[0] - require_story_io_1.FALLBACK_WINDOW);
103
+ const textBefore = sourceCode.getText().slice(start, range[0]);
104
+ if (typeof textBefore === "string" && textBefore.includes("@req")) {
105
+ return true;
106
+ }
107
+ }
108
+ catch {
109
+ /* noop */
110
+ }
111
+ return false;
112
+ }
45
113
  /**
46
114
  * Helper to determine whether a JSDoc or any nearby comments contain a @req annotation.
47
115
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
48
116
  * @req REQ-ANNOTATION-REQ-DETECTION - Determine presence of @req annotation
49
117
  */
50
- function hasReqAnnotation(jsdoc, comments) {
118
+ function hasReqAnnotation(jsdoc, comments, context, node) {
119
+ try {
120
+ const sourceCode = context && typeof context.getSourceCode === "function"
121
+ ? context.getSourceCode()
122
+ : undefined;
123
+ if (sourceCode && node) {
124
+ if (linesBeforeHasReq(sourceCode, node) ||
125
+ parentChainHasReq(sourceCode, node) ||
126
+ fallbackTextBeforeHasReq(sourceCode, node)) {
127
+ return true;
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // Swallow detection errors and fall through to simple checks.
133
+ }
51
134
  // BRANCH @req detection on JSDoc or comments
52
135
  // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
53
136
  // @req REQ-ANNOTATION-REQ-DETECTION
@@ -103,14 +186,21 @@ function createMissingReqFix(node) {
103
186
  * @req REQ-ANNOTATION-REPORTING - Report missing @req annotation to context
104
187
  * @req REQ-ERROR-SPECIFIC - Provide specific error details including node name
105
188
  * @req REQ-ERROR-LOCATION - Include contextual location information in errors
189
+ * @req REQ-ERROR-SUGGESTION - Provide actionable suggestions or fixes where possible
190
+ * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
106
191
  */
107
192
  function reportMissing(context, node, enableFix = true) {
108
193
  const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
109
194
  const name = rawName ?? "(anonymous)";
195
+ const nameNode = (node && node.id && node.id.type === "Identifier"
196
+ ? node.id
197
+ : node && node.key && node.key.type === "Identifier"
198
+ ? node.key
199
+ : node) ?? node;
110
200
  const reportOptions = {
111
- node,
201
+ node: nameNode,
112
202
  messageId: "missingReq",
113
- data: { name },
203
+ data: { name, functionName: name },
114
204
  };
115
205
  if (enableFix) {
116
206
  reportOptions.fix = createMissingReqFix(node);
@@ -133,7 +223,7 @@ function checkReqAnnotation(context, node, options) {
133
223
  const leading = getLeadingComments(node);
134
224
  const comments = getCommentsBefore(sourceCode, node);
135
225
  const all = combineComments(leading, comments);
136
- const hasReq = hasReqAnnotation(jsdoc, all);
226
+ const hasReq = hasReqAnnotation(jsdoc, all, context, node);
137
227
  // BRANCH when a @req annotation is missing and must be reported
138
228
  // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
139
229
  // @req REQ-ANNOTATION-REQ-DETECTION
@@ -31,6 +31,34 @@ export interface StoryExistenceResult {
31
31
  matchedPath?: string;
32
32
  error?: unknown;
33
33
  }
34
+ /**
35
+ * Result of validating that a candidate path stays within the project boundary.
36
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
37
+ * @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
38
+ */
39
+ export interface ProjectBoundaryCheckResult {
40
+ candidate: string;
41
+ isWithinProject: boolean;
42
+ }
43
+ /**
44
+ * Validate that a candidate path stays within the project boundary.
45
+ * This compares the resolved candidate path against the normalized cwd
46
+ * prefix, ensuring that even when storyDirectories are misconfigured, we
47
+ * never treat files outside the project as valid story references.
48
+ *
49
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
50
+ * @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
51
+ */
52
+ export declare function enforceProjectBoundary(candidate: string, cwd: string): ProjectBoundaryCheckResult;
53
+ /**
54
+ * Internal helper to reset the filesystem existence cache. This is primarily
55
+ * intended for tests that need to run multiple scenarios with different
56
+ * mocked filesystem behavior without carrying over cached results.
57
+ *
58
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
59
+ * @req REQ-PERFORMANCE-OPTIMIZATION - Allow safe cache reset in tests to avoid stale entries
60
+ */
61
+ export declare function __resetStoryExistenceCacheForTests(): void;
34
62
  /**
35
63
  * Build candidate file paths for a given story path.
36
64
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
@@ -3,6 +3,8 @@ 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
+ exports.enforceProjectBoundary = enforceProjectBoundary;
7
+ exports.__resetStoryExistenceCacheForTests = __resetStoryExistenceCacheForTests;
6
8
  exports.buildStoryCandidates = buildStoryCandidates;
7
9
  exports.getStoryExistence = getStoryExistence;
8
10
  exports.storyExists = storyExists;
@@ -22,6 +24,36 @@ exports.isUnsafeStoryPath = isUnsafeStoryPath;
22
24
  */
23
25
  const fs_1 = __importDefault(require("fs"));
24
26
  const path_1 = __importDefault(require("path"));
27
+ /**
28
+ * Validate that a candidate path stays within the project boundary.
29
+ * This compares the resolved candidate path against the normalized cwd
30
+ * prefix, ensuring that even when storyDirectories are misconfigured, we
31
+ * never treat files outside the project as valid story references.
32
+ *
33
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
34
+ * @req REQ-PROJECT-BOUNDARY - Validate files are within project boundaries
35
+ */
36
+ function enforceProjectBoundary(candidate, cwd) {
37
+ const normalizedCwd = path_1.default.resolve(cwd);
38
+ const normalizedCandidate = path_1.default.resolve(candidate);
39
+ const isWithinProject = normalizedCandidate === normalizedCwd ||
40
+ normalizedCandidate.startsWith(normalizedCwd + path_1.default.sep);
41
+ return {
42
+ candidate: normalizedCandidate,
43
+ isWithinProject,
44
+ };
45
+ }
46
+ /**
47
+ * Internal helper to reset the filesystem existence cache. This is primarily
48
+ * intended for tests that need to run multiple scenarios with different
49
+ * mocked filesystem behavior without carrying over cached results.
50
+ *
51
+ * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
52
+ * @req REQ-PERFORMANCE-OPTIMIZATION - Allow safe cache reset in tests to avoid stale entries
53
+ */
54
+ function __resetStoryExistenceCacheForTests() {
55
+ fileExistStatusCache.clear();
56
+ }
25
57
  /**
26
58
  * Build candidate file paths for a given story path.
27
59
  * @story docs/stories/006.0-DEV-FILE-VALIDATION.story.md
@@ -39,6 +39,6 @@ describe("CLI Error Handling for Traceability Plugin (Story 001.0-DEV-PLUGIN-SET
39
39
  });
40
40
  // Expect non-zero exit and missing annotation message on stdout
41
41
  expect(result.status).not.toBe(0);
42
- expect(result.stdout).toContain("Missing @story annotation");
42
+ expect(result.stdout).toContain("Function 'foo' must have an explicit @story annotation. Add a JSDoc or line comment with @story that points to the implementing story file (for example, docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md)");
43
43
  });
44
44
  });
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  /**
37
37
  * Tests for: docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
38
38
  * @story docs/stories/001.0-DEV-PLUGIN-SETUP.story.md
39
+ * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
39
40
  * @req REQ-PLUGIN-STRUCTURE - Validate plugin default export and configs in src/index.ts
41
+ * @req REQ-ERROR-SEVERITY - Validate error severity configuration in plugin configs
40
42
  */
41
43
  const index_1 = __importStar(require("../src/index"));
42
44
  describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", () => {
@@ -69,4 +71,18 @@ describe("Plugin Default Export and Configs (Story 001.0-DEV-PLUGIN-SETUP)", ()
69
71
  const strictRules = index_1.configs.strict[0].rules;
70
72
  expect(strictRules).toEqual(index_1.configs.recommended[0].rules);
71
73
  });
74
+ it("[REQ-ERROR-SEVERITY] configs.recommended maps valid-annotation-format to warn and others to error", () => {
75
+ const recommendedRules = index_1.configs.recommended[0].rules;
76
+ expect(recommendedRules).toHaveProperty("traceability/valid-annotation-format", "warn");
77
+ expect(recommendedRules).toHaveProperty("traceability/require-story-annotation", "error");
78
+ expect(recommendedRules).toHaveProperty("traceability/require-req-annotation", "error");
79
+ expect(recommendedRules).toHaveProperty("traceability/require-branch-annotation", "error");
80
+ expect(recommendedRules).toHaveProperty("traceability/valid-story-reference", "error");
81
+ expect(recommendedRules).toHaveProperty("traceability/valid-req-reference", "error");
82
+ });
83
+ it("[REQ-ERROR-SEVERITY] configs.strict uses same severity mapping as recommended", () => {
84
+ const strictRules = index_1.configs.strict[0].rules;
85
+ const recommendedRules = index_1.configs.recommended[0].rules;
86
+ expect(strictRules).toEqual(recommendedRules);
87
+ });
72
88
  });