git-coco 0.32.0 → 0.33.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/README.md +7 -0
- package/dist/index.d.ts +63 -8
- package/dist/index.esm.mjs +1222 -73
- package/dist/index.js +1220 -70
- package/package.json +7 -4
package/dist/index.esm.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { StructuredOutputParser, BaseOutputParser, StringOutputParser } from '@l
|
|
|
22
22
|
import { minimatch } from 'minimatch';
|
|
23
23
|
import { simpleGit, GitError } from 'simple-git';
|
|
24
24
|
import { Document, BaseDocumentTransformer } from '@langchain/core/documents';
|
|
25
|
-
import { createTwoFilesPatch } from 'diff';
|
|
25
|
+
import { createTwoFilesPatch, parsePatch, formatPatch } from 'diff';
|
|
26
26
|
import { ensureConfig, Runnable } from '@langchain/core/runnables';
|
|
27
27
|
import { BaseLangChain, BaseLanguageModel } from '@langchain/core/language_models/base';
|
|
28
28
|
import { RUN_KEY } from '@langchain/core/outputs';
|
|
@@ -37,7 +37,7 @@ import '@langchain/core/utils/json_patch';
|
|
|
37
37
|
import '@langchain/core/utils/env';
|
|
38
38
|
import '@langchain/core/utils/async_caller';
|
|
39
39
|
import { encoding_for_model } from 'tiktoken';
|
|
40
|
-
import {
|
|
40
|
+
import { spawn, exec } from 'child_process';
|
|
41
41
|
import * as readline from 'readline';
|
|
42
42
|
import { pathToFileURL } from 'url';
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ import { pathToFileURL } from 'url';
|
|
|
46
46
|
/**
|
|
47
47
|
* Current build version from package.json
|
|
48
48
|
*/
|
|
49
|
-
const BUILD_VERSION = "0.
|
|
49
|
+
const BUILD_VERSION = "0.33.0";
|
|
50
50
|
|
|
51
51
|
const isInteractive = (config) => {
|
|
52
52
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -501,6 +501,8 @@ function loadEnvConfig(config) {
|
|
|
501
501
|
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
502
502
|
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
503
503
|
'COCO_SERVICE_FIELDS',
|
|
504
|
+
'COCO_SERVICE_DYNAMIC_MODELS',
|
|
505
|
+
'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE',
|
|
504
506
|
];
|
|
505
507
|
envKeys.forEach((key) => {
|
|
506
508
|
const envVarName = toEnvVarName(key);
|
|
@@ -515,7 +517,9 @@ function loadEnvConfig(config) {
|
|
|
515
517
|
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
516
518
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
517
519
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
518
|
-
key === 'COCO_SERVICE_FIELDS'
|
|
520
|
+
key === 'COCO_SERVICE_FIELDS' ||
|
|
521
|
+
key === 'COCO_SERVICE_DYNAMIC_MODELS' ||
|
|
522
|
+
key === 'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE') {
|
|
519
523
|
// NOTE: We want to ensure that the service object is always defined
|
|
520
524
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
521
525
|
// @ts-ignore
|
|
@@ -570,6 +574,12 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
570
574
|
case 'COCO_SERVICE_FIELDS':
|
|
571
575
|
service.fields = value;
|
|
572
576
|
break;
|
|
577
|
+
case 'COCO_SERVICE_DYNAMIC_MODELS':
|
|
578
|
+
service.dynamicModels = value;
|
|
579
|
+
break;
|
|
580
|
+
case 'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE':
|
|
581
|
+
service.dynamicModelPreference = value;
|
|
582
|
+
break;
|
|
573
583
|
}
|
|
574
584
|
}
|
|
575
585
|
function parseEnvValue(key, value) {
|
|
@@ -978,7 +988,7 @@ const schema$1 = {
|
|
|
978
988
|
"$ref": "#/definitions/LLMProvider"
|
|
979
989
|
},
|
|
980
990
|
"model": {
|
|
981
|
-
"$ref": "#/definitions/
|
|
991
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
982
992
|
},
|
|
983
993
|
"baseURL": {
|
|
984
994
|
"type": "string",
|
|
@@ -1670,6 +1680,15 @@ const schema$1 = {
|
|
|
1670
1680
|
"type": "number",
|
|
1671
1681
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
1672
1682
|
"default": 3
|
|
1683
|
+
},
|
|
1684
|
+
"dynamicModels": {
|
|
1685
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
1686
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
1687
|
+
},
|
|
1688
|
+
"dynamicModelPreference": {
|
|
1689
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
1690
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1691
|
+
"default": "balanced"
|
|
1673
1692
|
}
|
|
1674
1693
|
},
|
|
1675
1694
|
"required": [
|
|
@@ -1686,6 +1705,17 @@ const schema$1 = {
|
|
|
1686
1705
|
"anthropic"
|
|
1687
1706
|
]
|
|
1688
1707
|
},
|
|
1708
|
+
"ConfiguredLLMModel": {
|
|
1709
|
+
"anyOf": [
|
|
1710
|
+
{
|
|
1711
|
+
"$ref": "#/definitions/LLMModel"
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
"type": "string",
|
|
1715
|
+
"const": "dynamic"
|
|
1716
|
+
}
|
|
1717
|
+
]
|
|
1718
|
+
},
|
|
1689
1719
|
"LLMModel": {
|
|
1690
1720
|
"anyOf": [
|
|
1691
1721
|
{
|
|
@@ -1983,6 +2013,41 @@ const schema$1 = {
|
|
|
1983
2013
|
null
|
|
1984
2014
|
]
|
|
1985
2015
|
},
|
|
2016
|
+
"DynamicModelProfile": {
|
|
2017
|
+
"type": "object",
|
|
2018
|
+
"properties": {
|
|
2019
|
+
"summarize": {
|
|
2020
|
+
"$ref": "#/definitions/LLMModel"
|
|
2021
|
+
},
|
|
2022
|
+
"commit": {
|
|
2023
|
+
"$ref": "#/definitions/LLMModel"
|
|
2024
|
+
},
|
|
2025
|
+
"changelog": {
|
|
2026
|
+
"$ref": "#/definitions/LLMModel"
|
|
2027
|
+
},
|
|
2028
|
+
"review": {
|
|
2029
|
+
"$ref": "#/definitions/LLMModel"
|
|
2030
|
+
},
|
|
2031
|
+
"recap": {
|
|
2032
|
+
"$ref": "#/definitions/LLMModel"
|
|
2033
|
+
},
|
|
2034
|
+
"repair": {
|
|
2035
|
+
"$ref": "#/definitions/LLMModel"
|
|
2036
|
+
},
|
|
2037
|
+
"largeDiff": {
|
|
2038
|
+
"$ref": "#/definitions/LLMModel"
|
|
2039
|
+
}
|
|
2040
|
+
},
|
|
2041
|
+
"additionalProperties": false
|
|
2042
|
+
},
|
|
2043
|
+
"DynamicModelPreference": {
|
|
2044
|
+
"type": "string",
|
|
2045
|
+
"enum": [
|
|
2046
|
+
"cost",
|
|
2047
|
+
"balanced",
|
|
2048
|
+
"quality"
|
|
2049
|
+
]
|
|
2050
|
+
},
|
|
1986
2051
|
"OllamaLLMService": {
|
|
1987
2052
|
"type": "object",
|
|
1988
2053
|
"additionalProperties": false,
|
|
@@ -1991,7 +2056,7 @@ const schema$1 = {
|
|
|
1991
2056
|
"$ref": "#/definitions/LLMProvider"
|
|
1992
2057
|
},
|
|
1993
2058
|
"model": {
|
|
1994
|
-
"$ref": "#/definitions/
|
|
2059
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
1995
2060
|
},
|
|
1996
2061
|
"endpoint": {
|
|
1997
2062
|
"type": "string"
|
|
@@ -2711,6 +2776,15 @@ const schema$1 = {
|
|
|
2711
2776
|
"type": "number",
|
|
2712
2777
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
2713
2778
|
"default": 3
|
|
2779
|
+
},
|
|
2780
|
+
"dynamicModels": {
|
|
2781
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
2782
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
2783
|
+
},
|
|
2784
|
+
"dynamicModelPreference": {
|
|
2785
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
2786
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
2787
|
+
"default": "balanced"
|
|
2714
2788
|
}
|
|
2715
2789
|
},
|
|
2716
2790
|
"required": [
|
|
@@ -2733,7 +2807,7 @@ const schema$1 = {
|
|
|
2733
2807
|
"$ref": "#/definitions/LLMProvider"
|
|
2734
2808
|
},
|
|
2735
2809
|
"model": {
|
|
2736
|
-
"$ref": "#/definitions/
|
|
2810
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
2737
2811
|
},
|
|
2738
2812
|
"fields": {
|
|
2739
2813
|
"type": "object",
|
|
@@ -2863,6 +2937,15 @@ const schema$1 = {
|
|
|
2863
2937
|
"type": "number",
|
|
2864
2938
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
2865
2939
|
"default": 3
|
|
2940
|
+
},
|
|
2941
|
+
"dynamicModels": {
|
|
2942
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
2943
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
2944
|
+
},
|
|
2945
|
+
"dynamicModelPreference": {
|
|
2946
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
2947
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
2948
|
+
"default": "balanced"
|
|
2866
2949
|
}
|
|
2867
2950
|
},
|
|
2868
2951
|
"required": [
|
|
@@ -6929,11 +7012,11 @@ const ChangelogResponseSchema = objectType({
|
|
|
6929
7012
|
title: stringType(),
|
|
6930
7013
|
content: stringType(),
|
|
6931
7014
|
});
|
|
6932
|
-
const command$
|
|
7015
|
+
const command$5 = 'changelog';
|
|
6933
7016
|
/**
|
|
6934
7017
|
* Command line options via yargs
|
|
6935
7018
|
*/
|
|
6936
|
-
const options$
|
|
7019
|
+
const options$5 = {
|
|
6937
7020
|
range: {
|
|
6938
7021
|
type: 'string',
|
|
6939
7022
|
alias: 'r',
|
|
@@ -6980,8 +7063,8 @@ const options$4 = {
|
|
|
6980
7063
|
description: 'Toggle interactive mode',
|
|
6981
7064
|
},
|
|
6982
7065
|
};
|
|
6983
|
-
const builder$
|
|
6984
|
-
return yargs.options(options$
|
|
7066
|
+
const builder$5 = (yargs) => {
|
|
7067
|
+
return yargs.options(options$5).usage(getCommandUsageHeader(command$5));
|
|
6985
7068
|
};
|
|
6986
7069
|
|
|
6987
7070
|
/**
|
|
@@ -7181,6 +7264,212 @@ function getLlm(provider, model, config) {
|
|
|
7181
7264
|
}
|
|
7182
7265
|
}
|
|
7183
7266
|
|
|
7267
|
+
const OPENAI_DYNAMIC_DEFAULTS = {
|
|
7268
|
+
cost: {
|
|
7269
|
+
summarize: 'gpt-4.1-nano',
|
|
7270
|
+
commit: 'gpt-4.1-mini',
|
|
7271
|
+
changelog: 'gpt-4.1-mini',
|
|
7272
|
+
review: 'gpt-4.1-mini',
|
|
7273
|
+
recap: 'gpt-4.1-nano',
|
|
7274
|
+
repair: 'gpt-4.1-mini',
|
|
7275
|
+
largeDiff: 'gpt-4.1',
|
|
7276
|
+
},
|
|
7277
|
+
balanced: {
|
|
7278
|
+
summarize: 'gpt-4.1-mini',
|
|
7279
|
+
commit: 'gpt-4.1-mini',
|
|
7280
|
+
changelog: 'gpt-4.1',
|
|
7281
|
+
review: 'gpt-4.1',
|
|
7282
|
+
recap: 'gpt-4.1-mini',
|
|
7283
|
+
repair: 'gpt-4.1',
|
|
7284
|
+
largeDiff: 'gpt-4.1',
|
|
7285
|
+
},
|
|
7286
|
+
quality: {
|
|
7287
|
+
summarize: 'gpt-4.1-mini',
|
|
7288
|
+
commit: 'gpt-4.1',
|
|
7289
|
+
changelog: 'gpt-4.1',
|
|
7290
|
+
review: 'gpt-4.1',
|
|
7291
|
+
recap: 'gpt-4.1',
|
|
7292
|
+
repair: 'gpt-4.1',
|
|
7293
|
+
largeDiff: 'gpt-4.1',
|
|
7294
|
+
},
|
|
7295
|
+
};
|
|
7296
|
+
const ANTHROPIC_DYNAMIC_DEFAULTS = {
|
|
7297
|
+
cost: {
|
|
7298
|
+
summarize: 'claude-3-5-haiku-latest',
|
|
7299
|
+
commit: 'claude-3-5-haiku-latest',
|
|
7300
|
+
changelog: 'claude-3-5-sonnet-latest',
|
|
7301
|
+
review: 'claude-3-5-sonnet-latest',
|
|
7302
|
+
recap: 'claude-3-5-haiku-latest',
|
|
7303
|
+
repair: 'claude-3-5-sonnet-latest',
|
|
7304
|
+
largeDiff: 'claude-3-5-sonnet-latest',
|
|
7305
|
+
},
|
|
7306
|
+
balanced: {
|
|
7307
|
+
summarize: 'claude-3-5-haiku-latest',
|
|
7308
|
+
commit: 'claude-3-5-sonnet-latest',
|
|
7309
|
+
changelog: 'claude-3-5-sonnet-latest',
|
|
7310
|
+
review: 'claude-3-7-sonnet-latest',
|
|
7311
|
+
recap: 'claude-3-5-sonnet-latest',
|
|
7312
|
+
repair: 'claude-3-7-sonnet-latest',
|
|
7313
|
+
largeDiff: 'claude-3-7-sonnet-latest',
|
|
7314
|
+
},
|
|
7315
|
+
quality: {
|
|
7316
|
+
summarize: 'claude-3-5-sonnet-latest',
|
|
7317
|
+
commit: 'claude-3-7-sonnet-latest',
|
|
7318
|
+
changelog: 'claude-3-7-sonnet-latest',
|
|
7319
|
+
review: 'claude-sonnet-4-0',
|
|
7320
|
+
recap: 'claude-3-7-sonnet-latest',
|
|
7321
|
+
repair: 'claude-sonnet-4-0',
|
|
7322
|
+
largeDiff: 'claude-sonnet-4-0',
|
|
7323
|
+
},
|
|
7324
|
+
};
|
|
7325
|
+
const OLLAMA_DYNAMIC_DEFAULTS = {
|
|
7326
|
+
cost: {
|
|
7327
|
+
summarize: 'llama3.2:3b',
|
|
7328
|
+
commit: 'llama3.1:8b',
|
|
7329
|
+
changelog: 'llama3.1:8b',
|
|
7330
|
+
review: 'qwen2.5-coder:7b',
|
|
7331
|
+
recap: 'llama3.2:3b',
|
|
7332
|
+
repair: 'qwen2.5-coder:7b',
|
|
7333
|
+
largeDiff: 'qwen2.5-coder:14b',
|
|
7334
|
+
},
|
|
7335
|
+
balanced: {
|
|
7336
|
+
summarize: 'llama3.1:8b',
|
|
7337
|
+
commit: 'qwen2.5-coder:14b',
|
|
7338
|
+
changelog: 'qwen2.5-coder:14b',
|
|
7339
|
+
review: 'qwen2.5-coder:32b',
|
|
7340
|
+
recap: 'llama3.1:8b',
|
|
7341
|
+
repair: 'qwen2.5-coder:32b',
|
|
7342
|
+
largeDiff: 'qwen2.5-coder:32b',
|
|
7343
|
+
},
|
|
7344
|
+
quality: {
|
|
7345
|
+
summarize: 'qwen2.5-coder:14b',
|
|
7346
|
+
commit: 'qwen2.5-coder:32b',
|
|
7347
|
+
changelog: 'qwen2.5-coder:32b',
|
|
7348
|
+
review: 'qwen2.5-coder:32b',
|
|
7349
|
+
recap: 'qwen2.5-coder:14b',
|
|
7350
|
+
repair: 'qwen2.5-coder:32b',
|
|
7351
|
+
largeDiff: 'qwen2.5-coder:32b',
|
|
7352
|
+
},
|
|
7353
|
+
};
|
|
7354
|
+
const DYNAMIC_DEFAULTS = {
|
|
7355
|
+
openai: OPENAI_DYNAMIC_DEFAULTS,
|
|
7356
|
+
anthropic: ANTHROPIC_DYNAMIC_DEFAULTS,
|
|
7357
|
+
ollama: OLLAMA_DYNAMIC_DEFAULTS,
|
|
7358
|
+
};
|
|
7359
|
+
const DYNAMIC_MODEL_TASKS = [
|
|
7360
|
+
'summarize',
|
|
7361
|
+
'commit',
|
|
7362
|
+
'changelog',
|
|
7363
|
+
'review',
|
|
7364
|
+
'recap',
|
|
7365
|
+
'repair',
|
|
7366
|
+
'largeDiff',
|
|
7367
|
+
];
|
|
7368
|
+
function validateDynamicModelProfile(service) {
|
|
7369
|
+
const dynamicModels = service.dynamicModels;
|
|
7370
|
+
if (!dynamicModels)
|
|
7371
|
+
return;
|
|
7372
|
+
const unknownTasks = Object.keys(dynamicModels).filter((task) => !DYNAMIC_MODEL_TASKS.includes(task));
|
|
7373
|
+
if (unknownTasks.length > 0) {
|
|
7374
|
+
throw new LangChainConfigurationError(`Unknown dynamic model task(s): ${unknownTasks.join(', ')}. Supported tasks: ${DYNAMIC_MODEL_TASKS.join(', ')}`, { unknownTasks, supportedTasks: DYNAMIC_MODEL_TASKS });
|
|
7375
|
+
}
|
|
7376
|
+
Object.entries(dynamicModels).forEach(([task, model]) => {
|
|
7377
|
+
if (typeof model !== 'string' || model.trim() === '' || model === 'dynamic') {
|
|
7378
|
+
throw new LangChainConfigurationError(`Dynamic model override for '${task}' must be a concrete model name`, { task, model });
|
|
7379
|
+
}
|
|
7380
|
+
});
|
|
7381
|
+
}
|
|
7382
|
+
function resolveDynamicModel(config, task) {
|
|
7383
|
+
const service = config.service;
|
|
7384
|
+
validateDynamicModelProfile(service);
|
|
7385
|
+
if (service.model !== 'dynamic') {
|
|
7386
|
+
return service.model;
|
|
7387
|
+
}
|
|
7388
|
+
const preference = service.dynamicModelPreference || 'balanced';
|
|
7389
|
+
const providerDefaults = DYNAMIC_DEFAULTS[service.provider];
|
|
7390
|
+
const defaultModel = providerDefaults[preference]?.[task];
|
|
7391
|
+
return service.dynamicModels?.[task] || defaultModel;
|
|
7392
|
+
}
|
|
7393
|
+
function resolveDynamicService(config, task) {
|
|
7394
|
+
const model = resolveDynamicModel(config, task);
|
|
7395
|
+
return {
|
|
7396
|
+
...config.service,
|
|
7397
|
+
model,
|
|
7398
|
+
};
|
|
7399
|
+
}
|
|
7400
|
+
|
|
7401
|
+
const telemetryByCommand = new Map();
|
|
7402
|
+
function estimatePromptTokens(tokenizer, renderedPrompt) {
|
|
7403
|
+
if (!tokenizer)
|
|
7404
|
+
return undefined;
|
|
7405
|
+
try {
|
|
7406
|
+
return tokenizer(renderedPrompt);
|
|
7407
|
+
}
|
|
7408
|
+
catch {
|
|
7409
|
+
return undefined;
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
function logLlmCall(logger, metadata) {
|
|
7413
|
+
if (!logger)
|
|
7414
|
+
return;
|
|
7415
|
+
recordLlmTelemetry(metadata);
|
|
7416
|
+
const fields = [
|
|
7417
|
+
`task=${metadata.task}`,
|
|
7418
|
+
metadata.command ? `command=${metadata.command}` : undefined,
|
|
7419
|
+
metadata.provider ? `provider=${metadata.provider}` : undefined,
|
|
7420
|
+
metadata.model ? `model=${metadata.model}` : undefined,
|
|
7421
|
+
metadata.retryAttempt ? `retryAttempt=${metadata.retryAttempt}` : undefined,
|
|
7422
|
+
metadata.promptTokens !== undefined ? `promptTokens=${metadata.promptTokens}` : undefined,
|
|
7423
|
+
metadata.elapsedMs !== undefined ? `elapsedMs=${metadata.elapsedMs}` : undefined,
|
|
7424
|
+
metadata.inputDocuments !== undefined ? `inputDocuments=${metadata.inputDocuments}` : undefined,
|
|
7425
|
+
metadata.inputChunks !== undefined ? `inputChunks=${metadata.inputChunks}` : undefined,
|
|
7426
|
+
metadata.parserType ? `parser=${metadata.parserType}` : undefined,
|
|
7427
|
+
metadata.variableKeys?.length ? `variableKeys=${metadata.variableKeys.join(',')}` : undefined,
|
|
7428
|
+
].filter(Boolean);
|
|
7429
|
+
logger.verbose(`[llm] ${fields.join(' ')}`, { color: 'cyan' });
|
|
7430
|
+
}
|
|
7431
|
+
function recordLlmTelemetry(metadata) {
|
|
7432
|
+
const command = metadata.command || 'unknown';
|
|
7433
|
+
const current = telemetryByCommand.get(command) || {
|
|
7434
|
+
calls: 0,
|
|
7435
|
+
promptTokens: 0,
|
|
7436
|
+
elapsedMs: 0,
|
|
7437
|
+
inputDocuments: 0,
|
|
7438
|
+
inputChunks: 0,
|
|
7439
|
+
tasks: new Set(),
|
|
7440
|
+
models: new Set(),
|
|
7441
|
+
};
|
|
7442
|
+
current.calls += 1;
|
|
7443
|
+
current.promptTokens += metadata.promptTokens || 0;
|
|
7444
|
+
current.elapsedMs += metadata.elapsedMs || 0;
|
|
7445
|
+
current.inputDocuments += metadata.inputDocuments || 0;
|
|
7446
|
+
current.inputChunks += metadata.inputChunks || 0;
|
|
7447
|
+
current.tasks.add(metadata.task);
|
|
7448
|
+
if (metadata.model) {
|
|
7449
|
+
current.models.add(metadata.model);
|
|
7450
|
+
}
|
|
7451
|
+
telemetryByCommand.set(command, current);
|
|
7452
|
+
}
|
|
7453
|
+
function logLlmTelemetrySummary(logger, command) {
|
|
7454
|
+
if (!logger)
|
|
7455
|
+
return;
|
|
7456
|
+
const summary = telemetryByCommand.get(command);
|
|
7457
|
+
if (!summary || summary.calls === 0)
|
|
7458
|
+
return;
|
|
7459
|
+
const fields = [
|
|
7460
|
+
`command=${command}`,
|
|
7461
|
+
`calls=${summary.calls}`,
|
|
7462
|
+
summary.promptTokens > 0 ? `promptTokens=${summary.promptTokens}` : undefined,
|
|
7463
|
+
summary.elapsedMs > 0 ? `elapsedMs=${summary.elapsedMs}` : undefined,
|
|
7464
|
+
summary.inputDocuments > 0 ? `inputDocuments=${summary.inputDocuments}` : undefined,
|
|
7465
|
+
summary.inputChunks > 0 ? `inputChunks=${summary.inputChunks}` : undefined,
|
|
7466
|
+
summary.tasks.size > 0 ? `tasks=${[...summary.tasks].join(',')}` : undefined,
|
|
7467
|
+
summary.models.size > 0 ? `models=${[...summary.models].join(',')}` : undefined,
|
|
7468
|
+
].filter(Boolean);
|
|
7469
|
+
logger.verbose(`[llm:summary] ${fields.join(' ')}`, { color: 'cyan' });
|
|
7470
|
+
telemetryByCommand.delete(command);
|
|
7471
|
+
}
|
|
7472
|
+
|
|
7184
7473
|
/**
|
|
7185
7474
|
* Creates a PromptTemplate from a template string or returns a fallback template.
|
|
7186
7475
|
*
|
|
@@ -7370,7 +7659,7 @@ function extractLlmInfo(llm) {
|
|
|
7370
7659
|
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
7371
7660
|
* @throws LangChainNetworkError if a network/connection error occurs
|
|
7372
7661
|
*/
|
|
7373
|
-
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, }) => {
|
|
7662
|
+
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, logger, tokenizer, metadata, }) => {
|
|
7374
7663
|
validateRequired(llm, 'llm', 'executeChain');
|
|
7375
7664
|
validateRequired(prompt, 'prompt', 'executeChain');
|
|
7376
7665
|
validateRequired(variables, 'variables', 'executeChain');
|
|
@@ -7388,8 +7677,21 @@ const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint
|
|
|
7388
7677
|
const effectiveProvider = provider || llmInfo.provider;
|
|
7389
7678
|
const effectiveEndpoint = endpoint || llmInfo.endpoint;
|
|
7390
7679
|
try {
|
|
7680
|
+
const renderedPrompt = await prompt.format(variables);
|
|
7681
|
+
const promptTokens = estimatePromptTokens(tokenizer, renderedPrompt);
|
|
7391
7682
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
7683
|
+
const startedAt = Date.now();
|
|
7392
7684
|
const result = (await chain.invoke(variables));
|
|
7685
|
+
const elapsedMs = Date.now() - startedAt;
|
|
7686
|
+
logLlmCall(logger, {
|
|
7687
|
+
task: metadata?.task || 'chain',
|
|
7688
|
+
provider: effectiveProvider,
|
|
7689
|
+
parserType: parser.constructor.name,
|
|
7690
|
+
variableKeys: Object.keys(variables),
|
|
7691
|
+
promptTokens,
|
|
7692
|
+
elapsedMs,
|
|
7693
|
+
...metadata,
|
|
7694
|
+
});
|
|
7393
7695
|
if (result === null || result === undefined) {
|
|
7394
7696
|
throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
|
|
7395
7697
|
}
|
|
@@ -8218,13 +8520,26 @@ function getPathFromFilePath(filePath) {
|
|
|
8218
8520
|
return filePath.split('/').slice(0, -1).join('/');
|
|
8219
8521
|
}
|
|
8220
8522
|
|
|
8221
|
-
async function summarize(documents, { chain, textSplitter, options }) {
|
|
8523
|
+
async function summarize(documents, { chain, textSplitter, options, logger, tokenizer, metadata }) {
|
|
8222
8524
|
const { returnIntermediateSteps = false } = options || {};
|
|
8223
8525
|
const docs = await textSplitter.splitDocuments(documents.map((doc) => new Document(doc)));
|
|
8526
|
+
const promptTokens = tokenizer
|
|
8527
|
+
? docs.reduce((sum, doc) => sum + tokenizer(doc.pageContent), 0)
|
|
8528
|
+
: undefined;
|
|
8529
|
+
const startedAt = Date.now();
|
|
8224
8530
|
const res = await chain.invoke({
|
|
8225
8531
|
input_documents: docs,
|
|
8226
8532
|
returnIntermediateSteps,
|
|
8227
8533
|
});
|
|
8534
|
+
const elapsedMs = Date.now() - startedAt;
|
|
8535
|
+
logLlmCall(logger, {
|
|
8536
|
+
task: 'summarize',
|
|
8537
|
+
promptTokens,
|
|
8538
|
+
elapsedMs,
|
|
8539
|
+
inputDocuments: documents.length,
|
|
8540
|
+
inputChunks: docs.length,
|
|
8541
|
+
...metadata,
|
|
8542
|
+
});
|
|
8228
8543
|
if (res.error)
|
|
8229
8544
|
throw new Error(res.error);
|
|
8230
8545
|
return res.text && res.text.trim();
|
|
@@ -8233,7 +8548,7 @@ async function summarize(documents, { chain, textSplitter, options }) {
|
|
|
8233
8548
|
/**
|
|
8234
8549
|
* Summarize a single file diff that exceeds the token threshold.
|
|
8235
8550
|
*/
|
|
8236
|
-
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer }) {
|
|
8551
|
+
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, }) {
|
|
8237
8552
|
try {
|
|
8238
8553
|
const fileSummary = await summarize([
|
|
8239
8554
|
{
|
|
@@ -8246,6 +8561,12 @@ async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer }) {
|
|
|
8246
8561
|
], {
|
|
8247
8562
|
chain,
|
|
8248
8563
|
textSplitter,
|
|
8564
|
+
tokenizer,
|
|
8565
|
+
logger,
|
|
8566
|
+
metadata: {
|
|
8567
|
+
...metadata,
|
|
8568
|
+
task: 'summarize-large-file',
|
|
8569
|
+
},
|
|
8249
8570
|
options: {
|
|
8250
8571
|
returnIntermediateSteps: false,
|
|
8251
8572
|
},
|
|
@@ -8285,7 +8606,7 @@ async function processInWaves$1(items, processor, maxConcurrent) {
|
|
|
8285
8606
|
* @returns Array of file diffs with large files summarized
|
|
8286
8607
|
*/
|
|
8287
8608
|
async function summarizeLargeFiles(diffs, options) {
|
|
8288
|
-
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter } = options;
|
|
8609
|
+
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter, metadata } = options;
|
|
8289
8610
|
// Identify files that need summarization
|
|
8290
8611
|
const filesToSummarize = [];
|
|
8291
8612
|
const results = [...diffs];
|
|
@@ -8299,7 +8620,7 @@ async function summarizeLargeFiles(diffs, options) {
|
|
|
8299
8620
|
}
|
|
8300
8621
|
logger.verbose(`Pre-summarizing ${filesToSummarize.length} large file(s)...`, { color: 'blue' });
|
|
8301
8622
|
// Process large files in waves
|
|
8302
|
-
const summarizedFiles = await processInWaves$1(filesToSummarize, async ({ diff }) => summarizeFileDiff(diff, { chain, textSplitter, tokenizer }), maxConcurrent);
|
|
8623
|
+
const summarizedFiles = await processInWaves$1(filesToSummarize, async ({ diff }) => summarizeFileDiff(diff, { chain, textSplitter, tokenizer, logger, metadata }), maxConcurrent);
|
|
8303
8624
|
// Update results with summarized files
|
|
8304
8625
|
summarizedFiles.forEach((summarizedDiff, i) => {
|
|
8305
8626
|
const originalIndex = filesToSummarize[i].index;
|
|
@@ -8362,7 +8683,7 @@ function createDirectoryDiffs(node) {
|
|
|
8362
8683
|
/**
|
|
8363
8684
|
* Summarize a directory diff asynchronously.
|
|
8364
8685
|
*/
|
|
8365
|
-
async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenizer }) {
|
|
8686
|
+
async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenizer, logger, metadata }) {
|
|
8366
8687
|
try {
|
|
8367
8688
|
const directorySummary = await summarize(directory.diffs.map((diff) => ({
|
|
8368
8689
|
pageContent: diff.diff,
|
|
@@ -8373,6 +8694,12 @@ async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenize
|
|
|
8373
8694
|
})), {
|
|
8374
8695
|
chain,
|
|
8375
8696
|
textSplitter,
|
|
8697
|
+
tokenizer,
|
|
8698
|
+
logger,
|
|
8699
|
+
metadata: {
|
|
8700
|
+
...metadata,
|
|
8701
|
+
task: 'summarize-directory-diff',
|
|
8702
|
+
},
|
|
8376
8703
|
options: {
|
|
8377
8704
|
returnIntermediateSteps: true,
|
|
8378
8705
|
},
|
|
@@ -8416,7 +8743,7 @@ const defaultOutputCallback = (group) => {
|
|
|
8416
8743
|
* while maintaining predictable behavior.
|
|
8417
8744
|
*/
|
|
8418
8745
|
async function summarizeInWaves(directories, options) {
|
|
8419
|
-
const { totalTokenCount: initialTotal, maxTokens, minTokensForSummary, maxConcurrent, logger, chain, textSplitter, tokenizer, } = options;
|
|
8746
|
+
const { totalTokenCount: initialTotal, maxTokens, minTokensForSummary, maxConcurrent, logger, chain, textSplitter, tokenizer, metadata, } = options;
|
|
8420
8747
|
let totalTokenCount = initialTotal;
|
|
8421
8748
|
const results = [...directories];
|
|
8422
8749
|
// Create sorted indices by token count (descending) for prioritized processing
|
|
@@ -8448,7 +8775,7 @@ async function summarizeInWaves(directories, options) {
|
|
|
8448
8775
|
}
|
|
8449
8776
|
logger.verbose(`\nProcessing wave of ${wave.length} directories...`, { color: 'blue' });
|
|
8450
8777
|
// Process wave in parallel
|
|
8451
|
-
const waveResults = await Promise.all(wave.map((idx) => summarizeDirectoryDiff(results[idx], { chain, textSplitter, tokenizer })));
|
|
8778
|
+
const waveResults = await Promise.all(wave.map((idx) => summarizeDirectoryDiff(results[idx], { chain, textSplitter, tokenizer, logger, metadata })));
|
|
8452
8779
|
// Update results and recalculate total
|
|
8453
8780
|
waveResults.forEach((result, i) => {
|
|
8454
8781
|
const idx = wave[i];
|
|
@@ -8485,7 +8812,7 @@ async function summarizeInWaves(directories, options) {
|
|
|
8485
8812
|
* - Efficient parallel processing with predictable behavior
|
|
8486
8813
|
* - Early exit when under token budget
|
|
8487
8814
|
*/
|
|
8488
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
8815
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8489
8816
|
// Calculate maxFileTokens as 25% of maxTokens if not specified
|
|
8490
8817
|
const effectiveMaxFileTokens = maxFileTokens ?? Math.floor(maxTokens * 0.25);
|
|
8491
8818
|
// PHASE 1: Directory grouping & assessment
|
|
@@ -8513,6 +8840,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 204
|
|
|
8513
8840
|
logger,
|
|
8514
8841
|
chain,
|
|
8515
8842
|
textSplitter,
|
|
8843
|
+
metadata,
|
|
8516
8844
|
});
|
|
8517
8845
|
logger.stopSpinner('Files pre-processed').stopTimer();
|
|
8518
8846
|
directoryDiffs = createDirectoryDiffs(preprocessedNode);
|
|
@@ -8536,6 +8864,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 204
|
|
|
8536
8864
|
chain,
|
|
8537
8865
|
textSplitter,
|
|
8538
8866
|
tokenizer,
|
|
8867
|
+
metadata,
|
|
8539
8868
|
});
|
|
8540
8869
|
logger.stopSpinner(`Diffs Consolidated`).stopTimer();
|
|
8541
8870
|
return summarizedDiffs.map(handleOutput).join('');
|
|
@@ -10477,7 +10806,7 @@ function isObject(subject) {
|
|
|
10477
10806
|
}
|
|
10478
10807
|
|
|
10479
10808
|
|
|
10480
|
-
function toArray(sequence) {
|
|
10809
|
+
function toArray$1(sequence) {
|
|
10481
10810
|
if (Array.isArray(sequence)) return sequence;
|
|
10482
10811
|
else if (isNothing(sequence)) return [];
|
|
10483
10812
|
|
|
@@ -10519,7 +10848,7 @@ function isNegativeZero(number) {
|
|
|
10519
10848
|
|
|
10520
10849
|
var isNothing_1 = isNothing;
|
|
10521
10850
|
var isObject_1 = isObject;
|
|
10522
|
-
var toArray_1 = toArray;
|
|
10851
|
+
var toArray_1 = toArray$1;
|
|
10523
10852
|
var repeat_1 = repeat;
|
|
10524
10853
|
var isNegativeZero_1 = isNegativeZero;
|
|
10525
10854
|
var extend_1 = extend;
|
|
@@ -11490,7 +11819,7 @@ for (var i = 0; i < 256; i++) {
|
|
|
11490
11819
|
simpleEscapeMap[i] = simpleEscapeSequence(i);
|
|
11491
11820
|
}
|
|
11492
11821
|
|
|
11493
|
-
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, }, }) {
|
|
11822
|
+
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, metadata, }, }) {
|
|
11494
11823
|
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
11495
11824
|
const summarizationChain = loadSummarizationChain(model, {
|
|
11496
11825
|
type: 'map_reduce',
|
|
@@ -11518,6 +11847,7 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
11518
11847
|
textSplitter,
|
|
11519
11848
|
chain: summarizationChain,
|
|
11520
11849
|
logger,
|
|
11850
|
+
metadata,
|
|
11521
11851
|
});
|
|
11522
11852
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
11523
11853
|
return summary;
|
|
@@ -11590,11 +11920,14 @@ async function processInWaves(items, processor, maxConcurrent = 6) {
|
|
|
11590
11920
|
}
|
|
11591
11921
|
return results;
|
|
11592
11922
|
}
|
|
11593
|
-
const handler$
|
|
11923
|
+
const handler$5 = async (argv, logger) => {
|
|
11594
11924
|
const config = loadConfig(argv);
|
|
11595
11925
|
const git = getRepo();
|
|
11596
11926
|
const key = getApiKeyForModel(config);
|
|
11597
|
-
const { provider
|
|
11927
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
11928
|
+
const changelogService = resolveDynamicService(config, 'changelog');
|
|
11929
|
+
const summaryService = resolveDynamicService(config, argv.withDiff || argv.onlyDiff ? 'largeDiff' : 'summarize');
|
|
11930
|
+
const model = changelogService.model;
|
|
11598
11931
|
const exclusiveOptions = [
|
|
11599
11932
|
argv.branch ? '--branch' : null,
|
|
11600
11933
|
argv.tag ? '--tag' : null,
|
|
@@ -11608,7 +11941,8 @@ const handler$4 = async (argv, logger) => {
|
|
|
11608
11941
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
11609
11942
|
process.exit(1);
|
|
11610
11943
|
}
|
|
11611
|
-
const llm = getLlm(provider, model, config);
|
|
11944
|
+
const llm = getLlm(provider, model, { ...config, service: changelogService });
|
|
11945
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
11612
11946
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
11613
11947
|
const INTERACTIVE = isInteractive(config);
|
|
11614
11948
|
if (INTERACTIVE) {
|
|
@@ -11678,12 +12012,17 @@ const handler$4 = async (argv, logger) => {
|
|
|
11678
12012
|
options: {
|
|
11679
12013
|
tokenizer,
|
|
11680
12014
|
git,
|
|
11681
|
-
llm,
|
|
12015
|
+
llm: summaryLlm,
|
|
11682
12016
|
logger,
|
|
11683
12017
|
maxTokens: config.service.tokenLimit,
|
|
11684
12018
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
11685
12019
|
maxFileTokens: config.service.maxFileTokens,
|
|
11686
12020
|
maxConcurrent: config.service.maxConcurrent,
|
|
12021
|
+
metadata: {
|
|
12022
|
+
command: 'changelog',
|
|
12023
|
+
provider,
|
|
12024
|
+
model: String(summaryService.model),
|
|
12025
|
+
},
|
|
11687
12026
|
},
|
|
11688
12027
|
})
|
|
11689
12028
|
: undefined,
|
|
@@ -11704,12 +12043,17 @@ const handler$4 = async (argv, logger) => {
|
|
|
11704
12043
|
options: {
|
|
11705
12044
|
tokenizer,
|
|
11706
12045
|
git,
|
|
11707
|
-
llm,
|
|
12046
|
+
llm: summaryLlm,
|
|
11708
12047
|
logger,
|
|
11709
12048
|
maxTokens: config.service.tokenLimit,
|
|
11710
12049
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
11711
12050
|
maxFileTokens: config.service.maxFileTokens,
|
|
11712
12051
|
maxConcurrent: config.service.maxConcurrent,
|
|
12052
|
+
metadata: {
|
|
12053
|
+
command: 'changelog',
|
|
12054
|
+
provider,
|
|
12055
|
+
model: String(summaryService.model),
|
|
12056
|
+
},
|
|
11713
12057
|
},
|
|
11714
12058
|
});
|
|
11715
12059
|
return `## Diff for ${data.branch}\n\n${diffSummary}`;
|
|
@@ -11776,6 +12120,14 @@ const handler$4 = async (argv, logger) => {
|
|
|
11776
12120
|
prompt,
|
|
11777
12121
|
variables: budgetedPrompt.variables,
|
|
11778
12122
|
parser,
|
|
12123
|
+
logger,
|
|
12124
|
+
tokenizer,
|
|
12125
|
+
metadata: {
|
|
12126
|
+
task: argv.withDiff ? 'changelog-with-diff' : argv.onlyDiff ? 'changelog-only-diff' : 'changelog',
|
|
12127
|
+
command: 'changelog',
|
|
12128
|
+
provider,
|
|
12129
|
+
model: String(model),
|
|
12130
|
+
},
|
|
11779
12131
|
});
|
|
11780
12132
|
const branchName = await getCurrentBranchName({ git });
|
|
11781
12133
|
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
@@ -11799,14 +12151,15 @@ const handler$4 = async (argv, logger) => {
|
|
|
11799
12151
|
},
|
|
11800
12152
|
mode: MODE,
|
|
11801
12153
|
});
|
|
12154
|
+
logLlmTelemetrySummary(logger, 'changelog');
|
|
11802
12155
|
};
|
|
11803
12156
|
|
|
11804
12157
|
var changelog = {
|
|
11805
|
-
command: command$
|
|
12158
|
+
command: command$5,
|
|
11806
12159
|
desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
|
|
11807
|
-
builder: builder$
|
|
11808
|
-
handler: commandExecutor(handler$
|
|
11809
|
-
options: options$
|
|
12160
|
+
builder: builder$5,
|
|
12161
|
+
handler: commandExecutor(handler$5),
|
|
12162
|
+
options: options$5,
|
|
11810
12163
|
};
|
|
11811
12164
|
|
|
11812
12165
|
const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?:/;
|
|
@@ -11823,11 +12176,11 @@ const ConventionalCommitMessageResponseSchema = objectType({
|
|
|
11823
12176
|
body: stringType().describe("Body of the commit message")
|
|
11824
12177
|
// .max(280, "Body must be 280 characters or less"),
|
|
11825
12178
|
}).describe("Object with Conventional Commit message 'title' and 'body' adhering to Conventional Commits specification");
|
|
11826
|
-
const command$
|
|
12179
|
+
const command$4 = 'commit';
|
|
11827
12180
|
/**
|
|
11828
12181
|
* Command line options via yargs
|
|
11829
12182
|
*/
|
|
11830
|
-
const options$
|
|
12183
|
+
const options$4 = {
|
|
11831
12184
|
i: {
|
|
11832
12185
|
alias: 'interactive',
|
|
11833
12186
|
description: 'Toggle interactive mode',
|
|
@@ -11883,9 +12236,24 @@ const options$3 = {
|
|
|
11883
12236
|
default: false,
|
|
11884
12237
|
alias: 'n',
|
|
11885
12238
|
},
|
|
12239
|
+
split: {
|
|
12240
|
+
description: 'Group staged changes into multiple related commit proposals',
|
|
12241
|
+
type: 'boolean',
|
|
12242
|
+
default: false,
|
|
12243
|
+
},
|
|
12244
|
+
plan: {
|
|
12245
|
+
description: 'Only print a commit split plan without changing git state',
|
|
12246
|
+
type: 'boolean',
|
|
12247
|
+
default: false,
|
|
12248
|
+
},
|
|
12249
|
+
apply: {
|
|
12250
|
+
description: 'Apply a generated file-level or hunk-level commit split plan and create commits',
|
|
12251
|
+
type: 'boolean',
|
|
12252
|
+
default: false,
|
|
12253
|
+
},
|
|
11886
12254
|
};
|
|
11887
|
-
const builder$
|
|
11888
|
-
return yargs.options(options$
|
|
12255
|
+
const builder$4 = (yargs) => {
|
|
12256
|
+
return yargs.options(options$4).usage(getCommandUsageHeader(command$4));
|
|
11889
12257
|
};
|
|
11890
12258
|
|
|
11891
12259
|
/**
|
|
@@ -11899,15 +12267,24 @@ const builder$3 = (yargs) => {
|
|
|
11899
12267
|
* @returns Parsed result matching the schema type
|
|
11900
12268
|
*/
|
|
11901
12269
|
async function executeChainWithSchema(schema, llm, prompt, variables, options = {}) {
|
|
11902
|
-
const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, ...parserOptions } = options;
|
|
12270
|
+
const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, logger, tokenizer, metadata, ...parserOptions } = options;
|
|
11903
12271
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11904
12272
|
const parser = createSchemaParser(schema, llm, parserOptions);
|
|
12273
|
+
let attempt = 0;
|
|
11905
12274
|
const operation = async () => {
|
|
12275
|
+
attempt++;
|
|
11906
12276
|
const result = await executeChain({
|
|
11907
12277
|
llm,
|
|
11908
12278
|
prompt,
|
|
11909
12279
|
variables,
|
|
11910
12280
|
parser,
|
|
12281
|
+
logger,
|
|
12282
|
+
tokenizer,
|
|
12283
|
+
metadata: {
|
|
12284
|
+
task: 'schema-chain',
|
|
12285
|
+
...metadata,
|
|
12286
|
+
retryAttempt: attempt,
|
|
12287
|
+
},
|
|
11911
12288
|
});
|
|
11912
12289
|
return result;
|
|
11913
12290
|
};
|
|
@@ -11924,6 +12301,12 @@ async function executeChainWithSchema(schema, llm, prompt, variables, options =
|
|
|
11924
12301
|
prompt,
|
|
11925
12302
|
variables,
|
|
11926
12303
|
parser: new StringOutputParser(),
|
|
12304
|
+
logger,
|
|
12305
|
+
tokenizer,
|
|
12306
|
+
metadata: {
|
|
12307
|
+
task: 'schema-chain-fallback',
|
|
12308
|
+
...metadata,
|
|
12309
|
+
},
|
|
11927
12310
|
});
|
|
11928
12311
|
const fallbackText = typeof fallbackResult === 'string' ? fallbackResult : String(fallbackResult);
|
|
11929
12312
|
return fallbackParser(fallbackText);
|
|
@@ -12386,17 +12769,345 @@ async function noResult$2({ git, logger }) {
|
|
|
12386
12769
|
}
|
|
12387
12770
|
}
|
|
12388
12771
|
|
|
12389
|
-
const
|
|
12772
|
+
const CommitSplitPlanSchema = objectType({
|
|
12773
|
+
groups: arrayType(objectType({
|
|
12774
|
+
title: stringType().min(1),
|
|
12775
|
+
body: stringType().optional(),
|
|
12776
|
+
rationale: stringType().optional(),
|
|
12777
|
+
files: arrayType(stringType()),
|
|
12778
|
+
hunks: arrayType(stringType()),
|
|
12779
|
+
})
|
|
12780
|
+
.refine((group) => group.files.length > 0 || group.hunks.length > 0, {
|
|
12781
|
+
message: 'Each group must include at least one file or hunk',
|
|
12782
|
+
}))
|
|
12783
|
+
.min(1),
|
|
12784
|
+
});
|
|
12785
|
+
const COMMIT_SPLIT_PROMPT = PromptTemplate.fromTemplate(`You are helping split staged git changes into a small sequence of coherent commits.
|
|
12786
|
+
|
|
12787
|
+
Return ONLY valid JSON matching this schema:
|
|
12788
|
+
{{
|
|
12789
|
+
"groups": [
|
|
12790
|
+
{{
|
|
12791
|
+
"title": "conventional commit style title",
|
|
12792
|
+
"body": "commit body",
|
|
12793
|
+
"rationale": "why these files belong together",
|
|
12794
|
+
"files": ["relative/path.ts"],
|
|
12795
|
+
"hunks": ["relative/path.ts::hunk-1"]
|
|
12796
|
+
}}
|
|
12797
|
+
]
|
|
12798
|
+
}}
|
|
12799
|
+
|
|
12800
|
+
Rules:
|
|
12801
|
+
- Use each staged file exactly once.
|
|
12802
|
+
- If a file has hunk IDs and contains unrelated changes, assign every hunk ID exactly once instead of assigning the whole file.
|
|
12803
|
+
- Do not list the same file in "files" when assigning that file through "hunks".
|
|
12804
|
+
- Only use file paths listed in the staged file inventory.
|
|
12805
|
+
- Only use hunk IDs listed in the staged hunk inventory.
|
|
12806
|
+
- Prefer 2-5 commits unless the changes are truly all one topic.
|
|
12807
|
+
- Keep commit titles concise and understandable.
|
|
12808
|
+
- Do not invent files.
|
|
12809
|
+
|
|
12810
|
+
Staged file inventory:
|
|
12811
|
+
{file_inventory}
|
|
12812
|
+
|
|
12813
|
+
Staged hunk inventory:
|
|
12814
|
+
{hunk_inventory}
|
|
12815
|
+
|
|
12816
|
+
Condensed staged diff:
|
|
12817
|
+
{summary}
|
|
12818
|
+
|
|
12819
|
+
Additional context:
|
|
12820
|
+
{additional_context}`);
|
|
12821
|
+
function isCommitSplitCommand(argv) {
|
|
12822
|
+
return Boolean(argv.split || argv.plan || argv.apply || argv._.includes('split'));
|
|
12823
|
+
}
|
|
12824
|
+
function formatCommitSplitPlan(plan) {
|
|
12825
|
+
return plan.groups
|
|
12826
|
+
.map((group, index) => {
|
|
12827
|
+
const body = group.body ? `\n\n${group.body}` : '';
|
|
12828
|
+
const rationale = group.rationale ? `\n\nRationale: ${group.rationale}` : '';
|
|
12829
|
+
const files = (group.files || []).map((file) => `- ${file}`).join('\n');
|
|
12830
|
+
const hunks = (group.hunks || []).map((hunk) => `- ${hunk}`).join('\n');
|
|
12831
|
+
const sections = [
|
|
12832
|
+
files ? `Files:\n${files}` : undefined,
|
|
12833
|
+
hunks ? `Hunks:\n${hunks}` : undefined,
|
|
12834
|
+
].filter(Boolean);
|
|
12835
|
+
return `## ${index + 1}. ${group.title}${body}${rationale}\n\n${sections.join('\n\n')}`;
|
|
12836
|
+
})
|
|
12837
|
+
.join('\n\n---\n\n');
|
|
12838
|
+
}
|
|
12839
|
+
function getStagedFileSet(changes) {
|
|
12840
|
+
return new Set(changes.map((change) => change.filePath));
|
|
12841
|
+
}
|
|
12842
|
+
function getGroupFiles(group) {
|
|
12843
|
+
return group.files || [];
|
|
12844
|
+
}
|
|
12845
|
+
function getGroupHunks(group) {
|
|
12846
|
+
return group.hunks || [];
|
|
12847
|
+
}
|
|
12848
|
+
function hunkHeader(hunk) {
|
|
12849
|
+
return `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
|
|
12850
|
+
}
|
|
12851
|
+
function hunkPreview(hunk) {
|
|
12852
|
+
return hunk.lines
|
|
12853
|
+
.filter((line) => line.startsWith('+') || line.startsWith('-'))
|
|
12854
|
+
.slice(0, 6)
|
|
12855
|
+
.join('\n');
|
|
12856
|
+
}
|
|
12857
|
+
async function collectHunkInventory(staged, git) {
|
|
12858
|
+
const hunks = [];
|
|
12859
|
+
const byId = new Map();
|
|
12860
|
+
const byFile = new Map();
|
|
12861
|
+
for (const change of staged) {
|
|
12862
|
+
if (change.status !== 'modified') {
|
|
12863
|
+
continue;
|
|
12864
|
+
}
|
|
12865
|
+
const diff = await git.diff(['--staged', '--', change.filePath]);
|
|
12866
|
+
const [patch] = parsePatch(diff);
|
|
12867
|
+
if (!patch || patch.hunks.length === 0) {
|
|
12868
|
+
continue;
|
|
12869
|
+
}
|
|
12870
|
+
patch.hunks.forEach((hunk, index) => {
|
|
12871
|
+
const stagedHunk = {
|
|
12872
|
+
id: `${change.filePath}::hunk-${index + 1}`,
|
|
12873
|
+
filePath: change.filePath,
|
|
12874
|
+
patch,
|
|
12875
|
+
hunk,
|
|
12876
|
+
header: hunkHeader(hunk),
|
|
12877
|
+
preview: hunkPreview(hunk),
|
|
12878
|
+
};
|
|
12879
|
+
hunks.push(stagedHunk);
|
|
12880
|
+
byId.set(stagedHunk.id, stagedHunk);
|
|
12881
|
+
byFile.set(change.filePath, [...(byFile.get(change.filePath) || []), stagedHunk]);
|
|
12882
|
+
});
|
|
12883
|
+
}
|
|
12884
|
+
return { hunks, byId, byFile };
|
|
12885
|
+
}
|
|
12886
|
+
function formatHunkInventory(inventory) {
|
|
12887
|
+
if (inventory.hunks.length === 0) {
|
|
12888
|
+
return 'No hunk-level inventory available. Use file-level groups.';
|
|
12889
|
+
}
|
|
12890
|
+
return inventory.hunks
|
|
12891
|
+
.map((hunk) => {
|
|
12892
|
+
const preview = hunk.preview ? `\n${hunk.preview}` : '';
|
|
12893
|
+
return `- ${hunk.id}: ${hunk.header}${preview}`;
|
|
12894
|
+
})
|
|
12895
|
+
.join('\n');
|
|
12896
|
+
}
|
|
12897
|
+
function validatePlanForStagedFiles(plan, staged, hunkInventory) {
|
|
12898
|
+
const stagedFiles = getStagedFileSet(staged);
|
|
12899
|
+
const seen = new Set();
|
|
12900
|
+
const seenHunks = new Set();
|
|
12901
|
+
const unknown = [];
|
|
12902
|
+
const duplicate = [];
|
|
12903
|
+
const unknownHunks = [];
|
|
12904
|
+
const duplicateHunks = [];
|
|
12905
|
+
plan.groups.forEach((group) => {
|
|
12906
|
+
getGroupFiles(group).forEach((file) => {
|
|
12907
|
+
if (!stagedFiles.has(file)) {
|
|
12908
|
+
unknown.push(file);
|
|
12909
|
+
return;
|
|
12910
|
+
}
|
|
12911
|
+
if (seen.has(file)) {
|
|
12912
|
+
duplicate.push(file);
|
|
12913
|
+
return;
|
|
12914
|
+
}
|
|
12915
|
+
seen.add(file);
|
|
12916
|
+
});
|
|
12917
|
+
getGroupHunks(group).forEach((hunkId) => {
|
|
12918
|
+
const hunk = hunkInventory?.byId.get(hunkId);
|
|
12919
|
+
if (!hunk) {
|
|
12920
|
+
unknownHunks.push(hunkId);
|
|
12921
|
+
return;
|
|
12922
|
+
}
|
|
12923
|
+
if (seenHunks.has(hunkId)) {
|
|
12924
|
+
duplicateHunks.push(hunkId);
|
|
12925
|
+
return;
|
|
12926
|
+
}
|
|
12927
|
+
seenHunks.add(hunkId);
|
|
12928
|
+
});
|
|
12929
|
+
});
|
|
12930
|
+
const hunkCoveredFiles = new Set([...seenHunks].map((hunkId) => hunkInventory?.byId.get(hunkId)?.filePath));
|
|
12931
|
+
const mixedFiles = [...seen].filter((file) => hunkCoveredFiles.has(file));
|
|
12932
|
+
const partiallyCoveredFiles = [...hunkCoveredFiles]
|
|
12933
|
+
.filter((file) => Boolean(file))
|
|
12934
|
+
.filter((file) => {
|
|
12935
|
+
const fileHunks = hunkInventory?.byFile.get(file) || [];
|
|
12936
|
+
return fileHunks.some((hunk) => !seenHunks.has(hunk.id));
|
|
12937
|
+
});
|
|
12938
|
+
const missing = [...stagedFiles].filter((file) => !seen.has(file) && !hunkCoveredFiles.has(file));
|
|
12939
|
+
if (unknown.length ||
|
|
12940
|
+
duplicate.length ||
|
|
12941
|
+
unknownHunks.length ||
|
|
12942
|
+
duplicateHunks.length ||
|
|
12943
|
+
mixedFiles.length ||
|
|
12944
|
+
partiallyCoveredFiles.length ||
|
|
12945
|
+
missing.length) {
|
|
12946
|
+
throw new Error([
|
|
12947
|
+
unknown.length ? `unknown files: ${unknown.join(', ')}` : undefined,
|
|
12948
|
+
duplicate.length ? `duplicate files: ${duplicate.join(', ')}` : undefined,
|
|
12949
|
+
unknownHunks.length ? `unknown hunks: ${unknownHunks.join(', ')}` : undefined,
|
|
12950
|
+
duplicateHunks.length ? `duplicate hunks: ${duplicateHunks.join(', ')}` : undefined,
|
|
12951
|
+
mixedFiles.length ? `files assigned both as whole files and hunks: ${mixedFiles.join(', ')}` : undefined,
|
|
12952
|
+
partiallyCoveredFiles.length
|
|
12953
|
+
? `files with only some hunks assigned: ${partiallyCoveredFiles.join(', ')}`
|
|
12954
|
+
: undefined,
|
|
12955
|
+
missing.length ? `missing files: ${missing.join(', ')}` : undefined,
|
|
12956
|
+
]
|
|
12957
|
+
.filter(Boolean)
|
|
12958
|
+
.join('; '));
|
|
12959
|
+
}
|
|
12960
|
+
}
|
|
12961
|
+
function assertNoUnstagedOverlap(plan, changes, hunkInventory) {
|
|
12962
|
+
const hunkFiles = new Set(plan.groups.flatMap((group) => getGroupHunks(group)
|
|
12963
|
+
.map((hunkId) => hunkInventory?.byId.get(hunkId)?.filePath)
|
|
12964
|
+
.filter((file) => Boolean(file))));
|
|
12965
|
+
const plannedFiles = new Set(plan.groups
|
|
12966
|
+
.flatMap((group) => getGroupFiles(group))
|
|
12967
|
+
.filter((file) => !hunkFiles.has(file)));
|
|
12968
|
+
const overlapping = [...(changes.unstaged || []), ...(changes.untracked || [])]
|
|
12969
|
+
.map((change) => change.filePath)
|
|
12970
|
+
.filter((file) => plannedFiles.has(file));
|
|
12971
|
+
if (overlapping.length > 0) {
|
|
12972
|
+
throw new Error(`Cannot apply split plan because these files also have unstaged or untracked changes: ${overlapping.join(', ')}`);
|
|
12973
|
+
}
|
|
12974
|
+
}
|
|
12975
|
+
function buildPatchForHunks(hunks) {
|
|
12976
|
+
const byFile = new Map();
|
|
12977
|
+
hunks.forEach((hunk) => {
|
|
12978
|
+
byFile.set(hunk.filePath, [...(byFile.get(hunk.filePath) || []), hunk]);
|
|
12979
|
+
});
|
|
12980
|
+
return [...byFile.values()]
|
|
12981
|
+
.map((fileHunks) => {
|
|
12982
|
+
const [firstHunk] = fileHunks;
|
|
12983
|
+
return formatPatch({
|
|
12984
|
+
...firstHunk.patch,
|
|
12985
|
+
hunks: fileHunks.map((hunk) => hunk.hunk),
|
|
12986
|
+
});
|
|
12987
|
+
})
|
|
12988
|
+
.join('\n');
|
|
12989
|
+
}
|
|
12990
|
+
async function applyPatchToIndex(patch, git) {
|
|
12991
|
+
const cwd = await git.revparse(['--show-toplevel']);
|
|
12992
|
+
await new Promise((resolve, reject) => {
|
|
12993
|
+
const child = spawn('git', ['apply', '--cached', '-'], {
|
|
12994
|
+
cwd,
|
|
12995
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
12996
|
+
});
|
|
12997
|
+
let stderr = '';
|
|
12998
|
+
child.stderr.on('data', (chunk) => {
|
|
12999
|
+
stderr += String(chunk);
|
|
13000
|
+
});
|
|
13001
|
+
child.on('error', reject);
|
|
13002
|
+
child.on('close', (code) => {
|
|
13003
|
+
if (code === 0) {
|
|
13004
|
+
resolve();
|
|
13005
|
+
return;
|
|
13006
|
+
}
|
|
13007
|
+
reject(new Error(`Failed to apply hunk patch to index: ${stderr.trim()}`));
|
|
13008
|
+
});
|
|
13009
|
+
child.stdin.write(patch);
|
|
13010
|
+
child.stdin.end();
|
|
13011
|
+
});
|
|
13012
|
+
}
|
|
13013
|
+
async function applyCommitSplitPlan({ plan, changes, hunkInventory, git, logger, noVerify, }) {
|
|
13014
|
+
validatePlanForStagedFiles(plan, changes.staged, hunkInventory);
|
|
13015
|
+
assertNoUnstagedOverlap(plan, changes, hunkInventory);
|
|
13016
|
+
await git.raw(['reset']);
|
|
13017
|
+
for (const group of plan.groups) {
|
|
13018
|
+
const groupFiles = getGroupFiles(group);
|
|
13019
|
+
const groupHunks = getGroupHunks(group).map((hunkId) => hunkInventory.byId.get(hunkId));
|
|
13020
|
+
if (groupFiles.length > 0) {
|
|
13021
|
+
await git.add(groupFiles);
|
|
13022
|
+
}
|
|
13023
|
+
if (groupHunks.length > 0) {
|
|
13024
|
+
const patch = buildPatchForHunks(groupHunks.filter((hunk) => Boolean(hunk)));
|
|
13025
|
+
await applyPatchToIndex(patch, git);
|
|
13026
|
+
}
|
|
13027
|
+
await createCommit(`${group.title}\n\n${group.body}`.trim(), git, undefined, { noVerify });
|
|
13028
|
+
logger.verbose(`Created split commit: ${group.title}`, { color: 'green' });
|
|
13029
|
+
}
|
|
13030
|
+
return `Created ${plan.groups.length} split commit(s).`;
|
|
13031
|
+
}
|
|
13032
|
+
async function handleCommitSplit({ argv, config, git, logger, tokenizer, llm, }) {
|
|
13033
|
+
const changes = await getChanges({
|
|
13034
|
+
git,
|
|
13035
|
+
options: {
|
|
13036
|
+
ignoredFiles: config.ignoredFiles || undefined,
|
|
13037
|
+
ignoredExtensions: config.ignoredExtensions || undefined,
|
|
13038
|
+
},
|
|
13039
|
+
});
|
|
13040
|
+
if (changes.staged.length === 0) {
|
|
13041
|
+
return 'No staged changes found.';
|
|
13042
|
+
}
|
|
13043
|
+
const hunkInventory = await collectHunkInventory(changes.staged, git);
|
|
13044
|
+
const summary = await fileChangeParser({
|
|
13045
|
+
changes: changes.staged,
|
|
13046
|
+
commit: '--staged',
|
|
13047
|
+
options: {
|
|
13048
|
+
tokenizer,
|
|
13049
|
+
git,
|
|
13050
|
+
llm,
|
|
13051
|
+
logger,
|
|
13052
|
+
maxTokens: config.service.tokenLimit,
|
|
13053
|
+
minTokensForSummary: config.service.minTokensForSummary,
|
|
13054
|
+
maxFileTokens: config.service.maxFileTokens,
|
|
13055
|
+
maxConcurrent: config.service.maxConcurrent,
|
|
13056
|
+
metadata: {
|
|
13057
|
+
command: 'commit',
|
|
13058
|
+
provider: config.service.provider,
|
|
13059
|
+
model: String(config.service.model),
|
|
13060
|
+
},
|
|
13061
|
+
},
|
|
13062
|
+
});
|
|
13063
|
+
const fileInventory = changes.staged
|
|
13064
|
+
.map((change) => `- ${change.filePath}: ${change.status} - ${change.summary}`)
|
|
13065
|
+
.join('\n');
|
|
13066
|
+
const hunkInventoryText = formatHunkInventory(hunkInventory);
|
|
13067
|
+
const plan = await executeChainWithSchema(CommitSplitPlanSchema, llm, COMMIT_SPLIT_PROMPT, {
|
|
13068
|
+
file_inventory: fileInventory,
|
|
13069
|
+
hunk_inventory: hunkInventoryText,
|
|
13070
|
+
summary,
|
|
13071
|
+
additional_context: argv.additional || '',
|
|
13072
|
+
}, {
|
|
13073
|
+
logger,
|
|
13074
|
+
tokenizer,
|
|
13075
|
+
metadata: {
|
|
13076
|
+
task: 'commit-split-plan',
|
|
13077
|
+
command: 'commit',
|
|
13078
|
+
provider: config.service.provider,
|
|
13079
|
+
model: String(config.service.model),
|
|
13080
|
+
},
|
|
13081
|
+
});
|
|
13082
|
+
validatePlanForStagedFiles(plan, changes.staged, hunkInventory);
|
|
13083
|
+
if (argv.apply) {
|
|
13084
|
+
return await applyCommitSplitPlan({
|
|
13085
|
+
plan,
|
|
13086
|
+
changes,
|
|
13087
|
+
hunkInventory,
|
|
13088
|
+
git,
|
|
13089
|
+
logger,
|
|
13090
|
+
noVerify: argv.noVerify || config.noVerify || false,
|
|
13091
|
+
});
|
|
13092
|
+
}
|
|
13093
|
+
return formatCommitSplitPlan(plan);
|
|
13094
|
+
}
|
|
13095
|
+
|
|
13096
|
+
const handler$4 = async (argv, logger) => {
|
|
12390
13097
|
const git = getRepo();
|
|
12391
13098
|
const config = loadConfig(argv);
|
|
12392
13099
|
const key = getApiKeyForModel(config);
|
|
12393
|
-
const { provider
|
|
13100
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
13101
|
+
const commitService = resolveDynamicService(config, 'commit');
|
|
13102
|
+
const summaryService = resolveDynamicService(config, 'summarize');
|
|
13103
|
+
const model = commitService.model;
|
|
12394
13104
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
12395
13105
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
12396
13106
|
process.exit(1);
|
|
12397
13107
|
}
|
|
12398
13108
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
12399
|
-
const llm = getLlm(provider, model, config);
|
|
13109
|
+
const llm = getLlm(provider, model, { ...config, service: commitService });
|
|
13110
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
12400
13111
|
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
12401
13112
|
if (INTERACTIVE) {
|
|
12402
13113
|
if (!config.hideCocoBanner) {
|
|
@@ -12414,6 +13125,22 @@ const handler$3 = async (argv, logger) => {
|
|
|
12414
13125
|
logger.verbose(`→ ${provider} (${model})`, {
|
|
12415
13126
|
color: 'green',
|
|
12416
13127
|
});
|
|
13128
|
+
if (isCommitSplitCommand(argv)) {
|
|
13129
|
+
const splitResult = await handleCommitSplit({
|
|
13130
|
+
argv,
|
|
13131
|
+
config,
|
|
13132
|
+
git,
|
|
13133
|
+
logger,
|
|
13134
|
+
tokenizer,
|
|
13135
|
+
llm,
|
|
13136
|
+
});
|
|
13137
|
+
await handleResult({
|
|
13138
|
+
result: splitResult,
|
|
13139
|
+
mode: config.mode || 'stdout',
|
|
13140
|
+
});
|
|
13141
|
+
logLlmTelemetrySummary(logger, 'commit');
|
|
13142
|
+
return;
|
|
13143
|
+
}
|
|
12417
13144
|
const USE_CONVENTIONAL_COMMITS = config.conventionalCommits || argv.conventional;
|
|
12418
13145
|
async function factory() {
|
|
12419
13146
|
if (config.noDiff) {
|
|
@@ -12451,12 +13178,17 @@ const handler$3 = async (argv, logger) => {
|
|
|
12451
13178
|
options: {
|
|
12452
13179
|
tokenizer,
|
|
12453
13180
|
git,
|
|
12454
|
-
llm,
|
|
13181
|
+
llm: summaryLlm,
|
|
12455
13182
|
logger,
|
|
12456
13183
|
maxTokens: config.service.tokenLimit,
|
|
12457
13184
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
12458
13185
|
maxFileTokens: config.service.maxFileTokens,
|
|
12459
13186
|
maxConcurrent: config.service.maxConcurrent,
|
|
13187
|
+
metadata: {
|
|
13188
|
+
command: 'commit',
|
|
13189
|
+
provider,
|
|
13190
|
+
model: String(summaryService.model),
|
|
13191
|
+
},
|
|
12460
13192
|
},
|
|
12461
13193
|
});
|
|
12462
13194
|
}
|
|
@@ -12606,6 +13338,14 @@ IMPORTANT RULES:
|
|
|
12606
13338
|
logger.verbose(`Rendered prompt exceeded token budget; trimmed summary to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
12607
13339
|
}
|
|
12608
13340
|
const commitMsg = await executeChainWithSchema(schema, llm, prompt, budgetedPrompt.variables, {
|
|
13341
|
+
logger,
|
|
13342
|
+
tokenizer,
|
|
13343
|
+
metadata: {
|
|
13344
|
+
task: USE_CONVENTIONAL_COMMITS ? 'commit-message-conventional' : 'commit-message',
|
|
13345
|
+
command: 'commit',
|
|
13346
|
+
provider,
|
|
13347
|
+
model: String(model),
|
|
13348
|
+
},
|
|
12609
13349
|
retryOptions: {
|
|
12610
13350
|
maxAttempts,
|
|
12611
13351
|
onRetry: (attempt, error) => {
|
|
@@ -12814,29 +13554,30 @@ IMPORTANT RULES:
|
|
|
12814
13554
|
},
|
|
12815
13555
|
mode: MODE,
|
|
12816
13556
|
});
|
|
13557
|
+
logLlmTelemetrySummary(logger, 'commit');
|
|
12817
13558
|
};
|
|
12818
13559
|
|
|
12819
13560
|
var commit = {
|
|
12820
|
-
command: command$
|
|
13561
|
+
command: command$4,
|
|
12821
13562
|
desc: 'Summarize the staged changes in a commit message.',
|
|
12822
|
-
builder: builder$
|
|
12823
|
-
handler: commandExecutor(handler$
|
|
12824
|
-
options: options$
|
|
13563
|
+
builder: builder$4,
|
|
13564
|
+
handler: commandExecutor(handler$4),
|
|
13565
|
+
options: options$4,
|
|
12825
13566
|
};
|
|
12826
13567
|
|
|
12827
|
-
const command$
|
|
13568
|
+
const command$3 = 'init';
|
|
12828
13569
|
/**
|
|
12829
13570
|
* Command line options via yargs
|
|
12830
13571
|
*/
|
|
12831
|
-
const options$
|
|
13572
|
+
const options$3 = {
|
|
12832
13573
|
scope: {
|
|
12833
13574
|
type: 'string',
|
|
12834
13575
|
description: 'configure coco for the current user or project?',
|
|
12835
13576
|
choices: ['global', 'project'],
|
|
12836
13577
|
},
|
|
12837
13578
|
};
|
|
12838
|
-
const builder$
|
|
12839
|
-
return yargs.options(options$
|
|
13579
|
+
const builder$3 = (yargs) => {
|
|
13580
|
+
return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
|
|
12840
13581
|
};
|
|
12841
13582
|
|
|
12842
13583
|
/**
|
|
@@ -13187,7 +13928,7 @@ const questions = {
|
|
|
13187
13928
|
}),
|
|
13188
13929
|
};
|
|
13189
13930
|
|
|
13190
|
-
const handler$
|
|
13931
|
+
const handler$3 = async (argv, logger) => {
|
|
13191
13932
|
const options = loadConfig(argv);
|
|
13192
13933
|
logger.log(LOGO);
|
|
13193
13934
|
let scope = options?.scope;
|
|
@@ -13358,8 +14099,287 @@ async function installCommitlintPackages(scope, logger) {
|
|
|
13358
14099
|
}
|
|
13359
14100
|
|
|
13360
14101
|
var init = {
|
|
13361
|
-
command: command$
|
|
14102
|
+
command: command$3,
|
|
13362
14103
|
desc: 'install & configure coco globally or for the current project',
|
|
14104
|
+
builder: builder$3,
|
|
14105
|
+
handler: commandExecutor(handler$3),
|
|
14106
|
+
options: options$3,
|
|
14107
|
+
};
|
|
14108
|
+
|
|
14109
|
+
const command$2 = 'log';
|
|
14110
|
+
const options$2 = {
|
|
14111
|
+
all: {
|
|
14112
|
+
description: 'Show commits from all local and remote refs',
|
|
14113
|
+
type: 'boolean',
|
|
14114
|
+
default: false,
|
|
14115
|
+
},
|
|
14116
|
+
author: {
|
|
14117
|
+
description: 'Filter commits by author',
|
|
14118
|
+
type: 'string',
|
|
14119
|
+
},
|
|
14120
|
+
branch: {
|
|
14121
|
+
description: 'Show commits reachable from a branch or ref',
|
|
14122
|
+
type: 'string',
|
|
14123
|
+
alias: 'b',
|
|
14124
|
+
},
|
|
14125
|
+
commit: {
|
|
14126
|
+
description: 'Show details and changed files for a single commit',
|
|
14127
|
+
type: 'string',
|
|
14128
|
+
alias: 'c',
|
|
14129
|
+
},
|
|
14130
|
+
format: {
|
|
14131
|
+
description: 'Output format',
|
|
14132
|
+
choices: ['table', 'json'],
|
|
14133
|
+
default: 'table',
|
|
14134
|
+
},
|
|
14135
|
+
limit: {
|
|
14136
|
+
description: 'Maximum number of commits to show',
|
|
14137
|
+
type: 'number',
|
|
14138
|
+
default: 30,
|
|
14139
|
+
alias: 'n',
|
|
14140
|
+
},
|
|
14141
|
+
noMerges: {
|
|
14142
|
+
description: 'Exclude merge commits',
|
|
14143
|
+
type: 'boolean',
|
|
14144
|
+
default: false,
|
|
14145
|
+
},
|
|
14146
|
+
path: {
|
|
14147
|
+
description: 'Filter commits by changed path',
|
|
14148
|
+
type: 'array',
|
|
14149
|
+
},
|
|
14150
|
+
since: {
|
|
14151
|
+
description: 'Show commits more recent than a date',
|
|
14152
|
+
type: 'string',
|
|
14153
|
+
},
|
|
14154
|
+
until: {
|
|
14155
|
+
description: 'Show commits older than a date',
|
|
14156
|
+
type: 'string',
|
|
14157
|
+
},
|
|
14158
|
+
};
|
|
14159
|
+
const builder$2 = (yargs) => {
|
|
14160
|
+
return yargs.options(options$2).usage(getCommandUsageHeader(command$2));
|
|
14161
|
+
};
|
|
14162
|
+
|
|
14163
|
+
const FIELD_SEPARATOR = '\x1f';
|
|
14164
|
+
const LOG_FORMAT = `%x1f%h%x1f%H%x1f%ad%x1f%an%x1f%d%x1f%s`;
|
|
14165
|
+
const DETAIL_FORMAT = `%H%x1f%h%x1f%ad%x1f%an%x1f%d%x1f%s%x1f%b`;
|
|
14166
|
+
function toArray(value) {
|
|
14167
|
+
if (!value) {
|
|
14168
|
+
return [];
|
|
14169
|
+
}
|
|
14170
|
+
return Array.isArray(value) ? value : [value];
|
|
14171
|
+
}
|
|
14172
|
+
function normalizeLimit(limit) {
|
|
14173
|
+
if (!limit || Number.isNaN(limit) || limit < 1) {
|
|
14174
|
+
return 30;
|
|
14175
|
+
}
|
|
14176
|
+
return Math.floor(limit);
|
|
14177
|
+
}
|
|
14178
|
+
function cleanRefs(refs) {
|
|
14179
|
+
const trimmed = refs.trim();
|
|
14180
|
+
if (!trimmed) {
|
|
14181
|
+
return [];
|
|
14182
|
+
}
|
|
14183
|
+
return trimmed
|
|
14184
|
+
.replace(/^\(/, '')
|
|
14185
|
+
.replace(/\)$/, '')
|
|
14186
|
+
.split(',')
|
|
14187
|
+
.map((ref) => ref.trim())
|
|
14188
|
+
.filter(Boolean);
|
|
14189
|
+
}
|
|
14190
|
+
function parseLogOutput(output) {
|
|
14191
|
+
return output
|
|
14192
|
+
.split('\n')
|
|
14193
|
+
.map((line) => line.trimEnd())
|
|
14194
|
+
.filter((line) => line.includes(FIELD_SEPARATOR))
|
|
14195
|
+
.map((line) => {
|
|
14196
|
+
const [graph, shortHash, hash, date, author, refs, message] = line.split(FIELD_SEPARATOR);
|
|
14197
|
+
return {
|
|
14198
|
+
graph: graph.trimEnd(),
|
|
14199
|
+
shortHash,
|
|
14200
|
+
hash,
|
|
14201
|
+
date,
|
|
14202
|
+
author,
|
|
14203
|
+
refs: cleanRefs(refs),
|
|
14204
|
+
message,
|
|
14205
|
+
};
|
|
14206
|
+
});
|
|
14207
|
+
}
|
|
14208
|
+
function parseNameStatus(output) {
|
|
14209
|
+
return output
|
|
14210
|
+
.split('\n')
|
|
14211
|
+
.map((line) => line.trim())
|
|
14212
|
+
.filter(Boolean)
|
|
14213
|
+
.map((line) => {
|
|
14214
|
+
const [status, firstPath, secondPath] = line.split('\t');
|
|
14215
|
+
if (status.startsWith('R') || status.startsWith('C')) {
|
|
14216
|
+
return {
|
|
14217
|
+
status,
|
|
14218
|
+
oldPath: firstPath,
|
|
14219
|
+
path: secondPath,
|
|
14220
|
+
};
|
|
14221
|
+
}
|
|
14222
|
+
return {
|
|
14223
|
+
status,
|
|
14224
|
+
path: firstPath,
|
|
14225
|
+
};
|
|
14226
|
+
});
|
|
14227
|
+
}
|
|
14228
|
+
function parseCommitDetail(metadata, files) {
|
|
14229
|
+
const [hash, shortHash, date, author, refs, message, body = ''] = metadata
|
|
14230
|
+
.trimEnd()
|
|
14231
|
+
.split(FIELD_SEPARATOR);
|
|
14232
|
+
return {
|
|
14233
|
+
shortHash,
|
|
14234
|
+
hash,
|
|
14235
|
+
date,
|
|
14236
|
+
author,
|
|
14237
|
+
refs: cleanRefs(refs),
|
|
14238
|
+
message,
|
|
14239
|
+
body: body.trim(),
|
|
14240
|
+
files: parseNameStatus(files),
|
|
14241
|
+
};
|
|
14242
|
+
}
|
|
14243
|
+
function truncate(value, width) {
|
|
14244
|
+
if (value.length <= width) {
|
|
14245
|
+
return value;
|
|
14246
|
+
}
|
|
14247
|
+
return `${value.slice(0, Math.max(0, width - 1))}.`;
|
|
14248
|
+
}
|
|
14249
|
+
function pad(value, width) {
|
|
14250
|
+
return truncate(value, width).padEnd(width, ' ');
|
|
14251
|
+
}
|
|
14252
|
+
function formatLogTable(entries) {
|
|
14253
|
+
if (entries.length === 0) {
|
|
14254
|
+
return 'No commits found.';
|
|
14255
|
+
}
|
|
14256
|
+
const rows = entries.map((entry) => {
|
|
14257
|
+
const refs = entry.refs.join(', ');
|
|
14258
|
+
return [
|
|
14259
|
+
pad(entry.graph || '*', 8),
|
|
14260
|
+
pad(entry.shortHash, 9),
|
|
14261
|
+
pad(entry.date, 10),
|
|
14262
|
+
pad(entry.author, 18),
|
|
14263
|
+
pad(refs, 26),
|
|
14264
|
+
entry.message,
|
|
14265
|
+
].join(' ');
|
|
14266
|
+
});
|
|
14267
|
+
return [
|
|
14268
|
+
[
|
|
14269
|
+
pad('Graph', 8),
|
|
14270
|
+
pad('Commit', 9),
|
|
14271
|
+
pad('Date', 10),
|
|
14272
|
+
pad('Author', 18),
|
|
14273
|
+
pad('Refs', 26),
|
|
14274
|
+
'Message',
|
|
14275
|
+
].join(' '),
|
|
14276
|
+
...rows,
|
|
14277
|
+
].join('\n');
|
|
14278
|
+
}
|
|
14279
|
+
function formatCommitDetail(detail, format) {
|
|
14280
|
+
if (format === 'json') {
|
|
14281
|
+
return JSON.stringify(detail, null, 2);
|
|
14282
|
+
}
|
|
14283
|
+
const refs = detail.refs.length ? ` (${detail.refs.join(', ')})` : '';
|
|
14284
|
+
const body = detail.body ? `\n\n${detail.body}` : '';
|
|
14285
|
+
const files = detail.files.length
|
|
14286
|
+
? detail.files
|
|
14287
|
+
.map((file) => {
|
|
14288
|
+
if (file.oldPath) {
|
|
14289
|
+
return ` ${file.status} ${file.oldPath} -> ${file.path}`;
|
|
14290
|
+
}
|
|
14291
|
+
return ` ${file.status} ${file.path}`;
|
|
14292
|
+
})
|
|
14293
|
+
.join('\n')
|
|
14294
|
+
: ' No changed files found.';
|
|
14295
|
+
return [
|
|
14296
|
+
`commit ${detail.hash}${refs}`,
|
|
14297
|
+
`Author: ${detail.author}`,
|
|
14298
|
+
`Date: ${detail.date}`,
|
|
14299
|
+
'',
|
|
14300
|
+
` ${detail.message}${body}`,
|
|
14301
|
+
'',
|
|
14302
|
+
'Changed files:',
|
|
14303
|
+
files,
|
|
14304
|
+
].join('\n');
|
|
14305
|
+
}
|
|
14306
|
+
function buildLogArgs(argv) {
|
|
14307
|
+
const args = [
|
|
14308
|
+
'log',
|
|
14309
|
+
'--graph',
|
|
14310
|
+
'--decorate=short',
|
|
14311
|
+
'--date=short',
|
|
14312
|
+
'--color=never',
|
|
14313
|
+
`--max-count=${normalizeLimit(argv.limit)}`,
|
|
14314
|
+
`--pretty=format:${LOG_FORMAT}`,
|
|
14315
|
+
];
|
|
14316
|
+
if (argv.noMerges) {
|
|
14317
|
+
args.push('--no-merges');
|
|
14318
|
+
}
|
|
14319
|
+
if (argv.author) {
|
|
14320
|
+
args.push(`--author=${argv.author}`);
|
|
14321
|
+
}
|
|
14322
|
+
if (argv.since) {
|
|
14323
|
+
args.push(`--since=${argv.since}`);
|
|
14324
|
+
}
|
|
14325
|
+
if (argv.until) {
|
|
14326
|
+
args.push(`--until=${argv.until}`);
|
|
14327
|
+
}
|
|
14328
|
+
if (argv.all) {
|
|
14329
|
+
args.push('--all');
|
|
14330
|
+
}
|
|
14331
|
+
else if (argv.branch) {
|
|
14332
|
+
args.push(argv.branch);
|
|
14333
|
+
}
|
|
14334
|
+
const paths = toArray(argv.path);
|
|
14335
|
+
if (paths.length > 0) {
|
|
14336
|
+
args.push('--', ...paths);
|
|
14337
|
+
}
|
|
14338
|
+
return args;
|
|
14339
|
+
}
|
|
14340
|
+
async function getCommitDetail(git, commit) {
|
|
14341
|
+
const metadata = await git.raw([
|
|
14342
|
+
'show',
|
|
14343
|
+
'--no-patch',
|
|
14344
|
+
'--date=short',
|
|
14345
|
+
'--color=never',
|
|
14346
|
+
`--pretty=format:${DETAIL_FORMAT}`,
|
|
14347
|
+
commit,
|
|
14348
|
+
]);
|
|
14349
|
+
const files = await git.raw([
|
|
14350
|
+
'show',
|
|
14351
|
+
'--name-status',
|
|
14352
|
+
'--format=',
|
|
14353
|
+
'--find-renames',
|
|
14354
|
+
'--color=never',
|
|
14355
|
+
commit,
|
|
14356
|
+
]);
|
|
14357
|
+
return parseCommitDetail(metadata, files);
|
|
14358
|
+
}
|
|
14359
|
+
const handler$2 = async (argv) => {
|
|
14360
|
+
const git = getRepo();
|
|
14361
|
+
const mode = argv.interactive ? 'interactive' : 'stdout';
|
|
14362
|
+
const format = argv.format === 'json' ? 'json' : 'table';
|
|
14363
|
+
if (argv.commit) {
|
|
14364
|
+
const detail = await getCommitDetail(git, argv.commit);
|
|
14365
|
+
await handleResult({
|
|
14366
|
+
result: formatCommitDetail(detail, format),
|
|
14367
|
+
mode,
|
|
14368
|
+
});
|
|
14369
|
+
return;
|
|
14370
|
+
}
|
|
14371
|
+
const output = await git.raw(buildLogArgs(argv));
|
|
14372
|
+
const entries = parseLogOutput(output);
|
|
14373
|
+
const result = format === 'json' ? JSON.stringify(entries, null, 2) : formatLogTable(entries);
|
|
14374
|
+
await handleResult({
|
|
14375
|
+
result,
|
|
14376
|
+
mode,
|
|
14377
|
+
});
|
|
14378
|
+
};
|
|
14379
|
+
|
|
14380
|
+
var log = {
|
|
14381
|
+
command: command$2,
|
|
14382
|
+
desc: 'Explore commit history with a branch graph, filters, and commit details.',
|
|
13363
14383
|
builder: builder$2,
|
|
13364
14384
|
handler: commandExecutor(handler$2),
|
|
13365
14385
|
options: options$2,
|
|
@@ -13437,13 +14457,17 @@ const handler$1 = async (argv, logger) => {
|
|
|
13437
14457
|
const git = getRepo();
|
|
13438
14458
|
const config = loadConfig(argv);
|
|
13439
14459
|
const key = getApiKeyForModel(config);
|
|
13440
|
-
const { provider
|
|
14460
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
14461
|
+
const recapService = resolveDynamicService(config, 'recap');
|
|
14462
|
+
const summaryService = resolveDynamicService(config, 'summarize');
|
|
14463
|
+
const model = recapService.model;
|
|
13441
14464
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
13442
14465
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
13443
14466
|
process.exit(1);
|
|
13444
14467
|
}
|
|
13445
14468
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
13446
|
-
const llm = getLlm(provider, model, config);
|
|
14469
|
+
const llm = getLlm(provider, model, { ...config, service: recapService });
|
|
14470
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
13447
14471
|
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
13448
14472
|
if (INTERACTIVE) {
|
|
13449
14473
|
if (!config.hideCocoBanner) {
|
|
@@ -13474,19 +14498,49 @@ const handler$1 = async (argv, logger) => {
|
|
|
13474
14498
|
const unstagedChanges = await fileChangeParser({
|
|
13475
14499
|
changes: unstaged || [],
|
|
13476
14500
|
commit: '--unstaged',
|
|
13477
|
-
options: {
|
|
14501
|
+
options: {
|
|
14502
|
+
tokenizer,
|
|
14503
|
+
git,
|
|
14504
|
+
llm: summaryLlm,
|
|
14505
|
+
logger,
|
|
14506
|
+
metadata: {
|
|
14507
|
+
command: 'recap',
|
|
14508
|
+
provider,
|
|
14509
|
+
model: String(summaryService.model),
|
|
14510
|
+
},
|
|
14511
|
+
},
|
|
13478
14512
|
});
|
|
13479
14513
|
const unstagedResponse = `Unstaged changes:\n${unstagedChanges}`;
|
|
13480
14514
|
const untrackedChanges = await fileChangeParser({
|
|
13481
14515
|
changes: untracked || [],
|
|
13482
14516
|
commit: '--untracked',
|
|
13483
|
-
options: {
|
|
14517
|
+
options: {
|
|
14518
|
+
tokenizer,
|
|
14519
|
+
git,
|
|
14520
|
+
llm: summaryLlm,
|
|
14521
|
+
logger,
|
|
14522
|
+
metadata: {
|
|
14523
|
+
command: 'recap',
|
|
14524
|
+
provider,
|
|
14525
|
+
model: String(summaryService.model),
|
|
14526
|
+
},
|
|
14527
|
+
},
|
|
13484
14528
|
});
|
|
13485
14529
|
const untrackedResponse = `Untracked changes:\n${untrackedChanges}`;
|
|
13486
14530
|
const stagedChanges = await fileChangeParser({
|
|
13487
14531
|
changes: staged,
|
|
13488
14532
|
commit: '--staged',
|
|
13489
|
-
options: {
|
|
14533
|
+
options: {
|
|
14534
|
+
tokenizer,
|
|
14535
|
+
git,
|
|
14536
|
+
llm: summaryLlm,
|
|
14537
|
+
logger,
|
|
14538
|
+
metadata: {
|
|
14539
|
+
command: 'recap',
|
|
14540
|
+
provider,
|
|
14541
|
+
model: String(summaryService.model),
|
|
14542
|
+
},
|
|
14543
|
+
},
|
|
13490
14544
|
});
|
|
13491
14545
|
const stagedResponse = `Staged changes:\n${stagedChanges}`;
|
|
13492
14546
|
return [unstagedResponse, untrackedResponse, stagedResponse];
|
|
@@ -13521,7 +14575,17 @@ const handler$1 = async (argv, logger) => {
|
|
|
13521
14575
|
const branchChanges = await fileChangeParser({
|
|
13522
14576
|
changes: changes.staged,
|
|
13523
14577
|
commit: baseBranch,
|
|
13524
|
-
options: {
|
|
14578
|
+
options: {
|
|
14579
|
+
tokenizer,
|
|
14580
|
+
git,
|
|
14581
|
+
llm: summaryLlm,
|
|
14582
|
+
logger,
|
|
14583
|
+
metadata: {
|
|
14584
|
+
command: 'recap',
|
|
14585
|
+
provider,
|
|
14586
|
+
model: String(summaryService.model),
|
|
14587
|
+
},
|
|
14588
|
+
},
|
|
13525
14589
|
});
|
|
13526
14590
|
return [branchChanges];
|
|
13527
14591
|
default:
|
|
@@ -13566,15 +14630,34 @@ const handler$1 = async (argv, logger) => {
|
|
|
13566
14630
|
try {
|
|
13567
14631
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13568
14632
|
const parser = createSchemaParser(RecapLlmResponseSchema, llm);
|
|
14633
|
+
const variables = {
|
|
14634
|
+
changes: context,
|
|
14635
|
+
format_instructions: formatInstructions,
|
|
14636
|
+
timeframe,
|
|
14637
|
+
};
|
|
14638
|
+
const budgetedPrompt = await enforcePromptBudget({
|
|
14639
|
+
prompt,
|
|
14640
|
+
variables,
|
|
14641
|
+
tokenizer,
|
|
14642
|
+
maxTokens: config.service.tokenLimit || 2048,
|
|
14643
|
+
summaryKey: 'changes',
|
|
14644
|
+
});
|
|
14645
|
+
if (budgetedPrompt.truncated) {
|
|
14646
|
+
logger.verbose(`Rendered prompt exceeded token budget; trimmed changes to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
14647
|
+
}
|
|
13569
14648
|
const response = await executeChain({
|
|
13570
14649
|
llm,
|
|
13571
14650
|
prompt,
|
|
13572
|
-
variables:
|
|
13573
|
-
changes: context,
|
|
13574
|
-
format_instructions: formatInstructions,
|
|
13575
|
-
timeframe,
|
|
13576
|
-
},
|
|
14651
|
+
variables: budgetedPrompt.variables,
|
|
13577
14652
|
parser,
|
|
14653
|
+
logger,
|
|
14654
|
+
tokenizer,
|
|
14655
|
+
metadata: {
|
|
14656
|
+
task: 'recap',
|
|
14657
|
+
command: 'recap',
|
|
14658
|
+
provider,
|
|
14659
|
+
model: String(model),
|
|
14660
|
+
},
|
|
13578
14661
|
});
|
|
13579
14662
|
return response ? `${response.title}\n\n${response.summary}` : 'no response';
|
|
13580
14663
|
}
|
|
@@ -13611,6 +14694,7 @@ ${errorMessage}
|
|
|
13611
14694
|
},
|
|
13612
14695
|
mode: MODE,
|
|
13613
14696
|
});
|
|
14697
|
+
logLlmTelemetrySummary(logger, 'recap');
|
|
13614
14698
|
};
|
|
13615
14699
|
|
|
13616
14700
|
var recap = {
|
|
@@ -13966,13 +15050,17 @@ const handler = async (argv, logger) => {
|
|
|
13966
15050
|
const git = getRepo();
|
|
13967
15051
|
const config = loadConfig(argv);
|
|
13968
15052
|
const key = getApiKeyForModel(config);
|
|
13969
|
-
const { provider
|
|
15053
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
15054
|
+
const reviewService = resolveDynamicService(config, 'review');
|
|
15055
|
+
const summaryService = resolveDynamicService(config, argv.branch ? 'largeDiff' : 'summarize');
|
|
15056
|
+
const model = reviewService.model;
|
|
13970
15057
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
13971
15058
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
13972
15059
|
process.exit(1);
|
|
13973
15060
|
}
|
|
13974
15061
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
13975
|
-
const llm = getLlm(provider, model, config);
|
|
15062
|
+
const llm = getLlm(provider, model, { ...config, service: reviewService });
|
|
15063
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
13976
15064
|
const INTERACTIVE = isInteractive(config);
|
|
13977
15065
|
if (INTERACTIVE) {
|
|
13978
15066
|
if (!config.hideCocoBanner) {
|
|
@@ -13996,7 +15084,17 @@ const handler = async (argv, logger) => {
|
|
|
13996
15084
|
const branchChanges = await fileChangeParser({
|
|
13997
15085
|
changes: diff.staged,
|
|
13998
15086
|
commit: `--branch-diff-${argv.branch}`,
|
|
13999
|
-
options: {
|
|
15087
|
+
options: {
|
|
15088
|
+
tokenizer,
|
|
15089
|
+
git,
|
|
15090
|
+
llm: summaryLlm,
|
|
15091
|
+
logger,
|
|
15092
|
+
metadata: {
|
|
15093
|
+
command: 'review',
|
|
15094
|
+
provider,
|
|
15095
|
+
model: String(summaryService.model),
|
|
15096
|
+
},
|
|
15097
|
+
},
|
|
14000
15098
|
});
|
|
14001
15099
|
return [branchChanges];
|
|
14002
15100
|
}
|
|
@@ -14018,19 +15116,49 @@ const handler = async (argv, logger) => {
|
|
|
14018
15116
|
const unstagedChanges = await fileChangeParser({
|
|
14019
15117
|
changes: unstaged || [],
|
|
14020
15118
|
commit: '--unstaged',
|
|
14021
|
-
options: {
|
|
15119
|
+
options: {
|
|
15120
|
+
tokenizer,
|
|
15121
|
+
git,
|
|
15122
|
+
llm: summaryLlm,
|
|
15123
|
+
logger,
|
|
15124
|
+
metadata: {
|
|
15125
|
+
command: 'review',
|
|
15126
|
+
provider,
|
|
15127
|
+
model: String(summaryService.model),
|
|
15128
|
+
},
|
|
15129
|
+
},
|
|
14022
15130
|
});
|
|
14023
15131
|
const unstagedResponse = `Unstaged changes:\n${unstagedChanges}`;
|
|
14024
15132
|
const untrackedChanges = await fileChangeParser({
|
|
14025
15133
|
changes: untracked || [],
|
|
14026
15134
|
commit: '--untracked',
|
|
14027
|
-
options: {
|
|
15135
|
+
options: {
|
|
15136
|
+
tokenizer,
|
|
15137
|
+
git,
|
|
15138
|
+
llm: summaryLlm,
|
|
15139
|
+
logger,
|
|
15140
|
+
metadata: {
|
|
15141
|
+
command: 'review',
|
|
15142
|
+
provider,
|
|
15143
|
+
model: String(summaryService.model),
|
|
15144
|
+
},
|
|
15145
|
+
},
|
|
14028
15146
|
});
|
|
14029
15147
|
const untrackedResponse = `Untracked changes:\n${untrackedChanges}`;
|
|
14030
15148
|
const stagedChanges = await fileChangeParser({
|
|
14031
15149
|
changes: staged,
|
|
14032
15150
|
commit: '--staged',
|
|
14033
|
-
options: {
|
|
15151
|
+
options: {
|
|
15152
|
+
tokenizer,
|
|
15153
|
+
git,
|
|
15154
|
+
llm: summaryLlm,
|
|
15155
|
+
logger,
|
|
15156
|
+
metadata: {
|
|
15157
|
+
command: 'review',
|
|
15158
|
+
provider,
|
|
15159
|
+
model: String(summaryService.model),
|
|
15160
|
+
},
|
|
15161
|
+
},
|
|
14034
15162
|
});
|
|
14035
15163
|
const stagedResponse = `Staged changes:\n${stagedChanges}`;
|
|
14036
15164
|
return [unstagedResponse, untrackedResponse, stagedResponse];
|
|
@@ -14070,14 +15198,33 @@ const handler = async (argv, logger) => {
|
|
|
14070
15198
|
variables: REVIEW_PROMPT.inputVariables,
|
|
14071
15199
|
fallback: REVIEW_PROMPT,
|
|
14072
15200
|
});
|
|
15201
|
+
const variables = {
|
|
15202
|
+
changes: context,
|
|
15203
|
+
format_instructions: formatInstructions,
|
|
15204
|
+
};
|
|
15205
|
+
const budgetedPrompt = await enforcePromptBudget({
|
|
15206
|
+
prompt,
|
|
15207
|
+
variables,
|
|
15208
|
+
tokenizer,
|
|
15209
|
+
maxTokens: config.service.tokenLimit || 2048,
|
|
15210
|
+
summaryKey: 'changes',
|
|
15211
|
+
});
|
|
15212
|
+
if (budgetedPrompt.truncated) {
|
|
15213
|
+
logger.verbose(`Rendered prompt exceeded token budget; trimmed changes to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
15214
|
+
}
|
|
14073
15215
|
const response = await executeChain({
|
|
14074
15216
|
llm,
|
|
14075
15217
|
prompt,
|
|
14076
|
-
variables:
|
|
14077
|
-
changes: context,
|
|
14078
|
-
format_instructions: formatInstructions,
|
|
14079
|
-
},
|
|
15218
|
+
variables: budgetedPrompt.variables,
|
|
14080
15219
|
parser,
|
|
15220
|
+
logger,
|
|
15221
|
+
tokenizer,
|
|
15222
|
+
metadata: {
|
|
15223
|
+
task: argv.branch ? 'review-branch' : 'review',
|
|
15224
|
+
command: 'review',
|
|
15225
|
+
provider,
|
|
15226
|
+
model: String(model),
|
|
15227
|
+
},
|
|
14081
15228
|
});
|
|
14082
15229
|
// sort by severity
|
|
14083
15230
|
return response.sort((a, b) => b.severity - a.severity);
|
|
@@ -14096,6 +15243,7 @@ const handler = async (argv, logger) => {
|
|
|
14096
15243
|
},
|
|
14097
15244
|
});
|
|
14098
15245
|
const reviewer = new TaskList(recap, { ...config, apiKey: key ?? undefined });
|
|
15246
|
+
logLlmTelemetrySummary(logger, 'review');
|
|
14099
15247
|
await reviewer.start();
|
|
14100
15248
|
};
|
|
14101
15249
|
|
|
@@ -14116,6 +15264,7 @@ y.command(changelog.command, changelog.desc, changelog.builder, changelog.handle
|
|
|
14116
15264
|
y.command(recap.command, recap.desc, recap.builder, recap.handler);
|
|
14117
15265
|
y.command(review.command, review.desc, review.builder, review.handler);
|
|
14118
15266
|
y.command(init.command, init.desc, init.builder, init.handler);
|
|
15267
|
+
y.command(log.command, log.desc, log.builder, log.handler);
|
|
14119
15268
|
y.help().parse(process.argv.slice(2));
|
|
14120
15269
|
|
|
14121
15270
|
/**
|
|
@@ -14567,4 +15716,4 @@ var commitValidationHandler = /*#__PURE__*/Object.freeze({
|
|
|
14567
15716
|
handleValidationErrors: handleValidationErrors
|
|
14568
15717
|
});
|
|
14569
15718
|
|
|
14570
|
-
export { changelog, commit, init, recap, types };
|
|
15719
|
+
export { changelog, commit, init, log, recap, types };
|