@grunnverk/kilde 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/pull_request_template.md +48 -0
  4. package/.github/workflows/deploy-docs.yml +59 -0
  5. package/.github/workflows/npm-publish.yml +48 -0
  6. package/.github/workflows/test.yml +48 -0
  7. package/CHANGELOG.md +92 -0
  8. package/CONTRIBUTING.md +438 -0
  9. package/LICENSE +190 -0
  10. package/PROJECT_SUMMARY.md +318 -0
  11. package/README.md +444 -0
  12. package/RELEASE_CHECKLIST.md +182 -0
  13. package/dist/application.js +166 -0
  14. package/dist/application.js.map +1 -0
  15. package/dist/commands/release.js +326 -0
  16. package/dist/commands/release.js.map +1 -0
  17. package/dist/constants.js +122 -0
  18. package/dist/constants.js.map +1 -0
  19. package/dist/logging.js +176 -0
  20. package/dist/logging.js.map +1 -0
  21. package/dist/main.js +24 -0
  22. package/dist/main.js.map +1 -0
  23. package/dist/mcp-server.js +17467 -0
  24. package/dist/mcp-server.js.map +7 -0
  25. package/dist/utils/config.js +89 -0
  26. package/dist/utils/config.js.map +1 -0
  27. package/docs/AI_GUIDE.md +618 -0
  28. package/eslint.config.mjs +85 -0
  29. package/guide/architecture.md +776 -0
  30. package/guide/commands.md +580 -0
  31. package/guide/configuration.md +779 -0
  32. package/guide/mcp-integration.md +708 -0
  33. package/guide/overview.md +225 -0
  34. package/package.json +91 -0
  35. package/scripts/build-mcp.js +115 -0
  36. package/scripts/test-mcp-compliance.js +254 -0
  37. package/src/application.ts +246 -0
  38. package/src/commands/release.ts +450 -0
  39. package/src/constants.ts +162 -0
  40. package/src/logging.ts +210 -0
  41. package/src/main.ts +25 -0
  42. package/src/mcp/prompts/index.ts +98 -0
  43. package/src/mcp/resources.ts +121 -0
  44. package/src/mcp/server.ts +195 -0
  45. package/src/mcp/tools.ts +219 -0
  46. package/src/types.ts +131 -0
  47. package/src/utils/config.ts +181 -0
  48. package/tests/application.test.ts +114 -0
  49. package/tests/commands/commit.test.ts +248 -0
  50. package/tests/commands/release.test.ts +325 -0
  51. package/tests/constants.test.ts +118 -0
  52. package/tests/logging.test.ts +142 -0
  53. package/tests/mcp/prompts/index.test.ts +202 -0
  54. package/tests/mcp/resources.test.ts +166 -0
  55. package/tests/mcp/tools.test.ts +211 -0
  56. package/tests/utils/config.test.ts +212 -0
  57. package/tsconfig.json +32 -0
  58. package/vite.config.ts +107 -0
  59. package/vitest.config.ts +40 -0
  60. package/website/index.html +14 -0
  61. package/website/src/App.css +142 -0
  62. package/website/src/App.tsx +34 -0
  63. package/website/src/components/Commands.tsx +182 -0
  64. package/website/src/components/Configuration.tsx +214 -0
  65. package/website/src/components/Examples.tsx +234 -0
  66. package/website/src/components/Footer.css +99 -0
  67. package/website/src/components/Footer.tsx +93 -0
  68. package/website/src/components/GettingStarted.tsx +94 -0
  69. package/website/src/components/Hero.css +95 -0
  70. package/website/src/components/Hero.tsx +50 -0
  71. package/website/src/components/Navigation.css +102 -0
  72. package/website/src/components/Navigation.tsx +57 -0
  73. package/website/src/index.css +36 -0
  74. package/website/src/main.tsx +10 -0
  75. package/website/vite.config.ts +12 -0
@@ -0,0 +1,166 @@
1
+ import { config } from 'dotenv';
2
+ import { setLogger } from '@grunnverk/git-tools';
3
+ import { initializeTemplates } from '@grunnverk/ai-service';
4
+ import { Command } from 'commander';
5
+ import * as CommandsGit from '@grunnverk/commands-git';
6
+ import { execute } from './commands/release.js';
7
+ import { PROGRAM_NAME, VERSION, KILDE_DEFAULTS, BUILD_HOSTNAME, BUILD_TIMESTAMP, COMMAND_COMMIT, COMMAND_RELEASE } from './constants.js';
8
+ import { setLogLevel, getLogger } from './logging.js';
9
+ import { getEffectiveConfig } from './utils/config.js';
10
+
11
+ // Load .env file if it exists, but NEVER override existing environment variables
12
+ config({
13
+ override: false,
14
+ debug: false
15
+ });
16
+ /**
17
+ * Check Node.js version and exit with clear error message if version is too old.
18
+ */ function checkNodeVersion() {
19
+ const requiredMajorVersion = 24;
20
+ const currentVersion = process.version;
21
+ const majorVersion = parseInt(currentVersion.slice(1).split('.')[0], 10);
22
+ if (majorVersion < requiredMajorVersion) {
23
+ // eslint-disable-next-line no-console
24
+ console.error(`\n❌ ERROR: Node.js version ${requiredMajorVersion}.0.0 or higher is required.`);
25
+ // eslint-disable-next-line no-console
26
+ console.error(` Current version: ${currentVersion}`);
27
+ // eslint-disable-next-line no-console
28
+ console.error(` Please upgrade your Node.js version to continue.\n`);
29
+ process.exit(1);
30
+ }
31
+ }
32
+ /**
33
+ * Parse command line arguments and merge with config
34
+ */ async function parseArguments() {
35
+ const program = new Command();
36
+ program.name(PROGRAM_NAME).description('Universal Git Automation Tool - AI-powered commit and release messages').version(VERSION).option('-v, --verbose', 'Enable verbose logging').option('-d, --debug', 'Enable debug logging').option('--dry-run', 'Preview without making changes').option('--model <model>', 'AI model to use (default: gpt-4o-mini)').option('--reasoning <level>', 'OpenAI reasoning level: low, medium, high (default: low)');
37
+ // Commit command
38
+ program.command('commit').description('Generate AI-powered commit message').option('--add', 'Stage all changes before committing').option('--cached', 'Only use staged changes').option('--sendit', 'Automatically commit with generated message').option('--interactive', 'Interactive mode for reviewing message').option('--amend', 'Amend the previous commit').option('--push [remote]', 'Push after committing (optionally specify remote)').option('--issue <number>', 'Reference issue number').option('--context <text>', 'Additional context for commit message').option('--context-files <files...>', 'Context files to include').action(async (options)=>{
39
+ const config = await buildConfig('commit', options);
40
+ await executeCommand(COMMAND_COMMIT, config);
41
+ });
42
+ // Release command
43
+ program.command('release').description('Generate release notes from git history').option('--from-tag <tag>', 'Start tag for release notes').option('--to-tag <tag>', 'End tag for release notes (default: HEAD)').option('--version <version>', 'Version number for release').option('--output <file>', 'Output file path').option('--interactive', 'Interactive mode for reviewing notes').option('--focus <text>', 'Focus area for release notes').option('--context <text>', 'Additional context for release notes').option('--context-files <files...>', 'Context files to include').action(async (options)=>{
44
+ const config = await buildConfig('release', options);
45
+ await executeCommand(COMMAND_RELEASE, config);
46
+ });
47
+ program.parse();
48
+ // Return empty config if help or version was shown
49
+ return {
50
+ commandName: '',
51
+ config: KILDE_DEFAULTS
52
+ };
53
+ }
54
+ /**
55
+ * Build final configuration from defaults, config file, and CLI arguments
56
+ */ async function buildConfig(commandName, cliOptions) {
57
+ // Load config from file
58
+ const fileConfig = await getEffectiveConfig();
59
+ // Start with defaults
60
+ const config = {
61
+ ...KILDE_DEFAULTS
62
+ };
63
+ // Merge file config
64
+ Object.assign(config, fileConfig);
65
+ // Merge CLI global options
66
+ if (cliOptions.parent.verbose !== undefined) config.verbose = cliOptions.parent.verbose;
67
+ if (cliOptions.parent.debug !== undefined) config.debug = cliOptions.parent.debug;
68
+ if (cliOptions.parent.dryRun !== undefined) config.dryRun = cliOptions.parent.dryRun;
69
+ if (cliOptions.parent.model) config.model = cliOptions.parent.model;
70
+ if (cliOptions.parent.reasoning) config.openaiReasoning = cliOptions.parent.reasoning;
71
+ // Merge command-specific options
72
+ if (commandName === 'commit') {
73
+ config.commit = config.commit || {};
74
+ if (cliOptions.add !== undefined) config.commit.add = cliOptions.add;
75
+ if (cliOptions.cached !== undefined) config.commit.cached = cliOptions.cached;
76
+ if (cliOptions.sendit !== undefined) config.commit.sendit = cliOptions.sendit;
77
+ if (cliOptions.interactive !== undefined) config.commit.interactive = cliOptions.interactive;
78
+ if (cliOptions.amend !== undefined) config.commit.amend = cliOptions.amend;
79
+ if (cliOptions.push !== undefined) config.commit.push = cliOptions.push;
80
+ if (cliOptions.context) config.commit.context = cliOptions.context;
81
+ if (cliOptions.contextFiles) config.commit.contextFiles = cliOptions.contextFiles;
82
+ } else if (commandName === 'release') {
83
+ config.release = config.release || {};
84
+ if (cliOptions.fromTag) config.release.from = cliOptions.fromTag;
85
+ if (cliOptions.toTag) config.release.to = cliOptions.toTag;
86
+ if (cliOptions.interactive !== undefined) config.release.interactive = cliOptions.interactive;
87
+ if (cliOptions.focus) config.release.focus = cliOptions.focus;
88
+ if (cliOptions.context) config.release.context = cliOptions.context;
89
+ if (cliOptions.contextFiles) config.release.contextFiles = cliOptions.contextFiles;
90
+ // Add non-standard fields as any
91
+ if (cliOptions.version) config.release.version = cliOptions.version;
92
+ if (cliOptions.output) config.release.output = cliOptions.output;
93
+ }
94
+ // Return config (no validation needed - already built from defaults)
95
+ return config;
96
+ }
97
+ /**
98
+ * Execute the specified command
99
+ */ async function executeCommand(commandName, runConfig) {
100
+ const logger = getLogger();
101
+ // Configure logging level
102
+ if (runConfig.verbose) {
103
+ setLogLevel('verbose');
104
+ }
105
+ if (runConfig.debug) {
106
+ setLogLevel('debug');
107
+ }
108
+ // Configure external packages to use our logger
109
+ setLogger(logger);
110
+ logger.info('APPLICATION_STARTING: Kilde application initializing | Version: %s | BuildHost: %s | BuildTime: %s | Status: starting', VERSION, BUILD_HOSTNAME, BUILD_TIMESTAMP);
111
+ logger.debug(`Executing command: ${commandName}`);
112
+ let summary = '';
113
+ try {
114
+ if (commandName === COMMAND_COMMIT) {
115
+ summary = await CommandsGit.commit(runConfig);
116
+ } else if (commandName === COMMAND_RELEASE) {
117
+ const releaseSummary = await execute(runConfig);
118
+ summary = `Release notes generated:\nTitle: ${releaseSummary.title}\n\n${releaseSummary.body}`;
119
+ } else {
120
+ throw new Error(`Unknown command: ${commandName}`);
121
+ }
122
+ if (summary) {
123
+ logger.info('COMMAND_COMPLETE: Command executed successfully | Status: success');
124
+ if (runConfig.verbose || runConfig.debug) {
125
+ logger.info('COMMAND_SUMMARY: %s', summary);
126
+ }
127
+ }
128
+ } catch (error) {
129
+ var _error_message;
130
+ // Handle user cancellation gracefully
131
+ if (error.name === 'UserCancellationError' || ((_error_message = error.message) === null || _error_message === void 0 ? void 0 : _error_message.includes('cancelled'))) {
132
+ logger.info('COMMAND_CANCELLED: Command cancelled by user | Status: cancelled');
133
+ return;
134
+ }
135
+ // Log and re-throw other errors
136
+ logger.error('COMMAND_FAILED: Command execution failed | Error: %s | Status: error', error.message);
137
+ throw error;
138
+ }
139
+ }
140
+ /**
141
+ * Configure early logging based on command line flags.
142
+ */ function configureEarlyLogging() {
143
+ const hasVerbose = process.argv.includes('--verbose') || process.argv.includes('-v');
144
+ const hasDebug = process.argv.includes('--debug') || process.argv.includes('-d');
145
+ // Set log level based on early flag detection
146
+ if (hasDebug) {
147
+ setLogLevel('debug');
148
+ } else if (hasVerbose) {
149
+ setLogLevel('verbose');
150
+ }
151
+ }
152
+ /**
153
+ * Main application entry point
154
+ */ async function runApplication() {
155
+ // Check Node.js version first
156
+ checkNodeVersion();
157
+ // Configure logging early
158
+ configureEarlyLogging();
159
+ // Initialize RiotPrompt templates for ai-service
160
+ initializeTemplates();
161
+ // Parse arguments and execute command
162
+ await parseArguments();
163
+ }
164
+
165
+ export { configureEarlyLogging, runApplication };
166
+ //# sourceMappingURL=application.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.js","sources":["../src/application.ts"],"sourcesContent":["// Load .env file if it exists, but NEVER override existing environment variables\nimport { config as dotenvConfig } from 'dotenv';\ndotenvConfig({ override: false, debug: false });\n\nimport { setLogger as setGitLogger } from '@grunnverk/git-tools';\nimport { initializeTemplates } from '@grunnverk/ai-service';\nimport { Config } from '@grunnverk/core';\nimport { Command } from 'commander';\n\n// Import commands\nimport * as CommandsGit from '@grunnverk/commands-git';\nimport * as ReleaseCommand from './commands/release';\n\nimport {\n COMMAND_COMMIT,\n COMMAND_RELEASE,\n VERSION,\n BUILD_HOSTNAME,\n BUILD_TIMESTAMP,\n KILDE_DEFAULTS,\n PROGRAM_NAME\n} from './constants';\nimport { getLogger, setLogLevel } from './logging';\nimport { getEffectiveConfig } from './utils/config';\n\n/**\n * Check Node.js version and exit with clear error message if version is too old.\n */\nfunction checkNodeVersion(): void {\n const requiredMajorVersion = 24;\n const currentVersion = process.version;\n const majorVersion = parseInt(currentVersion.slice(1).split('.')[0], 10);\n\n if (majorVersion < requiredMajorVersion) {\n // eslint-disable-next-line no-console\n console.error(`\\n❌ ERROR: Node.js version ${requiredMajorVersion}.0.0 or higher is required.`);\n // eslint-disable-next-line no-console\n console.error(` Current version: ${currentVersion}`);\n // eslint-disable-next-line no-console\n console.error(` Please upgrade your Node.js version to continue.\\n`);\n process.exit(1);\n }\n}\n\n/**\n * Get formatted version information including build metadata.\n */\nexport function getVersionInfo(): { version: string; buildHostname: string; buildTimestamp: string; formatted: string } {\n return {\n version: VERSION,\n buildHostname: BUILD_HOSTNAME,\n buildTimestamp: BUILD_TIMESTAMP,\n formatted: `${VERSION}\\nBuilt on: ${BUILD_HOSTNAME}\\nBuild time: ${BUILD_TIMESTAMP}`\n };\n}\n\n/**\n * Parse command line arguments and merge with config\n */\nasync function parseArguments(): Promise<{ commandName: string; config: Config }> {\n const program = new Command();\n\n program\n .name(PROGRAM_NAME)\n .description('Universal Git Automation Tool - AI-powered commit and release messages')\n .version(VERSION)\n .option('-v, --verbose', 'Enable verbose logging')\n .option('-d, --debug', 'Enable debug logging')\n .option('--dry-run', 'Preview without making changes')\n .option('--model <model>', 'AI model to use (default: gpt-4o-mini)')\n .option('--reasoning <level>', 'OpenAI reasoning level: low, medium, high (default: low)');\n\n // Commit command\n program\n .command('commit')\n .description('Generate AI-powered commit message')\n .option('--add', 'Stage all changes before committing')\n .option('--cached', 'Only use staged changes')\n .option('--sendit', 'Automatically commit with generated message')\n .option('--interactive', 'Interactive mode for reviewing message')\n .option('--amend', 'Amend the previous commit')\n .option('--push [remote]', 'Push after committing (optionally specify remote)')\n .option('--issue <number>', 'Reference issue number')\n .option('--context <text>', 'Additional context for commit message')\n .option('--context-files <files...>', 'Context files to include')\n .action(async (options) => {\n const config = await buildConfig('commit', options);\n await executeCommand(COMMAND_COMMIT, config);\n });\n\n // Release command\n program\n .command('release')\n .description('Generate release notes from git history')\n .option('--from-tag <tag>', 'Start tag for release notes')\n .option('--to-tag <tag>', 'End tag for release notes (default: HEAD)')\n .option('--version <version>', 'Version number for release')\n .option('--output <file>', 'Output file path')\n .option('--interactive', 'Interactive mode for reviewing notes')\n .option('--focus <text>', 'Focus area for release notes')\n .option('--context <text>', 'Additional context for release notes')\n .option('--context-files <files...>', 'Context files to include')\n .action(async (options) => {\n const config = await buildConfig('release', options);\n await executeCommand(COMMAND_RELEASE, config);\n });\n\n program.parse();\n\n // Return empty config if help or version was shown\n return { commandName: '', config: KILDE_DEFAULTS };\n}\n\n/**\n * Build final configuration from defaults, config file, and CLI arguments\n */\nasync function buildConfig(commandName: string, cliOptions: any): Promise<Config> {\n // Load config from file\n const fileConfig = await getEffectiveConfig();\n\n // Start with defaults\n const config: Config = { ...KILDE_DEFAULTS };\n\n // Merge file config\n Object.assign(config, fileConfig);\n\n // Merge CLI global options\n if (cliOptions.parent.verbose !== undefined) config.verbose = cliOptions.parent.verbose;\n if (cliOptions.parent.debug !== undefined) config.debug = cliOptions.parent.debug;\n if (cliOptions.parent.dryRun !== undefined) config.dryRun = cliOptions.parent.dryRun;\n if (cliOptions.parent.model) config.model = cliOptions.parent.model;\n if (cliOptions.parent.reasoning) config.openaiReasoning = cliOptions.parent.reasoning;\n\n // Merge command-specific options\n if (commandName === 'commit') {\n config.commit = config.commit || {};\n if (cliOptions.add !== undefined) config.commit.add = cliOptions.add;\n if (cliOptions.cached !== undefined) config.commit.cached = cliOptions.cached;\n if (cliOptions.sendit !== undefined) config.commit.sendit = cliOptions.sendit;\n if (cliOptions.interactive !== undefined) config.commit.interactive = cliOptions.interactive;\n if (cliOptions.amend !== undefined) config.commit.amend = cliOptions.amend;\n if (cliOptions.push !== undefined) config.commit.push = cliOptions.push;\n if (cliOptions.context) config.commit.context = cliOptions.context;\n if (cliOptions.contextFiles) config.commit.contextFiles = cliOptions.contextFiles;\n } else if (commandName === 'release') {\n config.release = config.release || {};\n if (cliOptions.fromTag) config.release.from = cliOptions.fromTag;\n if (cliOptions.toTag) config.release.to = cliOptions.toTag;\n if (cliOptions.interactive !== undefined) config.release.interactive = cliOptions.interactive;\n if (cliOptions.focus) config.release.focus = cliOptions.focus;\n if (cliOptions.context) config.release.context = cliOptions.context;\n if (cliOptions.contextFiles) config.release.contextFiles = cliOptions.contextFiles;\n // Add non-standard fields as any\n if (cliOptions.version) (config.release as any).version = cliOptions.version;\n if (cliOptions.output) (config.release as any).output = cliOptions.output;\n }\n\n // Return config (no validation needed - already built from defaults)\n return config as unknown as Config;\n}\n\n/**\n * Execute the specified command\n */\nasync function executeCommand(commandName: string, runConfig: Config): Promise<void> {\n const logger = getLogger();\n\n // Configure logging level\n if (runConfig.verbose) {\n setLogLevel('verbose');\n }\n if (runConfig.debug) {\n setLogLevel('debug');\n }\n\n // Configure external packages to use our logger\n setGitLogger(logger);\n\n logger.info('APPLICATION_STARTING: Kilde application initializing | Version: %s | BuildHost: %s | BuildTime: %s | Status: starting',\n VERSION, BUILD_HOSTNAME, BUILD_TIMESTAMP);\n\n logger.debug(`Executing command: ${commandName}`);\n\n let summary: string = '';\n\n try {\n if (commandName === COMMAND_COMMIT) {\n summary = await CommandsGit.commit(runConfig);\n } else if (commandName === COMMAND_RELEASE) {\n const releaseSummary = await ReleaseCommand.execute(runConfig);\n summary = `Release notes generated:\\nTitle: ${releaseSummary.title}\\n\\n${releaseSummary.body}`;\n } else {\n throw new Error(`Unknown command: ${commandName}`);\n }\n\n if (summary) {\n logger.info('COMMAND_COMPLETE: Command executed successfully | Status: success');\n if (runConfig.verbose || runConfig.debug) {\n logger.info('COMMAND_SUMMARY: %s', summary);\n }\n }\n\n } catch (error: any) {\n // Handle user cancellation gracefully\n if (error.name === 'UserCancellationError' || error.message?.includes('cancelled')) {\n logger.info('COMMAND_CANCELLED: Command cancelled by user | Status: cancelled');\n return;\n }\n\n // Log and re-throw other errors\n logger.error('COMMAND_FAILED: Command execution failed | Error: %s | Status: error', error.message);\n throw error;\n }\n}\n\n/**\n * Configure early logging based on command line flags.\n */\nexport function configureEarlyLogging(): void {\n const hasVerbose = process.argv.includes('--verbose') || process.argv.includes('-v');\n const hasDebug = process.argv.includes('--debug') || process.argv.includes('-d');\n\n // Set log level based on early flag detection\n if (hasDebug) {\n setLogLevel('debug');\n } else if (hasVerbose) {\n setLogLevel('verbose');\n }\n}\n\n/**\n * Main application entry point\n */\nexport async function runApplication(): Promise<void> {\n // Check Node.js version first\n checkNodeVersion();\n\n // Configure logging early\n configureEarlyLogging();\n\n // Initialize RiotPrompt templates for ai-service\n initializeTemplates();\n\n // Parse arguments and execute command\n await parseArguments();\n}\n"],"names":["dotenvConfig","override","debug","checkNodeVersion","requiredMajorVersion","currentVersion","process","version","majorVersion","parseInt","slice","split","console","error","exit","parseArguments","program","Command","name","PROGRAM_NAME","description","VERSION","option","command","action","options","config","buildConfig","executeCommand","COMMAND_COMMIT","COMMAND_RELEASE","parse","commandName","KILDE_DEFAULTS","cliOptions","fileConfig","getEffectiveConfig","Object","assign","parent","verbose","undefined","dryRun","model","reasoning","openaiReasoning","commit","add","cached","sendit","interactive","amend","push","context","contextFiles","release","fromTag","from","toTag","to","focus","output","runConfig","logger","getLogger","setLogLevel","setGitLogger","info","BUILD_HOSTNAME","BUILD_TIMESTAMP","summary","CommandsGit","releaseSummary","ReleaseCommand","title","body","Error","message","includes","configureEarlyLogging","hasVerbose","argv","hasDebug","runApplication","initializeTemplates"],"mappings":"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"}
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { getCurrentBranch, getDefaultFromRef } from '@grunnverk/git-tools';
4
+ import { Formatter } from '@riotprompt/riotprompt';
5
+ import { getDryRunLogger, DEFAULT_TO_COMMIT_ALIAS, Log, DEFAULT_MAX_DIFF_BYTES, Diff, DEFAULT_EXCLUDED_PATTERNS, DEFAULT_OUTPUT_DIRECTORY, toAIConfig, createStorageAdapter, createLoggerAdapter, getOutputPath, getTimestampedResponseFilename, getTimestampedRequestFilename, filterContent, getTimestampedReleaseNotesFilename, improveContentWithLLM, validateReleaseSummary } from '@grunnverk/core';
6
+ import { runAgenticRelease, requireTTY, generateReflectionReport, getUserChoice, STANDARD_CHOICES, getLLMFeedbackInEditor, editContentInEditor, createCompletionWithRetry, createReleasePrompt } from '@grunnverk/ai-service';
7
+ import { createStorage } from '@grunnverk/shared';
8
+
9
+ // Helper function to read context files
10
+ async function readContextFiles(contextFiles, logger) {
11
+ if (!contextFiles || contextFiles.length === 0) {
12
+ return '';
13
+ }
14
+ const storage = createStorage();
15
+ const contextParts = [];
16
+ for (const filePath of contextFiles){
17
+ try {
18
+ const content = await storage.readFile(filePath, 'utf8');
19
+ contextParts.push(`## Context from ${filePath}\n\n${content}\n`);
20
+ logger.debug(`Read context from file: ${filePath}`);
21
+ } catch (error) {
22
+ logger.warn(`Failed to read context file ${filePath}: ${error.message}`);
23
+ }
24
+ }
25
+ return contextParts.join('\n---\n\n');
26
+ }
27
+ // Helper function to edit release notes using editor
28
+ async function editReleaseNotesInteractively(releaseSummary) {
29
+ const templateLines = [
30
+ '# Edit your release notes below. Lines starting with "#" will be ignored.',
31
+ '# The first line is the title, everything else is the body.',
32
+ '# Save and close the editor when you are done.'
33
+ ];
34
+ const content = `${releaseSummary.title}\n\n${releaseSummary.body}`;
35
+ const result = await editContentInEditor(content, templateLines, '.md');
36
+ const lines = result.content.split('\n');
37
+ const title = lines[0].trim();
38
+ const body = lines.slice(1).join('\n').trim();
39
+ return {
40
+ title,
41
+ body
42
+ };
43
+ }
44
+ // Helper function to improve release notes using LLM
45
+ async function improveReleaseNotesWithLLM(releaseSummary, runConfig, promptConfig, promptContext, outputDirectory, logContent, diffContent) {
46
+ // Get user feedback on what to improve using the editor
47
+ const releaseNotesContent = `${releaseSummary.title}\n\n${releaseSummary.body}`;
48
+ const userFeedback = await getLLMFeedbackInEditor('release notes', releaseNotesContent);
49
+ const improvementConfig = {
50
+ contentType: 'release notes',
51
+ createImprovedPrompt: async (promptConfig, currentSummary, promptContext)=>{
52
+ var _aiConfig_commands_release, _aiConfig_commands;
53
+ const improvementPromptContent = {
54
+ logContent: logContent,
55
+ diffContent: diffContent,
56
+ releaseFocus: `Please improve these release notes based on the user's feedback: "${userFeedback}".
57
+
58
+ Current release notes:
59
+ Title: "${currentSummary.title}"
60
+ Body: "${currentSummary.body}"
61
+
62
+ Please revise the release notes according to the user's feedback while maintaining accuracy and following good release note practices.`
63
+ };
64
+ const promptResult = await createReleasePrompt(promptConfig, improvementPromptContent, promptContext);
65
+ // Format the prompt into a proper request with messages
66
+ const aiConfig = toAIConfig(runConfig);
67
+ 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';
68
+ return Formatter.create({
69
+ logger: getDryRunLogger(false)
70
+ }).formatPrompt(modelToUse, promptResult.prompt);
71
+ },
72
+ callLLM: async (request, runConfig, outputDirectory)=>{
73
+ var _aiConfig_commands_release, _aiConfig_commands, _aiConfig_commands_release1, _aiConfig_commands1;
74
+ const aiConfig = toAIConfig(runConfig);
75
+ const aiStorageAdapter = createStorageAdapter(outputDirectory);
76
+ const aiLogger = createLoggerAdapter(false);
77
+ 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';
78
+ 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;
79
+ return await createCompletionWithRetry(request.messages, {
80
+ model: modelToUse,
81
+ openaiReasoning,
82
+ responseFormat: {
83
+ type: 'json_object'
84
+ },
85
+ debug: runConfig.debug,
86
+ debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('release-improve')),
87
+ debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('release-improve')),
88
+ storage: aiStorageAdapter,
89
+ logger: aiLogger
90
+ });
91
+ },
92
+ processResponse: (response)=>{
93
+ return validateReleaseSummary(response);
94
+ }
95
+ };
96
+ return await improveContentWithLLM(releaseSummary, runConfig, promptConfig, promptContext, outputDirectory, improvementConfig);
97
+ }
98
+ // Helper function to generate self-reflection output for release notes using observability module
99
+ async function generateSelfReflection(agenticResult, outputDirectory, storage, logger) {
100
+ try {
101
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('.')[0];
102
+ const reflectionPath = getOutputPath(outputDirectory, `agentic-reflection-release-${timestamp}.md`);
103
+ // Use new observability reflection generator
104
+ const report = await generateReflectionReport({
105
+ iterations: agenticResult.iterations || 0,
106
+ toolCallsExecuted: agenticResult.toolCallsExecuted || 0,
107
+ maxIterations: agenticResult.maxIterations || 30,
108
+ toolMetrics: agenticResult.toolMetrics || [],
109
+ conversationHistory: agenticResult.conversationHistory || [],
110
+ releaseNotes: agenticResult.releaseNotes,
111
+ logger
112
+ });
113
+ // Save the report to output directory
114
+ await storage.writeFile(reflectionPath, report, 'utf8');
115
+ logger.info('');
116
+ logger.info('═'.repeat(80));
117
+ logger.info('📊 SELF-REFLECTION REPORT GENERATED');
118
+ logger.info('═'.repeat(80));
119
+ logger.info('');
120
+ logger.info('📁 Location: %s', reflectionPath);
121
+ logger.info('');
122
+ logger.info('📈 Report Summary:');
123
+ const iterations = agenticResult.iterations || 0;
124
+ const toolCalls = agenticResult.toolCallsExecuted || 0;
125
+ const uniqueTools = new Set((agenticResult.toolMetrics || []).map((m)=>m.name)).size;
126
+ logger.info(` • ${iterations} iterations completed`);
127
+ logger.info(` • ${toolCalls} tool calls executed`);
128
+ logger.info(` • ${uniqueTools} unique tools used`);
129
+ logger.info('');
130
+ logger.info('💡 Use this report to:');
131
+ logger.info(' • Understand which tools were most effective');
132
+ logger.info(' • Identify performance bottlenecks');
133
+ logger.info(' • Optimize tool selection and usage patterns');
134
+ logger.info(' • Improve agentic release notes generation');
135
+ logger.info('');
136
+ logger.info('═'.repeat(80));
137
+ } catch (error) {
138
+ logger.warn('Failed to generate self-reflection report: %s', error.message);
139
+ }
140
+ }
141
+ // Interactive feedback loop for release notes
142
+ async function handleInteractiveReleaseFeedback(releaseSummary, runConfig, promptConfig, promptContext, outputDirectory, storage, logContent, diffContent) {
143
+ const logger = getDryRunLogger(false);
144
+ let currentSummary = releaseSummary;
145
+ while(true){
146
+ // Display the current release notes
147
+ logger.info('\nRELEASE_NOTES_GENERATED: Generated release notes from AI | Title Length: ' + currentSummary.title.length + ' | Body Length: ' + currentSummary.body.length);
148
+ logger.info('─'.repeat(50));
149
+ logger.info('RELEASE_NOTES_TITLE: %s', currentSummary.title);
150
+ logger.info('');
151
+ logger.info('RELEASE_NOTES_BODY: Release notes content:');
152
+ logger.info(currentSummary.body);
153
+ logger.info('─'.repeat(50));
154
+ // Get user choice
155
+ const userChoice = await getUserChoice('\nWhat would you like to do with these release notes?', [
156
+ STANDARD_CHOICES.CONFIRM,
157
+ STANDARD_CHOICES.EDIT,
158
+ STANDARD_CHOICES.SKIP,
159
+ STANDARD_CHOICES.IMPROVE
160
+ ], {
161
+ nonTtyErrorSuggestions: [
162
+ 'Use --dry-run to see the generated content without interaction'
163
+ ]
164
+ });
165
+ switch(userChoice){
166
+ case 'c':
167
+ return {
168
+ action: 'confirm',
169
+ finalSummary: currentSummary
170
+ };
171
+ case 'e':
172
+ try {
173
+ currentSummary = await editReleaseNotesInteractively(currentSummary);
174
+ } catch (error) {
175
+ logger.error(`RELEASE_NOTES_EDIT_FAILED: Unable to edit release notes | Error: ${error.message} | Impact: Using original notes`);
176
+ // Continue the loop to show options again
177
+ }
178
+ break;
179
+ case 's':
180
+ return {
181
+ action: 'skip',
182
+ finalSummary: currentSummary
183
+ };
184
+ case 'i':
185
+ try {
186
+ currentSummary = await improveReleaseNotesWithLLM(currentSummary, runConfig, promptConfig, promptContext, outputDirectory, logContent, diffContent);
187
+ } catch (error) {
188
+ logger.error(`RELEASE_NOTES_IMPROVE_FAILED: Unable to improve release notes | Error: ${error.message} | Impact: Using current version`);
189
+ // Continue the loop to show options again
190
+ }
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Execute release notes generation (pure git version)
197
+ *
198
+ * This implementation uses ONLY git operations - no GitHub API calls.
199
+ * Works with any git repository regardless of host or language.
200
+ */ const execute = async (runConfig)=>{
201
+ var _ref, _ref1, _ref2, _runConfig_excludedPatterns;
202
+ var _runConfig_release, _runConfig_release1, _runConfig_release2, _runConfig_release3, _runConfig_release4, _runConfig_release5, _runConfig_release6, _runConfig_release7, _aiConfig_commands_release, _aiConfig_commands, _runConfig_release8, _aiConfig_commands_release1, _aiConfig_commands1, _runConfig_release9, _runConfig_release10, _runConfig_release11;
203
+ const isDryRun = runConfig.dryRun || false;
204
+ const logger = getDryRunLogger(isDryRun);
205
+ // Get current branch to help determine best tag comparison
206
+ const currentBranch = await getCurrentBranch();
207
+ // Resolve the from reference with fallback logic if not explicitly provided
208
+ const fromRef = (_ref = (_runConfig_release = runConfig.release) === null || _runConfig_release === void 0 ? void 0 : _runConfig_release.from) !== null && _ref !== void 0 ? _ref : await getDefaultFromRef(false, currentBranch);
209
+ const toRef = (_ref1 = (_runConfig_release1 = runConfig.release) === null || _runConfig_release1 === void 0 ? void 0 : _runConfig_release1.to) !== null && _ref1 !== void 0 ? _ref1 : DEFAULT_TO_COMMIT_ALIAS;
210
+ logger.debug(`Using git references: from=${fromRef}, to=${toRef}`);
211
+ const log = await Log.create({
212
+ from: fromRef,
213
+ to: toRef,
214
+ limit: (_runConfig_release2 = runConfig.release) === null || _runConfig_release2 === void 0 ? void 0 : _runConfig_release2.messageLimit
215
+ });
216
+ let logContent = '';
217
+ const maxDiffBytes = (_ref2 = (_runConfig_release3 = runConfig.release) === null || _runConfig_release3 === void 0 ? void 0 : _runConfig_release3.maxDiffBytes) !== null && _ref2 !== void 0 ? _ref2 : DEFAULT_MAX_DIFF_BYTES;
218
+ const diff = await Diff.create({
219
+ from: fromRef,
220
+ to: toRef,
221
+ excludedPatterns: (_runConfig_excludedPatterns = runConfig.excludedPatterns) !== null && _runConfig_excludedPatterns !== void 0 ? _runConfig_excludedPatterns : DEFAULT_EXCLUDED_PATTERNS,
222
+ maxDiffBytes
223
+ });
224
+ let diffContent = '';
225
+ diffContent = await diff.get();
226
+ logContent = await log.get();
227
+ const promptConfig = {
228
+ overridePaths: runConfig.discoveredConfigDirs || [],
229
+ overrides: runConfig.overrides || false
230
+ };
231
+ // Always ensure output directory exists for request/response files
232
+ const outputDirectory = runConfig.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
233
+ const storage = createStorage();
234
+ await storage.ensureDirectory(outputDirectory);
235
+ // Create adapters for ai-service
236
+ const aiConfig = toAIConfig(runConfig);
237
+ const aiStorageAdapter = createStorageAdapter(outputDirectory);
238
+ const aiLogger = createLoggerAdapter(isDryRun);
239
+ // Read context from files if provided
240
+ const contextFromFiles = await readContextFiles((_runConfig_release4 = runConfig.release) === null || _runConfig_release4 === void 0 ? void 0 : _runConfig_release4.contextFiles, logger);
241
+ // Combine file context with existing context
242
+ const combinedContext = [
243
+ (_runConfig_release5 = runConfig.release) === null || _runConfig_release5 === void 0 ? void 0 : _runConfig_release5.context,
244
+ contextFromFiles
245
+ ].filter(Boolean).join('\n\n---\n\n');
246
+ // Run agentic release notes generation
247
+ // NOTE: No milestone/GitHub integration - pure git only
248
+ const agenticResult = await runAgenticRelease({
249
+ fromRef,
250
+ toRef,
251
+ logContent,
252
+ diffContent,
253
+ milestoneIssues: '',
254
+ releaseFocus: (_runConfig_release6 = runConfig.release) === null || _runConfig_release6 === void 0 ? void 0 : _runConfig_release6.focus,
255
+ userContext: combinedContext || undefined,
256
+ targetVersion: (_runConfig_release7 = runConfig.release) === null || _runConfig_release7 === void 0 ? void 0 : _runConfig_release7.version,
257
+ 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',
258
+ maxIterations: ((_runConfig_release8 = runConfig.release) === null || _runConfig_release8 === void 0 ? void 0 : _runConfig_release8.maxAgenticIterations) || 30,
259
+ debug: runConfig.debug,
260
+ debugRequestFile: getOutputPath(outputDirectory, getTimestampedRequestFilename('release')),
261
+ debugResponseFile: getOutputPath(outputDirectory, getTimestampedResponseFilename('release')),
262
+ storage: aiStorageAdapter,
263
+ logger: aiLogger,
264
+ 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
265
+ });
266
+ const iterations = agenticResult.iterations || 0;
267
+ const toolCalls = agenticResult.toolCallsExecuted || 0;
268
+ logger.info(`🔍 Analysis complete: ${iterations} iterations, ${toolCalls} tool calls`);
269
+ // Generate self-reflection output if enabled
270
+ if ((_runConfig_release9 = runConfig.release) === null || _runConfig_release9 === void 0 ? void 0 : _runConfig_release9.selfReflection) {
271
+ await generateSelfReflection(agenticResult, outputDirectory, storage, logger);
272
+ }
273
+ // Apply stop-context filtering to release notes
274
+ const titleFilterResult = filterContent(agenticResult.releaseNotes.title, runConfig.stopContext);
275
+ const bodyFilterResult = filterContent(agenticResult.releaseNotes.body, runConfig.stopContext);
276
+ let releaseSummary = {
277
+ title: titleFilterResult.filtered,
278
+ body: bodyFilterResult.filtered
279
+ };
280
+ // Handle interactive mode
281
+ if (((_runConfig_release10 = runConfig.release) === null || _runConfig_release10 === void 0 ? void 0 : _runConfig_release10.interactive) && !isDryRun) {
282
+ requireTTY('Interactive mode requires a terminal. Use --dry-run instead.');
283
+ const interactivePromptContext = {
284
+ context: combinedContext || undefined,
285
+ directories: runConfig.contextDirectories
286
+ };
287
+ const interactiveResult = await handleInteractiveReleaseFeedback(releaseSummary, runConfig, promptConfig, interactivePromptContext, outputDirectory, storage, logContent, diffContent);
288
+ if (interactiveResult.action === 'skip') {
289
+ logger.info('RELEASE_ABORTED: Release notes generation aborted by user | Reason: User choice | Status: cancelled');
290
+ } else {
291
+ logger.info('RELEASE_FINALIZED: Release notes finalized and accepted | Status: ready | Next: Create release or save');
292
+ }
293
+ releaseSummary = interactiveResult.finalSummary;
294
+ }
295
+ // Save timestamped copy of release notes to output directory
296
+ try {
297
+ const timestampedFilename = getTimestampedReleaseNotesFilename();
298
+ const outputPath = getOutputPath(outputDirectory, timestampedFilename);
299
+ // Format the release notes as markdown
300
+ const releaseNotesContent = `# ${releaseSummary.title}\n\n${releaseSummary.body}`;
301
+ await storage.writeFile(outputPath, releaseNotesContent, 'utf-8');
302
+ logger.debug('Saved timestamped release notes: %s', outputPath);
303
+ } catch (error) {
304
+ logger.warn('RELEASE_SAVE_FAILED: Failed to save timestamped release notes | Error: %s | Impact: Notes not persisted to file', error.message);
305
+ }
306
+ // Save to specified output file if provided
307
+ const outputFile = (_runConfig_release11 = runConfig.release) === null || _runConfig_release11 === void 0 ? void 0 : _runConfig_release11.output;
308
+ if (outputFile) {
309
+ try {
310
+ const releaseNotesContent = `# ${releaseSummary.title}\n\n${releaseSummary.body}`;
311
+ await storage.writeFile(outputFile, releaseNotesContent, 'utf-8');
312
+ logger.info(`Saved release notes to: ${outputFile}`);
313
+ } catch (error) {
314
+ logger.error(`Failed to save release notes to ${outputFile}: ${error.message}`);
315
+ }
316
+ }
317
+ if (isDryRun) {
318
+ logger.info('RELEASE_SUMMARY_COMPLETE: Generated release summary successfully | Status: completed');
319
+ logger.info('RELEASE_SUMMARY_TITLE: %s', releaseSummary.title);
320
+ logger.info('RELEASE_SUMMARY_BODY: %s', releaseSummary.body);
321
+ }
322
+ return releaseSummary;
323
+ };
324
+
325
+ export { execute };
326
+ //# sourceMappingURL=release.js.map