@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,162 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Version and build information (replaced at build time)
6
+ */
7
+ export const VERSION = '__VERSION__ (__GIT_BRANCH__/__GIT_COMMIT__ __GIT_TAGS__ __GIT_COMMIT_DATE__) __SYSTEM_INFO__';
8
+ export const BUILD_HOSTNAME = '__BUILD_HOSTNAME__';
9
+ export const BUILD_TIMESTAMP = '__BUILD_TIMESTAMP__';
10
+
11
+ /**
12
+ * Program identification
13
+ */
14
+ export const PROGRAM_NAME = 'kilde';
15
+
16
+ /**
17
+ * Character encodings
18
+ */
19
+ export const DEFAULT_CHARACTER_ENCODING = 'utf-8';
20
+ export const DEFAULT_BINARY_TO_TEXT_ENCODING = 'base64';
21
+
22
+ /**
23
+ * Global defaults
24
+ */
25
+ export const DEFAULT_VERBOSE = false;
26
+ export const DEFAULT_DRY_RUN = false;
27
+ export const DEFAULT_DEBUG = false;
28
+ export const DEFAULT_MODEL = 'gpt-4o-mini';
29
+ export const DEFAULT_MODEL_STRONG = 'gpt-4o';
30
+ export const DEFAULT_OPENAI_REASONING = 'low';
31
+ export const DEFAULT_OPENAI_MAX_OUTPUT_TOKENS = 10000;
32
+ export const DEFAULT_OUTPUT_DIRECTORY = 'output/kilde';
33
+
34
+ /**
35
+ * Buffer size for git commands that may produce large output
36
+ */
37
+ export const DEFAULT_GIT_COMMAND_MAX_BUFFER = 50 * 1024 * 1024; // 50MB
38
+
39
+ /**
40
+ * Configuration directories
41
+ */
42
+ export const DEFAULT_CONFIG_DIR = '.kilde';
43
+ export const DEFAULT_PREFERENCES_DIRECTORY = path.join(os.homedir(), '.kilde');
44
+
45
+ /**
46
+ * Git commit aliases
47
+ */
48
+ export const DEFAULT_FROM_COMMIT_ALIAS = 'main';
49
+ export const DEFAULT_TO_COMMIT_ALIAS = 'HEAD';
50
+
51
+ /**
52
+ * Commit command defaults
53
+ */
54
+ export const DEFAULT_ADD = false;
55
+ export const DEFAULT_CACHED = false;
56
+ export const DEFAULT_SENDIT_MODE = false;
57
+ export const DEFAULT_INTERACTIVE_MODE = false;
58
+ export const DEFAULT_AMEND_MODE = false;
59
+ // CRITICAL: Keep this low (3-5) to prevent log context contamination.
60
+ // The LLM tends to pattern-match against recent commits instead of describing
61
+ // the actual diff when it sees too much commit history. Set to 0 to disable.
62
+ export const DEFAULT_MESSAGE_LIMIT = 3;
63
+ export const DEFAULT_MAX_DIFF_BYTES = 20480; // 20KB limit per file
64
+
65
+ /**
66
+ * Excluded patterns for git operations
67
+ */
68
+ export const DEFAULT_EXCLUDED_PATTERNS = [
69
+ 'node_modules', 'package-lock.json', 'yarn.lock', 'bun.lockb',
70
+ 'composer.lock', 'Cargo.lock', 'Gemfile.lock',
71
+ 'dist', 'build', 'out', '.next', '.nuxt', 'coverage',
72
+ '.vscode', '.idea', '.DS_Store', '.git', '.gitignore',
73
+ 'logs', 'tmp', '.cache', '*.log', '.env', '.env.*',
74
+ '*.pem', '*.crt', '*.key', '*.sqlite', '*.db',
75
+ '*.zip', '*.tar', '*.gz', '*.exe', '*.bin'
76
+ ];
77
+
78
+ /**
79
+ * Available commands
80
+ */
81
+ export const COMMAND_COMMIT = 'commit';
82
+ export const COMMAND_RELEASE = 'release';
83
+
84
+ export const ALLOWED_COMMANDS = [
85
+ COMMAND_COMMIT,
86
+ COMMAND_RELEASE,
87
+ ];
88
+
89
+ export const DEFAULT_COMMAND = COMMAND_COMMIT;
90
+
91
+ /**
92
+ * Default ignore patterns for file operations
93
+ */
94
+ export const DEFAULT_IGNORE_PATTERNS = [
95
+ 'node_modules/**',
96
+ '**/*.log',
97
+ '.git/**',
98
+ 'dist/**',
99
+ 'build/**',
100
+ 'coverage/**',
101
+ 'output/**',
102
+ '.DS_Store',
103
+ '*.tmp',
104
+ '*.cache',
105
+ '**/.kilde-*.json',
106
+ ];
107
+
108
+ /**
109
+ * Directory prefix for kilde internal files
110
+ */
111
+ export const DEFAULT_DIRECTORY_PREFIX = '.kilde';
112
+
113
+ /**
114
+ * Date formats
115
+ */
116
+ export const DATE_FORMAT_YEAR_MONTH_DAY = 'YYYY-MM-DD';
117
+ export const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = 'YYYY-MM-DD-HHmmss';
118
+ export const INTERNAL_DATETIME_FORMAT = 'YYYY-MM-DD_HH-mm-ss';
119
+
120
+ /**
121
+ * Kilde defaults configuration object
122
+ */
123
+ export const KILDE_DEFAULTS = {
124
+ dryRun: DEFAULT_DRY_RUN,
125
+ verbose: DEFAULT_VERBOSE,
126
+ debug: DEFAULT_DEBUG,
127
+ model: DEFAULT_MODEL,
128
+ openaiReasoning: DEFAULT_OPENAI_REASONING as 'low' | 'medium' | 'high',
129
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS,
130
+ outputDirectory: DEFAULT_OUTPUT_DIRECTORY,
131
+ configDirectory: DEFAULT_CONFIG_DIR,
132
+ discoveredConfigDirs: [] as string[],
133
+ resolvedConfigDirs: [] as string[],
134
+ excludedPatterns: DEFAULT_EXCLUDED_PATTERNS,
135
+ contextDirectories: [] as string[],
136
+ overrides: false,
137
+
138
+ commit: {
139
+ add: DEFAULT_ADD,
140
+ cached: DEFAULT_CACHED,
141
+ sendit: DEFAULT_SENDIT_MODE,
142
+ interactive: DEFAULT_INTERACTIVE_MODE,
143
+ amend: DEFAULT_AMEND_MODE,
144
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
145
+ skipFileCheck: false,
146
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
147
+ contextFiles: undefined,
148
+ openaiReasoning: DEFAULT_OPENAI_REASONING as 'low' | 'medium' | 'high',
149
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS,
150
+ },
151
+
152
+ release: {
153
+ fromTag: undefined,
154
+ toTag: 'HEAD',
155
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
156
+ interactive: DEFAULT_INTERACTIVE_MODE,
157
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
158
+ contextFiles: undefined,
159
+ openaiReasoning: DEFAULT_OPENAI_REASONING as 'low' | 'medium' | 'high',
160
+ openaiMaxOutputTokens: DEFAULT_OPENAI_MAX_OUTPUT_TOKENS,
161
+ },
162
+ };
package/src/logging.ts ADDED
@@ -0,0 +1,210 @@
1
+ import winston from 'winston';
2
+ // eslint-disable-next-line no-restricted-imports
3
+ import * as fs from 'fs';
4
+ import path from 'path';
5
+ import { DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS, PROGRAM_NAME, DEFAULT_OUTPUT_DIRECTORY } from './constants';
6
+
7
+ export interface LogContext {
8
+ [key: string]: any;
9
+ }
10
+
11
+ // Track if debug directory has been ensured for this session
12
+ let debugDirectoryEnsured = false;
13
+
14
+ const ensureDebugDirectory = () => {
15
+ if (debugDirectoryEnsured) return;
16
+
17
+ const debugDir = path.join(DEFAULT_OUTPUT_DIRECTORY, 'debug');
18
+
19
+ try {
20
+ fs.mkdirSync(debugDir, { recursive: true });
21
+ debugDirectoryEnsured = true;
22
+ } catch (error) {
23
+ // eslint-disable-next-line no-console
24
+ console.error(`Failed to create debug directory ${debugDir}:`, error);
25
+ }
26
+ };
27
+
28
+ const generateDebugLogFilename = () => {
29
+ const now = new Date();
30
+ const timestamp = now.toISOString()
31
+ .replace(/[-:]/g, '')
32
+ .replace(/\./g, '')
33
+ .replace('T', '-')
34
+ .replace('Z', '');
35
+
36
+ return `${timestamp}-debug.log`;
37
+ };
38
+
39
+ const createTransports = (level: string) => {
40
+ const transports: winston.transport[] = [];
41
+
42
+ // When running as MCP server, NEVER add console transports as they interfere with the protocol
43
+ // All output must go through log capture mechanism instead
44
+ const isMcpServer = process.env.KILDE_MCP_SERVER === 'true';
45
+
46
+ if (!isMcpServer) {
47
+ // Always add console transport for info level and above (only when NOT in MCP mode)
48
+ if (level === 'info') {
49
+ transports.push(
50
+ new winston.transports.Console({
51
+ format: winston.format.combine(
52
+ winston.format.colorize(),
53
+ winston.format.printf(({ level, message, dryRun }): string => {
54
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
55
+ // For info level messages, don't show the level prefix
56
+ if (level.includes('info')) {
57
+ return `${dryRunPrefix}${String(message)}`;
58
+ }
59
+ // For warn, error, etc., show the level prefix
60
+ return `${level}: ${dryRunPrefix}${String(message)}`;
61
+ })
62
+ )
63
+ })
64
+ );
65
+ } else {
66
+ // For debug/verbose levels, add console transport that shows info and above
67
+ transports.push(
68
+ new winston.transports.Console({
69
+ level: 'info', // Show info, warn, and error on console
70
+ format: winston.format.combine(
71
+ winston.format.colorize(),
72
+ winston.format.printf(({ timestamp, level, message, dryRun, ...meta }): string => {
73
+ // For info level messages, use simpler format without timestamp
74
+ if (level.includes('info')) {
75
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
76
+ return `${dryRunPrefix}${String(message)}`;
77
+ }
78
+
79
+ // Filter out winston internal metadata
80
+ const filteredMeta = Object.keys(meta).reduce((acc, key) => {
81
+ if (!['level', 'message', 'timestamp', 'dryRun', 'service', 'splat', 'Symbol(level)', 'Symbol(message)'].includes(key)) {
82
+ acc[key] = meta[key];
83
+ }
84
+ return acc;
85
+ }, {} as Record<string, any>);
86
+
87
+ const metaStr = Object.keys(filteredMeta).length ? ` ${JSON.stringify(filteredMeta, null, 2)}` : '';
88
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
89
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
90
+ })
91
+ )
92
+ })
93
+ );
94
+ }
95
+ }
96
+
97
+ // Add file transport for debug levels (debug and silly)
98
+ if (level === 'debug' || level === 'silly') {
99
+ ensureDebugDirectory();
100
+
101
+ const debugLogPath = path.join(DEFAULT_OUTPUT_DIRECTORY, 'debug', generateDebugLogFilename());
102
+
103
+ transports.push(
104
+ new winston.transports.File({
105
+ filename: debugLogPath,
106
+ level: 'debug', // Capture debug and above in the file
107
+ format: winston.format.combine(
108
+ winston.format.timestamp({ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS }),
109
+ winston.format.errors({ stack: true }),
110
+ winston.format.splat(),
111
+ winston.format.printf(({ timestamp, level, message, service, ...meta }) => {
112
+ // Filter out winston internal metadata and properly format remaining meta
113
+ const filteredMeta = Object.keys(meta).reduce((acc, key) => {
114
+ // Skip internal winston fields
115
+ if (!['level', 'message', 'timestamp', 'splat', 'Symbol(level)', 'Symbol(message)'].includes(key)) {
116
+ acc[key] = meta[key];
117
+ }
118
+ return acc;
119
+ }, {} as Record<string, any>);
120
+
121
+ const metaStr = Object.keys(filteredMeta).length
122
+ ? ` ${JSON.stringify(filteredMeta, null, 2)}`
123
+ : '';
124
+ const serviceStr = service ? ` [${service}]` : '';
125
+ return `${timestamp}${serviceStr} ${level}: ${message}${metaStr}`;
126
+ })
127
+ )
128
+ })
129
+ );
130
+ }
131
+
132
+ return transports;
133
+ };
134
+
135
+ const createFormat = (level: string) => {
136
+ const baseFormats = [
137
+ winston.format.errors({ stack: true }),
138
+ winston.format.splat(),
139
+ ];
140
+
141
+ if (level === 'info') {
142
+ return winston.format.combine(
143
+ ...baseFormats,
144
+ winston.format.printf(({ message, dryRun, ..._meta }): string => {
145
+ // Auto-format dry-run messages
146
+ if (dryRun) {
147
+ return `🔍 DRY RUN: ${message}`;
148
+ }
149
+ return String(message);
150
+ })
151
+ );
152
+ }
153
+
154
+ return winston.format.combine(
155
+ winston.format.timestamp({ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS }),
156
+ ...baseFormats,
157
+ winston.format.printf(({ timestamp, level, message, dryRun, ...meta }): string => {
158
+ // Filter out winston internal metadata
159
+ const filteredMeta = Object.keys(meta).reduce((acc, key) => {
160
+ if (!['level', 'message', 'timestamp', 'dryRun', 'service', 'splat', 'Symbol(level)', 'Symbol(message)'].includes(key)) {
161
+ acc[key] = meta[key];
162
+ }
163
+ return acc;
164
+ }, {} as Record<string, any>);
165
+
166
+ const metaStr = Object.keys(filteredMeta).length ? ` ${JSON.stringify(filteredMeta, null, 2)}` : '';
167
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
168
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
169
+ })
170
+ );
171
+ };
172
+
173
+ // Create the logger instance once
174
+ const logger = winston.createLogger({
175
+ level: 'info',
176
+ format: createFormat('info'),
177
+ defaultMeta: { service: PROGRAM_NAME },
178
+ transports: createTransports('info'),
179
+ });
180
+
181
+ export const setLogLevel = (level: string) => {
182
+ // Reconfigure the existing logger instead of creating a new one
183
+ logger.configure({
184
+ level,
185
+ format: createFormat(level),
186
+ defaultMeta: { service: PROGRAM_NAME },
187
+ transports: createTransports(level),
188
+ });
189
+ };
190
+
191
+ export const getLogger = () => logger;
192
+
193
+ /**
194
+ * Get a logger that automatically formats messages for dry-run mode
195
+ */
196
+ export const getDryRunLogger = (isDryRun: boolean) => {
197
+ if (!isDryRun) {
198
+ return logger;
199
+ }
200
+
201
+ // Return a wrapper that adds dry-run context to all log calls
202
+ return {
203
+ info: (message: string, ...args: any[]) => logger.info(message, { dryRun: true }, ...args),
204
+ warn: (message: string, ...args: any[]) => logger.warn(message, { dryRun: true }, ...args),
205
+ error: (message: string, ...args: any[]) => logger.error(message, { dryRun: true }, ...args),
206
+ debug: (message: string, ...args: any[]) => logger.debug(message, { dryRun: true }, ...args),
207
+ verbose: (message: string, ...args: any[]) => logger.verbose(message, { dryRun: true }, ...args),
208
+ silly: (message: string, ...args: any[]) => logger.silly(message, { dryRun: true }, ...args),
209
+ };
210
+ };
package/src/main.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { runApplication } from './application';
3
+ import { getLogger } from './logging';
4
+
5
+ /**
6
+ * Main entry point for Kilde
7
+ */
8
+ async function main(): Promise<void> {
9
+ try {
10
+ await runApplication();
11
+ } catch (error: any) {
12
+ const logger = getLogger();
13
+ logger.error('MAIN_ERROR_EXIT: Exiting due to error | Error: %s | Stack: %s | Status: terminating', error.message, error.stack);
14
+ process.exit(1);
15
+ }
16
+ }
17
+
18
+ // Properly handle the main function with error handling and explicit process exit
19
+ main().then(() => {
20
+ process.exit(0);
21
+ }).catch((error) => {
22
+ const logger = getLogger();
23
+ logger.error('MAIN_UNHANDLED_ERROR: Unhandled error in main process | Error: %s | Type: unhandled', error.message || error);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * MCP Prompts for Kilde
3
+ *
4
+ * Provides workflow templates for common operations
5
+ */
6
+
7
+ /**
8
+ * Get list of available prompts
9
+ */
10
+ export function getPrompts() {
11
+ return [
12
+ {
13
+ name: 'commit-workflow',
14
+ description: 'Complete workflow for generating and creating a commit',
15
+ arguments: [
16
+ {
17
+ name: 'context',
18
+ description: 'Additional context for the commit message',
19
+ required: false,
20
+ },
21
+ ],
22
+ },
23
+ {
24
+ name: 'release-workflow',
25
+ description: 'Complete workflow for generating release notes',
26
+ arguments: [
27
+ {
28
+ name: 'version',
29
+ description: 'Version number for the release',
30
+ required: false,
31
+ },
32
+ {
33
+ name: 'fromTag',
34
+ description: 'Start tag for release notes',
35
+ required: false,
36
+ },
37
+ ],
38
+ },
39
+ ];
40
+ }
41
+
42
+ /**
43
+ * Get a specific prompt by name
44
+ */
45
+ export async function getPrompt(
46
+ name: string,
47
+ args?: Record<string, string>
48
+ ): Promise<{ description?: string; messages: Array<{ role: 'user' | 'assistant'; content: { type: 'text'; text: string } }> }> {
49
+ if (name === 'commit-workflow') {
50
+ const context = args?.context || '';
51
+ const contextPart = context ? `\n\nAdditional context: ${context}` : '';
52
+
53
+ return {
54
+ description: 'Workflow for generating AI-powered commit messages',
55
+ messages: [
56
+ {
57
+ role: 'user' as const,
58
+ content: {
59
+ type: 'text' as const,
60
+ text: `I need to create a commit. Please help me generate a good commit message based on my staged changes.${contextPart}
61
+
62
+ Steps:
63
+ 1. First, use kilde_commit with cached=true and dryRun=true to preview the commit message
64
+ 2. Review the generated message
65
+ 3. If it looks good, use kilde_commit with sendit=true to create the commit
66
+ 4. If adjustments are needed, provide feedback and regenerate`
67
+ }
68
+ }
69
+ ]
70
+ };
71
+ } else if (name === 'release-workflow') {
72
+ const version = args?.version || '';
73
+ const fromTag = args?.fromTag || '';
74
+ const versionPart = version ? `\n- version: "${version}"` : '';
75
+ const fromTagPart = fromTag ? `\n- fromTag: "${fromTag}"` : '';
76
+
77
+ return {
78
+ description: 'Workflow for generating release notes from git history',
79
+ messages: [
80
+ {
81
+ role: 'user' as const,
82
+ content: {
83
+ type: 'text' as const,
84
+ text: `I need to generate release notes for a new release.${versionPart}${fromTagPart}
85
+
86
+ Steps:
87
+ 1. Use kilde_release with dryRun=true to preview the release notes
88
+ 2. Review the generated notes
89
+ 3. If they look good, use kilde_release with output="RELEASE_NOTES.md" to save them
90
+ 4. If adjustments are needed, use the focus parameter to guide the generation`
91
+ }
92
+ }
93
+ ]
94
+ };
95
+ } else {
96
+ throw new Error(`Unknown prompt: ${name}`);
97
+ }
98
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * MCP Resources for Kilde
3
+ *
4
+ * Provides access to configuration and status information
5
+ */
6
+
7
+ import { createStorage } from '@grunnverk/shared';
8
+ import { getEffectiveConfig } from '../utils/config';
9
+ import { execSync } from 'child_process';
10
+
11
+ /**
12
+ * Get list of available resources
13
+ */
14
+ export function getResources() {
15
+ return [
16
+ {
17
+ uri: 'kilde://config',
18
+ name: 'Current configuration',
19
+ description: 'Current kilde configuration (merged from defaults, config file, and CLI args)',
20
+ mimeType: 'application/json',
21
+ },
22
+ {
23
+ uri: 'kilde://status',
24
+ name: 'Git repository status',
25
+ description: 'Current git repository status and branch information',
26
+ mimeType: 'text/plain',
27
+ },
28
+ {
29
+ uri: 'kilde://workspace',
30
+ name: 'Workspace information',
31
+ description: 'Information about the current workspace (working directory, files, etc.)',
32
+ mimeType: 'application/json',
33
+ },
34
+ ];
35
+ }
36
+
37
+ /**
38
+ * Read a resource by URI
39
+ */
40
+ export async function readResource(uri: string): Promise<{ contents: Array<{ uri: string; mimeType?: string; text: string }> }> {
41
+ const storage = createStorage();
42
+
43
+ if (uri === 'kilde://config') {
44
+ // Return current configuration
45
+ const config = await getEffectiveConfig();
46
+ return {
47
+ contents: [
48
+ {
49
+ uri,
50
+ mimeType: 'application/json',
51
+ text: JSON.stringify(config, null, 2),
52
+ },
53
+ ],
54
+ };
55
+ } else if (uri === 'kilde://status') {
56
+ // Return git status
57
+ try {
58
+ const status = execSync('git status', { encoding: 'utf-8', cwd: process.cwd() });
59
+ const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: process.cwd() }).trim();
60
+ const lastCommit = execSync('git log -1 --oneline', { encoding: 'utf-8', cwd: process.cwd() }).trim();
61
+
62
+ const statusText = `Current Branch: ${branch}\nLast Commit: ${lastCommit}\n\n${status}`;
63
+
64
+ return {
65
+ contents: [
66
+ {
67
+ uri,
68
+ mimeType: 'text/plain',
69
+ text: statusText,
70
+ },
71
+ ],
72
+ };
73
+ } catch (error: any) {
74
+ return {
75
+ contents: [
76
+ {
77
+ uri,
78
+ mimeType: 'text/plain',
79
+ text: `Error reading git status: ${error.message}`,
80
+ },
81
+ ],
82
+ };
83
+ }
84
+ } else if (uri === 'kilde://workspace') {
85
+ // Return workspace information
86
+ try {
87
+ const workingDir = process.cwd();
88
+ const isGitRepo = execSync('git rev-parse --is-inside-work-tree', { encoding: 'utf-8', cwd: workingDir }).trim() === 'true';
89
+ const gitRoot = isGitRepo ? execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', cwd: workingDir }).trim() : null;
90
+
91
+ const workspace = {
92
+ workingDirectory: workingDir,
93
+ isGitRepository: isGitRepo,
94
+ gitRoot: gitRoot,
95
+ configExists: await storage.exists('.kilde/config.yaml') || await storage.exists('.kilderc.yaml'),
96
+ };
97
+
98
+ return {
99
+ contents: [
100
+ {
101
+ uri,
102
+ mimeType: 'application/json',
103
+ text: JSON.stringify(workspace, null, 2),
104
+ },
105
+ ],
106
+ };
107
+ } catch (error: any) {
108
+ return {
109
+ contents: [
110
+ {
111
+ uri,
112
+ mimeType: 'application/json',
113
+ text: JSON.stringify({ error: error.message }, null, 2),
114
+ },
115
+ ],
116
+ };
117
+ }
118
+ } else {
119
+ throw new Error(`Unknown resource URI: ${uri}`);
120
+ }
121
+ }