@rigour-labs/core 6.2.5 → 6.3.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.
- package/README.md +2 -0
- package/dist/discovery-signals.d.ts +27 -0
- package/dist/discovery-signals.js +96 -0
- package/dist/discovery.d.ts +1 -2
- package/dist/discovery.js +36 -49
- package/dist/discovery.test.js +84 -71
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +5 -1
- package/dist/gates/deep-intent.d.ts +3 -0
- package/dist/gates/deep-intent.js +57 -0
- package/dist/gates/deep-intent.test.d.ts +1 -0
- package/dist/gates/deep-intent.test.js +51 -0
- package/dist/gates/deep-runner.js +1 -0
- package/dist/gates/deprecated-apis-ast.d.ts +8 -0
- package/dist/gates/deprecated-apis-ast.js +25 -0
- package/dist/gates/deprecated-apis-rules-node.js +2 -8
- package/dist/gates/deprecated-apis.js +5 -1
- package/dist/gates/deprecated-apis.test.js +30 -0
- package/dist/gates/frontend-secret-exposure.js +5 -1
- package/dist/gates/frontend-secret-exposure.test.js +20 -0
- package/dist/gates/hallucinated-imports/framework-modules.d.ts +14 -0
- package/dist/gates/hallucinated-imports/framework-modules.js +75 -0
- package/dist/gates/hallucinated-imports/index.js +14 -6
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +16 -12
- package/dist/gates/hallucinated-imports/js-resolver.js +65 -153
- package/dist/gates/hallucinated-imports/ts-path-target.js +16 -0
- package/dist/gates/hallucinated-imports/tsconfig-paths.d.ts +16 -0
- package/dist/gates/hallucinated-imports/tsconfig-paths.js +120 -0
- package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +1 -0
- package/dist/gates/hallucinated-imports-sveltekit.test.js +108 -0
- package/dist/gates/runner.js +5 -0
- package/dist/gates/semantic-bugs.d.ts +23 -0
- package/dist/gates/semantic-bugs.js +48 -0
- package/dist/gates/semantic-bugs.test.d.ts +1 -0
- package/dist/gates/semantic-bugs.test.js +70 -0
- package/dist/gates/side-effect-analysis/index.js +11 -1
- package/dist/gates/side-effect-analysis.test.d.ts +1 -0
- package/dist/gates/side-effect-analysis.test.js +162 -0
- package/dist/gates/side-effect-helpers/pattern-detection.js +35 -7
- package/dist/gates/side-effect-helpers/resource-tracking.js +17 -10
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +6 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +20 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/inference/model-download.test.js +54 -2
- package/dist/inference/model-manager.d.ts +6 -0
- package/dist/inference/model-manager.js +58 -18
- package/dist/inference/sidecar-provider.js +12 -1
- package/dist/inference/types.d.ts +8 -2
- package/dist/inference/types.js +7 -3
- package/dist/semantic/ast.d.ts +29 -0
- package/dist/semantic/ast.js +122 -0
- package/dist/semantic/benchmark.d.ts +16 -0
- package/dist/semantic/benchmark.js +31 -0
- package/dist/semantic/benchmark.test.d.ts +1 -0
- package/dist/semantic/benchmark.test.js +18 -0
- package/dist/semantic/engine.d.ts +8 -0
- package/dist/semantic/engine.js +45 -0
- package/dist/semantic/intent/benchmark.d.ts +31 -0
- package/dist/semantic/intent/benchmark.js +74 -0
- package/dist/semantic/intent/intent-check.d.ts +43 -0
- package/dist/semantic/intent/intent-check.js +72 -0
- package/dist/semantic/intent/intent.test.d.ts +1 -0
- package/dist/semantic/intent/intent.test.js +46 -0
- package/dist/semantic/intent/parallel-reads.d.ts +23 -0
- package/dist/semantic/intent/parallel-reads.js +59 -0
- package/dist/semantic/learn/benchmark.d.ts +20 -0
- package/dist/semantic/learn/benchmark.js +71 -0
- package/dist/semantic/learn/compile.d.ts +4 -0
- package/dist/semantic/learn/compile.js +56 -0
- package/dist/semantic/learn/conditions.d.ts +22 -0
- package/dist/semantic/learn/conditions.js +93 -0
- package/dist/semantic/learn/extract.d.ts +30 -0
- package/dist/semantic/learn/extract.js +122 -0
- package/dist/semantic/learn/git-trees.d.ts +8 -0
- package/dist/semantic/learn/git-trees.js +32 -0
- package/dist/semantic/learn/identity.d.ts +18 -0
- package/dist/semantic/learn/identity.js +97 -0
- package/dist/semantic/learn/learn.d.ts +25 -0
- package/dist/semantic/learn/learn.js +109 -0
- package/dist/semantic/learn/learn.test.d.ts +1 -0
- package/dist/semantic/learn/learn.test.js +67 -0
- package/dist/semantic/learn/store.d.ts +4 -0
- package/dist/semantic/learn/store.js +39 -0
- package/dist/semantic/learn/types.d.ts +468 -0
- package/dist/semantic/learn/types.js +55 -0
- package/dist/semantic/origins.d.ts +42 -0
- package/dist/semantic/origins.js +149 -0
- package/dist/semantic/origins.test.d.ts +1 -0
- package/dist/semantic/origins.test.js +77 -0
- package/dist/semantic/program.d.ts +13 -0
- package/dist/semantic/program.js +62 -0
- package/dist/semantic/rules/credential-redirect.d.ts +2 -0
- package/dist/semantic/rules/credential-redirect.js +209 -0
- package/dist/semantic/rules/degraded-response-cached.d.ts +2 -0
- package/dist/semantic/rules/degraded-response-cached.js +271 -0
- package/dist/semantic/rules/in-memory-aggregation.d.ts +2 -0
- package/dist/semantic/rules/in-memory-aggregation.js +179 -0
- package/dist/semantic/summaries.d.ts +18 -0
- package/dist/semantic/summaries.js +33 -0
- package/dist/semantic/types.d.ts +32 -0
- package/dist/semantic/types.js +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/team-diagnostics.d.ts +22 -0
- package/dist/storage/team-diagnostics.js +58 -0
- package/dist/storage/team-diagnostics.test.d.ts +1 -0
- package/dist/storage/team-diagnostics.test.js +51 -0
- package/dist/storage/team-schema.d.ts +6 -0
- package/dist/storage/team-schema.js +82 -0
- package/dist/storage/team-store.js +25 -72
- package/dist/templates/universal-config.js +4 -0
- package/dist/types/index.d.ts +56 -0
- package/dist/types/index.js +7 -0
- package/package.json +6 -6
|
@@ -71,7 +71,7 @@ export function pathsOverlap(watchPath, writePath) {
|
|
|
71
71
|
// ═══════════════════════════════════════════════════════════════════
|
|
72
72
|
// LOOP & RECURSION ANALYSIS — Context-aware body extraction
|
|
73
73
|
// ═══════════════════════════════════════════════════════════════════
|
|
74
|
-
import { findBlockEndBrace, findBlockEndIndent } from './scope-analysis.js';
|
|
74
|
+
import { findBlockEndBrace, findBlockEndIndent, findExpressionEnd } from './scope-analysis.js';
|
|
75
75
|
/**
|
|
76
76
|
* Extract loop body with correct scope tracking.
|
|
77
77
|
* Uses brace/indent matching (not just "next N lines").
|
|
@@ -122,13 +122,24 @@ export function extractFunctionDefs(lines, lang) {
|
|
|
122
122
|
? findBlockEndIndent(lines, i)
|
|
123
123
|
: lang === 'rb'
|
|
124
124
|
? findBlockEndRuby(lines, i)
|
|
125
|
-
: findBlockEndBrace(lines, i);
|
|
125
|
+
: isExpressionArrow(lines, i, stripped, m.index) ? findExpressionEnd(lines, i) : findBlockEndBrace(lines, i);
|
|
126
126
|
defs.push({ name, start: i, end, params: lines[i] });
|
|
127
127
|
break;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
return defs;
|
|
131
131
|
}
|
|
132
|
+
/** An arrow whose body is an expression, not a `{ ... }` block. */
|
|
133
|
+
function isExpressionArrow(lines, line, stripped, matchAt) {
|
|
134
|
+
const arrow = stripped.indexOf('=>', matchAt);
|
|
135
|
+
if (arrow < 0)
|
|
136
|
+
return false;
|
|
137
|
+
const rest = stripped.slice(arrow + 2).trim();
|
|
138
|
+
if (rest !== '')
|
|
139
|
+
return !rest.startsWith('{');
|
|
140
|
+
const next = lines.slice(line + 1).find(l => l.trim() !== '');
|
|
141
|
+
return next !== undefined && !next.trim().startsWith('{');
|
|
142
|
+
}
|
|
132
143
|
function getFuncDefPatterns(lang) {
|
|
133
144
|
switch (lang) {
|
|
134
145
|
case 'py': return [/^\s*(?:async\s+)?def\s+(\w+)\s*\(/];
|
|
@@ -297,11 +308,24 @@ export function hasCatchWithContinue(body, lang) {
|
|
|
297
308
|
// ═══════════════════════════════════════════════════════════════════
|
|
298
309
|
// PROCESS & TIMER OPERATIONS
|
|
299
310
|
// ═══════════════════════════════════════════════════════════════════
|
|
311
|
+
/**
|
|
312
|
+
* Drop a trailing line comment (and whole comment lines) so prose such as
|
|
313
|
+
* "ported from the fork (verified …)" is not read as code. Strings are
|
|
314
|
+
* already blanked, so `//` inside a URL string is gone before this runs.
|
|
315
|
+
*/
|
|
316
|
+
function stripLineComment(line, lang) {
|
|
317
|
+
const trimmed = line.trim();
|
|
318
|
+
if (trimmed.startsWith('*') || trimmed.startsWith('/*'))
|
|
319
|
+
return '';
|
|
320
|
+
const marker = lang === 'py' || lang === 'rb' ? '#' : '//';
|
|
321
|
+
const at = line.indexOf(marker);
|
|
322
|
+
return at === -1 ? line : line.slice(0, at);
|
|
323
|
+
}
|
|
300
324
|
/**
|
|
301
325
|
* Check if a line contains a process spawn call.
|
|
302
326
|
*/
|
|
303
327
|
export function isProcessSpawn(line, lang) {
|
|
304
|
-
const stripped = stripStrings(line);
|
|
328
|
+
const stripped = stripLineComment(stripStrings(line), lang);
|
|
305
329
|
const patterns = getSpawnPatterns(lang);
|
|
306
330
|
for (const pat of patterns) {
|
|
307
331
|
const m = pat.exec(stripped);
|
|
@@ -314,9 +338,13 @@ function getSpawnPatterns(lang) {
|
|
|
314
338
|
switch (lang) {
|
|
315
339
|
case 'js':
|
|
316
340
|
case 'ts':
|
|
341
|
+
// A bare call (from a child_process import) or a call on a
|
|
342
|
+
// child_process namespace; `re.exec(src)` or `db.exec(sql)` is not a
|
|
343
|
+
// spawn. *Sync variants finish before returning, so they cannot orphan.
|
|
317
344
|
return [
|
|
318
|
-
|
|
319
|
-
|
|
345
|
+
/(?<![.\w$])(?:spawn|exec|execFile|fork)\s*\(/,
|
|
346
|
+
/(?<![.\w$])(?:execa|execaCommand)\s*\(/,
|
|
347
|
+
/\b(?:child_process|childProcess|cp)\.(?:spawn|exec|execFile|fork)\s*\(/,
|
|
320
348
|
];
|
|
321
349
|
case 'py':
|
|
322
350
|
return [
|
|
@@ -354,9 +382,9 @@ function getTimerCreatePatterns(lang) {
|
|
|
354
382
|
switch (lang) {
|
|
355
383
|
case 'js':
|
|
356
384
|
case 'ts':
|
|
385
|
+
// setTimeout fires once and cannot run unbounded; only setInterval repeats.
|
|
357
386
|
return [
|
|
358
387
|
[/\bsetInterval\s*\(/, 'setInterval'],
|
|
359
|
-
[/\bsetTimeout\s*\(/, 'setTimeout'],
|
|
360
388
|
];
|
|
361
389
|
case 'py':
|
|
362
390
|
return [
|
|
@@ -410,7 +438,7 @@ export function getProcessCleanupPatterns(lang) {
|
|
|
410
438
|
case 'js':
|
|
411
439
|
case 'ts':
|
|
412
440
|
return [
|
|
413
|
-
/\.on\s*\(\s*['"](?:exit|close)['"]/,
|
|
441
|
+
/\.(?:on|once|addListener)\s*\(\s*['"](?:exit|close)['"]/,
|
|
414
442
|
/\.kill\s*\(/,
|
|
415
443
|
/\.disconnect\s*\(/,
|
|
416
444
|
];
|
|
@@ -39,8 +39,8 @@ export function extractVariableBinding(line, lang) {
|
|
|
39
39
|
const jsMatch = stripped.match(/^(?:const|let|var|final|auto|val)\s+(\w+)\s*=\s*/);
|
|
40
40
|
if (jsMatch)
|
|
41
41
|
return jsMatch[1];
|
|
42
|
-
// Member assignment: `this.timer = ...`, `self.timer = ...`
|
|
43
|
-
const memberMatch = stripped.match(/^(?:this|self)\.([\w]+)\s*=\s*/);
|
|
42
|
+
// Member assignment: `this.timer = ...`, `self.timer = ...`, `this.#timer = ...`
|
|
43
|
+
const memberMatch = stripped.match(/^(?:this|self)\.(#?[\w]+)\s*=\s*/);
|
|
44
44
|
if (memberMatch)
|
|
45
45
|
return memberMatch[1];
|
|
46
46
|
// Simple assignment: `timer = ...`
|
|
@@ -68,26 +68,33 @@ export function hasCleanupForVariable(lines, varName, scopeStart, scopeEnd, clea
|
|
|
68
68
|
const scope = lines.slice(scopeStart, scopeEnd);
|
|
69
69
|
for (let i = 0; i < scope.length; i++) {
|
|
70
70
|
const stripped = stripStrings(scope[i]);
|
|
71
|
-
// Check cleanup patterns that reference the specific variable
|
|
71
|
+
// Check cleanup patterns that reference the specific variable. Some
|
|
72
|
+
// patterns need the quoted event name (`.once('exit', …)`), which
|
|
73
|
+
// stripStrings blanks, so they are also tried on the raw line.
|
|
72
74
|
for (const pat of cleanupPatterns) {
|
|
73
|
-
if (!pat.test(stripped))
|
|
75
|
+
if (!pat.test(stripped) && !pat.test(scope[i]))
|
|
74
76
|
continue;
|
|
75
|
-
// The cleanup call should reference our variable
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (varRef.test(stripped))
|
|
77
|
+
// The cleanup call should reference our variable. Lookarounds, not
|
|
78
|
+
// \b, so a private field such as `#timer` matches too.
|
|
79
|
+
if (variableReference(varName).test(stripped))
|
|
79
80
|
return true;
|
|
80
81
|
// Also check method calls on the variable: timer.close(), timer.stop()
|
|
81
82
|
// The pattern might match a generic .close() — check if it's on our var
|
|
82
83
|
}
|
|
83
84
|
// Direct method cleanup on the variable: varName.close(), varName.Stop()
|
|
84
|
-
const escapedVar = varName
|
|
85
|
-
const methodCleanup = new RegExp(
|
|
85
|
+
const escapedVar = escapeRegExp(varName);
|
|
86
|
+
const methodCleanup = new RegExp(`(?<![\\w$#])${escapedVar}\\.(?:close|stop|destroy|kill|terminate|dispose|cancel|shutdown|unsubscribe|disconnect|end|release|Clear|Stop|Dispose|Close|Cancel)\\s*\\(`);
|
|
86
87
|
if (methodCleanup.test(stripped))
|
|
87
88
|
return true;
|
|
88
89
|
}
|
|
89
90
|
return false;
|
|
90
91
|
}
|
|
92
|
+
function escapeRegExp(value) {
|
|
93
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
94
|
+
}
|
|
95
|
+
function variableReference(varName) {
|
|
96
|
+
return new RegExp(`(?<![\\w$#])${escapeRegExp(varName)}(?![\\w$])`);
|
|
97
|
+
}
|
|
91
98
|
/**
|
|
92
99
|
* Check if a line is inside a cleanup/teardown context.
|
|
93
100
|
*
|
|
@@ -14,6 +14,12 @@ export declare function findEnclosingFunction(lines: string[], lineIdx: number,
|
|
|
14
14
|
* Find the end of a brace-delimited block starting at `start`.
|
|
15
15
|
*/
|
|
16
16
|
export declare function findBlockEndBrace(lines: string[], start: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Find the end of an expression-bodied arrow (`const f = (x) => x + 1;`):
|
|
19
|
+
* the first line where brackets balance and the expression does not
|
|
20
|
+
* continue. Scanning for a `{` instead would run into the next block.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findExpressionEnd(lines: string[], start: number): number;
|
|
17
23
|
/**
|
|
18
24
|
* Find the end of an indentation-delimited block (Python).
|
|
19
25
|
*/
|
|
@@ -95,6 +95,26 @@ export function findBlockEndBrace(lines, start) {
|
|
|
95
95
|
}
|
|
96
96
|
return maxScan;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Find the end of an expression-bodied arrow (`const f = (x) => x + 1;`):
|
|
100
|
+
* the first line where brackets balance and the expression does not
|
|
101
|
+
* continue. Scanning for a `{` instead would run into the next block.
|
|
102
|
+
*/
|
|
103
|
+
export function findExpressionEnd(lines, start) {
|
|
104
|
+
let depth = 0;
|
|
105
|
+
const maxScan = Math.min(lines.length, start + 300);
|
|
106
|
+
for (let j = start; j < maxScan; j++) {
|
|
107
|
+
const stripped = stripStrings(lines[j]);
|
|
108
|
+
depth += bracketDelta(stripped);
|
|
109
|
+
const continues = /(?:=>|[=+\-*/%&|?:,.(])\s*$/.test(stripped.trimEnd());
|
|
110
|
+
if (depth <= 0 && !continues)
|
|
111
|
+
return j + 1;
|
|
112
|
+
}
|
|
113
|
+
return maxScan;
|
|
114
|
+
}
|
|
115
|
+
function bracketDelta(text) {
|
|
116
|
+
return (text.match(/[([{]/g)?.length ?? 0) - (text.match(/[)\]}]/g)?.length ?? 0);
|
|
117
|
+
}
|
|
98
118
|
/**
|
|
99
119
|
* Find the end of an indentation-delimited block (Python).
|
|
100
120
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './stor
|
|
|
31
31
|
export type { ProjectStats } from './storage/index.js';
|
|
32
32
|
export { recordInteractionEvidence, countInteractionEvidence, recordInteractionLesson, listLessons, listKnowledgeLessons, getApplicableLessons, transitionLesson, getRepositoryId, queueLocalLessonsForTeam } from './storage/index.js';
|
|
33
33
|
export type { ApplicableLessons, InteractionEvidence, LessonRecord, LessonState, LessonVisibility, LocalLessonImportOptions, LocalLessonImportResult } from './storage/index.js';
|
|
34
|
-
export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, } from './storage/index.js';
|
|
34
|
+
export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, explainTeamConnectionError, } from './storage/index.js';
|
|
35
35
|
export type { TeamConfiguration, TeamSemanticConfiguration, TeamModeStatus, TeamDoctorResult, SemanticKnowledgeCandidate, SemanticKnowledgeResult, } from './storage/index.js';
|
|
36
36
|
export { generateTemporalDriftReport, formatDriftSummary } from './services/temporal-drift.js';
|
|
37
37
|
export type { TemporalDriftReport, ProvenanceStream, MonthlyBucket, WeeklyBucket, DriftDirection } from './services/temporal-drift.js';
|
|
@@ -51,4 +51,8 @@ export * from './services/system-health.js';
|
|
|
51
51
|
export * from './services/agent-history.js';
|
|
52
52
|
export * from './services/engineering-knowledge-graph.js';
|
|
53
53
|
export * from './firewall/index.js';
|
|
54
|
+
export { learnFromFix, type LearnReport, type Rejection } from './semantic/learn/learn.js';
|
|
55
|
+
export { extractFixTrees, type FixTrees } from './semantic/learn/git-trees.js';
|
|
56
|
+
export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
|
|
57
|
+
export type { LearnedRule, LearnedPattern } from './semantic/learn/types.js';
|
|
54
58
|
export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export { openDatabase, isSQLiteAvailable, compactDatabase, getDatabaseSize, rese
|
|
|
30
30
|
// Local Project Memory (hybrid intelligence — SQLite-backed per-project learning)
|
|
31
31
|
export { checkLocalPatterns, persistAndReinforce, getProjectStats } from './storage/index.js';
|
|
32
32
|
export { recordInteractionEvidence, countInteractionEvidence, recordInteractionLesson, listLessons, listKnowledgeLessons, getApplicableLessons, transitionLesson, getRepositoryId, queueLocalLessonsForTeam } from './storage/index.js';
|
|
33
|
-
export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, } from './storage/index.js';
|
|
33
|
+
export { loadTeamConfiguration, saveTeamConfiguration, initializeTeamSchema, getTeamModeStatus, doctorTeamConnection, syncTeamOutbox, searchTeamKnowledge, backfillTeamEmbeddings, TEAM_CONFIG_PATH, validateTeamDatabaseUrl, explainTeamConnectionError, } from './storage/index.js';
|
|
34
34
|
// Temporal Drift Engine (v5 — cross-session trend analysis + per-provenance EWMA)
|
|
35
35
|
export { generateTemporalDriftReport, formatDriftSummary } from './services/temporal-drift.js';
|
|
36
36
|
// Adaptive Thresholds (v5 — Z-score + per-provenance trends)
|
|
@@ -50,6 +50,9 @@ export * from './services/system-health.js';
|
|
|
50
50
|
export * from './services/agent-history.js';
|
|
51
51
|
export * from './services/engineering-knowledge-graph.js';
|
|
52
52
|
export * from './firewall/index.js';
|
|
53
|
+
export { learnFromFix } from './semantic/learn/learn.js';
|
|
54
|
+
export { extractFixTrees } from './semantic/learn/git-trees.js';
|
|
55
|
+
export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
|
|
53
56
|
export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
|
|
54
57
|
// Pattern Index is intentionally NOT exported here to prevent
|
|
55
58
|
// native dependency issues (sharp/transformers) from leaking into
|
|
@@ -9,7 +9,7 @@ const home = vi.hoisted(() => {
|
|
|
9
9
|
return p.join(o.tmpdir(), `rigour-models-${process.pid}-${Date.now()}`);
|
|
10
10
|
});
|
|
11
11
|
vi.mock('../storage/db.js', () => ({ RIGOUR_DIR: home }));
|
|
12
|
-
const { downloadModel, getCachedModel } = await import('./model-manager.js');
|
|
12
|
+
const { checkForUpdates, downloadModel, getCachedModel, normalizeModelVersion } = await import('./model-manager.js');
|
|
13
13
|
const { FALLBACK_MODELS, MODELS } = await import('./types.js');
|
|
14
14
|
const modelsDir = path.join(home, 'models');
|
|
15
15
|
const PAYLOAD = Buffer.from('gguf-model-bytes');
|
|
@@ -22,7 +22,7 @@ describe('downloadModel', () => {
|
|
|
22
22
|
beforeEach(async () => {
|
|
23
23
|
await fs.remove(home);
|
|
24
24
|
// A fresh version check: no network call to Hugging Face.
|
|
25
|
-
await fs.outputJson(path.join(modelsDir, '.latest_version.json'), { version: '
|
|
25
|
+
await fs.outputJson(path.join(modelsDir, '.latest_version.json'), { format: 2, version: '5', checkedAt: new Date().toISOString() });
|
|
26
26
|
});
|
|
27
27
|
afterAll(async () => { await fs.remove(home); });
|
|
28
28
|
it('falls back to the stock model when the fine-tuned model is unavailable, and says so', async () => {
|
|
@@ -39,6 +39,19 @@ describe('downloadModel', () => {
|
|
|
39
39
|
const meta = await fs.readJson(`${modelPath}.meta.json`);
|
|
40
40
|
expect(meta).toMatchObject({ sha256: PAYLOAD_SHA, sizeBytes: PAYLOAD.length, sourceUrl: FALLBACK_MODELS.lite.url });
|
|
41
41
|
});
|
|
42
|
+
it('retries the fine-tuned model once a day when only the stock fallback is cached', async () => {
|
|
43
|
+
const failing = (url) => (url.includes('rigour-labs/') ? unauthorized : ok(PAYLOAD, PAYLOAD_SHA));
|
|
44
|
+
const stockPath = await downloadModel('lite', undefined, failing);
|
|
45
|
+
const requested = [];
|
|
46
|
+
const again = await downloadModel('lite', undefined, (url) => { requested.push(url); return unauthorized; });
|
|
47
|
+
expect(requested).toEqual([]); // failed less than a day ago: keep the fallback, no request
|
|
48
|
+
expect(again).toBe(stockPath);
|
|
49
|
+
await fs.writeJson(path.join(modelsDir, '.fine-tuned-unavailable-lite.json'), { failedAt: '2000-01-01T00:00:00Z', url: MODELS.lite.url });
|
|
50
|
+
const fineTuned = await downloadModel('lite', undefined, (url) => { requested.push(url); return ok(PAYLOAD, PAYLOAD_SHA); });
|
|
51
|
+
expect(requested).toEqual([MODELS.lite.url]);
|
|
52
|
+
expect(path.basename(fineTuned)).toBe(MODELS.lite.filename);
|
|
53
|
+
expect(await fs.pathExists(path.join(modelsDir, '.fine-tuned-unavailable-lite.json'))).toBe(false);
|
|
54
|
+
});
|
|
42
55
|
it('rejects a checksum mismatch and deletes the partial download', async () => {
|
|
43
56
|
const get = () => ok(PAYLOAD, 'f'.repeat(64));
|
|
44
57
|
await expect(downloadModel('lite', undefined, get)).rejects.toThrow('Checksum mismatch');
|
|
@@ -65,9 +78,48 @@ describe('getCachedModel', () => {
|
|
|
65
78
|
const cached = await getCachedModel('deep');
|
|
66
79
|
expect(cached).toMatchObject({ fallback: true, info: { filename: FALLBACK_MODELS.deep.filename } });
|
|
67
80
|
});
|
|
81
|
+
it('accepts a verified fine-tuned file smaller than the size estimate', async () => {
|
|
82
|
+
await cacheFile(MODELS.lite.filename, 397_807_360); // the published v5 lite model
|
|
83
|
+
expect(await getCachedModel('lite')).toMatchObject({ fallback: false, info: { filename: MODELS.lite.filename } });
|
|
84
|
+
});
|
|
85
|
+
it('accepts a file verified within the same millisecond it was written', async () => {
|
|
86
|
+
const file = path.join(modelsDir, FALLBACK_MODELS.deep.filename);
|
|
87
|
+
await cacheFile(FALLBACK_MODELS.deep.filename, 10);
|
|
88
|
+
const written = new Date('2026-01-01T00:00:00.000Z');
|
|
89
|
+
await fs.utimes(file, written, written.getTime() / 1000 + 0.0007); // mtime 0.7 ms into the same millisecond
|
|
90
|
+
await fs.writeJson(`${file}.meta.json`, { sha256: 'a'.repeat(64), sizeBytes: 10, verifiedAt: written.toISOString(), sourceUrl: 'https://x' });
|
|
91
|
+
expect(await getCachedModel('deep')).toMatchObject({ fallback: true });
|
|
92
|
+
});
|
|
93
|
+
it('rejects a file modified after it was verified', async () => {
|
|
94
|
+
const file = path.join(modelsDir, FALLBACK_MODELS.deep.filename);
|
|
95
|
+
await cacheFile(FALLBACK_MODELS.deep.filename, 10);
|
|
96
|
+
await fs.writeJson(`${file}.meta.json`, { sha256: 'a'.repeat(64), sizeBytes: 10, verifiedAt: '2000-01-01T00:00:00.000Z', sourceUrl: 'https://x' });
|
|
97
|
+
expect(await getCachedModel('deep')).toBeNull();
|
|
98
|
+
});
|
|
68
99
|
it('ignores a file without verified metadata', async () => {
|
|
69
100
|
await fs.ensureDir(modelsDir);
|
|
70
101
|
await fs.writeFile(path.join(modelsDir, FALLBACK_MODELS.deep.filename), 'partial');
|
|
71
102
|
expect(await getCachedModel('deep')).toBeNull();
|
|
72
103
|
});
|
|
73
104
|
});
|
|
105
|
+
describe('model versions', () => {
|
|
106
|
+
beforeEach(async () => { await fs.remove(home); vi.unstubAllGlobals(); });
|
|
107
|
+
it('keeps published version strings as they are', () => {
|
|
108
|
+
expect(normalizeModelVersion(5)).toBe('5');
|
|
109
|
+
expect(normalizeModelVersion('5')).toBe('5');
|
|
110
|
+
expect(normalizeModelVersion('v2.1.0')).toBe('2.1.0');
|
|
111
|
+
expect(normalizeModelVersion('5.0')).toBeNull();
|
|
112
|
+
expect(normalizeModelVersion({})).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
it('names the published v5 repositories by default', () => {
|
|
115
|
+
expect(MODELS.lite.url).toBe('https://huggingface.co/rigour-labs/rigour-lite-v5-gguf/resolve/main/rigour-lite-v5-q4_k_m.gguf');
|
|
116
|
+
expect(MODELS.deep.url).toBe('https://huggingface.co/rigour-labs/rigour-deep-v5-gguf/resolve/main/rigour-deep-v5-q4_k_m.gguf');
|
|
117
|
+
});
|
|
118
|
+
it('ignores a version cache from the old SemVer conversion and re-reads the published version', async () => {
|
|
119
|
+
await fs.outputJson(path.join(modelsDir, '.latest_version.json'), { version: '5.0.0', checkedAt: new Date().toISOString() });
|
|
120
|
+
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, json: async () => ({ version: 5 }) })));
|
|
121
|
+
expect(await checkForUpdates()).toBe('5');
|
|
122
|
+
expect(await fs.readJson(path.join(modelsDir, '.latest_version.json'))).toMatchObject({ format: 2, version: '5' });
|
|
123
|
+
expect(MODELS.deep.filename).toBe('rigour-deep-v5-q4_k_m.gguf');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -26,6 +26,12 @@ export declare function getModelPath(tier: ModelTier): string;
|
|
|
26
26
|
* Get model info for a tier.
|
|
27
27
|
*/
|
|
28
28
|
export declare function getModelInfo(tier: ModelTier): ModelInfo;
|
|
29
|
+
/**
|
|
30
|
+
* The version exactly as published. Integer versions (the RLAIF pipeline
|
|
31
|
+
* before SemVer) are published as `v5`, SemVer versions as `v2.1.0`; either
|
|
32
|
+
* way the string goes into the repository and file names unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizeModelVersion(raw: unknown): string | null;
|
|
29
35
|
/**
|
|
30
36
|
* Check HuggingFace for a newer model version (like antivirus signature updates).
|
|
31
37
|
* Reads latest_version.json from the RLAIF dataset repo. Non-blocking — if the
|
|
@@ -8,6 +8,7 @@ import { createHash } from 'crypto';
|
|
|
8
8
|
import { RIGOUR_DIR } from '../storage/db.js';
|
|
9
9
|
import { downloadToFile } from './http-download.js';
|
|
10
10
|
import { MODELS, FALLBACK_MODELS, VERSION_CHECK_URL, BUNDLED_MODEL_VERSION, updateModelVersion } from './types.js';
|
|
11
|
+
import { Logger } from '../utils/logger.js';
|
|
11
12
|
const MODELS_DIR = path.join(RIGOUR_DIR, 'models');
|
|
12
13
|
const VERSION_CACHE_PATH = path.join(MODELS_DIR, '.latest_version.json');
|
|
13
14
|
const VERSION_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // Check once per day
|
|
@@ -63,13 +64,14 @@ async function isFileCached(model) {
|
|
|
63
64
|
const metadata = await readModelMeta(model.filename);
|
|
64
65
|
if (!metadata)
|
|
65
66
|
return false;
|
|
67
|
+
// The metadata is written only after the checksum passed, so its exact size is
|
|
68
|
+
// the completeness check; ModelInfo sizes are display estimates that differ by version.
|
|
66
69
|
const stat = await fs.stat(modelPath);
|
|
67
|
-
const tolerance = model.sizeBytes * 0.1;
|
|
68
|
-
if (stat.size <= model.sizeBytes - tolerance)
|
|
69
|
-
return false;
|
|
70
70
|
if (metadata.sizeBytes !== stat.size)
|
|
71
71
|
return false;
|
|
72
|
-
|
|
72
|
+
// verifiedAt has millisecond precision and mtimeMs a sub-millisecond fraction: compare
|
|
73
|
+
// whole milliseconds, or a file verified within the same millisecond looks modified.
|
|
74
|
+
if (new Date(metadata.verifiedAt).getTime() < Math.floor(stat.mtimeMs))
|
|
73
75
|
return false;
|
|
74
76
|
return true;
|
|
75
77
|
}
|
|
@@ -161,6 +163,27 @@ async function downloadFromUrl(model, onProgress, get) {
|
|
|
161
163
|
onProgress?.(`Model ${model.name} ready`, 100);
|
|
162
164
|
return destPath;
|
|
163
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Version cache format. Format 1 turned a published integer version into
|
|
168
|
+
* SemVer ("5" -> "5.0.0"), which names repositories that were never published,
|
|
169
|
+
* so those caches are ignored.
|
|
170
|
+
*/
|
|
171
|
+
const VERSION_CACHE_FORMAT = 2;
|
|
172
|
+
/** How long a failed fine-tuned download keeps the stock fallback before retrying. */
|
|
173
|
+
const FINE_TUNED_RETRY_MS = 24 * 60 * 60 * 1000;
|
|
174
|
+
/**
|
|
175
|
+
* The version exactly as published. Integer versions (the RLAIF pipeline
|
|
176
|
+
* before SemVer) are published as `v5`, SemVer versions as `v2.1.0`; either
|
|
177
|
+
* way the string goes into the repository and file names unchanged.
|
|
178
|
+
*/
|
|
179
|
+
export function normalizeModelVersion(raw) {
|
|
180
|
+
if (typeof raw === 'number' && Number.isInteger(raw) && raw > 0)
|
|
181
|
+
return String(raw);
|
|
182
|
+
if (typeof raw !== 'string')
|
|
183
|
+
return null;
|
|
184
|
+
const value = raw.trim().replace(/^v/, '');
|
|
185
|
+
return /^\d+$/.test(value) || /^\d+\.\d+\.\d+$/.test(value) ? value : null;
|
|
186
|
+
}
|
|
164
187
|
/**
|
|
165
188
|
* Check HuggingFace for a newer model version (like antivirus signature updates).
|
|
166
189
|
* Reads latest_version.json from the RLAIF dataset repo. Non-blocking — if the
|
|
@@ -175,8 +198,8 @@ export async function checkForUpdates(onProgress) {
|
|
|
175
198
|
if (await fs.pathExists(VERSION_CACHE_PATH)) {
|
|
176
199
|
const cached = await fs.readJson(VERSION_CACHE_PATH);
|
|
177
200
|
const age = Date.now() - new Date(cached.checkedAt).getTime();
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
const v = cached.format === VERSION_CACHE_FORMAT ? normalizeModelVersion(cached.version) : null;
|
|
202
|
+
if (age < VERSION_CHECK_INTERVAL_MS && v) {
|
|
180
203
|
updateModelVersion(v);
|
|
181
204
|
return v;
|
|
182
205
|
}
|
|
@@ -193,13 +216,10 @@ export async function checkForUpdates(onProgress) {
|
|
|
193
216
|
clearTimeout(timeout);
|
|
194
217
|
if (response.ok) {
|
|
195
218
|
const data = await response.json();
|
|
196
|
-
|
|
197
|
-
let rawVersion = data.version ?? BUNDLED_MODEL_VERSION;
|
|
198
|
-
const latestVersion = typeof rawVersion === 'number'
|
|
199
|
-
? `${rawVersion}.0.0`
|
|
200
|
-
: String(rawVersion);
|
|
219
|
+
const latestVersion = normalizeModelVersion(data.version) ?? BUNDLED_MODEL_VERSION;
|
|
201
220
|
// Cache the result locally
|
|
202
221
|
await fs.writeJson(VERSION_CACHE_PATH, {
|
|
222
|
+
format: VERSION_CACHE_FORMAT,
|
|
203
223
|
version: latestVersion,
|
|
204
224
|
checkedAt: new Date().toISOString(),
|
|
205
225
|
source: 'huggingface',
|
|
@@ -226,22 +246,42 @@ export async function downloadModel(tier, onProgress, get) {
|
|
|
226
246
|
// Check for newer model version (non-blocking, cached 24h)
|
|
227
247
|
await checkForUpdates(onProgress);
|
|
228
248
|
const cached = await getCachedModel(tier);
|
|
229
|
-
if (cached) {
|
|
249
|
+
if (cached && !cached.fallback) {
|
|
230
250
|
onProgress?.(`Model ${cached.info.name} already cached`, 100);
|
|
231
251
|
return cached.path;
|
|
232
252
|
}
|
|
253
|
+
// Only the stock fallback is cached: try the fine-tuned model again, at most once a day,
|
|
254
|
+
// so one failed download does not pin a user to the stock model forever.
|
|
255
|
+
if (cached && !(await fineTunedRetryDue(tier)))
|
|
256
|
+
return cached.path;
|
|
233
257
|
const model = MODELS[tier];
|
|
234
258
|
onProgress?.(`Downloading ${model.name} (${model.sizeHuman})...`, 0);
|
|
235
259
|
try {
|
|
236
|
-
|
|
260
|
+
const downloaded = await downloadFromUrl(model, onProgress, get);
|
|
261
|
+
await fs.remove(retryMarker(tier)).catch(() => { });
|
|
262
|
+
return downloaded;
|
|
237
263
|
}
|
|
238
264
|
catch (error) {
|
|
239
265
|
const fallback = FALLBACK_MODELS[tier];
|
|
240
|
-
if (fallback
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
266
|
+
if (!fallback || fallback.url === model.url)
|
|
267
|
+
throw error;
|
|
268
|
+
const reason = error?.message ?? 'unknown error';
|
|
269
|
+
Logger.warn(`Fine-tuned model ${model.name} could not be downloaded from ${model.url} (${reason}); using the stock ${fallback.name}. Deep findings will be less accurate.`);
|
|
270
|
+
onProgress?.(`Fine-tuned model unavailable (${reason}); using stock ${fallback.name}`, 0);
|
|
271
|
+
await fs.writeJson(retryMarker(tier), { failedAt: new Date().toISOString(), url: model.url, reason }).catch(() => { });
|
|
272
|
+
return cached?.path ?? downloadFromUrl(fallback, onProgress, get);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function retryMarker(tier) {
|
|
276
|
+
return path.join(MODELS_DIR, `.fine-tuned-unavailable-${tier}.json`);
|
|
277
|
+
}
|
|
278
|
+
async function fineTunedRetryDue(tier) {
|
|
279
|
+
try {
|
|
280
|
+
const marker = await fs.readJson(retryMarker(tier));
|
|
281
|
+
return Date.now() - new Date(marker.failedAt).getTime() >= FINE_TUNED_RETRY_MS || marker.url !== MODELS[tier].url;
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return true;
|
|
245
285
|
}
|
|
246
286
|
}
|
|
247
287
|
/**
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Code never leaves the machine: prompts go to a local child process.
|
|
11
11
|
*/
|
|
12
|
+
import crypto from 'crypto';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import os from 'os';
|
|
14
15
|
import fs from 'fs-extra';
|
|
@@ -88,7 +89,7 @@ export class SidecarProvider {
|
|
|
88
89
|
maxTokens: options?.maxTokens || 1024,
|
|
89
90
|
threads: this.threads,
|
|
90
91
|
temperature: options?.temperature ?? 0.1,
|
|
91
|
-
schemaPath: options?.jsonMode ? this.schemaPath ?? undefined : undefined,
|
|
92
|
+
schemaPath: options?.jsonSchema ? await schemaFileFor(options.jsonSchema) : options?.jsonMode ? this.schemaPath ?? undefined : undefined,
|
|
92
93
|
});
|
|
93
94
|
let result;
|
|
94
95
|
try {
|
|
@@ -159,6 +160,16 @@ async function findOnPath(name) {
|
|
|
159
160
|
return null;
|
|
160
161
|
}
|
|
161
162
|
}
|
|
163
|
+
/** A schema file per distinct schema, written once and reused across calls. */
|
|
164
|
+
async function schemaFileFor(schema) {
|
|
165
|
+
const text = JSON.stringify(schema);
|
|
166
|
+
const schemaPath = path.join(managedEngineDir(), '..', 'schemas', `${crypto.createHash('sha256').update(text).digest('hex').slice(0, 16)}.json`);
|
|
167
|
+
if (!(await fs.pathExists(schemaPath))) {
|
|
168
|
+
await fs.ensureDir(path.dirname(schemaPath));
|
|
169
|
+
await fs.writeFile(schemaPath, text);
|
|
170
|
+
}
|
|
171
|
+
return schemaPath;
|
|
172
|
+
}
|
|
162
173
|
async function writeFindingsSchema() {
|
|
163
174
|
const schemaPath = path.join(managedEngineDir(), '..', 'findings.schema.json');
|
|
164
175
|
await fs.ensureDir(path.dirname(schemaPath));
|
|
@@ -29,6 +29,8 @@ export interface InferenceOptions {
|
|
|
29
29
|
temperature?: number;
|
|
30
30
|
timeout?: number;
|
|
31
31
|
jsonMode?: boolean;
|
|
32
|
+
/** Constrain local output to this JSON schema instead of the findings schema. */
|
|
33
|
+
jsonSchema?: Record<string, unknown>;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* A single finding from deep LLM analysis.
|
|
@@ -84,12 +86,16 @@ export interface ModelInfo {
|
|
|
84
86
|
* At startup, model-manager checks HF for the latest version and downloads
|
|
85
87
|
* it automatically (like antivirus signature updates).
|
|
86
88
|
*
|
|
87
|
-
*
|
|
89
|
+
* The fallback is the last version actually published: v5, from the RLAIF
|
|
90
|
+
* pipeline's integer versions. SemVer versions (MAJOR.MINOR.PATCH) are
|
|
91
|
+
* published under their SemVer names.
|
|
92
|
+
*
|
|
93
|
+
* Version format: SemVer (MAJOR.MINOR.PATCH), or a legacy integer
|
|
88
94
|
* - MAJOR: Training data format change, base model change, pipeline architecture
|
|
89
95
|
* - MINOR: New training repos, updated dataset, hyperparameter improvements
|
|
90
96
|
* - PATCH: Bug fixes, retraining with same data/format
|
|
91
97
|
*/
|
|
92
|
-
export declare const BUNDLED_MODEL_VERSION = "
|
|
98
|
+
export declare const BUNDLED_MODEL_VERSION = "5";
|
|
93
99
|
/** HuggingFace dataset repo where latest_version.json lives */
|
|
94
100
|
export declare const VERSION_CHECK_URL = "https://huggingface.co/datasets/rigour-labs/rigour-rlaif-data/resolve/main/latest_version.json";
|
|
95
101
|
/** Build model info for a given tier and version */
|
package/dist/inference/types.js
CHANGED
|
@@ -5,19 +5,23 @@
|
|
|
5
5
|
* At startup, model-manager checks HF for the latest version and downloads
|
|
6
6
|
* it automatically (like antivirus signature updates).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* The fallback is the last version actually published: v5, from the RLAIF
|
|
9
|
+
* pipeline's integer versions. SemVer versions (MAJOR.MINOR.PATCH) are
|
|
10
|
+
* published under their SemVer names.
|
|
11
|
+
*
|
|
12
|
+
* Version format: SemVer (MAJOR.MINOR.PATCH), or a legacy integer
|
|
9
13
|
* - MAJOR: Training data format change, base model change, pipeline architecture
|
|
10
14
|
* - MINOR: New training repos, updated dataset, hyperparameter improvements
|
|
11
15
|
* - PATCH: Bug fixes, retraining with same data/format
|
|
12
16
|
*/
|
|
13
|
-
export const BUNDLED_MODEL_VERSION = '
|
|
17
|
+
export const BUNDLED_MODEL_VERSION = '5';
|
|
14
18
|
/** HuggingFace dataset repo where latest_version.json lives */
|
|
15
19
|
export const VERSION_CHECK_URL = 'https://huggingface.co/datasets/rigour-labs/rigour-rlaif-data/resolve/main/latest_version.json';
|
|
16
20
|
/** Build model info for a given tier and version */
|
|
17
21
|
export function buildModelInfo(tier, version) {
|
|
18
22
|
const meta = {
|
|
19
23
|
deep: { base: 'Qwen2.5-Coder-1.5B', size: 900_000_000, sizeH: '900MB' },
|
|
20
|
-
lite: { base: 'Qwen2.5-Coder-0.5B', size:
|
|
24
|
+
lite: { base: 'Qwen2.5-Coder-0.5B', size: 400_000_000, sizeH: '400MB' },
|
|
21
25
|
legacy: { base: 'Qwen2.5-Coder-0.5B', size: 350_000_000, sizeH: '350MB' },
|
|
22
26
|
};
|
|
23
27
|
const m = meta[tier];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export type FunctionLike = ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | ts.MethodDeclaration;
|
|
3
|
+
export declare function isFunctionLike(node: ts.Node): node is FunctionLike;
|
|
4
|
+
/** Drop parentheses, type assertions, non-null and `await` around an expression. */
|
|
5
|
+
export declare function unwrap(expr: ts.Expression): ts.Expression;
|
|
6
|
+
/**
|
|
7
|
+
* Symbol for a node, following import aliases to the declaration. For the
|
|
8
|
+
* name in a shorthand property (`{ headers }`) this is the variable it reads,
|
|
9
|
+
* not the property it creates.
|
|
10
|
+
*/
|
|
11
|
+
export declare function symbolOf(checker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined;
|
|
12
|
+
/** The function a call invokes, when it resolves to one declared in source. */
|
|
13
|
+
export declare function calledFunction(checker: ts.TypeChecker, call: ts.CallExpression): FunctionLike | undefined;
|
|
14
|
+
/** `fetch` in `fetch(...)`, `deps.fetch(...)`, `this.fetch(...)`. */
|
|
15
|
+
export declare function calleeName(call: ts.CallExpression): string | undefined;
|
|
16
|
+
export declare function enclosingFunction(node: ts.Node): FunctionLike | undefined;
|
|
17
|
+
/** Visit every descendant of `root` that belongs to `root` itself, not to a nested function. */
|
|
18
|
+
export declare function forEachOwnNode(root: ts.Node, visit: (node: ts.Node) => void): void;
|
|
19
|
+
export declare function forEachNode(root: ts.Node, visit: (node: ts.Node) => void): void;
|
|
20
|
+
/** Text of a property name (`a`, `'a'`, `"x-token"`), or undefined for computed names. */
|
|
21
|
+
export declare function propertyNameText(name: ts.PropertyName | undefined): string | undefined;
|
|
22
|
+
export declare function lineOf(node: ts.Node): number;
|
|
23
|
+
/** "http.ts:51" style location for evidence, relative to cwd. */
|
|
24
|
+
export declare function locationOf(cwd: string, node: ts.Node): string;
|
|
25
|
+
export declare function relativeFile(cwd: string, sf: ts.SourceFile): string;
|
|
26
|
+
/** Short, single-line source text for messages. */
|
|
27
|
+
export declare function snippet(node: ts.Node, max?: number): string;
|
|
28
|
+
/** True when any identifier inside `expr` resolves to `target`. */
|
|
29
|
+
export declare function mentionsSymbol(checker: ts.TypeChecker, expr: ts.Node, target: ts.Symbol): boolean;
|