@grunnverk/kodrdriv 1.3.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 (84) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.cursor/rules/no-local-dependencies.md +6 -0
  3. package/.gitignore~ +23 -0
  4. package/.kodrdriv-example-branch-targeting.yaml +71 -0
  5. package/BUG_TREE_PUBLISH_CONFIG_DIR.md +79 -0
  6. package/LICENSE +190 -0
  7. package/README.md +218 -0
  8. package/dist/application.js +228 -0
  9. package/dist/application.js.map +1 -0
  10. package/dist/arguments.js +1307 -0
  11. package/dist/arguments.js.map +1 -0
  12. package/dist/constants.js +255 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/logging.js +176 -0
  15. package/dist/logging.js.map +1 -0
  16. package/dist/main.js +24 -0
  17. package/dist/main.js.map +1 -0
  18. package/dist/mcp/prompts/check_development.md +169 -0
  19. package/dist/mcp/prompts/dependency_update.md +62 -0
  20. package/dist/mcp/prompts/fix_and_commit.md +53 -0
  21. package/dist/mcp/prompts/publish.md +100 -0
  22. package/dist/mcp/prompts/tree_fix_and_commit.md +102 -0
  23. package/dist/mcp/prompts/tree_publish.md +118 -0
  24. package/dist/mcp-server.js +15601 -0
  25. package/dist/mcp-server.js.map +7 -0
  26. package/dist/types.js +303 -0
  27. package/dist/types.js.map +1 -0
  28. package/guide/ai-system.md +522 -0
  29. package/guide/architecture.md +349 -0
  30. package/guide/commands.md +383 -0
  31. package/guide/configuration.md +516 -0
  32. package/guide/debugging.md +587 -0
  33. package/guide/development.md +632 -0
  34. package/guide/index.md +224 -0
  35. package/guide/integration.md +510 -0
  36. package/guide/monorepo.md +533 -0
  37. package/guide/quickstart.md +249 -0
  38. package/guide/testing.md +463 -0
  39. package/guide/tree-operations.md +621 -0
  40. package/guide/usage.md +578 -0
  41. package/input/250509-kodrdriv-library-rules.m4a +0 -0
  42. package/package.json +105 -0
  43. package/packages/components/package.json +7 -0
  44. package/packages/tools/package.json +7 -0
  45. package/packages/utils/package.json +7 -0
  46. package/processed/250705-kodrdriv-confirm-editor-for-commit-and-release.m4a +0 -0
  47. package/processed/250705-kodrdriv-confirm-flag-release.m4a +0 -0
  48. package/processed/250705-kodrdriv-context-for-review.m4a +0 -0
  49. package/processed/250705-kodrdriv-feedback-on-publish-pipeline.m4a +0 -0
  50. package/processed/250705-kodrdriv-intelligent-eslint-style.m4a +0 -0
  51. package/processed/250705-kodrdriv-make-review-less-strict.m4a +0 -0
  52. package/processed/250705-kodrdriv-multilevel-transcription.m4a +0 -0
  53. package/processed/250705-kodrdriv-opinionated-review.m4a +0 -0
  54. package/processed/250705-kodrdriv-publish-next-version.m4a +0 -0
  55. package/processed/250705-kodrdriv-release-branches-and-milestones.m4a +0 -0
  56. package/processed/250705-kodrdriv-scope-check-fix-or-ignore.m4a +0 -0
  57. package/processed/250705-kodrdriv-scope-checker.m4a +0 -0
  58. package/processed/250705-kodrdriv-specify-a-release-note-for-publish.m4a +0 -0
  59. package/scripts/build-mcp.js +111 -0
  60. package/scripts/pre-commit-hook.sh +52 -0
  61. package/scripts/test-get-version-tool.js +102 -0
  62. package/scripts/test-mcp-compliance.js +254 -0
  63. package/scripts/update-test-log-assertions.js +73 -0
  64. package/temp-dist/arguments.js +817 -0
  65. package/temp-dist/constants.js +202 -0
  66. package/temp-dist/logging.js +130 -0
  67. package/temp-dist/types.js +112 -0
  68. package/temp-dist/util/stdin.js +132 -0
  69. package/temp-dist/util/storage.js +149 -0
  70. package/temp-dist/util/validation.js +110 -0
  71. package/test-external-unlink/package.json +16 -0
  72. package/test-externals/package.json +8 -0
  73. package/test-increment.js +0 -0
  74. package/test-multiline/cli/package.json +8 -0
  75. package/test-multiline/core/package.json +5 -0
  76. package/test-multiline/mobile/package.json +8 -0
  77. package/test-multiline/web/package.json +8 -0
  78. package/test-project/package-lock.json +21 -0
  79. package/test-project/package.json +1 -0
  80. package/test-review-flow.sh +15 -0
  81. package/test-sort-files/alpha.md +3 -0
  82. package/test-sort-files/middle.txt +3 -0
  83. package/test-sort-files/zebra.txt +3 -0
  84. package/test_output.txt +161 -0
@@ -0,0 +1,202 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ export const VERSION = '__VERSION__ (__GIT_BRANCH__/__GIT_COMMIT__ __GIT_TAGS__ __GIT_COMMIT_DATE__) __SYSTEM_INFO__';
4
+ export const PROGRAM_NAME = 'kodrdriv';
5
+ export const DEFAULT_CHARACTER_ENCODING = 'utf-8';
6
+ export const DEFAULT_BINARY_TO_TEXT_ENCODING = 'base64';
7
+ export const DEFAULT_DIFF = true;
8
+ export const DEFAULT_LOG = false;
9
+ export const DEFAULT_OVERRIDES = false;
10
+ export const DATE_FORMAT_MONTH_DAY = 'MM-DD';
11
+ export const DATE_FORMAT_YEAR = 'YYYY';
12
+ export const DATE_FORMAT_YEAR_MONTH = 'YYYY-MM';
13
+ export const DATE_FORMAT_YEAR_MONTH_DAY = 'YYYY-MM-DD';
14
+ export const DATE_FORMAT_YEAR_MONTH_DAY_SLASH = 'YYYY/MM/DD';
15
+ export const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES = 'YYYY-MM-DD-HHmm';
16
+ export const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = 'YYYY-MM-DD-HHmmss';
17
+ export const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS = 'YYYY-MM-DD-HHmmss.SSS';
18
+ export const DATE_FORMAT_SHORT_TIMESTAMP = 'YYMMdd-HHmm';
19
+ export const DATE_FORMAT_MONTH = 'MM';
20
+ export const DATE_FORMAT_DAY = 'DD';
21
+ export const DATE_FORMAT_HOURS = 'HHmm';
22
+ export const DATE_FORMAT_MINUTES = 'mm';
23
+ export const DATE_FORMAT_SECONDS = 'ss';
24
+ export const DATE_FORMAT_MILLISECONDS = 'SSS';
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_OUTPUT_DIRECTORY = 'output/kodrdriv';
31
+ // Buffer size for git commands that may produce large output (like git log)
32
+ export const DEFAULT_GIT_COMMAND_MAX_BUFFER = 50 * 1024 * 1024; // 50MB
33
+ export const DEFAULT_CONTEXT_DIRECTORIES = [];
34
+ export const DEFAULT_CONFIG_DIR = '.kodrdriv';
35
+ export const DEFAULT_PREFERENCES_DIRECTORY = path.join(os.homedir(), '.kodrdriv');
36
+ export const DEFAULT_FROM_COMMIT_ALIAS = 'origin/HEAD';
37
+ export const DEFAULT_TO_COMMIT_ALIAS = 'HEAD';
38
+ export const DEFAULT_ADD = false;
39
+ export const DEFAULT_CACHED = false;
40
+ export const DEFAULT_SENDIT_MODE = false;
41
+ export const DEFAULT_INTERACTIVE_MODE = false;
42
+ export const DEFAULT_MESSAGE_LIMIT = 50;
43
+ export const DEFAULT_MAX_DIFF_BYTES = 2048; // 2KB default limit per file
44
+ export const DEFAULT_MERGE_METHOD = 'squash';
45
+ export const DEFAULT_EXCLUDED_PATTERNS = [
46
+ 'node_modules', 'package-lock.json', 'yarn.lock', 'bun.lockb',
47
+ 'composer.lock', 'Cargo.lock', 'Gemfile.lock',
48
+ 'dist', 'build', 'out', '.next', '.nuxt', 'coverage',
49
+ '.vscode', '.idea', '.DS_Store', '.git', '.gitignore',
50
+ 'logs', 'tmp', '.cache', '*.log', '.env', '.env.*',
51
+ '*.pem', '*.crt', '*.key', '*.sqlite', '*.db',
52
+ '*.zip', '*.tar', '*.gz', '*.exe', '*.bin'
53
+ ];
54
+ export const COMMAND_COMMIT = 'commit';
55
+ export const COMMAND_AUDIO_COMMIT = 'audio-commit';
56
+ export const COMMAND_SELECT_AUDIO = 'select-audio';
57
+ export const COMMAND_RELEASE = 'release';
58
+ export const COMMAND_REVIEW = 'review';
59
+ export const COMMAND_AUDIO_REVIEW = 'audio-review';
60
+ export const COMMAND_PUBLISH = 'publish';
61
+ export const COMMAND_TREE = 'tree';
62
+ export const COMMAND_LINK = 'link';
63
+ export const COMMAND_UNLINK = 'unlink';
64
+ export const COMMAND_CLEAN = 'clean';
65
+ export const COMMAND_CHECK_CONFIG = 'check-config';
66
+ export const COMMAND_INIT_CONFIG = 'init-config';
67
+ export const ALLOWED_COMMANDS = [
68
+ COMMAND_COMMIT,
69
+ COMMAND_AUDIO_COMMIT,
70
+ COMMAND_SELECT_AUDIO,
71
+ COMMAND_RELEASE,
72
+ COMMAND_REVIEW,
73
+ COMMAND_AUDIO_REVIEW,
74
+ COMMAND_PUBLISH,
75
+ COMMAND_TREE,
76
+ COMMAND_LINK,
77
+ COMMAND_UNLINK,
78
+ COMMAND_CLEAN
79
+ ];
80
+ export const DEFAULT_COMMAND = COMMAND_COMMIT;
81
+ export const DEFAULT_INSTRUCTIONS_DIR = `instructions`;
82
+ export const DEFAULT_PERSONA_DIR = `personas`;
83
+ export const DEFAULT_INSTRUCTIONS_COMMIT_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/commit.md`;
84
+ export const DEFAULT_INSTRUCTIONS_RELEASE_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/release.md`;
85
+ export const DEFAULT_INSTRUCTIONS_REVIEW_FILE = `${DEFAULT_INSTRUCTIONS_DIR}/review.md`;
86
+ export const DEFAULT_PERSONA_RELEASER_FILE = `${DEFAULT_PERSONA_DIR}/releaser.md`;
87
+ export const DEFAULT_PERSONA_YOU_FILE = `${DEFAULT_PERSONA_DIR}/you.md`;
88
+ // Default instructions for each persona
89
+ export const DEFAULT_INSTRUCTIONS_MAP = {
90
+ [COMMAND_COMMIT]: DEFAULT_INSTRUCTIONS_COMMIT_FILE,
91
+ [COMMAND_AUDIO_COMMIT]: DEFAULT_INSTRUCTIONS_COMMIT_FILE, // Reuse commit instructions
92
+ [COMMAND_RELEASE]: DEFAULT_INSTRUCTIONS_RELEASE_FILE,
93
+ [COMMAND_REVIEW]: DEFAULT_INSTRUCTIONS_REVIEW_FILE, // Reuse audio-review instructions for now
94
+ [COMMAND_AUDIO_REVIEW]: DEFAULT_INSTRUCTIONS_REVIEW_FILE,
95
+ };
96
+ // Default personas for each command
97
+ export const DEFAULT_PERSONA_MAP = {
98
+ [COMMAND_COMMIT]: DEFAULT_PERSONA_YOU_FILE,
99
+ [COMMAND_AUDIO_COMMIT]: DEFAULT_PERSONA_YOU_FILE, // Use You persona
100
+ [COMMAND_RELEASE]: DEFAULT_PERSONA_RELEASER_FILE,
101
+ [COMMAND_REVIEW]: DEFAULT_PERSONA_YOU_FILE, // Use You persona
102
+ [COMMAND_AUDIO_REVIEW]: DEFAULT_PERSONA_YOU_FILE,
103
+ };
104
+ // Used by child process to create paths
105
+ export const DEFAULT_PATH_SEPARATOR = '/';
106
+ // Used by util/general for file filtering
107
+ export const DEFAULT_IGNORE_PATTERNS = [
108
+ 'node_modules/**',
109
+ '**/*.log',
110
+ '.git/**',
111
+ 'dist/**',
112
+ 'build/**',
113
+ 'coverage/**',
114
+ '.DS_Store',
115
+ '*.tmp',
116
+ '*.cache',
117
+ ];
118
+ // Used by util/storage for directory names
119
+ export const DEFAULT_DIRECTORY_PREFIX = '.kodrdriv';
120
+ // Used by other commands but not exposed in CLI
121
+ export const INTERNAL_DEFAULT_OUTPUT_FILE = 'output.txt';
122
+ export const INTERNAL_DATETIME_FORMAT = 'YYYY-MM-DD_HH-mm-ss';
123
+ // Define defaults in one place
124
+ export const KODRDRIV_DEFAULTS = {
125
+ dryRun: DEFAULT_DRY_RUN,
126
+ verbose: DEFAULT_VERBOSE,
127
+ debug: DEFAULT_DEBUG,
128
+ overrides: DEFAULT_OVERRIDES,
129
+ model: DEFAULT_MODEL,
130
+ contextDirectories: DEFAULT_CONTEXT_DIRECTORIES,
131
+ commandName: DEFAULT_COMMAND,
132
+ configDirectory: DEFAULT_CONFIG_DIR,
133
+ outputDirectory: DEFAULT_OUTPUT_DIRECTORY,
134
+ preferencesDirectory: DEFAULT_PREFERENCES_DIRECTORY,
135
+ commit: {
136
+ add: DEFAULT_ADD,
137
+ cached: DEFAULT_CACHED,
138
+ sendit: DEFAULT_SENDIT_MODE,
139
+ interactive: DEFAULT_INTERACTIVE_MODE,
140
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
141
+ skipFileCheck: false,
142
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
143
+ },
144
+ release: {
145
+ from: DEFAULT_FROM_COMMIT_ALIAS,
146
+ to: DEFAULT_TO_COMMIT_ALIAS,
147
+ messageLimit: DEFAULT_MESSAGE_LIMIT,
148
+ interactive: DEFAULT_INTERACTIVE_MODE,
149
+ maxDiffBytes: DEFAULT_MAX_DIFF_BYTES,
150
+ },
151
+ audioCommit: {
152
+ maxRecordingTime: 300, // 5 minutes default
153
+ audioDevice: undefined, // Auto-detect by default
154
+ },
155
+ review: {
156
+ includeCommitHistory: true,
157
+ includeRecentDiffs: true,
158
+ includeReleaseNotes: false,
159
+ includeGithubIssues: true,
160
+ commitHistoryLimit: 10,
161
+ diffHistoryLimit: 5,
162
+ releaseNotesLimit: 3,
163
+ githubIssuesLimit: 20,
164
+ sendit: DEFAULT_SENDIT_MODE,
165
+ },
166
+ audioReview: {
167
+ includeCommitHistory: true,
168
+ includeRecentDiffs: true,
169
+ includeReleaseNotes: false,
170
+ includeGithubIssues: true,
171
+ commitHistoryLimit: 10,
172
+ diffHistoryLimit: 5,
173
+ releaseNotesLimit: 3,
174
+ githubIssuesLimit: 20,
175
+ sendit: DEFAULT_SENDIT_MODE,
176
+ maxRecordingTime: 300, // 5 minutes default
177
+ audioDevice: undefined, // Auto-detect by default
178
+ directory: undefined, // No default directory
179
+ },
180
+ publish: {
181
+ mergeMethod: DEFAULT_MERGE_METHOD,
182
+ requiredEnvVars: ['GITHUB_TOKEN', 'OPENAI_API_KEY'],
183
+ linkWorkspacePackages: true,
184
+ unlinkWorkspacePackages: true,
185
+ sendit: DEFAULT_SENDIT_MODE,
186
+ targetBranch: 'main',
187
+ },
188
+ link: {
189
+ scopeRoots: {},
190
+ dryRun: false,
191
+ },
192
+ tree: {
193
+ directories: undefined,
194
+ excludedPatterns: undefined,
195
+ startFrom: undefined,
196
+ cmd: undefined,
197
+ parallel: false,
198
+ builtInCommand: undefined,
199
+ continue: false,
200
+ },
201
+ excludedPatterns: DEFAULT_EXCLUDED_PATTERNS,
202
+ };
@@ -0,0 +1,130 @@
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_MILLISECONDS, PROGRAM_NAME, DEFAULT_OUTPUT_DIRECTORY } from './constants';
6
+ // Track if debug directory has been ensured for this session
7
+ let debugDirectoryEnsured = false;
8
+ const ensureDebugDirectory = () => {
9
+ if (debugDirectoryEnsured)
10
+ return;
11
+ const debugDir = path.join(DEFAULT_OUTPUT_DIRECTORY, 'debug');
12
+ try {
13
+ fs.mkdirSync(debugDir, { recursive: true });
14
+ debugDirectoryEnsured = true;
15
+ }
16
+ catch (error) {
17
+ // eslint-disable-next-line no-console
18
+ console.error(`Failed to create debug directory ${debugDir}:`, error);
19
+ }
20
+ };
21
+ const generateDebugLogFilename = () => {
22
+ const now = new Date();
23
+ const timestamp = now.toISOString()
24
+ .replace(/[-:]/g, '')
25
+ .replace(/\./g, '')
26
+ .replace('T', '-')
27
+ .replace('Z', '');
28
+ return `${timestamp}-debug.log`;
29
+ };
30
+ const createTransports = (level) => {
31
+ const transports = [];
32
+ // Always add console transport for info level and above
33
+ if (level === 'info') {
34
+ transports.push(new winston.transports.Console({
35
+ format: winston.format.combine(winston.format.colorize(), winston.format.printf(({ level, message, dryRun }) => {
36
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
37
+ // For info level messages, don't show the level prefix
38
+ if (level.includes('info')) {
39
+ return `${dryRunPrefix}${String(message)}`;
40
+ }
41
+ // For warn, error, etc., show the level prefix
42
+ return `${level}: ${dryRunPrefix}${String(message)}`;
43
+ }))
44
+ }));
45
+ }
46
+ else {
47
+ // For debug/verbose levels, add console transport that shows info and above
48
+ transports.push(new winston.transports.Console({
49
+ level: 'info', // Show info, warn, and error on console
50
+ format: winston.format.combine(winston.format.colorize(), winston.format.printf(({ timestamp, level, message, dryRun, ...meta }) => {
51
+ // For info level messages, use simpler format without timestamp
52
+ if (level.includes('info')) {
53
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
54
+ return `${dryRunPrefix}${String(message)}`;
55
+ }
56
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
57
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
58
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
59
+ }))
60
+ }));
61
+ // Add file transport for debug levels (debug and silly)
62
+ if (level === 'debug' || level === 'silly') {
63
+ ensureDebugDirectory();
64
+ const debugLogPath = path.join(DEFAULT_OUTPUT_DIRECTORY, 'debug', generateDebugLogFilename());
65
+ transports.push(new winston.transports.File({
66
+ filename: debugLogPath,
67
+ level: 'debug', // Capture debug and above in the file
68
+ format: winston.format.combine(winston.format.timestamp({ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.printf(({ timestamp, level, message, ...meta }) => {
69
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
70
+ return `${timestamp} ${level}: ${message}${metaStr}`;
71
+ }))
72
+ }));
73
+ }
74
+ }
75
+ return transports;
76
+ };
77
+ const createFormat = (level) => {
78
+ const baseFormats = [
79
+ winston.format.errors({ stack: true }),
80
+ winston.format.splat(),
81
+ ];
82
+ if (level === 'info') {
83
+ return winston.format.combine(...baseFormats, winston.format.printf(({ message, dryRun, ..._meta }) => {
84
+ // Auto-format dry-run messages
85
+ if (dryRun) {
86
+ return `🔍 DRY RUN: ${message}`;
87
+ }
88
+ return String(message);
89
+ }));
90
+ }
91
+ return winston.format.combine(winston.format.timestamp({ format: DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS_MILLISECONDS }), ...baseFormats, winston.format.printf(({ timestamp, level, message, dryRun, ...meta }) => {
92
+ const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : '';
93
+ const dryRunPrefix = dryRun ? '🔍 DRY RUN: ' : '';
94
+ return `${timestamp} ${level}: ${dryRunPrefix}${String(message)}${metaStr}`;
95
+ }));
96
+ };
97
+ // Create the logger instance once
98
+ const logger = winston.createLogger({
99
+ level: 'info',
100
+ format: createFormat('info'),
101
+ defaultMeta: { service: PROGRAM_NAME },
102
+ transports: createTransports('info'),
103
+ });
104
+ export const setLogLevel = (level) => {
105
+ // Reconfigure the existing logger instead of creating a new one
106
+ logger.configure({
107
+ level,
108
+ format: createFormat(level),
109
+ defaultMeta: { service: PROGRAM_NAME },
110
+ transports: createTransports(level),
111
+ });
112
+ };
113
+ export const getLogger = () => logger;
114
+ /**
115
+ * Get a logger that automatically formats messages for dry-run mode
116
+ */
117
+ export const getDryRunLogger = (isDryRun) => {
118
+ if (!isDryRun) {
119
+ return logger;
120
+ }
121
+ // Return a wrapper that adds dry-run context to all log calls
122
+ return {
123
+ info: (message, ...args) => logger.info(message, { dryRun: true }, ...args),
124
+ warn: (message, ...args) => logger.warn(message, { dryRun: true }, ...args),
125
+ error: (message, ...args) => logger.error(message, { dryRun: true }, ...args),
126
+ debug: (message, ...args) => logger.debug(message, { dryRun: true }, ...args),
127
+ verbose: (message, ...args) => logger.verbose(message, { dryRun: true }, ...args),
128
+ silly: (message, ...args) => logger.silly(message, { dryRun: true }, ...args),
129
+ };
130
+ };
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ export const ConfigSchema = z.object({
3
+ dryRun: z.boolean().optional(),
4
+ verbose: z.boolean().optional(),
5
+ debug: z.boolean().optional(),
6
+ overrides: z.boolean().optional(),
7
+ model: z.string().optional(),
8
+ contextDirectories: z.array(z.string()).optional(),
9
+ outputDirectory: z.string().optional(),
10
+ preferencesDirectory: z.string().optional(),
11
+ commit: z.object({
12
+ add: z.boolean().optional(),
13
+ cached: z.boolean().optional(),
14
+ sendit: z.boolean().optional(),
15
+ interactive: z.boolean().optional(),
16
+ messageLimit: z.number().optional(),
17
+ context: z.string().optional(),
18
+ direction: z.string().optional(),
19
+ skipFileCheck: z.boolean().optional(),
20
+ maxDiffBytes: z.number().optional(),
21
+ model: z.string().optional(),
22
+ }).optional(),
23
+ audioCommit: z.object({
24
+ maxRecordingTime: z.number().optional(),
25
+ audioDevice: z.string().optional(),
26
+ file: z.string().optional(),
27
+ keepTemp: z.boolean().optional(),
28
+ }).optional(),
29
+ release: z.object({
30
+ from: z.string().optional(),
31
+ to: z.string().optional(),
32
+ messageLimit: z.number().optional(),
33
+ context: z.string().optional(),
34
+ interactive: z.boolean().optional(),
35
+ focus: z.string().optional(),
36
+ maxDiffBytes: z.number().optional(),
37
+ model: z.string().optional(),
38
+ }).optional(),
39
+ review: z.object({
40
+ includeCommitHistory: z.boolean().optional(),
41
+ includeRecentDiffs: z.boolean().optional(),
42
+ includeReleaseNotes: z.boolean().optional(),
43
+ includeGithubIssues: z.boolean().optional(),
44
+ commitHistoryLimit: z.number().optional(),
45
+ diffHistoryLimit: z.number().optional(),
46
+ releaseNotesLimit: z.number().optional(),
47
+ githubIssuesLimit: z.number().optional(),
48
+ context: z.string().optional(),
49
+ sendit: z.boolean().optional(),
50
+ note: z.string().optional(),
51
+ editorTimeout: z.number().optional(),
52
+ maxContextErrors: z.number().optional(),
53
+ model: z.string().optional(),
54
+ }).optional(),
55
+ audioReview: z.object({
56
+ includeCommitHistory: z.boolean().optional(),
57
+ includeRecentDiffs: z.boolean().optional(),
58
+ includeReleaseNotes: z.boolean().optional(),
59
+ includeGithubIssues: z.boolean().optional(),
60
+ commitHistoryLimit: z.number().optional(),
61
+ diffHistoryLimit: z.number().optional(),
62
+ releaseNotesLimit: z.number().optional(),
63
+ githubIssuesLimit: z.number().optional(),
64
+ context: z.string().optional(),
65
+ sendit: z.boolean().optional(),
66
+ maxRecordingTime: z.number().optional(),
67
+ audioDevice: z.string().optional(),
68
+ file: z.string().optional(),
69
+ directory: z.string().optional(),
70
+ keepTemp: z.boolean().optional(),
71
+ }).optional(),
72
+ publish: z.object({
73
+ mergeMethod: z.enum(['merge', 'squash', 'rebase']).optional(),
74
+ dependencyUpdatePatterns: z.array(z.string()).optional(),
75
+ requiredEnvVars: z.array(z.string()).optional(),
76
+ linkWorkspacePackages: z.boolean().optional(),
77
+ unlinkWorkspacePackages: z.boolean().optional(),
78
+ checksTimeout: z.number().optional(),
79
+ skipUserConfirmation: z.boolean().optional(),
80
+ sendit: z.boolean().optional(),
81
+ waitForReleaseWorkflows: z.boolean().optional(),
82
+ releaseWorkflowsTimeout: z.number().optional(),
83
+ releaseWorkflowNames: z.array(z.string()).optional(),
84
+ targetBranch: z.string().optional(),
85
+ }).optional(),
86
+ link: z.object({
87
+ scopeRoots: z.record(z.string(), z.string()).optional(),
88
+ dryRun: z.boolean().optional(),
89
+ }).optional(),
90
+ unlink: z.object({
91
+ scopeRoots: z.record(z.string(), z.string()).optional(),
92
+ workspaceFile: z.string().optional(),
93
+ dryRun: z.boolean().optional(),
94
+ cleanNodeModules: z.boolean().optional(),
95
+ }).optional(),
96
+ tree: z.object({
97
+ directories: z.array(z.string()).optional(),
98
+ excludedPatterns: z.array(z.string()).optional(),
99
+ startFrom: z.string().optional(),
100
+ cmd: z.string().optional(),
101
+ parallel: z.boolean().optional(),
102
+ builtInCommand: z.string().optional(),
103
+ continue: z.boolean().optional(),
104
+ }).optional(),
105
+ excludedPatterns: z.array(z.string()).optional(),
106
+ });
107
+ export const SecureConfigSchema = z.object({
108
+ openaiApiKey: z.string().optional(),
109
+ });
110
+ export const CommandConfigSchema = z.object({
111
+ commandName: z.string().optional(),
112
+ });
@@ -0,0 +1,132 @@
1
+ // Function to read from STDIN if available
2
+ export async function readStdin() {
3
+ // In test environment, allow mocking to work by skipping TTY check
4
+ if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true') {
5
+ return new Promise((resolve) => {
6
+ let input = '';
7
+ let hasData = false;
8
+ let resolved = false;
9
+ const timeout = setTimeout(() => {
10
+ if (!hasData && !resolved) {
11
+ resolved = true;
12
+ cleanup();
13
+ resolve(null);
14
+ }
15
+ }, 10); // Very short timeout for tests
16
+ const onData = (chunk) => {
17
+ hasData = true;
18
+ clearTimeout(timeout);
19
+ input += chunk;
20
+ };
21
+ const onEnd = () => {
22
+ if (!resolved) {
23
+ resolved = true;
24
+ cleanup();
25
+ resolve(input.trim() || null);
26
+ }
27
+ };
28
+ const onError = () => {
29
+ if (!resolved) {
30
+ resolved = true;
31
+ clearTimeout(timeout);
32
+ cleanup();
33
+ resolve(null);
34
+ }
35
+ };
36
+ const cleanup = () => {
37
+ process.stdin.removeListener('data', onData);
38
+ process.stdin.removeListener('end', onEnd);
39
+ process.stdin.removeListener('error', onError);
40
+ process.stdin.pause();
41
+ };
42
+ process.stdin.setEncoding('utf8');
43
+ process.stdin.on('data', onData);
44
+ process.stdin.on('end', onEnd);
45
+ process.stdin.on('error', onError);
46
+ process.stdin.resume();
47
+ });
48
+ }
49
+ return new Promise((resolve) => {
50
+ // Check if stdin is TTY (interactive terminal)
51
+ if (process.stdin.isTTY) {
52
+ resolve(null);
53
+ return;
54
+ }
55
+ let input = '';
56
+ let hasData = false;
57
+ let resolved = false;
58
+ const timeout = setTimeout(() => {
59
+ if (!hasData && !resolved) {
60
+ resolved = true;
61
+ cleanup();
62
+ resolve(null);
63
+ }
64
+ }, 100); // Short timeout to detect if data is available
65
+ const onData = (chunk) => {
66
+ hasData = true;
67
+ clearTimeout(timeout);
68
+ input += chunk;
69
+ };
70
+ const onEnd = () => {
71
+ if (!resolved) {
72
+ resolved = true;
73
+ cleanup();
74
+ resolve(input.trim() || null);
75
+ }
76
+ };
77
+ const onError = () => {
78
+ if (!resolved) {
79
+ resolved = true;
80
+ clearTimeout(timeout);
81
+ cleanup();
82
+ resolve(null);
83
+ }
84
+ };
85
+ const cleanup = () => {
86
+ process.stdin.removeListener('data', onData);
87
+ process.stdin.removeListener('end', onEnd);
88
+ process.stdin.removeListener('error', onError);
89
+ process.stdin.pause();
90
+ };
91
+ process.stdin.setEncoding('utf8');
92
+ process.stdin.on('data', onData);
93
+ process.stdin.on('end', onEnd);
94
+ process.stdin.on('error', onError);
95
+ // If no data comes in quickly, assume no stdin
96
+ process.stdin.resume();
97
+ });
98
+ }
99
+ // Function to prompt user for confirmation (y/n)
100
+ export async function promptConfirmation(message) {
101
+ // In test environment, return true by default
102
+ if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true') {
103
+ return true;
104
+ }
105
+ // Use process.stdout.write instead of console.log to avoid linter issues
106
+ process.stdout.write(message + '\n');
107
+ process.stdout.write('Please enter "y" for yes or "n" for no: ');
108
+ return new Promise((resolve) => {
109
+ const handleInput = (chunk) => {
110
+ const input = chunk.toString().trim().toLowerCase();
111
+ if (input === 'y' || input === 'yes') {
112
+ cleanup();
113
+ resolve(true);
114
+ }
115
+ else if (input === 'n' || input === 'no') {
116
+ cleanup();
117
+ resolve(false);
118
+ }
119
+ else {
120
+ process.stdout.write('Please enter "y" for yes or "n" for no: ');
121
+ // Continue listening for input
122
+ }
123
+ };
124
+ const cleanup = () => {
125
+ process.stdin.removeListener('data', handleInput);
126
+ process.stdin.pause();
127
+ };
128
+ process.stdin.setEncoding('utf8');
129
+ process.stdin.on('data', handleInput);
130
+ process.stdin.resume();
131
+ });
132
+ }