@kernlang/review 3.1.4 → 3.1.6

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,127 @@
1
+ /**
2
+ * Terminal review rules — active when target = terminal.
3
+ *
4
+ * Focused on interactive ANSI / readline terminal apps.
5
+ * Codex base rules + Claude extras (signal-handler, cursor-restore, unthrottled-render).
6
+ */
7
+ import { finding } from './utils.js';
8
+ function lineForIndex(text, index) {
9
+ return text.slice(0, index).split('\n').length;
10
+ }
11
+ function matchLine(text, pattern) {
12
+ const match = pattern.exec(text);
13
+ return match ? lineForIndex(text, match.index) : null;
14
+ }
15
+ // ── Rule: terminal-missing-tty-guard (Codex) ────────────────────────────
16
+ function missingTtyGuard(ctx) {
17
+ const fullText = ctx.sourceFile.getFullText();
18
+ const interactivePattern = /\b(?:readline\.)?createInterface\s*\(|\b(?:process\.)?stdin\.setRawMode\s*\(\s*true|\b(?:process\.)?stdout\.(?:write|clearLine|cursorTo|clearScreenDown)\s*\(|\?1049h|\x1b\[|\u001b\[/;
19
+ if (!interactivePattern.test(fullText))
20
+ return [];
21
+ if (/\b(?:process\.)?(?:stdout|stdin)\.isTTY\b|\btty\.isatty\s*\(/.test(fullText))
22
+ return [];
23
+ const line = matchLine(fullText, interactivePattern) ?? 1;
24
+ return [finding('terminal-missing-tty-guard', 'warning', 'bug', 'Interactive terminal code runs without a TTY guard — pipes and non-interactive shells will render incorrectly', ctx.filePath, line, 1, { suggestion: 'Guard interactive paths with process.stdout.isTTY / process.stdin.isTTY before enabling ANSI UI features' })];
25
+ }
26
+ // ── Rule: terminal-raw-mode-no-restore (Codex) ──────────────────────────
27
+ function rawModeNoRestore(ctx) {
28
+ const fullText = ctx.sourceFile.getFullText();
29
+ const enable = /\.setRawMode\s*\(\s*true\s*\)/g;
30
+ if (!enable.test(fullText))
31
+ return [];
32
+ if (/\.setRawMode\s*\(\s*false\s*\)/.test(fullText))
33
+ return [];
34
+ enable.lastIndex = 0;
35
+ const match = enable.exec(fullText);
36
+ const line = match ? lineForIndex(fullText, match.index) : 1;
37
+ return [finding('terminal-raw-mode-no-restore', 'error', 'bug', 'stdin raw mode is enabled without restoring it — the shell can be left in a broken state after exit', ctx.filePath, line, 1, { suggestion: 'Restore raw mode in cleanup handlers with process.stdin.setRawMode(false)' })];
38
+ }
39
+ // ── Rule: terminal-readline-no-close (Codex) ────────────────────────────
40
+ function readlineNoClose(ctx) {
41
+ const findings = [];
42
+ const fullText = ctx.sourceFile.getFullText();
43
+ const decl = /\b(?:const|let|var)\s+(\w+)\s*=\s*(?:readline\.)?createInterface\s*\(/g;
44
+ let match;
45
+ while ((match = decl.exec(fullText)) !== null) {
46
+ const name = match[1];
47
+ const closePattern = new RegExp(`\\b${name}\\.close\\s*\\(`);
48
+ if (closePattern.test(fullText))
49
+ continue;
50
+ findings.push(finding('terminal-readline-no-close', 'warning', 'bug', `Readline interface '${name}' is never closed — stdin can remain open and keep the process hanging`, ctx.filePath, lineForIndex(fullText, match.index), 1, { suggestion: `Call ${name}.close() in completion and shutdown paths` }));
51
+ }
52
+ return findings;
53
+ }
54
+ // ── Rule: terminal-alt-screen-no-restore (Codex) ────────────────────────
55
+ function altScreenNoRestore(ctx) {
56
+ const fullText = ctx.sourceFile.getFullText();
57
+ const enter = /\?1049h|\?47h/g;
58
+ if (!enter.test(fullText))
59
+ return [];
60
+ if (/\?1049l|\?47l/.test(fullText))
61
+ return [];
62
+ enter.lastIndex = 0;
63
+ const match = enter.exec(fullText);
64
+ const line = match ? lineForIndex(fullText, match.index) : 1;
65
+ return [finding('terminal-alt-screen-no-restore', 'warning', 'bug', 'Terminal app enters the alternate screen without restoring it on exit — users can be left in a blank session', ctx.filePath, line, 1, { suggestion: 'Emit the matching ?1049l / ?47l escape sequence during shutdown and signal cleanup' })];
66
+ }
67
+ // ── Rule: terminal-missing-signal-handler (Claude) ──────────────────────
68
+ function missingSignalHandler(ctx) {
69
+ const fullText = ctx.sourceFile.getFullText();
70
+ // Only check files that do terminal-specific work
71
+ const isTerminal = /\b(setRawMode|cursorTo|clearLine|moveCursor|createInterface|blessed|inquirer|ora|chalk\.)\b/.test(fullText);
72
+ if (!isTerminal)
73
+ return [];
74
+ const hasSigint = /process\.on\s*\(\s*['"`]SIGINT['"`]/.test(fullText);
75
+ const hasSigterm = /process\.on\s*\(\s*['"`]SIGTERM['"`]/.test(fullText);
76
+ if (!hasSigint && !hasSigterm) {
77
+ const termMatch = fullText.match(/\b(setRawMode|cursorTo|clearLine|moveCursor|createInterface)\b/);
78
+ const line = termMatch ? lineForIndex(fullText, termMatch.index) : 1;
79
+ return [finding('terminal-missing-signal-handler', 'warning', 'pattern', 'Terminal app has no SIGINT/SIGTERM handler — cleanup code may not run on Ctrl+C', ctx.filePath, line, 1, { suggestion: 'Add process.on("SIGINT", () => { cleanup(); process.exit(130); })' })];
80
+ }
81
+ return [];
82
+ }
83
+ // ── Rule: terminal-cursor-not-restored (Claude) ─────────────────────────
84
+ function cursorNotRestored(ctx) {
85
+ const fullText = ctx.sourceFile.getFullText();
86
+ const hidesCursor = /\\(?:x1[Bb]|u001[Bb]|e|033)\[\?25l/.test(fullText) ||
87
+ fullText.includes('cursor(false)') ||
88
+ fullText.includes('hideCursor') ||
89
+ fullText.includes('cursor.hide');
90
+ if (!hidesCursor)
91
+ return [];
92
+ const restoresCursor = /\\(?:x1[Bb]|u001[Bb]|e|033)\[\?25h/.test(fullText) ||
93
+ fullText.includes('cursor(true)') ||
94
+ fullText.includes('showCursor') ||
95
+ fullText.includes('cursor.show');
96
+ if (!restoresCursor) {
97
+ const match = fullText.match(/(?:hideCursor|cursor\.hide|cursor\(false\)|\?\s*25\s*l)/);
98
+ const line = match ? lineForIndex(fullText, match.index) : 1;
99
+ return [finding('terminal-cursor-not-restored', 'warning', 'bug', 'Cursor hidden without restore — cursor will stay invisible after exit', ctx.filePath, line, 1, { suggestion: 'Add process.on("exit", () => process.stdout.write("\\x1B[?25h"))' })];
100
+ }
101
+ return [];
102
+ }
103
+ // ── Rule: terminal-unthrottled-render (Claude) ──────────────────────────
104
+ function unthrottledRender(ctx) {
105
+ const findings = [];
106
+ const fullText = ctx.sourceFile.getFullText();
107
+ const intervalRegex = /setInterval\s*\([^,]+,\s*(\d+)\s*\)/g;
108
+ let match;
109
+ while ((match = intervalRegex.exec(fullText)) !== null) {
110
+ const ms = parseInt(match[1], 10);
111
+ if (ms < 16) {
112
+ const line = lineForIndex(fullText, match.index);
113
+ findings.push(finding('terminal-unthrottled-render', 'warning', 'pattern', `setInterval at ${ms}ms (>${Math.round(1000 / ms)}fps) — excessive terminal redraws cause flicker`, ctx.filePath, line, 1, { suggestion: 'Use ≥16ms (60fps) for smooth terminal rendering without excess CPU' }));
114
+ }
115
+ }
116
+ return findings;
117
+ }
118
+ export const terminalRules = [
119
+ missingTtyGuard,
120
+ rawModeNoRestore,
121
+ readlineNoClose,
122
+ altScreenNoRestore,
123
+ missingSignalHandler,
124
+ cursorNotRestored,
125
+ unthrottledRender,
126
+ ];
127
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/rules/terminal.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernlang/review",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Kern Review — scan TS, infer .kern IR, roundtrip diff, report",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,10 +26,10 @@
26
26
  "license": "AGPL-3.0",
27
27
  "dependencies": {
28
28
  "ts-morph": "^27.0.0",
29
- "@kernlang/core": "3.1.4"
29
+ "@kernlang/core": "3.1.6"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsc -b",
33
- "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --forceExit --config jest.config.js"
33
+ "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --config jest.config.js"
34
34
  }
35
35
  }