eslint-plugin-traceability 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +107 -11
  3. package/lib/src/index.js +53 -33
  4. package/lib/src/maintenance/commands.d.ts +4 -0
  5. package/lib/src/maintenance/commands.js +4 -0
  6. package/lib/src/maintenance/index.d.ts +1 -0
  7. package/lib/src/maintenance/index.js +1 -0
  8. package/lib/src/maintenance/report.js +2 -2
  9. package/lib/src/maintenance/update.js +4 -2
  10. package/lib/src/rules/helpers/test-callback-exclusion.d.ts +5 -1
  11. package/lib/src/rules/helpers/test-callback-exclusion.js +2 -11
  12. package/lib/src/rules/helpers/valid-annotation-format-validators.js +8 -2
  13. package/lib/src/rules/no-redundant-annotation.js +4 -0
  14. package/lib/src/rules/prefer-implements-annotation.js +25 -20
  15. package/lib/src/rules/require-branch-annotation.js +16 -0
  16. package/lib/src/rules/valid-annotation-format.js +62 -42
  17. package/lib/src/utils/branch-annotation-helpers.d.ts +8 -1
  18. package/lib/src/utils/branch-annotation-helpers.js +2 -1
  19. package/lib/src/utils/branch-annotation-report-helpers.d.ts +1 -0
  20. package/lib/src/utils/branch-annotation-report-helpers.js +40 -11
  21. package/lib/tests/integration/no-redundant-annotation.integration.test.js +31 -0
  22. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.d.ts +1 -0
  23. package/lib/tests/integration/require-traceability-test-callbacks.integration.test.js +148 -0
  24. package/lib/tests/maintenance/detect-isolated.test.js +22 -14
  25. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +145 -64
  26. package/lib/tests/perf/maintenance-large-workspace.test.js +65 -46
  27. package/lib/tests/rules/no-redundant-annotation.test.js +15 -0
  28. package/lib/tests/rules/require-branch-annotation.test.js +18 -0
  29. package/lib/tests/utils/{annotation-checker-branches.test.d.ts → annotation-checker-autofix-behavior.test.d.ts} +1 -1
  30. package/lib/tests/utils/{annotation-checker-branches.test.js → annotation-checker-autofix-behavior.test.js} +2 -2
  31. package/package.json +2 -2
  32. package/user-docs/api-reference.md +6 -1
  33. package/user-docs/examples.md +32 -0
  34. package/user-docs/migration-guide.md +35 -1
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- # [1.17.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.16.1...v1.17.0) (2025-12-09)
1
+ # [1.18.0](https://github.com/voder-ai/eslint-plugin-traceability/compare/v1.17.1...v1.18.0) (2025-12-18)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * allow configuring additional excluded test helper callbacks ([d266197](https://github.com/voder-ai/eslint-plugin-traceability/commit/d26619721b1826fe97d01a63647f07505e35c846))
6
+ * add annotationPlacement option for branch annotations ([9cf4189](https://github.com/voder-ai/eslint-plugin-traceability/commit/9cf41897cdbc962eb41f304847ba8a911987d0fd))
7
7
 
8
8
  # Changelog
9
9
 
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # eslint-plugin-traceability
2
2
 
3
- A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.
3
+ An ESLint plugin that creates searchable verification indices in your code, enabling systematic validation of requirement-to-code mapping.
4
+
5
+ **The Core Value:** Transforms an impossible question ("Does code exist somewhere that should support requirement X?") into a tractable question ("Does this annotated code actually support the requirement it claims to?").
6
+
7
+ The plugin requires `@supports` annotations at key verification points (functions and control flow branches), creating direct checkpoints where each annotation is a verifiable claim you can search, find, and verify locally without needing to understand the broader codebase context.
4
8
 
5
9
  ## Attribution
6
10
 
@@ -89,21 +93,89 @@ export default [
89
93
 
90
94
  The plugin exposes several rules. For **new configurations**, the unified function-level rule and `@supports` annotations are the canonical choice; the `@story` and `@req` forms remain available primarily for backward compatibility and gradual migration.
91
95
 
92
- - `traceability/require-traceability` **Unified function-level traceability rule.** Ensures that in-scope functions and methods have both story coverage and requirement coverage. It accepts either `@supports` (preferred for new code) or legacy `@story` / `@req` annotations and is enabled by default in the plugin's `recommended` and `strict` presets.
93
- - `traceability/require-story-annotation` – Legacy function-level rule key that focuses on the **story** side of function-level traceability. It is kept for backward compatibility and is wired to the same underlying engine as `traceability/require-traceability`, so existing configurations that refer to this rule continue to work. New configurations should normally rely on `traceability/require-traceability` instead of enabling this rule directly.
94
- - `traceability/require-req-annotation` – Legacy function-level rule key that focuses on the **requirement** side of function-level traceability. Like `traceability/require-story-annotation`, it is retained for backward compatibility and conceptually composes the same checks exposed by `traceability/require-traceability`. New configurations can usually rely on the unified rule alone unless you have specific reasons to tune the legacy keys separately.
95
- - `traceability/require-branch-annotation` – Enforces presence of branch annotations on significant control-flow branches (if/else, switch cases, loops, try/catch). Branch annotations can use a single `@supports` line (preferred) or the older `@story`/`@req` pair for backward compatibility. (See the rule documentation in the plugin's user guide.)
96
- - `traceability/valid-annotation-format` – Enforces correct format of traceability annotations, including `@supports` (preferred), `@story`, and `@req`. (See the rule documentation in the plugin's user guide.)
97
- - `traceability/valid-story-reference` – Validates that story references (whether written via `@story` or embedded in `@supports`) point to existing story files. (See the rule documentation in the plugin's user guide.)
98
- - `traceability/valid-req-reference` – Validates that requirement identifiers (whether written via `@req` or embedded in `@supports`) point to existing requirement IDs in your story files. (See the rule documentation in the plugin's user guide.)
99
- - `traceability/require-test-traceability` – Enforces traceability conventions in test files by requiring file-level `@supports` annotations, story references in `describe` blocks, and `[REQ-...]` prefixes in `it`/`test` names. (See the rule documentation in the plugin's user guide.)
100
- - `traceability/no-redundant-annotation` – Detects and optionally removes redundant traceability annotations on simple leaf statements that are already covered by an enclosing annotated scope. It is enabled at severity `warn` in both the `recommended` and `strict` presets by default; consumers can override its severity (including promoting it to `error`) or disable it explicitly in their ESLint configuration if they prefer.
101
- - `traceability/prefer-supports-annotation` – Optional migration helper that recommends converting legacy single-story `@story`/`@req` JSDoc blocks and inline comments into the newer `@supports` format. It is disabled by default and must be explicitly enabled. The legacy rule name `traceability/prefer-implements-annotation` remains available as a deprecated alias. (See the rule documentation in the plugin's user guide.)
96
+ #### Core Verification Index Rules
97
+
98
+ - `traceability/require-traceability` – **Unified function-level verification rule.** Ensures every function has a verification checkpoint. Without this, functions could exist without explaining their purpose, breaking verification completeness. Accepts either `@supports` (preferred for new code) or legacy `@story` / `@req` annotations and is enabled by default in the plugin's `recommended` and `strict` presets.
99
+
100
+ - `traceability/require-story-annotation` – Legacy function-level rule key that focuses on the **story** side of function-level verification. It is kept for backward compatibility and is wired to the same underlying engine as `traceability/require-traceability`. New configurations should normally rely on `traceability/require-traceability` instead.
101
+
102
+ - `traceability/require-req-annotation` – Legacy function-level rule key that focuses on the **requirement** side of function-level verification. Like `traceability/require-story-annotation`, it is retained for backward compatibility. New configurations can usually rely on the unified rule alone unless you have specific reasons to tune the legacy keys separately.
103
+
104
+ - `traceability/require-branch-annotation` – **Creates verification checkpoints at every branch** (if/else/switch/try/catch). This enables local verification - you can check each branch independently without understanding the entire function. Without branch annotations, you must read entire functions to find branch logic. With branch annotations, you can verify each branch by searching for the requirement ID. Branch annotations can use `@supports` (preferred) or the older `@story`/`@req` pair for backward compatibility.
105
+
106
+ - `traceability/valid-annotation-format` – **Ensures annotations are searchable** with consistent patterns. Verification workflows depend on being able to search for requirement IDs reliably. Enforces correct format of traceability annotations, including `@supports` (preferred), `@story`, and `@req`.
107
+
108
+ - `traceability/valid-story-reference` – **Validates story file references** to ensure verification checkpoints point to real documentation. Validates that story references (whether written via `@story` or embedded in `@supports`) point to existing story files.
109
+
110
+ - `traceability/valid-req-reference` – **Validates requirement identifiers** to ensure verification checkpoints reference valid requirements. Validates that requirement identifiers (whether written via `@req` or embedded in `@supports`) point to existing requirement IDs in your story files.
111
+
112
+ - `traceability/require-test-traceability` – **Enforces verification conventions in test files** by requiring file-level `@supports` annotations, story references in `describe` blocks, and `[REQ-...]` prefixes in `it`/`test` names. This ensures tests are traceable back to requirements for comprehensive verification coverage.
113
+
114
+ #### Quality Rules
115
+
116
+ - `traceability/no-redundant-annotation` – **Removes annotations on simple statements** already covered by enclosing scope. These don't create useful verification checkpoints (no branches to verify), so removing them reduces noise without hurting verifiability. It is enabled at severity `warn` in both the `recommended` and `strict` presets by default; consumers can override its severity or disable it explicitly.
117
+
118
+ - `traceability/prefer-supports-annotation` – **Optional migration helper** that recommends converting legacy single-story `@story`/`@req` JSDoc blocks and inline comments into the newer `@supports` format for better verification trails. It is disabled by default and must be explicitly enabled. The legacy rule name `traceability/prefer-implements-annotation` remains available as a deprecated alias.
102
119
 
103
120
  Configuration options: For detailed per-rule options (such as scopes, branch types, and story directory settings), see the individual rule docs in the plugin's user guide and the consolidated [API Reference](user-docs/api-reference.md).
104
121
 
105
122
  For development and contribution guidelines, see the contribution guide in the repository.
106
123
 
124
+ ## How It Works
125
+
126
+ ### The Verification Workflow
127
+
128
+ Traceability annotations enable systematic requirement verification through a simple three-step process:
129
+
130
+ 1. **Search:** `grep -r "REQ-AUTH-VALIDATION" src/`
131
+ 2. **Find:** Get a list of every location claiming to support that requirement
132
+ 3. **Verify:** For each result, read the annotation and adjacent code to confirm they match
133
+
134
+ ### Why Annotations at Every Branch
135
+
136
+ Each annotation creates a **local verification checkpoint**:
137
+
138
+ - ✅ **Searchable** - Direct search finds all implementation claims
139
+ - ✅ **Local** - Annotation is adjacent to the code being verified
140
+ - ✅ **No context needed** - Verify the claim without understanding control flow
141
+ - ✅ **Parallelizable** - Multiple reviewers work independently
142
+ - ✅ **Complete coverage** - No code can exist without explaining its purpose
143
+
144
+ This enables you to verify requirements systematically rather than trying to remember which code should implement what.
145
+
146
+ ### Example: Verifying a Switch Statement
147
+
148
+ ```javascript
149
+ switch (severity) {
150
+ // @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
151
+ case "low":
152
+ logLevel = "info";
153
+ break;
154
+ // @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
155
+ case "moderate":
156
+ logLevel = "warn";
157
+ break;
158
+ // @supports docs/stories/logging.md REQ-SEVERITY-LEVELS
159
+ case "high":
160
+ logLevel = "error";
161
+ break;
162
+ }
163
+ ```
164
+
165
+ **Verification process:**
166
+
167
+ - Search finds 3 annotations
168
+ - Verify each: "Does this case handle REQ-SEVERITY-LEVELS correctly?"
169
+ - Time: ~30 seconds per case, ~90 seconds total
170
+ - Can be split across team members
171
+
172
+ **Without explicit annotations:**
173
+
174
+ - Must read entire switch to understand what it does
175
+ - Must remember which requirement you're checking
176
+ - Cannot split the work effectively
177
+ - Risk missing cases or misunderstanding intent
178
+
107
179
  ## Quick Start
108
180
 
109
181
  1. Create a flat ESLint config file (`eslint.config.js`):
@@ -143,6 +215,30 @@ function initAuth() {
143
215
  npx eslint "src/**/*.js"
144
216
  ```
145
217
 
218
+ ## Common Misconceptions
219
+
220
+ ### "The annotations are redundant"
221
+
222
+ **Reality:** The apparent redundancy is intentional indexing. Each annotation is a separate verification checkpoint. When the same requirement appears in multiple branches, each annotation creates an independent checkpoint that can be verified in ~30 seconds without understanding the broader code structure.
223
+
224
+ Removing "duplicate" annotations would break the verification workflow by forcing reviewers to trace through code structure instead of verifying local claims.
225
+
226
+ ### "This only works for small codebases"
227
+
228
+ **Reality:** The plugin is specifically designed for codebases where manual review is impractical. Small codebases could use manual review; larger ones need systematic, searchable verification indices to make requirement validation tractable.
229
+
230
+ ### "This is just documentation"
231
+
232
+ **Reality:** These are verification indices, not documentation. The goal is searchability and local verification, not explaining code. Documentation aims to reduce redundancy; verification indices intentionally create checkpoints for independent validation.
233
+
234
+ ### "Inheritance would reduce boilerplate"
235
+
236
+ **Reality:** Inheritance would break verification tractability. When you search for a requirement, you need to find every place that implements it - not find a parent annotation and manually trace what inherits from it. Direct, explicit annotations at each implementation point enable grep-based verification.
237
+
238
+ ## Verification Workflow Guide
239
+
240
+ For detailed verification workflows, examples, and best practices, see the [Verification Workflow Guide](docs/verification-workflow-guide.md).
241
+
146
242
  ## API Reference
147
243
 
148
244
  Detailed API specification and configuration options can be found in the [API Reference](user-docs/api-reference.md).
package/lib/src/index.js CHANGED
@@ -86,61 +86,77 @@ RULE_NAMES.forEach(
86
86
  * and diagnostics).
87
87
  *
88
88
  * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED REQ-CONFIGURABLE-SCOPE REQ-EXPORT-PRIORITY
89
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE
89
90
  */
90
- {
91
+ function createAliasRuleMeta(unifiedRule, legacyRule) {
92
+ if (!legacyRule) {
93
+ return null;
94
+ }
95
+ const baseMeta = (unifiedRule.meta ?? {});
96
+ const legacyMeta = (legacyRule.meta ?? {});
97
+ return {
98
+ ...baseMeta,
99
+ ...legacyMeta,
100
+ docs: {
101
+ ...(baseMeta.docs ?? {}),
102
+ ...(legacyMeta.docs ?? {}),
103
+ },
104
+ messages: {
105
+ ...(baseMeta.messages ?? {}),
106
+ ...(legacyMeta.messages ?? {}),
107
+ },
108
+ schema: legacyMeta.schema ??
109
+ baseMeta.schema ??
110
+ [],
111
+ hasSuggestions: legacyMeta.hasSuggestions ??
112
+ baseMeta.hasSuggestions,
113
+ fixable: legacyMeta.fixable ??
114
+ baseMeta.fixable,
115
+ deprecated: legacyMeta.deprecated ??
116
+ baseMeta.deprecated,
117
+ replacedBy: legacyMeta.replacedBy ??
118
+ baseMeta.replacedBy,
119
+ type: legacyMeta.type ??
120
+ baseMeta.type ??
121
+ "problem",
122
+ };
123
+ }
124
+ /**
125
+ * Wire up the unified `require-traceability` rule and its legacy alias rules
126
+ * so that they share the same implementation while preserving legacy metadata.
127
+ *
128
+ * @supports docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md REQ-ANNOTATION-REQUIRED REQ-EXPORT-PRIORITY
129
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-UNIFIED-ALIAS-ENGINE
130
+ */
131
+ function wireUnifiedFunctionAnnotationAliases() {
91
132
  const unifiedRule = rules["require-traceability"];
92
133
  const legacyStoryRule = rules["require-story-annotation"];
93
134
  const legacyReqRule = rules["require-req-annotation"];
94
135
  if (unifiedRule) {
95
136
  const createAliasRule = (legacyRule) => {
96
- if (!legacyRule) {
137
+ const mergedMeta = createAliasRuleMeta(unifiedRule, legacyRule);
138
+ if (!mergedMeta) {
97
139
  return unifiedRule;
98
140
  }
99
- const baseMeta = (unifiedRule.meta ?? {});
100
- const legacyMeta = (legacyRule.meta ?? {});
101
- const mergedMeta = {
102
- ...baseMeta,
103
- ...legacyMeta,
104
- docs: {
105
- ...(baseMeta.docs ?? {}),
106
- ...(legacyMeta.docs ?? {}),
107
- },
108
- messages: {
109
- ...(baseMeta.messages ?? {}),
110
- ...(legacyMeta.messages ?? {}),
111
- },
112
- schema: legacyMeta.schema ??
113
- baseMeta.schema ??
114
- [],
115
- hasSuggestions: legacyMeta.hasSuggestions ??
116
- baseMeta.hasSuggestions,
117
- fixable: legacyMeta.fixable ??
118
- baseMeta.fixable,
119
- deprecated: legacyMeta.deprecated ??
120
- baseMeta.deprecated,
121
- replacedBy: legacyMeta.replacedBy ??
122
- baseMeta.replacedBy,
123
- type: legacyMeta.type ??
124
- baseMeta.type ??
125
- "problem",
126
- };
127
- const aliasRule = {
141
+ return {
128
142
  ...unifiedRule,
129
143
  meta: mergedMeta,
130
144
  create: unifiedRule.create,
131
145
  };
132
- return aliasRule;
133
146
  };
134
147
  rules["require-story-annotation"] = createAliasRule(legacyStoryRule);
135
148
  rules["require-req-annotation"] = createAliasRule(legacyReqRule);
136
149
  }
137
150
  }
151
+ wireUnifiedFunctionAnnotationAliases();
138
152
  /**
139
153
  * @supports docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md REQ-RULE-NAME
140
154
  * Wire up traceability/prefer-supports-annotation as the primary rule name and
141
155
  * traceability/prefer-implements-annotation as its deprecated alias.
156
+ *
157
+ * @supports docs/stories/010.4-DEV-UNIFIED-FUNCTION-RULE-AND-ALIASES.story.md REQ-MIGRATION-RULE-NAMING
142
158
  */
143
- {
159
+ function wirePreferSupportsAlias() {
144
160
  const implementsRule = rules["prefer-implements-annotation"];
145
161
  if (implementsRule) {
146
162
  const originalMeta = implementsRule.meta ?? {};
@@ -163,6 +179,7 @@ RULE_NAMES.forEach(
163
179
  }
164
180
  }
165
181
  }
182
+ wirePreferSupportsAlias();
166
183
  /**
167
184
  * Plugin metadata used by ESLint for debugging and caching.
168
185
  *
@@ -226,6 +243,9 @@ const TRACEABILITY_RULE_SEVERITIES = {
226
243
  * @req REQ-ERROR-SEVERITY - Map rule types to appropriate ESLint severity levels (errors vs warnings)
227
244
  * @story docs/stories/002.0-DEV-ESLINT-CONFIG.story.md
228
245
  * @req REQ-CONFIG-PRESETS - Provide flat-config presets that self-register the plugin and core rules
246
+ *
247
+ * @supports docs/stories/007.0-DEV-ERROR-REPORTING.story.md REQ-ERROR-SEVERITY
248
+ * @supports docs/stories/002.0-DEV-ESLINT-CONFIG.story.md REQ-CONFIG-PRESETS
229
249
  */
230
250
  function createTraceabilityFlatConfig() {
231
251
  return {
@@ -7,6 +7,7 @@ export declare const EXIT_USAGE = 2;
7
7
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
8
8
  * @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
9
9
  * @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
10
11
  */
11
12
  export declare function handleDetect(normalized: NormalizedCliArgs): number;
12
13
  /**
@@ -14,6 +15,7 @@ export declare function handleDetect(normalized: NormalizedCliArgs): number;
14
15
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
15
16
  * @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
16
17
  * @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
18
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
17
19
  */
18
20
  export declare function handleVerify(normalized: NormalizedCliArgs): number;
19
21
  /**
@@ -21,6 +23,7 @@ export declare function handleVerify(normalized: NormalizedCliArgs): number;
21
23
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
22
24
  * @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
23
25
  * @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
26
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
24
27
  */
25
28
  export declare function handleReport(normalized: NormalizedCliArgs): number;
26
29
  /**
@@ -28,5 +31,6 @@ export declare function handleReport(normalized: NormalizedCliArgs): number;
28
31
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
29
32
  * @req REQ-MAINT-UPDATE - CLI surface for updating annotation references
30
33
  * @req REQ-MAINT-SAFE - Provide dry-run mode and explicit parameter checks
34
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE REQ-MAINT-SAFE
31
35
  */
32
36
  export declare function handleUpdate(normalized: NormalizedCliArgs): number;
@@ -28,6 +28,7 @@ exports.EXIT_USAGE = 2;
28
28
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
29
29
  * @req REQ-MAINT-DETECT - CLI surface for detection of stale annotations
30
30
  * @req REQ-MAINT-SAFE - Return specific exit codes for stale vs clean states
31
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-SAFE
31
32
  */
32
33
  function handleDetect(normalized) {
33
34
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -54,6 +55,7 @@ Run 'traceability-maint report' for a structured summary.`);
54
55
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
55
56
  * @req REQ-MAINT-VERIFY - CLI surface for verification of annotations
56
57
  * @req REQ-MAINT-SAFE - Return distinct exit codes for verification failures
58
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-VERIFY REQ-MAINT-SAFE
57
59
  */
58
60
  function handleVerify(normalized) {
59
61
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -71,6 +73,7 @@ function handleVerify(normalized) {
71
73
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
72
74
  * @req REQ-MAINT-REPORT - CLI surface for human-readable maintenance reports
73
75
  * @req REQ-MAINT-SAFE - Support machine-readable formats for safe automation
76
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT REQ-MAINT-SAFE
74
77
  */
75
78
  function handleReport(normalized) {
76
79
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -96,6 +99,7 @@ function handleReport(normalized) {
96
99
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
97
100
  * @req REQ-MAINT-UPDATE - CLI surface for updating annotation references
98
101
  * @req REQ-MAINT-SAFE - Provide dry-run mode and explicit parameter checks
102
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE REQ-MAINT-SAFE
99
103
  */
100
104
  function handleUpdate(normalized) {
101
105
  const flags = (0, flags_1.parseFlags)(normalized);
@@ -7,6 +7,7 @@
7
7
  * @req REQ-MAINT-VERIFY
8
8
  * @req REQ-MAINT-REPORT
9
9
  * @req REQ-MAINT-SAFE
10
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-SAFE
10
11
  */
11
12
  export { detectStaleAnnotations } from "./detect";
12
13
  export { updateAnnotationReferences } from "./update";
@@ -10,6 +10,7 @@ exports.generateMaintenanceReport = exports.verifyAnnotations = exports.batchUpd
10
10
  * @req REQ-MAINT-VERIFY
11
11
  * @req REQ-MAINT-REPORT
12
12
  * @req REQ-MAINT-SAFE
13
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-DETECT REQ-MAINT-UPDATE REQ-MAINT-BATCH REQ-MAINT-VERIFY REQ-MAINT-REPORT REQ-MAINT-SAFE
13
14
  */
14
15
  var detect_1 = require("./detect");
15
16
  Object.defineProperty(exports, "detectStaleAnnotations", { enumerable: true, get: function () { return detect_1.detectStaleAnnotations; } });
@@ -12,8 +12,8 @@ const detect_1 = require("./detect");
12
12
  */
13
13
  function generateMaintenanceReport(codebasePath) {
14
14
  const staleAnnotations = (0, detect_1.detectStaleAnnotations)(codebasePath);
15
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md // @req REQ-MAINT-SAFE - When no stale annotations are found, return empty string to indicate no actions required
16
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md // @req REQ-MAINT-REPORT - When stale annotations exist, produce a newline-separated report
15
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-SAFE - When no stale annotations are found, return empty string to indicate no actions required
16
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-REPORT - When stale annotations exist, produce a newline-separated report
17
17
  if (staleAnnotations.length === 0) {
18
18
  return "";
19
19
  }
@@ -40,6 +40,7 @@ const utils_1 = require("./utils");
40
40
  * Helper to process a single file for annotation reference updates
41
41
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
42
42
  * @req REQ-MAINT-UPDATE
43
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
43
44
  */
44
45
  function processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef) {
45
46
  const content = fs.readFileSync(fullPath, "utf8"); // getAllFiles already returns regular files
@@ -78,8 +79,7 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
78
79
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
79
80
  * @req REQ-MAINT-UPDATE
80
81
  */
81
- // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
82
- // @req REQ-MAINT-UPDATE
82
+ // @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
83
83
  if (!fs.existsSync(codebasePath) ||
84
84
  !fs.statSync(codebasePath).isDirectory()) {
85
85
  return 0;
@@ -92,11 +92,13 @@ function updateAnnotationReferences(codebasePath, oldPath, newPath) {
92
92
  * Iterate over all files and replace annotation references
93
93
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
94
94
  * @req REQ-MAINT-UPDATE
95
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
95
96
  */
96
97
  /**
97
98
  * Loop over each discovered file path
98
99
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
99
100
  * @req REQ-MAINT-UPDATE
101
+ * @supports docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md REQ-MAINT-UPDATE
100
102
  */
101
103
  for (const fullPath of files) {
102
104
  processFileForAnnotationUpdates(fullPath, regex, newPath, replacementCountRef);
@@ -8,6 +8,7 @@
8
8
  * @story docs/stories/013-exclude-test-framework-callbacks.proposed.md
9
9
  * @req REQ-TEST-CALLBACK-EXCLUSION - Provide reusable test callback exclusion logic
10
10
  */
11
+ import type { TSESTree } from "@typescript-eslint/utils";
11
12
  /**
12
13
  * Options controlling how test callbacks are treated by the helpers.
13
14
  *
@@ -21,6 +22,9 @@ interface CallbackExclusionOptions {
21
22
  excludeTestCallbacks?: boolean;
22
23
  additionalTestHelperNames?: string[];
23
24
  }
25
+ type TraceabilityNodeWithParent = TSESTree.Node & {
26
+ parent?: TraceabilityNodeWithParent | null;
27
+ };
24
28
  /**
25
29
  * Determine whether a node represents a callback passed to a known test
26
30
  * framework function (Jest, Mocha, Vitest, etc).
@@ -34,6 +38,6 @@ interface CallbackExclusionOptions {
34
38
  *
35
39
  * @req REQ-TEST-CALLBACK-EXCLUSION
36
40
  */
37
- declare function isTestFrameworkCallback(node: any, options?: CallbackExclusionOptions): boolean;
41
+ declare function isTestFrameworkCallback(node: TraceabilityNodeWithParent | null | undefined, options?: CallbackExclusionOptions): boolean;
38
42
  export type { CallbackExclusionOptions };
39
43
  export { isTestFrameworkCallback };
@@ -1,14 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Shared helpers for determining whether a function-like node should be
4
- * treated as a test framework callback that may be excluded from
5
- * function-level annotation requirements.
6
- *
7
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
8
- * @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
9
- * @story docs/stories/013-exclude-test-framework-callbacks.proposed.md
10
- * @req REQ-TEST-CALLBACK-EXCLUSION - Provide reusable test callback exclusion logic
11
- */
12
2
  Object.defineProperty(exports, "__esModule", { value: true });
13
3
  exports.isTestFrameworkCallback = isTestFrameworkCallback;
14
4
  /**
@@ -91,7 +81,8 @@ function isTestFrameworkCallback(node, options) {
91
81
  if (!parent || parent.type !== "CallExpression") {
92
82
  return false;
93
83
  }
94
- const callee = parent.callee;
84
+ const callExpressionParent = parent;
85
+ const callee = callExpressionParent.callee;
95
86
  if (callee.type === "Identifier") {
96
87
  return isRecognizedTestHelperName(callee.name, options);
97
88
  }
@@ -175,8 +175,8 @@ function validateStoryAnnotation(context, comment, rawValue, options) {
175
175
  return;
176
176
  }
177
177
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
178
- // @req REQ-PATH-FORMAT - Reject @story values containing internal whitespace as invalid
179
- if (/\s/.test(trimmed)) {
178
+ // @req REQ-PATH-FORMAT - Reject @story values containing internal whitespace that do not collapse into a valid story path
179
+ if (/\s/.test(trimmed) && !pathPattern.test(collapsed)) {
180
180
  reportInvalidStoryFormat(context, comment, collapsed, options);
181
181
  return;
182
182
  }
@@ -223,6 +223,12 @@ function validateReqAnnotation(context, comment, rawValue, options) {
223
223
  return;
224
224
  }
225
225
  const collapsed = (0, valid_annotation_utils_1.collapseAnnotationValue)(trimmed);
226
+ // Allow mixed @req/@supports lines to pass without additional @req validation,
227
+ // while still validating simple multi-line @req identifiers that collapse
228
+ // to a single token.
229
+ if (collapsed.includes("@supports")) {
230
+ return;
231
+ }
226
232
  const reqPattern = options.reqPattern;
227
233
  // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
228
234
  // @req REQ-REQ-FORMAT - Flag @req identifiers that do not match the configured pattern
@@ -326,6 +326,10 @@ const rule = {
326
326
  if (process.env.TRACEABILITY_DEBUG === "1") {
327
327
  console.log("[no-redundant-annotation] BlockStatement parent=%s statements=%d", parent && parent.type, Array.isArray(node.body) ? node.body.length : 0);
328
328
  }
329
+ // @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-CATCH-BLOCK-HANDLING
330
+ if (parent && parent.type === "CatchClause") {
331
+ return;
332
+ }
329
333
  const scopePairs = collectScopePairs(context, parent, options.maxScopeDepth);
330
334
  debugScopePairs(parent, scopePairs);
331
335
  if (scopePairs.size === 0)
@@ -286,17 +286,8 @@ function tryBuildInlineAutoFix(context, comments, storyIndex, reqIndices) {
286
286
  * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
287
287
  * @req REQ-MIGRATE-INLINE
288
288
  */
289
- function handleInlineStorySequence(context, group, startIndex) {
289
+ function collectReqIndicesAfterStory(group, startIndex) {
290
290
  const n = group.length;
291
- const current = group[startIndex];
292
- const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
293
- if (!normalized || !/^@story\b/.test(normalized)) {
294
- return startIndex + 1;
295
- }
296
- if (/^@supports\b/.test(normalized)) {
297
- return startIndex + 1;
298
- }
299
- const storyIndex = startIndex;
300
291
  const reqIndices = [];
301
292
  let j = startIndex + 1;
302
293
  while (j < n) {
@@ -312,6 +303,19 @@ function handleInlineStorySequence(context, group, startIndex) {
312
303
  }
313
304
  break;
314
305
  }
306
+ return { reqIndices, nextIndex: j };
307
+ }
308
+ function handleInlineStorySequence(context, group, startIndex) {
309
+ const current = group[startIndex];
310
+ const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
311
+ if (!normalized || !/^@story\b/.test(normalized)) {
312
+ return startIndex + 1;
313
+ }
314
+ if (/^@supports\b/.test(normalized)) {
315
+ return startIndex + 1;
316
+ }
317
+ const storyIndex = startIndex;
318
+ const { reqIndices, nextIndex } = collectReqIndicesAfterStory(group, startIndex);
315
319
  if (reqIndices.length === 0) {
316
320
  context.report({
317
321
  node: current,
@@ -333,7 +337,7 @@ function handleInlineStorySequence(context, group, startIndex) {
333
337
  messageId: "preferImplements",
334
338
  });
335
339
  }
336
- return reqIndices[reqIndices.length - 1] + 1;
340
+ return nextIndex;
337
341
  }
338
342
  /**
339
343
  * Process a contiguous group of inline line comments, identifying legacy
@@ -343,19 +347,20 @@ function handleInlineStorySequence(context, group, startIndex) {
343
347
  * @story docs/stories/010.3-DEV-MIGRATE-TO-SUPPORTS.story.md
344
348
  * @req REQ-MIGRATE-INLINE
345
349
  */
350
+ function advanceInlineGroupIndex(context, group, currentIndex) {
351
+ const current = group[currentIndex];
352
+ const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
353
+ if (!normalized || !/^@story\b/.test(normalized)) {
354
+ return currentIndex + 1;
355
+ }
356
+ return handleInlineStorySequence(context, group, currentIndex);
357
+ }
346
358
  function processInlineGroup(context, group) {
347
359
  if (group.length === 0)
348
360
  return;
349
- const n = group.length;
350
361
  let i = 0;
351
- while (i < n) {
352
- const current = group[i];
353
- const normalized = (0, valid_annotation_format_internal_1.normalizeCommentLine)(current.value || "");
354
- if (!normalized || !/^@story\b/.test(normalized)) {
355
- i += 1;
356
- continue;
357
- }
358
- i = handleInlineStorySequence(context, group, i);
362
+ while (i < group.length) {
363
+ i = advanceInlineGroupIndex(context, group, i);
359
364
  }
360
365
  }
361
366
  /**
@@ -99,6 +99,12 @@ const rule = {
99
99
  items: { type: "string" },
100
100
  uniqueItems: true,
101
101
  },
102
+ /**
103
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG REQ-DEFAULT-BACKWARD-COMPAT
104
+ */
105
+ annotationPlacement: {
106
+ enum: ["before", "inside"],
107
+ },
102
108
  },
103
109
  additionalProperties: false,
104
110
  },
@@ -124,6 +130,16 @@ const rule = {
124
130
  return branchTypesOrListener;
125
131
  }
126
132
  const branchTypes = branchTypesOrListener;
133
+ /**
134
+ * Resolve annotation placement configuration with backward-compatible default.
135
+ *
136
+ * @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG REQ-DEFAULT-BACKWARD-COMPAT
137
+ */
138
+ const rawOptions = context.options[0] || {};
139
+ const _annotationPlacement = rawOptions.annotationPlacement === "inside" ||
140
+ rawOptions.annotationPlacement === "before"
141
+ ? rawOptions.annotationPlacement
142
+ : "before";
127
143
  const storyFixCountRef = { count: 0 };
128
144
  const handlers = {};
129
145
  branchTypes.forEach((type) => {