@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,195 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Kilde MCP Server
4
+ *
5
+ * Exposes kilde commands via Model Context Protocol
6
+ */
7
+
8
+ /* eslint-disable import/extensions */
9
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import { z } from 'zod';
12
+ import { executeTool } from './tools.js';
13
+ import { getResources, readResource } from './resources.js';
14
+ import { getPrompts, getPrompt } from './prompts/index.js';
15
+ import { getLogger } from '../logging.js';
16
+ import winston from 'winston';
17
+ /* eslint-enable import/extensions */
18
+
19
+ async function main() {
20
+ // Mark that we're running as MCP server
21
+ process.env.KILDE_MCP_SERVER = 'true';
22
+
23
+ // Remove console transports from logger
24
+ const logger = getLogger();
25
+ const transports = [...((logger as any).transports || [])];
26
+ for (const transport of transports) {
27
+ if (transport instanceof winston.transports.Console) {
28
+ logger.remove(transport);
29
+ }
30
+ }
31
+
32
+ // Initialize MCP server
33
+ const server = new McpServer(
34
+ {
35
+ name: 'kilde',
36
+ version: '0.1.0',
37
+ },
38
+ {
39
+ capabilities: {
40
+ tools: {},
41
+ resources: {},
42
+ prompts: {},
43
+ },
44
+ }
45
+ );
46
+
47
+ // ========================================================================
48
+ // Tools Handlers
49
+ // ========================================================================
50
+
51
+ // Register kilde_commit
52
+ server.tool(
53
+ 'kilde_commit',
54
+ 'Generate AI-powered commit message and optionally create the commit',
55
+ {
56
+ add: z.boolean().optional(),
57
+ cached: z.boolean().optional(),
58
+ sendit: z.boolean().optional(),
59
+ interactive: z.boolean().optional(),
60
+ amend: z.boolean().optional(),
61
+ context: z.string().optional(),
62
+ contextFiles: z.array(z.string()).optional(),
63
+ dryRun: z.boolean().optional(),
64
+ verbose: z.boolean().optional(),
65
+ debug: z.boolean().optional(),
66
+ },
67
+ async (args) => {
68
+ const context = {
69
+ workingDirectory: process.cwd(),
70
+ config: undefined,
71
+ logger: getLogger(),
72
+ };
73
+
74
+ try {
75
+ return await executeTool('kilde_commit', args, context);
76
+ } catch (error: any) {
77
+ logger.error(`Tool kilde_commit failed: ${error.message}`);
78
+ return {
79
+ content: [{
80
+ type: 'text' as const,
81
+ text: `Error: ${error.message}`,
82
+ }],
83
+ isError: true,
84
+ };
85
+ }
86
+ }
87
+ );
88
+
89
+ // Register kilde_release
90
+ server.tool(
91
+ 'kilde_release',
92
+ 'Generate release notes from git commit history (tag-based)',
93
+ {
94
+ fromTag: z.string().optional(),
95
+ toTag: z.string().optional(),
96
+ version: z.string().optional(),
97
+ output: z.string().optional(),
98
+ interactive: z.boolean().optional(),
99
+ focus: z.string().optional(),
100
+ context: z.string().optional(),
101
+ contextFiles: z.array(z.string()).optional(),
102
+ dryRun: z.boolean().optional(),
103
+ verbose: z.boolean().optional(),
104
+ debug: z.boolean().optional(),
105
+ },
106
+ async (args) => {
107
+ const context = {
108
+ workingDirectory: process.cwd(),
109
+ config: undefined,
110
+ logger: getLogger(),
111
+ };
112
+
113
+ try {
114
+ return await executeTool('kilde_release', args, context);
115
+ } catch (error: any) {
116
+ logger.error(`Tool kilde_release failed: ${error.message}`);
117
+ return {
118
+ content: [{
119
+ type: 'text' as const,
120
+ text: `Error: ${error.message}`,
121
+ }],
122
+ isError: true,
123
+ };
124
+ }
125
+ }
126
+ );
127
+
128
+ // ========================================================================
129
+ // Resources Handlers
130
+ // ========================================================================
131
+
132
+ const resources = getResources();
133
+ for (const resource of resources) {
134
+ server.resource(
135
+ resource.name,
136
+ resource.uri,
137
+ {
138
+ description: resource.description || '',
139
+ },
140
+ async () => {
141
+ const data = await readResource(resource.uri);
142
+ return data;
143
+ }
144
+ );
145
+ }
146
+
147
+ // ========================================================================
148
+ // Prompts Handlers
149
+ // ========================================================================
150
+
151
+ const prompts = getPrompts();
152
+ for (const prompt of prompts) {
153
+ // Convert prompt arguments to zod schema
154
+ const promptArgs: Record<string, z.ZodTypeAny> = {};
155
+ if (prompt.arguments) {
156
+ for (const arg of prompt.arguments) {
157
+ promptArgs[arg.name] = arg.required ? z.string() : z.string().optional();
158
+ }
159
+ }
160
+
161
+ server.prompt(
162
+ prompt.name,
163
+ prompt.description,
164
+ promptArgs,
165
+ async (args, _extra) => {
166
+ // Convert args to Record<string, string> for getPrompt
167
+ const argsRecord: Record<string, string> = {};
168
+ for (const [key, value] of Object.entries(args)) {
169
+ if (typeof value === 'string') {
170
+ argsRecord[key] = value;
171
+ }
172
+ }
173
+ const result = await getPrompt(prompt.name, argsRecord);
174
+ return result;
175
+ }
176
+ );
177
+ }
178
+
179
+ // ========================================================================
180
+ // Server Lifecycle
181
+ // ========================================================================
182
+
183
+ // Start the server with stdio transport
184
+ const transport = new StdioServerTransport();
185
+ await server.connect(transport);
186
+
187
+ logger.info('Kilde MCP server started');
188
+ }
189
+
190
+ // Run the server
191
+ main().catch((error) => {
192
+ const logger = getLogger();
193
+ logger.error(`MCP server failed: ${error.message}`);
194
+ process.exit(1);
195
+ });
@@ -0,0 +1,219 @@
1
+ /**
2
+ * MCP Tools for Kilde
3
+ *
4
+ * Exposes commit and release commands as MCP tools
5
+ */
6
+
7
+ import * as CommandsGit from '@grunnverk/commands-git';
8
+ import * as ReleaseCommand from '../commands/release';
9
+ import { Config } from '@grunnverk/core';
10
+ import { getLogger } from '../logging';
11
+ import { createStorage } from '@grunnverk/shared';
12
+ import { DEFAULT_CONFIG_DIR } from '../constants';
13
+
14
+ export interface ToolContext {
15
+ workingDirectory: string;
16
+ config: Config | undefined;
17
+ logger: any;
18
+ }
19
+
20
+ /**
21
+ * Execute a tool with the given name and arguments
22
+ */
23
+ export async function executeTool(
24
+ name: string,
25
+ args: Record<string, any>,
26
+ context: ToolContext
27
+ ): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
28
+ const logger = context.logger || getLogger();
29
+ const storage = createStorage();
30
+
31
+ // Build config from args with required fields
32
+ const config: Config = {
33
+ configDirectory: DEFAULT_CONFIG_DIR,
34
+ discoveredConfigDirs: [],
35
+ resolvedConfigDirs: [],
36
+ ...(context.config || {}),
37
+ dryRun: args.dryRun ?? false,
38
+ verbose: args.verbose ?? false,
39
+ debug: args.debug ?? false,
40
+ } as Config;
41
+
42
+ try {
43
+ if (name === 'kilde_commit') {
44
+ // Build commit config
45
+ config.commit = {
46
+ add: args.add ?? false,
47
+ cached: args.cached ?? false,
48
+ sendit: args.sendit ?? false,
49
+ interactive: args.interactive ?? false,
50
+ amend: args.amend ?? false,
51
+ context: args.context,
52
+ contextFiles: args.contextFiles,
53
+ };
54
+
55
+ // Execute commit command
56
+ const summary = await CommandsGit.commit(config);
57
+
58
+ return {
59
+ content: [
60
+ {
61
+ type: 'text' as const,
62
+ text: summary
63
+ }
64
+ ]
65
+ };
66
+
67
+ } else if (name === 'kilde_release') {
68
+ // Build release config
69
+ config.release = {
70
+ from: args.fromTag,
71
+ to: args.toTag ?? 'HEAD',
72
+ interactive: args.interactive ?? false,
73
+ focus: args.focus,
74
+ context: args.context,
75
+ contextFiles: args.contextFiles,
76
+ };
77
+
78
+ // Add non-standard fields
79
+ if (args.version) (config.release as any).version = args.version;
80
+ if (args.output) (config.release as any).output = args.output;
81
+
82
+ // Execute release command
83
+ const releaseSummary = await ReleaseCommand.execute(config);
84
+
85
+ // If output file specified, write the result
86
+ if (args.output) {
87
+ const releaseNotesContent = `# ${releaseSummary.title}\n\n${releaseSummary.body}`;
88
+ await storage.writeFile(args.output, releaseNotesContent, 'utf-8');
89
+ }
90
+
91
+ return {
92
+ content: [
93
+ {
94
+ type: 'text' as const,
95
+ text: `# ${releaseSummary.title}\n\n${releaseSummary.body}`
96
+ }
97
+ ]
98
+ };
99
+
100
+ } else {
101
+ throw new Error(`Unknown tool: ${name}`);
102
+ }
103
+ } catch (error: any) {
104
+ logger.error(`Tool execution failed: ${error.message}`);
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Get list of available tools
111
+ */
112
+ export function getTools() {
113
+ return [
114
+ {
115
+ name: 'kilde_commit',
116
+ description: 'Generate AI-powered commit message and optionally create the commit',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ add: {
121
+ type: 'boolean',
122
+ description: 'Stage all changes before committing',
123
+ },
124
+ cached: {
125
+ type: 'boolean',
126
+ description: 'Only use staged changes',
127
+ },
128
+ sendit: {
129
+ type: 'boolean',
130
+ description: 'Automatically commit with generated message',
131
+ },
132
+ interactive: {
133
+ type: 'boolean',
134
+ description: 'Interactive mode for reviewing message',
135
+ },
136
+ amend: {
137
+ type: 'boolean',
138
+ description: 'Amend the previous commit',
139
+ },
140
+ context: {
141
+ type: 'string',
142
+ description: 'Additional context for commit message generation',
143
+ },
144
+ contextFiles: {
145
+ type: 'array',
146
+ items: { type: 'string' },
147
+ description: 'Context files to include',
148
+ },
149
+ dryRun: {
150
+ type: 'boolean',
151
+ description: 'Preview without making changes',
152
+ },
153
+ verbose: {
154
+ type: 'boolean',
155
+ description: 'Enable verbose logging',
156
+ },
157
+ debug: {
158
+ type: 'boolean',
159
+ description: 'Enable debug logging',
160
+ },
161
+ },
162
+ },
163
+ },
164
+ {
165
+ name: 'kilde_release',
166
+ description: 'Generate release notes from git commit history (tag-based)',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ fromTag: {
171
+ type: 'string',
172
+ description: 'Start tag for release notes (default: last tag)',
173
+ },
174
+ toTag: {
175
+ type: 'string',
176
+ description: 'End tag for release notes (default: HEAD)',
177
+ },
178
+ version: {
179
+ type: 'string',
180
+ description: 'Version number for the release',
181
+ },
182
+ output: {
183
+ type: 'string',
184
+ description: 'Output file path for release notes',
185
+ },
186
+ interactive: {
187
+ type: 'boolean',
188
+ description: 'Interactive mode for reviewing notes',
189
+ },
190
+ focus: {
191
+ type: 'string',
192
+ description: 'Focus area for release notes (e.g., "breaking changes")',
193
+ },
194
+ context: {
195
+ type: 'string',
196
+ description: 'Additional context for release notes generation',
197
+ },
198
+ contextFiles: {
199
+ type: 'array',
200
+ items: { type: 'string' },
201
+ description: 'Context files to include',
202
+ },
203
+ dryRun: {
204
+ type: 'boolean',
205
+ description: 'Preview without saving to file',
206
+ },
207
+ verbose: {
208
+ type: 'boolean',
209
+ description: 'Enable verbose logging',
210
+ },
211
+ debug: {
212
+ type: 'boolean',
213
+ description: 'Enable debug logging',
214
+ },
215
+ },
216
+ },
217
+ },
218
+ ];
219
+ }
package/src/types.ts ADDED
@@ -0,0 +1,131 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Configuration schema for Kilde
5
+ * Simplified for commit and release commands only
6
+ */
7
+ export const ConfigSchema = z.object({
8
+ dryRun: z.boolean().optional(),
9
+ verbose: z.boolean().optional(),
10
+ debug: z.boolean().optional(),
11
+ model: z.string().optional(),
12
+ openaiReasoning: z.enum(['low', 'medium', 'high']).optional(),
13
+ openaiMaxOutputTokens: z.number().optional(),
14
+ outputDirectory: z.string().optional(),
15
+ configDirectory: z.string().optional(),
16
+ discoveredConfigDirs: z.array(z.string()).optional(),
17
+ resolvedConfigDirs: z.array(z.string()).optional(),
18
+ excludedPatterns: z.array(z.string()).optional(),
19
+ contextDirectories: z.array(z.string()).optional(),
20
+ overrides: z.boolean().optional(),
21
+ stopContext: z.any().optional(),
22
+
23
+ // Commit configuration
24
+ commit: z.object({
25
+ add: z.boolean().optional(),
26
+ cached: z.boolean().optional(),
27
+ sendit: z.boolean().optional(),
28
+ interactive: z.boolean().optional(),
29
+ amend: z.boolean().optional(),
30
+ push: z.union([z.boolean(), z.string()]).optional(),
31
+ messageLimit: z.number().optional(),
32
+ context: z.string().optional(),
33
+ contextFiles: z.array(z.string()).optional(),
34
+ direction: z.string().optional(),
35
+ skipFileCheck: z.boolean().optional(),
36
+ maxDiffBytes: z.number().optional(),
37
+ model: z.string().optional(),
38
+ openaiReasoning: z.enum(['low', 'medium', 'high']).optional(),
39
+ openaiMaxOutputTokens: z.number().optional(),
40
+ maxAgenticIterations: z.number().optional(),
41
+ allowCommitSplitting: z.boolean().optional(),
42
+ autoSplit: z.boolean().optional(),
43
+ toolTimeout: z.number().optional(),
44
+ selfReflection: z.boolean().optional(),
45
+ }).optional(),
46
+
47
+ // Release configuration (pure git only)
48
+ release: z.object({
49
+ from: z.string().optional(),
50
+ to: z.string().optional(),
51
+ fromTag: z.string().optional(),
52
+ toTag: z.string().optional(),
53
+ version: z.string().optional(),
54
+ output: z.string().optional(),
55
+ messageLimit: z.number().optional(),
56
+ context: z.string().optional(),
57
+ contextFiles: z.array(z.string()).optional(),
58
+ interactive: z.boolean().optional(),
59
+ focus: z.string().optional(),
60
+ maxDiffBytes: z.number().optional(),
61
+ model: z.string().optional(),
62
+ openaiReasoning: z.enum(['low', 'medium', 'high']).optional(),
63
+ openaiMaxOutputTokens: z.number().optional(),
64
+ maxAgenticIterations: z.number().optional(),
65
+ selfReflection: z.boolean().optional(),
66
+ }).optional(),
67
+ });
68
+
69
+ export const SecureConfigSchema = z.object({
70
+ openaiApiKey: z.string().optional(),
71
+ });
72
+
73
+ export type Config = z.infer<typeof ConfigSchema>;
74
+ export type SecureConfig = z.infer<typeof SecureConfigSchema>;
75
+
76
+ /**
77
+ * Commit configuration type
78
+ */
79
+ export type CommitConfig = {
80
+ add?: boolean;
81
+ cached?: boolean;
82
+ sendit?: boolean;
83
+ interactive?: boolean;
84
+ amend?: boolean;
85
+ push?: string | boolean;
86
+ messageLimit?: number;
87
+ context?: string;
88
+ contextFiles?: string[];
89
+ direction?: string;
90
+ skipFileCheck?: boolean;
91
+ maxDiffBytes?: number;
92
+ model?: string;
93
+ openaiReasoning?: 'low' | 'medium' | 'high';
94
+ openaiMaxOutputTokens?: number;
95
+ maxAgenticIterations?: number;
96
+ allowCommitSplitting?: boolean;
97
+ autoSplit?: boolean;
98
+ toolTimeout?: number;
99
+ selfReflection?: boolean;
100
+ }
101
+
102
+ /**
103
+ * Release configuration type (pure git only)
104
+ */
105
+ export type ReleaseConfig = {
106
+ from?: string;
107
+ to?: string;
108
+ fromTag?: string;
109
+ toTag?: string;
110
+ version?: string;
111
+ output?: string;
112
+ context?: string;
113
+ contextFiles?: string[];
114
+ interactive?: boolean;
115
+ focus?: string;
116
+ messageLimit?: number;
117
+ maxDiffBytes?: number;
118
+ model?: string;
119
+ openaiReasoning?: 'low' | 'medium' | 'high';
120
+ openaiMaxOutputTokens?: number;
121
+ maxAgenticIterations?: number;
122
+ selfReflection?: boolean;
123
+ }
124
+
125
+ /**
126
+ * Release summary returned by AI
127
+ */
128
+ export type ReleaseSummary = {
129
+ title: string;
130
+ body: string;
131
+ }