kodu 2.1.3 → 3.0.1

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 (131) hide show
  1. package/README.md +24 -3
  2. package/bin/kodu.js +23 -0
  3. package/package.json +11 -67
  4. package/scripts/postinstall.js +69 -0
  5. package/AGENTS.md +0 -214
  6. package/__tests__/core/fs/fs.service.test.ts +0 -72
  7. package/__tests__/shared/cleaner/cleaner.service.test.ts +0 -102
  8. package/__tests__/shared/git/git.service.test.ts +0 -84
  9. package/__tests__/shared/tokenizer/tokenizer.service.test.ts +0 -45
  10. package/biome.json +0 -50
  11. package/dist/package.json +0 -96
  12. package/dist/src/app.module.d.ts +0 -2
  13. package/dist/src/app.module.js +0 -36
  14. package/dist/src/app.module.js.map +0 -1
  15. package/dist/src/commands/clean/clean.command.d.ts +0 -37
  16. package/dist/src/commands/clean/clean.command.js +0 -240
  17. package/dist/src/commands/clean/clean.command.js.map +0 -1
  18. package/dist/src/commands/clean/clean.module.d.ts +0 -2
  19. package/dist/src/commands/clean/clean.module.js +0 -26
  20. package/dist/src/commands/clean/clean.module.js.map +0 -1
  21. package/dist/src/commands/init/init.command.d.ts +0 -10
  22. package/dist/src/commands/init/init.command.js +0 -96
  23. package/dist/src/commands/init/init.command.js.map +0 -1
  24. package/dist/src/commands/init/init.module.d.ts +0 -2
  25. package/dist/src/commands/init/init.module.js +0 -22
  26. package/dist/src/commands/init/init.module.js.map +0 -1
  27. package/dist/src/commands/pack/pack.command.d.ts +0 -51
  28. package/dist/src/commands/pack/pack.command.js +0 -355
  29. package/dist/src/commands/pack/pack.command.js.map +0 -1
  30. package/dist/src/commands/pack/pack.module.d.ts +0 -2
  31. package/dist/src/commands/pack/pack.module.js +0 -27
  32. package/dist/src/commands/pack/pack.module.js.map +0 -1
  33. package/dist/src/core/config/config.module.d.ts +0 -2
  34. package/dist/src/core/config/config.module.js +0 -23
  35. package/dist/src/core/config/config.module.js.map +0 -1
  36. package/dist/src/core/config/config.schema.d.ts +0 -19
  37. package/dist/src/core/config/config.schema.js +0 -56
  38. package/dist/src/core/config/config.schema.js.map +0 -1
  39. package/dist/src/core/config/config.service.d.ts +0 -7
  40. package/dist/src/core/config/config.service.js +0 -49
  41. package/dist/src/core/config/config.service.js.map +0 -1
  42. package/dist/src/core/config/prompt.service.d.ts +0 -10
  43. package/dist/src/core/config/prompt.service.js +0 -80
  44. package/dist/src/core/config/prompt.service.js.map +0 -1
  45. package/dist/src/core/file-system/fs.module.d.ts +0 -2
  46. package/dist/src/core/file-system/fs.module.js +0 -21
  47. package/dist/src/core/file-system/fs.module.js.map +0 -1
  48. package/dist/src/core/file-system/fs.service.d.ts +0 -27
  49. package/dist/src/core/file-system/fs.service.js +0 -203
  50. package/dist/src/core/file-system/fs.service.js.map +0 -1
  51. package/dist/src/core/ui/ui.module.d.ts +0 -2
  52. package/dist/src/core/ui/ui.module.js +0 -22
  53. package/dist/src/core/ui/ui.module.js.map +0 -1
  54. package/dist/src/core/ui/ui.service.d.ts +0 -22
  55. package/dist/src/core/ui/ui.service.js +0 -43
  56. package/dist/src/core/ui/ui.service.js.map +0 -1
  57. package/dist/src/main.d.ts +0 -2
  58. package/dist/src/main.js +0 -16
  59. package/dist/src/main.js.map +0 -1
  60. package/dist/src/shared/cleaner/cleaner.service.d.ts +0 -23
  61. package/dist/src/shared/cleaner/cleaner.service.js +0 -223
  62. package/dist/src/shared/cleaner/cleaner.service.js.map +0 -1
  63. package/dist/src/shared/cleaner/cleaner.types.d.ts +0 -21
  64. package/dist/src/shared/cleaner/cleaner.types.js +0 -3
  65. package/dist/src/shared/cleaner/cleaner.types.js.map +0 -1
  66. package/dist/src/shared/constants.d.ts +0 -4
  67. package/dist/src/shared/constants.js +0 -113
  68. package/dist/src/shared/constants.js.map +0 -1
  69. package/dist/src/shared/deps/deps.module.d.ts +0 -2
  70. package/dist/src/shared/deps/deps.module.js +0 -21
  71. package/dist/src/shared/deps/deps.module.js.map +0 -1
  72. package/dist/src/shared/deps/deps.service.d.ts +0 -15
  73. package/dist/src/shared/deps/deps.service.js +0 -114
  74. package/dist/src/shared/deps/deps.service.js.map +0 -1
  75. package/dist/src/shared/git/git.module.d.ts +0 -2
  76. package/dist/src/shared/git/git.module.js +0 -21
  77. package/dist/src/shared/git/git.module.js.map +0 -1
  78. package/dist/src/shared/git/git.service.d.ts +0 -5
  79. package/dist/src/shared/git/git.service.js +0 -56
  80. package/dist/src/shared/git/git.service.js.map +0 -1
  81. package/dist/src/shared/tokenizer/tokenizer.module.d.ts +0 -2
  82. package/dist/src/shared/tokenizer/tokenizer.module.js +0 -21
  83. package/dist/src/shared/tokenizer/tokenizer.module.js.map +0 -1
  84. package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -10
  85. package/dist/src/shared/tokenizer/tokenizer.service.js +0 -36
  86. package/dist/src/shared/tokenizer/tokenizer.service.js.map +0 -1
  87. package/dist/tsconfig.build.tsbuildinfo +0 -1
  88. package/docs/todo.md +0 -7
  89. package/knip.json +0 -10
  90. package/kodu.json +0 -63
  91. package/kodu.schema.json +0 -100
  92. package/lefthook.yml +0 -11
  93. package/nest-cli.json +0 -8
  94. package/scripts/generate-json-schema.ts +0 -18
  95. package/skills/doc-gen/SKILL.md +0 -490
  96. package/skills/doc-gen/scripts/doc_gen.py +0 -911
  97. package/skills/implement-project/SKILL.md +0 -409
  98. package/skills/liteend-init/SKILL.md +0 -84
  99. package/skills/litefront-init/SKILL.md +0 -96
  100. package/skills/litefront-prototype/SKILL.md +0 -484
  101. package/skills/project-setup-standardizer/SKILL.md +0 -285
  102. package/skills/start/SKILL.md +0 -319
  103. package/skills/tech-blueprint/SKILL.md +0 -890
  104. package/skills/tech-blueprint/scripts/blueprint_validator.py +0 -417
  105. package/src/app.module.ts +0 -23
  106. package/src/commands/clean/clean.command.ts +0 -235
  107. package/src/commands/clean/clean.module.ts +0 -13
  108. package/src/commands/init/init.command.ts +0 -92
  109. package/src/commands/init/init.module.ts +0 -9
  110. package/src/commands/pack/pack.command.ts +0 -347
  111. package/src/commands/pack/pack.module.ts +0 -14
  112. package/src/core/config/config.module.ts +0 -10
  113. package/src/core/config/config.schema.ts +0 -58
  114. package/src/core/config/config.service.ts +0 -43
  115. package/src/core/config/prompt.service.ts +0 -80
  116. package/src/core/file-system/fs.module.ts +0 -8
  117. package/src/core/file-system/fs.service.ts +0 -248
  118. package/src/core/ui/ui.module.ts +0 -9
  119. package/src/core/ui/ui.service.ts +0 -39
  120. package/src/main.ts +0 -12
  121. package/src/shared/cleaner/cleaner.service.ts +0 -289
  122. package/src/shared/cleaner/cleaner.types.ts +0 -23
  123. package/src/shared/constants.ts +0 -118
  124. package/src/shared/deps/deps.module.ts +0 -8
  125. package/src/shared/deps/deps.service.ts +0 -175
  126. package/src/shared/git/git.module.ts +0 -8
  127. package/src/shared/git/git.service.ts +0 -47
  128. package/src/shared/tokenizer/tokenizer.module.ts +0 -8
  129. package/src/shared/tokenizer/tokenizer.service.ts +0 -30
  130. package/tsconfig.build.json +0 -7
  131. package/tsconfig.json +0 -28
@@ -1,92 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import { Command, CommandRunner } from 'nest-commander';
4
- import { UiService } from '../../core/ui/ui.service';
5
-
6
- const GITIGNORE_ENTRY = '.kodu/context.txt';
7
-
8
- const DEFAULT_KODU_JSON = {
9
- $schema:
10
- 'https://raw.githubusercontent.com/anomalyco/kodu/main/kodu.schema.json',
11
- cleaner: {
12
- whitelist: ['//!'],
13
- keepJSDoc: true,
14
- useGitignore: true,
15
- ignore: [],
16
- },
17
- packer: {
18
- ignore: [
19
- 'package-lock.json',
20
- 'yarn.lock',
21
- 'pnpm-lock.yaml',
22
- '.git',
23
- '.kodu',
24
- 'node_modules',
25
- 'dist',
26
- 'coverage',
27
- ],
28
- useGitignore: true,
29
- contentBasedBinaryDetection: false,
30
- },
31
- };
32
-
33
- @Command({ name: 'init', description: 'Initialize kodu configuration' })
34
- export class InitCommand extends CommandRunner {
35
- constructor(private readonly ui: UiService) {
36
- super();
37
- }
38
-
39
- async run(): Promise<void> {
40
- await this.ensureKoduJson();
41
- await this.updateGitignore();
42
- this.ui.log.success('Done.');
43
- }
44
-
45
- private async ensureKoduJson(): Promise<void> {
46
- const configPath = path.join(process.cwd(), 'kodu.json');
47
-
48
- if (await this.exists(configPath)) {
49
- this.ui.log.info('kodu.json already exists');
50
- return;
51
- }
52
-
53
- await fs.writeFile(
54
- configPath,
55
- `${JSON.stringify(DEFAULT_KODU_JSON, null, 2)}\n`,
56
- 'utf8',
57
- );
58
- this.ui.log.success('Created kodu.json');
59
- }
60
-
61
- private async updateGitignore(): Promise<void> {
62
- const gitignorePath = path.join(process.cwd(), '.gitignore');
63
-
64
- if (!(await this.exists(gitignorePath))) {
65
- this.ui.log.warn('.gitignore not found, skipping.');
66
- return;
67
- }
68
-
69
- const content = await fs.readFile(gitignorePath, 'utf8');
70
- const lines = content.split(/\r?\n/);
71
-
72
- if (lines.some((line) => line.trim() === GITIGNORE_ENTRY)) {
73
- this.ui.log.info(`${GITIGNORE_ENTRY} already in .gitignore`);
74
- return;
75
- }
76
-
77
- const trimmed = content.trimEnd();
78
- const next =
79
- trimmed.length > 0 ? `${trimmed}\n${GITIGNORE_ENTRY}` : GITIGNORE_ENTRY;
80
- await fs.writeFile(gitignorePath, `${next}\n`, 'utf8');
81
- this.ui.log.success(`Added ${GITIGNORE_ENTRY} to .gitignore`);
82
- }
83
-
84
- private async exists(target: string): Promise<boolean> {
85
- try {
86
- await fs.access(target);
87
- return true;
88
- } catch {
89
- return false;
90
- }
91
- }
92
- }
@@ -1,9 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { UiModule } from '../../core/ui/ui.module';
3
- import { InitCommand } from './init.command';
4
-
5
- @Module({
6
- imports: [UiModule],
7
- providers: [InitCommand],
8
- })
9
- export class InitModule {}
@@ -1,347 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import clipboard from 'clipboardy';
4
- import { Command, CommandRunner, Option } from 'nest-commander';
5
- import { ConfigService } from '../../core/config/config.service';
6
- import { PromptService } from '../../core/config/prompt.service';
7
- import { FsService } from '../../core/file-system/fs.service';
8
- import { UiService } from '../../core/ui/ui.service';
9
- import { CleanerService } from '../../shared/cleaner/cleaner.service';
10
- import { DepsService } from '../../shared/deps/deps.service';
11
- import { TokenizerService } from '../../shared/tokenizer/tokenizer.service';
12
-
13
- type OutputFormat = 'xml' | 'text';
14
-
15
- type PackOptions = {
16
- copy?: boolean;
17
- template?: string;
18
- out?: string;
19
- path?: string[];
20
- exclude?: string[];
21
- list?: boolean;
22
- format?: OutputFormat;
23
- clean?: boolean;
24
- deps?: boolean;
25
- depsDepth?: number;
26
- explain?: boolean;
27
- };
28
-
29
- type TemplateContext = {
30
- context: string;
31
- fileList: string;
32
- tokenCount: number;
33
- usdEstimate: number;
34
- };
35
-
36
- @Command({
37
- name: 'pack',
38
- description: 'Collect project context into a single file',
39
- })
40
- export class PackCommand extends CommandRunner {
41
- constructor(
42
- private readonly ui: UiService,
43
- private readonly configService: ConfigService,
44
- private readonly promptService: PromptService,
45
- private readonly fsService: FsService,
46
- private readonly tokenizer: TokenizerService,
47
- private readonly cleaner: CleanerService,
48
- private readonly depsService: DepsService,
49
- ) {
50
- super();
51
- }
52
-
53
- @Option({ flags: '-c, --copy', description: 'Copy result to clipboard' })
54
- parseCopy(): boolean {
55
- return true;
56
- }
57
-
58
- @Option({
59
- flags: '-t, --template <name>',
60
- description: 'Template name from .kodu/prompts',
61
- })
62
- parseTemplate(value: string): string {
63
- return value;
64
- }
65
-
66
- @Option({
67
- flags: '-o, --out <path>',
68
- description: 'Path to save result',
69
- })
70
- parseOut(value: string): string {
71
- return value;
72
- }
73
-
74
- @Option({
75
- flags: '-p, --path <path>',
76
- description: 'Directory or glob to include (repeatable)',
77
- })
78
- parsePath(value: string, previous: string[] = []): string[] {
79
- return [...previous, value];
80
- }
81
-
82
- @Option({
83
- flags: '-e, --exclude <pattern>',
84
- description: 'Additional exclude pattern (repeatable)',
85
- })
86
- parseExclude(value: string, previous: string[] = []): string[] {
87
- return [...previous, value];
88
- }
89
-
90
- @Option({
91
- flags: '-l, --list',
92
- description: 'Print file list only, without content',
93
- })
94
- parseList(): boolean {
95
- return true;
96
- }
97
-
98
- @Option({
99
- flags: '--clean',
100
- description: 'Strip comments in-memory before packing (files not modified)',
101
- })
102
- parseClean(): boolean {
103
- return true;
104
- }
105
-
106
- @Option({
107
- flags: '--deps',
108
- description:
109
- 'Trace imports from entry point(s) and include their dependencies',
110
- })
111
- parseDeps(): boolean {
112
- return true;
113
- }
114
-
115
- @Option({
116
- flags: '--deps-depth <n>',
117
- description: 'Max import depth when using --deps (default: unlimited)',
118
- })
119
- parseDepsDepth(value: string): number {
120
- const n = Number.parseInt(value, 10);
121
- if (Number.isNaN(n) || n < 1) {
122
- this.ui.log.warn(`Invalid --deps-depth "${value}", ignoring`);
123
- return Infinity;
124
- }
125
- return n;
126
- }
127
-
128
- @Option({
129
- flags: '--explain',
130
- description: 'Print why each file was included (requires --deps)',
131
- })
132
- parseExplain(): boolean {
133
- return true;
134
- }
135
-
136
- @Option({
137
- flags: '-f, --format <format>',
138
- description: 'Output format: xml (default) or text',
139
- })
140
- parseFormat(value: string): OutputFormat {
141
- if (value !== 'xml' && value !== 'text') {
142
- this.ui.log.warn(`Unknown format "${value}", using "xml"`);
143
- return 'xml';
144
- }
145
- return value;
146
- }
147
-
148
- async run(inputs: string[], options: PackOptions): Promise<void> {
149
- const spinner = this.ui
150
- .createSpinner({ text: 'Collecting files...' })
151
- .start();
152
-
153
- try {
154
- const { packer } = this.configService.getConfig();
155
- const extraExcludes = options.exclude ?? [];
156
-
157
- let files: string[];
158
- let explainMap: Map<string, string> | undefined;
159
-
160
- if (options.deps) {
161
- if (inputs.length === 0) {
162
- spinner.error('--deps requires at least one entry file as argument');
163
- this.ui.log.error('Usage: kodu pack <entry.ts> [more.ts...] --deps');
164
- process.exitCode = 1;
165
- return;
166
- }
167
-
168
- spinner.text = 'Resolving dependency graph...';
169
- const result = this.depsService.collectDependencies(
170
- inputs,
171
- process.cwd(),
172
- {
173
- maxDepth: options.depsDepth,
174
- includeTypes: true,
175
- includeDynamic: false,
176
- },
177
- );
178
- files = result.files;
179
- explainMap = result.explain;
180
- } else {
181
- files = await this.fsService.findProjectFiles({
182
- excludeBinary: true,
183
- useGitignore: packer.useGitignore,
184
- ignore: [...packer.ignore, ...extraExcludes],
185
- contentBasedBinaryDetection: packer.contentBasedBinaryDetection,
186
- rootPaths: inputs.length > 0 ? inputs : options.path,
187
- });
188
- }
189
-
190
- if (files.length === 0) {
191
- spinner.stop('No files to pack.');
192
- this.ui.log.warn('No files to pack.');
193
- return;
194
- }
195
-
196
- if (options.list) {
197
- spinner.success(`Found ${files.length} files`);
198
- for (const file of files) {
199
- if (options.explain && explainMap) {
200
- const absFile = path.resolve(process.cwd(), file);
201
- const reason =
202
- explainMap.get(absFile) ?? explainMap.get(file) ?? '';
203
- this.ui.log.info(`${file} ← ${reason}`);
204
- } else {
205
- this.ui.log.info(file);
206
- }
207
- }
208
- return;
209
- }
210
-
211
- if (options.explain && explainMap) {
212
- spinner.success(`Found ${files.length} files`);
213
- this.ui.log.info('Dependency explanation:');
214
- for (const file of files) {
215
- const absFile = path.resolve(process.cwd(), file);
216
- const reason = explainMap.get(absFile) ?? explainMap.get(file) ?? '';
217
- this.ui.log.info(` ${file} ← ${reason}`);
218
- }
219
- }
220
-
221
- const format: OutputFormat = options.format ?? 'xml';
222
- const context = await this.buildContext(files, format, options.clean);
223
- const fileList = files.join('\n');
224
- const { tokens, usdEstimate } = this.tokenizer.count(context);
225
-
226
- const basePrompt = await this.applyConfiguredPrompt({
227
- context,
228
- fileList,
229
- tokenCount: tokens,
230
- usdEstimate,
231
- });
232
-
233
- const templateApplied = options.template
234
- ? await this.applyTemplate(options.template, {
235
- context,
236
- fileList,
237
- tokenCount: tokens,
238
- usdEstimate,
239
- })
240
- : basePrompt;
241
-
242
- const outputPath = await this.writeOutput(templateApplied, options.out);
243
-
244
- if (options.copy) {
245
- await clipboard.write(templateApplied);
246
- }
247
-
248
- spinner.success('Collection complete');
249
- this.ui.log.info(`Files: ${files.length}`);
250
- this.ui.log.info(`Tokens: ${tokens}`);
251
- this.ui.log.info(`Cost estimate: ~$${usdEstimate.toFixed(4)}`);
252
- this.ui.log.info(
253
- `Format: ${format}${options.clean ? ' (comments stripped)' : ''}`,
254
- );
255
- this.ui.log.success(`Saved to ${outputPath}`);
256
-
257
- if (options.copy) {
258
- this.ui.log.success('Result copied to clipboard');
259
- }
260
- } catch (error) {
261
- spinner.error('Error collecting context');
262
- const message = error instanceof Error ? error.message : 'Unknown error';
263
- this.ui.log.error(message);
264
- process.exitCode = 1;
265
- }
266
- }
267
-
268
- private async buildContext(
269
- files: string[],
270
- format: OutputFormat,
271
- clean = false,
272
- ): Promise<string> {
273
- const chunks = await Promise.all(
274
- files.map(async (file) => {
275
- let content = await this.fsService.readFileRelative(file);
276
- if (clean) {
277
- content = this.cleaner.cleanContent(file, content);
278
- }
279
- if (format === 'xml') {
280
- return `<file path="${file}">\n${content}\n</file>`;
281
- }
282
- return `// file: ${file}\n${content}`;
283
- }),
284
- );
285
-
286
- if (format === 'xml') {
287
- return `<files>\n${chunks.join('\n\n')}\n</files>`;
288
- }
289
- return chunks.join('\n\n');
290
- }
291
-
292
- private async applyTemplate(
293
- name: string,
294
- ctx: TemplateContext,
295
- ): Promise<string> {
296
- const template = await this.loadTemplate(name);
297
- return this.fillTemplate(template, ctx);
298
- }
299
-
300
- private async loadTemplate(name: string): Promise<string> {
301
- return this.promptService.loadFromPromptsDir(name);
302
- }
303
-
304
- private async writeOutput(
305
- content: string,
306
- outPath?: string,
307
- ): Promise<string> {
308
- const target = outPath ?? path.join(process.cwd(), '.kodu', 'context.txt');
309
- const dir = path.dirname(target);
310
- await fs.mkdir(dir, { recursive: true });
311
- await fs.writeFile(target, `${content}\n`, 'utf8');
312
- return target;
313
- }
314
-
315
- private async applyConfiguredPrompt(ctx: TemplateContext): Promise<string> {
316
- const config = this.configService.getConfig();
317
- const packPrompt = config.prompts?.pack;
318
-
319
- if (!packPrompt) {
320
- return ctx.context;
321
- }
322
-
323
- try {
324
- const template = await this.promptService.load(packPrompt);
325
- return this.fillTemplate(template, ctx);
326
- } catch {
327
- this.ui.log.warn(
328
- `Prompt file not found: ${packPrompt}, using raw context`,
329
- );
330
- return ctx.context;
331
- }
332
- }
333
-
334
- private fillTemplate(template: string, ctx: TemplateContext): string {
335
- const filled = template
336
- .replace(/\{\{context\}\}/g, ctx.context)
337
- .replace(/\{\{fileList\}\}/g, ctx.fileList)
338
- .replace(/\{\{tokenCount\}\}/g, ctx.tokenCount.toString())
339
- .replace(/\{\{usdEstimate\}\}/g, ctx.usdEstimate.toFixed(4));
340
-
341
- if (!template.includes('{{context}}')) {
342
- return `${filled}\n\n${ctx.context}`;
343
- }
344
-
345
- return filled;
346
- }
347
- }
@@ -1,14 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { ConfigModule } from '../../core/config/config.module';
3
- import { FsModule } from '../../core/file-system/fs.module';
4
- import { UiModule } from '../../core/ui/ui.module';
5
- import { CleanerService } from '../../shared/cleaner/cleaner.service';
6
- import { DepsModule } from '../../shared/deps/deps.module';
7
- import { TokenizerModule } from '../../shared/tokenizer/tokenizer.module';
8
- import { PackCommand } from './pack.command';
9
-
10
- @Module({
11
- imports: [ConfigModule, UiModule, FsModule, TokenizerModule, DepsModule],
12
- providers: [PackCommand, CleanerService],
13
- })
14
- export class PackModule {}
@@ -1,10 +0,0 @@
1
- import { Global, Module } from '@nestjs/common';
2
- import { ConfigService } from './config.service';
3
- import { PromptService } from './prompt.service';
4
-
5
- @Global()
6
- @Module({
7
- providers: [ConfigService, PromptService],
8
- exports: [ConfigService, PromptService],
9
- })
10
- export class ConfigModule {}
@@ -1,58 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- const cleanerSchema = z.object({
4
- whitelist: z.array(z.string()).default(['//!']),
5
- keepJSDoc: z.boolean().default(true),
6
- useGitignore: z.boolean().default(true),
7
- ignore: z.array(z.string()).default([]),
8
- });
9
-
10
- const packerSchema = z.object({
11
- ignore: z
12
- .array(z.string())
13
- .default([
14
- 'package-lock.json',
15
- 'yarn.lock',
16
- 'pnpm-lock.yaml',
17
- '.git',
18
- '.kodu',
19
- 'node_modules',
20
- 'dist',
21
- 'coverage',
22
- ]),
23
- useGitignore: z.boolean().default(true),
24
- contentBasedBinaryDetection: z.boolean().default(false),
25
- });
26
-
27
- const promptsSchema = z
28
- .object({
29
- pack: z.string().optional(),
30
- })
31
- .optional();
32
-
33
- export const configSchema = z.object({
34
- $schema: z.string().optional(),
35
- cleaner: cleanerSchema.default({
36
- whitelist: ['//!'],
37
- keepJSDoc: true,
38
- useGitignore: true,
39
- ignore: [],
40
- }),
41
- packer: packerSchema.default({
42
- ignore: [
43
- 'package-lock.json',
44
- 'yarn.lock',
45
- 'pnpm-lock.yaml',
46
- '.git',
47
- '.kodu',
48
- 'node_modules',
49
- 'dist',
50
- 'coverage',
51
- ],
52
- useGitignore: true,
53
- contentBasedBinaryDetection: false,
54
- }),
55
- prompts: promptsSchema,
56
- });
57
-
58
- export type KoduConfig = z.infer<typeof configSchema>;
@@ -1,43 +0,0 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { lilconfigSync } from 'lilconfig';
3
- import pc from 'picocolors';
4
- import { configSchema, type KoduConfig } from './config.schema';
5
-
6
- @Injectable()
7
- export class ConfigService {
8
- private config?: KoduConfig;
9
-
10
- getConfig(): KoduConfig {
11
- if (!this.config) {
12
- this.config = this.loadConfig();
13
- }
14
-
15
- return this.config;
16
- }
17
-
18
- private loadConfig(): KoduConfig {
19
- const explorer = lilconfigSync('kodu', { searchPlaces: ['kodu.json'] });
20
- const result = explorer.search(process.cwd());
21
-
22
- const rawConfig =
23
- result && !result.isEmpty && result.config ? result.config : {};
24
-
25
- const parsed = configSchema.safeParse(rawConfig);
26
-
27
- if (!parsed.success) {
28
- console.error(pc.red('kodu.json is invalid:'));
29
- parsed.error.issues.forEach((issue) => {
30
- const path = issue.path.join('.') || '(root)';
31
- console.error(pc.red(`- ${path}: ${issue.message}`));
32
- });
33
- this.terminate('Fix the config and run the command again.');
34
- }
35
-
36
- return parsed.data;
37
- }
38
-
39
- private terminate(message: string): never {
40
- console.error(pc.red(message));
41
- process.exit(1);
42
- }
43
- }
@@ -1,80 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import { Injectable } from '@nestjs/common';
4
-
5
- @Injectable()
6
- export class PromptService {
7
- private readonly cache = new Map<string, string>();
8
-
9
- async load(source: string): Promise<string> {
10
- return this.readSource(source);
11
- }
12
-
13
- async loadFromPromptsDir(name: string): Promise<string> {
14
- const candidates = this.buildCandidates(name);
15
-
16
- for (const candidate of candidates) {
17
- if (await this.exists(candidate)) {
18
- return this.readAndCache(candidate);
19
- }
20
- }
21
-
22
- throw new Error(
23
- `Template ${name} not found. Expected files: ${candidates
24
- .map((c) => path.relative(process.cwd(), c))
25
- .join(', ')}`,
26
- );
27
- }
28
-
29
- private async readSource(source: string): Promise<string> {
30
- const resolved = path.isAbsolute(source)
31
- ? source
32
- : path.resolve(process.cwd(), source);
33
-
34
- if (await this.exists(resolved)) {
35
- return this.readAndCache(resolved);
36
- }
37
-
38
- if (this.looksLikeInline(source)) {
39
- return source;
40
- }
41
-
42
- throw new Error(
43
- `Prompt file not found: ${path.relative(process.cwd(), resolved)}`,
44
- );
45
- }
46
-
47
- private async readAndCache(target: string): Promise<string> {
48
- const cached = this.cache.get(target);
49
- if (cached) {
50
- return cached;
51
- }
52
- const content = await fs.readFile(target, 'utf8');
53
- this.cache.set(target, content);
54
- return content;
55
- }
56
-
57
- private looksLikeInline(value: string): boolean {
58
- if (value.includes('\n')) {
59
- return true;
60
- }
61
- const hasPathSegments = value.includes('/') || value.includes('\\');
62
- const hasExtension = path.extname(value) !== '';
63
- return value.trim().length > 0 && !hasPathSegments && !hasExtension;
64
- }
65
-
66
- private buildCandidates(name: string): string[] {
67
- const names = path.extname(name) ? [name] : [`${name}.md`, `${name}.txt`];
68
- const root = path.join(process.cwd(), '.kodu', 'prompts');
69
- return names.map((variant) => path.join(root, variant));
70
- }
71
-
72
- private async exists(target: string): Promise<boolean> {
73
- try {
74
- await fs.access(target);
75
- return true;
76
- } catch {
77
- return false;
78
- }
79
- }
80
- }
@@ -1,8 +0,0 @@
1
- import { Module } from '@nestjs/common';
2
- import { FsService } from './fs.service';
3
-
4
- @Module({
5
- providers: [FsService],
6
- exports: [FsService],
7
- })
8
- export class FsModule {}