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.
- package/CHANGELOG.md +2 -2
- package/README.md +3 -2
- package/lib/src/maintenance/cli.js +12 -12
- package/lib/src/maintenance/detect.js +19 -19
- package/lib/src/maintenance/flags.js +111 -25
- package/lib/src/rules/helpers/require-story-core.d.ts +55 -9
- package/lib/src/rules/helpers/require-story-core.js +85 -62
- package/lib/src/rules/helpers/require-story-helpers.d.ts +27 -48
- package/lib/src/rules/helpers/require-story-helpers.js +154 -116
- package/lib/src/rules/helpers/require-story-io.js +51 -31
- package/lib/src/rules/helpers/require-story-visitors.js +47 -6
- package/lib/src/rules/helpers/valid-annotation-format-validators.js +5 -1
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +9 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +67 -20
- package/lib/src/rules/helpers/valid-annotation-utils.js +31 -31
- package/lib/src/rules/helpers/valid-story-reference-helpers.js +19 -19
- package/lib/src/rules/prefer-implements-annotation.js +29 -1
- package/lib/src/rules/require-story-annotation.js +15 -0
- package/lib/src/rules/require-test-traceability.js +1 -6
- package/lib/src/utils/annotation-checker.js +32 -8
- package/lib/src/utils/reqAnnotationDetection.js +36 -22
- package/lib/tests/cli-error-handling.test.js +1 -0
- package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
- package/lib/tests/config/eslint-config-validation.test.js +8 -0
- package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
- package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
- package/lib/tests/config/require-story-annotation-config.test.js +9 -0
- package/lib/tests/integration/cli-integration.test.js +9 -1
- package/lib/tests/maintenance/batch.test.js +1 -0
- package/lib/tests/maintenance/cli.test.js +1 -0
- package/lib/tests/maintenance/detect-isolated.test.js +1 -0
- package/lib/tests/maintenance/detect.test.js +1 -0
- package/lib/tests/maintenance/index.test.js +1 -0
- package/lib/tests/maintenance/report.test.js +1 -0
- package/lib/tests/maintenance/update-isolated.test.js +1 -0
- package/lib/tests/maintenance/update.test.js +1 -0
- package/lib/tests/perf/maintenance-cli-large-workspace.test.d.ts +1 -0
- package/lib/tests/perf/maintenance-cli-large-workspace.test.js +130 -0
- package/lib/tests/perf/maintenance-large-workspace.test.d.ts +1 -0
- package/lib/tests/perf/maintenance-large-workspace.test.js +149 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
- package/lib/tests/plugin-setup-error.test.d.ts +1 -0
- package/lib/tests/plugin-setup-error.test.js +1 -0
- package/lib/tests/plugin-setup.test.js +1 -1
- package/lib/tests/rules/auto-fix-behavior-008.test.js +39 -0
- package/lib/tests/rules/error-reporting.test.js +1 -0
- package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
- package/lib/tests/rules/require-branch-annotation.test.js +2 -0
- package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-core.autofix.test.js +10 -3
- package/lib/tests/rules/require-story-core.test.js +14 -7
- package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.js +2 -1
- package/lib/tests/rules/require-story-helpers.test.js +18 -11
- package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
- package/lib/tests/rules/valid-story-reference.test.js +2 -0
- package/lib/tests/utils/annotation-checker.test.js +2 -1
- package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
- package/lib/tests/utils/require-story-core-test-helpers.d.ts +1 -1
- package/lib/tests/utils/require-story-core-test-helpers.js +16 -16
- package/lib/tests/utils/temp-dir-helpers.js +1 -1
- package/package.json +9 -2
- package/user-docs/api-reference.md +123 -12
- package/user-docs/examples.md +41 -0
- package/user-docs/migration-guide.md +36 -3
|
@@ -133,42 +133,32 @@ function parentChainHasStory(sourceCode, node) {
|
|
|
133
133
|
return false;
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
136
|
+
* Safely compute the starting range index for fallback text scanning.
|
|
137
|
+
* Centralizes guards around sourceCode.getText and node.range structure.
|
|
138
138
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
139
|
-
* @
|
|
140
|
-
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
141
|
-
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @supports annotations as satisfying story presence in fallback checks
|
|
139
|
+
* @req REQ-ANNOTATION-REQUIRED - Centralize guards for fallback range computation
|
|
142
140
|
*/
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// @req REQ-ANNOTATION-REQUIRED - Avoid throwing when source text or range metadata cannot be read
|
|
147
|
-
if (typeof sourceCode?.getText !== "function" ||
|
|
148
|
-
!Array.isArray((node && node.range) || [])) {
|
|
149
|
-
return false;
|
|
141
|
+
function getFallbackRangeStart(sourceCode, node) {
|
|
142
|
+
if (typeof sourceCode?.getText !== "function") {
|
|
143
|
+
return null;
|
|
150
144
|
}
|
|
151
|
-
const range = node.range;
|
|
152
|
-
// Guard against malformed range values that cannot provide a numeric start index for slicing.
|
|
153
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
154
|
-
// @req REQ-ANNOTATION-REQUIRED - Validate node range structure before computing fallback window
|
|
145
|
+
const range = (node && node.range) || null;
|
|
155
146
|
if (!Array.isArray(range) || typeof range[0] !== "number") {
|
|
156
|
-
return
|
|
147
|
+
return null;
|
|
157
148
|
}
|
|
149
|
+
return range[0];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Safely slice a bounded fallback text window immediately preceding the node start index.
|
|
153
|
+
* Restricts scanning to a fixed-size window and treats IO/slicing failures as non-fatal.
|
|
154
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
155
|
+
* @req REQ-ANNOTATION-REQUIRED - Restrict fallback text scanning to a safe, fixed-size window and handle failures gracefully
|
|
156
|
+
*/
|
|
157
|
+
function getFallbackTextWindow(sourceCode, nodeStartIndex) {
|
|
158
|
+
const start = Math.max(0, nodeStartIndex - exports.FALLBACK_WINDOW);
|
|
158
159
|
try {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// @req REQ-ANNOTATION-REQUIRED - Restrict fallback text scanning to a safe, fixed-size window
|
|
162
|
-
const start = Math.max(0, range[0] - exports.FALLBACK_WINDOW);
|
|
163
|
-
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
164
|
-
// Detect any @story or @supports marker that appears within the bounded fallback window.
|
|
165
|
-
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
166
|
-
// @req REQ-ANNOTATION-REQUIRED - Recognize story annotations discovered via fallback text scanning
|
|
167
|
-
// @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Recognize @supports annotations discovered via fallback text scanning
|
|
168
|
-
if (typeof textBefore === "string" &&
|
|
169
|
-
(textBefore.includes("@story") || textBefore.includes("@supports"))) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
160
|
+
const textBefore = sourceCode.getText().slice(start, nodeStartIndex);
|
|
161
|
+
return typeof textBefore === "string" ? textBefore : null;
|
|
172
162
|
}
|
|
173
163
|
catch {
|
|
174
164
|
/*
|
|
@@ -176,6 +166,36 @@ function fallbackTextBeforeHasStory(sourceCode, node) {
|
|
|
176
166
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
177
167
|
* @req REQ-ANNOTATION-REQUIRED - Treat fallback text inspection failures as "no annotation" instead of raising
|
|
178
168
|
*/
|
|
169
|
+
return null;
|
|
179
170
|
}
|
|
180
|
-
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Detect whether the provided fallback text window contains a story marker.
|
|
174
|
+
* Recognizes both @story and @supports annotations in the inspected text.
|
|
175
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
176
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
177
|
+
* @req REQ-ANNOTATION-REQUIRED - Recognize story annotations discovered via fallback text scanning
|
|
178
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Recognize @supports annotations discovered via fallback text scanning
|
|
179
|
+
*/
|
|
180
|
+
function fallbackTextHasMarker(textBefore) {
|
|
181
|
+
if (typeof textBefore !== "string") {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return textBefore.includes("@story") || textBefore.includes("@supports");
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
|
|
188
|
+
* Also accepts @supports annotations as satisfying story presence for this rule.
|
|
189
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
190
|
+
* @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
|
|
191
|
+
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
192
|
+
* @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @supports annotations as satisfying story presence in fallback checks
|
|
193
|
+
*/
|
|
194
|
+
function fallbackTextBeforeHasStory(sourceCode, node) {
|
|
195
|
+
const nodeStartIndex = getFallbackRangeStart(sourceCode, node);
|
|
196
|
+
if (nodeStartIndex === null) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const textBefore = getFallbackTextWindow(sourceCode, nodeStartIndex);
|
|
200
|
+
return fallbackTextHasMarker(textBefore);
|
|
181
201
|
}
|
|
@@ -27,7 +27,14 @@ function buildFunctionDeclarationVisitor(context, sourceCode, options) {
|
|
|
27
27
|
if (!options.shouldProcessNode(node))
|
|
28
28
|
return;
|
|
29
29
|
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
30
|
-
(0, require_story_helpers_1.reportMissing)(context, sourceCode,
|
|
30
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, {
|
|
31
|
+
node,
|
|
32
|
+
target,
|
|
33
|
+
options: {
|
|
34
|
+
annotationTemplateOverride: options.annotationTemplate,
|
|
35
|
+
autoFixToggle: options.autoFix,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
31
38
|
}
|
|
32
39
|
return {
|
|
33
40
|
FunctionDeclaration: handleFunctionDeclaration,
|
|
@@ -55,7 +62,14 @@ function buildFunctionExpressionVisitor(context, sourceCode, options) {
|
|
|
55
62
|
if (node.parent && node.parent.type === "MethodDefinition")
|
|
56
63
|
return;
|
|
57
64
|
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
58
|
-
(0, require_story_helpers_1.reportMissing)(context, sourceCode,
|
|
65
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, {
|
|
66
|
+
node,
|
|
67
|
+
target,
|
|
68
|
+
options: {
|
|
69
|
+
annotationTemplateOverride: options.annotationTemplate,
|
|
70
|
+
autoFixToggle: options.autoFix,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
59
73
|
}
|
|
60
74
|
return {
|
|
61
75
|
FunctionExpression: handleFunctionExpression,
|
|
@@ -76,7 +90,14 @@ function buildArrowFunctionVisitor(context, sourceCode, options) {
|
|
|
76
90
|
if (!options.shouldProcessNode(node))
|
|
77
91
|
return;
|
|
78
92
|
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
79
|
-
(0, require_story_helpers_1.reportMissing)(context, sourceCode,
|
|
93
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, {
|
|
94
|
+
node,
|
|
95
|
+
target,
|
|
96
|
+
options: {
|
|
97
|
+
annotationTemplateOverride: options.annotationTemplate,
|
|
98
|
+
autoFixToggle: options.autoFix,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
80
101
|
}
|
|
81
102
|
return {
|
|
82
103
|
ArrowFunctionExpression: handleArrowFunctionExpression,
|
|
@@ -96,7 +117,14 @@ function buildTSDeclareFunctionVisitor(context, sourceCode, options) {
|
|
|
96
117
|
function handleTSDeclareFunction(node) {
|
|
97
118
|
if (!options.shouldProcessNode(node))
|
|
98
119
|
return;
|
|
99
|
-
(0, require_story_helpers_1.reportMissing)(context, sourceCode,
|
|
120
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, {
|
|
121
|
+
node,
|
|
122
|
+
target: node,
|
|
123
|
+
options: {
|
|
124
|
+
annotationTemplateOverride: options.annotationTemplate,
|
|
125
|
+
autoFixToggle: options.autoFix,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
100
128
|
}
|
|
101
129
|
return {
|
|
102
130
|
TSDeclareFunction: handleTSDeclareFunction,
|
|
@@ -117,7 +145,14 @@ function buildTSMethodSignatureVisitor(context, sourceCode, options) {
|
|
|
117
145
|
if (!options.shouldProcessNode(node))
|
|
118
146
|
return;
|
|
119
147
|
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
120
|
-
(0, require_story_helpers_1.reportMissing)(context, sourceCode,
|
|
148
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, {
|
|
149
|
+
node,
|
|
150
|
+
target,
|
|
151
|
+
options: {
|
|
152
|
+
annotationTemplateOverride: options.methodAnnotationTemplate ?? options.annotationTemplate,
|
|
153
|
+
autoFixToggle: options.autoFix,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
121
156
|
}
|
|
122
157
|
return {
|
|
123
158
|
TSMethodSignature: handleTSMethodSignature,
|
|
@@ -137,7 +172,13 @@ function buildMethodDefinitionVisitor(context, sourceCode, options) {
|
|
|
137
172
|
function handleMethodDefinition(node) {
|
|
138
173
|
if (!options.shouldProcessNode(node))
|
|
139
174
|
return;
|
|
140
|
-
(0, require_story_helpers_1.reportMethod)(context, sourceCode,
|
|
175
|
+
(0, require_story_helpers_1.reportMethod)(context, sourceCode, {
|
|
176
|
+
node,
|
|
177
|
+
options: {
|
|
178
|
+
annotationTemplateOverride: options.methodAnnotationTemplate ?? options.annotationTemplate,
|
|
179
|
+
autoFixToggle: options.autoFix,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
141
182
|
}
|
|
142
183
|
return {
|
|
143
184
|
MethodDefinition: handleMethodDefinition,
|
|
@@ -167,7 +167,11 @@ function validateStoryAnnotation(context, comment, rawValue, options) {
|
|
|
167
167
|
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md
|
|
168
168
|
// @req REQ-AUTOFIX-FORMAT - Apply suffix-only auto-fix when it yields a pattern-compliant path
|
|
169
169
|
if (fixed && pathPattern.test(fixed)) {
|
|
170
|
-
|
|
170
|
+
if (options.autoFix !== false) {
|
|
171
|
+
reportInvalidStoryFormatWithFix(context, comment, collapsed, fixed);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
reportInvalidStoryFormat(context, comment, collapsed, options);
|
|
171
175
|
return;
|
|
172
176
|
}
|
|
173
177
|
reportInvalidStoryFormat(context, comment, collapsed, options);
|
|
@@ -53,6 +53,11 @@ export interface AnnotationRuleOptions {
|
|
|
53
53
|
* Human-readable example requirement ID used in error messages.
|
|
54
54
|
*/
|
|
55
55
|
requirementIdExample?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Global toggle for auto-fix behavior in valid-annotation-format.
|
|
58
|
+
* When false, no automatic suffix-normalization fixes are applied.
|
|
59
|
+
*/
|
|
60
|
+
autoFix?: boolean;
|
|
56
61
|
}
|
|
57
62
|
/**
|
|
58
63
|
* Resolved, runtime-ready options for the rule.
|
|
@@ -62,6 +67,7 @@ export interface ResolvedAnnotationOptions {
|
|
|
62
67
|
storyExample: string;
|
|
63
68
|
reqPattern: RegExp;
|
|
64
69
|
reqExample: string;
|
|
70
|
+
autoFix: boolean;
|
|
65
71
|
}
|
|
66
72
|
export declare function getDefaultReqExample(): string;
|
|
67
73
|
export declare function getResolvedDefaults(): ResolvedAnnotationOptions;
|
|
@@ -113,6 +119,9 @@ export declare function getRuleSchema(): {
|
|
|
113
119
|
requirementIdExample: {
|
|
114
120
|
type: string;
|
|
115
121
|
};
|
|
122
|
+
autoFix: {
|
|
123
|
+
type: string;
|
|
124
|
+
};
|
|
116
125
|
};
|
|
117
126
|
additionalProperties: boolean;
|
|
118
127
|
}[];
|
|
@@ -26,6 +26,7 @@ let resolvedDefaults = {
|
|
|
26
26
|
storyExample: getDefaultStoryExample(),
|
|
27
27
|
reqPattern: getDefaultReqPattern(),
|
|
28
28
|
reqExample: getDefaultReqExample(),
|
|
29
|
+
autoFix: true,
|
|
29
30
|
};
|
|
30
31
|
/**
|
|
31
32
|
* Collected configuration errors encountered while resolving options.
|
|
@@ -104,43 +105,88 @@ function resolveExample(nestedExample, flatExample, defaultExample) {
|
|
|
104
105
|
}
|
|
105
106
|
return defaultExample;
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const nestedStoryExample = user?.story?.example;
|
|
117
|
-
const flatStoryExample = user?.storyPathExample;
|
|
118
|
-
const nestedReqPattern = user?.req?.pattern;
|
|
119
|
-
const flatReqPattern = user?.requirementIdPattern;
|
|
120
|
-
const nestedReqExample = user?.req?.example;
|
|
121
|
-
const flatReqExample = user?.requirementIdExample;
|
|
122
|
-
const storyPattern = resolvePattern({
|
|
108
|
+
function getUserOptions(rawOptions) {
|
|
109
|
+
return normalizeUserOptions(rawOptions);
|
|
110
|
+
}
|
|
111
|
+
function resolveAutoFixFlag(user) {
|
|
112
|
+
const autoFixFlag = user?.autoFix;
|
|
113
|
+
return typeof autoFixFlag === "boolean" ? autoFixFlag : true;
|
|
114
|
+
}
|
|
115
|
+
function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
|
|
116
|
+
return resolvePattern({
|
|
123
117
|
nestedPattern: nestedStoryPattern,
|
|
124
118
|
nestedFieldName: "story.pattern",
|
|
125
119
|
flatPattern: flatStoryPattern,
|
|
126
120
|
flatFieldName: "storyPathPattern",
|
|
127
121
|
defaultPattern: getDefaultStoryPattern(),
|
|
128
122
|
});
|
|
129
|
-
|
|
123
|
+
}
|
|
124
|
+
function resolveReqPattern(nestedReqPattern, flatReqPattern) {
|
|
125
|
+
return resolvePattern({
|
|
130
126
|
nestedPattern: nestedReqPattern,
|
|
131
127
|
nestedFieldName: "req.pattern",
|
|
132
128
|
flatPattern: flatReqPattern,
|
|
133
129
|
flatFieldName: "requirementIdPattern",
|
|
134
130
|
defaultPattern: getDefaultReqPattern(),
|
|
135
131
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
}
|
|
133
|
+
function resolveStoryExample(nestedStoryExample, flatStoryExample) {
|
|
134
|
+
return resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
|
|
135
|
+
}
|
|
136
|
+
function resolveReqExample(nestedReqExample, flatReqExample) {
|
|
137
|
+
return resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
|
|
138
|
+
}
|
|
139
|
+
function getStoryPatternInputs(user) {
|
|
140
|
+
return {
|
|
141
|
+
nestedStoryPattern: user?.story?.pattern,
|
|
142
|
+
flatStoryPattern: user?.storyPathPattern,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function getStoryExampleInputs(user) {
|
|
146
|
+
return {
|
|
147
|
+
nestedStoryExample: user?.story?.example,
|
|
148
|
+
flatStoryExample: user?.storyPathExample,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function getReqPatternInputs(user) {
|
|
152
|
+
return {
|
|
153
|
+
nestedReqPattern: user?.req?.pattern,
|
|
154
|
+
flatReqPattern: user?.requirementIdPattern,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function getReqExampleInputs(user) {
|
|
158
|
+
return {
|
|
159
|
+
nestedReqExample: user?.req?.example,
|
|
160
|
+
flatReqExample: user?.requirementIdExample,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function resolveOptionsInternal(user) {
|
|
164
|
+
const { nestedStoryPattern, flatStoryPattern } = getStoryPatternInputs(user);
|
|
165
|
+
const { nestedStoryExample, flatStoryExample } = getStoryExampleInputs(user);
|
|
166
|
+
const { nestedReqPattern, flatReqPattern } = getReqPatternInputs(user);
|
|
167
|
+
const { nestedReqExample, flatReqExample } = getReqExampleInputs(user);
|
|
168
|
+
const autoFix = resolveAutoFixFlag(user);
|
|
169
|
+
const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern);
|
|
170
|
+
const reqPattern = resolveReqPattern(nestedReqPattern, flatReqPattern);
|
|
171
|
+
const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample);
|
|
172
|
+
const reqExample = resolveReqExample(nestedReqExample, flatReqExample);
|
|
173
|
+
return {
|
|
139
174
|
storyPattern,
|
|
140
175
|
storyExample,
|
|
141
176
|
reqPattern,
|
|
142
177
|
reqExample,
|
|
178
|
+
autoFix,
|
|
143
179
|
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Resolve user options into concrete, validated configuration.
|
|
183
|
+
* Falls back to existing defaults when options are not provided or invalid.
|
|
184
|
+
*/
|
|
185
|
+
function resolveOptions(rawOptions) {
|
|
186
|
+
optionErrors = [];
|
|
187
|
+
const user = getUserOptions(rawOptions);
|
|
188
|
+
const resolved = resolveOptionsInternal(user);
|
|
189
|
+
resolvedDefaults = resolved;
|
|
144
190
|
return resolvedDefaults;
|
|
145
191
|
}
|
|
146
192
|
/**
|
|
@@ -171,6 +217,7 @@ function getRuleSchema() {
|
|
|
171
217
|
storyPathExample: { type: "string" },
|
|
172
218
|
requirementIdPattern: { type: "string" },
|
|
173
219
|
requirementIdExample: { type: "string" },
|
|
220
|
+
autoFix: { type: "boolean" },
|
|
174
221
|
},
|
|
175
222
|
additionalProperties: false,
|
|
176
223
|
},
|
|
@@ -39,7 +39,7 @@ exports.STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
|
|
|
39
39
|
* @req REQ-AUTOFIX-FORMAT - Provide safe, minimal automatic fixes for common format issues
|
|
40
40
|
*/
|
|
41
41
|
function collapseAnnotationValue(value) {
|
|
42
|
-
// @
|
|
42
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-MULTILINE-SUPPORT
|
|
43
43
|
return value.replace(/\s+/g, "");
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
@@ -62,42 +62,42 @@ function collapseAnnotationValue(value) {
|
|
|
62
62
|
*/
|
|
63
63
|
function getFixedStoryPath(original) {
|
|
64
64
|
// @story docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md | REQ-AUTOFIX-SAFE - Reject auto-fix when the path contains ".." traversal segments to avoid broadening the reference.
|
|
65
|
-
// @
|
|
65
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces correctness of the story identifier by rejecting paths that use unsafe traversal segments.
|
|
66
66
|
if (original.includes("..")) {
|
|
67
|
-
// @
|
|
68
|
-
// @
|
|
69
|
-
// @
|
|
67
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT
|
|
68
|
+
// @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-SAFE
|
|
69
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-AUTOFIX-SAFE
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
72
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md | REQ-AUTOFIX-FORMAT - Leave correctly formatted ".story.md" paths unchanged so diagnostics are not hidden by redundant fixes.
|
|
73
|
-
// @
|
|
73
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces correctness of the story identifier by recognizing already valid ".story.md" paths without altering them.
|
|
74
74
|
if (/\.story\.md$/.test(original)) {
|
|
75
|
-
// @
|
|
76
|
-
// @
|
|
77
|
-
// @
|
|
75
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT
|
|
76
|
+
// @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-FORMAT
|
|
77
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-AUTOFIX-FORMAT
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
80
|
// @story docs/stories/008.0-DEV-AUTO-FIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE - When ".story" is present but ".md" is missing, append only the extension without altering the base path.
|
|
81
|
-
// @
|
|
81
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces correctness of the story identifier by completing a partially correct ".story" suffix to the canonical ".story.md".
|
|
82
82
|
if (/\.story$/.test(original)) {
|
|
83
|
-
// @
|
|
84
|
-
// @
|
|
85
|
-
// @
|
|
83
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT
|
|
84
|
+
// @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE
|
|
85
|
+
// @supports docs/stories/010.2-REQ-STORY-PATH-AUTOFIX.story.md REQ-AUTOFIX-FORMAT
|
|
86
86
|
return `${original}.md`;
|
|
87
87
|
}
|
|
88
88
|
// @story docs/stories/010.2-REQ-STORY-PATH-AUTOFIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE - Normalize plain ".md" doc paths to ".story.md" while keeping the rest of the path intact.
|
|
89
|
-
// @
|
|
89
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces correctness of the story identifier by transforming generic ".md" references into canonical ".story.md" story paths.
|
|
90
90
|
if (/\.md$/.test(original)) {
|
|
91
|
-
// @
|
|
92
|
-
// @
|
|
93
|
-
// @
|
|
91
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT
|
|
92
|
+
// @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE
|
|
93
|
+
// @supports docs/stories/010.2-REQ-STORY-PATH-AUTOFIX.story.md REQ-AUTOFIX-FORMAT
|
|
94
94
|
return original.replace(/\.md$/, ".story.md");
|
|
95
95
|
}
|
|
96
96
|
// @story docs/stories/010.2-REQ-STORY-PATH-AUTOFIX.story.md | REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE REQ-AUTOFIX-SAFE - For bare paths with no extension, append ".story.md" as a canonical story reference without touching the directory.
|
|
97
|
-
// @
|
|
98
|
-
// @
|
|
99
|
-
// @
|
|
100
|
-
// @
|
|
97
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces presence and correctness of the story identifier by supplying the standard ".story.md" suffix when no extension is provided.
|
|
98
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT
|
|
99
|
+
// @supports docs/stories/008.0-DEV-AUTO-FIX.story.md REQ-AUTOFIX-FORMAT REQ-AUTOFIX-PRESERVE REQ-AUTOFIX-SAFE
|
|
100
|
+
// @supports docs/stories/010.2-REQ-STORY-PATH-AUTOFIX.story.md REQ-AUTOFIX-FORMAT
|
|
101
101
|
return `${original}.story.md`;
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
@@ -112,14 +112,14 @@ function getFixedStoryPath(original) {
|
|
|
112
112
|
function buildStoryErrorMessage(kind, value, options) {
|
|
113
113
|
const example = options.storyExample || exports.STORY_EXAMPLE_PATH;
|
|
114
114
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md | REQ-ERROR-SPECIFICITY - Use a dedicated message variant when the @story value is completely missing.
|
|
115
|
-
// @
|
|
115
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces presence of the story identifier by emitting a targeted message when the @story value is absent.
|
|
116
116
|
if (kind === "missing") {
|
|
117
|
-
// @
|
|
118
|
-
// @
|
|
117
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-ERROR-SPECIFICITY
|
|
118
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-ERROR-SPECIFICITY
|
|
119
119
|
return `Missing story path for @story annotation. Expected a path like "${example}".`;
|
|
120
120
|
}
|
|
121
|
-
// @
|
|
122
|
-
// @
|
|
121
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-ERROR-SPECIFICITY
|
|
122
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-ERROR-SPECIFICITY
|
|
123
123
|
return `Invalid story path "${value ?? ""}" for @story annotation. Expected a path like "${example}".`;
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
@@ -134,13 +134,13 @@ function buildStoryErrorMessage(kind, value, options) {
|
|
|
134
134
|
function buildReqErrorMessage(kind, value, options) {
|
|
135
135
|
const example = options.reqExample || (0, valid_annotation_options_1.getDefaultReqExample)();
|
|
136
136
|
// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md | REQ-ERROR-SPECIFICITY - Distinguish a completely missing @req from one that is present but malformed.
|
|
137
|
-
// @
|
|
137
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-REQ-FORMAT REQ-ERROR-SPECIFICITY - Enforces presence of the requirement identifier by emitting a specific message when the @req value is missing.
|
|
138
138
|
if (kind === "missing") {
|
|
139
|
-
// @
|
|
140
|
-
// @
|
|
139
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-ERROR-SPECIFICITY
|
|
140
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-ERROR-SPECIFICITY
|
|
141
141
|
return `Missing requirement ID for @req annotation. Expected an identifier like "${example}".`;
|
|
142
142
|
}
|
|
143
|
-
// @
|
|
144
|
-
// @
|
|
143
|
+
// @supports docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md REQ-ERROR-SPECIFICITY
|
|
144
|
+
// @supports docs/stories/010.1-REQ-STORY-PATH-STRICTNESS.story.md REQ-ERROR-SPECIFICITY
|
|
145
145
|
return `Invalid requirement ID "${value ?? ""}" for @req annotation. Expected an identifier like "${example}" (uppercase letters, numbers, and dashes only).`;
|
|
146
146
|
}
|
|
@@ -20,14 +20,14 @@ function analyzeCandidateBoundaries(candidates, cwd) {
|
|
|
20
20
|
let hasInProjectCandidate = false;
|
|
21
21
|
let hasOutOfProjectCandidate = false;
|
|
22
22
|
for (const candidate of candidates) {
|
|
23
|
-
// @
|
|
23
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
24
24
|
const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(candidate, cwd);
|
|
25
25
|
if (boundary.isWithinProject) {
|
|
26
|
-
// @
|
|
26
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY
|
|
27
27
|
hasInProjectCandidate = true;
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
// @
|
|
30
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY
|
|
31
31
|
hasOutOfProjectCandidate = true;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -47,12 +47,12 @@ function analyzeCandidateBoundaries(candidates, cwd) {
|
|
|
47
47
|
*/
|
|
48
48
|
function handleProjectBoundaryForExistence({ storyPath, commentNode, context, cwd, candidates, existenceResult, reportInvalidPath, }) {
|
|
49
49
|
if (candidates.length > 0) {
|
|
50
|
-
// @
|
|
51
|
-
// @
|
|
50
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
51
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
52
52
|
const { hasInProjectCandidate, hasOutOfProjectCandidate } = analyzeCandidateBoundaries(candidates, cwd);
|
|
53
53
|
if (hasOutOfProjectCandidate && !hasInProjectCandidate) {
|
|
54
|
-
// @
|
|
55
|
-
// @
|
|
54
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
55
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY
|
|
56
56
|
reportInvalidPath({ storyPath, commentNode, context });
|
|
57
57
|
return true;
|
|
58
58
|
}
|
|
@@ -60,12 +60,12 @@ function handleProjectBoundaryForExistence({ storyPath, commentNode, context, cw
|
|
|
60
60
|
if (existenceResult &&
|
|
61
61
|
existenceResult.status === "exists" &&
|
|
62
62
|
existenceResult.matchedPath) {
|
|
63
|
-
// @
|
|
64
|
-
// @
|
|
63
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
64
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY
|
|
65
65
|
const boundary = (0, storyReferenceUtils_1.enforceProjectBoundary)(existenceResult.matchedPath, cwd);
|
|
66
66
|
if (!boundary.isWithinProject) {
|
|
67
|
-
// @
|
|
68
|
-
// @
|
|
67
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
68
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY
|
|
69
69
|
reportInvalidPath({ storyPath, commentNode, context });
|
|
70
70
|
return true;
|
|
71
71
|
}
|
|
@@ -83,11 +83,11 @@ function handleProjectBoundaryForExistence({ storyPath, commentNode, context, cw
|
|
|
83
83
|
function performSecurityValidations({ storyPath, commentNode, context, cwd, allowAbsolute, reportInvalidPath, }) {
|
|
84
84
|
// Absolute path check
|
|
85
85
|
if (path_1.default.isAbsolute(storyPath)) {
|
|
86
|
-
// @
|
|
87
|
-
// @
|
|
86
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
87
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-SECURITY-VALIDATION
|
|
88
88
|
if (!allowAbsolute) {
|
|
89
|
-
// @
|
|
90
|
-
// @
|
|
89
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
90
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-SECURITY-VALIDATION
|
|
91
91
|
reportInvalidPath({ storyPath, commentNode, context });
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
@@ -97,12 +97,12 @@ function performSecurityValidations({ storyPath, commentNode, context, cwd, allo
|
|
|
97
97
|
// Path traversal check
|
|
98
98
|
const containsTraversal = storyPath.includes("..") || /\\|\//.test(storyPath);
|
|
99
99
|
if (containsTraversal) {
|
|
100
|
-
// @
|
|
101
|
-
// @
|
|
100
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
101
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-SECURITY-VALIDATION
|
|
102
102
|
const full = path_1.default.resolve(cwd, path_1.default.normalize(storyPath));
|
|
103
103
|
if (!full.startsWith(cwd + path_1.default.sep)) {
|
|
104
|
-
// @
|
|
105
|
-
// @
|
|
104
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-PROJECT-BOUNDARY REQ-SECURITY-VALIDATION
|
|
105
|
+
// @supports docs/stories/006.0-DEV-FILE-VALIDATION.story.md REQ-SECURITY-VALIDATION
|
|
106
106
|
reportInvalidPath({ storyPath, commentNode, context });
|
|
107
107
|
return false;
|
|
108
108
|
}
|
|
@@ -12,6 +12,14 @@ const MIN_STORY_TOKENS = 2;
|
|
|
12
12
|
const MIN_REQ_TOKENS = MIN_STORY_TOKENS;
|
|
13
13
|
// Length of the opening "/*" portion of a block comment prefix.
|
|
14
14
|
const COMMENT_PREFIX_LENGTH = 2;
|
|
15
|
+
/**
|
|
16
|
+
* Collect line indices and metadata for @story and @req annotations within a
|
|
17
|
+
* single block comment. This helper isolates the parsing logic used by the
|
|
18
|
+
* auto-fix path so that complex or ambiguous patterns can be detected and
|
|
19
|
+
* safely rejected.
|
|
20
|
+
*
|
|
21
|
+
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-AUTO-FIX REQ-SINGLE-STORY-FIX REQ-VALID-OUTPUT
|
|
22
|
+
*/
|
|
15
23
|
function collectStoryAndReqMetadata(comment) {
|
|
16
24
|
const rawValue = comment.value || "";
|
|
17
25
|
const rawLines = rawValue.split(/\r?\n/);
|
|
@@ -52,6 +60,13 @@ function collectStoryAndReqMetadata(comment) {
|
|
|
52
60
|
});
|
|
53
61
|
return { storyLineIndices, reqLineIndices, reqIds, storyPath };
|
|
54
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Apply the @supports replacement for simple, single-story legacy blocks,
|
|
65
|
+
* constructing a fixed comment body that preserves existing indentation and
|
|
66
|
+
* prefix formatting while removing the original @story/@req lines.
|
|
67
|
+
*
|
|
68
|
+
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-AUTO-FIX REQ-SINGLE-STORY-FIX REQ-PRESERVE-FORMAT REQ-VALID-OUTPUT
|
|
69
|
+
*/
|
|
55
70
|
function applyImplementsReplacement(context, comment, details) {
|
|
56
71
|
const { storyIdx, allIndicesToRemove, storyPath, reqIds } = details;
|
|
57
72
|
const rawValue = comment.value || "";
|
|
@@ -98,7 +113,7 @@ function applyImplementsReplacement(context, comment, details) {
|
|
|
98
113
|
* More complex patterns remain diagnostics-only with no fix to avoid
|
|
99
114
|
* producing invalid or ambiguous output.
|
|
100
115
|
*
|
|
101
|
-
* @
|
|
116
|
+
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
|
|
102
117
|
* @req REQ-AUTO-FIX - Provide safe, opt-in auto-fix for simple legacy patterns
|
|
103
118
|
* @req REQ-SINGLE-STORY-FIX - Restrict auto-fix to single-story, single-path cases
|
|
104
119
|
* @req REQ-PRESERVE-FORMAT - Preserve original JSDoc indentation and prefix formatting
|
|
@@ -126,6 +141,12 @@ function buildImplementsAutoFix(context, comment, storyPaths) {
|
|
|
126
141
|
reqIds,
|
|
127
142
|
});
|
|
128
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Analyze a block comment to detect legacy @story/@req usage, existing
|
|
146
|
+
* @supports lines, and the presence of multiple distinct @story paths.
|
|
147
|
+
*
|
|
148
|
+
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-OPTIONAL-WARNING REQ-MULTI-STORY-DETECT
|
|
149
|
+
*/
|
|
129
150
|
function analyzeComment(comment) {
|
|
130
151
|
const rawLines = (comment.value || "").split(/\r?\n/);
|
|
131
152
|
let hasStory = false;
|
|
@@ -158,6 +179,13 @@ function hasMultipleStories(storyPaths) {
|
|
|
158
179
|
// @req REQ-MULTI-STORY-DETECT - Use named threshold constant instead of a magic number
|
|
159
180
|
return storyPaths.size > MULTI_STORY_THRESHOLD;
|
|
160
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* End-to-end processing for a single block comment: classify its
|
|
184
|
+
* traceability annotations, decide whether to report recommendations only
|
|
185
|
+
* or emit an auto-fix, and surface the appropriate message ID.
|
|
186
|
+
*
|
|
187
|
+
* @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-OPTIONAL-WARNING REQ-MULTI-STORY-DETECT REQ-AUTO-FIX REQ-VALID-OUTPUT
|
|
188
|
+
*/
|
|
161
189
|
function processComment(comment, context) {
|
|
162
190
|
const { hasStory, hasReq, hasImplements, storyPaths } = analyzeComment(comment);
|
|
163
191
|
if (!hasStory || !hasReq) {
|