@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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/pull_request_template.md +48 -0
- package/.github/workflows/deploy-docs.yml +59 -0
- package/.github/workflows/npm-publish.yml +48 -0
- package/.github/workflows/test.yml +48 -0
- package/CHANGELOG.md +92 -0
- package/CONTRIBUTING.md +438 -0
- package/LICENSE +190 -0
- package/PROJECT_SUMMARY.md +318 -0
- package/README.md +444 -0
- package/RELEASE_CHECKLIST.md +182 -0
- package/dist/application.js +166 -0
- package/dist/application.js.map +1 -0
- package/dist/commands/release.js +326 -0
- package/dist/commands/release.js.map +1 -0
- package/dist/constants.js +122 -0
- package/dist/constants.js.map +1 -0
- package/dist/logging.js +176 -0
- package/dist/logging.js.map +1 -0
- package/dist/main.js +24 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-server.js +17467 -0
- package/dist/mcp-server.js.map +7 -0
- package/dist/utils/config.js +89 -0
- package/dist/utils/config.js.map +1 -0
- package/docs/AI_GUIDE.md +618 -0
- package/eslint.config.mjs +85 -0
- package/guide/architecture.md +776 -0
- package/guide/commands.md +580 -0
- package/guide/configuration.md +779 -0
- package/guide/mcp-integration.md +708 -0
- package/guide/overview.md +225 -0
- package/package.json +91 -0
- package/scripts/build-mcp.js +115 -0
- package/scripts/test-mcp-compliance.js +254 -0
- package/src/application.ts +246 -0
- package/src/commands/release.ts +450 -0
- package/src/constants.ts +162 -0
- package/src/logging.ts +210 -0
- package/src/main.ts +25 -0
- package/src/mcp/prompts/index.ts +98 -0
- package/src/mcp/resources.ts +121 -0
- package/src/mcp/server.ts +195 -0
- package/src/mcp/tools.ts +219 -0
- package/src/types.ts +131 -0
- package/src/utils/config.ts +181 -0
- package/tests/application.test.ts +114 -0
- package/tests/commands/commit.test.ts +248 -0
- package/tests/commands/release.test.ts +325 -0
- package/tests/constants.test.ts +118 -0
- package/tests/logging.test.ts +142 -0
- package/tests/mcp/prompts/index.test.ts +202 -0
- package/tests/mcp/resources.test.ts +166 -0
- package/tests/mcp/tools.test.ts +211 -0
- package/tests/utils/config.test.ts +212 -0
- package/tsconfig.json +32 -0
- package/vite.config.ts +107 -0
- package/vitest.config.ts +40 -0
- package/website/index.html +14 -0
- package/website/src/App.css +142 -0
- package/website/src/App.tsx +34 -0
- package/website/src/components/Commands.tsx +182 -0
- package/website/src/components/Configuration.tsx +214 -0
- package/website/src/components/Examples.tsx +234 -0
- package/website/src/components/Footer.css +99 -0
- package/website/src/components/Footer.tsx +93 -0
- package/website/src/components/GettingStarted.tsx +94 -0
- package/website/src/components/Hero.css +95 -0
- package/website/src/components/Hero.tsx +50 -0
- package/website/src/components/Navigation.css +102 -0
- package/website/src/components/Navigation.tsx +57 -0
- package/website/src/index.css +36 -0
- package/website/src/main.tsx +10 -0
- package/website/vite.config.ts +12 -0
package/src/constants.ts
ADDED
|
@@ -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
|
+
}
|