git-history-ui 2.0.3 → 3.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +75 -13
  3. package/build/frontend/chunk-2QTYGOOP.js +1 -0
  4. package/build/frontend/chunk-36NFLS3P.js +1 -0
  5. package/build/frontend/chunk-3FFYILBL.js +1 -0
  6. package/build/frontend/chunk-C56FFICU.js +2 -0
  7. package/build/frontend/chunk-HIERJLKT.js +1 -0
  8. package/build/frontend/chunk-HYRHOB47.js +7 -0
  9. package/build/frontend/chunk-ITIFFECZ.js +1 -0
  10. package/build/frontend/chunk-N7UHDKJ7.js +1 -0
  11. package/build/frontend/chunk-NUMLL3OZ.js +1 -0
  12. package/build/frontend/chunk-TQE5NWMZ.js +5 -0
  13. package/build/frontend/chunk-TQVUSJBM.js +1 -0
  14. package/build/frontend/chunk-YSTG766K.js +1 -0
  15. package/build/frontend/index.html +2 -2
  16. package/build/frontend/main-44WYBFGF.js +1 -0
  17. package/build/frontend/styles-GSJUSSXL.css +1 -0
  18. package/dist/backend/aggregations.d.ts +57 -0
  19. package/dist/backend/aggregations.js +130 -0
  20. package/dist/backend/aggregations.js.map +1 -0
  21. package/dist/backend/annotations.d.ts +30 -0
  22. package/dist/backend/annotations.js +90 -0
  23. package/dist/backend/annotations.js.map +1 -0
  24. package/dist/backend/gitService.d.ts +14 -0
  25. package/dist/backend/gitService.js +81 -0
  26. package/dist/backend/gitService.js.map +1 -1
  27. package/dist/backend/grouping/prGrouping.d.ts +39 -0
  28. package/dist/backend/grouping/prGrouping.js +210 -0
  29. package/dist/backend/grouping/prGrouping.js.map +1 -0
  30. package/dist/backend/impact.d.ts +16 -0
  31. package/dist/backend/impact.js +66 -0
  32. package/dist/backend/impact.js.map +1 -0
  33. package/dist/backend/insights.d.ts +9 -0
  34. package/dist/backend/insights.js +44 -0
  35. package/dist/backend/insights.js.map +1 -0
  36. package/dist/backend/llm/anthropicProvider.d.ts +13 -0
  37. package/dist/backend/llm/anthropicProvider.js +90 -0
  38. package/dist/backend/llm/anthropicProvider.js.map +1 -0
  39. package/dist/backend/llm/heuristicProvider.d.ts +15 -0
  40. package/dist/backend/llm/heuristicProvider.js +127 -0
  41. package/dist/backend/llm/heuristicProvider.js.map +1 -0
  42. package/dist/backend/llm/index.d.ts +17 -0
  43. package/dist/backend/llm/index.js +66 -0
  44. package/dist/backend/llm/index.js.map +1 -0
  45. package/dist/backend/llm/openaiProvider.d.ts +13 -0
  46. package/dist/backend/llm/openaiProvider.js +100 -0
  47. package/dist/backend/llm/openaiProvider.js.map +1 -0
  48. package/dist/backend/llm/types.d.ts +32 -0
  49. package/dist/backend/llm/types.js +3 -0
  50. package/dist/backend/llm/types.js.map +1 -0
  51. package/dist/backend/search/datePhrase.d.ts +20 -0
  52. package/dist/backend/search/datePhrase.js +113 -0
  53. package/dist/backend/search/datePhrase.js.map +1 -0
  54. package/dist/backend/search/nlSearch.d.ts +39 -0
  55. package/dist/backend/search/nlSearch.js +90 -0
  56. package/dist/backend/search/nlSearch.js.map +1 -0
  57. package/dist/backend/server.d.ts +3 -0
  58. package/dist/backend/server.js +137 -2
  59. package/dist/backend/server.js.map +1 -1
  60. package/dist/backend/snapshot.d.ts +16 -0
  61. package/dist/backend/snapshot.js +42 -0
  62. package/dist/backend/snapshot.js.map +1 -0
  63. package/docs/launch.md +66 -0
  64. package/package.json +12 -3
  65. package/build/frontend/main-VDZAFLAV.js +0 -11
  66. package/build/frontend/styles-CO6MLMTR.css +0 -1
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnthropicProvider = void 0;
4
+ const DEFAULT_MODEL = 'claude-3-5-haiku-latest';
5
+ const ENDPOINT = 'https://api.anthropic.com/v1/messages';
6
+ const VERSION = '2023-06-01';
7
+ class AnthropicProvider {
8
+ apiKey;
9
+ model;
10
+ name = 'anthropic';
11
+ isAi = true;
12
+ constructor(apiKey, model = DEFAULT_MODEL) {
13
+ this.apiKey = apiKey;
14
+ this.model = model;
15
+ }
16
+ async score(query, candidates) {
17
+ if (candidates.length === 0)
18
+ return [];
19
+ // Slice candidate text to keep prompt small.
20
+ const items = candidates.map((c, i) => ({
21
+ idx: i,
22
+ id: c.id,
23
+ text: c.text.replace(/\s+/g, ' ').slice(0, 240)
24
+ }));
25
+ const prompt = [
26
+ 'You score commit relevance to a developer query.',
27
+ 'Return ONLY a JSON array of {idx:number,score:number} where score is in [0,1].',
28
+ 'No prose, no markdown.',
29
+ `Query: ${query}`,
30
+ 'Candidates:',
31
+ ...items.map((it) => `[${it.idx}] ${it.text}`)
32
+ ].join('\n');
33
+ const text = await this.call(prompt, 1024);
34
+ const parsed = parseScores(text, items.length);
35
+ return items.map((it) => ({ id: it.id, score: parsed[it.idx] ?? 0 }));
36
+ }
37
+ async summarize(text, opts) {
38
+ const prompt = [
39
+ opts?.hint ?? 'Summarize the following text in one short paragraph (max 3 sentences).',
40
+ '---',
41
+ text.length > 8000 ? text.slice(0, 8000) + '\n[truncated]' : text
42
+ ].join('\n');
43
+ const out = await this.call(prompt, 320);
44
+ return out.trim();
45
+ }
46
+ async call(prompt, maxTokens) {
47
+ const resp = await fetch(ENDPOINT, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ 'x-api-key': this.apiKey,
52
+ 'anthropic-version': VERSION
53
+ },
54
+ body: JSON.stringify({
55
+ model: this.model,
56
+ max_tokens: maxTokens,
57
+ messages: [{ role: 'user', content: prompt }]
58
+ })
59
+ });
60
+ if (!resp.ok) {
61
+ const err = await resp.text().catch(() => '');
62
+ throw new Error(`anthropic ${resp.status}: ${err.slice(0, 200)}`);
63
+ }
64
+ const data = (await resp.json());
65
+ const block = data.content?.find((b) => b.type === 'text');
66
+ return block?.text ?? '';
67
+ }
68
+ }
69
+ exports.AnthropicProvider = AnthropicProvider;
70
+ function parseScores(raw, length) {
71
+ const result = {};
72
+ const match = raw.match(/\[[\s\S]*\]/);
73
+ if (!match)
74
+ return result;
75
+ try {
76
+ const arr = JSON.parse(match[0]);
77
+ for (const item of arr) {
78
+ if (typeof item?.idx === 'number' && item.idx >= 0 && item.idx < length) {
79
+ const s = Number(item.score);
80
+ if (Number.isFinite(s))
81
+ result[item.idx] = Math.max(0, Math.min(1, s));
82
+ }
83
+ }
84
+ }
85
+ catch {
86
+ /* ignore — return what we have */
87
+ }
88
+ return result;
89
+ }
90
+ //# sourceMappingURL=anthropicProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropicProvider.js","sourceRoot":"","sources":["../../../src/backend/llm/anthropicProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { LlmService, ScoreCandidate, ScoredCandidate } from './types';
2
+ export declare function expandKeywords(query: string): string[];
3
+ export declare function tokenize(text: string): string[];
4
+ /**
5
+ * Heuristic relevance scorer based on token overlap with synonym expansion
6
+ * and a small TF-IDF-ish weighting (rare tokens count more).
7
+ */
8
+ export declare class HeuristicProvider implements LlmService {
9
+ readonly name: "heuristic";
10
+ readonly isAi = false;
11
+ score(query: string, candidates: ScoreCandidate[]): Promise<ScoredCandidate[]>;
12
+ summarize(text: string, _opts?: {
13
+ hint?: string;
14
+ }): Promise<string>;
15
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeuristicProvider = void 0;
4
+ exports.expandKeywords = expandKeywords;
5
+ exports.tokenize = tokenize;
6
+ /**
7
+ * Synonyms expand a single query token into a set of related tokens.
8
+ * Designed for software engineering vocabulary: bug fixes, features,
9
+ * common subsystems. Keep entries lower-case.
10
+ */
11
+ const SYNONYMS = {
12
+ fix: ['fix', 'bug', 'hotfix', 'patch', 'repair', 'correct', 'resolve'],
13
+ bug: ['bug', 'fix', 'hotfix', 'issue', 'defect', 'regression'],
14
+ feat: ['feat', 'feature', 'add', 'implement', 'introduce', 'new'],
15
+ add: ['add', 'feat', 'feature', 'implement', 'introduce'],
16
+ remove: ['remove', 'delete', 'drop', 'deprecate', 'kill'],
17
+ refactor: ['refactor', 'cleanup', 'restructure', 'rework', 'simplify'],
18
+ perf: ['perf', 'performance', 'optimize', 'speed', 'fast', 'cache'],
19
+ docs: ['docs', 'doc', 'documentation', 'readme', 'comment', 'comments'],
20
+ test: ['test', 'tests', 'spec', 'unit', 'integration', 'e2e'],
21
+ ci: ['ci', 'pipeline', 'workflow', 'github-actions', 'jenkins', 'travis'],
22
+ build: ['build', 'compile', 'webpack', 'vite', 'rollup', 'esbuild'],
23
+ deps: ['deps', 'dependencies', 'upgrade', 'bump', 'update', 'package'],
24
+ auth: ['auth', 'authentication', 'login', 'logout', 'signin', 'signout', 'oauth', 'jwt', 'session', 'token'],
25
+ login: ['login', 'logon', 'signin', 'sign-in', 'auth', 'authentication'],
26
+ payment: ['payment', 'payments', 'pay', 'billing', 'invoice', 'charge', 'stripe', 'checkout'],
27
+ ui: ['ui', 'frontend', 'view', 'component', 'css', 'style', 'theme'],
28
+ api: ['api', 'endpoint', 'route', 'rest', 'graphql', 'rpc', 'service'],
29
+ db: ['db', 'database', 'sql', 'migration', 'schema', 'query', 'postgres', 'mysql', 'mongo'],
30
+ security: ['security', 'cve', 'vuln', 'vulnerability', 'audit', 'xss', 'csrf', 'sqli'],
31
+ release: ['release', 'tag', 'version', 'publish', 'changelog']
32
+ };
33
+ const STOP_WORDS = new Set([
34
+ 'a', 'an', 'the', 'and', 'or', 'of', 'to', 'in', 'on', 'for', 'with', 'is', 'was', 'be', 'i', 'we', 'our',
35
+ 'my', 'me', 'it', 'this', 'that', 'these', 'those', 'where', 'when', 'what', 'which', 'who', 'how',
36
+ 'did', 'do', 'does', 'has', 'have', 'had', 'last', 'changes', 'change', 'commit', 'commits', 'related'
37
+ ]);
38
+ // Reverse synonym map built once: every member token maps to all its bucket-mates.
39
+ const REVERSE_SYNONYMS = (() => {
40
+ const map = new Map();
41
+ for (const [key, members] of Object.entries(SYNONYMS)) {
42
+ const all = new Set([key, ...members]);
43
+ for (const m of all) {
44
+ const cur = map.get(m);
45
+ if (cur)
46
+ for (const a of all)
47
+ cur.add(a);
48
+ else
49
+ map.set(m, new Set(all));
50
+ }
51
+ }
52
+ return map;
53
+ })();
54
+ function expandKeywords(query) {
55
+ const tokens = tokenize(query);
56
+ const out = new Set();
57
+ for (const t of tokens) {
58
+ out.add(t);
59
+ const syn = REVERSE_SYNONYMS.get(t);
60
+ if (syn)
61
+ for (const s of syn)
62
+ out.add(s);
63
+ }
64
+ return Array.from(out);
65
+ }
66
+ function tokenize(text) {
67
+ return (text || '')
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9_\-/.]+/g, ' ')
70
+ .split(/\s+/)
71
+ .filter((t) => t.length >= 2 && !STOP_WORDS.has(t));
72
+ }
73
+ /**
74
+ * Heuristic relevance scorer based on token overlap with synonym expansion
75
+ * and a small TF-IDF-ish weighting (rare tokens count more).
76
+ */
77
+ class HeuristicProvider {
78
+ name = 'heuristic';
79
+ isAi = false;
80
+ async score(query, candidates) {
81
+ const queryTokens = expandKeywords(query);
82
+ if (queryTokens.length === 0) {
83
+ return candidates.map((c) => ({ id: c.id, score: 0 }));
84
+ }
85
+ // Document frequency for IDF weighting.
86
+ const docFreq = new Map();
87
+ const docTokens = candidates.map((c) => {
88
+ const toks = tokenize(c.text);
89
+ const seen = new Set(toks);
90
+ for (const t of seen)
91
+ docFreq.set(t, (docFreq.get(t) ?? 0) + 1);
92
+ return toks;
93
+ });
94
+ const N = Math.max(1, candidates.length);
95
+ return candidates.map((c, i) => {
96
+ const tokens = docTokens[i];
97
+ const tf = new Map();
98
+ for (const t of tokens)
99
+ tf.set(t, (tf.get(t) ?? 0) + 1);
100
+ let raw = 0;
101
+ let matched = 0;
102
+ for (const qt of queryTokens) {
103
+ const f = tf.get(qt);
104
+ if (!f)
105
+ continue;
106
+ matched++;
107
+ const idf = Math.log(1 + N / (1 + (docFreq.get(qt) ?? 0)));
108
+ raw += (1 + Math.log(f)) * idf;
109
+ }
110
+ const coverage = matched / queryTokens.length;
111
+ const length = Math.max(1, tokens.length);
112
+ const norm = raw / Math.sqrt(length);
113
+ return { id: c.id, score: Math.min(1, 0.55 * coverage + 0.45 * Math.tanh(norm)) };
114
+ });
115
+ }
116
+ async summarize(text, _opts) {
117
+ const trimmed = (text || '').trim();
118
+ if (!trimmed)
119
+ return '';
120
+ // Heuristic summary: first non-empty paragraph, truncated.
121
+ const para = trimmed.split(/\n\n+/).find((p) => p.trim().length > 0) ?? trimmed;
122
+ const oneLine = para.replace(/\s+/g, ' ').trim();
123
+ return oneLine.length > 280 ? oneLine.substring(0, 277) + '...' : oneLine;
124
+ }
125
+ }
126
+ exports.HeuristicProvider = HeuristicProvider;
127
+ //# sourceMappingURL=heuristicProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heuristicProvider.js","sourceRoot":"","sources":["../../../src/backend/llm/heuristicProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { LlmConfig, LlmService } from './types';
2
+ export type { LlmConfig, LlmProviderName, LlmService, ScoreCandidate, ScoredCandidate } from './types';
3
+ export { HeuristicProvider, expandKeywords, tokenize } from './heuristicProvider';
4
+ export { AnthropicProvider } from './anthropicProvider';
5
+ export { OpenAiProvider } from './openaiProvider';
6
+ /**
7
+ * Resolve provider configuration from explicit config + environment.
8
+ * Precedence:
9
+ * 1. Explicit config.provider (must have matching key for ai providers).
10
+ * 2. GHUI_LLM_PROVIDER env var.
11
+ * 3. Auto-detect: anthropic key wins over openai key.
12
+ * 4. Heuristic fallback.
13
+ */
14
+ export declare function createLlmService(config?: LlmConfig): LlmService;
15
+ export declare function getDefaultLlmService(config?: LlmConfig): LlmService;
16
+ /** Force-reset for tests. */
17
+ export declare function _resetLlmCache(): void;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAiProvider = exports.AnthropicProvider = exports.tokenize = exports.expandKeywords = exports.HeuristicProvider = void 0;
4
+ exports.createLlmService = createLlmService;
5
+ exports.getDefaultLlmService = getDefaultLlmService;
6
+ exports._resetLlmCache = _resetLlmCache;
7
+ const anthropicProvider_1 = require("./anthropicProvider");
8
+ const heuristicProvider_1 = require("./heuristicProvider");
9
+ const openaiProvider_1 = require("./openaiProvider");
10
+ var heuristicProvider_2 = require("./heuristicProvider");
11
+ Object.defineProperty(exports, "HeuristicProvider", { enumerable: true, get: function () { return heuristicProvider_2.HeuristicProvider; } });
12
+ Object.defineProperty(exports, "expandKeywords", { enumerable: true, get: function () { return heuristicProvider_2.expandKeywords; } });
13
+ Object.defineProperty(exports, "tokenize", { enumerable: true, get: function () { return heuristicProvider_2.tokenize; } });
14
+ var anthropicProvider_2 = require("./anthropicProvider");
15
+ Object.defineProperty(exports, "AnthropicProvider", { enumerable: true, get: function () { return anthropicProvider_2.AnthropicProvider; } });
16
+ var openaiProvider_2 = require("./openaiProvider");
17
+ Object.defineProperty(exports, "OpenAiProvider", { enumerable: true, get: function () { return openaiProvider_2.OpenAiProvider; } });
18
+ /**
19
+ * Resolve provider configuration from explicit config + environment.
20
+ * Precedence:
21
+ * 1. Explicit config.provider (must have matching key for ai providers).
22
+ * 2. GHUI_LLM_PROVIDER env var.
23
+ * 3. Auto-detect: anthropic key wins over openai key.
24
+ * 4. Heuristic fallback.
25
+ */
26
+ function createLlmService(config = {}) {
27
+ const env = process.env;
28
+ const anthropicKey = config.anthropicApiKey || env.ANTHROPIC_API_KEY;
29
+ const openaiKey = config.openaiApiKey || env.OPENAI_API_KEY;
30
+ const requested = config.provider || env.GHUI_LLM_PROVIDER;
31
+ if (requested === 'anthropic' && anthropicKey)
32
+ return new anthropicProvider_1.AnthropicProvider(anthropicKey, config.model);
33
+ if (requested === 'openai' && openaiKey)
34
+ return new openaiProvider_1.OpenAiProvider(openaiKey, config.model);
35
+ if (requested === 'heuristic')
36
+ return new heuristicProvider_1.HeuristicProvider();
37
+ // Auto-detect when no explicit pick.
38
+ if (!requested) {
39
+ if (anthropicKey)
40
+ return new anthropicProvider_1.AnthropicProvider(anthropicKey, config.model);
41
+ if (openaiKey)
42
+ return new openaiProvider_1.OpenAiProvider(openaiKey, config.model);
43
+ }
44
+ return new heuristicProvider_1.HeuristicProvider();
45
+ }
46
+ let cached = null;
47
+ let cachedKey = '';
48
+ function getDefaultLlmService(config = {}) {
49
+ const key = JSON.stringify({
50
+ provider: config.provider ?? process.env.GHUI_LLM_PROVIDER ?? '',
51
+ a: !!(config.anthropicApiKey || process.env.ANTHROPIC_API_KEY),
52
+ o: !!(config.openaiApiKey || process.env.OPENAI_API_KEY),
53
+ m: config.model ?? ''
54
+ });
55
+ if (cached && cachedKey === key)
56
+ return cached;
57
+ cached = createLlmService(config);
58
+ cachedKey = key;
59
+ return cached;
60
+ }
61
+ /** Force-reset for tests. */
62
+ function _resetLlmCache() {
63
+ cached = null;
64
+ cachedKey = '';
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backend/llm/index.ts"],"names":[],"mappings":";;;AAkBA,4CAkBC;AAID,oDAWC;AAGD,wCAGC;AAzDD,2DAAwD;AACxD,2DAAwD;AACxD,qDAAkD;AAIlD,yDAAkF;AAAzE,sHAAA,iBAAiB,OAAA;AAAE,mHAAA,cAAc,OAAA;AAAE,6GAAA,QAAQ,OAAA;AACpD,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAC1B,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB;;;;;;;GAOG;AACH,SAAgB,gBAAgB,CAAC,SAAoB,EAAE;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,YAAY,GAAG,MAAM,CAAC,eAAe,IAAI,GAAG,CAAC,iBAAiB,CAAC;IACrE,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,IAAI,GAAG,CAAC,cAAc,CAAC;IAC5D,MAAM,SAAS,GACb,MAAM,CAAC,QAAQ,IAAK,GAAG,CAAC,iBAAiD,CAAC;IAE5E,IAAI,SAAS,KAAK,WAAW,IAAI,YAAY;QAAE,OAAO,IAAI,qCAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACxG,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS;QAAE,OAAO,IAAI,+BAAc,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5F,IAAI,SAAS,KAAK,WAAW;QAAE,OAAO,IAAI,qCAAiB,EAAE,CAAC;IAE9D,qCAAqC;IACrC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,IAAI,YAAY;YAAE,OAAO,IAAI,qCAAiB,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3E,IAAI,SAAS;YAAE,OAAO,IAAI,+BAAc,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,IAAI,qCAAiB,EAAE,CAAC;AACjC,CAAC;AAED,IAAI,MAAM,GAAsB,IAAI,CAAC;AACrC,IAAI,SAAS,GAAG,EAAE,CAAC;AACnB,SAAgB,oBAAoB,CAAC,SAAoB,EAAE;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE;QAChE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QAC9D,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QACxD,CAAC,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;KACtB,CAAC,CAAC;IACH,IAAI,MAAM,IAAI,SAAS,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IAC/C,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClC,SAAS,GAAG,GAAG,CAAC;IAChB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6BAA6B;AAC7B,SAAgB,cAAc;IAC5B,MAAM,GAAG,IAAI,CAAC;IACd,SAAS,GAAG,EAAE,CAAC;AACjB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { LlmService, ScoreCandidate, ScoredCandidate } from './types';
2
+ export declare class OpenAiProvider implements LlmService {
3
+ private apiKey;
4
+ private model;
5
+ readonly name: "openai";
6
+ readonly isAi = true;
7
+ constructor(apiKey: string, model?: string);
8
+ score(query: string, candidates: ScoreCandidate[]): Promise<ScoredCandidate[]>;
9
+ summarize(text: string, opts?: {
10
+ hint?: string;
11
+ }): Promise<string>;
12
+ private call;
13
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAiProvider = void 0;
4
+ const DEFAULT_MODEL = 'gpt-4o-mini';
5
+ const ENDPOINT = 'https://api.openai.com/v1/chat/completions';
6
+ class OpenAiProvider {
7
+ apiKey;
8
+ model;
9
+ name = 'openai';
10
+ isAi = true;
11
+ constructor(apiKey, model = DEFAULT_MODEL) {
12
+ this.apiKey = apiKey;
13
+ this.model = model;
14
+ }
15
+ async score(query, candidates) {
16
+ if (candidates.length === 0)
17
+ return [];
18
+ const items = candidates.map((c, i) => ({
19
+ idx: i,
20
+ id: c.id,
21
+ text: c.text.replace(/\s+/g, ' ').slice(0, 240)
22
+ }));
23
+ const prompt = [
24
+ 'You score commit relevance to a developer query.',
25
+ 'Return ONLY a JSON object {"scores":[{"idx":number,"score":number}]} with score in [0,1].',
26
+ `Query: ${query}`,
27
+ 'Candidates:',
28
+ ...items.map((it) => `[${it.idx}] ${it.text}`)
29
+ ].join('\n');
30
+ const text = await this.call(prompt, 1024, true);
31
+ const parsed = parseScores(text, items.length);
32
+ return items.map((it) => ({ id: it.id, score: parsed[it.idx] ?? 0 }));
33
+ }
34
+ async summarize(text, opts) {
35
+ const prompt = [
36
+ opts?.hint ?? 'Summarize the following text in one short paragraph (max 3 sentences).',
37
+ '---',
38
+ text.length > 8000 ? text.slice(0, 8000) + '\n[truncated]' : text
39
+ ].join('\n');
40
+ const out = await this.call(prompt, 320, false);
41
+ return out.trim();
42
+ }
43
+ async call(prompt, maxTokens, json) {
44
+ const resp = await fetch(ENDPOINT, {
45
+ method: 'POST',
46
+ headers: {
47
+ 'Content-Type': 'application/json',
48
+ Authorization: `Bearer ${this.apiKey}`
49
+ },
50
+ body: JSON.stringify({
51
+ model: this.model,
52
+ max_tokens: maxTokens,
53
+ messages: [{ role: 'user', content: prompt }],
54
+ ...(json ? { response_format: { type: 'json_object' } } : {})
55
+ })
56
+ });
57
+ if (!resp.ok) {
58
+ const err = await resp.text().catch(() => '');
59
+ throw new Error(`openai ${resp.status}: ${err.slice(0, 200)}`);
60
+ }
61
+ const data = (await resp.json());
62
+ return data.choices?.[0]?.message?.content ?? '';
63
+ }
64
+ }
65
+ exports.OpenAiProvider = OpenAiProvider;
66
+ function parseScores(raw, length) {
67
+ const result = {};
68
+ // Try {scores:[...]} first, then bare array.
69
+ let parsedArr = null;
70
+ try {
71
+ const obj = JSON.parse(raw);
72
+ if (Array.isArray(obj?.scores))
73
+ parsedArr = obj.scores;
74
+ }
75
+ catch {
76
+ /* fall through */
77
+ }
78
+ if (!parsedArr) {
79
+ const match = raw.match(/\[[\s\S]*\]/);
80
+ if (match) {
81
+ try {
82
+ parsedArr = JSON.parse(match[0]);
83
+ }
84
+ catch {
85
+ /* ignore */
86
+ }
87
+ }
88
+ }
89
+ if (!parsedArr)
90
+ return result;
91
+ for (const item of parsedArr) {
92
+ if (typeof item?.idx === 'number' && item.idx >= 0 && item.idx < length) {
93
+ const s = Number(item.score);
94
+ if (Number.isFinite(s))
95
+ result[item.idx] = Math.max(0, Math.min(1, s));
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+ //# sourceMappingURL=openaiProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiProvider.js","sourceRoot":"","sources":["../../../src/backend/llm/openaiProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export type LlmProviderName = 'heuristic' | 'anthropic' | 'openai';
2
+ export interface ScoreCandidate {
3
+ id: string;
4
+ text: string;
5
+ }
6
+ export interface ScoredCandidate {
7
+ id: string;
8
+ score: number;
9
+ }
10
+ export interface LlmService {
11
+ readonly name: LlmProviderName;
12
+ readonly isAi: boolean;
13
+ /**
14
+ * Re-rank candidates by semantic relevance to a query.
15
+ * Returns scores in [0,1]. Implementations MAY return a subset of inputs
16
+ * (heuristic returns a score for every input; AI providers may also do so).
17
+ */
18
+ score(query: string, candidates: ScoreCandidate[]): Promise<ScoredCandidate[]>;
19
+ /**
20
+ * Produce a short prose summary of an arbitrary block of text (typically
21
+ * a unified diff or commit message body). Hard cap output at ~600 chars.
22
+ */
23
+ summarize(text: string, opts?: {
24
+ hint?: string;
25
+ }): Promise<string>;
26
+ }
27
+ export interface LlmConfig {
28
+ provider?: LlmProviderName;
29
+ anthropicApiKey?: string;
30
+ openaiApiKey?: string;
31
+ model?: string;
32
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/backend/llm/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tiny dependency-free natural-language date phrase parser.
3
+ * Returns ISO date strings (YYYY-MM-DD) for `since`/`until` slots.
4
+ *
5
+ * Handles the common phrases users actually type:
6
+ * - "last week" / "last month" / "last year"
7
+ * - "this week" / "this month"
8
+ * - "yesterday" / "today"
9
+ * - "in the last 30 days" / "past 2 weeks" / "last 3 months"
10
+ * - "since friday" / "since 2026-01-01"
11
+ * - bare ISO dates pass through
12
+ */
13
+ export interface DateRange {
14
+ since?: string;
15
+ until?: string;
16
+ matchedPhrase?: string;
17
+ }
18
+ export declare function parseDatePhrase(query: string, now?: Date): DateRange;
19
+ /** Strip date-related phrases from the query so they don't pollute keyword search. */
20
+ export declare function stripDatePhrase(query: string): string;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Tiny dependency-free natural-language date phrase parser.
4
+ * Returns ISO date strings (YYYY-MM-DD) for `since`/`until` slots.
5
+ *
6
+ * Handles the common phrases users actually type:
7
+ * - "last week" / "last month" / "last year"
8
+ * - "this week" / "this month"
9
+ * - "yesterday" / "today"
10
+ * - "in the last 30 days" / "past 2 weeks" / "last 3 months"
11
+ * - "since friday" / "since 2026-01-01"
12
+ * - bare ISO dates pass through
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.parseDatePhrase = parseDatePhrase;
16
+ exports.stripDatePhrase = stripDatePhrase;
17
+ const WEEKDAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
18
+ function parseDatePhrase(query, now = new Date()) {
19
+ const text = ' ' + query.toLowerCase() + ' ';
20
+ // "in the last N days/weeks/months/years" or "past N weeks" or "last 3 months"
21
+ const recent = text.match(/\b(?:in\s+the\s+)?(?:last|past)\s+(\d+)\s+(day|days|week|weeks|month|months|year|years)\b/);
22
+ if (recent) {
23
+ const n = parseInt(recent[1], 10);
24
+ const unit = recent[2];
25
+ const since = subtract(now, n, unit);
26
+ return { since: iso(since), matchedPhrase: recent[0].trim() };
27
+ }
28
+ // "last week"
29
+ if (/\blast\s+week\b/.test(text)) {
30
+ return { since: iso(subtract(now, 7, 'day')), matchedPhrase: 'last week' };
31
+ }
32
+ // "last month"
33
+ if (/\blast\s+month\b/.test(text)) {
34
+ return { since: iso(subtract(now, 30, 'day')), matchedPhrase: 'last month' };
35
+ }
36
+ // "last year"
37
+ if (/\blast\s+year\b/.test(text)) {
38
+ return { since: iso(subtract(now, 365, 'day')), matchedPhrase: 'last year' };
39
+ }
40
+ // "this week"
41
+ if (/\bthis\s+week\b/.test(text)) {
42
+ const d = new Date(now);
43
+ d.setUTCDate(d.getUTCDate() - d.getUTCDay());
44
+ return { since: iso(d), matchedPhrase: 'this week' };
45
+ }
46
+ // "this month"
47
+ if (/\bthis\s+month\b/.test(text)) {
48
+ const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
49
+ return { since: iso(d), matchedPhrase: 'this month' };
50
+ }
51
+ // "yesterday"
52
+ if (/\byesterday\b/.test(text)) {
53
+ const d = subtract(now, 1, 'day');
54
+ return { since: iso(d), until: iso(d), matchedPhrase: 'yesterday' };
55
+ }
56
+ // "today"
57
+ if (/\btoday\b/.test(text)) {
58
+ return { since: iso(now), matchedPhrase: 'today' };
59
+ }
60
+ // "since <weekday>"
61
+ const sinceWd = text.match(/\bsince\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\b/);
62
+ if (sinceWd) {
63
+ const target = WEEKDAYS.indexOf(sinceWd[1]);
64
+ const cur = now.getUTCDay();
65
+ const diff = ((cur - target + 7) % 7) || 7;
66
+ return { since: iso(subtract(now, diff, 'day')), matchedPhrase: sinceWd[0] };
67
+ }
68
+ // "since <iso-date>" or "since <yyyy/mm/dd>"
69
+ const sinceIso = text.match(/\bsince\s+(\d{4}[-/]\d{1,2}[-/]\d{1,2})\b/);
70
+ if (sinceIso) {
71
+ return { since: normalizeDate(sinceIso[1]), matchedPhrase: sinceIso[0] };
72
+ }
73
+ // bare iso date in query
74
+ const bareIso = text.match(/\b(\d{4}-\d{2}-\d{2})\b/);
75
+ if (bareIso) {
76
+ return { since: bareIso[1], matchedPhrase: bareIso[1] };
77
+ }
78
+ return {};
79
+ }
80
+ function subtract(d, n, unit) {
81
+ const out = new Date(d);
82
+ if (unit.startsWith('day'))
83
+ out.setUTCDate(out.getUTCDate() - n);
84
+ else if (unit.startsWith('week'))
85
+ out.setUTCDate(out.getUTCDate() - 7 * n);
86
+ else if (unit.startsWith('month'))
87
+ out.setUTCMonth(out.getUTCMonth() - n);
88
+ else if (unit.startsWith('year'))
89
+ out.setUTCFullYear(out.getUTCFullYear() - n);
90
+ return out;
91
+ }
92
+ function iso(d) {
93
+ return d.toISOString().slice(0, 10);
94
+ }
95
+ function normalizeDate(s) {
96
+ return s.replace(/\//g, '-').split('-').map((p, i) => (i === 0 ? p : p.padStart(2, '0'))).join('-');
97
+ }
98
+ /** Strip date-related phrases from the query so they don't pollute keyword search. */
99
+ function stripDatePhrase(query) {
100
+ const phrases = [
101
+ /\b(?:in\s+the\s+)?(?:last|past)\s+\d+\s+(?:day|days|week|weeks|month|months|year|years)\b/gi,
102
+ /\blast\s+(?:week|month|year)\b/gi,
103
+ /\bthis\s+(?:week|month)\b/gi,
104
+ /\b(?:yesterday|today)\b/gi,
105
+ /\bsince\s+(?:sunday|monday|tuesday|wednesday|thursday|friday|saturday)\b/gi,
106
+ /\bsince\s+\d{4}[-/]\d{1,2}[-/]\d{1,2}\b/gi
107
+ ];
108
+ let out = query;
109
+ for (const re of phrases)
110
+ out = out.replace(re, ' ');
111
+ return out.replace(/\s+/g, ' ').trim();
112
+ }
113
+ //# sourceMappingURL=datePhrase.js.map