contextdiet-cli 0.1.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/LICENSE +21 -0
- package/README.md +206 -0
- package/bin/contextdiet.js +4 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +104 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/bundler/index.d.ts +49 -0
- package/dist/core/bundler/index.d.ts.map +1 -0
- package/dist/core/bundler/index.js +57 -0
- package/dist/core/bundler/index.js.map +1 -0
- package/dist/core/graph/index.d.ts +29 -0
- package/dist/core/graph/index.d.ts.map +1 -0
- package/dist/core/graph/index.js +195 -0
- package/dist/core/graph/index.js.map +1 -0
- package/dist/core/graph/selector.d.ts +29 -0
- package/dist/core/graph/selector.d.ts.map +1 -0
- package/dist/core/graph/selector.js +49 -0
- package/dist/core/graph/selector.js.map +1 -0
- package/dist/core/graph/symbol-selector.d.ts +31 -0
- package/dist/core/graph/symbol-selector.d.ts.map +1 -0
- package/dist/core/graph/symbol-selector.js +0 -0
- package/dist/core/graph/symbol-selector.js.map +1 -0
- package/dist/core/graph/types.d.ts +58 -0
- package/dist/core/graph/types.d.ts.map +1 -0
- package/dist/core/graph/types.js +11 -0
- package/dist/core/graph/types.js.map +1 -0
- package/dist/core/metrics/index.d.ts +51 -0
- package/dist/core/metrics/index.d.ts.map +1 -0
- package/dist/core/metrics/index.js +91 -0
- package/dist/core/metrics/index.js.map +1 -0
- package/dist/core/parser/index.d.ts +32 -0
- package/dist/core/parser/index.d.ts.map +1 -0
- package/dist/core/parser/index.js +274 -0
- package/dist/core/parser/index.js.map +1 -0
- package/dist/core/parser/types.d.ts +127 -0
- package/dist/core/parser/types.d.ts.map +1 -0
- package/dist/core/parser/types.js +24 -0
- package/dist/core/parser/types.js.map +1 -0
- package/dist/core/pipeline.d.ts +38 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +69 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/pruner/index.d.ts +25 -0
- package/dist/core/pruner/index.d.ts.map +1 -0
- package/dist/core/pruner/index.js +56 -0
- package/dist/core/pruner/index.js.map +1 -0
- package/dist/core/pruner/types.d.ts +18 -0
- package/dist/core/pruner/types.d.ts.map +1 -0
- package/dist/core/pruner/types.js +2 -0
- package/dist/core/pruner/types.js.map +1 -0
- package/dist/core/ranker/index.d.ts +26 -0
- package/dist/core/ranker/index.d.ts.map +1 -0
- package/dist/core/ranker/index.js +149 -0
- package/dist/core/ranker/index.js.map +1 -0
- package/dist/core/ranker/types.d.ts +61 -0
- package/dist/core/ranker/types.d.ts.map +1 -0
- package/dist/core/ranker/types.js +12 -0
- package/dist/core/ranker/types.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AstPruner (Task 3.5).
|
|
3
|
+
*
|
|
4
|
+
* Reconstructs a file down to just the symbols the Selector kept, plus the
|
|
5
|
+
* imports those symbols still reference. Strategy:
|
|
6
|
+
*
|
|
7
|
+
* 1. Parse top-level symbols and whole import statements.
|
|
8
|
+
* 2. Keep the symbols whose names are in the keep-set.
|
|
9
|
+
* 3. Find which identifiers the kept symbols reference, and keep only the
|
|
10
|
+
* import statements that introduce at least one referenced binding
|
|
11
|
+
* (dead-import elimination).
|
|
12
|
+
* 4. Re-emit kept imports + kept symbols in original source order, each sliced
|
|
13
|
+
* verbatim via `Parser.sliceNode`.
|
|
14
|
+
*
|
|
15
|
+
* Because it only ever *omits* whole declarations and re-emits kept ones
|
|
16
|
+
* unchanged, the pruner cannot introduce a syntax error into what it emits.
|
|
17
|
+
*/
|
|
18
|
+
import { AstGrepperParser } from '../parser/index.js';
|
|
19
|
+
export class AstPruner {
|
|
20
|
+
parser;
|
|
21
|
+
constructor(parser = new AstGrepperParser()) {
|
|
22
|
+
this.parser = parser;
|
|
23
|
+
}
|
|
24
|
+
async prune(source, filePath, keep) {
|
|
25
|
+
const symbols = await this.parser.extractSymbols(source, filePath);
|
|
26
|
+
const keptSymbols = symbols.filter((symbol) => keep.has(symbol.name));
|
|
27
|
+
if (keptSymbols.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
// Which imports do the survivors still use?
|
|
30
|
+
const referenced = await this.parser.collectReferences(source, filePath, keptSymbols.map((symbol) => symbol.range));
|
|
31
|
+
const importStatements = await this.parser.extractImportStatements(source, filePath);
|
|
32
|
+
const keptImports = importStatements.filter((statement) => statement.localNames.some((name) => referenced.has(name)));
|
|
33
|
+
// Emit imports then symbols, in original source order, de-duplicating ranges
|
|
34
|
+
// (a multi-binding declaration like `const a = 1, b = 2` yields one range for
|
|
35
|
+
// several kept symbols and must be sliced exactly once).
|
|
36
|
+
const ranges = dedupeRanges([
|
|
37
|
+
...keptImports.map((statement) => statement.range),
|
|
38
|
+
...keptSymbols.map((symbol) => symbol.range),
|
|
39
|
+
]).sort((a, b) => a.start.index - b.start.index);
|
|
40
|
+
return ranges.map((range) => this.parser.sliceNode(source, range)).join('\n\n') + '\n';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Remove ranges that cover the exact same span. */
|
|
44
|
+
function dedupeRanges(ranges) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const unique = [];
|
|
47
|
+
for (const range of ranges) {
|
|
48
|
+
const key = `${range.start.index}:${range.end.index}`;
|
|
49
|
+
if (seen.has(key))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(key);
|
|
52
|
+
unique.push(range);
|
|
53
|
+
}
|
|
54
|
+
return unique;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/pruner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD,MAAM,OAAO,SAAS;IACH,MAAM,CAAS;IAEhC,YAAY,MAAM,GAAW,IAAI,gBAAgB,EAAE;QACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,QAAgB,EAAE,IAAyB;QACrE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACnE,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAExC,4CAA4C;QAC5C,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CACpD,MAAM,EACN,QAAQ,EACR,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAC1C,CAAC;QACF,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACrF,MAAM,WAAW,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CACxD,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAC1D,CAAC;QAEF,6EAA6E;QAC7E,8EAA8E;QAC9E,yDAAyD;QACzD,MAAM,MAAM,GAAG,YAAY,CAAC;YAC1B,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;YAClD,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SAC7C,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAEjD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACzF,CAAC;CACF;AAED,oDAAoD;AACpD,SAAS,YAAY,CAAC,MAAwB;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACtD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pruner contract for ContextDiet (Task 3.5).
|
|
3
|
+
*
|
|
4
|
+
* The pruner is the last transform before bundling: given a single file's source
|
|
5
|
+
* and the set of symbol names the Selector chose to keep, it reconstructs a
|
|
6
|
+
* trimmed version of that file containing only those symbols and the imports they
|
|
7
|
+
* still use. It never rewrites code — it re-emits kept declarations verbatim — so
|
|
8
|
+
* emitted output is syntactically valid by construction.
|
|
9
|
+
*/
|
|
10
|
+
export interface Pruner {
|
|
11
|
+
/**
|
|
12
|
+
* Reconstruct `source` keeping only the top-level symbols named in `keep`,
|
|
13
|
+
* plus the import statements those surviving symbols reference. Returns the
|
|
14
|
+
* empty string when nothing is kept.
|
|
15
|
+
*/
|
|
16
|
+
prune(source: string, filePath: string, keep: ReadonlySet<string>): Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/pruner/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/pruner/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LexicalRanker — Task 3.0.
|
|
3
|
+
*
|
|
4
|
+
* A lightweight, deterministic, $0 matching engine. It turns a natural-language
|
|
5
|
+
* focus string into a set of seed symbols by pure string analysis: tokenize the
|
|
6
|
+
* query (dropping stop words), tokenize each symbol name (splitting camelCase /
|
|
7
|
+
* snake_case), and score the overlap. No embeddings, no API, no network, no I/O.
|
|
8
|
+
*
|
|
9
|
+
* Scoring is intentionally simple and explainable:
|
|
10
|
+
* - An *exact* query-token ↔ symbol-token match is worth the most.
|
|
11
|
+
* - A *partial* (substring) match is worth a fraction of that.
|
|
12
|
+
* - Earlier query tokens weigh slightly more (users front-load intent, e.g.
|
|
13
|
+
* "JWT" in "Fix JWT auth"), which lets `verifyJWT` outrank `authMiddleware`.
|
|
14
|
+
* - The per-symbol raw score is squashed into `[0, 1]` so weights are stable
|
|
15
|
+
* regardless of query length.
|
|
16
|
+
*/
|
|
17
|
+
import type { SymbolNode } from '../parser/types.js';
|
|
18
|
+
import type { Ranker, RankerOptions, SeedNode } from './types.js';
|
|
19
|
+
export declare class LexicalRanker implements Ranker {
|
|
20
|
+
private readonly minWeight;
|
|
21
|
+
private readonly limit;
|
|
22
|
+
constructor(options?: RankerOptions);
|
|
23
|
+
tokenize(focusQuery: string): string[];
|
|
24
|
+
determineSeeds(focusQuery: string, allSymbols: readonly SymbolNode[]): SeedNode[];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/ranker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA0BlE,qBAAa,aAAc,YAAW,MAAM;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAE3C,YAAY,OAAO,GAAE,aAAkB,EAGtC;IAED,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAarC;IAED,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,UAAU,EAAE,GAAG,QAAQ,EAAE,CAqChF;CACF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LexicalRanker — Task 3.0.
|
|
3
|
+
*
|
|
4
|
+
* A lightweight, deterministic, $0 matching engine. It turns a natural-language
|
|
5
|
+
* focus string into a set of seed symbols by pure string analysis: tokenize the
|
|
6
|
+
* query (dropping stop words), tokenize each symbol name (splitting camelCase /
|
|
7
|
+
* snake_case), and score the overlap. No embeddings, no API, no network, no I/O.
|
|
8
|
+
*
|
|
9
|
+
* Scoring is intentionally simple and explainable:
|
|
10
|
+
* - An *exact* query-token ↔ symbol-token match is worth the most.
|
|
11
|
+
* - A *partial* (substring) match is worth a fraction of that.
|
|
12
|
+
* - Earlier query tokens weigh slightly more (users front-load intent, e.g.
|
|
13
|
+
* "JWT" in "Fix JWT auth"), which lets `verifyJWT` outrank `authMiddleware`.
|
|
14
|
+
* - The per-symbol raw score is squashed into `[0, 1]` so weights are stable
|
|
15
|
+
* regardless of query length.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Common English filler plus task-verb noise that carries no signal about which
|
|
19
|
+
* code symbol matters. Kept deliberately small and hand-audited — over-stemming
|
|
20
|
+
* hurts precision more than a few extra stop words help.
|
|
21
|
+
*/
|
|
22
|
+
const STOP_WORDS = new Set([
|
|
23
|
+
// articles / conjunctions / prepositions
|
|
24
|
+
'a', 'an', 'the', 'and', 'or', 'but', 'of', 'to', 'in', 'on', 'at', 'for',
|
|
25
|
+
'with', 'from', 'into', 'by', 'as', 'is', 'are', 'be', 'this', 'that', 'it',
|
|
26
|
+
// generic task verbs / nouns that describe the *action*, not the *subject*
|
|
27
|
+
'fix', 'fixing', 'fixed', 'bug', 'bugs', 'issue', 'issues', 'error', 'errors',
|
|
28
|
+
'add', 'adding', 'update', 'updating', 'change', 'changing', 'refactor',
|
|
29
|
+
'implement', 'handle', 'support', 'make', 'please', 'need', 'want', 'should',
|
|
30
|
+
]);
|
|
31
|
+
/** Weight of an exact token match vs. a partial (substring) match. */
|
|
32
|
+
const EXACT_MATCH_SCORE = 1;
|
|
33
|
+
const PARTIAL_MATCH_SCORE = 0.4;
|
|
34
|
+
/** How strongly earlier query tokens are favored (0 = no positional bias). */
|
|
35
|
+
const POSITION_DECAY = 0.15;
|
|
36
|
+
const DEFAULT_MIN_WEIGHT = 0;
|
|
37
|
+
export class LexicalRanker {
|
|
38
|
+
minWeight;
|
|
39
|
+
limit;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.minWeight = options.minWeight ?? DEFAULT_MIN_WEIGHT;
|
|
42
|
+
this.limit = options.limit;
|
|
43
|
+
}
|
|
44
|
+
tokenize(focusQuery) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const tokens = [];
|
|
47
|
+
for (const raw of splitWords(focusQuery)) {
|
|
48
|
+
const token = raw.toLowerCase();
|
|
49
|
+
if (token.length === 0 || STOP_WORDS.has(token))
|
|
50
|
+
continue;
|
|
51
|
+
if (seen.has(token))
|
|
52
|
+
continue;
|
|
53
|
+
seen.add(token);
|
|
54
|
+
tokens.push(token);
|
|
55
|
+
}
|
|
56
|
+
return tokens;
|
|
57
|
+
}
|
|
58
|
+
determineSeeds(focusQuery, allSymbols) {
|
|
59
|
+
const queryTokens = this.tokenize(focusQuery);
|
|
60
|
+
if (queryTokens.length === 0)
|
|
61
|
+
return [];
|
|
62
|
+
// Positional weight: token 0 gets 1.0, each subsequent token slightly less.
|
|
63
|
+
// Normalized by the max so a single-token query still tops out at 1.0.
|
|
64
|
+
const positionWeights = queryTokens.map((_, i) => 1 / (1 + POSITION_DECAY * i));
|
|
65
|
+
const maxPossible = positionWeights.reduce((sum, w) => sum + w * EXACT_MATCH_SCORE, 0);
|
|
66
|
+
const seeds = [];
|
|
67
|
+
for (const symbol of allSymbols) {
|
|
68
|
+
const symbolTokens = tokenizeIdentifier(symbol.name);
|
|
69
|
+
if (symbolTokens.length === 0)
|
|
70
|
+
continue;
|
|
71
|
+
let raw = 0;
|
|
72
|
+
const matchedTokens = [];
|
|
73
|
+
queryTokens.forEach((queryToken, i) => {
|
|
74
|
+
const contribution = bestTokenMatch(queryToken, symbolTokens);
|
|
75
|
+
if (contribution > 0) {
|
|
76
|
+
raw += contribution * positionWeights[i];
|
|
77
|
+
matchedTokens.push(queryToken);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
if (raw <= 0)
|
|
81
|
+
continue;
|
|
82
|
+
const weight = clamp01(raw / maxPossible);
|
|
83
|
+
if (weight <= this.minWeight)
|
|
84
|
+
continue;
|
|
85
|
+
seeds.push({ symbol, weight, matchedTokens });
|
|
86
|
+
}
|
|
87
|
+
seeds.sort(byWeightThenName);
|
|
88
|
+
return this.limit === undefined ? seeds : seeds.slice(0, this.limit);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Best score for a single query token against all tokens of one symbol:
|
|
93
|
+
* a full exact match if any symbol token equals it, otherwise the best
|
|
94
|
+
* substring (partial) match, otherwise zero.
|
|
95
|
+
*/
|
|
96
|
+
function bestTokenMatch(queryToken, symbolTokens) {
|
|
97
|
+
let best = 0;
|
|
98
|
+
for (const symbolToken of symbolTokens) {
|
|
99
|
+
if (symbolToken === queryToken) {
|
|
100
|
+
return EXACT_MATCH_SCORE; // can't beat exact — short-circuit
|
|
101
|
+
}
|
|
102
|
+
if (symbolToken.includes(queryToken) || queryToken.includes(symbolToken)) {
|
|
103
|
+
best = Math.max(best, PARTIAL_MATCH_SCORE);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return best;
|
|
107
|
+
}
|
|
108
|
+
/** Split a free-text query into raw word chunks on any non-alphanumeric run. */
|
|
109
|
+
function splitWords(text) {
|
|
110
|
+
return text.split(/[^A-Za-z0-9]+/).filter((w) => w.length > 0);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Break a code identifier into lowercase lexical tokens, understanding the
|
|
114
|
+
* conventions the parser emits: camelCase, PascalCase, snake_case, and
|
|
115
|
+
* embedded acronyms (`verifyJWT` → ['verify', 'jwt'], `HTTPServer` →
|
|
116
|
+
* ['http', 'server']).
|
|
117
|
+
*/
|
|
118
|
+
function tokenizeIdentifier(name) {
|
|
119
|
+
const withBoundaries = name
|
|
120
|
+
// insert a space between a lower/digit and a following upper: verifyJWT -> verify JWT
|
|
121
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
122
|
+
// insert a space between an acronym run and a following Capitalized word:
|
|
123
|
+
// JWTUtils -> JWT Utils
|
|
124
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
const tokens = [];
|
|
127
|
+
for (const chunk of splitWords(withBoundaries)) {
|
|
128
|
+
const token = chunk.toLowerCase();
|
|
129
|
+
if (seen.has(token))
|
|
130
|
+
continue;
|
|
131
|
+
seen.add(token);
|
|
132
|
+
tokens.push(token);
|
|
133
|
+
}
|
|
134
|
+
return tokens;
|
|
135
|
+
}
|
|
136
|
+
function clamp01(value) {
|
|
137
|
+
if (value < 0)
|
|
138
|
+
return 0;
|
|
139
|
+
if (value > 1)
|
|
140
|
+
return 1;
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
/** Descending weight; ties broken alphabetically for stable, deterministic output. */
|
|
144
|
+
function byWeightThenName(a, b) {
|
|
145
|
+
if (b.weight !== a.weight)
|
|
146
|
+
return b.weight - a.weight;
|
|
147
|
+
return a.symbol.name.localeCompare(b.symbol.name);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/ranker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;;;GAIG;AACH,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC;IAC9C,yCAAyC;IACzC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;IACzE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;IAC3E,2EAA2E;IAC3E,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ;IAC7E,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU;IACvE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ;CAC7E,CAAC,CAAC;AAEH,sEAAsE;AACtE,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,8EAA8E;AAC9E,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,MAAM,OAAO,aAAa;IACP,SAAS,CAAS;IAClB,KAAK,CAAqB;IAE3C,YAAY,OAAO,GAAkB,EAAE;QACrC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACzD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,CAAC;IAED,QAAQ,CAAC,UAAkB;QACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,cAAc,CAAC,UAAkB,EAAE,UAAiC;QAClE,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAExC,4EAA4E;QAC5E,uEAAuE;QACvE,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,WAAW,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,iBAAiB,EAAE,CAAC,CAAC,CAAC;QAEvF,MAAM,KAAK,GAAe,EAAE,CAAC;QAE7B,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;YAChC,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAExC,IAAI,GAAG,GAAG,CAAC,CAAC;YACZ,MAAM,aAAa,GAAa,EAAE,CAAC;YAEnC,WAAW,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE;gBACpC,MAAM,YAAY,GAAG,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;gBAC9D,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;oBACrB,GAAG,IAAI,YAAY,GAAG,eAAe,CAAC,CAAC,CAAE,CAAC;oBAC1C,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,GAAG,IAAI,CAAC;gBAAE,SAAS;YAEvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC;YAC1C,IAAI,MAAM,IAAI,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEvC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAE7B,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvE,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,UAAkB,EAAE,YAA+B;IACzE,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YAC/B,OAAO,iBAAiB,CAAC,CAAC,mCAAmC;QAC/D,CAAC;QACD,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACzE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,MAAM,cAAc,GAAG,IAAI;QACzB,sFAAsF;SACrF,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;QACvC,0EAA0E;QAC1E,wBAAwB;SACvB,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAE7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,SAAS;QAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,OAAO,CAAC,KAAa;IAC5B,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sFAAsF;AACtF,SAAS,gBAAgB,CAAC,CAAW,EAAE,CAAW;IAChD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACtD,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranker contracts for ContextDiet (Task 3.0: LexicalRanker).
|
|
3
|
+
*
|
|
4
|
+
* The ranker is the bridge between a human's fuzzy intent ("Fix the JWT auth
|
|
5
|
+
* bug") and the concrete graph. It turns that focus string into a small set of
|
|
6
|
+
* high-confidence "seed" symbols that the graph traversal starts from. Like the
|
|
7
|
+
* parser, downstream code (the pruner) depends only on these interfaces, never
|
|
8
|
+
* on a concrete implementation — so the matching strategy (lexical today,
|
|
9
|
+
* embeddings later) can be swapped without a rewrite.
|
|
10
|
+
*/
|
|
11
|
+
import type { SymbolNode } from '../parser/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* A symbol the ranker believes is relevant to the focus query, paired with a
|
|
14
|
+
* confidence weight. This is the entry point ("seed") the graph traversal
|
|
15
|
+
* expands from.
|
|
16
|
+
*/
|
|
17
|
+
export interface SeedNode {
|
|
18
|
+
/** The matched declaration from the parser. */
|
|
19
|
+
readonly symbol: SymbolNode;
|
|
20
|
+
/**
|
|
21
|
+
* Confidence that this symbol is relevant to the focus query, in `[0, 1]`.
|
|
22
|
+
* Higher means a stronger lexical match. Callers may threshold on this to
|
|
23
|
+
* decide how many seeds to expand.
|
|
24
|
+
*/
|
|
25
|
+
readonly weight: number;
|
|
26
|
+
/**
|
|
27
|
+
* The focus tokens that contributed to this match, for explainability/debug.
|
|
28
|
+
* e.g. matching `verifyJWT` against "Fix JWT auth" yields `['jwt']`.
|
|
29
|
+
*/
|
|
30
|
+
readonly matchedTokens: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
/** Tunables for lexical matching. All optional; sensible defaults are used. */
|
|
33
|
+
export interface RankerOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Minimum weight required for a symbol to be returned as a seed. Symbols
|
|
36
|
+
* scoring at or below this are dropped. Defaults to `0` (drop only zeros).
|
|
37
|
+
*/
|
|
38
|
+
readonly minWeight?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Cap on the number of seeds returned, keeping the highest-weighted. When
|
|
41
|
+
* omitted, all symbols above `minWeight` are returned.
|
|
42
|
+
*/
|
|
43
|
+
readonly limit?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The matching-engine contract. Pure and deterministic: same query + symbols
|
|
47
|
+
* always yields the same seeds, with zero I/O and zero network calls.
|
|
48
|
+
*/
|
|
49
|
+
export interface Ranker {
|
|
50
|
+
/**
|
|
51
|
+
* Tokenize `focusQuery` into lexical seeds, match them against `allSymbols`,
|
|
52
|
+
* and return the relevant symbols ordered by descending weight.
|
|
53
|
+
*/
|
|
54
|
+
determineSeeds(focusQuery: string, allSymbols: readonly SymbolNode[]): SeedNode[];
|
|
55
|
+
/**
|
|
56
|
+
* Expose the tokenizer so tests and callers can inspect exactly which
|
|
57
|
+
* keywords a query reduces to (stop words removed, normalized, deduped).
|
|
58
|
+
*/
|
|
59
|
+
tokenize(focusQuery: string): string[];
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/ranker/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB;;;OAGG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,UAAU,EAAE,GAAG,QAAQ,EAAE,CAAC;IAElF;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACxC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranker contracts for ContextDiet (Task 3.0: LexicalRanker).
|
|
3
|
+
*
|
|
4
|
+
* The ranker is the bridge between a human's fuzzy intent ("Fix the JWT auth
|
|
5
|
+
* bug") and the concrete graph. It turns that focus string into a small set of
|
|
6
|
+
* high-confidence "seed" symbols that the graph traversal starts from. Like the
|
|
7
|
+
* parser, downstream code (the pruner) depends only on these interfaces, never
|
|
8
|
+
* on a concrete implementation — so the matching strategy (lexical today,
|
|
9
|
+
* embeddings later) can be swapped without a rewrite.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/ranker/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contextdiet-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AST-based token optimizer that trims codebase context for AI agents to eliminate context-window bloat and cut LLM API costs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ast",
|
|
9
|
+
"llm",
|
|
10
|
+
"context",
|
|
11
|
+
"tokens",
|
|
12
|
+
"ai-agents",
|
|
13
|
+
"ast-grep",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"author": "rizzhab",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/risshabs22-quantified/contextDiet.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/risshabs22-quantified/contextDiet#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/risshabs22-quantified/contextDiet/issues"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"contextdiet": "bin/contextdiet.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/core/pipeline.js",
|
|
29
|
+
"types": "./dist/core/pipeline.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/core/pipeline.d.ts",
|
|
33
|
+
"default": "./dist/core/pipeline.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"bin"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@ast-grep/napi": "^0.44.1",
|
|
52
|
+
"commander": "^15.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^26.1.1",
|
|
56
|
+
"typescript": "^7.0.2",
|
|
57
|
+
"vitest": "^4.1.10"
|
|
58
|
+
}
|
|
59
|
+
}
|