@skillsmith/core 0.5.2 → 0.5.4
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 +14 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/activation/ActivationManager.d.ts +7 -0
- package/dist/src/activation/ActivationManager.d.ts.map +1 -1
- package/dist/src/activation/ActivationManager.js +13 -4
- package/dist/src/activation/ActivationManager.js.map +1 -1
- package/dist/src/analysis/adapters/python.d.ts +16 -11
- package/dist/src/analysis/adapters/python.d.ts.map +1 -1
- package/dist/src/analysis/adapters/python.js +46 -61
- package/dist/src/analysis/adapters/python.js.map +1 -1
- package/dist/src/analysis/router.test.d.ts +2 -0
- package/dist/src/analysis/router.test.d.ts.map +1 -0
- package/dist/src/analysis/router.test.js +411 -0
- package/dist/src/analysis/router.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
- package/dist/src/analysis/tree-sitter/manager.js +12 -5
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
- package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
- package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +116 -45
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/schemas.d.ts +19 -4
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +8 -0
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.js +405 -0
- package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
- package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
- package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
- package/dist/src/indexer/SkillParser.test.d.ts +2 -0
- package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.test.js +375 -0
- package/dist/src/indexer/SkillParser.test.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
- package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
- package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
- package/dist/src/sources/index.d.ts +1 -1
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sources/types.d.ts +28 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +13 -0
- package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
- package/dist/src/telemetry/tracer-imports.js +26 -0
- package/dist/src/telemetry/tracer-imports.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +18 -21
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/utils/rate-limit.d.ts +39 -0
- package/dist/src/utils/rate-limit.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.js +48 -0
- package/dist/src/utils/rate-limit.js.map +1 -0
- package/dist/src/utils/rate-limit.test.d.ts +11 -0
- package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.test.js +86 -0
- package/dist/src/utils/rate-limit.test.js.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
- package/dist/src/webhooks/WebhookQueue.js +19 -0
- package/dist/src/webhooks/WebhookQueue.js.map +1 -1
- package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
- package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
- package/dist/src/webhooks/index.d.ts +1 -0
- package/dist/src/webhooks/index.d.ts.map +1 -1
- package/dist/src/webhooks/index.js +2 -0
- package/dist/src/webhooks/index.js.map +1 -1
- package/dist/tests/ActivationManager.test.d.ts +13 -0
- package/dist/tests/ActivationManager.test.d.ts.map +1 -0
- package/dist/tests/ActivationManager.test.js +218 -0
- package/dist/tests/ActivationManager.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
- package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
- package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.test.js +87 -0
- package/dist/tests/ROIDashboardService.test.js.map +1 -1
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
- package/dist/tests/ScraperAdapters.test.d.ts +5 -1
- package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
- package/dist/tests/ScraperAdapters.test.js +6 -336
- package/dist/tests/ScraperAdapters.test.js.map +1 -1
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
- package/dist/tests/WebhookHandler.test.js +93 -1
- package/dist/tests/WebhookHandler.test.js.map +1 -1
- package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
- package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
- package/dist/tests/WebhookQueue.coverage.test.js +190 -0
- package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.js +37 -0
- package/dist/tests/api/client.validation.test.js.map +1 -1
- package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
- package/dist/tests/billing/GDPRCompliance.test.js +221 -36
- package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
- package/dist/tests/telemetry.test.js +126 -0
- package/dist/tests/telemetry.test.js.map +1 -1
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-4315 + SMI-4316: hardening tests for pythonIncremental.
|
|
3
|
+
*
|
|
4
|
+
* SMI-4315 — `resolvePythonWasmPath` probes each candidate with
|
|
5
|
+
* `fs.existsSync` instead of always returning `candidates[0]`. Regression
|
|
6
|
+
* test for the package-local-fallback case.
|
|
7
|
+
*
|
|
8
|
+
* SMI-4316 — both silent catches now emit logger.warn. Tests verify:
|
|
9
|
+
* - `parseSync` catch: rate-limited warn keyed per file; payload is
|
|
10
|
+
* `{ file, error }` only with error truncated to <=200 chars and no
|
|
11
|
+
* `stack` field (no source code leak).
|
|
12
|
+
* - `doInit` catch: one-shot warn per parser instance on init failure.
|
|
13
|
+
*
|
|
14
|
+
* All SUT access goes through a dynamic `import('./pythonIncremental.js')`
|
|
15
|
+
* inside the tests so the vi.mock calls below are applied even when vitest
|
|
16
|
+
* shares its module cache with sibling test files in the same worker.
|
|
17
|
+
* Mixing static `import { X } from './pythonIncremental.js'` with vi.mock
|
|
18
|
+
* against the same module has been observed to bypass the mock when a
|
|
19
|
+
* sibling test file imports the SUT first.
|
|
20
|
+
*/
|
|
21
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
22
|
+
import { resetRateLimiter } from '../../utils/rate-limit.js';
|
|
23
|
+
// vi.hoisted runs before vi.mock factories; closure-sharing the spy
|
|
24
|
+
// objects avoids the createLogger-spy trap (feedback_logger_spy_pattern).
|
|
25
|
+
const { loggerSpies, existsSyncMock } = vi.hoisted(() => ({
|
|
26
|
+
loggerSpies: {
|
|
27
|
+
warn: vi.fn(),
|
|
28
|
+
error: vi.fn(),
|
|
29
|
+
info: vi.fn(),
|
|
30
|
+
debug: vi.fn(),
|
|
31
|
+
auditLog: vi.fn(),
|
|
32
|
+
securityLog: vi.fn(),
|
|
33
|
+
},
|
|
34
|
+
existsSyncMock: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
vi.mock('../../utils/logger.js', () => ({
|
|
37
|
+
createLogger: () => loggerSpies,
|
|
38
|
+
}));
|
|
39
|
+
vi.mock('node:fs', async () => {
|
|
40
|
+
const actual = await vi.importActual('node:fs');
|
|
41
|
+
return { ...actual, existsSync: (p) => existsSyncMock(p) };
|
|
42
|
+
});
|
|
43
|
+
let SUT_CACHE = null;
|
|
44
|
+
async function getSUT() {
|
|
45
|
+
if (!SUT_CACHE)
|
|
46
|
+
SUT_CACHE = await import('./pythonIncremental.js');
|
|
47
|
+
return SUT_CACHE;
|
|
48
|
+
}
|
|
49
|
+
describe('SMI-4315 · resolvePythonWasmPath existsSync probe', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
existsSyncMock.mockReset();
|
|
52
|
+
});
|
|
53
|
+
it('returns candidates[0] when the workspace-hoist path exists', async () => {
|
|
54
|
+
const { resolvePythonWasmPath } = await getSUT();
|
|
55
|
+
const seen = [];
|
|
56
|
+
existsSyncMock.mockImplementation((p) => {
|
|
57
|
+
seen.push(p);
|
|
58
|
+
return true; // first candidate hits; loop exits after one probe
|
|
59
|
+
});
|
|
60
|
+
const result = resolvePythonWasmPath();
|
|
61
|
+
expect(result).toMatch(/tree-sitter-python\.wasm$/);
|
|
62
|
+
expect(seen).toHaveLength(1);
|
|
63
|
+
expect(result).toBe(seen[0]);
|
|
64
|
+
});
|
|
65
|
+
it('falls back to candidates[1] (package-local) when the hoist is missing', async () => {
|
|
66
|
+
const { resolvePythonWasmPath } = await getSUT();
|
|
67
|
+
const seen = [];
|
|
68
|
+
existsSyncMock.mockImplementation((p) => {
|
|
69
|
+
seen.push(p);
|
|
70
|
+
return seen.length === 2; // second candidate exists
|
|
71
|
+
});
|
|
72
|
+
const result = resolvePythonWasmPath();
|
|
73
|
+
expect(result).toBe(seen[1]);
|
|
74
|
+
expect(seen).toHaveLength(2);
|
|
75
|
+
expect(seen[0]).not.toBe(seen[1]);
|
|
76
|
+
});
|
|
77
|
+
it('returns candidates[0] as a stable error anchor when neither exists', async () => {
|
|
78
|
+
const { resolvePythonWasmPath } = await getSUT();
|
|
79
|
+
const seen = [];
|
|
80
|
+
existsSyncMock.mockImplementation((p) => {
|
|
81
|
+
seen.push(p);
|
|
82
|
+
return false;
|
|
83
|
+
});
|
|
84
|
+
const result = resolvePythonWasmPath();
|
|
85
|
+
expect(result).toBe(seen[0]);
|
|
86
|
+
expect(seen).toHaveLength(2);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('SMI-4316 · parseSync catch emits rate-limited warn', () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
loggerSpies.warn.mockReset();
|
|
92
|
+
resetRateLimiter();
|
|
93
|
+
// existsSync returns true so the constructor wasm-path resolution is
|
|
94
|
+
// stable even though we don't actually load WASM in these tests.
|
|
95
|
+
existsSyncMock.mockReturnValue(true);
|
|
96
|
+
});
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
resetRateLimiter();
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Build a parser primed with a cached entry whose `tree.edit` throws on
|
|
102
|
+
* re-parse. This drives `parseSync` into the catch without needing a
|
|
103
|
+
* real WASM runtime.
|
|
104
|
+
*/
|
|
105
|
+
function primeFailingCache(parser, filePath, error) {
|
|
106
|
+
const p = parser;
|
|
107
|
+
// Force `isReady` true with minimal stubs.
|
|
108
|
+
p.parser = { parse: () => ({}) };
|
|
109
|
+
p.language = {};
|
|
110
|
+
p.queries = {};
|
|
111
|
+
p.cache.set(filePath, {
|
|
112
|
+
tree: {
|
|
113
|
+
edit: () => {
|
|
114
|
+
throw error;
|
|
115
|
+
},
|
|
116
|
+
delete: () => { },
|
|
117
|
+
},
|
|
118
|
+
content: 'placeholder\n',
|
|
119
|
+
lastUsed: 1,
|
|
120
|
+
lastResult: null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
it('emits one warn on the first parseSync failure with safe payload', async () => {
|
|
124
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
125
|
+
const parser = new PythonIncrementalParser();
|
|
126
|
+
const filePath = 'a.py';
|
|
127
|
+
primeFailingCache(parser, filePath, new Error('simulated parse failure'));
|
|
128
|
+
const result = parser.parseSync('different content\n', filePath);
|
|
129
|
+
expect(result).toBeNull();
|
|
130
|
+
expect(loggerSpies.warn).toHaveBeenCalledTimes(1);
|
|
131
|
+
const [message, payload] = loggerSpies.warn.mock.calls[0];
|
|
132
|
+
expect(message).toContain('parseSync failed');
|
|
133
|
+
// Strictly { file, error } — no stack, no source content.
|
|
134
|
+
expect(payload).toEqual({
|
|
135
|
+
file: filePath,
|
|
136
|
+
error: 'simulated parse failure',
|
|
137
|
+
});
|
|
138
|
+
expect(Object.keys(payload ?? {}).sort()).toEqual(['error', 'file']);
|
|
139
|
+
});
|
|
140
|
+
it('truncates error messages to <=200 chars (no source code leak)', async () => {
|
|
141
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
142
|
+
const parser = new PythonIncrementalParser();
|
|
143
|
+
const filePath = 'b.py';
|
|
144
|
+
const longMsg = 'E'.repeat(500);
|
|
145
|
+
// Include a newline that contains fake "source" content; the formatter
|
|
146
|
+
// must only keep the first line.
|
|
147
|
+
const faux = new Error(`${longMsg}\nSECRET_API_KEY=sk_live_should_never_appear`);
|
|
148
|
+
primeFailingCache(parser, filePath, faux);
|
|
149
|
+
parser.parseSync('xx\n', filePath);
|
|
150
|
+
const payload = loggerSpies.warn.mock.calls[0][1];
|
|
151
|
+
expect(payload.error.length).toBeLessThanOrEqual(200);
|
|
152
|
+
expect(payload.error).not.toContain('\n');
|
|
153
|
+
expect(payload.error).not.toContain('SECRET_API_KEY');
|
|
154
|
+
});
|
|
155
|
+
it('rate-limits a hot-loop flood (FIRST_N fire, then 1-in-SAMPLE_EVERY)', async () => {
|
|
156
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
157
|
+
const parser = new PythonIncrementalParser();
|
|
158
|
+
const filePath = 'flood.py';
|
|
159
|
+
// FIRST_N (5) fires; after that, one-in-SAMPLE_EVERY (100) fires.
|
|
160
|
+
// 200 events after the first 5 → 2 sampled hits. Total = 7.
|
|
161
|
+
for (let i = 0; i < 205; i += 1) {
|
|
162
|
+
primeFailingCache(parser, filePath, new Error(`fail ${i}`));
|
|
163
|
+
parser.parseSync(`content ${i}\n`, filePath);
|
|
164
|
+
}
|
|
165
|
+
expect(loggerSpies.warn).toHaveBeenCalledTimes(7);
|
|
166
|
+
});
|
|
167
|
+
it('isolates rate-limit buckets across files', async () => {
|
|
168
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
169
|
+
const parser = new PythonIncrementalParser();
|
|
170
|
+
// Drive hot.py well past its FIRST_N + next sampled boundary (5 + 1 = 6).
|
|
171
|
+
for (let i = 0; i < 50; i += 1) {
|
|
172
|
+
primeFailingCache(parser, 'hot.py', new Error('x'));
|
|
173
|
+
parser.parseSync(`content ${i}\n`, 'hot.py');
|
|
174
|
+
}
|
|
175
|
+
const hotWarns = loggerSpies.warn.mock.calls.length;
|
|
176
|
+
// hot.py should have burned FIRST_N=5 + 1 sampled (count=6) = 6 warns.
|
|
177
|
+
expect(hotWarns).toBe(6);
|
|
178
|
+
// cool.py still gets its own FIRST_N budget, so the very first failure
|
|
179
|
+
// emits a warn regardless of what hot.py did.
|
|
180
|
+
primeFailingCache(parser, 'cool.py', new Error('y'));
|
|
181
|
+
parser.parseSync('ctn\n', 'cool.py');
|
|
182
|
+
expect(loggerSpies.warn.mock.calls.length).toBe(hotWarns + 1);
|
|
183
|
+
const cool = loggerSpies.warn.mock.calls.find((c) => c[1].file === 'cool.py');
|
|
184
|
+
expect(cool).toBeDefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe('SMI-4316 · doInit catch emits one-shot warn', () => {
|
|
188
|
+
beforeEach(() => {
|
|
189
|
+
loggerSpies.warn.mockReset();
|
|
190
|
+
resetRateLimiter();
|
|
191
|
+
existsSyncMock.mockReturnValue(true);
|
|
192
|
+
});
|
|
193
|
+
it('logs a single warn on init failure with wasmPath + error + stack', async () => {
|
|
194
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
195
|
+
const failingLoader = async () => {
|
|
196
|
+
throw new Error('module not found');
|
|
197
|
+
};
|
|
198
|
+
const parser = new PythonIncrementalParser({}, failingLoader);
|
|
199
|
+
const result = await parser.parse('def x(): pass\n', 'fail.py');
|
|
200
|
+
expect(result).toBeNull();
|
|
201
|
+
expect(parser.hasFailedInit).toBe(true);
|
|
202
|
+
expect(loggerSpies.warn).toHaveBeenCalledTimes(1);
|
|
203
|
+
const [message, payload] = loggerSpies.warn.mock.calls[0];
|
|
204
|
+
expect(message).toContain('init failed');
|
|
205
|
+
const p = payload;
|
|
206
|
+
expect(p.wasmPath).toMatch(/tree-sitter-python\.wasm$/);
|
|
207
|
+
expect(p.error).toBe('module not found');
|
|
208
|
+
expect(typeof p.stack).toBe('string');
|
|
209
|
+
});
|
|
210
|
+
it('does not warn on a successful parse path (no init failure)', async () => {
|
|
211
|
+
const { PythonIncrementalParser } = await getSUT();
|
|
212
|
+
const parser = new PythonIncrementalParser();
|
|
213
|
+
// Mark ready via stubs; we only assert doInit never fired a warn.
|
|
214
|
+
const p = parser;
|
|
215
|
+
p.parser = { parse: () => ({ rootNode: {} }) };
|
|
216
|
+
p.language = {};
|
|
217
|
+
p.queries = {};
|
|
218
|
+
p.initFailed = false;
|
|
219
|
+
try {
|
|
220
|
+
parser.parseSync('def ok(): pass\n', 'ok.py');
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// swallow — assertion is on the warn spy, not the parse result.
|
|
224
|
+
}
|
|
225
|
+
const initWarns = loggerSpies.warn.mock.calls.filter((c) => String(c[0]).includes('init failed'));
|
|
226
|
+
expect(initWarns).toHaveLength(0);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
//# sourceMappingURL=pythonIncremental.hardening.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pythonIncremental.hardening.test.js","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/pythonIncremental.hardening.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-4293: Python Incremental Parser Controller
|
|
3
|
+
*
|
|
4
|
+
* Couples the WASM tree-sitter parser, per-file tree cache, and edit-based
|
|
5
|
+
* incremental parsing into a single entry point for the Python adapter.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Lazy-init the WASM parser (via web-tree-sitter) and Python language
|
|
9
|
+
* grammar (from tree-sitter-wasms).
|
|
10
|
+
* - Cache parse trees per file path with LRU eviction (max 100 trees) and
|
|
11
|
+
* proper `.delete()` lifecycle.
|
|
12
|
+
* - Apply tree.edit() and reuse the previous tree when content changes
|
|
13
|
+
* incrementally; fall back to full parse on cache miss or corruption.
|
|
14
|
+
* - Delegate extraction to pythonExtractor (query-based, replaces regex).
|
|
15
|
+
*
|
|
16
|
+
* @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
|
|
17
|
+
* @module analysis/tree-sitter/pythonIncremental
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import { calculateEdit, findMinimalEdit } from '../incremental.js';
|
|
23
|
+
import { createLogger } from '../../utils/logger.js';
|
|
24
|
+
import { rateLimited } from '../../utils/rate-limit.js';
|
|
25
|
+
import { PythonQuerySet, extractPythonParseResult } from './pythonExtractor.js';
|
|
26
|
+
const logger = createLogger('PythonIncrementalParser');
|
|
27
|
+
/** First line of a string, truncated to 200 chars. Used to avoid leaking source content into logs. */
|
|
28
|
+
function firstLine(s) {
|
|
29
|
+
return s.split('\n', 1)[0]?.slice(0, 200) ?? '';
|
|
30
|
+
}
|
|
31
|
+
/** Maximum number of cached trees (per SMI-1309 / SMI-4293 spec). */
|
|
32
|
+
const DEFAULT_MAX_TREES = 100;
|
|
33
|
+
/** Resolve the path to the Python WASM grammar distributed via tree-sitter-wasms. */
|
|
34
|
+
export function resolvePythonWasmPath() {
|
|
35
|
+
// Caller is inside packages/core/dist or packages/core/src; tree-sitter-wasms
|
|
36
|
+
// is hoisted to the repo root node_modules in every layout.
|
|
37
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const candidates = [
|
|
39
|
+
// Monorepo root (workspace hoist)
|
|
40
|
+
path.resolve(here, '..', '..', '..', '..', '..', 'node_modules', 'tree-sitter-wasms', 'out', 'tree-sitter-python.wasm'),
|
|
41
|
+
// Package-local (npm-registry install consumers without hoist)
|
|
42
|
+
path.resolve(here, '..', '..', '..', '..', 'node_modules', 'tree-sitter-wasms', 'out', 'tree-sitter-python.wasm'),
|
|
43
|
+
];
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
if (fs.existsSync(candidate))
|
|
46
|
+
return candidate;
|
|
47
|
+
}
|
|
48
|
+
// Neither exists on disk; return the first so `doInit()` has a stable
|
|
49
|
+
// error path to surface. Probe is best-effort — the real load failure
|
|
50
|
+
// is still caught by `doInit()` below.
|
|
51
|
+
return candidates[0];
|
|
52
|
+
}
|
|
53
|
+
const defaultLoader = async () => {
|
|
54
|
+
// Optional peer; falls back to error if not installed at runtime.
|
|
55
|
+
const mod = (await import('web-tree-sitter'));
|
|
56
|
+
return {
|
|
57
|
+
Parser: mod.Parser,
|
|
58
|
+
Language: mod.Language,
|
|
59
|
+
Query: mod.Query,
|
|
60
|
+
// Parser.init is a static method on the named export.
|
|
61
|
+
init: async () => {
|
|
62
|
+
const maybeInit = mod.Parser.init;
|
|
63
|
+
if (typeof maybeInit === 'function') {
|
|
64
|
+
await maybeInit.call(mod.Parser);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Incremental Python parser: lazily boots web-tree-sitter, parses with
|
|
71
|
+
* `tree.edit()` reuse when possible, falls back gracefully otherwise.
|
|
72
|
+
*/
|
|
73
|
+
export class PythonIncrementalParser {
|
|
74
|
+
maxTrees;
|
|
75
|
+
wasmPath;
|
|
76
|
+
loader;
|
|
77
|
+
cache = new Map();
|
|
78
|
+
parser = null;
|
|
79
|
+
language = null;
|
|
80
|
+
queries = null;
|
|
81
|
+
initPromise = null;
|
|
82
|
+
initFailed = false;
|
|
83
|
+
useCounter = 0;
|
|
84
|
+
constructor(options = {}, loader = defaultLoader) {
|
|
85
|
+
this.maxTrees = options.maxTrees ?? DEFAULT_MAX_TREES;
|
|
86
|
+
this.wasmPath = options.wasmPath ?? resolvePythonWasmPath();
|
|
87
|
+
this.loader = loader;
|
|
88
|
+
}
|
|
89
|
+
/** True when the WASM runtime and Python grammar loaded successfully. */
|
|
90
|
+
get isReady() {
|
|
91
|
+
return this.parser !== null && this.language !== null && this.queries !== null;
|
|
92
|
+
}
|
|
93
|
+
/** True when a prior init attempt failed; callers should use regex fallback. */
|
|
94
|
+
get hasFailedInit() {
|
|
95
|
+
return this.initFailed;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ensure the WASM parser + Python grammar are loaded. Callers should
|
|
99
|
+
* `await` this once before relying on `parseSync` for a synchronous path.
|
|
100
|
+
*/
|
|
101
|
+
async ensureReady() {
|
|
102
|
+
await this.ensureInit();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse asynchronously: ensures init, then delegates to the sync path.
|
|
106
|
+
* Returns null if init has permanently failed (caller falls back to regex).
|
|
107
|
+
*/
|
|
108
|
+
async parse(content, filePath) {
|
|
109
|
+
await this.ensureInit();
|
|
110
|
+
return this.parseSync(content, filePath);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Synchronous parse: usable only after `ensureReady()` has resolved. Uses
|
|
114
|
+
* the previous tree via `tree.edit()` when content changed incrementally.
|
|
115
|
+
*
|
|
116
|
+
* Returns null when the parser isn't ready or any parse/extract step fails;
|
|
117
|
+
* callers should fall back to regex extraction in that case.
|
|
118
|
+
*/
|
|
119
|
+
parseSync(content, filePath) {
|
|
120
|
+
if (!this.isReady)
|
|
121
|
+
return null;
|
|
122
|
+
const parser = this.parser;
|
|
123
|
+
const queries = this.queries;
|
|
124
|
+
try {
|
|
125
|
+
const cached = this.cache.get(filePath);
|
|
126
|
+
let tree;
|
|
127
|
+
let reusedResult = null;
|
|
128
|
+
if (cached) {
|
|
129
|
+
const diff = findMinimalEdit(cached.content, content);
|
|
130
|
+
if (!diff) {
|
|
131
|
+
// Unchanged content — reuse tree AND memoised extraction result
|
|
132
|
+
// without re-parse or re-extract.
|
|
133
|
+
this.touch(filePath, cached);
|
|
134
|
+
tree = cached.tree;
|
|
135
|
+
reusedResult = cached.lastResult;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const edit = calculateEdit(cached.content, content, diff.changeStart, diff.changeEnd, diff.newText);
|
|
139
|
+
// Apply edit to the previous tree. `edit()` is in-place.
|
|
140
|
+
const editable = cached.tree;
|
|
141
|
+
editable.edit(edit);
|
|
142
|
+
tree = parser.parse(content, cached.tree);
|
|
143
|
+
// Replace cached tree; delete old to free WASM memory.
|
|
144
|
+
if (tree !== cached.tree)
|
|
145
|
+
this.safeDelete(cached.tree);
|
|
146
|
+
this.store(filePath, tree, content);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
tree = parser.parse(content);
|
|
151
|
+
this.store(filePath, tree, content);
|
|
152
|
+
}
|
|
153
|
+
if (reusedResult)
|
|
154
|
+
return reusedResult;
|
|
155
|
+
const result = extractPythonParseResult(tree, queries, filePath);
|
|
156
|
+
const entry = this.cache.get(filePath);
|
|
157
|
+
if (entry)
|
|
158
|
+
entry.lastResult = result;
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// Any failure (corrupt tree, grammar error) invalidates this file's
|
|
163
|
+
// cache and signals the adapter to use regex fallback for this call.
|
|
164
|
+
// Log rate-limited so a pathological grammar hit cannot flood logs.
|
|
165
|
+
// Emit only `{ file, error(firstLine, <=200) }` — never the source
|
|
166
|
+
// content or the stack (tree-sitter errors can quote source lines).
|
|
167
|
+
if (rateLimited(`python-parse:${filePath}`)) {
|
|
168
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
169
|
+
logger.warn('Python parseSync failed; regex fallback for this file', {
|
|
170
|
+
file: filePath,
|
|
171
|
+
error: firstLine(message),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
this.invalidate(filePath);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Invalidate a single file's cache entry and free its tree. */
|
|
179
|
+
invalidate(filePath) {
|
|
180
|
+
const entry = this.cache.get(filePath);
|
|
181
|
+
if (!entry)
|
|
182
|
+
return;
|
|
183
|
+
this.safeDelete(entry.tree);
|
|
184
|
+
this.cache.delete(filePath);
|
|
185
|
+
}
|
|
186
|
+
/** Clear and dispose all cached trees. */
|
|
187
|
+
dispose() {
|
|
188
|
+
for (const entry of this.cache.values())
|
|
189
|
+
this.safeDelete(entry.tree);
|
|
190
|
+
this.cache.clear();
|
|
191
|
+
if (this.parser) {
|
|
192
|
+
try {
|
|
193
|
+
this.parser.delete();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// ignore
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.parser = null;
|
|
200
|
+
this.language = null;
|
|
201
|
+
this.queries = null;
|
|
202
|
+
this.initPromise = null;
|
|
203
|
+
}
|
|
204
|
+
/** Current cache size (exposed for tests and instrumentation). */
|
|
205
|
+
get cacheSize() {
|
|
206
|
+
return this.cache.size;
|
|
207
|
+
}
|
|
208
|
+
// ----------------------------------------------------------------
|
|
209
|
+
// Private
|
|
210
|
+
// ----------------------------------------------------------------
|
|
211
|
+
async ensureInit() {
|
|
212
|
+
if (this.isReady || this.initFailed)
|
|
213
|
+
return;
|
|
214
|
+
if (this.initPromise)
|
|
215
|
+
return this.initPromise;
|
|
216
|
+
this.initPromise = this.doInit();
|
|
217
|
+
try {
|
|
218
|
+
await this.initPromise;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
this.initPromise = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async doInit() {
|
|
225
|
+
try {
|
|
226
|
+
const deps = await this.loader();
|
|
227
|
+
await deps.init();
|
|
228
|
+
const parser = new deps.Parser();
|
|
229
|
+
const language = await deps.Language.load(this.wasmPath);
|
|
230
|
+
parser.setLanguage(language);
|
|
231
|
+
this.parser = parser;
|
|
232
|
+
this.language = language;
|
|
233
|
+
this.queries = new PythonQuerySet(deps.Query, language);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
this.initFailed = true;
|
|
237
|
+
this.parser = null;
|
|
238
|
+
this.language = null;
|
|
239
|
+
this.queries = null;
|
|
240
|
+
// One-shot warn per parser instance: fires at most once in `doInit`.
|
|
241
|
+
// Log `{ wasmPath, error, stack }` for operator diagnostics; the WASM
|
|
242
|
+
// path is not secret, the stack points inside web-tree-sitter /
|
|
243
|
+
// resolvePythonWasmPath, not user source.
|
|
244
|
+
logger.warn('Python tree-sitter init failed; regex fallback in use', {
|
|
245
|
+
wasmPath: this.wasmPath,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
store(filePath, tree, content) {
|
|
252
|
+
if (!this.cache.has(filePath) && this.cache.size >= this.maxTrees) {
|
|
253
|
+
this.evictLRU();
|
|
254
|
+
}
|
|
255
|
+
this.cache.set(filePath, {
|
|
256
|
+
tree,
|
|
257
|
+
content,
|
|
258
|
+
lastUsed: ++this.useCounter,
|
|
259
|
+
lastResult: null,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
touch(filePath, entry) {
|
|
263
|
+
entry.lastUsed = ++this.useCounter;
|
|
264
|
+
this.cache.set(filePath, entry);
|
|
265
|
+
}
|
|
266
|
+
evictLRU() {
|
|
267
|
+
let oldestKey = null;
|
|
268
|
+
let oldest = Infinity;
|
|
269
|
+
for (const [key, entry] of this.cache) {
|
|
270
|
+
if (entry.lastUsed < oldest) {
|
|
271
|
+
oldest = entry.lastUsed;
|
|
272
|
+
oldestKey = key;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (oldestKey)
|
|
276
|
+
this.invalidate(oldestKey);
|
|
277
|
+
}
|
|
278
|
+
safeDelete(tree) {
|
|
279
|
+
try {
|
|
280
|
+
tree.delete();
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// Already-deleted trees throw; swallow for robustness.
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=pythonIncremental.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pythonIncremental.js","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/pythonIncremental.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-4293: PythonIncrementalParser unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers the six cases called out in the plan:
|
|
5
|
+
* 1. First parse caches the tree.
|
|
6
|
+
* 2. Unchanged content re-parse hits the cache (no re-parse).
|
|
7
|
+
* 3. Incremental edits re-use the previous tree via tree.edit().
|
|
8
|
+
* 4. LRU eviction at the configured max.
|
|
9
|
+
* 5. Corrupted cached tree falls back gracefully (returns null, adapter
|
|
10
|
+
* then falls back to regex).
|
|
11
|
+
* 6. Unsupported / unavailable grammar surfaces `isReady === false` and
|
|
12
|
+
* `parseSync` returns null.
|
|
13
|
+
*
|
|
14
|
+
* @see docs/internal/implementation/github-wave-5c-tree-sitter-incremental.md
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=pythonIncremental.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pythonIncremental.test.d.ts","sourceRoot":"","sources":["../../../../src/analysis/tree-sitter/pythonIncremental.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|