mkctx 3.0.0 ā 4.0.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/README.md +20 -14
- package/bin/mkctx.js +312 -164
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- š **Multi-platform** - Works on Windows, macOS, and Linux
|
|
24
24
|
- š **Smart Ignoring** - Respects custom ignore patterns and common system files
|
|
25
25
|
- āļø **Configurable** - Customize source directories, output locations, and comments
|
|
26
|
+
- āļø **Custom Naming** - Specify custom filenames for your outputs or use the default 'context'
|
|
26
27
|
- šÆ **AI-Friendly** - Outputs code in markdown format ideal for AI prompts
|
|
27
28
|
- šØ **Syntax Highlighting** - Proper language detection for code blocks
|
|
28
29
|
- š **Dynamic Mode** - Interactive path selection when needed
|
|
@@ -49,7 +50,7 @@ mkctx
|
|
|
49
50
|
This opens an interactive menu where you can:
|
|
50
51
|
1. Generate context from config file or dynamically
|
|
51
52
|
2. View context statistics
|
|
52
|
-
3.
|
|
53
|
+
3. Choose output formats and save with a custom name
|
|
53
54
|
|
|
54
55
|
### Create Configuration File
|
|
55
56
|
|
|
@@ -86,12 +87,18 @@ After generating context:
|
|
|
86
87
|
Size: 156.23 KB
|
|
87
88
|
Est. tokens: ~39,058
|
|
88
89
|
|
|
89
|
-
?
|
|
90
|
-
āÆ
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
? Select output format:
|
|
91
|
+
⯠š¦ All formats (MD, JSON, TOON, XML)
|
|
92
|
+
š Markdown (.md)
|
|
93
|
+
š§ JSON (.json) - Simple array
|
|
94
|
+
š TOON (.toon) - Token-optimized
|
|
95
|
+
š XML (.xml)
|
|
96
|
+
|
|
97
|
+
? Enter a name for the output files: (context)
|
|
93
98
|
```
|
|
94
99
|
|
|
100
|
+
> **Note:** Simply press Enter to use the default name `context`.
|
|
101
|
+
|
|
95
102
|
## āļø Configuration
|
|
96
103
|
|
|
97
104
|
### Project Configuration (`mkctx.config.json`)
|
|
@@ -144,7 +151,7 @@ These are always ignored automatically:
|
|
|
144
151
|
|
|
145
152
|
## š Output Format
|
|
146
153
|
|
|
147
|
-
The generated
|
|
154
|
+
The generated file contains your project code. When saving, you can specify a filename (e.g., `my_project_docs.md`) or use the default.
|
|
148
155
|
|
|
149
156
|
````markdown
|
|
150
157
|
/* Project Context */
|
|
@@ -206,7 +213,7 @@ mkctx automatically detects and applies proper syntax highlighting for:
|
|
|
206
213
|
- **š Code Understanding** - Share project overview for quick understanding
|
|
207
214
|
- **š„ Code Reviews** - Share project overview with reviewers
|
|
208
215
|
- **š Onboarding** - Help new developers understand the project
|
|
209
|
-
- **š Documentation** - Generate a snapshot of your codebase
|
|
216
|
+
- **š Documentation** - Generate a snapshot of your codebase with custom naming
|
|
210
217
|
|
|
211
218
|
## š„ļø Platform Support
|
|
212
219
|
|
|
@@ -236,6 +243,11 @@ Or fix npm permissions: https://docs.npmjs.com/resolving-eacces-permissions-erro
|
|
|
236
243
|
|
|
237
244
|
## š Changelog
|
|
238
245
|
|
|
246
|
+
### v4.0.0
|
|
247
|
+
|
|
248
|
+
- āļø Added interactive filename selection when saving (defaults to "context")
|
|
249
|
+
- šØ Improved UI/UX for file saving workflow
|
|
250
|
+
|
|
239
251
|
### v3.0.0
|
|
240
252
|
|
|
241
253
|
- šÆ Simplified to focus on context generation
|
|
@@ -258,10 +270,4 @@ Contributions are welcome! Please feel free to submit pull requests or open issu
|
|
|
258
270
|
|
|
259
271
|
## š License
|
|
260
272
|
|
|
261
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
<p align="center">
|
|
266
|
-
Made with ā¤ļø for developers who love AI-assisted coding
|
|
267
|
-
</p>
|
|
273
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
package/bin/mkctx.js
CHANGED
|
@@ -25,7 +25,7 @@ const CONFIG_FILE = 'mkctx.config.json';
|
|
|
25
25
|
|
|
26
26
|
const DEFAULT_PROJECT_CONFIG = {
|
|
27
27
|
src: ".",
|
|
28
|
-
ignore: "mkctx.config.json, pnpm-lock.yaml, **/.titan/, mkctx/, node_modules/, .git/, dist/, build/, target/, .next/, out/, .cache, package-lock.json,
|
|
28
|
+
ignore: "mkctx.config.json, pnpm-lock.yaml, **/.titan/, mkctx/, node_modules/, .git/, dist/, build/, target/, .next/, out/, .cache, package-lock.json, *.log, temp/, tmp/, coverage/, .nyc_output, .env, .env.local, .env.development.local, .env.test.local, .env.production.local, npm-debug.log*, yarn-debug.log*, yarn-error.log*, .npm, .yarn-integrity, .parcel-cache, .vuepress/dist, .svelte-kit, **/*.rs.bk, .idea/, .vscode/, .DS_Store, Thumbs.db, *.swp, *.swo, .~lock.*, Cargo.lock, .cargo/registry/, .cargo/git/, .rustup/, *.pdb, *.dSYM/, *.so, *.dll, *.dylib, *.exe, *.lib, *.a, *.o, *.rlib, *.d, *.tmp, *.bak, *.orig, *.rej, *.pyc, *.pyo, *.class, *.jar, *.war, *.ear, *.zip, *.tar.gz, *.rar, *.7z, *.iso, *.img, *.dmg, *.pdf, *.doc, *.docx, *.xls, *.xlsx, *.ppt, *.pptx",
|
|
29
29
|
output: "./mkctx",
|
|
30
30
|
first_comment: "/* Project Context */",
|
|
31
31
|
last_comment: "/* End of Context */"
|
|
@@ -73,17 +73,11 @@ const KNOWN_FILES = new Set([
|
|
|
73
73
|
'procfile', 'vagrantfile', 'jenkinsfile',
|
|
74
74
|
'.gitignore', '.gitattributes', '.editorconfig',
|
|
75
75
|
'.eslintrc', '.prettierrc', '.babelrc',
|
|
76
|
-
'.env', '.env.example', '.env.local'
|
|
76
|
+
'.env', '.env.example', '.env.local',
|
|
77
|
+
'readme.md', 'readme.txt', 'readme',
|
|
78
|
+
'license', 'license.md', 'license.txt'
|
|
77
79
|
]);
|
|
78
80
|
|
|
79
|
-
// ============================================
|
|
80
|
-
// GLOBAL STATE
|
|
81
|
-
// ============================================
|
|
82
|
-
|
|
83
|
-
let generatedContext = null;
|
|
84
|
-
let contextFiles = [];
|
|
85
|
-
let contextStats = {};
|
|
86
|
-
|
|
87
81
|
// ============================================
|
|
88
82
|
// CONFIGURATION MANAGEMENT
|
|
89
83
|
// ============================================
|
|
@@ -107,7 +101,7 @@ function hasProjectConfig() {
|
|
|
107
101
|
|
|
108
102
|
function createProjectConfig() {
|
|
109
103
|
loadDependencies();
|
|
110
|
-
|
|
104
|
+
|
|
111
105
|
if (!fs.existsSync('mkctx')) {
|
|
112
106
|
fs.mkdirSync('mkctx', { recursive: true });
|
|
113
107
|
}
|
|
@@ -151,6 +145,11 @@ function estimateTokens(text) {
|
|
|
151
145
|
return Math.ceil(text.length / 4);
|
|
152
146
|
}
|
|
153
147
|
|
|
148
|
+
// Normalize path to always use forward slashes
|
|
149
|
+
function normalizePath(filePath) {
|
|
150
|
+
return filePath.replace(/\\/g, '/');
|
|
151
|
+
}
|
|
152
|
+
|
|
154
153
|
// ============================================
|
|
155
154
|
// FILE OPERATIONS
|
|
156
155
|
// ============================================
|
|
@@ -194,6 +193,10 @@ function matchWildcard(pattern, filename) {
|
|
|
194
193
|
}
|
|
195
194
|
|
|
196
195
|
function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
196
|
+
// Normalize paths for comparison
|
|
197
|
+
const normalizedFull = normalizePath(fullPath);
|
|
198
|
+
const normalizedRelative = normalizePath(relativePath);
|
|
199
|
+
|
|
197
200
|
const systemIgnores = [
|
|
198
201
|
'.git', '.DS_Store', 'Thumbs.db', 'node_modules',
|
|
199
202
|
'.svn', '.hg', '__pycache__', '.pytest_cache',
|
|
@@ -201,8 +204,9 @@ function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
|
201
204
|
];
|
|
202
205
|
|
|
203
206
|
for (const ignore of systemIgnores) {
|
|
204
|
-
if (
|
|
205
|
-
|
|
207
|
+
if (normalizedFull.includes('/' + ignore + '/') ||
|
|
208
|
+
normalizedFull.includes('/' + ignore) ||
|
|
209
|
+
normalizedFull.endsWith('/' + ignore) ||
|
|
206
210
|
name === ignore) {
|
|
207
211
|
return true;
|
|
208
212
|
}
|
|
@@ -211,19 +215,19 @@ function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
|
211
215
|
for (const pattern of patterns) {
|
|
212
216
|
if (pattern.includes('*')) {
|
|
213
217
|
if (matchWildcard(pattern, name)) return true;
|
|
214
|
-
if (matchWildcard(pattern,
|
|
218
|
+
if (matchWildcard(pattern, normalizedRelative)) return true;
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
if (pattern.endsWith('/')) {
|
|
218
222
|
const dir = pattern.slice(0, -1);
|
|
219
|
-
if (
|
|
220
|
-
|
|
223
|
+
if (normalizedFull.includes('/' + dir + '/') ||
|
|
224
|
+
normalizedFull.endsWith('/' + dir) ||
|
|
221
225
|
name === dir) {
|
|
222
226
|
return true;
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
|
|
226
|
-
if (
|
|
230
|
+
if (normalizedRelative === pattern || name === pattern) {
|
|
227
231
|
return true;
|
|
228
232
|
}
|
|
229
233
|
}
|
|
@@ -231,9 +235,16 @@ function shouldIgnore(fullPath, name, relativePath, patterns) {
|
|
|
231
235
|
return false;
|
|
232
236
|
}
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
|
|
238
|
+
// ============================================
|
|
239
|
+
// SCAN AND BUILD JSON IN ONE PASS
|
|
240
|
+
// ============================================
|
|
241
|
+
|
|
242
|
+
function scanAndBuildJson(srcPath, config) {
|
|
243
|
+
const jsonArray = [];
|
|
236
244
|
const ignorePatterns = parseIgnorePatterns(config.ignore);
|
|
245
|
+
let totalSize = 0;
|
|
246
|
+
let totalLines = 0;
|
|
247
|
+
const filesByExt = {};
|
|
237
248
|
|
|
238
249
|
function walk(dir) {
|
|
239
250
|
if (!fs.existsSync(dir)) return;
|
|
@@ -256,83 +267,175 @@ function getFiles(srcPath, config) {
|
|
|
256
267
|
if (entry.isDirectory()) {
|
|
257
268
|
walk(fullPath);
|
|
258
269
|
} else if (entry.isFile() && isTextFile(entry.name)) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
// Read file immediately when found
|
|
271
|
+
let content;
|
|
272
|
+
try {
|
|
273
|
+
content = fs.readFileSync(fullPath, 'utf-8');
|
|
274
|
+
} catch (err) {
|
|
275
|
+
// Skip files that can't be read
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ext = path.extname(entry.name).slice(1).toLowerCase() || null;
|
|
280
|
+
const lines = content.split('\n').length;
|
|
281
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
282
|
+
const language = getLanguage(entry.name);
|
|
283
|
+
|
|
284
|
+
// Update stats
|
|
285
|
+
totalSize += size;
|
|
286
|
+
totalLines += lines;
|
|
287
|
+
filesByExt[ext || 'other'] = (filesByExt[ext || 'other'] || 0) + 1;
|
|
288
|
+
|
|
289
|
+
// Add to JSON array immediately
|
|
290
|
+
jsonArray.push({
|
|
291
|
+
path: normalizePath(relativePath),
|
|
262
292
|
name: entry.name,
|
|
263
|
-
|
|
293
|
+
extension: ext,
|
|
294
|
+
language: language,
|
|
295
|
+
lines: lines,
|
|
296
|
+
size: size,
|
|
297
|
+
content: content
|
|
264
298
|
});
|
|
265
299
|
}
|
|
266
300
|
}
|
|
267
301
|
}
|
|
268
302
|
|
|
269
303
|
walk(srcPath);
|
|
270
|
-
|
|
304
|
+
|
|
305
|
+
// Sort by path for consistency
|
|
306
|
+
jsonArray.sort((a, b) => a.path.localeCompare(b.path));
|
|
307
|
+
|
|
308
|
+
const stats = {
|
|
309
|
+
files: jsonArray.length,
|
|
310
|
+
totalSize,
|
|
311
|
+
totalLines,
|
|
312
|
+
filesByExt
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return { jsonArray, stats };
|
|
271
316
|
}
|
|
272
317
|
|
|
273
|
-
|
|
318
|
+
// ============================================
|
|
319
|
+
// FORMAT CONVERTERS (from base JSON)
|
|
320
|
+
// ============================================
|
|
321
|
+
|
|
322
|
+
function toJson(baseJson) {
|
|
323
|
+
return JSON.stringify(baseJson, null, 2);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function toMarkdown(baseJson, config) {
|
|
274
327
|
let content = '';
|
|
275
|
-
let totalSize = 0;
|
|
276
|
-
let totalLines = 0;
|
|
277
|
-
const filesByExt = {};
|
|
278
328
|
|
|
279
329
|
if (config.first_comment) {
|
|
280
330
|
content += config.first_comment + '\n\n';
|
|
281
331
|
}
|
|
282
332
|
|
|
333
|
+
// Project structure
|
|
283
334
|
content += '## Project Structure\n\n```\n';
|
|
284
335
|
const dirs = new Set();
|
|
285
|
-
|
|
286
|
-
const dir = path.dirname(f.
|
|
336
|
+
baseJson.forEach(f => {
|
|
337
|
+
const dir = path.dirname(f.path);
|
|
287
338
|
if (dir !== '.') dirs.add(dir);
|
|
288
339
|
});
|
|
289
|
-
dirs.forEach(d => content += `š ${d}/\n`);
|
|
290
|
-
content += `\n${
|
|
340
|
+
Array.from(dirs).sort().forEach(d => content += `š ${d}/\n`);
|
|
341
|
+
content += `\n${baseJson.length} files total\n\`\`\`\n\n`;
|
|
291
342
|
|
|
343
|
+
// Source files
|
|
292
344
|
content += '## Source Files\n\n';
|
|
293
345
|
|
|
294
|
-
for (const file of
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
continue;
|
|
346
|
+
for (const file of baseJson) {
|
|
347
|
+
content += `### ${file.path}\n\n`;
|
|
348
|
+
content += '```' + file.language + '\n';
|
|
349
|
+
content += file.content;
|
|
350
|
+
if (!file.content.endsWith('\n')) {
|
|
351
|
+
content += '\n';
|
|
301
352
|
}
|
|
353
|
+
content += '```\n\n';
|
|
354
|
+
}
|
|
302
355
|
|
|
303
|
-
|
|
304
|
-
|
|
356
|
+
if (config.last_comment) {
|
|
357
|
+
content += config.last_comment;
|
|
358
|
+
}
|
|
305
359
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
filesByExt[file.ext || 'other'] = (filesByExt[file.ext || 'other'] || 0) + 1;
|
|
360
|
+
return content;
|
|
361
|
+
}
|
|
309
362
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
content += fileContent;
|
|
363
|
+
function toToon(baseJson, stats) {
|
|
364
|
+
let content = '';
|
|
313
365
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
366
|
+
// Meta header
|
|
367
|
+
content += `# Project Context\n`;
|
|
368
|
+
content += `# Generated: ${new Date().toISOString()}\n`;
|
|
369
|
+
content += `# Files: ${baseJson.length}\n`;
|
|
370
|
+
content += `# Lines: ${stats.totalLines}\n`;
|
|
371
|
+
content += `# Size: ${stats.totalSize} bytes\n\n`;
|
|
372
|
+
|
|
373
|
+
// Files table (compact tabular format - TOON's strength)
|
|
374
|
+
content += `files[${baseJson.length}]{path,name,extension,language,lines,size}:\n`;
|
|
375
|
+
for (const file of baseJson) {
|
|
376
|
+
const ext = file.extension || '';
|
|
377
|
+
content += ` ${escapeToonValue(file.path)},${escapeToonValue(file.name)},${ext},${file.language},${file.lines},${file.size}\n`;
|
|
378
|
+
}
|
|
317
379
|
|
|
318
|
-
|
|
380
|
+
content += '\n';
|
|
381
|
+
|
|
382
|
+
// File contents
|
|
383
|
+
for (let i = 0; i < baseJson.length; i++) {
|
|
384
|
+
const file = baseJson[i];
|
|
385
|
+
content += `---\n`;
|
|
386
|
+
content += `[${i}] ${file.path}\n`;
|
|
387
|
+
content += `language: ${file.language}\n`;
|
|
388
|
+
content += `content:\n`;
|
|
389
|
+
// Indent each line with 2 spaces
|
|
390
|
+
const lines = file.content.split('\n');
|
|
391
|
+
for (const line of lines) {
|
|
392
|
+
content += ` ${line}\n`;
|
|
393
|
+
}
|
|
319
394
|
}
|
|
320
395
|
|
|
321
|
-
|
|
322
|
-
|
|
396
|
+
return content;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function escapeToonValue(value) {
|
|
400
|
+
if (value === null || value === undefined) return '';
|
|
401
|
+
const str = String(value);
|
|
402
|
+
if (str.includes(',') || str.includes('\n') || str.includes('"') ||
|
|
403
|
+
str.startsWith(' ') || str.endsWith(' ')) {
|
|
404
|
+
return '"' + str.replace(/"/g, '""').replace(/\n/g, '\\n') + '"';
|
|
323
405
|
}
|
|
406
|
+
return str;
|
|
407
|
+
}
|
|
324
408
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
409
|
+
function toXml(baseJson) {
|
|
410
|
+
let content = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
411
|
+
content += '<context>\n';
|
|
412
|
+
|
|
413
|
+
for (const file of baseJson) {
|
|
414
|
+
content += ` <file>\n`;
|
|
415
|
+
content += ` <path>${escapeXml(file.path)}</path>\n`;
|
|
416
|
+
content += ` <name>${escapeXml(file.name)}</name>\n`;
|
|
417
|
+
content += ` <extension>${escapeXml(file.extension || '')}</extension>\n`;
|
|
418
|
+
content += ` <language>${escapeXml(file.language)}</language>\n`;
|
|
419
|
+
content += ` <lines>${file.lines}</lines>\n`;
|
|
420
|
+
content += ` <size>${file.size}</size>\n`;
|
|
421
|
+
content += ` <content><![CDATA[\n${file.content}${file.content.endsWith('\n') ? '' : '\n'}]]></content>\n`;
|
|
422
|
+
content += ` </file>\n`;
|
|
423
|
+
}
|
|
332
424
|
|
|
425
|
+
content += '</context>\n';
|
|
333
426
|
return content;
|
|
334
427
|
}
|
|
335
428
|
|
|
429
|
+
function escapeXml(str) {
|
|
430
|
+
if (str === null || str === undefined) return '';
|
|
431
|
+
return String(str)
|
|
432
|
+
.replace(/&/g, '&')
|
|
433
|
+
.replace(/</g, '<')
|
|
434
|
+
.replace(/>/g, '>')
|
|
435
|
+
.replace(/"/g, '"')
|
|
436
|
+
.replace(/'/g, ''');
|
|
437
|
+
}
|
|
438
|
+
|
|
336
439
|
// ============================================
|
|
337
440
|
// CONTEXT GENERATION
|
|
338
441
|
// ============================================
|
|
@@ -356,45 +459,42 @@ async function generateContextDynamic() {
|
|
|
356
459
|
]);
|
|
357
460
|
|
|
358
461
|
const config = { ...DEFAULT_PROJECT_CONFIG, src: srcPath };
|
|
359
|
-
return
|
|
462
|
+
return generateContext(config, srcPath);
|
|
360
463
|
}
|
|
361
464
|
|
|
362
465
|
async function generateContextFromConfigFile() {
|
|
363
466
|
loadDependencies();
|
|
364
|
-
|
|
467
|
+
|
|
365
468
|
const config = loadProjectConfig();
|
|
366
469
|
if (!config) {
|
|
367
470
|
console.log(chalk.yellow('\nā ļø No config file found.'));
|
|
368
471
|
return null;
|
|
369
472
|
}
|
|
370
473
|
|
|
371
|
-
return
|
|
474
|
+
return generateContext(config, config.src);
|
|
372
475
|
}
|
|
373
476
|
|
|
374
|
-
function
|
|
375
|
-
const spinner = ora(`Scanning ${srcPath}...`).start();
|
|
477
|
+
function generateContext(config, srcPath) {
|
|
478
|
+
const spinner = ora(`Scanning and reading files from ${srcPath}...`).start();
|
|
376
479
|
|
|
377
480
|
if (!fs.existsSync(srcPath)) {
|
|
378
481
|
spinner.fail(`Source path does not exist: ${srcPath}`);
|
|
379
482
|
return null;
|
|
380
483
|
}
|
|
381
484
|
|
|
382
|
-
|
|
485
|
+
// Single pass: scan AND build JSON at the same time
|
|
486
|
+
const { jsonArray, stats } = scanAndBuildJson(srcPath, config);
|
|
383
487
|
|
|
384
|
-
if (
|
|
488
|
+
if (jsonArray.length === 0) {
|
|
385
489
|
spinner.fail(`No files found in: ${srcPath}`);
|
|
386
490
|
return null;
|
|
387
491
|
}
|
|
388
492
|
|
|
389
|
-
spinner.
|
|
390
|
-
generatedContext = buildContextContent(contextFiles, config, srcPath);
|
|
391
|
-
|
|
392
|
-
spinner.succeed(`Context generated: ${chalk.yellow(contextFiles.length)} files, ${chalk.yellow(formatSize(contextStats.totalSize))}`);
|
|
493
|
+
spinner.succeed(`Context built: ${chalk.yellow(jsonArray.length)} files, ${chalk.yellow(formatSize(stats.totalSize))}`);
|
|
393
494
|
|
|
394
495
|
return {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
stats: contextStats,
|
|
496
|
+
baseJson: jsonArray,
|
|
497
|
+
stats: stats,
|
|
398
498
|
config
|
|
399
499
|
};
|
|
400
500
|
}
|
|
@@ -403,36 +503,113 @@ function generateContextFromConfig(config, srcPath) {
|
|
|
403
503
|
// SAVE CONTEXT
|
|
404
504
|
// ============================================
|
|
405
505
|
|
|
406
|
-
|
|
506
|
+
// ============================================
|
|
507
|
+
// SAVE CONTEXT
|
|
508
|
+
// ============================================
|
|
509
|
+
|
|
510
|
+
async function saveContext(result, formats) {
|
|
407
511
|
loadDependencies();
|
|
408
512
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
default: './mkctx'
|
|
420
|
-
}
|
|
421
|
-
]);
|
|
422
|
-
outputPath = savePath;
|
|
423
|
-
}
|
|
513
|
+
const { fileName } = await inquirer.prompt([
|
|
514
|
+
{
|
|
515
|
+
type: 'input',
|
|
516
|
+
name: 'fileName',
|
|
517
|
+
message: 'Enter a name for the output files:',
|
|
518
|
+
default: 'context'
|
|
519
|
+
}
|
|
520
|
+
]);
|
|
521
|
+
|
|
522
|
+
let outputPath = result.config.output || './mkctx';
|
|
424
523
|
|
|
425
524
|
if (!fs.existsSync(outputPath)) {
|
|
426
525
|
fs.mkdirSync(outputPath, { recursive: true });
|
|
427
526
|
}
|
|
428
527
|
|
|
429
|
-
const
|
|
430
|
-
|
|
528
|
+
const savedFiles = [];
|
|
529
|
+
|
|
530
|
+
for (const format of formats) {
|
|
531
|
+
let content;
|
|
532
|
+
let filename;
|
|
533
|
+
|
|
534
|
+
switch (format) {
|
|
535
|
+
case 'json':
|
|
536
|
+
content = toJson(result.baseJson);
|
|
537
|
+
filename = `${fileName}.json`;
|
|
538
|
+
break;
|
|
539
|
+
case 'md':
|
|
540
|
+
content = toMarkdown(result.baseJson, result.config);
|
|
541
|
+
filename = `${fileName}.md`;
|
|
542
|
+
break;
|
|
543
|
+
case 'toon':
|
|
544
|
+
content = toToon(result.baseJson, result.stats);
|
|
545
|
+
filename = `${fileName}.toon`;
|
|
546
|
+
break;
|
|
547
|
+
case 'xml':
|
|
548
|
+
content = toXml(result.baseJson);
|
|
549
|
+
filename = `${fileName}.xml`;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const outputFile = path.join(outputPath, filename);
|
|
554
|
+
fs.writeFileSync(outputFile, content);
|
|
555
|
+
const size = Buffer.byteLength(content, 'utf-8');
|
|
556
|
+
const tokens = estimateTokens(content);
|
|
557
|
+
savedFiles.push({ format, file: outputFile, size, tokens });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
console.log(chalk.green('\nā
Context saved:\n'));
|
|
561
|
+
for (const { format, file, size, tokens } of savedFiles) {
|
|
562
|
+
console.log(chalk.white(` ${chalk.cyan(format.toUpperCase().padEnd(4))} ā ${chalk.yellow(file)}`));
|
|
563
|
+
console.log(chalk.gray(` ${formatSize(size)} | ~${tokens.toLocaleString()} tokens\n`));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return savedFiles;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ============================================
|
|
570
|
+
// FORMAT SELECTION
|
|
571
|
+
// ============================================
|
|
572
|
+
|
|
573
|
+
async function selectFormat() {
|
|
574
|
+
loadDependencies();
|
|
575
|
+
|
|
576
|
+
const { format } = await inquirer.prompt([
|
|
577
|
+
{
|
|
578
|
+
type: 'list',
|
|
579
|
+
name: 'format',
|
|
580
|
+
message: 'Select output format:',
|
|
581
|
+
default: 'all',
|
|
582
|
+
choices: [
|
|
583
|
+
{
|
|
584
|
+
name: chalk.magenta('š¦ All formats (MD, JSON, TOON, XML)'),
|
|
585
|
+
value: 'all'
|
|
586
|
+
},
|
|
587
|
+
new inquirer.Separator(),
|
|
588
|
+
{
|
|
589
|
+
name: chalk.blue('š Markdown (.md)'),
|
|
590
|
+
value: 'md'
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: chalk.green('š§ JSON (.json) - Simple array'),
|
|
594
|
+
value: 'json'
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
name: chalk.yellow('š TOON (.toon) - Token-optimized'),
|
|
598
|
+
value: 'toon'
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: chalk.red('š XML (.xml)'),
|
|
602
|
+
value: 'xml'
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
]);
|
|
431
607
|
|
|
432
|
-
|
|
433
|
-
|
|
608
|
+
if (format === 'all') {
|
|
609
|
+
return ['json', 'md', 'toon', 'xml'];
|
|
610
|
+
}
|
|
434
611
|
|
|
435
|
-
return
|
|
612
|
+
return [format];
|
|
436
613
|
}
|
|
437
614
|
|
|
438
615
|
// ============================================
|
|
@@ -464,8 +641,8 @@ async function showMainMenu() {
|
|
|
464
641
|
},
|
|
465
642
|
new inquirer.Separator(),
|
|
466
643
|
{
|
|
467
|
-
name: hasConfig
|
|
468
|
-
? chalk.gray('āļø View configuration')
|
|
644
|
+
name: hasConfig
|
|
645
|
+
? chalk.gray('āļø View configuration')
|
|
469
646
|
: chalk.yellow('āļø Create configuration file'),
|
|
470
647
|
value: 'config'
|
|
471
648
|
},
|
|
@@ -488,41 +665,6 @@ async function showMainMenu() {
|
|
|
488
665
|
return action;
|
|
489
666
|
}
|
|
490
667
|
|
|
491
|
-
async function showPostGenerationMenu(result) {
|
|
492
|
-
loadDependencies();
|
|
493
|
-
|
|
494
|
-
console.log(chalk.cyan('\nš Context Summary:'));
|
|
495
|
-
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
496
|
-
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
497
|
-
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
498
|
-
console.log(chalk.white(` Est. tokens: ~${result.stats.estimatedTokens.toLocaleString()}`));
|
|
499
|
-
|
|
500
|
-
const { action } = await inquirer.prompt([
|
|
501
|
-
{
|
|
502
|
-
type: 'list',
|
|
503
|
-
name: 'action',
|
|
504
|
-
message: 'What would you like to do with this context?',
|
|
505
|
-
choices: [
|
|
506
|
-
{
|
|
507
|
-
name: chalk.blue('š¾ Save context to file'),
|
|
508
|
-
value: 'save'
|
|
509
|
-
},
|
|
510
|
-
new inquirer.Separator(),
|
|
511
|
-
{
|
|
512
|
-
name: chalk.gray('š Back to main menu'),
|
|
513
|
-
value: 'back'
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
name: chalk.red('ā Exit'),
|
|
517
|
-
value: 'exit'
|
|
518
|
-
}
|
|
519
|
-
]
|
|
520
|
-
}
|
|
521
|
-
]);
|
|
522
|
-
|
|
523
|
-
return action;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
668
|
// ============================================
|
|
527
669
|
// MAIN APPLICATION
|
|
528
670
|
// ============================================
|
|
@@ -557,18 +699,16 @@ async function main() {
|
|
|
557
699
|
case 'from-config': {
|
|
558
700
|
const result = await generateContextFromConfigFile();
|
|
559
701
|
if (result) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
running = false;
|
|
571
|
-
}
|
|
702
|
+
console.log(chalk.cyan('\nš Context Summary:'));
|
|
703
|
+
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
704
|
+
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
705
|
+
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
706
|
+
|
|
707
|
+
const formats = await selectFormat();
|
|
708
|
+
await saveContext(result, formats);
|
|
709
|
+
|
|
710
|
+
console.log(chalk.yellow('š Done!\n'));
|
|
711
|
+
running = false;
|
|
572
712
|
}
|
|
573
713
|
break;
|
|
574
714
|
}
|
|
@@ -576,18 +716,16 @@ async function main() {
|
|
|
576
716
|
case 'dynamic': {
|
|
577
717
|
const result = await generateContextDynamic();
|
|
578
718
|
if (result) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
running = false;
|
|
590
|
-
}
|
|
719
|
+
console.log(chalk.cyan('\nš Context Summary:'));
|
|
720
|
+
console.log(chalk.white(` Files: ${result.stats.files}`));
|
|
721
|
+
console.log(chalk.white(` Lines: ${result.stats.totalLines.toLocaleString()}`));
|
|
722
|
+
console.log(chalk.white(` Size: ${formatSize(result.stats.totalSize)}`));
|
|
723
|
+
|
|
724
|
+
const formats = await selectFormat();
|
|
725
|
+
await saveContext(result, formats);
|
|
726
|
+
|
|
727
|
+
console.log(chalk.yellow('š Done!\n'));
|
|
728
|
+
running = false;
|
|
591
729
|
}
|
|
592
730
|
break;
|
|
593
731
|
}
|
|
@@ -613,7 +751,7 @@ async function main() {
|
|
|
613
751
|
|
|
614
752
|
function showHelp() {
|
|
615
753
|
loadDependencies();
|
|
616
|
-
|
|
754
|
+
|
|
617
755
|
console.log(chalk.cyan(`
|
|
618
756
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
619
757
|
ā š mkctx - Make Context for AI Code Analysis ā
|
|
@@ -627,12 +765,22 @@ function showHelp() {
|
|
|
627
765
|
mkctx help Show this help message
|
|
628
766
|
mkctx version Show version
|
|
629
767
|
|
|
630
|
-
${chalk.yellow('
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
768
|
+
${chalk.yellow('Output Formats:')}
|
|
769
|
+
${chalk.green('JSON')} Simple array of file objects (base format)
|
|
770
|
+
${chalk.blue('MD')} Markdown with code blocks
|
|
771
|
+
${chalk.yellow('TOON')} Token-Oriented Object Notation (LLM optimized)
|
|
772
|
+
${chalk.red('XML')} XML with CDATA sections
|
|
773
|
+
|
|
774
|
+
${chalk.yellow('JSON Structure:')}
|
|
775
|
+
[{
|
|
776
|
+
"path": "src/index.ts",
|
|
777
|
+
"name": "index.ts",
|
|
778
|
+
"extension": "ts",
|
|
779
|
+
"language": "typescript",
|
|
780
|
+
"lines": 150,
|
|
781
|
+
"size": 4096,
|
|
782
|
+
"content": "..."
|
|
783
|
+
}]
|
|
636
784
|
|
|
637
785
|
${chalk.gray('More info: https://github.com/pnkkzero/mkctx')}
|
|
638
786
|
`));
|
|
@@ -643,7 +791,7 @@ function showVersion() {
|
|
|
643
791
|
const pkg = require('./package.json');
|
|
644
792
|
console.log(`mkctx v${pkg.version}`);
|
|
645
793
|
} catch {
|
|
646
|
-
console.log('mkctx
|
|
794
|
+
console.log('mkctx v4.0.0');
|
|
647
795
|
}
|
|
648
796
|
}
|
|
649
797
|
|