tryassay 0.33.1 → 0.34.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.
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/hunt.d.ts +2 -0
- package/dist/commands/hunt.js +58 -7
- package/dist/commands/hunt.js.map +1 -1
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.js +18 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/watch.d.ts +19 -0
- package/dist/commands/watch.js +158 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/hunt/__tests__/finding-to-template.test.d.ts +1 -0
- package/dist/hunt/__tests__/finding-to-template.test.js +213 -0
- package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -0
- package/dist/hunt/__tests__/parse-utils.test.js +28 -1
- package/dist/hunt/__tests__/parse-utils.test.js.map +1 -1
- package/dist/hunt/__tests__/taint-analysis.test.d.ts +1 -0
- package/dist/hunt/__tests__/taint-analysis.test.js +556 -0
- package/dist/hunt/__tests__/taint-analysis.test.js.map +1 -0
- package/dist/hunt/__tests__/templates.test.js +2 -2
- package/dist/hunt/__tests__/templates.test.js.map +1 -1
- package/dist/hunt/deep-dive.d.ts +2 -2
- package/dist/hunt/deep-dive.js +4 -4
- package/dist/hunt/deep-dive.js.map +1 -1
- package/dist/hunt/discovery.js +2 -2
- package/dist/hunt/discovery.js.map +1 -1
- package/dist/hunt/finding-to-template.d.ts +47 -0
- package/dist/hunt/finding-to-template.js +288 -0
- package/dist/hunt/finding-to-template.js.map +1 -0
- package/dist/hunt/orchestrator.d.ts +3 -0
- package/dist/hunt/orchestrator.js +20 -5
- package/dist/hunt/orchestrator.js.map +1 -1
- package/dist/hunt/taint-analysis.d.ts +49 -0
- package/dist/hunt/taint-analysis.js +429 -0
- package/dist/hunt/taint-analysis.js.map +1 -0
- package/dist/hunt/templates/csv-injection.d.ts +2 -0
- package/dist/hunt/templates/csv-injection.js +148 -0
- package/dist/hunt/templates/csv-injection.js.map +1 -0
- package/dist/hunt/templates/django-misconfig.d.ts +2 -0
- package/dist/hunt/templates/django-misconfig.js +172 -0
- package/dist/hunt/templates/django-misconfig.js.map +1 -0
- package/dist/hunt/templates/express-misconfig.d.ts +2 -0
- package/dist/hunt/templates/express-misconfig.js +156 -0
- package/dist/hunt/templates/express-misconfig.js.map +1 -0
- package/dist/hunt/templates/file-upload.d.ts +2 -0
- package/dist/hunt/templates/file-upload.js +131 -0
- package/dist/hunt/templates/file-upload.js.map +1 -0
- package/dist/hunt/templates/graphql-abuse.d.ts +2 -0
- package/dist/hunt/templates/graphql-abuse.js +161 -0
- package/dist/hunt/templates/graphql-abuse.js.map +1 -0
- package/dist/hunt/templates/hardcoded-credentials.d.ts +2 -0
- package/dist/hunt/templates/hardcoded-credentials.js +109 -0
- package/dist/hunt/templates/hardcoded-credentials.js.map +1 -0
- package/dist/hunt/templates/idor.d.ts +2 -0
- package/dist/hunt/templates/idor.js +102 -0
- package/dist/hunt/templates/idor.js.map +1 -0
- package/dist/hunt/templates/index.d.ts +2 -2
- package/dist/hunt/templates/index.js +38 -5
- package/dist/hunt/templates/index.js.map +1 -1
- package/dist/hunt/templates/insecure-deserialization.d.ts +2 -0
- package/dist/hunt/templates/insecure-deserialization.js +131 -0
- package/dist/hunt/templates/insecure-deserialization.js.map +1 -0
- package/dist/hunt/templates/mass-assignment.d.ts +2 -0
- package/dist/hunt/templates/mass-assignment.js +101 -0
- package/dist/hunt/templates/mass-assignment.js.map +1 -0
- package/dist/hunt/templates/nextjs-misconfig.d.ts +2 -0
- package/dist/hunt/templates/nextjs-misconfig.js +127 -0
- package/dist/hunt/templates/nextjs-misconfig.js.map +1 -0
- package/dist/hunt/templates/postmessage.d.ts +2 -0
- package/dist/hunt/templates/postmessage.js +180 -0
- package/dist/hunt/templates/postmessage.js.map +1 -0
- package/dist/hunt/templates/race-condition.d.ts +2 -0
- package/dist/hunt/templates/race-condition.js +138 -0
- package/dist/hunt/templates/race-condition.js.map +1 -0
- package/dist/hunt/templates/spring-misconfig.d.ts +2 -0
- package/dist/hunt/templates/spring-misconfig.js +177 -0
- package/dist/hunt/templates/spring-misconfig.js.map +1 -0
- package/dist/hunt/templates/xxe.d.ts +2 -0
- package/dist/hunt/templates/xxe.js +187 -0
- package/dist/hunt/templates/xxe.js.map +1 -0
- package/dist/hunt/triage.d.ts +2 -2
- package/dist/hunt/triage.js +4 -4
- package/dist/hunt/triage.js.map +1 -1
- package/dist/realtime/__tests__/catch-real-bugs.test.d.ts +9 -0
- package/dist/realtime/__tests__/catch-real-bugs.test.js +205 -0
- package/dist/realtime/__tests__/catch-real-bugs.test.js.map +1 -0
- package/dist/realtime/__tests__/code-buffer.test.d.ts +1 -0
- package/dist/realtime/__tests__/code-buffer.test.js +202 -0
- package/dist/realtime/__tests__/code-buffer.test.js.map +1 -0
- package/dist/realtime/__tests__/correction-injector.test.d.ts +1 -0
- package/dist/realtime/__tests__/correction-injector.test.js +168 -0
- package/dist/realtime/__tests__/correction-injector.test.js.map +1 -0
- package/dist/realtime/__tests__/stream-interceptor.test.d.ts +1 -0
- package/dist/realtime/__tests__/stream-interceptor.test.js +193 -0
- package/dist/realtime/__tests__/stream-interceptor.test.js.map +1 -0
- package/dist/realtime/__tests__/streaming-checks.test.d.ts +1 -0
- package/dist/realtime/__tests__/streaming-checks.test.js +479 -0
- package/dist/realtime/__tests__/streaming-checks.test.js.map +1 -0
- package/dist/realtime/__tests__/streaming-verifier.test.d.ts +1 -0
- package/dist/realtime/__tests__/streaming-verifier.test.js +157 -0
- package/dist/realtime/__tests__/streaming-verifier.test.js.map +1 -0
- package/dist/realtime/code-buffer.d.ts +52 -0
- package/dist/realtime/code-buffer.js +276 -0
- package/dist/realtime/code-buffer.js.map +1 -0
- package/dist/realtime/correction-injector.d.ts +56 -0
- package/dist/realtime/correction-injector.js +96 -0
- package/dist/realtime/correction-injector.js.map +1 -0
- package/dist/realtime/index.d.ts +14 -0
- package/dist/realtime/index.js +11 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/mcp-server.d.ts +14 -0
- package/dist/realtime/mcp-server.js +200 -0
- package/dist/realtime/mcp-server.js.map +1 -0
- package/dist/realtime/stream-interceptor.d.ts +65 -0
- package/dist/realtime/stream-interceptor.js +174 -0
- package/dist/realtime/stream-interceptor.js.map +1 -0
- package/dist/realtime/streaming-checks.d.ts +55 -0
- package/dist/realtime/streaming-checks.js +452 -0
- package/dist/realtime/streaming-checks.js.map +1 -0
- package/dist/realtime/streaming-verifier.d.ts +57 -0
- package/dist/realtime/streaming-verifier.js +134 -0
- package/dist/realtime/streaming-verifier.js.map +1 -0
- package/dist/realtime/types.d.ts +99 -0
- package/dist/realtime/types.js +8 -0
- package/dist/realtime/types.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { StreamingVerifier } from '../streaming-verifier.js';
|
|
3
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
4
|
+
/** Push a string one character at a time to simulate real token streaming. */
|
|
5
|
+
function pushCharByChar(verifier, code) {
|
|
6
|
+
const allEvents = [];
|
|
7
|
+
for (const ch of code) {
|
|
8
|
+
allEvents.push(...verifier.push(ch));
|
|
9
|
+
}
|
|
10
|
+
return allEvents;
|
|
11
|
+
}
|
|
12
|
+
// ── End-to-end: SQL injection caught ────────────────────────────
|
|
13
|
+
describe('StreamingVerifier', () => {
|
|
14
|
+
it('catches SQL injection via string concatenation', () => {
|
|
15
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
16
|
+
const code = 'const q = "SELECT * FROM users WHERE id = " + userId;\n';
|
|
17
|
+
const events = pushCharByChar(verifier, code);
|
|
18
|
+
const findings = events.filter(e => e.verdict === 'FAIL');
|
|
19
|
+
expect(findings.length).toBeGreaterThanOrEqual(1);
|
|
20
|
+
const sqlFinding = findings.find(e => e.checkId.includes('sql'));
|
|
21
|
+
expect(sqlFinding).toBeDefined();
|
|
22
|
+
expect(sqlFinding.severity).toBe('critical');
|
|
23
|
+
});
|
|
24
|
+
// ── End-to-end: Clean code passes ───────────────────────────
|
|
25
|
+
it('produces no findings for clean code', () => {
|
|
26
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
27
|
+
const code = 'const x = 1;\n';
|
|
28
|
+
const events = pushCharByChar(verifier, code);
|
|
29
|
+
const findings = events.filter(e => e.verdict === 'FAIL');
|
|
30
|
+
expect(findings.length).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
// ── Multi-unit stream ───────────────────────────────────────
|
|
33
|
+
it('handles multi-unit stream with mixed good and bad code', () => {
|
|
34
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
35
|
+
const code = [
|
|
36
|
+
'const x = 1;',
|
|
37
|
+
'const q = "SELECT * FROM users WHERE id = " + userId;',
|
|
38
|
+
'const y = 2;',
|
|
39
|
+
'',
|
|
40
|
+
].join('\n');
|
|
41
|
+
const events = pushCharByChar(verifier, code);
|
|
42
|
+
const findings = events.filter(e => e.verdict === 'FAIL');
|
|
43
|
+
// Only the SQL injection line should produce a finding
|
|
44
|
+
expect(findings.length).toBeGreaterThanOrEqual(1);
|
|
45
|
+
for (const f of findings) {
|
|
46
|
+
expect(f.codeUnit.text).toContain('SELECT');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// ── Flush catches trailing code ─────────────────────────────
|
|
50
|
+
it('flush catches trailing code without a terminator', () => {
|
|
51
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
52
|
+
// No semicolon, no newline — the buffer won't emit a code unit
|
|
53
|
+
const code = 'const secret = "sk-abc123secret456"';
|
|
54
|
+
verifier.push(code);
|
|
55
|
+
// No events from push since no code unit boundary was reached
|
|
56
|
+
const pushFindings = verifier.getFindings();
|
|
57
|
+
// Flush should create a trailing unit and check it
|
|
58
|
+
const flushEvents = verifier.flush();
|
|
59
|
+
const flushFindings = flushEvents.filter(e => e.verdict === 'FAIL');
|
|
60
|
+
expect(flushFindings.length).toBeGreaterThanOrEqual(1);
|
|
61
|
+
const secretFinding = flushFindings.find(e => e.checkId.includes('secret'));
|
|
62
|
+
expect(secretFinding).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
// ── Stats tracking ──────────────────────────────────────────
|
|
65
|
+
it('tracks stats correctly across multiple pushes', () => {
|
|
66
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
67
|
+
verifier.push('const a = 1;\n');
|
|
68
|
+
verifier.push('const b = 2;\n');
|
|
69
|
+
verifier.push('const q = "SELECT * FROM t WHERE id = " + x;\n');
|
|
70
|
+
const stats = verifier.getStats();
|
|
71
|
+
expect(stats.unitsProcessed).toBeGreaterThanOrEqual(3);
|
|
72
|
+
expect(stats.checksRun).toBeGreaterThan(0);
|
|
73
|
+
expect(stats.findings).toBeGreaterThanOrEqual(1);
|
|
74
|
+
expect(stats.totalTimeMs).toBeGreaterThanOrEqual(0);
|
|
75
|
+
expect(stats.avgLatencyMs).toBeGreaterThanOrEqual(0);
|
|
76
|
+
});
|
|
77
|
+
// ── onEvent callback fires ──────────────────────────────────
|
|
78
|
+
it('fires onEvent callback for findings', () => {
|
|
79
|
+
const callback = vi.fn();
|
|
80
|
+
const verifier = new StreamingVerifier({
|
|
81
|
+
language: 'typescript',
|
|
82
|
+
onEvent: callback,
|
|
83
|
+
});
|
|
84
|
+
verifier.push('const q = "SELECT * FROM users WHERE id = " + userId;\n');
|
|
85
|
+
expect(callback).toHaveBeenCalled();
|
|
86
|
+
const callArg = callback.mock.calls[0][0];
|
|
87
|
+
expect(callArg.verdict).toBe('FAIL');
|
|
88
|
+
expect(callArg.checkId).toContain('sql');
|
|
89
|
+
});
|
|
90
|
+
// ── getFindings filters correctly ───────────────────────────
|
|
91
|
+
it('getFindings returns only FAIL events', () => {
|
|
92
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
93
|
+
// Push clean code and bad code
|
|
94
|
+
verifier.push('const x = 1;\n');
|
|
95
|
+
verifier.push('const q = "SELECT * FROM users WHERE id = " + userId;\n');
|
|
96
|
+
verifier.push('const y = 2;\n');
|
|
97
|
+
const findings = verifier.getFindings();
|
|
98
|
+
// Every item in findings must be FAIL
|
|
99
|
+
for (const f of findings) {
|
|
100
|
+
expect(f.verdict).toBe('FAIL');
|
|
101
|
+
}
|
|
102
|
+
expect(findings.length).toBeGreaterThanOrEqual(1);
|
|
103
|
+
});
|
|
104
|
+
// ── Reset clears state ──────────────────────────────────────
|
|
105
|
+
it('reset clears all state', () => {
|
|
106
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
107
|
+
verifier.push('const q = "SELECT * FROM users WHERE id = " + userId;\n');
|
|
108
|
+
expect(verifier.getStats().unitsProcessed).toBeGreaterThan(0);
|
|
109
|
+
expect(verifier.getFindings().length).toBeGreaterThan(0);
|
|
110
|
+
expect(verifier.getGeneratedCode().length).toBeGreaterThan(0);
|
|
111
|
+
verifier.reset();
|
|
112
|
+
const stats = verifier.getStats();
|
|
113
|
+
expect(stats.unitsProcessed).toBe(0);
|
|
114
|
+
expect(stats.checksRun).toBe(0);
|
|
115
|
+
expect(stats.findings).toBe(0);
|
|
116
|
+
expect(stats.totalTimeMs).toBe(0);
|
|
117
|
+
expect(stats.maxLatencyMs).toBe(0);
|
|
118
|
+
expect(verifier.getFindings().length).toBe(0);
|
|
119
|
+
expect(verifier.getGeneratedCode()).toBe('');
|
|
120
|
+
});
|
|
121
|
+
// ── Function block verification ─────────────────────────────
|
|
122
|
+
it('catches SQL injection inside a complete function block', () => {
|
|
123
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
124
|
+
const code = 'function getUser() { const q = "SELECT * FROM users WHERE id = " + id; return db.query(q); }\n';
|
|
125
|
+
const events = pushCharByChar(verifier, code);
|
|
126
|
+
const findings = events.filter(e => e.verdict === 'FAIL');
|
|
127
|
+
expect(findings.length).toBeGreaterThanOrEqual(1);
|
|
128
|
+
const sqlFinding = findings.find(e => e.checkId.includes('sql'));
|
|
129
|
+
expect(sqlFinding).toBeDefined();
|
|
130
|
+
});
|
|
131
|
+
// ── Performance ─────────────────────────────────────────────
|
|
132
|
+
it('processes 20 statements in under 100ms', () => {
|
|
133
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
134
|
+
const start = performance.now();
|
|
135
|
+
for (let i = 0; i < 20; i++) {
|
|
136
|
+
verifier.push(`const x${i} = ${i};\n`);
|
|
137
|
+
}
|
|
138
|
+
const elapsed = performance.now() - start;
|
|
139
|
+
expect(elapsed).toBeLessThan(100);
|
|
140
|
+
expect(verifier.getStats().unitsProcessed).toBeGreaterThanOrEqual(20);
|
|
141
|
+
});
|
|
142
|
+
// ── getGeneratedCode returns full buffer ────────────────────
|
|
143
|
+
it('getGeneratedCode returns accumulated code', () => {
|
|
144
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
145
|
+
verifier.push('const a = 1;');
|
|
146
|
+
verifier.push('\nconst b = 2;\n');
|
|
147
|
+
expect(verifier.getGeneratedCode()).toBe('const a = 1;\nconst b = 2;\n');
|
|
148
|
+
});
|
|
149
|
+
// ── Flush is no-op when buffer is fully covered ─────────────
|
|
150
|
+
it('flush returns empty when all code was already checked', () => {
|
|
151
|
+
const verifier = new StreamingVerifier({ language: 'typescript' });
|
|
152
|
+
verifier.push('const x = 1;\n');
|
|
153
|
+
const flushEvents = verifier.flush();
|
|
154
|
+
expect(flushEvents.length).toBe(0);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=streaming-verifier.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-verifier.test.js","sourceRoot":"","sources":["../../../src/realtime/__tests__/streaming-verifier.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBuffer: Accumulates streaming tokens and detects completed code units.
|
|
3
|
+
*
|
|
4
|
+
* Tracks brace depth, string/comment state, and semicolons to determine when
|
|
5
|
+
* a statement, function, block, or line is complete. This lets the streaming
|
|
6
|
+
* verifier run checks against structurally complete code rather than arbitrary
|
|
7
|
+
* token boundaries.
|
|
8
|
+
*
|
|
9
|
+
* Known limitation: Regex literals are not handled. A regex containing braces
|
|
10
|
+
* (e.g. /\d{3}/) could desync the brace depth tracker. This is acceptable for
|
|
11
|
+
* MVP since regex-heavy code units are rare and the conservative emission
|
|
12
|
+
* strategy means a missed boundary just produces a larger unit later.
|
|
13
|
+
*/
|
|
14
|
+
import type { CodeUnit } from './types.js';
|
|
15
|
+
export declare class CodeBuffer {
|
|
16
|
+
private buffer;
|
|
17
|
+
private readonly lang;
|
|
18
|
+
private braceDepth;
|
|
19
|
+
private stringState;
|
|
20
|
+
private commentState;
|
|
21
|
+
private templateDepth;
|
|
22
|
+
private escaped;
|
|
23
|
+
/** Start offset (in the full buffer) of the current in-progress unit. */
|
|
24
|
+
private unitStart;
|
|
25
|
+
constructor(language: string);
|
|
26
|
+
/**
|
|
27
|
+
* Append tokens to the buffer and return any newly completed code units.
|
|
28
|
+
*/
|
|
29
|
+
push(tokens: string): CodeUnit[];
|
|
30
|
+
/** Return all accumulated text. */
|
|
31
|
+
getFullBuffer(): string;
|
|
32
|
+
/** Return configured language. */
|
|
33
|
+
getLanguage(): string;
|
|
34
|
+
/** Clear all state. */
|
|
35
|
+
reset(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Build a CodeUnit from unitStart..endIndex (inclusive) and advance unitStart.
|
|
38
|
+
* Returns null if the text is only whitespace.
|
|
39
|
+
*/
|
|
40
|
+
private emitUnit;
|
|
41
|
+
/**
|
|
42
|
+
* Try to emit a line unit at a newline character. Only emits if the line
|
|
43
|
+
* content doesn't end with `{` or `;` (those are handled by block/statement
|
|
44
|
+
* detectors instead).
|
|
45
|
+
*/
|
|
46
|
+
private tryEmitLine;
|
|
47
|
+
/**
|
|
48
|
+
* Upgrade a generic 'block' kind to 'function' if the text looks like a
|
|
49
|
+
* function or method declaration.
|
|
50
|
+
*/
|
|
51
|
+
private classifyKind;
|
|
52
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBuffer: Accumulates streaming tokens and detects completed code units.
|
|
3
|
+
*
|
|
4
|
+
* Tracks brace depth, string/comment state, and semicolons to determine when
|
|
5
|
+
* a statement, function, block, or line is complete. This lets the streaming
|
|
6
|
+
* verifier run checks against structurally complete code rather than arbitrary
|
|
7
|
+
* token boundaries.
|
|
8
|
+
*
|
|
9
|
+
* Known limitation: Regex literals are not handled. A regex containing braces
|
|
10
|
+
* (e.g. /\d{3}/) could desync the brace depth tracker. This is acceptable for
|
|
11
|
+
* MVP since regex-heavy code units are rare and the conservative emission
|
|
12
|
+
* strategy means a missed boundary just produces a larger unit later.
|
|
13
|
+
*/
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// CodeBuffer
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export class CodeBuffer {
|
|
18
|
+
buffer = '';
|
|
19
|
+
lang;
|
|
20
|
+
// Parser state ----------------------------------------------------------
|
|
21
|
+
braceDepth = 0;
|
|
22
|
+
stringState = 0 /* StringState.None */;
|
|
23
|
+
commentState = 0 /* CommentState.None */;
|
|
24
|
+
templateDepth = 0; // nesting of ${} inside template literals
|
|
25
|
+
escaped = false;
|
|
26
|
+
// Unit tracking ---------------------------------------------------------
|
|
27
|
+
/** Start offset (in the full buffer) of the current in-progress unit. */
|
|
28
|
+
unitStart = 0;
|
|
29
|
+
constructor(language) {
|
|
30
|
+
this.lang = language;
|
|
31
|
+
}
|
|
32
|
+
// -- Public API ---------------------------------------------------------
|
|
33
|
+
/**
|
|
34
|
+
* Append tokens to the buffer and return any newly completed code units.
|
|
35
|
+
*/
|
|
36
|
+
push(tokens) {
|
|
37
|
+
if (tokens.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
const units = [];
|
|
40
|
+
const startLen = this.buffer.length;
|
|
41
|
+
this.buffer += tokens;
|
|
42
|
+
for (let i = startLen; i < this.buffer.length; i++) {
|
|
43
|
+
const ch = this.buffer[i];
|
|
44
|
+
const prev = i > 0 ? this.buffer[i - 1] : '';
|
|
45
|
+
// ---- Handle escape sequences inside strings ----
|
|
46
|
+
if (this.escaped) {
|
|
47
|
+
this.escaped = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (this.stringState !== 0 /* StringState.None */ &&
|
|
51
|
+
ch === '\\') {
|
|
52
|
+
this.escaped = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// ---- Inside a line comment ----
|
|
56
|
+
if (this.commentState === 1 /* CommentState.LineComment */) {
|
|
57
|
+
if (ch === '\n') {
|
|
58
|
+
this.commentState = 0 /* CommentState.None */;
|
|
59
|
+
// Emit the comment line as a line unit (or skip if whitespace-only).
|
|
60
|
+
// This prevents comment text from merging into the next code unit.
|
|
61
|
+
const commentText = this.buffer.slice(this.unitStart, i + 1).trim();
|
|
62
|
+
if (commentText.length > 0) {
|
|
63
|
+
const unit = {
|
|
64
|
+
text: commentText,
|
|
65
|
+
kind: 'line',
|
|
66
|
+
startOffset: this.unitStart,
|
|
67
|
+
endOffset: i + 1,
|
|
68
|
+
language: this.lang,
|
|
69
|
+
};
|
|
70
|
+
this.unitStart = i + 1;
|
|
71
|
+
units.push(unit);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.unitStart = i + 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// ---- Inside a block comment ----
|
|
80
|
+
if (this.commentState === 2 /* CommentState.BlockComment */) {
|
|
81
|
+
if (ch === '/' && prev === '*') {
|
|
82
|
+
this.commentState = 0 /* CommentState.None */;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// ---- Inside a string literal ----
|
|
87
|
+
if (this.stringState !== 0 /* StringState.None */) {
|
|
88
|
+
if (this.stringState === 3 /* StringState.Backtick */) {
|
|
89
|
+
// Handle template expression ${...}
|
|
90
|
+
if (ch === '$' && i + 1 < this.buffer.length && this.buffer[i + 1] === '{') {
|
|
91
|
+
this.templateDepth++;
|
|
92
|
+
i++; // skip the {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (this.templateDepth > 0) {
|
|
96
|
+
if (ch === '{') {
|
|
97
|
+
this.templateDepth++;
|
|
98
|
+
}
|
|
99
|
+
else if (ch === '}') {
|
|
100
|
+
this.templateDepth--;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (ch === '`') {
|
|
105
|
+
this.stringState = 0 /* StringState.None */;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (this.stringState === 1 /* StringState.SingleQuote */ && ch === "'") {
|
|
109
|
+
this.stringState = 0 /* StringState.None */;
|
|
110
|
+
}
|
|
111
|
+
else if (this.stringState === 2 /* StringState.DoubleQuote */ && ch === '"') {
|
|
112
|
+
this.stringState = 0 /* StringState.None */;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// ---- Not inside string or comment: detect openers ----
|
|
117
|
+
// Comment detection (must come before string detection)
|
|
118
|
+
if (ch === '/') {
|
|
119
|
+
if (i + 1 < this.buffer.length) {
|
|
120
|
+
const next = this.buffer[i + 1];
|
|
121
|
+
if (next === '/') {
|
|
122
|
+
this.commentState = 1 /* CommentState.LineComment */;
|
|
123
|
+
i++; // skip second /
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (next === '*') {
|
|
127
|
+
this.commentState = 2 /* CommentState.BlockComment */;
|
|
128
|
+
i++; // skip *
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// String openers
|
|
134
|
+
if (ch === "'") {
|
|
135
|
+
this.stringState = 1 /* StringState.SingleQuote */;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === '"') {
|
|
139
|
+
this.stringState = 2 /* StringState.DoubleQuote */;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (ch === '`') {
|
|
143
|
+
this.stringState = 3 /* StringState.Backtick */;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// ---- Brace tracking ----
|
|
147
|
+
if (ch === '{') {
|
|
148
|
+
this.braceDepth++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ch === '}') {
|
|
152
|
+
this.braceDepth--;
|
|
153
|
+
if (this.braceDepth <= 0) {
|
|
154
|
+
this.braceDepth = 0;
|
|
155
|
+
// Look ahead: if the next non-whitespace is `;`, wait for it
|
|
156
|
+
// (e.g. `const fn = () => { ... };`)
|
|
157
|
+
const remaining = this.buffer.slice(i + 1);
|
|
158
|
+
const nextSignificant = remaining.match(/^\s*;/);
|
|
159
|
+
if (nextSignificant) {
|
|
160
|
+
// Don't emit yet; the semicolon handler will emit
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const unit = this.emitUnit(i, 'block');
|
|
164
|
+
if (unit)
|
|
165
|
+
units.push(unit);
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// ---- Statement detection (semicolon at depth 0) ----
|
|
170
|
+
if (ch === ';' && this.braceDepth === 0) {
|
|
171
|
+
const unit = this.emitUnit(i, 'statement');
|
|
172
|
+
if (unit)
|
|
173
|
+
units.push(unit);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// ---- Line detection (newline at depth 0) ----
|
|
177
|
+
if (ch === '\n' && this.braceDepth === 0) {
|
|
178
|
+
const unit = this.tryEmitLine(i);
|
|
179
|
+
if (unit)
|
|
180
|
+
units.push(unit);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return units;
|
|
185
|
+
}
|
|
186
|
+
/** Return all accumulated text. */
|
|
187
|
+
getFullBuffer() {
|
|
188
|
+
return this.buffer;
|
|
189
|
+
}
|
|
190
|
+
/** Return configured language. */
|
|
191
|
+
getLanguage() {
|
|
192
|
+
return this.lang;
|
|
193
|
+
}
|
|
194
|
+
/** Clear all state. */
|
|
195
|
+
reset() {
|
|
196
|
+
this.buffer = '';
|
|
197
|
+
this.braceDepth = 0;
|
|
198
|
+
this.stringState = 0 /* StringState.None */;
|
|
199
|
+
this.commentState = 0 /* CommentState.None */;
|
|
200
|
+
this.templateDepth = 0;
|
|
201
|
+
this.escaped = false;
|
|
202
|
+
this.unitStart = 0;
|
|
203
|
+
}
|
|
204
|
+
// -- Private helpers ----------------------------------------------------
|
|
205
|
+
/**
|
|
206
|
+
* Build a CodeUnit from unitStart..endIndex (inclusive) and advance unitStart.
|
|
207
|
+
* Returns null if the text is only whitespace.
|
|
208
|
+
*/
|
|
209
|
+
emitUnit(endIndex, kind) {
|
|
210
|
+
const text = this.buffer.slice(this.unitStart, endIndex + 1);
|
|
211
|
+
const trimmed = text.trim();
|
|
212
|
+
if (trimmed.length === 0) {
|
|
213
|
+
this.unitStart = endIndex + 1;
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
// Classify: if the text contains a function/class keyword at depth-0, upgrade kind
|
|
217
|
+
const resolvedKind = this.classifyKind(trimmed, kind);
|
|
218
|
+
const unit = {
|
|
219
|
+
text: trimmed,
|
|
220
|
+
kind: resolvedKind,
|
|
221
|
+
startOffset: this.unitStart,
|
|
222
|
+
endOffset: endIndex + 1,
|
|
223
|
+
language: this.lang,
|
|
224
|
+
};
|
|
225
|
+
this.unitStart = endIndex + 1;
|
|
226
|
+
return unit;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Try to emit a line unit at a newline character. Only emits if the line
|
|
230
|
+
* content doesn't end with `{` or `;` (those are handled by block/statement
|
|
231
|
+
* detectors instead).
|
|
232
|
+
*/
|
|
233
|
+
tryEmitLine(newlineIndex) {
|
|
234
|
+
const text = this.buffer.slice(this.unitStart, newlineIndex + 1);
|
|
235
|
+
const trimmed = text.trim();
|
|
236
|
+
if (trimmed.length === 0) {
|
|
237
|
+
this.unitStart = newlineIndex + 1;
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
// If the line ends with { or ;, don't emit as a line —
|
|
241
|
+
// the block/statement handler will catch it.
|
|
242
|
+
if (trimmed.endsWith('{') || trimmed.endsWith(';')) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
// Don't emit partial lines that are clearly continuations
|
|
246
|
+
// (e.g., line ending with an operator)
|
|
247
|
+
const lastChar = trimmed[trimmed.length - 1];
|
|
248
|
+
if (lastChar === '(' || lastChar === ',' || lastChar === '+' || lastChar === '-' || lastChar === '=') {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const unit = {
|
|
252
|
+
text: trimmed,
|
|
253
|
+
kind: 'line',
|
|
254
|
+
startOffset: this.unitStart,
|
|
255
|
+
endOffset: newlineIndex + 1,
|
|
256
|
+
language: this.lang,
|
|
257
|
+
};
|
|
258
|
+
this.unitStart = newlineIndex + 1;
|
|
259
|
+
return unit;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Upgrade a generic 'block' kind to 'function' if the text looks like a
|
|
263
|
+
* function or method declaration.
|
|
264
|
+
*/
|
|
265
|
+
classifyKind(text, fallback) {
|
|
266
|
+
if (fallback === 'statement' || fallback === 'line')
|
|
267
|
+
return fallback;
|
|
268
|
+
// Check for function-like patterns
|
|
269
|
+
if (/^\s*(export\s+)?(default\s+)?(async\s+)?function\b/.test(text) ||
|
|
270
|
+
/^\s*(export\s+)?(default\s+)?class\b/.test(text)) {
|
|
271
|
+
return 'function';
|
|
272
|
+
}
|
|
273
|
+
return fallback;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=code-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-buffer.js","sourceRoot":"","sources":["../../src/realtime/code-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqBH,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,MAAM,OAAO,UAAU;IACb,MAAM,GAAG,EAAE,CAAC;IACH,IAAI,CAAS;IAE9B,0EAA0E;IAClE,UAAU,GAAG,CAAC,CAAC;IACf,WAAW,4BAAiC;IAC5C,YAAY,6BAAmC;IAC/C,aAAa,GAAG,CAAC,CAAC,CAAC,0CAA0C;IAC7D,OAAO,GAAG,KAAK,CAAC;IAExB,0EAA0E;IAC1E,yEAAyE;IACjE,SAAS,GAAG,CAAC,CAAC;IAEtB,YAAY,QAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;IACvB,CAAC;IAED,0EAA0E;IAE1E;;OAEG;IACH,IAAI,CAAC,MAAc;QACjB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEnC,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QACpC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC;QAEtB,KAAK,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAE7C,mDAAmD;YACnD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,IACE,IAAI,CAAC,WAAW,6BAAqB;gBACrC,EAAE,KAAK,IAAI,EACX,CAAC;gBACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,SAAS;YACX,CAAC;YAED,kCAAkC;YAClC,IAAI,IAAI,CAAC,YAAY,qCAA6B,EAAE,CAAC;gBACnD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;oBAChB,IAAI,CAAC,YAAY,4BAAoB,CAAC;oBACtC,qEAAqE;oBACrE,mEAAmE;oBACnE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;oBACpE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3B,MAAM,IAAI,GAAa;4BACrB,IAAI,EAAE,WAAW;4BACjB,IAAI,EAAE,MAAM;4BACZ,WAAW,EAAE,IAAI,CAAC,SAAS;4BAC3B,SAAS,EAAE,CAAC,GAAG,CAAC;4BAChB,QAAQ,EAAE,IAAI,CAAC,IAAI;yBACpB,CAAC;wBACF,IAAI,CAAC,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;wBACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;oBACzB,CAAC;gBACH,CAAC;gBACD,SAAS;YACX,CAAC;YAED,mCAAmC;YACnC,IAAI,IAAI,CAAC,YAAY,sCAA8B,EAAE,CAAC;gBACpD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;oBAC/B,IAAI,CAAC,YAAY,4BAAoB,CAAC;gBACxC,CAAC;gBACD,SAAS;YACX,CAAC;YAED,oCAAoC;YACpC,IAAI,IAAI,CAAC,WAAW,6BAAqB,EAAE,CAAC;gBAC1C,IAAI,IAAI,CAAC,WAAW,iCAAyB,EAAE,CAAC;oBAC9C,oCAAoC;oBACpC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;wBAC3E,IAAI,CAAC,aAAa,EAAE,CAAC;wBACrB,CAAC,EAAE,CAAC,CAAC,aAAa;wBAClB,SAAS;oBACX,CAAC;oBACD,IAAI,IAAI,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;wBAC3B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;4BACf,IAAI,CAAC,aAAa,EAAE,CAAC;wBACvB,CAAC;6BAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;4BACtB,IAAI,CAAC,aAAa,EAAE,CAAC;wBACvB,CAAC;wBACD,SAAS;oBACX,CAAC;oBACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;wBACf,IAAI,CAAC,WAAW,2BAAmB,CAAC;oBACtC,CAAC;gBACH,CAAC;qBAAM,IAAI,IAAI,CAAC,WAAW,oCAA4B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;oBACtE,IAAI,CAAC,WAAW,2BAAmB,CAAC;gBACtC,CAAC;qBAAM,IAAI,IAAI,CAAC,WAAW,oCAA4B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;oBACtE,IAAI,CAAC,WAAW,2BAAmB,CAAC;gBACtC,CAAC;gBACD,SAAS;YACX,CAAC;YAED,yDAAyD;YAEzD,wDAAwD;YACxD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;oBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAChC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;wBACjB,IAAI,CAAC,YAAY,mCAA2B,CAAC;wBAC7C,CAAC,EAAE,CAAC,CAAC,gBAAgB;wBACrB,SAAS;oBACX,CAAC;oBACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;wBACjB,IAAI,CAAC,YAAY,oCAA4B,CAAC;wBAC9C,CAAC,EAAE,CAAC,CAAC,SAAS;wBACd,SAAS;oBACX,CAAC;gBACH,CAAC;YACH,CAAC;YAED,iBAAiB;YACjB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,WAAW,kCAA0B,CAAC;gBAC3C,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,WAAW,kCAA0B,CAAC;gBAC3C,SAAS;YACX,CAAC;YACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,WAAW,+BAAuB,CAAC;gBACxC,SAAS;YACX,CAAC;YAED,2BAA2B;YAC3B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,SAAS;YACX,CAAC;YAED,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;oBACpB,6DAA6D;oBAC7D,qCAAqC;oBACrC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC3C,MAAM,eAAe,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACjD,IAAI,eAAe,EAAE,CAAC;wBACpB,kDAAkD;wBAClD,SAAS;oBACX,CAAC;oBACD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;oBACvC,IAAI,IAAI;wBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC7B,CAAC;gBACD,SAAS;YACX,CAAC;YAED,uDAAuD;YACvD,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;gBAC3C,IAAI,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,SAAS;YACX,CAAC;YAED,gDAAgD;YAChD,IAAI,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;gBACzC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gBACjC,IAAI,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,SAAS;YACX,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mCAAmC;IACnC,aAAa;QACX,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,kCAAkC;IAClC,WAAW;QACT,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,uBAAuB;IACvB,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,WAAW,2BAAmB,CAAC;QACpC,IAAI,CAAC,YAAY,4BAAoB,CAAC;QACtC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,0EAA0E;IAE1E;;;OAGG;IACK,QAAQ,CAAC,QAAgB,EAAE,IAAkB;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,SAAS,GAAG,QAAQ,GAAG,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,mFAAmF;QACnF,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAEtD,MAAM,IAAI,GAAa;YACrB,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,IAAI,CAAC,SAAS;YAC3B,SAAS,EAAE,QAAQ,GAAG,CAAC;YACvB,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,QAAQ,GAAG,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACK,WAAW,CAAC,YAAoB;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,uDAAuD;QACvD,6CAA6C;QAC7C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,0DAA0D;QAC1D,uCAAuC;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACrG,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAa;YACrB,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,IAAI,CAAC,SAAS;YAC3B,SAAS,EAAE,YAAY,GAAG,CAAC;YAC3B,QAAQ,EAAE,IAAI,CAAC,IAAI;SACpB,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,IAAY,EAAE,QAAsB;QACvD,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,MAAM;YAAE,OAAO,QAAQ,CAAC;QACrE,mCAAmC;QACnC,IACE,oDAAoD,CAAC,IAAI,CAAC,IAAI,CAAC;YAC/D,sCAAsC,CAAC,IAAI,CAAC,IAAI,CAAC,EACjD,CAAC;YACD,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CorrectionInjector — decides whether to correct a finding mid-stream
|
|
3
|
+
* and builds the prompt messages for re-generation.
|
|
4
|
+
*
|
|
5
|
+
* Strategy: Prefill technique (Option B).
|
|
6
|
+
* When a finding triggers:
|
|
7
|
+
* 1. Stop the current stream
|
|
8
|
+
* 2. Take all text generated so far as the assistant's partial response (prefill)
|
|
9
|
+
* 3. Add a user message explaining the issue and requesting a fix
|
|
10
|
+
* 4. Resume generation — the LLM continues from where it left off
|
|
11
|
+
*/
|
|
12
|
+
import type { CheckSeverity, VerificationEvent } from './types.js';
|
|
13
|
+
declare const SEVERITY_RANK: Record<CheckSeverity, number>;
|
|
14
|
+
export interface CorrectionInjectorOptions {
|
|
15
|
+
/** Only correct findings at or above this severity. Default: 'high' */
|
|
16
|
+
minSeverity?: CheckSeverity;
|
|
17
|
+
/** Maximum corrections per stream to avoid infinite loops. Default: 5 */
|
|
18
|
+
maxCorrections?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CorrectionMessage {
|
|
21
|
+
role: 'user' | 'assistant';
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
export declare class CorrectionInjector {
|
|
25
|
+
private readonly minSeverity;
|
|
26
|
+
private readonly maxCorrections;
|
|
27
|
+
private correctionCount;
|
|
28
|
+
constructor(options?: CorrectionInjectorOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Decide if this finding warrants stopping and correcting.
|
|
31
|
+
*
|
|
32
|
+
* Returns false when:
|
|
33
|
+
* - verdict is not FAIL
|
|
34
|
+
* - severity is below minSeverity
|
|
35
|
+
* - maxCorrections has been reached
|
|
36
|
+
*/
|
|
37
|
+
shouldCorrect(event: VerificationEvent): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Build the messages array for the correction re-generation.
|
|
40
|
+
*
|
|
41
|
+
* Uses the Anthropic API prefill technique:
|
|
42
|
+
* 1. Original user prompt
|
|
43
|
+
* 2. Assistant prefill (everything generated so far)
|
|
44
|
+
* 3. User message explaining the issue
|
|
45
|
+
*
|
|
46
|
+
* The system prompt is passed separately to the API, not included here.
|
|
47
|
+
*/
|
|
48
|
+
buildCorrectionMessages(generatedSoFar: string, event: VerificationEvent, _originalSystemPrompt: string, originalUserPrompt: string): CorrectionMessage[];
|
|
49
|
+
/** Record that a correction was made (for tracking max corrections). */
|
|
50
|
+
recordCorrection(_event: VerificationEvent): void;
|
|
51
|
+
/** Get number of corrections made so far. */
|
|
52
|
+
getCorrectionCount(): number;
|
|
53
|
+
/** Reset correction counter (e.g. for a new stream). */
|
|
54
|
+
reset(): void;
|
|
55
|
+
}
|
|
56
|
+
export { SEVERITY_RANK };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CorrectionInjector — decides whether to correct a finding mid-stream
|
|
3
|
+
* and builds the prompt messages for re-generation.
|
|
4
|
+
*
|
|
5
|
+
* Strategy: Prefill technique (Option B).
|
|
6
|
+
* When a finding triggers:
|
|
7
|
+
* 1. Stop the current stream
|
|
8
|
+
* 2. Take all text generated so far as the assistant's partial response (prefill)
|
|
9
|
+
* 3. Add a user message explaining the issue and requesting a fix
|
|
10
|
+
* 4. Resume generation — the LLM continues from where it left off
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Severity ordering (higher index = more severe)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const SEVERITY_RANK = {
|
|
16
|
+
low: 0,
|
|
17
|
+
medium: 1,
|
|
18
|
+
high: 2,
|
|
19
|
+
critical: 3,
|
|
20
|
+
};
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// CorrectionInjector
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export class CorrectionInjector {
|
|
25
|
+
minSeverity;
|
|
26
|
+
maxCorrections;
|
|
27
|
+
correctionCount = 0;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.minSeverity = options?.minSeverity ?? 'high';
|
|
30
|
+
this.maxCorrections = options?.maxCorrections ?? 5;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decide if this finding warrants stopping and correcting.
|
|
34
|
+
*
|
|
35
|
+
* Returns false when:
|
|
36
|
+
* - verdict is not FAIL
|
|
37
|
+
* - severity is below minSeverity
|
|
38
|
+
* - maxCorrections has been reached
|
|
39
|
+
*/
|
|
40
|
+
shouldCorrect(event) {
|
|
41
|
+
if (event.verdict !== 'FAIL')
|
|
42
|
+
return false;
|
|
43
|
+
if (SEVERITY_RANK[event.severity] < SEVERITY_RANK[this.minSeverity])
|
|
44
|
+
return false;
|
|
45
|
+
if (this.correctionCount >= this.maxCorrections)
|
|
46
|
+
return false;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the messages array for the correction re-generation.
|
|
51
|
+
*
|
|
52
|
+
* Uses the Anthropic API prefill technique:
|
|
53
|
+
* 1. Original user prompt
|
|
54
|
+
* 2. Assistant prefill (everything generated so far)
|
|
55
|
+
* 3. User message explaining the issue
|
|
56
|
+
*
|
|
57
|
+
* The system prompt is passed separately to the API, not included here.
|
|
58
|
+
*/
|
|
59
|
+
buildCorrectionMessages(generatedSoFar, event, _originalSystemPrompt, originalUserPrompt) {
|
|
60
|
+
const correctionText = buildCorrectionText(event);
|
|
61
|
+
return [
|
|
62
|
+
{ role: 'user', content: originalUserPrompt },
|
|
63
|
+
{ role: 'assistant', content: generatedSoFar },
|
|
64
|
+
{ role: 'user', content: correctionText },
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
/** Record that a correction was made (for tracking max corrections). */
|
|
68
|
+
recordCorrection(_event) {
|
|
69
|
+
this.correctionCount++;
|
|
70
|
+
}
|
|
71
|
+
/** Get number of corrections made so far. */
|
|
72
|
+
getCorrectionCount() {
|
|
73
|
+
return this.correctionCount;
|
|
74
|
+
}
|
|
75
|
+
/** Reset correction counter (e.g. for a new stream). */
|
|
76
|
+
reset() {
|
|
77
|
+
this.correctionCount = 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function buildCorrectionText(event) {
|
|
84
|
+
const lines = [
|
|
85
|
+
'The code you just wrote has an issue:',
|
|
86
|
+
`${event.checkName}: ${event.evidence}`,
|
|
87
|
+
];
|
|
88
|
+
if (event.suggestion) {
|
|
89
|
+
lines.push(event.suggestion);
|
|
90
|
+
}
|
|
91
|
+
lines.push('', 'Please continue generating code, fixing this issue. Do not repeat the code you already wrote — just continue from where you left off with the fix applied.');
|
|
92
|
+
return lines.join('\n');
|
|
93
|
+
}
|
|
94
|
+
// Re-export for testing
|
|
95
|
+
export { SEVERITY_RANK };
|
|
96
|
+
//# sourceMappingURL=correction-injector.js.map
|