@eldrforge/kodrdriv 1.2.132 → 1.2.133
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/AI-GUIDE.md +834 -0
- package/README.md +35 -6
- package/dist/arguments.js +54 -21
- package/dist/arguments.js.map +1 -1
- package/dist/commands/audio-commit.js +2 -1
- package/dist/commands/audio-commit.js.map +1 -1
- package/dist/commands/audio-review.js +4 -2
- package/dist/commands/audio-review.js.map +1 -1
- package/dist/commands/commit.js +91 -133
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/publish.js +3 -6
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/release.js +62 -139
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.js +1 -1
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/tree.js +29 -33
- package/dist/commands/tree.js.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/util/storageAdapter.js +9 -2
- package/dist/util/storageAdapter.js.map +1 -1
- package/guide/ai-system.md +519 -0
- package/guide/architecture.md +346 -0
- package/guide/commands.md +380 -0
- package/guide/configuration.md +513 -0
- package/guide/debugging.md +584 -0
- package/guide/development.md +629 -0
- package/guide/index.md +212 -0
- package/guide/integration.md +507 -0
- package/guide/monorepo.md +530 -0
- package/guide/quickstart.md +223 -0
- package/guide/testing.md +460 -0
- package/guide/tree-operations.md +618 -0
- package/guide/usage.md +575 -0
- package/package.json +6 -6
package/dist/commands/release.js
CHANGED
|
@@ -4,8 +4,8 @@ import 'dotenv/config';
|
|
|
4
4
|
import { DEFAULT_TO_COMMIT_ALIAS, DEFAULT_MAX_DIFF_BYTES, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_OUTPUT_DIRECTORY } from '../constants.js';
|
|
5
5
|
import { getCurrentBranch, getDefaultFromRef, safeJsonParse } from '@eldrforge/git-tools';
|
|
6
6
|
import { create } from '../content/log.js';
|
|
7
|
-
import { create as create$1
|
|
8
|
-
import { runAgenticRelease, requireTTY,
|
|
7
|
+
import { create as create$1 } from '../content/diff.js';
|
|
8
|
+
import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt } from '@eldrforge/ai-service';
|
|
9
9
|
import { improveContentWithLLM } from '../util/interactive.js';
|
|
10
10
|
import { toAIConfig } from '../util/aiAdapter.js';
|
|
11
11
|
import { createStorageAdapter } from '../util/storageAdapter.js';
|
|
@@ -17,6 +17,24 @@ import { validateReleaseSummary } from '../util/validation.js';
|
|
|
17
17
|
import * as GitHub from '@eldrforge/github-tools';
|
|
18
18
|
import { filterContent } from '../util/stopContext.js';
|
|
19
19
|
|
|
20
|
+
// Helper function to read context files
|
|
21
|
+
async function readContextFiles(contextFiles, logger) {
|
|
22
|
+
if (!contextFiles || contextFiles.length === 0) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
const storage = createStorage();
|
|
26
|
+
const contextParts = [];
|
|
27
|
+
for (const filePath of contextFiles){
|
|
28
|
+
try {
|
|
29
|
+
const content = await storage.readFile(filePath, 'utf8');
|
|
30
|
+
contextParts.push(`## Context from ${filePath}\n\n${content}\n`);
|
|
31
|
+
logger.debug(`Read context from file: ${filePath}`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.warn(`Failed to read context file ${filePath}: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return contextParts.join('\n---\n\n');
|
|
37
|
+
}
|
|
20
38
|
// Helper function to edit release notes using editor
|
|
21
39
|
async function editReleaseNotesInteractively(releaseSummary) {
|
|
22
40
|
const templateLines = [
|
|
@@ -65,7 +83,7 @@ Please revise the release notes according to the user's feedback while maintaini
|
|
|
65
83
|
callLLM: async (request, runConfig, outputDirectory)=>{
|
|
66
84
|
var _aiConfig_commands_release, _aiConfig_commands, _aiConfig_commands_release1, _aiConfig_commands1;
|
|
67
85
|
const aiConfig = toAIConfig(runConfig);
|
|
68
|
-
const aiStorageAdapter = createStorageAdapter();
|
|
86
|
+
const aiStorageAdapter = createStorageAdapter(outputDirectory);
|
|
69
87
|
const aiLogger = createLoggerAdapter(false);
|
|
70
88
|
const modelToUse = ((_aiConfig_commands = aiConfig.commands) === null || _aiConfig_commands === void 0 ? void 0 : (_aiConfig_commands_release = _aiConfig_commands.release) === null || _aiConfig_commands_release === void 0 ? void 0 : _aiConfig_commands_release.model) || aiConfig.model || 'gpt-4o-mini';
|
|
71
89
|
const openaiReasoning = ((_aiConfig_commands1 = aiConfig.commands) === null || _aiConfig_commands1 === void 0 ? void 0 : (_aiConfig_commands_release1 = _aiConfig_commands1.release) === null || _aiConfig_commands_release1 === void 0 ? void 0 : _aiConfig_commands_release1.reasoning) || aiConfig.reasoning;
|
|
@@ -103,10 +121,8 @@ async function generateSelfReflection(agenticResult, outputDirectory, storage, l
|
|
|
103
121
|
releaseNotes: agenticResult.releaseNotes,
|
|
104
122
|
logger
|
|
105
123
|
});
|
|
106
|
-
// Save the report
|
|
107
|
-
|
|
108
|
-
const filename = `agentic-reflection-release-${timestamp}.md`;
|
|
109
|
-
await storageAdapter.writeOutput(filename, report);
|
|
124
|
+
// Save the report to output directory
|
|
125
|
+
await storage.writeFile(reflectionPath, report, 'utf8');
|
|
110
126
|
logger.info('');
|
|
111
127
|
logger.info('═'.repeat(80));
|
|
112
128
|
logger.info('📊 SELF-REFLECTION REPORT GENERATED');
|
|
@@ -188,7 +204,7 @@ async function handleInteractiveReleaseFeedback(releaseSummary, runConfig, promp
|
|
|
188
204
|
}
|
|
189
205
|
}
|
|
190
206
|
const execute = async (runConfig)=>{
|
|
191
|
-
var _runConfig_release, _runConfig_release1, _runConfig_release2, _runConfig_release3, _runConfig_release4, _runConfig_release5, _runConfig_release6, _runConfig_release7, _runConfig_release8, _runConfig_release9, _aiConfig_commands_release, _aiConfig_commands, _aiConfig_commands_release1, _aiConfig_commands1,
|
|
207
|
+
var _runConfig_release, _runConfig_release1, _runConfig_release2, _runConfig_release3, _runConfig_release4, _runConfig_release5, _runConfig_release6, _runConfig_release7, _runConfig_release8, _runConfig_release9, _aiConfig_commands_release, _aiConfig_commands, _runConfig_release10, _aiConfig_commands_release1, _aiConfig_commands1, _runConfig_release11, _runConfig_release12;
|
|
192
208
|
const isDryRun = runConfig.dryRun || false;
|
|
193
209
|
const logger = getDryRunLogger(isDryRun);
|
|
194
210
|
// Get current branch to help determine best tag comparison
|
|
@@ -275,154 +291,61 @@ const execute = async (runConfig)=>{
|
|
|
275
291
|
} else {
|
|
276
292
|
logger.debug('Milestone integration disabled via --no-milestones');
|
|
277
293
|
}
|
|
278
|
-
// Create adapters for ai-service
|
|
279
|
-
const aiConfig = toAIConfig(runConfig);
|
|
280
|
-
const aiStorageAdapter = createStorageAdapter();
|
|
281
|
-
const aiLogger = createLoggerAdapter(isDryRun);
|
|
282
294
|
// Always ensure output directory exists for request/response files
|
|
283
295
|
const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
|
|
284
296
|
const storage = createStorage();
|
|
285
297
|
await storage.ensureDirectory(outputDirectory);
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
debug: runConfig.debug,
|
|
302
|
-
debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('release-agentic')),
|
|
303
|
-
debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('release-agentic')),
|
|
304
|
-
storage: aiStorageAdapter,
|
|
305
|
-
logger: aiLogger,
|
|
306
|
-
openaiReasoning: ((_aiConfig_commands3 = aiConfig.commands) === null || _aiConfig_commands3 === void 0 ? void 0 : (_aiConfig_commands_release3 = _aiConfig_commands3.release) === null || _aiConfig_commands_release3 === void 0 ? void 0 : _aiConfig_commands_release3.reasoning) || aiConfig.reasoning
|
|
307
|
-
});
|
|
308
|
-
const iterations = agenticResult.iterations || 0;
|
|
309
|
-
const toolCalls = agenticResult.toolCallsExecuted || 0;
|
|
310
|
-
logger.info(`🔍 Agentic analysis complete: ${iterations} iterations, ${toolCalls} tool calls`);
|
|
311
|
-
// Generate self-reflection output if enabled
|
|
312
|
-
if ((_runConfig_release14 = runConfig.release) === null || _runConfig_release14 === void 0 ? void 0 : _runConfig_release14.selfReflection) {
|
|
313
|
-
await generateSelfReflection(agenticResult, outputDirectory, storage, logger);
|
|
314
|
-
}
|
|
315
|
-
// Apply stop-context filtering to release notes
|
|
316
|
-
const titleFilterResult = filterContent(agenticResult.releaseNotes.title, runConfig.stopContext);
|
|
317
|
-
const bodyFilterResult = filterContent(agenticResult.releaseNotes.body, runConfig.stopContext);
|
|
318
|
-
let releaseSummary = {
|
|
319
|
-
title: titleFilterResult.filtered,
|
|
320
|
-
body: bodyFilterResult.filtered
|
|
321
|
-
};
|
|
322
|
-
// Handle interactive mode
|
|
323
|
-
if (((_runConfig_release15 = runConfig.release) === null || _runConfig_release15 === void 0 ? void 0 : _runConfig_release15.interactive) && !isDryRun) {
|
|
324
|
-
var _runConfig_release16;
|
|
325
|
-
requireTTY('Interactive mode requires a terminal. Use --dry-run instead.');
|
|
326
|
-
const interactivePromptContext = {
|
|
327
|
-
context: (_runConfig_release16 = runConfig.release) === null || _runConfig_release16 === void 0 ? void 0 : _runConfig_release16.context,
|
|
328
|
-
directories: runConfig.contextDirectories
|
|
329
|
-
};
|
|
330
|
-
const interactiveResult = await handleInteractiveReleaseFeedback(releaseSummary, runConfig, promptConfig, interactivePromptContext, outputDirectory, storage, logContent, diffContent);
|
|
331
|
-
if (interactiveResult.action === 'skip') {
|
|
332
|
-
logger.info('RELEASE_ABORTED: Release notes generation aborted by user | Reason: User choice | Status: cancelled');
|
|
333
|
-
} else {
|
|
334
|
-
logger.info('RELEASE_FINALIZED: Release notes finalized and accepted | Status: ready | Next: Create release or save');
|
|
335
|
-
}
|
|
336
|
-
releaseSummary = interactiveResult.finalSummary;
|
|
337
|
-
}
|
|
338
|
-
// Save timestamped copy of release notes to output directory
|
|
339
|
-
try {
|
|
340
|
-
const timestampedFilename = getTimestampedReleaseNotesFilename();
|
|
341
|
-
const outputPath = getOutputPath(outputDirectory, timestampedFilename);
|
|
342
|
-
// Format the release notes as markdown
|
|
343
|
-
const releaseNotesContent = `# ${releaseSummary.title}\n\n${releaseSummary.body}`;
|
|
344
|
-
await storage.writeFile(outputPath, releaseNotesContent, 'utf-8');
|
|
345
|
-
logger.debug('Saved timestamped release notes: %s', outputPath);
|
|
346
|
-
} catch (error) {
|
|
347
|
-
logger.warn('RELEASE_SAVE_FAILED: Failed to save timestamped release notes | Error: %s | Impact: Notes not persisted to file', error.message);
|
|
348
|
-
}
|
|
349
|
-
if (isDryRun) {
|
|
350
|
-
logger.info('RELEASE_SUMMARY_COMPLETE: Generated release summary successfully | Status: completed');
|
|
351
|
-
logger.info('RELEASE_SUMMARY_TITLE: %s', releaseSummary.title);
|
|
352
|
-
logger.info('RELEASE_SUMMARY_BODY: %s', releaseSummary.body);
|
|
353
|
-
}
|
|
354
|
-
return releaseSummary;
|
|
355
|
-
}
|
|
356
|
-
// Non-agentic mode: use traditional prompt-based approach
|
|
357
|
-
const promptContent = {
|
|
298
|
+
// Create adapters for ai-service
|
|
299
|
+
const aiConfig = toAIConfig(runConfig);
|
|
300
|
+
const aiStorageAdapter = createStorageAdapter(outputDirectory);
|
|
301
|
+
const aiLogger = createLoggerAdapter(isDryRun);
|
|
302
|
+
// Read context from files if provided
|
|
303
|
+
const contextFromFiles = await readContextFiles((_runConfig_release7 = runConfig.release) === null || _runConfig_release7 === void 0 ? void 0 : _runConfig_release7.contextFiles, logger);
|
|
304
|
+
// Combine file context with existing context
|
|
305
|
+
const combinedContext = [
|
|
306
|
+
(_runConfig_release8 = runConfig.release) === null || _runConfig_release8 === void 0 ? void 0 : _runConfig_release8.context,
|
|
307
|
+
contextFromFiles
|
|
308
|
+
].filter(Boolean).join('\n\n---\n\n');
|
|
309
|
+
// Run agentic release notes generation
|
|
310
|
+
const agenticResult = await runAgenticRelease({
|
|
311
|
+
fromRef,
|
|
312
|
+
toRef,
|
|
358
313
|
logContent,
|
|
359
314
|
diffContent,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
directories: runConfig.contextDirectories
|
|
366
|
-
};
|
|
367
|
-
const promptResult = await createReleasePrompt(promptConfig, promptContent, promptContext);
|
|
368
|
-
const modelToUse = ((_aiConfig_commands = aiConfig.commands) === null || _aiConfig_commands === void 0 ? void 0 : (_aiConfig_commands_release = _aiConfig_commands.release) === null || _aiConfig_commands_release === void 0 ? void 0 : _aiConfig_commands_release.model) || aiConfig.model || 'gpt-4o-mini';
|
|
369
|
-
const request = Formatter.create({
|
|
370
|
-
logger
|
|
371
|
-
}).formatPrompt(modelToUse, promptResult.prompt);
|
|
372
|
-
logger.debug('Release analysis: isLargeRelease=%s, maxTokens=%d', promptResult.isLargeRelease, promptResult.maxTokens);
|
|
373
|
-
// Create retry callback that reduces diff size on token limit errors
|
|
374
|
-
const createRetryCallback = (originalDiffContent, originalLogContent)=>async (attempt)=>{
|
|
375
|
-
var _runConfig_release, _runConfig_release1;
|
|
376
|
-
logger.info('RELEASE_RETRY: Retrying with reduced diff size | Attempt: %d | Strategy: Truncate diff | Reason: Previous attempt failed', attempt);
|
|
377
|
-
// Progressively reduce the diff size on retries
|
|
378
|
-
const reductionFactor = Math.pow(0.5, attempt - 1); // 50% reduction per retry
|
|
379
|
-
const reducedMaxDiffBytes = Math.max(512, Math.floor(maxDiffBytes * reductionFactor));
|
|
380
|
-
logger.debug('Reducing maxDiffBytes from %d to %d for retry', maxDiffBytes, reducedMaxDiffBytes);
|
|
381
|
-
// Re-truncate the diff with smaller limits
|
|
382
|
-
const reducedDiffContent = originalDiffContent.length > reducedMaxDiffBytes ? truncateDiffByFiles(originalDiffContent, reducedMaxDiffBytes) : originalDiffContent;
|
|
383
|
-
// Rebuild the prompt with the reduced diff
|
|
384
|
-
const reducedPromptContent = {
|
|
385
|
-
logContent: originalLogContent,
|
|
386
|
-
diffContent: reducedDiffContent,
|
|
387
|
-
releaseFocus: (_runConfig_release = runConfig.release) === null || _runConfig_release === void 0 ? void 0 : _runConfig_release.focus,
|
|
388
|
-
milestoneIssues: milestoneIssuesContent
|
|
389
|
-
};
|
|
390
|
-
const reducedPromptContext = {
|
|
391
|
-
context: (_runConfig_release1 = runConfig.release) === null || _runConfig_release1 === void 0 ? void 0 : _runConfig_release1.context,
|
|
392
|
-
directories: runConfig.contextDirectories
|
|
393
|
-
};
|
|
394
|
-
const retryPromptResult = await createReleasePrompt(promptConfig, reducedPromptContent, reducedPromptContext);
|
|
395
|
-
const retryRequest = Formatter.create({
|
|
396
|
-
logger
|
|
397
|
-
}).formatPrompt(modelToUse, retryPromptResult.prompt);
|
|
398
|
-
return retryRequest.messages;
|
|
399
|
-
};
|
|
400
|
-
const summary = await createCompletionWithRetry(request.messages, {
|
|
401
|
-
model: modelToUse,
|
|
402
|
-
openaiReasoning: ((_aiConfig_commands1 = aiConfig.commands) === null || _aiConfig_commands1 === void 0 ? void 0 : (_aiConfig_commands_release1 = _aiConfig_commands1.release) === null || _aiConfig_commands_release1 === void 0 ? void 0 : _aiConfig_commands_release1.reasoning) || aiConfig.reasoning,
|
|
403
|
-
maxTokens: promptResult.maxTokens,
|
|
404
|
-
responseFormat: {
|
|
405
|
-
type: 'json_object'
|
|
406
|
-
},
|
|
315
|
+
milestoneIssues: milestoneIssuesContent,
|
|
316
|
+
releaseFocus: (_runConfig_release9 = runConfig.release) === null || _runConfig_release9 === void 0 ? void 0 : _runConfig_release9.focus,
|
|
317
|
+
userContext: combinedContext || undefined,
|
|
318
|
+
model: ((_aiConfig_commands = aiConfig.commands) === null || _aiConfig_commands === void 0 ? void 0 : (_aiConfig_commands_release = _aiConfig_commands.release) === null || _aiConfig_commands_release === void 0 ? void 0 : _aiConfig_commands_release.model) || aiConfig.model || 'gpt-4o',
|
|
319
|
+
maxIterations: ((_runConfig_release10 = runConfig.release) === null || _runConfig_release10 === void 0 ? void 0 : _runConfig_release10.maxAgenticIterations) || 30,
|
|
407
320
|
debug: runConfig.debug,
|
|
408
321
|
debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('release')),
|
|
409
322
|
debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('release')),
|
|
410
323
|
storage: aiStorageAdapter,
|
|
411
|
-
logger: aiLogger
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
324
|
+
logger: aiLogger,
|
|
325
|
+
openaiReasoning: ((_aiConfig_commands1 = aiConfig.commands) === null || _aiConfig_commands1 === void 0 ? void 0 : (_aiConfig_commands_release1 = _aiConfig_commands1.release) === null || _aiConfig_commands_release1 === void 0 ? void 0 : _aiConfig_commands_release1.reasoning) || aiConfig.reasoning
|
|
326
|
+
});
|
|
327
|
+
const iterations = agenticResult.iterations || 0;
|
|
328
|
+
const toolCalls = agenticResult.toolCallsExecuted || 0;
|
|
329
|
+
logger.info(`🔍 Analysis complete: ${iterations} iterations, ${toolCalls} tool calls`);
|
|
330
|
+
// Generate self-reflection output if enabled
|
|
331
|
+
if ((_runConfig_release11 = runConfig.release) === null || _runConfig_release11 === void 0 ? void 0 : _runConfig_release11.selfReflection) {
|
|
332
|
+
await generateSelfReflection(agenticResult, outputDirectory, storage, logger);
|
|
333
|
+
}
|
|
415
334
|
// Apply stop-context filtering to release notes
|
|
416
|
-
const titleFilterResult = filterContent(
|
|
417
|
-
const bodyFilterResult = filterContent(
|
|
335
|
+
const titleFilterResult = filterContent(agenticResult.releaseNotes.title, runConfig.stopContext);
|
|
336
|
+
const bodyFilterResult = filterContent(agenticResult.releaseNotes.body, runConfig.stopContext);
|
|
418
337
|
let releaseSummary = {
|
|
419
338
|
title: titleFilterResult.filtered,
|
|
420
339
|
body: bodyFilterResult.filtered
|
|
421
340
|
};
|
|
422
341
|
// Handle interactive mode
|
|
423
|
-
if (((
|
|
342
|
+
if (((_runConfig_release12 = runConfig.release) === null || _runConfig_release12 === void 0 ? void 0 : _runConfig_release12.interactive) && !isDryRun) {
|
|
424
343
|
requireTTY('Interactive mode requires a terminal. Use --dry-run instead.');
|
|
425
|
-
const
|
|
344
|
+
const interactivePromptContext = {
|
|
345
|
+
context: combinedContext || undefined,
|
|
346
|
+
directories: runConfig.contextDirectories
|
|
347
|
+
};
|
|
348
|
+
const interactiveResult = await handleInteractiveReleaseFeedback(releaseSummary, runConfig, promptConfig, interactivePromptContext, outputDirectory, storage, logContent, diffContent);
|
|
426
349
|
if (interactiveResult.action === 'skip') {
|
|
427
350
|
logger.info('RELEASE_ABORTED: Release notes generation aborted by user | Reason: User choice | Status: cancelled');
|
|
428
351
|
} else {
|