@blumintinc/eslint-plugin-blumint 1.18.16 → 1.19.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.
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noHarnessCoupledDisables = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ /**
6
+ * Recognizes ESLint disable directives (block or line) by their leading
7
+ * keyword. `eslint-enable` deliberately does not match: it carries no
8
+ * justification to classify.
9
+ */
10
+ const DIRECTIVE_PREFIX = /^eslint-disable(?:-next-line|-line)?\b/;
11
+ /**
12
+ * Unconditional harness terms — always flag. Each describes the Claude Code
13
+ * CLI development harness and has no code-level meaning in a suppression
14
+ * reason. When several co-occur, the one appearing FIRST in the justification
15
+ * text is reported (see `findHarnessTerm`), so "claude session ... cwd" reports
16
+ * `claude`, not `cwd`.
17
+ */
18
+ const UNCONDITIONAL_MATCHERS = [
19
+ // Word-boundary start, suffix/compound tolerant: worktree, worktrees,
20
+ // worktree-cwd, cross-worktree, worktree-hook.
21
+ { term: 'worktree', pattern: /\bworktree/ },
22
+ // Word-boundary token, plural/possessive tolerant: cwd, cwds, cwd's.
23
+ { term: 'cwd', pattern: /\bcwd(?:'?s)?\b/ },
24
+ // Word-boundary start: claude, claude's, claude-code, claude code.
25
+ { term: 'claude', pattern: /\bclaude/ },
26
+ // The Claude Code Stop hook compound: stop-hook / stop hook / stop_hook,
27
+ // plus near-adjacent forms with at most one intervening word ("stop lint
28
+ // hook"). Distant incidental co-occurrence of "stop" and "hook" does not
29
+ // match, keeping bare React-hook justifications safe.
30
+ { term: 'stop-hook', pattern: /\bstop[-_ ]+(?:\w+[-_ ]+)?hook/ },
31
+ ];
32
+ /**
33
+ * Conditional session compound. `session` (auth session) is legitimate
34
+ * code-level vocabulary, so it only flags in the unambiguous harness compounds
35
+ * `agent session` / `claude session`. When it co-occurs with an unconditional
36
+ * term, that term reports instead — so in practice only `agent session`
37
+ * surfaces `session` as the matched term.
38
+ */
39
+ const SESSION_MATCHER = {
40
+ term: 'session',
41
+ pattern: /\b(?:agent|claude)[-_ ]+sessions?\b/,
42
+ };
43
+ function isDirectiveComment(comment) {
44
+ return DIRECTIVE_PREFIX.test(comment.value.trim());
45
+ }
46
+ /**
47
+ * Isolates the justification text of a directive comment: everything after the
48
+ * first `--` separator, spanning every line of a multi-line block body. The
49
+ * directive keyword and the comma-separated rule-name list both precede the
50
+ * `--` (and never contain `--`), so slicing past it strips them in one step.
51
+ * Returns `null` when there is no `--` — a bare disable with no justification
52
+ * is out of scope.
53
+ */
54
+ function extractJustification(comment) {
55
+ const separatorIndex = comment.value.indexOf('--');
56
+ if (separatorIndex === -1) {
57
+ return null;
58
+ }
59
+ return comment.value.slice(separatorIndex + 2);
60
+ }
61
+ function findHarnessTerm(text) {
62
+ const lowered = text.toLowerCase();
63
+ let earliest = null;
64
+ for (const { term, pattern } of UNCONDITIONAL_MATCHERS) {
65
+ const match = pattern.exec(lowered);
66
+ if (match && (earliest === null || match.index < earliest.index)) {
67
+ earliest = { term, index: match.index };
68
+ }
69
+ }
70
+ if (earliest !== null) {
71
+ return earliest.term;
72
+ }
73
+ // Only reached when no unconditional term is present: an isolated harness
74
+ // session compound.
75
+ if (SESSION_MATCHER.pattern.test(lowered)) {
76
+ return SESSION_MATCHER.term;
77
+ }
78
+ return null;
79
+ }
80
+ exports.noHarnessCoupledDisables = (0, createRule_1.createRule)({
81
+ name: 'no-harness-coupled-disables',
82
+ meta: {
83
+ type: 'problem',
84
+ docs: {
85
+ description: 'Disallow eslint-disable justifications that reference the agent development harness (worktree, cwd, stop-hook, claude) instead of the code’s own semantics.',
86
+ recommended: 'error',
87
+ },
88
+ fixable: undefined,
89
+ schema: [],
90
+ messages: {
91
+ harnessCoupled: "This eslint-disable justification references the development harness ('{{matchedTerm}}'), not the code's own semantics. Harness quirks (how the stop hook invokes lint, cwd resolution, worktree path mapping) belong in harness config — fix the hook, not the source. See the harness docs referenced in the disable-directive hygiene section of the repo's linting skill doc. If this disable is NOT harness-related and the word is a false match (e.g. 'hook' meaning a React hook), rephrase the justification to describe the code-level reason instead.",
92
+ },
93
+ },
94
+ defaultOptions: [],
95
+ create(context) {
96
+ const sourceCode = context.getSourceCode();
97
+ const comments = sourceCode.getAllComments();
98
+ return {
99
+ Program() {
100
+ comments.forEach((comment, index) => {
101
+ if (!isDirectiveComment(comment)) {
102
+ return;
103
+ }
104
+ const justification = extractJustification(comment);
105
+ // No `--` separator, or nothing but whitespace after it: a bare
106
+ // disable carries no justification to classify (out of scope).
107
+ if (justification === null || justification.trim() === '') {
108
+ return;
109
+ }
110
+ // Split-justification style: an immediately-adjacent preceding
111
+ // non-directive comment (no intervening blank line or code) is part
112
+ // of the directive's rationale when the directive defers to it.
113
+ let scanned = justification;
114
+ const previous = comments[index - 1];
115
+ if (previous &&
116
+ !isDirectiveComment(previous) &&
117
+ comment.loc.start.line - previous.loc.end.line <= 1) {
118
+ scanned = `${previous.value}\n${scanned}`;
119
+ }
120
+ const matchedTerm = findHarnessTerm(scanned);
121
+ if (matchedTerm === null) {
122
+ return;
123
+ }
124
+ context.report({
125
+ loc: comment.loc,
126
+ messageId: 'harnessCoupled',
127
+ data: { matchedTerm },
128
+ });
129
+ });
130
+ },
131
+ };
132
+ },
133
+ });
134
+ //# sourceMappingURL=no-harness-coupled-disables.js.map
@@ -52,15 +52,20 @@ exports.preferCloneDeep = (0, createRule_1.createRule)({
52
52
  node.key.callee.name === 'Symbol')) {
53
53
  hasSymbol = true;
54
54
  }
55
- // Visit child nodes without traversing parent references
55
+ // Visit child nodes without traversing parent references. Depth tracks
56
+ // object-nesting level: an object's OWN direct properties stay at the
57
+ // object's depth, and only descending into a property's value (a
58
+ // genuinely nested child) increments it. This keeps the top-level
59
+ // object's own `...spread` at depth 0 so it is never miscounted as a
60
+ // nested spread.
56
61
  if (node.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
57
62
  if (depth > 0) {
58
63
  hasNestedObject = true;
59
64
  }
60
- node.properties.forEach((prop) => visit(prop, depth + 1));
65
+ node.properties.forEach((prop) => visit(prop, depth));
61
66
  }
62
67
  else if (node.type === utils_1.AST_NODE_TYPES.Property) {
63
- visit(node.value, depth);
68
+ visit(node.value, depth + 1);
64
69
  }
65
70
  else if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
66
71
  visit(node.argument, depth);
@@ -0,0 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'preferMap' | 'preferMapManual';
3
+ export declare const preferMapOverConditionalDispatch: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};