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.js
CHANGED
|
@@ -68,7 +68,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
68
68
|
/**
|
|
69
69
|
* Current build version from package.json
|
|
70
70
|
*/
|
|
71
|
-
const BUILD_VERSION = "0.
|
|
71
|
+
const BUILD_VERSION = "0.33.0";
|
|
72
72
|
|
|
73
73
|
const isInteractive = (config) => {
|
|
74
74
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -523,6 +523,8 @@ function loadEnvConfig(config) {
|
|
|
523
523
|
'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT',
|
|
524
524
|
'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES',
|
|
525
525
|
'COCO_SERVICE_FIELDS',
|
|
526
|
+
'COCO_SERVICE_DYNAMIC_MODELS',
|
|
527
|
+
'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE',
|
|
526
528
|
];
|
|
527
529
|
envKeys.forEach((key) => {
|
|
528
530
|
const envVarName = toEnvVarName(key);
|
|
@@ -537,7 +539,9 @@ function loadEnvConfig(config) {
|
|
|
537
539
|
key === 'COCO_SERVICE_ENDPOINT' ||
|
|
538
540
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_TIMEOUT' ||
|
|
539
541
|
key === 'COCO_SERVICE_REQUEST_OPTIONS_MAX_RETRIES' ||
|
|
540
|
-
key === 'COCO_SERVICE_FIELDS'
|
|
542
|
+
key === 'COCO_SERVICE_FIELDS' ||
|
|
543
|
+
key === 'COCO_SERVICE_DYNAMIC_MODELS' ||
|
|
544
|
+
key === 'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE') {
|
|
541
545
|
// NOTE: We want to ensure that the service object is always defined
|
|
542
546
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
543
547
|
// @ts-ignore
|
|
@@ -592,6 +596,12 @@ function handleServiceEnvVar(service, key, value) {
|
|
|
592
596
|
case 'COCO_SERVICE_FIELDS':
|
|
593
597
|
service.fields = value;
|
|
594
598
|
break;
|
|
599
|
+
case 'COCO_SERVICE_DYNAMIC_MODELS':
|
|
600
|
+
service.dynamicModels = value;
|
|
601
|
+
break;
|
|
602
|
+
case 'COCO_SERVICE_DYNAMIC_MODEL_PREFERENCE':
|
|
603
|
+
service.dynamicModelPreference = value;
|
|
604
|
+
break;
|
|
595
605
|
}
|
|
596
606
|
}
|
|
597
607
|
function parseEnvValue(key, value) {
|
|
@@ -1000,7 +1010,7 @@ const schema$1 = {
|
|
|
1000
1010
|
"$ref": "#/definitions/LLMProvider"
|
|
1001
1011
|
},
|
|
1002
1012
|
"model": {
|
|
1003
|
-
"$ref": "#/definitions/
|
|
1013
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
1004
1014
|
},
|
|
1005
1015
|
"baseURL": {
|
|
1006
1016
|
"type": "string",
|
|
@@ -1692,6 +1702,15 @@ const schema$1 = {
|
|
|
1692
1702
|
"type": "number",
|
|
1693
1703
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
1694
1704
|
"default": 3
|
|
1705
|
+
},
|
|
1706
|
+
"dynamicModels": {
|
|
1707
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
1708
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
1709
|
+
},
|
|
1710
|
+
"dynamicModelPreference": {
|
|
1711
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
1712
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1713
|
+
"default": "balanced"
|
|
1695
1714
|
}
|
|
1696
1715
|
},
|
|
1697
1716
|
"required": [
|
|
@@ -1708,6 +1727,17 @@ const schema$1 = {
|
|
|
1708
1727
|
"anthropic"
|
|
1709
1728
|
]
|
|
1710
1729
|
},
|
|
1730
|
+
"ConfiguredLLMModel": {
|
|
1731
|
+
"anyOf": [
|
|
1732
|
+
{
|
|
1733
|
+
"$ref": "#/definitions/LLMModel"
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
"type": "string",
|
|
1737
|
+
"const": "dynamic"
|
|
1738
|
+
}
|
|
1739
|
+
]
|
|
1740
|
+
},
|
|
1711
1741
|
"LLMModel": {
|
|
1712
1742
|
"anyOf": [
|
|
1713
1743
|
{
|
|
@@ -2005,6 +2035,41 @@ const schema$1 = {
|
|
|
2005
2035
|
null
|
|
2006
2036
|
]
|
|
2007
2037
|
},
|
|
2038
|
+
"DynamicModelProfile": {
|
|
2039
|
+
"type": "object",
|
|
2040
|
+
"properties": {
|
|
2041
|
+
"summarize": {
|
|
2042
|
+
"$ref": "#/definitions/LLMModel"
|
|
2043
|
+
},
|
|
2044
|
+
"commit": {
|
|
2045
|
+
"$ref": "#/definitions/LLMModel"
|
|
2046
|
+
},
|
|
2047
|
+
"changelog": {
|
|
2048
|
+
"$ref": "#/definitions/LLMModel"
|
|
2049
|
+
},
|
|
2050
|
+
"review": {
|
|
2051
|
+
"$ref": "#/definitions/LLMModel"
|
|
2052
|
+
},
|
|
2053
|
+
"recap": {
|
|
2054
|
+
"$ref": "#/definitions/LLMModel"
|
|
2055
|
+
},
|
|
2056
|
+
"repair": {
|
|
2057
|
+
"$ref": "#/definitions/LLMModel"
|
|
2058
|
+
},
|
|
2059
|
+
"largeDiff": {
|
|
2060
|
+
"$ref": "#/definitions/LLMModel"
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
"additionalProperties": false
|
|
2064
|
+
},
|
|
2065
|
+
"DynamicModelPreference": {
|
|
2066
|
+
"type": "string",
|
|
2067
|
+
"enum": [
|
|
2068
|
+
"cost",
|
|
2069
|
+
"balanced",
|
|
2070
|
+
"quality"
|
|
2071
|
+
]
|
|
2072
|
+
},
|
|
2008
2073
|
"OllamaLLMService": {
|
|
2009
2074
|
"type": "object",
|
|
2010
2075
|
"additionalProperties": false,
|
|
@@ -2013,7 +2078,7 @@ const schema$1 = {
|
|
|
2013
2078
|
"$ref": "#/definitions/LLMProvider"
|
|
2014
2079
|
},
|
|
2015
2080
|
"model": {
|
|
2016
|
-
"$ref": "#/definitions/
|
|
2081
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
2017
2082
|
},
|
|
2018
2083
|
"endpoint": {
|
|
2019
2084
|
"type": "string"
|
|
@@ -2733,6 +2798,15 @@ const schema$1 = {
|
|
|
2733
2798
|
"type": "number",
|
|
2734
2799
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
2735
2800
|
"default": 3
|
|
2801
|
+
},
|
|
2802
|
+
"dynamicModels": {
|
|
2803
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
2804
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
2805
|
+
},
|
|
2806
|
+
"dynamicModelPreference": {
|
|
2807
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
2808
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
2809
|
+
"default": "balanced"
|
|
2736
2810
|
}
|
|
2737
2811
|
},
|
|
2738
2812
|
"required": [
|
|
@@ -2755,7 +2829,7 @@ const schema$1 = {
|
|
|
2755
2829
|
"$ref": "#/definitions/LLMProvider"
|
|
2756
2830
|
},
|
|
2757
2831
|
"model": {
|
|
2758
|
-
"$ref": "#/definitions/
|
|
2832
|
+
"$ref": "#/definitions/ConfiguredLLMModel"
|
|
2759
2833
|
},
|
|
2760
2834
|
"fields": {
|
|
2761
2835
|
"type": "object",
|
|
@@ -2885,6 +2959,15 @@ const schema$1 = {
|
|
|
2885
2959
|
"type": "number",
|
|
2886
2960
|
"description": "The maximum number of attempts for schema parsing with retry logic.",
|
|
2887
2961
|
"default": 3
|
|
2962
|
+
},
|
|
2963
|
+
"dynamicModels": {
|
|
2964
|
+
"$ref": "#/definitions/DynamicModelProfile",
|
|
2965
|
+
"description": "Optional task-to-model overrides used when model is set to \"dynamic\"."
|
|
2966
|
+
},
|
|
2967
|
+
"dynamicModelPreference": {
|
|
2968
|
+
"$ref": "#/definitions/DynamicModelPreference",
|
|
2969
|
+
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
2970
|
+
"default": "balanced"
|
|
2888
2971
|
}
|
|
2889
2972
|
},
|
|
2890
2973
|
"required": [
|
|
@@ -6951,11 +7034,11 @@ const ChangelogResponseSchema = objectType({
|
|
|
6951
7034
|
title: stringType(),
|
|
6952
7035
|
content: stringType(),
|
|
6953
7036
|
});
|
|
6954
|
-
const command$
|
|
7037
|
+
const command$5 = 'changelog';
|
|
6955
7038
|
/**
|
|
6956
7039
|
* Command line options via yargs
|
|
6957
7040
|
*/
|
|
6958
|
-
const options$
|
|
7041
|
+
const options$5 = {
|
|
6959
7042
|
range: {
|
|
6960
7043
|
type: 'string',
|
|
6961
7044
|
alias: 'r',
|
|
@@ -7002,8 +7085,8 @@ const options$4 = {
|
|
|
7002
7085
|
description: 'Toggle interactive mode',
|
|
7003
7086
|
},
|
|
7004
7087
|
};
|
|
7005
|
-
const builder$
|
|
7006
|
-
return yargs.options(options$
|
|
7088
|
+
const builder$5 = (yargs) => {
|
|
7089
|
+
return yargs.options(options$5).usage(getCommandUsageHeader(command$5));
|
|
7007
7090
|
};
|
|
7008
7091
|
|
|
7009
7092
|
/**
|
|
@@ -7203,6 +7286,212 @@ function getLlm(provider, model, config) {
|
|
|
7203
7286
|
}
|
|
7204
7287
|
}
|
|
7205
7288
|
|
|
7289
|
+
const OPENAI_DYNAMIC_DEFAULTS = {
|
|
7290
|
+
cost: {
|
|
7291
|
+
summarize: 'gpt-4.1-nano',
|
|
7292
|
+
commit: 'gpt-4.1-mini',
|
|
7293
|
+
changelog: 'gpt-4.1-mini',
|
|
7294
|
+
review: 'gpt-4.1-mini',
|
|
7295
|
+
recap: 'gpt-4.1-nano',
|
|
7296
|
+
repair: 'gpt-4.1-mini',
|
|
7297
|
+
largeDiff: 'gpt-4.1',
|
|
7298
|
+
},
|
|
7299
|
+
balanced: {
|
|
7300
|
+
summarize: 'gpt-4.1-mini',
|
|
7301
|
+
commit: 'gpt-4.1-mini',
|
|
7302
|
+
changelog: 'gpt-4.1',
|
|
7303
|
+
review: 'gpt-4.1',
|
|
7304
|
+
recap: 'gpt-4.1-mini',
|
|
7305
|
+
repair: 'gpt-4.1',
|
|
7306
|
+
largeDiff: 'gpt-4.1',
|
|
7307
|
+
},
|
|
7308
|
+
quality: {
|
|
7309
|
+
summarize: 'gpt-4.1-mini',
|
|
7310
|
+
commit: 'gpt-4.1',
|
|
7311
|
+
changelog: 'gpt-4.1',
|
|
7312
|
+
review: 'gpt-4.1',
|
|
7313
|
+
recap: 'gpt-4.1',
|
|
7314
|
+
repair: 'gpt-4.1',
|
|
7315
|
+
largeDiff: 'gpt-4.1',
|
|
7316
|
+
},
|
|
7317
|
+
};
|
|
7318
|
+
const ANTHROPIC_DYNAMIC_DEFAULTS = {
|
|
7319
|
+
cost: {
|
|
7320
|
+
summarize: 'claude-3-5-haiku-latest',
|
|
7321
|
+
commit: 'claude-3-5-haiku-latest',
|
|
7322
|
+
changelog: 'claude-3-5-sonnet-latest',
|
|
7323
|
+
review: 'claude-3-5-sonnet-latest',
|
|
7324
|
+
recap: 'claude-3-5-haiku-latest',
|
|
7325
|
+
repair: 'claude-3-5-sonnet-latest',
|
|
7326
|
+
largeDiff: 'claude-3-5-sonnet-latest',
|
|
7327
|
+
},
|
|
7328
|
+
balanced: {
|
|
7329
|
+
summarize: 'claude-3-5-haiku-latest',
|
|
7330
|
+
commit: 'claude-3-5-sonnet-latest',
|
|
7331
|
+
changelog: 'claude-3-5-sonnet-latest',
|
|
7332
|
+
review: 'claude-3-7-sonnet-latest',
|
|
7333
|
+
recap: 'claude-3-5-sonnet-latest',
|
|
7334
|
+
repair: 'claude-3-7-sonnet-latest',
|
|
7335
|
+
largeDiff: 'claude-3-7-sonnet-latest',
|
|
7336
|
+
},
|
|
7337
|
+
quality: {
|
|
7338
|
+
summarize: 'claude-3-5-sonnet-latest',
|
|
7339
|
+
commit: 'claude-3-7-sonnet-latest',
|
|
7340
|
+
changelog: 'claude-3-7-sonnet-latest',
|
|
7341
|
+
review: 'claude-sonnet-4-0',
|
|
7342
|
+
recap: 'claude-3-7-sonnet-latest',
|
|
7343
|
+
repair: 'claude-sonnet-4-0',
|
|
7344
|
+
largeDiff: 'claude-sonnet-4-0',
|
|
7345
|
+
},
|
|
7346
|
+
};
|
|
7347
|
+
const OLLAMA_DYNAMIC_DEFAULTS = {
|
|
7348
|
+
cost: {
|
|
7349
|
+
summarize: 'llama3.2:3b',
|
|
7350
|
+
commit: 'llama3.1:8b',
|
|
7351
|
+
changelog: 'llama3.1:8b',
|
|
7352
|
+
review: 'qwen2.5-coder:7b',
|
|
7353
|
+
recap: 'llama3.2:3b',
|
|
7354
|
+
repair: 'qwen2.5-coder:7b',
|
|
7355
|
+
largeDiff: 'qwen2.5-coder:14b',
|
|
7356
|
+
},
|
|
7357
|
+
balanced: {
|
|
7358
|
+
summarize: 'llama3.1:8b',
|
|
7359
|
+
commit: 'qwen2.5-coder:14b',
|
|
7360
|
+
changelog: 'qwen2.5-coder:14b',
|
|
7361
|
+
review: 'qwen2.5-coder:32b',
|
|
7362
|
+
recap: 'llama3.1:8b',
|
|
7363
|
+
repair: 'qwen2.5-coder:32b',
|
|
7364
|
+
largeDiff: 'qwen2.5-coder:32b',
|
|
7365
|
+
},
|
|
7366
|
+
quality: {
|
|
7367
|
+
summarize: 'qwen2.5-coder:14b',
|
|
7368
|
+
commit: 'qwen2.5-coder:32b',
|
|
7369
|
+
changelog: 'qwen2.5-coder:32b',
|
|
7370
|
+
review: 'qwen2.5-coder:32b',
|
|
7371
|
+
recap: 'qwen2.5-coder:14b',
|
|
7372
|
+
repair: 'qwen2.5-coder:32b',
|
|
7373
|
+
largeDiff: 'qwen2.5-coder:32b',
|
|
7374
|
+
},
|
|
7375
|
+
};
|
|
7376
|
+
const DYNAMIC_DEFAULTS = {
|
|
7377
|
+
openai: OPENAI_DYNAMIC_DEFAULTS,
|
|
7378
|
+
anthropic: ANTHROPIC_DYNAMIC_DEFAULTS,
|
|
7379
|
+
ollama: OLLAMA_DYNAMIC_DEFAULTS,
|
|
7380
|
+
};
|
|
7381
|
+
const DYNAMIC_MODEL_TASKS = [
|
|
7382
|
+
'summarize',
|
|
7383
|
+
'commit',
|
|
7384
|
+
'changelog',
|
|
7385
|
+
'review',
|
|
7386
|
+
'recap',
|
|
7387
|
+
'repair',
|
|
7388
|
+
'largeDiff',
|
|
7389
|
+
];
|
|
7390
|
+
function validateDynamicModelProfile(service) {
|
|
7391
|
+
const dynamicModels = service.dynamicModels;
|
|
7392
|
+
if (!dynamicModels)
|
|
7393
|
+
return;
|
|
7394
|
+
const unknownTasks = Object.keys(dynamicModels).filter((task) => !DYNAMIC_MODEL_TASKS.includes(task));
|
|
7395
|
+
if (unknownTasks.length > 0) {
|
|
7396
|
+
throw new LangChainConfigurationError(`Unknown dynamic model task(s): ${unknownTasks.join(', ')}. Supported tasks: ${DYNAMIC_MODEL_TASKS.join(', ')}`, { unknownTasks, supportedTasks: DYNAMIC_MODEL_TASKS });
|
|
7397
|
+
}
|
|
7398
|
+
Object.entries(dynamicModels).forEach(([task, model]) => {
|
|
7399
|
+
if (typeof model !== 'string' || model.trim() === '' || model === 'dynamic') {
|
|
7400
|
+
throw new LangChainConfigurationError(`Dynamic model override for '${task}' must be a concrete model name`, { task, model });
|
|
7401
|
+
}
|
|
7402
|
+
});
|
|
7403
|
+
}
|
|
7404
|
+
function resolveDynamicModel(config, task) {
|
|
7405
|
+
const service = config.service;
|
|
7406
|
+
validateDynamicModelProfile(service);
|
|
7407
|
+
if (service.model !== 'dynamic') {
|
|
7408
|
+
return service.model;
|
|
7409
|
+
}
|
|
7410
|
+
const preference = service.dynamicModelPreference || 'balanced';
|
|
7411
|
+
const providerDefaults = DYNAMIC_DEFAULTS[service.provider];
|
|
7412
|
+
const defaultModel = providerDefaults[preference]?.[task];
|
|
7413
|
+
return service.dynamicModels?.[task] || defaultModel;
|
|
7414
|
+
}
|
|
7415
|
+
function resolveDynamicService(config, task) {
|
|
7416
|
+
const model = resolveDynamicModel(config, task);
|
|
7417
|
+
return {
|
|
7418
|
+
...config.service,
|
|
7419
|
+
model,
|
|
7420
|
+
};
|
|
7421
|
+
}
|
|
7422
|
+
|
|
7423
|
+
const telemetryByCommand = new Map();
|
|
7424
|
+
function estimatePromptTokens(tokenizer, renderedPrompt) {
|
|
7425
|
+
if (!tokenizer)
|
|
7426
|
+
return undefined;
|
|
7427
|
+
try {
|
|
7428
|
+
return tokenizer(renderedPrompt);
|
|
7429
|
+
}
|
|
7430
|
+
catch {
|
|
7431
|
+
return undefined;
|
|
7432
|
+
}
|
|
7433
|
+
}
|
|
7434
|
+
function logLlmCall(logger, metadata) {
|
|
7435
|
+
if (!logger)
|
|
7436
|
+
return;
|
|
7437
|
+
recordLlmTelemetry(metadata);
|
|
7438
|
+
const fields = [
|
|
7439
|
+
`task=${metadata.task}`,
|
|
7440
|
+
metadata.command ? `command=${metadata.command}` : undefined,
|
|
7441
|
+
metadata.provider ? `provider=${metadata.provider}` : undefined,
|
|
7442
|
+
metadata.model ? `model=${metadata.model}` : undefined,
|
|
7443
|
+
metadata.retryAttempt ? `retryAttempt=${metadata.retryAttempt}` : undefined,
|
|
7444
|
+
metadata.promptTokens !== undefined ? `promptTokens=${metadata.promptTokens}` : undefined,
|
|
7445
|
+
metadata.elapsedMs !== undefined ? `elapsedMs=${metadata.elapsedMs}` : undefined,
|
|
7446
|
+
metadata.inputDocuments !== undefined ? `inputDocuments=${metadata.inputDocuments}` : undefined,
|
|
7447
|
+
metadata.inputChunks !== undefined ? `inputChunks=${metadata.inputChunks}` : undefined,
|
|
7448
|
+
metadata.parserType ? `parser=${metadata.parserType}` : undefined,
|
|
7449
|
+
metadata.variableKeys?.length ? `variableKeys=${metadata.variableKeys.join(',')}` : undefined,
|
|
7450
|
+
].filter(Boolean);
|
|
7451
|
+
logger.verbose(`[llm] ${fields.join(' ')}`, { color: 'cyan' });
|
|
7452
|
+
}
|
|
7453
|
+
function recordLlmTelemetry(metadata) {
|
|
7454
|
+
const command = metadata.command || 'unknown';
|
|
7455
|
+
const current = telemetryByCommand.get(command) || {
|
|
7456
|
+
calls: 0,
|
|
7457
|
+
promptTokens: 0,
|
|
7458
|
+
elapsedMs: 0,
|
|
7459
|
+
inputDocuments: 0,
|
|
7460
|
+
inputChunks: 0,
|
|
7461
|
+
tasks: new Set(),
|
|
7462
|
+
models: new Set(),
|
|
7463
|
+
};
|
|
7464
|
+
current.calls += 1;
|
|
7465
|
+
current.promptTokens += metadata.promptTokens || 0;
|
|
7466
|
+
current.elapsedMs += metadata.elapsedMs || 0;
|
|
7467
|
+
current.inputDocuments += metadata.inputDocuments || 0;
|
|
7468
|
+
current.inputChunks += metadata.inputChunks || 0;
|
|
7469
|
+
current.tasks.add(metadata.task);
|
|
7470
|
+
if (metadata.model) {
|
|
7471
|
+
current.models.add(metadata.model);
|
|
7472
|
+
}
|
|
7473
|
+
telemetryByCommand.set(command, current);
|
|
7474
|
+
}
|
|
7475
|
+
function logLlmTelemetrySummary(logger, command) {
|
|
7476
|
+
if (!logger)
|
|
7477
|
+
return;
|
|
7478
|
+
const summary = telemetryByCommand.get(command);
|
|
7479
|
+
if (!summary || summary.calls === 0)
|
|
7480
|
+
return;
|
|
7481
|
+
const fields = [
|
|
7482
|
+
`command=${command}`,
|
|
7483
|
+
`calls=${summary.calls}`,
|
|
7484
|
+
summary.promptTokens > 0 ? `promptTokens=${summary.promptTokens}` : undefined,
|
|
7485
|
+
summary.elapsedMs > 0 ? `elapsedMs=${summary.elapsedMs}` : undefined,
|
|
7486
|
+
summary.inputDocuments > 0 ? `inputDocuments=${summary.inputDocuments}` : undefined,
|
|
7487
|
+
summary.inputChunks > 0 ? `inputChunks=${summary.inputChunks}` : undefined,
|
|
7488
|
+
summary.tasks.size > 0 ? `tasks=${[...summary.tasks].join(',')}` : undefined,
|
|
7489
|
+
summary.models.size > 0 ? `models=${[...summary.models].join(',')}` : undefined,
|
|
7490
|
+
].filter(Boolean);
|
|
7491
|
+
logger.verbose(`[llm:summary] ${fields.join(' ')}`, { color: 'cyan' });
|
|
7492
|
+
telemetryByCommand.delete(command);
|
|
7493
|
+
}
|
|
7494
|
+
|
|
7206
7495
|
/**
|
|
7207
7496
|
* Creates a PromptTemplate from a template string or returns a fallback template.
|
|
7208
7497
|
*
|
|
@@ -7392,7 +7681,7 @@ function extractLlmInfo(llm) {
|
|
|
7392
7681
|
* @throws LangChainExecutionError if the chain execution fails or returns empty results
|
|
7393
7682
|
* @throws LangChainNetworkError if a network/connection error occurs
|
|
7394
7683
|
*/
|
|
7395
|
-
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, }) => {
|
|
7684
|
+
const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint, logger, tokenizer, metadata, }) => {
|
|
7396
7685
|
validateRequired(llm, 'llm', 'executeChain');
|
|
7397
7686
|
validateRequired(prompt, 'prompt', 'executeChain');
|
|
7398
7687
|
validateRequired(variables, 'variables', 'executeChain');
|
|
@@ -7410,8 +7699,21 @@ const executeChain = async ({ llm, prompt, variables, parser, provider, endpoint
|
|
|
7410
7699
|
const effectiveProvider = provider || llmInfo.provider;
|
|
7411
7700
|
const effectiveEndpoint = endpoint || llmInfo.endpoint;
|
|
7412
7701
|
try {
|
|
7702
|
+
const renderedPrompt = await prompt.format(variables);
|
|
7703
|
+
const promptTokens = estimatePromptTokens(tokenizer, renderedPrompt);
|
|
7413
7704
|
const chain = prompt.pipe(llm).pipe(parser);
|
|
7705
|
+
const startedAt = Date.now();
|
|
7414
7706
|
const result = (await chain.invoke(variables));
|
|
7707
|
+
const elapsedMs = Date.now() - startedAt;
|
|
7708
|
+
logLlmCall(logger, {
|
|
7709
|
+
task: metadata?.task || 'chain',
|
|
7710
|
+
provider: effectiveProvider,
|
|
7711
|
+
parserType: parser.constructor.name,
|
|
7712
|
+
variableKeys: Object.keys(variables),
|
|
7713
|
+
promptTokens,
|
|
7714
|
+
elapsedMs,
|
|
7715
|
+
...metadata,
|
|
7716
|
+
});
|
|
7415
7717
|
if (result === null || result === undefined) {
|
|
7416
7718
|
throw new LangChainExecutionError('executeChain: Chain execution returned null or undefined result', { variables, promptInputVariables: prompt.inputVariables });
|
|
7417
7719
|
}
|
|
@@ -8240,13 +8542,26 @@ function getPathFromFilePath(filePath) {
|
|
|
8240
8542
|
return filePath.split('/').slice(0, -1).join('/');
|
|
8241
8543
|
}
|
|
8242
8544
|
|
|
8243
|
-
async function summarize(documents$1, { chain, textSplitter, options }) {
|
|
8545
|
+
async function summarize(documents$1, { chain, textSplitter, options, logger, tokenizer, metadata }) {
|
|
8244
8546
|
const { returnIntermediateSteps = false } = options || {};
|
|
8245
8547
|
const docs = await textSplitter.splitDocuments(documents$1.map((doc) => new documents.Document(doc)));
|
|
8548
|
+
const promptTokens = tokenizer
|
|
8549
|
+
? docs.reduce((sum, doc) => sum + tokenizer(doc.pageContent), 0)
|
|
8550
|
+
: undefined;
|
|
8551
|
+
const startedAt = Date.now();
|
|
8246
8552
|
const res = await chain.invoke({
|
|
8247
8553
|
input_documents: docs,
|
|
8248
8554
|
returnIntermediateSteps,
|
|
8249
8555
|
});
|
|
8556
|
+
const elapsedMs = Date.now() - startedAt;
|
|
8557
|
+
logLlmCall(logger, {
|
|
8558
|
+
task: 'summarize',
|
|
8559
|
+
promptTokens,
|
|
8560
|
+
elapsedMs,
|
|
8561
|
+
inputDocuments: documents$1.length,
|
|
8562
|
+
inputChunks: docs.length,
|
|
8563
|
+
...metadata,
|
|
8564
|
+
});
|
|
8250
8565
|
if (res.error)
|
|
8251
8566
|
throw new Error(res.error);
|
|
8252
8567
|
return res.text && res.text.trim();
|
|
@@ -8255,7 +8570,7 @@ async function summarize(documents$1, { chain, textSplitter, options }) {
|
|
|
8255
8570
|
/**
|
|
8256
8571
|
* Summarize a single file diff that exceeds the token threshold.
|
|
8257
8572
|
*/
|
|
8258
|
-
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer }) {
|
|
8573
|
+
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, }) {
|
|
8259
8574
|
try {
|
|
8260
8575
|
const fileSummary = await summarize([
|
|
8261
8576
|
{
|
|
@@ -8268,6 +8583,12 @@ async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer }) {
|
|
|
8268
8583
|
], {
|
|
8269
8584
|
chain,
|
|
8270
8585
|
textSplitter,
|
|
8586
|
+
tokenizer,
|
|
8587
|
+
logger,
|
|
8588
|
+
metadata: {
|
|
8589
|
+
...metadata,
|
|
8590
|
+
task: 'summarize-large-file',
|
|
8591
|
+
},
|
|
8271
8592
|
options: {
|
|
8272
8593
|
returnIntermediateSteps: false,
|
|
8273
8594
|
},
|
|
@@ -8307,7 +8628,7 @@ async function processInWaves$1(items, processor, maxConcurrent) {
|
|
|
8307
8628
|
* @returns Array of file diffs with large files summarized
|
|
8308
8629
|
*/
|
|
8309
8630
|
async function summarizeLargeFiles(diffs, options) {
|
|
8310
|
-
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter } = options;
|
|
8631
|
+
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter, metadata } = options;
|
|
8311
8632
|
// Identify files that need summarization
|
|
8312
8633
|
const filesToSummarize = [];
|
|
8313
8634
|
const results = [...diffs];
|
|
@@ -8321,7 +8642,7 @@ async function summarizeLargeFiles(diffs, options) {
|
|
|
8321
8642
|
}
|
|
8322
8643
|
logger.verbose(`Pre-summarizing ${filesToSummarize.length} large file(s)...`, { color: 'blue' });
|
|
8323
8644
|
// Process large files in waves
|
|
8324
|
-
const summarizedFiles = await processInWaves$1(filesToSummarize, async ({ diff }) => summarizeFileDiff(diff, { chain, textSplitter, tokenizer }), maxConcurrent);
|
|
8645
|
+
const summarizedFiles = await processInWaves$1(filesToSummarize, async ({ diff }) => summarizeFileDiff(diff, { chain, textSplitter, tokenizer, logger, metadata }), maxConcurrent);
|
|
8325
8646
|
// Update results with summarized files
|
|
8326
8647
|
summarizedFiles.forEach((summarizedDiff, i) => {
|
|
8327
8648
|
const originalIndex = filesToSummarize[i].index;
|
|
@@ -8384,7 +8705,7 @@ function createDirectoryDiffs(node) {
|
|
|
8384
8705
|
/**
|
|
8385
8706
|
* Summarize a directory diff asynchronously.
|
|
8386
8707
|
*/
|
|
8387
|
-
async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenizer }) {
|
|
8708
|
+
async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenizer, logger, metadata }) {
|
|
8388
8709
|
try {
|
|
8389
8710
|
const directorySummary = await summarize(directory.diffs.map((diff) => ({
|
|
8390
8711
|
pageContent: diff.diff,
|
|
@@ -8395,6 +8716,12 @@ async function summarizeDirectoryDiff(directory, { chain, textSplitter, tokenize
|
|
|
8395
8716
|
})), {
|
|
8396
8717
|
chain,
|
|
8397
8718
|
textSplitter,
|
|
8719
|
+
tokenizer,
|
|
8720
|
+
logger,
|
|
8721
|
+
metadata: {
|
|
8722
|
+
...metadata,
|
|
8723
|
+
task: 'summarize-directory-diff',
|
|
8724
|
+
},
|
|
8398
8725
|
options: {
|
|
8399
8726
|
returnIntermediateSteps: true,
|
|
8400
8727
|
},
|
|
@@ -8438,7 +8765,7 @@ const defaultOutputCallback = (group) => {
|
|
|
8438
8765
|
* while maintaining predictable behavior.
|
|
8439
8766
|
*/
|
|
8440
8767
|
async function summarizeInWaves(directories, options) {
|
|
8441
|
-
const { totalTokenCount: initialTotal, maxTokens, minTokensForSummary, maxConcurrent, logger, chain, textSplitter, tokenizer, } = options;
|
|
8768
|
+
const { totalTokenCount: initialTotal, maxTokens, minTokensForSummary, maxConcurrent, logger, chain, textSplitter, tokenizer, metadata, } = options;
|
|
8442
8769
|
let totalTokenCount = initialTotal;
|
|
8443
8770
|
const results = [...directories];
|
|
8444
8771
|
// Create sorted indices by token count (descending) for prioritized processing
|
|
@@ -8470,7 +8797,7 @@ async function summarizeInWaves(directories, options) {
|
|
|
8470
8797
|
}
|
|
8471
8798
|
logger.verbose(`\nProcessing wave of ${wave.length} directories...`, { color: 'blue' });
|
|
8472
8799
|
// Process wave in parallel
|
|
8473
|
-
const waveResults = await Promise.all(wave.map((idx) => summarizeDirectoryDiff(results[idx], { chain, textSplitter, tokenizer })));
|
|
8800
|
+
const waveResults = await Promise.all(wave.map((idx) => summarizeDirectoryDiff(results[idx], { chain, textSplitter, tokenizer, logger, metadata })));
|
|
8474
8801
|
// Update results and recalculate total
|
|
8475
8802
|
waveResults.forEach((result, i) => {
|
|
8476
8803
|
const idx = wave[i];
|
|
@@ -8507,7 +8834,7 @@ async function summarizeInWaves(directories, options) {
|
|
|
8507
8834
|
* - Efficient parallel processing with predictable behavior
|
|
8508
8835
|
* - Early exit when under token budget
|
|
8509
8836
|
*/
|
|
8510
|
-
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, handleOutput = defaultOutputCallback, }) {
|
|
8837
|
+
async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 2048, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8511
8838
|
// Calculate maxFileTokens as 25% of maxTokens if not specified
|
|
8512
8839
|
const effectiveMaxFileTokens = maxFileTokens ?? Math.floor(maxTokens * 0.25);
|
|
8513
8840
|
// PHASE 1: Directory grouping & assessment
|
|
@@ -8535,6 +8862,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 204
|
|
|
8535
8862
|
logger,
|
|
8536
8863
|
chain,
|
|
8537
8864
|
textSplitter,
|
|
8865
|
+
metadata,
|
|
8538
8866
|
});
|
|
8539
8867
|
logger.stopSpinner('Files pre-processed').stopTimer();
|
|
8540
8868
|
directoryDiffs = createDirectoryDiffs(preprocessedNode);
|
|
@@ -8558,6 +8886,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger, maxTokens = 204
|
|
|
8558
8886
|
chain,
|
|
8559
8887
|
textSplitter,
|
|
8560
8888
|
tokenizer,
|
|
8889
|
+
metadata,
|
|
8561
8890
|
});
|
|
8562
8891
|
logger.stopSpinner(`Diffs Consolidated`).stopTimer();
|
|
8563
8892
|
return summarizedDiffs.map(handleOutput).join('');
|
|
@@ -10499,7 +10828,7 @@ function isObject(subject) {
|
|
|
10499
10828
|
}
|
|
10500
10829
|
|
|
10501
10830
|
|
|
10502
|
-
function toArray(sequence) {
|
|
10831
|
+
function toArray$1(sequence) {
|
|
10503
10832
|
if (Array.isArray(sequence)) return sequence;
|
|
10504
10833
|
else if (isNothing(sequence)) return [];
|
|
10505
10834
|
|
|
@@ -10541,7 +10870,7 @@ function isNegativeZero(number) {
|
|
|
10541
10870
|
|
|
10542
10871
|
var isNothing_1 = isNothing;
|
|
10543
10872
|
var isObject_1 = isObject;
|
|
10544
|
-
var toArray_1 = toArray;
|
|
10873
|
+
var toArray_1 = toArray$1;
|
|
10545
10874
|
var repeat_1 = repeat;
|
|
10546
10875
|
var isNegativeZero_1 = isNegativeZero;
|
|
10547
10876
|
var extend_1 = extend;
|
|
@@ -11512,7 +11841,7 @@ for (var i = 0; i < 256; i++) {
|
|
|
11512
11841
|
simpleEscapeMap[i] = simpleEscapeSequence(i);
|
|
11513
11842
|
}
|
|
11514
11843
|
|
|
11515
|
-
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, }, }) {
|
|
11844
|
+
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, metadata, }, }) {
|
|
11516
11845
|
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
11517
11846
|
const summarizationChain = loadSummarizationChain(model, {
|
|
11518
11847
|
type: 'map_reduce',
|
|
@@ -11540,6 +11869,7 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
11540
11869
|
textSplitter,
|
|
11541
11870
|
chain: summarizationChain,
|
|
11542
11871
|
logger,
|
|
11872
|
+
metadata,
|
|
11543
11873
|
});
|
|
11544
11874
|
logger.stopTimer(`\nSummary generated for ${changes.length} staged files`, { color: 'green' });
|
|
11545
11875
|
return summary;
|
|
@@ -11612,11 +11942,14 @@ async function processInWaves(items, processor, maxConcurrent = 6) {
|
|
|
11612
11942
|
}
|
|
11613
11943
|
return results;
|
|
11614
11944
|
}
|
|
11615
|
-
const handler$
|
|
11945
|
+
const handler$5 = async (argv, logger) => {
|
|
11616
11946
|
const config = loadConfig(argv);
|
|
11617
11947
|
const git = getRepo();
|
|
11618
11948
|
const key = getApiKeyForModel(config);
|
|
11619
|
-
const { provider
|
|
11949
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
11950
|
+
const changelogService = resolveDynamicService(config, 'changelog');
|
|
11951
|
+
const summaryService = resolveDynamicService(config, argv.withDiff || argv.onlyDiff ? 'largeDiff' : 'summarize');
|
|
11952
|
+
const model = changelogService.model;
|
|
11620
11953
|
const exclusiveOptions = [
|
|
11621
11954
|
argv.branch ? '--branch' : null,
|
|
11622
11955
|
argv.tag ? '--tag' : null,
|
|
@@ -11630,7 +11963,8 @@ const handler$4 = async (argv, logger) => {
|
|
|
11630
11963
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
11631
11964
|
process.exit(1);
|
|
11632
11965
|
}
|
|
11633
|
-
const llm = getLlm(provider, model, config);
|
|
11966
|
+
const llm = getLlm(provider, model, { ...config, service: changelogService });
|
|
11967
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
11634
11968
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
11635
11969
|
const INTERACTIVE = isInteractive(config);
|
|
11636
11970
|
if (INTERACTIVE) {
|
|
@@ -11700,12 +12034,17 @@ const handler$4 = async (argv, logger) => {
|
|
|
11700
12034
|
options: {
|
|
11701
12035
|
tokenizer,
|
|
11702
12036
|
git,
|
|
11703
|
-
llm,
|
|
12037
|
+
llm: summaryLlm,
|
|
11704
12038
|
logger,
|
|
11705
12039
|
maxTokens: config.service.tokenLimit,
|
|
11706
12040
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
11707
12041
|
maxFileTokens: config.service.maxFileTokens,
|
|
11708
12042
|
maxConcurrent: config.service.maxConcurrent,
|
|
12043
|
+
metadata: {
|
|
12044
|
+
command: 'changelog',
|
|
12045
|
+
provider,
|
|
12046
|
+
model: String(summaryService.model),
|
|
12047
|
+
},
|
|
11709
12048
|
},
|
|
11710
12049
|
})
|
|
11711
12050
|
: undefined,
|
|
@@ -11726,12 +12065,17 @@ const handler$4 = async (argv, logger) => {
|
|
|
11726
12065
|
options: {
|
|
11727
12066
|
tokenizer,
|
|
11728
12067
|
git,
|
|
11729
|
-
llm,
|
|
12068
|
+
llm: summaryLlm,
|
|
11730
12069
|
logger,
|
|
11731
12070
|
maxTokens: config.service.tokenLimit,
|
|
11732
12071
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
11733
12072
|
maxFileTokens: config.service.maxFileTokens,
|
|
11734
12073
|
maxConcurrent: config.service.maxConcurrent,
|
|
12074
|
+
metadata: {
|
|
12075
|
+
command: 'changelog',
|
|
12076
|
+
provider,
|
|
12077
|
+
model: String(summaryService.model),
|
|
12078
|
+
},
|
|
11735
12079
|
},
|
|
11736
12080
|
});
|
|
11737
12081
|
return `## Diff for ${data.branch}\n\n${diffSummary}`;
|
|
@@ -11798,6 +12142,14 @@ const handler$4 = async (argv, logger) => {
|
|
|
11798
12142
|
prompt,
|
|
11799
12143
|
variables: budgetedPrompt.variables,
|
|
11800
12144
|
parser,
|
|
12145
|
+
logger,
|
|
12146
|
+
tokenizer,
|
|
12147
|
+
metadata: {
|
|
12148
|
+
task: argv.withDiff ? 'changelog-with-diff' : argv.onlyDiff ? 'changelog-only-diff' : 'changelog',
|
|
12149
|
+
command: 'changelog',
|
|
12150
|
+
provider,
|
|
12151
|
+
model: String(model),
|
|
12152
|
+
},
|
|
11801
12153
|
});
|
|
11802
12154
|
const branchName = await getCurrentBranchName({ git });
|
|
11803
12155
|
const ticketId = extractTicketIdFromBranchName(branchName);
|
|
@@ -11821,14 +12173,15 @@ const handler$4 = async (argv, logger) => {
|
|
|
11821
12173
|
},
|
|
11822
12174
|
mode: MODE,
|
|
11823
12175
|
});
|
|
12176
|
+
logLlmTelemetrySummary(logger, 'changelog');
|
|
11824
12177
|
};
|
|
11825
12178
|
|
|
11826
12179
|
var changelog = {
|
|
11827
|
-
command: command$
|
|
12180
|
+
command: command$5,
|
|
11828
12181
|
desc: 'Generate a changelog from current or target branch, provided commit range, or since the last tag.',
|
|
11829
|
-
builder: builder$
|
|
11830
|
-
handler: commandExecutor(handler$
|
|
11831
|
-
options: options$
|
|
12182
|
+
builder: builder$5,
|
|
12183
|
+
handler: commandExecutor(handler$5),
|
|
12184
|
+
options: options$5,
|
|
11832
12185
|
};
|
|
11833
12186
|
|
|
11834
12187
|
const conventionalTypeRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?:/;
|
|
@@ -11845,11 +12198,11 @@ const ConventionalCommitMessageResponseSchema = objectType({
|
|
|
11845
12198
|
body: stringType().describe("Body of the commit message")
|
|
11846
12199
|
// .max(280, "Body must be 280 characters or less"),
|
|
11847
12200
|
}).describe("Object with Conventional Commit message 'title' and 'body' adhering to Conventional Commits specification");
|
|
11848
|
-
const command$
|
|
12201
|
+
const command$4 = 'commit';
|
|
11849
12202
|
/**
|
|
11850
12203
|
* Command line options via yargs
|
|
11851
12204
|
*/
|
|
11852
|
-
const options$
|
|
12205
|
+
const options$4 = {
|
|
11853
12206
|
i: {
|
|
11854
12207
|
alias: 'interactive',
|
|
11855
12208
|
description: 'Toggle interactive mode',
|
|
@@ -11905,9 +12258,24 @@ const options$3 = {
|
|
|
11905
12258
|
default: false,
|
|
11906
12259
|
alias: 'n',
|
|
11907
12260
|
},
|
|
12261
|
+
split: {
|
|
12262
|
+
description: 'Group staged changes into multiple related commit proposals',
|
|
12263
|
+
type: 'boolean',
|
|
12264
|
+
default: false,
|
|
12265
|
+
},
|
|
12266
|
+
plan: {
|
|
12267
|
+
description: 'Only print a commit split plan without changing git state',
|
|
12268
|
+
type: 'boolean',
|
|
12269
|
+
default: false,
|
|
12270
|
+
},
|
|
12271
|
+
apply: {
|
|
12272
|
+
description: 'Apply a generated file-level or hunk-level commit split plan and create commits',
|
|
12273
|
+
type: 'boolean',
|
|
12274
|
+
default: false,
|
|
12275
|
+
},
|
|
11908
12276
|
};
|
|
11909
|
-
const builder$
|
|
11910
|
-
return yargs.options(options$
|
|
12277
|
+
const builder$4 = (yargs) => {
|
|
12278
|
+
return yargs.options(options$4).usage(getCommandUsageHeader(command$4));
|
|
11911
12279
|
};
|
|
11912
12280
|
|
|
11913
12281
|
/**
|
|
@@ -11921,15 +12289,24 @@ const builder$3 = (yargs) => {
|
|
|
11921
12289
|
* @returns Parsed result matching the schema type
|
|
11922
12290
|
*/
|
|
11923
12291
|
async function executeChainWithSchema(schema, llm, prompt, variables, options = {}) {
|
|
11924
|
-
const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, ...parserOptions } = options;
|
|
12292
|
+
const { retryOptions = { maxAttempts: 3 }, fallbackParser, onFallback, logger, tokenizer, metadata, ...parserOptions } = options;
|
|
11925
12293
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11926
12294
|
const parser = createSchemaParser(schema, llm, parserOptions);
|
|
12295
|
+
let attempt = 0;
|
|
11927
12296
|
const operation = async () => {
|
|
12297
|
+
attempt++;
|
|
11928
12298
|
const result = await executeChain({
|
|
11929
12299
|
llm,
|
|
11930
12300
|
prompt,
|
|
11931
12301
|
variables,
|
|
11932
12302
|
parser,
|
|
12303
|
+
logger,
|
|
12304
|
+
tokenizer,
|
|
12305
|
+
metadata: {
|
|
12306
|
+
task: 'schema-chain',
|
|
12307
|
+
...metadata,
|
|
12308
|
+
retryAttempt: attempt,
|
|
12309
|
+
},
|
|
11933
12310
|
});
|
|
11934
12311
|
return result;
|
|
11935
12312
|
};
|
|
@@ -11946,6 +12323,12 @@ async function executeChainWithSchema(schema, llm, prompt, variables, options =
|
|
|
11946
12323
|
prompt,
|
|
11947
12324
|
variables,
|
|
11948
12325
|
parser: new output_parsers.StringOutputParser(),
|
|
12326
|
+
logger,
|
|
12327
|
+
tokenizer,
|
|
12328
|
+
metadata: {
|
|
12329
|
+
task: 'schema-chain-fallback',
|
|
12330
|
+
...metadata,
|
|
12331
|
+
},
|
|
11949
12332
|
});
|
|
11950
12333
|
const fallbackText = typeof fallbackResult === 'string' ? fallbackResult : String(fallbackResult);
|
|
11951
12334
|
return fallbackParser(fallbackText);
|
|
@@ -12408,17 +12791,345 @@ async function noResult$2({ git, logger }) {
|
|
|
12408
12791
|
}
|
|
12409
12792
|
}
|
|
12410
12793
|
|
|
12411
|
-
const
|
|
12794
|
+
const CommitSplitPlanSchema = objectType({
|
|
12795
|
+
groups: arrayType(objectType({
|
|
12796
|
+
title: stringType().min(1),
|
|
12797
|
+
body: stringType().optional(),
|
|
12798
|
+
rationale: stringType().optional(),
|
|
12799
|
+
files: arrayType(stringType()),
|
|
12800
|
+
hunks: arrayType(stringType()),
|
|
12801
|
+
})
|
|
12802
|
+
.refine((group) => group.files.length > 0 || group.hunks.length > 0, {
|
|
12803
|
+
message: 'Each group must include at least one file or hunk',
|
|
12804
|
+
}))
|
|
12805
|
+
.min(1),
|
|
12806
|
+
});
|
|
12807
|
+
const COMMIT_SPLIT_PROMPT = prompts$1.PromptTemplate.fromTemplate(`You are helping split staged git changes into a small sequence of coherent commits.
|
|
12808
|
+
|
|
12809
|
+
Return ONLY valid JSON matching this schema:
|
|
12810
|
+
{{
|
|
12811
|
+
"groups": [
|
|
12812
|
+
{{
|
|
12813
|
+
"title": "conventional commit style title",
|
|
12814
|
+
"body": "commit body",
|
|
12815
|
+
"rationale": "why these files belong together",
|
|
12816
|
+
"files": ["relative/path.ts"],
|
|
12817
|
+
"hunks": ["relative/path.ts::hunk-1"]
|
|
12818
|
+
}}
|
|
12819
|
+
]
|
|
12820
|
+
}}
|
|
12821
|
+
|
|
12822
|
+
Rules:
|
|
12823
|
+
- Use each staged file exactly once.
|
|
12824
|
+
- If a file has hunk IDs and contains unrelated changes, assign every hunk ID exactly once instead of assigning the whole file.
|
|
12825
|
+
- Do not list the same file in "files" when assigning that file through "hunks".
|
|
12826
|
+
- Only use file paths listed in the staged file inventory.
|
|
12827
|
+
- Only use hunk IDs listed in the staged hunk inventory.
|
|
12828
|
+
- Prefer 2-5 commits unless the changes are truly all one topic.
|
|
12829
|
+
- Keep commit titles concise and understandable.
|
|
12830
|
+
- Do not invent files.
|
|
12831
|
+
|
|
12832
|
+
Staged file inventory:
|
|
12833
|
+
{file_inventory}
|
|
12834
|
+
|
|
12835
|
+
Staged hunk inventory:
|
|
12836
|
+
{hunk_inventory}
|
|
12837
|
+
|
|
12838
|
+
Condensed staged diff:
|
|
12839
|
+
{summary}
|
|
12840
|
+
|
|
12841
|
+
Additional context:
|
|
12842
|
+
{additional_context}`);
|
|
12843
|
+
function isCommitSplitCommand(argv) {
|
|
12844
|
+
return Boolean(argv.split || argv.plan || argv.apply || argv._.includes('split'));
|
|
12845
|
+
}
|
|
12846
|
+
function formatCommitSplitPlan(plan) {
|
|
12847
|
+
return plan.groups
|
|
12848
|
+
.map((group, index) => {
|
|
12849
|
+
const body = group.body ? `\n\n${group.body}` : '';
|
|
12850
|
+
const rationale = group.rationale ? `\n\nRationale: ${group.rationale}` : '';
|
|
12851
|
+
const files = (group.files || []).map((file) => `- ${file}`).join('\n');
|
|
12852
|
+
const hunks = (group.hunks || []).map((hunk) => `- ${hunk}`).join('\n');
|
|
12853
|
+
const sections = [
|
|
12854
|
+
files ? `Files:\n${files}` : undefined,
|
|
12855
|
+
hunks ? `Hunks:\n${hunks}` : undefined,
|
|
12856
|
+
].filter(Boolean);
|
|
12857
|
+
return `## ${index + 1}. ${group.title}${body}${rationale}\n\n${sections.join('\n\n')}`;
|
|
12858
|
+
})
|
|
12859
|
+
.join('\n\n---\n\n');
|
|
12860
|
+
}
|
|
12861
|
+
function getStagedFileSet(changes) {
|
|
12862
|
+
return new Set(changes.map((change) => change.filePath));
|
|
12863
|
+
}
|
|
12864
|
+
function getGroupFiles(group) {
|
|
12865
|
+
return group.files || [];
|
|
12866
|
+
}
|
|
12867
|
+
function getGroupHunks(group) {
|
|
12868
|
+
return group.hunks || [];
|
|
12869
|
+
}
|
|
12870
|
+
function hunkHeader(hunk) {
|
|
12871
|
+
return `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
|
|
12872
|
+
}
|
|
12873
|
+
function hunkPreview(hunk) {
|
|
12874
|
+
return hunk.lines
|
|
12875
|
+
.filter((line) => line.startsWith('+') || line.startsWith('-'))
|
|
12876
|
+
.slice(0, 6)
|
|
12877
|
+
.join('\n');
|
|
12878
|
+
}
|
|
12879
|
+
async function collectHunkInventory(staged, git) {
|
|
12880
|
+
const hunks = [];
|
|
12881
|
+
const byId = new Map();
|
|
12882
|
+
const byFile = new Map();
|
|
12883
|
+
for (const change of staged) {
|
|
12884
|
+
if (change.status !== 'modified') {
|
|
12885
|
+
continue;
|
|
12886
|
+
}
|
|
12887
|
+
const diff$1 = await git.diff(['--staged', '--', change.filePath]);
|
|
12888
|
+
const [patch] = diff.parsePatch(diff$1);
|
|
12889
|
+
if (!patch || patch.hunks.length === 0) {
|
|
12890
|
+
continue;
|
|
12891
|
+
}
|
|
12892
|
+
patch.hunks.forEach((hunk, index) => {
|
|
12893
|
+
const stagedHunk = {
|
|
12894
|
+
id: `${change.filePath}::hunk-${index + 1}`,
|
|
12895
|
+
filePath: change.filePath,
|
|
12896
|
+
patch,
|
|
12897
|
+
hunk,
|
|
12898
|
+
header: hunkHeader(hunk),
|
|
12899
|
+
preview: hunkPreview(hunk),
|
|
12900
|
+
};
|
|
12901
|
+
hunks.push(stagedHunk);
|
|
12902
|
+
byId.set(stagedHunk.id, stagedHunk);
|
|
12903
|
+
byFile.set(change.filePath, [...(byFile.get(change.filePath) || []), stagedHunk]);
|
|
12904
|
+
});
|
|
12905
|
+
}
|
|
12906
|
+
return { hunks, byId, byFile };
|
|
12907
|
+
}
|
|
12908
|
+
function formatHunkInventory(inventory) {
|
|
12909
|
+
if (inventory.hunks.length === 0) {
|
|
12910
|
+
return 'No hunk-level inventory available. Use file-level groups.';
|
|
12911
|
+
}
|
|
12912
|
+
return inventory.hunks
|
|
12913
|
+
.map((hunk) => {
|
|
12914
|
+
const preview = hunk.preview ? `\n${hunk.preview}` : '';
|
|
12915
|
+
return `- ${hunk.id}: ${hunk.header}${preview}`;
|
|
12916
|
+
})
|
|
12917
|
+
.join('\n');
|
|
12918
|
+
}
|
|
12919
|
+
function validatePlanForStagedFiles(plan, staged, hunkInventory) {
|
|
12920
|
+
const stagedFiles = getStagedFileSet(staged);
|
|
12921
|
+
const seen = new Set();
|
|
12922
|
+
const seenHunks = new Set();
|
|
12923
|
+
const unknown = [];
|
|
12924
|
+
const duplicate = [];
|
|
12925
|
+
const unknownHunks = [];
|
|
12926
|
+
const duplicateHunks = [];
|
|
12927
|
+
plan.groups.forEach((group) => {
|
|
12928
|
+
getGroupFiles(group).forEach((file) => {
|
|
12929
|
+
if (!stagedFiles.has(file)) {
|
|
12930
|
+
unknown.push(file);
|
|
12931
|
+
return;
|
|
12932
|
+
}
|
|
12933
|
+
if (seen.has(file)) {
|
|
12934
|
+
duplicate.push(file);
|
|
12935
|
+
return;
|
|
12936
|
+
}
|
|
12937
|
+
seen.add(file);
|
|
12938
|
+
});
|
|
12939
|
+
getGroupHunks(group).forEach((hunkId) => {
|
|
12940
|
+
const hunk = hunkInventory?.byId.get(hunkId);
|
|
12941
|
+
if (!hunk) {
|
|
12942
|
+
unknownHunks.push(hunkId);
|
|
12943
|
+
return;
|
|
12944
|
+
}
|
|
12945
|
+
if (seenHunks.has(hunkId)) {
|
|
12946
|
+
duplicateHunks.push(hunkId);
|
|
12947
|
+
return;
|
|
12948
|
+
}
|
|
12949
|
+
seenHunks.add(hunkId);
|
|
12950
|
+
});
|
|
12951
|
+
});
|
|
12952
|
+
const hunkCoveredFiles = new Set([...seenHunks].map((hunkId) => hunkInventory?.byId.get(hunkId)?.filePath));
|
|
12953
|
+
const mixedFiles = [...seen].filter((file) => hunkCoveredFiles.has(file));
|
|
12954
|
+
const partiallyCoveredFiles = [...hunkCoveredFiles]
|
|
12955
|
+
.filter((file) => Boolean(file))
|
|
12956
|
+
.filter((file) => {
|
|
12957
|
+
const fileHunks = hunkInventory?.byFile.get(file) || [];
|
|
12958
|
+
return fileHunks.some((hunk) => !seenHunks.has(hunk.id));
|
|
12959
|
+
});
|
|
12960
|
+
const missing = [...stagedFiles].filter((file) => !seen.has(file) && !hunkCoveredFiles.has(file));
|
|
12961
|
+
if (unknown.length ||
|
|
12962
|
+
duplicate.length ||
|
|
12963
|
+
unknownHunks.length ||
|
|
12964
|
+
duplicateHunks.length ||
|
|
12965
|
+
mixedFiles.length ||
|
|
12966
|
+
partiallyCoveredFiles.length ||
|
|
12967
|
+
missing.length) {
|
|
12968
|
+
throw new Error([
|
|
12969
|
+
unknown.length ? `unknown files: ${unknown.join(', ')}` : undefined,
|
|
12970
|
+
duplicate.length ? `duplicate files: ${duplicate.join(', ')}` : undefined,
|
|
12971
|
+
unknownHunks.length ? `unknown hunks: ${unknownHunks.join(', ')}` : undefined,
|
|
12972
|
+
duplicateHunks.length ? `duplicate hunks: ${duplicateHunks.join(', ')}` : undefined,
|
|
12973
|
+
mixedFiles.length ? `files assigned both as whole files and hunks: ${mixedFiles.join(', ')}` : undefined,
|
|
12974
|
+
partiallyCoveredFiles.length
|
|
12975
|
+
? `files with only some hunks assigned: ${partiallyCoveredFiles.join(', ')}`
|
|
12976
|
+
: undefined,
|
|
12977
|
+
missing.length ? `missing files: ${missing.join(', ')}` : undefined,
|
|
12978
|
+
]
|
|
12979
|
+
.filter(Boolean)
|
|
12980
|
+
.join('; '));
|
|
12981
|
+
}
|
|
12982
|
+
}
|
|
12983
|
+
function assertNoUnstagedOverlap(plan, changes, hunkInventory) {
|
|
12984
|
+
const hunkFiles = new Set(plan.groups.flatMap((group) => getGroupHunks(group)
|
|
12985
|
+
.map((hunkId) => hunkInventory?.byId.get(hunkId)?.filePath)
|
|
12986
|
+
.filter((file) => Boolean(file))));
|
|
12987
|
+
const plannedFiles = new Set(plan.groups
|
|
12988
|
+
.flatMap((group) => getGroupFiles(group))
|
|
12989
|
+
.filter((file) => !hunkFiles.has(file)));
|
|
12990
|
+
const overlapping = [...(changes.unstaged || []), ...(changes.untracked || [])]
|
|
12991
|
+
.map((change) => change.filePath)
|
|
12992
|
+
.filter((file) => plannedFiles.has(file));
|
|
12993
|
+
if (overlapping.length > 0) {
|
|
12994
|
+
throw new Error(`Cannot apply split plan because these files also have unstaged or untracked changes: ${overlapping.join(', ')}`);
|
|
12995
|
+
}
|
|
12996
|
+
}
|
|
12997
|
+
function buildPatchForHunks(hunks) {
|
|
12998
|
+
const byFile = new Map();
|
|
12999
|
+
hunks.forEach((hunk) => {
|
|
13000
|
+
byFile.set(hunk.filePath, [...(byFile.get(hunk.filePath) || []), hunk]);
|
|
13001
|
+
});
|
|
13002
|
+
return [...byFile.values()]
|
|
13003
|
+
.map((fileHunks) => {
|
|
13004
|
+
const [firstHunk] = fileHunks;
|
|
13005
|
+
return diff.formatPatch({
|
|
13006
|
+
...firstHunk.patch,
|
|
13007
|
+
hunks: fileHunks.map((hunk) => hunk.hunk),
|
|
13008
|
+
});
|
|
13009
|
+
})
|
|
13010
|
+
.join('\n');
|
|
13011
|
+
}
|
|
13012
|
+
async function applyPatchToIndex(patch, git) {
|
|
13013
|
+
const cwd = await git.revparse(['--show-toplevel']);
|
|
13014
|
+
await new Promise((resolve, reject) => {
|
|
13015
|
+
const child = child_process.spawn('git', ['apply', '--cached', '-'], {
|
|
13016
|
+
cwd,
|
|
13017
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
13018
|
+
});
|
|
13019
|
+
let stderr = '';
|
|
13020
|
+
child.stderr.on('data', (chunk) => {
|
|
13021
|
+
stderr += String(chunk);
|
|
13022
|
+
});
|
|
13023
|
+
child.on('error', reject);
|
|
13024
|
+
child.on('close', (code) => {
|
|
13025
|
+
if (code === 0) {
|
|
13026
|
+
resolve();
|
|
13027
|
+
return;
|
|
13028
|
+
}
|
|
13029
|
+
reject(new Error(`Failed to apply hunk patch to index: ${stderr.trim()}`));
|
|
13030
|
+
});
|
|
13031
|
+
child.stdin.write(patch);
|
|
13032
|
+
child.stdin.end();
|
|
13033
|
+
});
|
|
13034
|
+
}
|
|
13035
|
+
async function applyCommitSplitPlan({ plan, changes, hunkInventory, git, logger, noVerify, }) {
|
|
13036
|
+
validatePlanForStagedFiles(plan, changes.staged, hunkInventory);
|
|
13037
|
+
assertNoUnstagedOverlap(plan, changes, hunkInventory);
|
|
13038
|
+
await git.raw(['reset']);
|
|
13039
|
+
for (const group of plan.groups) {
|
|
13040
|
+
const groupFiles = getGroupFiles(group);
|
|
13041
|
+
const groupHunks = getGroupHunks(group).map((hunkId) => hunkInventory.byId.get(hunkId));
|
|
13042
|
+
if (groupFiles.length > 0) {
|
|
13043
|
+
await git.add(groupFiles);
|
|
13044
|
+
}
|
|
13045
|
+
if (groupHunks.length > 0) {
|
|
13046
|
+
const patch = buildPatchForHunks(groupHunks.filter((hunk) => Boolean(hunk)));
|
|
13047
|
+
await applyPatchToIndex(patch, git);
|
|
13048
|
+
}
|
|
13049
|
+
await createCommit(`${group.title}\n\n${group.body}`.trim(), git, undefined, { noVerify });
|
|
13050
|
+
logger.verbose(`Created split commit: ${group.title}`, { color: 'green' });
|
|
13051
|
+
}
|
|
13052
|
+
return `Created ${plan.groups.length} split commit(s).`;
|
|
13053
|
+
}
|
|
13054
|
+
async function handleCommitSplit({ argv, config, git, logger, tokenizer, llm, }) {
|
|
13055
|
+
const changes = await getChanges({
|
|
13056
|
+
git,
|
|
13057
|
+
options: {
|
|
13058
|
+
ignoredFiles: config.ignoredFiles || undefined,
|
|
13059
|
+
ignoredExtensions: config.ignoredExtensions || undefined,
|
|
13060
|
+
},
|
|
13061
|
+
});
|
|
13062
|
+
if (changes.staged.length === 0) {
|
|
13063
|
+
return 'No staged changes found.';
|
|
13064
|
+
}
|
|
13065
|
+
const hunkInventory = await collectHunkInventory(changes.staged, git);
|
|
13066
|
+
const summary = await fileChangeParser({
|
|
13067
|
+
changes: changes.staged,
|
|
13068
|
+
commit: '--staged',
|
|
13069
|
+
options: {
|
|
13070
|
+
tokenizer,
|
|
13071
|
+
git,
|
|
13072
|
+
llm,
|
|
13073
|
+
logger,
|
|
13074
|
+
maxTokens: config.service.tokenLimit,
|
|
13075
|
+
minTokensForSummary: config.service.minTokensForSummary,
|
|
13076
|
+
maxFileTokens: config.service.maxFileTokens,
|
|
13077
|
+
maxConcurrent: config.service.maxConcurrent,
|
|
13078
|
+
metadata: {
|
|
13079
|
+
command: 'commit',
|
|
13080
|
+
provider: config.service.provider,
|
|
13081
|
+
model: String(config.service.model),
|
|
13082
|
+
},
|
|
13083
|
+
},
|
|
13084
|
+
});
|
|
13085
|
+
const fileInventory = changes.staged
|
|
13086
|
+
.map((change) => `- ${change.filePath}: ${change.status} - ${change.summary}`)
|
|
13087
|
+
.join('\n');
|
|
13088
|
+
const hunkInventoryText = formatHunkInventory(hunkInventory);
|
|
13089
|
+
const plan = await executeChainWithSchema(CommitSplitPlanSchema, llm, COMMIT_SPLIT_PROMPT, {
|
|
13090
|
+
file_inventory: fileInventory,
|
|
13091
|
+
hunk_inventory: hunkInventoryText,
|
|
13092
|
+
summary,
|
|
13093
|
+
additional_context: argv.additional || '',
|
|
13094
|
+
}, {
|
|
13095
|
+
logger,
|
|
13096
|
+
tokenizer,
|
|
13097
|
+
metadata: {
|
|
13098
|
+
task: 'commit-split-plan',
|
|
13099
|
+
command: 'commit',
|
|
13100
|
+
provider: config.service.provider,
|
|
13101
|
+
model: String(config.service.model),
|
|
13102
|
+
},
|
|
13103
|
+
});
|
|
13104
|
+
validatePlanForStagedFiles(plan, changes.staged, hunkInventory);
|
|
13105
|
+
if (argv.apply) {
|
|
13106
|
+
return await applyCommitSplitPlan({
|
|
13107
|
+
plan,
|
|
13108
|
+
changes,
|
|
13109
|
+
hunkInventory,
|
|
13110
|
+
git,
|
|
13111
|
+
logger,
|
|
13112
|
+
noVerify: argv.noVerify || config.noVerify || false,
|
|
13113
|
+
});
|
|
13114
|
+
}
|
|
13115
|
+
return formatCommitSplitPlan(plan);
|
|
13116
|
+
}
|
|
13117
|
+
|
|
13118
|
+
const handler$4 = async (argv, logger) => {
|
|
12412
13119
|
const git = getRepo();
|
|
12413
13120
|
const config = loadConfig(argv);
|
|
12414
13121
|
const key = getApiKeyForModel(config);
|
|
12415
|
-
const { provider
|
|
13122
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
13123
|
+
const commitService = resolveDynamicService(config, 'commit');
|
|
13124
|
+
const summaryService = resolveDynamicService(config, 'summarize');
|
|
13125
|
+
const model = commitService.model;
|
|
12416
13126
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
12417
13127
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
12418
13128
|
process.exit(1);
|
|
12419
13129
|
}
|
|
12420
13130
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
12421
|
-
const llm = getLlm(provider, model, config);
|
|
13131
|
+
const llm = getLlm(provider, model, { ...config, service: commitService });
|
|
13132
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
12422
13133
|
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
12423
13134
|
if (INTERACTIVE) {
|
|
12424
13135
|
if (!config.hideCocoBanner) {
|
|
@@ -12436,6 +13147,22 @@ const handler$3 = async (argv, logger) => {
|
|
|
12436
13147
|
logger.verbose(`→ ${provider} (${model})`, {
|
|
12437
13148
|
color: 'green',
|
|
12438
13149
|
});
|
|
13150
|
+
if (isCommitSplitCommand(argv)) {
|
|
13151
|
+
const splitResult = await handleCommitSplit({
|
|
13152
|
+
argv,
|
|
13153
|
+
config,
|
|
13154
|
+
git,
|
|
13155
|
+
logger,
|
|
13156
|
+
tokenizer,
|
|
13157
|
+
llm,
|
|
13158
|
+
});
|
|
13159
|
+
await handleResult({
|
|
13160
|
+
result: splitResult,
|
|
13161
|
+
mode: config.mode || 'stdout',
|
|
13162
|
+
});
|
|
13163
|
+
logLlmTelemetrySummary(logger, 'commit');
|
|
13164
|
+
return;
|
|
13165
|
+
}
|
|
12439
13166
|
const USE_CONVENTIONAL_COMMITS = config.conventionalCommits || argv.conventional;
|
|
12440
13167
|
async function factory() {
|
|
12441
13168
|
if (config.noDiff) {
|
|
@@ -12473,12 +13200,17 @@ const handler$3 = async (argv, logger) => {
|
|
|
12473
13200
|
options: {
|
|
12474
13201
|
tokenizer,
|
|
12475
13202
|
git,
|
|
12476
|
-
llm,
|
|
13203
|
+
llm: summaryLlm,
|
|
12477
13204
|
logger,
|
|
12478
13205
|
maxTokens: config.service.tokenLimit,
|
|
12479
13206
|
minTokensForSummary: config.service.minTokensForSummary,
|
|
12480
13207
|
maxFileTokens: config.service.maxFileTokens,
|
|
12481
13208
|
maxConcurrent: config.service.maxConcurrent,
|
|
13209
|
+
metadata: {
|
|
13210
|
+
command: 'commit',
|
|
13211
|
+
provider,
|
|
13212
|
+
model: String(summaryService.model),
|
|
13213
|
+
},
|
|
12482
13214
|
},
|
|
12483
13215
|
});
|
|
12484
13216
|
}
|
|
@@ -12628,6 +13360,14 @@ IMPORTANT RULES:
|
|
|
12628
13360
|
logger.verbose(`Rendered prompt exceeded token budget; trimmed summary to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
12629
13361
|
}
|
|
12630
13362
|
const commitMsg = await executeChainWithSchema(schema, llm, prompt, budgetedPrompt.variables, {
|
|
13363
|
+
logger,
|
|
13364
|
+
tokenizer,
|
|
13365
|
+
metadata: {
|
|
13366
|
+
task: USE_CONVENTIONAL_COMMITS ? 'commit-message-conventional' : 'commit-message',
|
|
13367
|
+
command: 'commit',
|
|
13368
|
+
provider,
|
|
13369
|
+
model: String(model),
|
|
13370
|
+
},
|
|
12631
13371
|
retryOptions: {
|
|
12632
13372
|
maxAttempts,
|
|
12633
13373
|
onRetry: (attempt, error) => {
|
|
@@ -12836,29 +13576,30 @@ IMPORTANT RULES:
|
|
|
12836
13576
|
},
|
|
12837
13577
|
mode: MODE,
|
|
12838
13578
|
});
|
|
13579
|
+
logLlmTelemetrySummary(logger, 'commit');
|
|
12839
13580
|
};
|
|
12840
13581
|
|
|
12841
13582
|
var commit = {
|
|
12842
|
-
command: command$
|
|
13583
|
+
command: command$4,
|
|
12843
13584
|
desc: 'Summarize the staged changes in a commit message.',
|
|
12844
|
-
builder: builder$
|
|
12845
|
-
handler: commandExecutor(handler$
|
|
12846
|
-
options: options$
|
|
13585
|
+
builder: builder$4,
|
|
13586
|
+
handler: commandExecutor(handler$4),
|
|
13587
|
+
options: options$4,
|
|
12847
13588
|
};
|
|
12848
13589
|
|
|
12849
|
-
const command$
|
|
13590
|
+
const command$3 = 'init';
|
|
12850
13591
|
/**
|
|
12851
13592
|
* Command line options via yargs
|
|
12852
13593
|
*/
|
|
12853
|
-
const options$
|
|
13594
|
+
const options$3 = {
|
|
12854
13595
|
scope: {
|
|
12855
13596
|
type: 'string',
|
|
12856
13597
|
description: 'configure coco for the current user or project?',
|
|
12857
13598
|
choices: ['global', 'project'],
|
|
12858
13599
|
},
|
|
12859
13600
|
};
|
|
12860
|
-
const builder$
|
|
12861
|
-
return yargs.options(options$
|
|
13601
|
+
const builder$3 = (yargs) => {
|
|
13602
|
+
return yargs.options(options$3).usage(getCommandUsageHeader(command$3));
|
|
12862
13603
|
};
|
|
12863
13604
|
|
|
12864
13605
|
/**
|
|
@@ -13209,7 +13950,7 @@ const questions = {
|
|
|
13209
13950
|
}),
|
|
13210
13951
|
};
|
|
13211
13952
|
|
|
13212
|
-
const handler$
|
|
13953
|
+
const handler$3 = async (argv, logger) => {
|
|
13213
13954
|
const options = loadConfig(argv);
|
|
13214
13955
|
logger.log(LOGO);
|
|
13215
13956
|
let scope = options?.scope;
|
|
@@ -13380,8 +14121,287 @@ async function installCommitlintPackages(scope, logger) {
|
|
|
13380
14121
|
}
|
|
13381
14122
|
|
|
13382
14123
|
var init = {
|
|
13383
|
-
command: command$
|
|
14124
|
+
command: command$3,
|
|
13384
14125
|
desc: 'install & configure coco globally or for the current project',
|
|
14126
|
+
builder: builder$3,
|
|
14127
|
+
handler: commandExecutor(handler$3),
|
|
14128
|
+
options: options$3,
|
|
14129
|
+
};
|
|
14130
|
+
|
|
14131
|
+
const command$2 = 'log';
|
|
14132
|
+
const options$2 = {
|
|
14133
|
+
all: {
|
|
14134
|
+
description: 'Show commits from all local and remote refs',
|
|
14135
|
+
type: 'boolean',
|
|
14136
|
+
default: false,
|
|
14137
|
+
},
|
|
14138
|
+
author: {
|
|
14139
|
+
description: 'Filter commits by author',
|
|
14140
|
+
type: 'string',
|
|
14141
|
+
},
|
|
14142
|
+
branch: {
|
|
14143
|
+
description: 'Show commits reachable from a branch or ref',
|
|
14144
|
+
type: 'string',
|
|
14145
|
+
alias: 'b',
|
|
14146
|
+
},
|
|
14147
|
+
commit: {
|
|
14148
|
+
description: 'Show details and changed files for a single commit',
|
|
14149
|
+
type: 'string',
|
|
14150
|
+
alias: 'c',
|
|
14151
|
+
},
|
|
14152
|
+
format: {
|
|
14153
|
+
description: 'Output format',
|
|
14154
|
+
choices: ['table', 'json'],
|
|
14155
|
+
default: 'table',
|
|
14156
|
+
},
|
|
14157
|
+
limit: {
|
|
14158
|
+
description: 'Maximum number of commits to show',
|
|
14159
|
+
type: 'number',
|
|
14160
|
+
default: 30,
|
|
14161
|
+
alias: 'n',
|
|
14162
|
+
},
|
|
14163
|
+
noMerges: {
|
|
14164
|
+
description: 'Exclude merge commits',
|
|
14165
|
+
type: 'boolean',
|
|
14166
|
+
default: false,
|
|
14167
|
+
},
|
|
14168
|
+
path: {
|
|
14169
|
+
description: 'Filter commits by changed path',
|
|
14170
|
+
type: 'array',
|
|
14171
|
+
},
|
|
14172
|
+
since: {
|
|
14173
|
+
description: 'Show commits more recent than a date',
|
|
14174
|
+
type: 'string',
|
|
14175
|
+
},
|
|
14176
|
+
until: {
|
|
14177
|
+
description: 'Show commits older than a date',
|
|
14178
|
+
type: 'string',
|
|
14179
|
+
},
|
|
14180
|
+
};
|
|
14181
|
+
const builder$2 = (yargs) => {
|
|
14182
|
+
return yargs.options(options$2).usage(getCommandUsageHeader(command$2));
|
|
14183
|
+
};
|
|
14184
|
+
|
|
14185
|
+
const FIELD_SEPARATOR = '\x1f';
|
|
14186
|
+
const LOG_FORMAT = `%x1f%h%x1f%H%x1f%ad%x1f%an%x1f%d%x1f%s`;
|
|
14187
|
+
const DETAIL_FORMAT = `%H%x1f%h%x1f%ad%x1f%an%x1f%d%x1f%s%x1f%b`;
|
|
14188
|
+
function toArray(value) {
|
|
14189
|
+
if (!value) {
|
|
14190
|
+
return [];
|
|
14191
|
+
}
|
|
14192
|
+
return Array.isArray(value) ? value : [value];
|
|
14193
|
+
}
|
|
14194
|
+
function normalizeLimit(limit) {
|
|
14195
|
+
if (!limit || Number.isNaN(limit) || limit < 1) {
|
|
14196
|
+
return 30;
|
|
14197
|
+
}
|
|
14198
|
+
return Math.floor(limit);
|
|
14199
|
+
}
|
|
14200
|
+
function cleanRefs(refs) {
|
|
14201
|
+
const trimmed = refs.trim();
|
|
14202
|
+
if (!trimmed) {
|
|
14203
|
+
return [];
|
|
14204
|
+
}
|
|
14205
|
+
return trimmed
|
|
14206
|
+
.replace(/^\(/, '')
|
|
14207
|
+
.replace(/\)$/, '')
|
|
14208
|
+
.split(',')
|
|
14209
|
+
.map((ref) => ref.trim())
|
|
14210
|
+
.filter(Boolean);
|
|
14211
|
+
}
|
|
14212
|
+
function parseLogOutput(output) {
|
|
14213
|
+
return output
|
|
14214
|
+
.split('\n')
|
|
14215
|
+
.map((line) => line.trimEnd())
|
|
14216
|
+
.filter((line) => line.includes(FIELD_SEPARATOR))
|
|
14217
|
+
.map((line) => {
|
|
14218
|
+
const [graph, shortHash, hash, date, author, refs, message] = line.split(FIELD_SEPARATOR);
|
|
14219
|
+
return {
|
|
14220
|
+
graph: graph.trimEnd(),
|
|
14221
|
+
shortHash,
|
|
14222
|
+
hash,
|
|
14223
|
+
date,
|
|
14224
|
+
author,
|
|
14225
|
+
refs: cleanRefs(refs),
|
|
14226
|
+
message,
|
|
14227
|
+
};
|
|
14228
|
+
});
|
|
14229
|
+
}
|
|
14230
|
+
function parseNameStatus(output) {
|
|
14231
|
+
return output
|
|
14232
|
+
.split('\n')
|
|
14233
|
+
.map((line) => line.trim())
|
|
14234
|
+
.filter(Boolean)
|
|
14235
|
+
.map((line) => {
|
|
14236
|
+
const [status, firstPath, secondPath] = line.split('\t');
|
|
14237
|
+
if (status.startsWith('R') || status.startsWith('C')) {
|
|
14238
|
+
return {
|
|
14239
|
+
status,
|
|
14240
|
+
oldPath: firstPath,
|
|
14241
|
+
path: secondPath,
|
|
14242
|
+
};
|
|
14243
|
+
}
|
|
14244
|
+
return {
|
|
14245
|
+
status,
|
|
14246
|
+
path: firstPath,
|
|
14247
|
+
};
|
|
14248
|
+
});
|
|
14249
|
+
}
|
|
14250
|
+
function parseCommitDetail(metadata, files) {
|
|
14251
|
+
const [hash, shortHash, date, author, refs, message, body = ''] = metadata
|
|
14252
|
+
.trimEnd()
|
|
14253
|
+
.split(FIELD_SEPARATOR);
|
|
14254
|
+
return {
|
|
14255
|
+
shortHash,
|
|
14256
|
+
hash,
|
|
14257
|
+
date,
|
|
14258
|
+
author,
|
|
14259
|
+
refs: cleanRefs(refs),
|
|
14260
|
+
message,
|
|
14261
|
+
body: body.trim(),
|
|
14262
|
+
files: parseNameStatus(files),
|
|
14263
|
+
};
|
|
14264
|
+
}
|
|
14265
|
+
function truncate(value, width) {
|
|
14266
|
+
if (value.length <= width) {
|
|
14267
|
+
return value;
|
|
14268
|
+
}
|
|
14269
|
+
return `${value.slice(0, Math.max(0, width - 1))}.`;
|
|
14270
|
+
}
|
|
14271
|
+
function pad(value, width) {
|
|
14272
|
+
return truncate(value, width).padEnd(width, ' ');
|
|
14273
|
+
}
|
|
14274
|
+
function formatLogTable(entries) {
|
|
14275
|
+
if (entries.length === 0) {
|
|
14276
|
+
return 'No commits found.';
|
|
14277
|
+
}
|
|
14278
|
+
const rows = entries.map((entry) => {
|
|
14279
|
+
const refs = entry.refs.join(', ');
|
|
14280
|
+
return [
|
|
14281
|
+
pad(entry.graph || '*', 8),
|
|
14282
|
+
pad(entry.shortHash, 9),
|
|
14283
|
+
pad(entry.date, 10),
|
|
14284
|
+
pad(entry.author, 18),
|
|
14285
|
+
pad(refs, 26),
|
|
14286
|
+
entry.message,
|
|
14287
|
+
].join(' ');
|
|
14288
|
+
});
|
|
14289
|
+
return [
|
|
14290
|
+
[
|
|
14291
|
+
pad('Graph', 8),
|
|
14292
|
+
pad('Commit', 9),
|
|
14293
|
+
pad('Date', 10),
|
|
14294
|
+
pad('Author', 18),
|
|
14295
|
+
pad('Refs', 26),
|
|
14296
|
+
'Message',
|
|
14297
|
+
].join(' '),
|
|
14298
|
+
...rows,
|
|
14299
|
+
].join('\n');
|
|
14300
|
+
}
|
|
14301
|
+
function formatCommitDetail(detail, format) {
|
|
14302
|
+
if (format === 'json') {
|
|
14303
|
+
return JSON.stringify(detail, null, 2);
|
|
14304
|
+
}
|
|
14305
|
+
const refs = detail.refs.length ? ` (${detail.refs.join(', ')})` : '';
|
|
14306
|
+
const body = detail.body ? `\n\n${detail.body}` : '';
|
|
14307
|
+
const files = detail.files.length
|
|
14308
|
+
? detail.files
|
|
14309
|
+
.map((file) => {
|
|
14310
|
+
if (file.oldPath) {
|
|
14311
|
+
return ` ${file.status} ${file.oldPath} -> ${file.path}`;
|
|
14312
|
+
}
|
|
14313
|
+
return ` ${file.status} ${file.path}`;
|
|
14314
|
+
})
|
|
14315
|
+
.join('\n')
|
|
14316
|
+
: ' No changed files found.';
|
|
14317
|
+
return [
|
|
14318
|
+
`commit ${detail.hash}${refs}`,
|
|
14319
|
+
`Author: ${detail.author}`,
|
|
14320
|
+
`Date: ${detail.date}`,
|
|
14321
|
+
'',
|
|
14322
|
+
` ${detail.message}${body}`,
|
|
14323
|
+
'',
|
|
14324
|
+
'Changed files:',
|
|
14325
|
+
files,
|
|
14326
|
+
].join('\n');
|
|
14327
|
+
}
|
|
14328
|
+
function buildLogArgs(argv) {
|
|
14329
|
+
const args = [
|
|
14330
|
+
'log',
|
|
14331
|
+
'--graph',
|
|
14332
|
+
'--decorate=short',
|
|
14333
|
+
'--date=short',
|
|
14334
|
+
'--color=never',
|
|
14335
|
+
`--max-count=${normalizeLimit(argv.limit)}`,
|
|
14336
|
+
`--pretty=format:${LOG_FORMAT}`,
|
|
14337
|
+
];
|
|
14338
|
+
if (argv.noMerges) {
|
|
14339
|
+
args.push('--no-merges');
|
|
14340
|
+
}
|
|
14341
|
+
if (argv.author) {
|
|
14342
|
+
args.push(`--author=${argv.author}`);
|
|
14343
|
+
}
|
|
14344
|
+
if (argv.since) {
|
|
14345
|
+
args.push(`--since=${argv.since}`);
|
|
14346
|
+
}
|
|
14347
|
+
if (argv.until) {
|
|
14348
|
+
args.push(`--until=${argv.until}`);
|
|
14349
|
+
}
|
|
14350
|
+
if (argv.all) {
|
|
14351
|
+
args.push('--all');
|
|
14352
|
+
}
|
|
14353
|
+
else if (argv.branch) {
|
|
14354
|
+
args.push(argv.branch);
|
|
14355
|
+
}
|
|
14356
|
+
const paths = toArray(argv.path);
|
|
14357
|
+
if (paths.length > 0) {
|
|
14358
|
+
args.push('--', ...paths);
|
|
14359
|
+
}
|
|
14360
|
+
return args;
|
|
14361
|
+
}
|
|
14362
|
+
async function getCommitDetail(git, commit) {
|
|
14363
|
+
const metadata = await git.raw([
|
|
14364
|
+
'show',
|
|
14365
|
+
'--no-patch',
|
|
14366
|
+
'--date=short',
|
|
14367
|
+
'--color=never',
|
|
14368
|
+
`--pretty=format:${DETAIL_FORMAT}`,
|
|
14369
|
+
commit,
|
|
14370
|
+
]);
|
|
14371
|
+
const files = await git.raw([
|
|
14372
|
+
'show',
|
|
14373
|
+
'--name-status',
|
|
14374
|
+
'--format=',
|
|
14375
|
+
'--find-renames',
|
|
14376
|
+
'--color=never',
|
|
14377
|
+
commit,
|
|
14378
|
+
]);
|
|
14379
|
+
return parseCommitDetail(metadata, files);
|
|
14380
|
+
}
|
|
14381
|
+
const handler$2 = async (argv) => {
|
|
14382
|
+
const git = getRepo();
|
|
14383
|
+
const mode = argv.interactive ? 'interactive' : 'stdout';
|
|
14384
|
+
const format = argv.format === 'json' ? 'json' : 'table';
|
|
14385
|
+
if (argv.commit) {
|
|
14386
|
+
const detail = await getCommitDetail(git, argv.commit);
|
|
14387
|
+
await handleResult({
|
|
14388
|
+
result: formatCommitDetail(detail, format),
|
|
14389
|
+
mode,
|
|
14390
|
+
});
|
|
14391
|
+
return;
|
|
14392
|
+
}
|
|
14393
|
+
const output = await git.raw(buildLogArgs(argv));
|
|
14394
|
+
const entries = parseLogOutput(output);
|
|
14395
|
+
const result = format === 'json' ? JSON.stringify(entries, null, 2) : formatLogTable(entries);
|
|
14396
|
+
await handleResult({
|
|
14397
|
+
result,
|
|
14398
|
+
mode,
|
|
14399
|
+
});
|
|
14400
|
+
};
|
|
14401
|
+
|
|
14402
|
+
var log = {
|
|
14403
|
+
command: command$2,
|
|
14404
|
+
desc: 'Explore commit history with a branch graph, filters, and commit details.',
|
|
13385
14405
|
builder: builder$2,
|
|
13386
14406
|
handler: commandExecutor(handler$2),
|
|
13387
14407
|
options: options$2,
|
|
@@ -13459,13 +14479,17 @@ const handler$1 = async (argv, logger) => {
|
|
|
13459
14479
|
const git = getRepo();
|
|
13460
14480
|
const config = loadConfig(argv);
|
|
13461
14481
|
const key = getApiKeyForModel(config);
|
|
13462
|
-
const { provider
|
|
14482
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
14483
|
+
const recapService = resolveDynamicService(config, 'recap');
|
|
14484
|
+
const summaryService = resolveDynamicService(config, 'summarize');
|
|
14485
|
+
const model = recapService.model;
|
|
13463
14486
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
13464
14487
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
13465
14488
|
process.exit(1);
|
|
13466
14489
|
}
|
|
13467
14490
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
13468
|
-
const llm = getLlm(provider, model, config);
|
|
14491
|
+
const llm = getLlm(provider, model, { ...config, service: recapService });
|
|
14492
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
13469
14493
|
const INTERACTIVE = argv.interactive || isInteractive(config);
|
|
13470
14494
|
if (INTERACTIVE) {
|
|
13471
14495
|
if (!config.hideCocoBanner) {
|
|
@@ -13496,19 +14520,49 @@ const handler$1 = async (argv, logger) => {
|
|
|
13496
14520
|
const unstagedChanges = await fileChangeParser({
|
|
13497
14521
|
changes: unstaged || [],
|
|
13498
14522
|
commit: '--unstaged',
|
|
13499
|
-
options: {
|
|
14523
|
+
options: {
|
|
14524
|
+
tokenizer,
|
|
14525
|
+
git,
|
|
14526
|
+
llm: summaryLlm,
|
|
14527
|
+
logger,
|
|
14528
|
+
metadata: {
|
|
14529
|
+
command: 'recap',
|
|
14530
|
+
provider,
|
|
14531
|
+
model: String(summaryService.model),
|
|
14532
|
+
},
|
|
14533
|
+
},
|
|
13500
14534
|
});
|
|
13501
14535
|
const unstagedResponse = `Unstaged changes:\n${unstagedChanges}`;
|
|
13502
14536
|
const untrackedChanges = await fileChangeParser({
|
|
13503
14537
|
changes: untracked || [],
|
|
13504
14538
|
commit: '--untracked',
|
|
13505
|
-
options: {
|
|
14539
|
+
options: {
|
|
14540
|
+
tokenizer,
|
|
14541
|
+
git,
|
|
14542
|
+
llm: summaryLlm,
|
|
14543
|
+
logger,
|
|
14544
|
+
metadata: {
|
|
14545
|
+
command: 'recap',
|
|
14546
|
+
provider,
|
|
14547
|
+
model: String(summaryService.model),
|
|
14548
|
+
},
|
|
14549
|
+
},
|
|
13506
14550
|
});
|
|
13507
14551
|
const untrackedResponse = `Untracked changes:\n${untrackedChanges}`;
|
|
13508
14552
|
const stagedChanges = await fileChangeParser({
|
|
13509
14553
|
changes: staged,
|
|
13510
14554
|
commit: '--staged',
|
|
13511
|
-
options: {
|
|
14555
|
+
options: {
|
|
14556
|
+
tokenizer,
|
|
14557
|
+
git,
|
|
14558
|
+
llm: summaryLlm,
|
|
14559
|
+
logger,
|
|
14560
|
+
metadata: {
|
|
14561
|
+
command: 'recap',
|
|
14562
|
+
provider,
|
|
14563
|
+
model: String(summaryService.model),
|
|
14564
|
+
},
|
|
14565
|
+
},
|
|
13512
14566
|
});
|
|
13513
14567
|
const stagedResponse = `Staged changes:\n${stagedChanges}`;
|
|
13514
14568
|
return [unstagedResponse, untrackedResponse, stagedResponse];
|
|
@@ -13543,7 +14597,17 @@ const handler$1 = async (argv, logger) => {
|
|
|
13543
14597
|
const branchChanges = await fileChangeParser({
|
|
13544
14598
|
changes: changes.staged,
|
|
13545
14599
|
commit: baseBranch,
|
|
13546
|
-
options: {
|
|
14600
|
+
options: {
|
|
14601
|
+
tokenizer,
|
|
14602
|
+
git,
|
|
14603
|
+
llm: summaryLlm,
|
|
14604
|
+
logger,
|
|
14605
|
+
metadata: {
|
|
14606
|
+
command: 'recap',
|
|
14607
|
+
provider,
|
|
14608
|
+
model: String(summaryService.model),
|
|
14609
|
+
},
|
|
14610
|
+
},
|
|
13547
14611
|
});
|
|
13548
14612
|
return [branchChanges];
|
|
13549
14613
|
default:
|
|
@@ -13588,15 +14652,34 @@ const handler$1 = async (argv, logger) => {
|
|
|
13588
14652
|
try {
|
|
13589
14653
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13590
14654
|
const parser = createSchemaParser(RecapLlmResponseSchema, llm);
|
|
14655
|
+
const variables = {
|
|
14656
|
+
changes: context,
|
|
14657
|
+
format_instructions: formatInstructions,
|
|
14658
|
+
timeframe,
|
|
14659
|
+
};
|
|
14660
|
+
const budgetedPrompt = await enforcePromptBudget({
|
|
14661
|
+
prompt,
|
|
14662
|
+
variables,
|
|
14663
|
+
tokenizer,
|
|
14664
|
+
maxTokens: config.service.tokenLimit || 2048,
|
|
14665
|
+
summaryKey: 'changes',
|
|
14666
|
+
});
|
|
14667
|
+
if (budgetedPrompt.truncated) {
|
|
14668
|
+
logger.verbose(`Rendered prompt exceeded token budget; trimmed changes to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
14669
|
+
}
|
|
13591
14670
|
const response = await executeChain({
|
|
13592
14671
|
llm,
|
|
13593
14672
|
prompt,
|
|
13594
|
-
variables:
|
|
13595
|
-
changes: context,
|
|
13596
|
-
format_instructions: formatInstructions,
|
|
13597
|
-
timeframe,
|
|
13598
|
-
},
|
|
14673
|
+
variables: budgetedPrompt.variables,
|
|
13599
14674
|
parser,
|
|
14675
|
+
logger,
|
|
14676
|
+
tokenizer,
|
|
14677
|
+
metadata: {
|
|
14678
|
+
task: 'recap',
|
|
14679
|
+
command: 'recap',
|
|
14680
|
+
provider,
|
|
14681
|
+
model: String(model),
|
|
14682
|
+
},
|
|
13600
14683
|
});
|
|
13601
14684
|
return response ? `${response.title}\n\n${response.summary}` : 'no response';
|
|
13602
14685
|
}
|
|
@@ -13633,6 +14716,7 @@ ${errorMessage}
|
|
|
13633
14716
|
},
|
|
13634
14717
|
mode: MODE,
|
|
13635
14718
|
});
|
|
14719
|
+
logLlmTelemetrySummary(logger, 'recap');
|
|
13636
14720
|
};
|
|
13637
14721
|
|
|
13638
14722
|
var recap = {
|
|
@@ -13988,13 +15072,17 @@ const handler = async (argv, logger) => {
|
|
|
13988
15072
|
const git = getRepo();
|
|
13989
15073
|
const config = loadConfig(argv);
|
|
13990
15074
|
const key = getApiKeyForModel(config);
|
|
13991
|
-
const { provider
|
|
15075
|
+
const { provider } = getModelAndProviderFromConfig(config);
|
|
15076
|
+
const reviewService = resolveDynamicService(config, 'review');
|
|
15077
|
+
const summaryService = resolveDynamicService(config, argv.branch ? 'largeDiff' : 'summarize');
|
|
15078
|
+
const model = reviewService.model;
|
|
13992
15079
|
if (config.service.authentication.type !== 'None' && !key) {
|
|
13993
15080
|
logger.log(`No API Key found. 🗝️🚪`, { color: 'red' });
|
|
13994
15081
|
process.exit(1);
|
|
13995
15082
|
}
|
|
13996
15083
|
const tokenizer = await getTokenCounter(provider === 'openai' ? model : 'gpt-4o');
|
|
13997
|
-
const llm = getLlm(provider, model, config);
|
|
15084
|
+
const llm = getLlm(provider, model, { ...config, service: reviewService });
|
|
15085
|
+
const summaryLlm = getLlm(provider, summaryService.model, { ...config, service: summaryService });
|
|
13998
15086
|
const INTERACTIVE = isInteractive(config);
|
|
13999
15087
|
if (INTERACTIVE) {
|
|
14000
15088
|
if (!config.hideCocoBanner) {
|
|
@@ -14018,7 +15106,17 @@ const handler = async (argv, logger) => {
|
|
|
14018
15106
|
const branchChanges = await fileChangeParser({
|
|
14019
15107
|
changes: diff.staged,
|
|
14020
15108
|
commit: `--branch-diff-${argv.branch}`,
|
|
14021
|
-
options: {
|
|
15109
|
+
options: {
|
|
15110
|
+
tokenizer,
|
|
15111
|
+
git,
|
|
15112
|
+
llm: summaryLlm,
|
|
15113
|
+
logger,
|
|
15114
|
+
metadata: {
|
|
15115
|
+
command: 'review',
|
|
15116
|
+
provider,
|
|
15117
|
+
model: String(summaryService.model),
|
|
15118
|
+
},
|
|
15119
|
+
},
|
|
14022
15120
|
});
|
|
14023
15121
|
return [branchChanges];
|
|
14024
15122
|
}
|
|
@@ -14040,19 +15138,49 @@ const handler = async (argv, logger) => {
|
|
|
14040
15138
|
const unstagedChanges = await fileChangeParser({
|
|
14041
15139
|
changes: unstaged || [],
|
|
14042
15140
|
commit: '--unstaged',
|
|
14043
|
-
options: {
|
|
15141
|
+
options: {
|
|
15142
|
+
tokenizer,
|
|
15143
|
+
git,
|
|
15144
|
+
llm: summaryLlm,
|
|
15145
|
+
logger,
|
|
15146
|
+
metadata: {
|
|
15147
|
+
command: 'review',
|
|
15148
|
+
provider,
|
|
15149
|
+
model: String(summaryService.model),
|
|
15150
|
+
},
|
|
15151
|
+
},
|
|
14044
15152
|
});
|
|
14045
15153
|
const unstagedResponse = `Unstaged changes:\n${unstagedChanges}`;
|
|
14046
15154
|
const untrackedChanges = await fileChangeParser({
|
|
14047
15155
|
changes: untracked || [],
|
|
14048
15156
|
commit: '--untracked',
|
|
14049
|
-
options: {
|
|
15157
|
+
options: {
|
|
15158
|
+
tokenizer,
|
|
15159
|
+
git,
|
|
15160
|
+
llm: summaryLlm,
|
|
15161
|
+
logger,
|
|
15162
|
+
metadata: {
|
|
15163
|
+
command: 'review',
|
|
15164
|
+
provider,
|
|
15165
|
+
model: String(summaryService.model),
|
|
15166
|
+
},
|
|
15167
|
+
},
|
|
14050
15168
|
});
|
|
14051
15169
|
const untrackedResponse = `Untracked changes:\n${untrackedChanges}`;
|
|
14052
15170
|
const stagedChanges = await fileChangeParser({
|
|
14053
15171
|
changes: staged,
|
|
14054
15172
|
commit: '--staged',
|
|
14055
|
-
options: {
|
|
15173
|
+
options: {
|
|
15174
|
+
tokenizer,
|
|
15175
|
+
git,
|
|
15176
|
+
llm: summaryLlm,
|
|
15177
|
+
logger,
|
|
15178
|
+
metadata: {
|
|
15179
|
+
command: 'review',
|
|
15180
|
+
provider,
|
|
15181
|
+
model: String(summaryService.model),
|
|
15182
|
+
},
|
|
15183
|
+
},
|
|
14056
15184
|
});
|
|
14057
15185
|
const stagedResponse = `Staged changes:\n${stagedChanges}`;
|
|
14058
15186
|
return [unstagedResponse, untrackedResponse, stagedResponse];
|
|
@@ -14092,14 +15220,33 @@ const handler = async (argv, logger) => {
|
|
|
14092
15220
|
variables: REVIEW_PROMPT.inputVariables,
|
|
14093
15221
|
fallback: REVIEW_PROMPT,
|
|
14094
15222
|
});
|
|
15223
|
+
const variables = {
|
|
15224
|
+
changes: context,
|
|
15225
|
+
format_instructions: formatInstructions,
|
|
15226
|
+
};
|
|
15227
|
+
const budgetedPrompt = await enforcePromptBudget({
|
|
15228
|
+
prompt,
|
|
15229
|
+
variables,
|
|
15230
|
+
tokenizer,
|
|
15231
|
+
maxTokens: config.service.tokenLimit || 2048,
|
|
15232
|
+
summaryKey: 'changes',
|
|
15233
|
+
});
|
|
15234
|
+
if (budgetedPrompt.truncated) {
|
|
15235
|
+
logger.verbose(`Rendered prompt exceeded token budget; trimmed changes to ${budgetedPrompt.promptTokenCount} tokens.`, { color: 'yellow' });
|
|
15236
|
+
}
|
|
14095
15237
|
const response = await executeChain({
|
|
14096
15238
|
llm,
|
|
14097
15239
|
prompt,
|
|
14098
|
-
variables:
|
|
14099
|
-
changes: context,
|
|
14100
|
-
format_instructions: formatInstructions,
|
|
14101
|
-
},
|
|
15240
|
+
variables: budgetedPrompt.variables,
|
|
14102
15241
|
parser,
|
|
15242
|
+
logger,
|
|
15243
|
+
tokenizer,
|
|
15244
|
+
metadata: {
|
|
15245
|
+
task: argv.branch ? 'review-branch' : 'review',
|
|
15246
|
+
command: 'review',
|
|
15247
|
+
provider,
|
|
15248
|
+
model: String(model),
|
|
15249
|
+
},
|
|
14103
15250
|
});
|
|
14104
15251
|
// sort by severity
|
|
14105
15252
|
return response.sort((a, b) => b.severity - a.severity);
|
|
@@ -14118,6 +15265,7 @@ const handler = async (argv, logger) => {
|
|
|
14118
15265
|
},
|
|
14119
15266
|
});
|
|
14120
15267
|
const reviewer = new TaskList(recap, { ...config, apiKey: key ?? undefined });
|
|
15268
|
+
logLlmTelemetrySummary(logger, 'review');
|
|
14121
15269
|
await reviewer.start();
|
|
14122
15270
|
};
|
|
14123
15271
|
|
|
@@ -14138,6 +15286,7 @@ y.command(changelog.command, changelog.desc, changelog.builder, changelog.handle
|
|
|
14138
15286
|
y.command(recap.command, recap.desc, recap.builder, recap.handler);
|
|
14139
15287
|
y.command(review.command, review.desc, review.builder, review.handler);
|
|
14140
15288
|
y.command(init.command, init.desc, init.builder, init.handler);
|
|
15289
|
+
y.command(log.command, log.desc, log.builder, log.handler);
|
|
14141
15290
|
y.help().parse(process.argv.slice(2));
|
|
14142
15291
|
|
|
14143
15292
|
/**
|
|
@@ -14592,5 +15741,6 @@ var commitValidationHandler = /*#__PURE__*/Object.freeze({
|
|
|
14592
15741
|
exports.changelog = changelog;
|
|
14593
15742
|
exports.commit = commit;
|
|
14594
15743
|
exports.init = init;
|
|
15744
|
+
exports.log = log;
|
|
14595
15745
|
exports.recap = recap;
|
|
14596
15746
|
exports.types = types;
|