@dharminnagar/bruh 0.1.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/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ import { log } from '@clack/prompts';
2
+ import { formatUserError } from './errors';
3
+ import { main } from './main';
4
+ main().catch((error) => {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ const formatted = formatUserError(message);
7
+ if (process.stdout.isTTY) {
8
+ log.error(formatted.title);
9
+ log.message(`Details: ${formatted.details}`);
10
+ log.info(`Next: ${formatted.recovery}`);
11
+ }
12
+ else {
13
+ console.error(`ERROR: ${formatted.title}`);
14
+ console.error(`DETAILS: ${formatted.details}`);
15
+ console.error(`NEXT: ${formatted.recovery}`);
16
+ }
17
+ process.exitCode = 1;
18
+ });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAE3C,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3B,GAAG,CAAC,OAAO,CAAC,YAAY,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,UAAU,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,YAAY,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,KAAK,CAAC,SAAS,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function main(argv?: string[]): Promise<void>;
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAiIA,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,GACrC,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
package/dist/main.js ADDED
@@ -0,0 +1,132 @@
1
+ import { cancel, isCancel, log, spinner, text } from '@clack/prompts';
2
+ import { parseArgs, usageText } from './args';
3
+ import { loadConfig, mergeProviderContext, resolveConfigPath, runInteractiveSetup, saveConfig, } from './config';
4
+ import { assertCommitExists, assertGitRepository, collectCommitsAfter, currentRepositoryPath, } from './git';
5
+ import { runProviderAnalysis } from './providers';
6
+ import { formatReport } from './report';
7
+ function logInfo(message) {
8
+ if (process.stdout.isTTY) {
9
+ log.info(message);
10
+ return;
11
+ }
12
+ console.log(`[info] ${message}`);
13
+ }
14
+ function formatElapsedTime(milliseconds) {
15
+ if (milliseconds < 1000) {
16
+ return `${milliseconds}ms`;
17
+ }
18
+ const seconds = (milliseconds / 1000).toFixed(1);
19
+ return `${seconds}s`;
20
+ }
21
+ function supportsSpinner() {
22
+ return process.stdout.isTTY;
23
+ }
24
+ async function runWithStatus(message, work, successMessage) {
25
+ if (!supportsSpinner()) {
26
+ logInfo(message);
27
+ return work();
28
+ }
29
+ const activity = spinner();
30
+ const startedAt = Date.now();
31
+ activity.start(message);
32
+ try {
33
+ const result = await work();
34
+ const elapsed = formatElapsedTime(Date.now() - startedAt);
35
+ activity.stop(successMessage ?? `${message} (${elapsed})`);
36
+ return result;
37
+ }
38
+ catch (error) {
39
+ activity.stop(`${message} failed`, 1);
40
+ throw error;
41
+ }
42
+ }
43
+ function normalizeCommitInput(value) {
44
+ return value.trim();
45
+ }
46
+ async function promptForCommitHash() {
47
+ const value = await text({
48
+ message: 'Enter the base commit hash (analysis starts after this commit)',
49
+ validate(inputValue) {
50
+ return inputValue.trim().length > 0
51
+ ? undefined
52
+ : 'Commit hash is required';
53
+ },
54
+ });
55
+ if (isCancel(value)) {
56
+ cancel('Commit input cancelled.');
57
+ throw new Error('Commit input cancelled by user.');
58
+ }
59
+ return normalizeCommitInput(value);
60
+ }
61
+ function resolveProvider(cliProvider, storedConfig) {
62
+ return cliProvider ?? storedConfig.provider;
63
+ }
64
+ async function ensureConfig(configPath, forceInteractive) {
65
+ const existing = await loadConfig(configPath);
66
+ if (!forceInteractive && existing && existing.apiKey.trim().length > 0) {
67
+ return existing;
68
+ }
69
+ logInfo('Running interactive setup to configure provider credentials.');
70
+ const configured = await runInteractiveSetup(existing ?? undefined);
71
+ await saveConfig(configPath, configured);
72
+ logInfo(`Saved config to ${configPath}`);
73
+ return configured;
74
+ }
75
+ async function maybeWriteOutput(outputPath, report) {
76
+ if (!outputPath) {
77
+ return;
78
+ }
79
+ await Bun.write(outputPath, report);
80
+ logInfo(`Report written to ${outputPath}`);
81
+ }
82
+ export async function main(argv = process.argv.slice(2)) {
83
+ const runStartedAt = Date.now();
84
+ const args = parseArgs(argv);
85
+ if (args.help) {
86
+ console.log(usageText());
87
+ return;
88
+ }
89
+ const configPath = resolveConfigPath(args.configPath);
90
+ const storedConfig = await ensureConfig(configPath, args.init);
91
+ if (args.init && !args.commit) {
92
+ console.log('Setup complete. Run again with a commit hash to analyze history.');
93
+ return;
94
+ }
95
+ const provider = resolveProvider(args.provider, storedConfig);
96
+ const providerContext = mergeProviderContext(provider, storedConfig, {
97
+ apiKey: args.apiKey,
98
+ model: args.model,
99
+ });
100
+ const commitInput = args.commit
101
+ ? normalizeCommitInput(args.commit)
102
+ : await promptForCommitHash();
103
+ await runWithStatus('Validating git repository', async () => {
104
+ await assertGitRepository();
105
+ });
106
+ const validatedCommit = await runWithStatus('Validating commit hash', async () => assertCommitExists(commitInput));
107
+ const commits = await runWithStatus('Collecting commits and diffs from git history', async () => collectCommitsAfter(validatedCommit));
108
+ const analysisInput = {
109
+ repositoryPath: currentRepositoryPath(),
110
+ sinceCommit: validatedCommit,
111
+ commits,
112
+ };
113
+ const providerResult = await runWithStatus(`Analyzing ${commits.length} commit(s) with ${provider}`, async () => runProviderAnalysis(provider, providerContext, analysisInput));
114
+ const metadata = {
115
+ provider,
116
+ model: providerResult.model,
117
+ commitCount: commits.length,
118
+ sinceCommit: validatedCommit,
119
+ generatedAt: new Date().toISOString(),
120
+ };
121
+ const output = await runWithStatus('Formatting report', async () => Promise.resolve(formatReport(args.format, metadata, providerResult.analysis)));
122
+ await maybeWriteOutput(args.outputPath, output);
123
+ console.log(output);
124
+ const totalElapsed = formatElapsedTime(Date.now() - runStartedAt);
125
+ if (process.stdout.isTTY) {
126
+ log.success(`Analysis complete in ${totalElapsed}`);
127
+ }
128
+ else {
129
+ console.log(`[info] Analysis complete in ${totalElapsed}`);
130
+ }
131
+ }
132
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQxC,SAAS,OAAO,CAAC,OAAe;IAC9B,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAClB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,iBAAiB,CAAC,YAAoB;IAC7C,IAAI,YAAY,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,GAAG,YAAY,IAAI,CAAC;IAC7B,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,GAAG,OAAO,GAAG,CAAC;AACvB,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;AAC9B,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,OAAe,EACf,IAAsB,EACtB,cAAuB;IAEvB,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,OAAO,CAAC,CAAC;QACjB,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAExB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC,cAAc,IAAI,GAAG,OAAO,KAAK,OAAO,GAAG,CAAC,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,SAAS,EAAE,CAAC,CAAC,CAAC;QACtC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,KAAK,UAAU,mBAAmB;IAChC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;QACvB,OAAO,EAAE,gEAAgE;QACzE,QAAQ,CAAC,UAAU;YACjB,OAAO,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;gBACjC,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,yBAAyB,CAAC;QAChC,CAAC;KACF,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,yBAAyB,CAAC,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,eAAe,CACtB,WAAiC,EACjC,YAA0B;IAE1B,OAAO,WAAW,IAAI,YAAY,CAAC,QAAQ,CAAC;AAC9C,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,UAAkB,EAClB,gBAAyB;IAEzB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IAE9C,IAAI,CAAC,gBAAgB,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,8DAA8D,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,QAAQ,IAAI,SAAS,CAAC,CAAC;IACpE,MAAM,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,CAAC,mBAAmB,UAAU,EAAE,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,UAA8B,EAC9B,MAAc;IAEd,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;IACT,CAAC;IAED,MAAM,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,OAAiB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAEtC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAE7B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAE/D,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CACT,kEAAkE,CACnE,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,eAAe,GAAG,oBAAoB,CAAC,QAAQ,EAAE,YAAY,EAAE;QACnE,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM;QAC7B,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC;QACnC,CAAC,CAAC,MAAM,mBAAmB,EAAE,CAAC;IAEhC,MAAM,aAAa,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,mBAAmB,EAAE,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,MAAM,aAAa,CACzC,wBAAwB,EACxB,KAAK,IAAI,EAAE,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAC5C,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,aAAa,CACjC,+CAA+C,EAC/C,KAAK,IAAI,EAAE,CAAC,mBAAmB,CAAC,eAAe,CAAC,CACjD,CAAC;IAEF,MAAM,aAAa,GAAkB;QACnC,cAAc,EAAE,qBAAqB,EAAE;QACvC,WAAW,EAAE,eAAe;QAC5B,OAAO;KACR,CAAC;IAEF,MAAM,cAAc,GAAG,MAAM,aAAa,CACxC,aAAa,OAAO,CAAC,MAAM,mBAAmB,QAAQ,EAAE,EACxD,KAAK,IAAI,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,CAAC,CAC1E,CAAC;IAEF,MAAM,QAAQ,GAAqB;QACjC,QAAQ;QACR,KAAK,EAAE,cAAc,CAAC,KAAK;QAC3B,WAAW,EAAE,OAAO,CAAC,MAAM;QAC3B,WAAW,EAAE,eAAe;QAC5B,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACtC,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE,CACjE,OAAO,CAAC,OAAO,CACb,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,CAC7D,CACF,CAAC;IAEF,MAAM,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEpB,MAAM,YAAY,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,OAAO,CAAC,wBAAwB,YAAY,EAAE,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,+BAA+B,YAAY,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AnalysisInput, Provider, ProviderContext, ProviderRunResult } from './types';
2
+ export declare function runProviderAnalysis(provider: Provider, context: ProviderContext, input: AnalysisInput): Promise<ProviderRunResult>;
3
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb,QAAQ,EACR,eAAe,EACf,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAioBjB,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAW5B"}
@@ -0,0 +1,484 @@
1
+ const OPENAI_DEFAULT_MODEL = 'gpt-4.1-mini';
2
+ const CLAUDE_DEFAULT_MODEL = 'claude-3-5-haiku-latest';
3
+ const CLOUDFLARE_DEFAULT_MODEL = '@cf/meta/llama-3.1-8b-instruct';
4
+ function isLikelyRunnableCloudflareModel(value) {
5
+ const trimmed = value.trim();
6
+ return trimmed.length > 0 && trimmed.includes('/') && !trimmed.includes(' ');
7
+ }
8
+ function cloudflareModelCandidates(model) {
9
+ const base = model.trim();
10
+ const candidates = [base];
11
+ if (base.includes('-fp8-fast')) {
12
+ candidates.push(base.replace('-fp8-fast', '-fp8'));
13
+ candidates.push(base.replace('-fp8-fast', ''));
14
+ }
15
+ else if (base.includes('-fp8')) {
16
+ candidates.push(base.replace('-fp8', '-fp8-fast'));
17
+ candidates.push(base.replace('-fp8', ''));
18
+ }
19
+ return [...new Set(candidates.map((item) => item.trim()).filter(Boolean))];
20
+ }
21
+ function extractCloudflareText(result) {
22
+ if (!result) {
23
+ return '';
24
+ }
25
+ if (typeof result === 'string') {
26
+ return result.trim();
27
+ }
28
+ if (!Array.isArray(result) && typeof result === 'object') {
29
+ const record = result;
30
+ const directCandidates = [
31
+ record.response,
32
+ record.output_text,
33
+ record.output,
34
+ record.text,
35
+ record.completion,
36
+ ];
37
+ for (const candidate of directCandidates) {
38
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
39
+ return candidate.trim();
40
+ }
41
+ }
42
+ if (Array.isArray(record.response)) {
43
+ const responseText = record.response
44
+ .map((item) => (typeof item === 'string' ? item.trim() : ''))
45
+ .filter((item) => item.length > 0)
46
+ .join('\n');
47
+ if (responseText.length > 0) {
48
+ return responseText;
49
+ }
50
+ }
51
+ if (Array.isArray(record.choices)) {
52
+ const choiceText = record.choices
53
+ .map((choice) => {
54
+ if (!choice || typeof choice !== 'object') {
55
+ return '';
56
+ }
57
+ const choiceRecord = choice;
58
+ const message = choiceRecord.message;
59
+ if (message && typeof message === 'object') {
60
+ const messageRecord = message;
61
+ const content = messageRecord.content;
62
+ if (typeof content === 'string') {
63
+ return content.trim();
64
+ }
65
+ // OpenAI-compatible responses may return content as an array of parts.
66
+ if (Array.isArray(content)) {
67
+ const contentFromParts = content
68
+ .map((part) => {
69
+ if (!part || typeof part !== 'object') {
70
+ return '';
71
+ }
72
+ const partRecord = part;
73
+ const partText = partRecord.text;
74
+ return typeof partText === 'string' ? partText.trim() : '';
75
+ })
76
+ .filter((item) => item.length > 0)
77
+ .join('\n');
78
+ if (contentFromParts.length > 0) {
79
+ return contentFromParts;
80
+ }
81
+ }
82
+ const messageReasoning = messageRecord.reasoning_content;
83
+ if (typeof messageReasoning === 'string' &&
84
+ messageReasoning.trim().length > 0) {
85
+ return messageReasoning.trim();
86
+ }
87
+ }
88
+ const reasoningContent = choiceRecord.reasoning_content;
89
+ if (typeof reasoningContent === 'string' &&
90
+ reasoningContent.trim().length > 0) {
91
+ return reasoningContent.trim();
92
+ }
93
+ const text = choiceRecord.text;
94
+ return typeof text === 'string' ? text.trim() : '';
95
+ })
96
+ .filter((item) => item.length > 0)
97
+ .join('\n');
98
+ if (choiceText.length > 0) {
99
+ return choiceText;
100
+ }
101
+ }
102
+ }
103
+ return '';
104
+ }
105
+ function buildPrompt(input) {
106
+ const system = [
107
+ 'You are an expert software engineering release analyst.',
108
+ 'Given commit metadata and diffs, produce a factual report.',
109
+ 'Return ONLY valid JSON with this shape:',
110
+ '{',
111
+ ' "overview": "string",',
112
+ ' "keyChanges": ["string"],',
113
+ ' "risks": ["string"],',
114
+ ' "followUps": ["string"],',
115
+ ' "impactedAreas": ["string"]',
116
+ '}',
117
+ 'Rules:',
118
+ '- Analyze all provided commits as a single release range.',
119
+ '- Keep keyChanges, risks, followUps, impactedAreas concise and actionable.',
120
+ '- Mention concrete files/components when possible.',
121
+ ].join('\n');
122
+ const commitPayload = input.commits.map((commit) => ({
123
+ hash: commit.hash,
124
+ author: commit.author,
125
+ date: commit.date,
126
+ title: commit.title,
127
+ body: commit.body,
128
+ diff: commit.diff,
129
+ diffWasTruncated: commit.diffWasTruncated,
130
+ }));
131
+ const user = [
132
+ `Repository: ${input.repositoryPath}`,
133
+ `Analyze commits after: ${input.sinceCommit}`,
134
+ `Commit count: ${input.commits.length}`,
135
+ '',
136
+ JSON.stringify({ commits: commitPayload }, null, 2),
137
+ ].join('\n');
138
+ return { system, user };
139
+ }
140
+ function normalizeStringArray(value) {
141
+ if (!Array.isArray(value)) {
142
+ return [];
143
+ }
144
+ return value
145
+ .map((item) => (typeof item === 'string' ? item.trim() : ''))
146
+ .filter((item) => item.length > 0);
147
+ }
148
+ function parseJsonObject(text) {
149
+ const trimmed = text.trim();
150
+ try {
151
+ return JSON.parse(trimmed);
152
+ }
153
+ catch {
154
+ const firstBrace = trimmed.indexOf('{');
155
+ const lastBrace = trimmed.lastIndexOf('}');
156
+ if (firstBrace < 0 || lastBrace < 0 || firstBrace >= lastBrace) {
157
+ return null;
158
+ }
159
+ const candidate = trimmed.slice(firstBrace, lastBrace + 1);
160
+ try {
161
+ return JSON.parse(candidate);
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
167
+ }
168
+ function stripCodeFences(text) {
169
+ return text
170
+ .replace(/^```[a-zA-Z]*\n?/g, '')
171
+ .replace(/\n?```$/g, '')
172
+ .trim();
173
+ }
174
+ function extractQuotedValue(raw, fieldName) {
175
+ const pattern = new RegExp(`"${fieldName}"\\s*:\\s*"([\\s\\S]*?)"`, 'i');
176
+ const match = raw.match(pattern);
177
+ if (!match || !match[1]) {
178
+ return null;
179
+ }
180
+ return match[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').trim();
181
+ }
182
+ function extractStringArray(raw, fieldName) {
183
+ const pattern = new RegExp(`"${fieldName}"\\s*:\\s*\\[([\\s\\S]*?)\\]`, 'i');
184
+ const match = raw.match(pattern);
185
+ if (!match || !match[1]) {
186
+ return [];
187
+ }
188
+ const arrayBody = match[1];
189
+ const stringMatches = [...arrayBody.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"/g)];
190
+ return stringMatches
191
+ .map((item) => item[1].replace(/\\n/g, '\n').replace(/\\"/g, '"').trim())
192
+ .filter((item) => item.length > 0);
193
+ }
194
+ function normalizeBulletOrSentenceList(value) {
195
+ const trimmed = value.trim();
196
+ if (!trimmed) {
197
+ return [];
198
+ }
199
+ if (trimmed.includes('\n- ') || trimmed.startsWith('- ')) {
200
+ return trimmed
201
+ .split('\n')
202
+ .map((line) => line.replace(/^[-*]\s+/, '').trim())
203
+ .filter((line) => line.length > 0);
204
+ }
205
+ return trimmed
206
+ .split(/\.(?:\s+|$)|;\s+|\n+/)
207
+ .map((part) => part.trim())
208
+ .filter((part) => part.length > 0)
209
+ .slice(0, 8);
210
+ }
211
+ function extractNarrativeSection(raw, title) {
212
+ const escapedTitle = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
213
+ const boundary = '(?:Overview|Key changes?|Risks?|Follow-?ups?|Impacted areas?)\\s*:';
214
+ const regex = new RegExp(`${escapedTitle}\\s*:\\s*([\\s\\S]*?)(?=\\n\\s*(?:${boundary})|$)`, 'i');
215
+ const match = raw.match(regex);
216
+ return match?.[1]?.trim() ?? '';
217
+ }
218
+ function isPromptEchoLine(line) {
219
+ const normalized = line.toLowerCase();
220
+ return (normalized.startsWith('we need to produce json report') ||
221
+ normalized.startsWith('return only valid json') ||
222
+ normalized.startsWith('given commit metadata and diffs') ||
223
+ normalized.startsWith('analyze all provided commits as a single release range'));
224
+ }
225
+ function sanitizeNarrativeOverview(value) {
226
+ const lines = value
227
+ .split('\n')
228
+ .map((line) => line.trim())
229
+ .filter((line) => line.length > 0);
230
+ if (lines.length === 0) {
231
+ return '';
232
+ }
233
+ const withoutPromptEcho = lines.filter((line) => !isPromptEchoLine(line));
234
+ const selectedLines = withoutPromptEcho.length > 0 ? withoutPromptEcho : lines;
235
+ return selectedLines.join(' ').trim();
236
+ }
237
+ function synthesizeNarrativeOverview(keyChanges, risks, followUps, impactedAreas) {
238
+ const summaryParts = [];
239
+ if (keyChanges.length > 0) {
240
+ summaryParts.push(`Key updates include ${keyChanges.slice(0, 2).join('; ')}.`);
241
+ }
242
+ if (risks.length > 0) {
243
+ summaryParts.push(`Main risks: ${risks.slice(0, 1).join('; ')}.`);
244
+ }
245
+ if (followUps.length > 0) {
246
+ summaryParts.push(`Suggested follow-up: ${followUps.slice(0, 1).join('; ')}.`);
247
+ }
248
+ if (impactedAreas.length > 0) {
249
+ summaryParts.push(`Impacted areas include ${impactedAreas.slice(0, 2).join('; ')}.`);
250
+ }
251
+ return summaryParts.join(' ').trim();
252
+ }
253
+ function parseNarrativeAnalysis(rawText) {
254
+ const stripped = stripCodeFences(rawText).trim();
255
+ if (!stripped) {
256
+ return null;
257
+ }
258
+ const overviewRaw = extractNarrativeSection(stripped, 'Overview');
259
+ const keyChangesRaw = extractNarrativeSection(stripped, 'Key changes');
260
+ const risksRaw = extractNarrativeSection(stripped, 'Risks');
261
+ const followUpsRaw = extractNarrativeSection(stripped, 'Follow-ups');
262
+ const impactedAreasRaw = extractNarrativeSection(stripped, 'Impacted Areas');
263
+ const hasNamedSections = overviewRaw.length > 0 ||
264
+ keyChangesRaw.length > 0 ||
265
+ risksRaw.length > 0 ||
266
+ followUpsRaw.length > 0 ||
267
+ impactedAreasRaw.length > 0;
268
+ if (!hasNamedSections) {
269
+ return null;
270
+ }
271
+ const keyChanges = normalizeBulletOrSentenceList(keyChangesRaw);
272
+ const risks = normalizeBulletOrSentenceList(risksRaw);
273
+ const followUps = normalizeBulletOrSentenceList(followUpsRaw);
274
+ const impactedAreas = normalizeBulletOrSentenceList(impactedAreasRaw);
275
+ const sanitizedOverview = sanitizeNarrativeOverview(overviewRaw);
276
+ const synthesizedOverview = synthesizeNarrativeOverview(keyChanges, risks, followUps, impactedAreas);
277
+ const overview = sanitizedOverview || synthesizedOverview;
278
+ return {
279
+ overview: overview || 'No overview provided by provider.',
280
+ keyChanges,
281
+ risks,
282
+ followUps,
283
+ impactedAreas,
284
+ rawProviderOutput: rawText,
285
+ };
286
+ }
287
+ function parsePartialJsonLikeAnalysis(rawText) {
288
+ const stripped = stripCodeFences(rawText);
289
+ if (!stripped.includes('"overview"') && !stripped.includes('"keyChanges"')) {
290
+ return null;
291
+ }
292
+ const overview = extractQuotedValue(stripped, 'overview') ?? '';
293
+ const parsedResult = {
294
+ overview: overview || 'No overview provided by provider.',
295
+ keyChanges: extractStringArray(stripped, 'keyChanges'),
296
+ risks: extractStringArray(stripped, 'risks'),
297
+ followUps: extractStringArray(stripped, 'followUps'),
298
+ impactedAreas: extractStringArray(stripped, 'impactedAreas'),
299
+ rawProviderOutput: rawText,
300
+ };
301
+ return parsedResult;
302
+ }
303
+ function normalizeAnalysis(rawText) {
304
+ const parsed = parseJsonObject(rawText);
305
+ if (!parsed) {
306
+ const partial = parsePartialJsonLikeAnalysis(rawText);
307
+ if (partial) {
308
+ return {
309
+ overview: partial.overview,
310
+ keyChanges: partial.keyChanges,
311
+ risks: partial.risks,
312
+ followUps: partial.followUps,
313
+ impactedAreas: partial.impactedAreas,
314
+ rawProviderOutput: rawText,
315
+ };
316
+ }
317
+ const narrative = parseNarrativeAnalysis(rawText);
318
+ if (narrative) {
319
+ return narrative;
320
+ }
321
+ return {
322
+ overview: rawText.trim() || 'No overview provided by provider.',
323
+ keyChanges: [],
324
+ risks: [],
325
+ followUps: [],
326
+ impactedAreas: [],
327
+ rawProviderOutput: rawText,
328
+ };
329
+ }
330
+ const overview = typeof parsed.overview === 'string' && parsed.overview.trim().length > 0
331
+ ? parsed.overview.trim()
332
+ : 'No overview provided by provider.';
333
+ return {
334
+ overview,
335
+ keyChanges: normalizeStringArray(parsed.keyChanges),
336
+ risks: normalizeStringArray(parsed.risks),
337
+ followUps: normalizeStringArray(parsed.followUps),
338
+ impactedAreas: normalizeStringArray(parsed.impactedAreas),
339
+ rawProviderOutput: rawText,
340
+ };
341
+ }
342
+ async function callOpenAI(context, input) {
343
+ const model = context.model ?? OPENAI_DEFAULT_MODEL;
344
+ const prompt = buildPrompt(input);
345
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
346
+ method: 'POST',
347
+ headers: {
348
+ 'Content-Type': 'application/json',
349
+ Authorization: `Bearer ${context.apiKey}`,
350
+ },
351
+ body: JSON.stringify({
352
+ model,
353
+ temperature: 0.2,
354
+ messages: [
355
+ { role: 'system', content: prompt.system },
356
+ { role: 'user', content: prompt.user },
357
+ ],
358
+ }),
359
+ });
360
+ if (!response.ok) {
361
+ const errorText = await response.text();
362
+ throw new Error(`OpenAI request failed: ${response.status} ${errorText}`);
363
+ }
364
+ const data = (await response.json());
365
+ const content = data.choices?.[0]?.message?.content;
366
+ if (!content) {
367
+ throw new Error('OpenAI response did not include completion content.');
368
+ }
369
+ return {
370
+ model,
371
+ analysis: normalizeAnalysis(content),
372
+ };
373
+ }
374
+ async function callClaude(context, input) {
375
+ const model = context.model ?? CLAUDE_DEFAULT_MODEL;
376
+ const prompt = buildPrompt(input);
377
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
378
+ method: 'POST',
379
+ headers: {
380
+ 'Content-Type': 'application/json',
381
+ 'x-api-key': context.apiKey,
382
+ 'anthropic-version': '2023-06-01',
383
+ },
384
+ body: JSON.stringify({
385
+ model,
386
+ max_tokens: 1800,
387
+ temperature: 0.2,
388
+ system: prompt.system,
389
+ messages: [{ role: 'user', content: prompt.user }],
390
+ }),
391
+ });
392
+ if (!response.ok) {
393
+ const errorText = await response.text();
394
+ throw new Error(`Claude request failed: ${response.status} ${errorText}`);
395
+ }
396
+ const data = (await response.json());
397
+ const textContent = data.content
398
+ ?.filter((item) => item.type === 'text' && typeof item.text === 'string')
399
+ .map((item) => item.text?.trim() ?? '')
400
+ .join('\n') ?? '';
401
+ if (!textContent) {
402
+ throw new Error('Claude response did not include text content.');
403
+ }
404
+ return {
405
+ model,
406
+ analysis: normalizeAnalysis(textContent),
407
+ };
408
+ }
409
+ async function callCloudflare(context, input) {
410
+ if (!context.cloudflareAccountId) {
411
+ throw new Error('Cloudflare account ID is missing. Re-run setup with --init and provide account ID.');
412
+ }
413
+ const model = context.model ?? CLOUDFLARE_DEFAULT_MODEL;
414
+ if (!isLikelyRunnableCloudflareModel(model)) {
415
+ throw new Error(`Invalid Cloudflare model id: ${model}. Use a full route-style id like @cf/meta/llama-3.3-70b-instruct-fp8-fast and re-run --init.`);
416
+ }
417
+ const prompt = buildPrompt(input);
418
+ const attemptedModels = [];
419
+ let data = null;
420
+ let resolvedModel = model;
421
+ for (const candidate of cloudflareModelCandidates(model)) {
422
+ attemptedModels.push(candidate);
423
+ const normalizedModel = encodeURI(candidate.trim().replace(/^\/+/, ''));
424
+ const endpoint = `https://api.cloudflare.com/client/v4/accounts/${context.cloudflareAccountId}/ai/run/${normalizedModel}`;
425
+ const response = await fetch(endpoint, {
426
+ method: 'POST',
427
+ headers: {
428
+ 'Content-Type': 'application/json',
429
+ Authorization: `Bearer ${context.apiKey}`,
430
+ },
431
+ body: JSON.stringify({
432
+ messages: [
433
+ { role: 'system', content: prompt.system },
434
+ { role: 'user', content: prompt.user },
435
+ ],
436
+ temperature: 0.2,
437
+ }),
438
+ });
439
+ if (!response.ok) {
440
+ const errorText = await response.text();
441
+ if (response.status === 400 &&
442
+ errorText.includes('No route for that URI')) {
443
+ continue;
444
+ }
445
+ throw new Error(`Cloudflare request failed: ${response.status} ${errorText}`);
446
+ }
447
+ data = (await response.json());
448
+ resolvedModel = candidate;
449
+ break;
450
+ }
451
+ if (!data) {
452
+ throw new Error(`Cloudflare request failed: model route not found for ${model}. Tried: ${attemptedModels.join(', ')}. Re-run setup and choose a currently available full model id.`);
453
+ }
454
+ if (data.success === false && data.errors?.length) {
455
+ const errorMessage = data.errors
456
+ .map((item) => item.message ?? 'Unknown Cloudflare error')
457
+ .join('; ');
458
+ throw new Error(`Cloudflare request failed: ${errorMessage}`);
459
+ }
460
+ const content = extractCloudflareText(data.result);
461
+ if (!content) {
462
+ const preview = data.result && typeof data.result === 'object'
463
+ ? JSON.stringify(data.result).slice(0, 400)
464
+ : String(data.result ?? 'null');
465
+ throw new Error(`Cloudflare response did not include analysis text. This usually means the selected model is not a text-generation/chat model. Model: ${model}. Result preview: ${preview}`);
466
+ }
467
+ return {
468
+ model: resolvedModel,
469
+ analysis: normalizeAnalysis(content),
470
+ };
471
+ }
472
+ export async function runProviderAnalysis(provider, context, input) {
473
+ switch (provider) {
474
+ case 'openai':
475
+ return callOpenAI(context, input);
476
+ case 'claude':
477
+ return callClaude(context, input);
478
+ case 'cloudflare':
479
+ return callCloudflare(context, input);
480
+ default:
481
+ throw new Error(`Unsupported provider: ${provider}`);
482
+ }
483
+ }
484
+ //# sourceMappingURL=providers.js.map