kodu 2.2.0 → 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 (233) 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__/core/registry/registry.service.test.ts +0 -82
  8. package/__tests__/shared/cleaner/cleaner.service.test.ts +0 -102
  9. package/__tests__/shared/git/git.service.test.ts +0 -84
  10. package/__tests__/shared/runbook/runbook.service.test.ts +0 -104
  11. package/__tests__/shared/tokenizer/tokenizer.service.test.ts +0 -45
  12. package/biome.json +0 -50
  13. package/dist/package.json +0 -96
  14. package/dist/src/app.module.d.ts +0 -2
  15. package/dist/src/app.module.js +0 -42
  16. package/dist/src/app.module.js.map +0 -1
  17. package/dist/src/commands/clean/clean.command.d.ts +0 -37
  18. package/dist/src/commands/clean/clean.command.js +0 -240
  19. package/dist/src/commands/clean/clean.command.js.map +0 -1
  20. package/dist/src/commands/clean/clean.module.d.ts +0 -2
  21. package/dist/src/commands/clean/clean.module.js +0 -26
  22. package/dist/src/commands/clean/clean.module.js.map +0 -1
  23. package/dist/src/commands/init/init.command.d.ts +0 -10
  24. package/dist/src/commands/init/init.command.js +0 -96
  25. package/dist/src/commands/init/init.command.js.map +0 -1
  26. package/dist/src/commands/init/init.module.d.ts +0 -2
  27. package/dist/src/commands/init/init.module.js +0 -22
  28. package/dist/src/commands/init/init.module.js.map +0 -1
  29. package/dist/src/commands/ops/ops-add.command.d.ts +0 -18
  30. package/dist/src/commands/ops/ops-add.command.js +0 -102
  31. package/dist/src/commands/ops/ops-add.command.js.map +0 -1
  32. package/dist/src/commands/ops/ops-init.command.d.ts +0 -22
  33. package/dist/src/commands/ops/ops-init.command.js +0 -130
  34. package/dist/src/commands/ops/ops-init.command.js.map +0 -1
  35. package/dist/src/commands/ops/ops-list.command.d.ts +0 -12
  36. package/dist/src/commands/ops/ops-list.command.js +0 -73
  37. package/dist/src/commands/ops/ops-list.command.js.map +0 -1
  38. package/dist/src/commands/ops/ops-path.command.d.ts +0 -9
  39. package/dist/src/commands/ops/ops-path.command.js +0 -52
  40. package/dist/src/commands/ops/ops-path.command.js.map +0 -1
  41. package/dist/src/commands/ops/ops-runbook.command.d.ts +0 -12
  42. package/dist/src/commands/ops/ops-runbook.command.js +0 -81
  43. package/dist/src/commands/ops/ops-runbook.command.js.map +0 -1
  44. package/dist/src/commands/ops/ops-status.command.d.ts +0 -11
  45. package/dist/src/commands/ops/ops-status.command.js +0 -62
  46. package/dist/src/commands/ops/ops-status.command.js.map +0 -1
  47. package/dist/src/commands/ops/ops-use.command.d.ts +0 -12
  48. package/dist/src/commands/ops/ops-use.command.js +0 -76
  49. package/dist/src/commands/ops/ops-use.command.js.map +0 -1
  50. package/dist/src/commands/ops/ops.command.d.ts +0 -7
  51. package/dist/src/commands/ops/ops.command.js +0 -56
  52. package/dist/src/commands/ops/ops.command.js.map +0 -1
  53. package/dist/src/commands/ops/ops.helpers.d.ts +0 -2
  54. package/dist/src/commands/ops/ops.helpers.js +0 -11
  55. package/dist/src/commands/ops/ops.helpers.js.map +0 -1
  56. package/dist/src/commands/ops/ops.module.d.ts +0 -2
  57. package/dist/src/commands/ops/ops.module.js +0 -36
  58. package/dist/src/commands/ops/ops.module.js.map +0 -1
  59. package/dist/src/commands/pack/pack.command.d.ts +0 -51
  60. package/dist/src/commands/pack/pack.command.js +0 -355
  61. package/dist/src/commands/pack/pack.command.js.map +0 -1
  62. package/dist/src/commands/pack/pack.module.d.ts +0 -2
  63. package/dist/src/commands/pack/pack.module.js +0 -27
  64. package/dist/src/commands/pack/pack.module.js.map +0 -1
  65. package/dist/src/core/config/config.module.d.ts +0 -2
  66. package/dist/src/core/config/config.module.js +0 -23
  67. package/dist/src/core/config/config.module.js.map +0 -1
  68. package/dist/src/core/config/config.schema.d.ts +0 -19
  69. package/dist/src/core/config/config.schema.js +0 -56
  70. package/dist/src/core/config/config.schema.js.map +0 -1
  71. package/dist/src/core/config/config.service.d.ts +0 -7
  72. package/dist/src/core/config/config.service.js +0 -49
  73. package/dist/src/core/config/config.service.js.map +0 -1
  74. package/dist/src/core/config/prompt.service.d.ts +0 -10
  75. package/dist/src/core/config/prompt.service.js +0 -80
  76. package/dist/src/core/config/prompt.service.js.map +0 -1
  77. package/dist/src/core/file-system/fs.module.d.ts +0 -2
  78. package/dist/src/core/file-system/fs.module.js +0 -21
  79. package/dist/src/core/file-system/fs.module.js.map +0 -1
  80. package/dist/src/core/file-system/fs.service.d.ts +0 -27
  81. package/dist/src/core/file-system/fs.service.js +0 -203
  82. package/dist/src/core/file-system/fs.service.js.map +0 -1
  83. package/dist/src/core/registry/registry.module.d.ts +0 -2
  84. package/dist/src/core/registry/registry.module.js +0 -22
  85. package/dist/src/core/registry/registry.module.js.map +0 -1
  86. package/dist/src/core/registry/registry.schema.d.ts +0 -24
  87. package/dist/src/core/registry/registry.schema.js +0 -21
  88. package/dist/src/core/registry/registry.schema.js.map +0 -1
  89. package/dist/src/core/registry/registry.service.d.ts +0 -16
  90. package/dist/src/core/registry/registry.service.js +0 -91
  91. package/dist/src/core/registry/registry.service.js.map +0 -1
  92. package/dist/src/core/ui/ui.module.d.ts +0 -2
  93. package/dist/src/core/ui/ui.module.js +0 -22
  94. package/dist/src/core/ui/ui.module.js.map +0 -1
  95. package/dist/src/core/ui/ui.service.d.ts +0 -22
  96. package/dist/src/core/ui/ui.service.js +0 -43
  97. package/dist/src/core/ui/ui.service.js.map +0 -1
  98. package/dist/src/main.d.ts +0 -2
  99. package/dist/src/main.js +0 -16
  100. package/dist/src/main.js.map +0 -1
  101. package/dist/src/shared/cleaner/cleaner.service.d.ts +0 -23
  102. package/dist/src/shared/cleaner/cleaner.service.js +0 -223
  103. package/dist/src/shared/cleaner/cleaner.service.js.map +0 -1
  104. package/dist/src/shared/cleaner/cleaner.types.d.ts +0 -21
  105. package/dist/src/shared/cleaner/cleaner.types.js +0 -3
  106. package/dist/src/shared/cleaner/cleaner.types.js.map +0 -1
  107. package/dist/src/shared/constants.d.ts +0 -4
  108. package/dist/src/shared/constants.js +0 -113
  109. package/dist/src/shared/constants.js.map +0 -1
  110. package/dist/src/shared/deps/deps.module.d.ts +0 -2
  111. package/dist/src/shared/deps/deps.module.js +0 -21
  112. package/dist/src/shared/deps/deps.module.js.map +0 -1
  113. package/dist/src/shared/deps/deps.service.d.ts +0 -15
  114. package/dist/src/shared/deps/deps.service.js +0 -114
  115. package/dist/src/shared/deps/deps.service.js.map +0 -1
  116. package/dist/src/shared/git/git.module.d.ts +0 -2
  117. package/dist/src/shared/git/git.module.js +0 -21
  118. package/dist/src/shared/git/git.module.js.map +0 -1
  119. package/dist/src/shared/git/git.service.d.ts +0 -5
  120. package/dist/src/shared/git/git.service.js +0 -56
  121. package/dist/src/shared/git/git.service.js.map +0 -1
  122. package/dist/src/shared/runbook/runbook.module.d.ts +0 -2
  123. package/dist/src/shared/runbook/runbook.module.js +0 -22
  124. package/dist/src/shared/runbook/runbook.module.js.map +0 -1
  125. package/dist/src/shared/runbook/runbook.service.d.ts +0 -20
  126. package/dist/src/shared/runbook/runbook.service.js +0 -118
  127. package/dist/src/shared/runbook/runbook.service.js.map +0 -1
  128. package/dist/src/shared/runbook/runbook.templates.d.ts +0 -6
  129. package/dist/src/shared/runbook/runbook.templates.js +0 -49
  130. package/dist/src/shared/runbook/runbook.templates.js.map +0 -1
  131. package/dist/src/shared/tokenizer/tokenizer.module.d.ts +0 -2
  132. package/dist/src/shared/tokenizer/tokenizer.module.js +0 -21
  133. package/dist/src/shared/tokenizer/tokenizer.module.js.map +0 -1
  134. package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -10
  135. package/dist/src/shared/tokenizer/tokenizer.service.js +0 -36
  136. package/dist/src/shared/tokenizer/tokenizer.service.js.map +0 -1
  137. package/dist/tsconfig.build.tsbuildinfo +0 -1
  138. package/docs/todo.md +0 -7
  139. package/knip.json +0 -10
  140. package/kodu.json +0 -63
  141. package/kodu.schema.json +0 -100
  142. package/lefthook.yml +0 -11
  143. package/nest-cli.json +0 -8
  144. package/registry.schema.json +0 -39
  145. package/scripts/generate-json-schema.ts +0 -27
  146. package/skills/ac/SKILL.md +0 -239
  147. package/skills/al/SKILL.md +0 -98
  148. package/skills/audit/SKILL.md +0 -205
  149. package/skills/audit/audit-baseline-template.yml +0 -188
  150. package/skills/audit/runtime-detect.md +0 -64
  151. package/skills/audit/stacks/_generic.md +0 -41
  152. package/skills/audit/stacks/_registry.md +0 -47
  153. package/skills/audit/stacks/go.md +0 -66
  154. package/skills/audit/stacks/java.md +0 -44
  155. package/skills/audit/stacks/node.md +0 -57
  156. package/skills/audit/stacks/python.md +0 -45
  157. package/skills/audit/stacks/rust.md +0 -44
  158. package/skills/audit-api-contracts/SKILL.md +0 -201
  159. package/skills/audit-architecture/SKILL.md +0 -200
  160. package/skills/audit-bugs/SKILL.md +0 -226
  161. package/skills/audit-concurrency/SKILL.md +0 -197
  162. package/skills/audit-deployment/SKILL.md +0 -218
  163. package/skills/audit-docs/SKILL.md +0 -209
  164. package/skills/audit-errors/SKILL.md +0 -216
  165. package/skills/audit-logging/SKILL.md +0 -197
  166. package/skills/audit-matrix/SKILL.md +0 -245
  167. package/skills/audit-meta/SKILL.md +0 -120
  168. package/skills/audit-naming/SKILL.md +0 -200
  169. package/skills/audit-owasp/SKILL.md +0 -223
  170. package/skills/audit-performance/SKILL.md +0 -199
  171. package/skills/audit-reinvention/SKILL.md +0 -214
  172. package/skills/audit-secrets/SKILL.md +0 -198
  173. package/skills/audit-tests/SKILL.md +0 -210
  174. package/skills/audit-validation/SKILL.md +0 -206
  175. package/skills/audit-verify/SKILL.md +0 -139
  176. package/skills/audit-yagni/SKILL.md +0 -188
  177. package/skills/doc-gen/SKILL.md +0 -490
  178. package/skills/doc-gen/scripts/doc_gen.py +0 -911
  179. package/skills/generate-project-docs/SKILL.md +0 -380
  180. package/skills/implement-project/SKILL.md +0 -409
  181. package/skills/liteend-init/SKILL.md +0 -84
  182. package/skills/litefront-init/SKILL.md +0 -96
  183. package/skills/litefront-prototype/SKILL.md +0 -484
  184. package/skills/ops/SKILL.md +0 -94
  185. package/skills/post-call-task-builder/SKILL.md +0 -419
  186. package/skills/project-setup-standardizer/SKILL.md +0 -285
  187. package/skills/skills-best-practices/SKILL.md +0 -415
  188. package/skills/start/SKILL.md +0 -319
  189. package/skills/tech-blueprint/SKILL.md +0 -890
  190. package/skills/tech-blueprint/scripts/blueprint_validator.py +0 -417
  191. package/src/app.module.ts +0 -29
  192. package/src/commands/clean/clean.command.ts +0 -235
  193. package/src/commands/clean/clean.module.ts +0 -13
  194. package/src/commands/init/init.command.ts +0 -92
  195. package/src/commands/init/init.module.ts +0 -9
  196. package/src/commands/ops/ops-add.command.ts +0 -83
  197. package/src/commands/ops/ops-init.command.ts +0 -125
  198. package/src/commands/ops/ops-list.command.ts +0 -57
  199. package/src/commands/ops/ops-path.command.ts +0 -38
  200. package/src/commands/ops/ops-runbook.command.ts +0 -74
  201. package/src/commands/ops/ops-status.command.ts +0 -47
  202. package/src/commands/ops/ops-use.command.ts +0 -76
  203. package/src/commands/ops/ops.command.ts +0 -42
  204. package/src/commands/ops/ops.helpers.ts +0 -20
  205. package/src/commands/ops/ops.module.ts +0 -23
  206. package/src/commands/pack/pack.command.ts +0 -347
  207. package/src/commands/pack/pack.module.ts +0 -14
  208. package/src/core/config/config.module.ts +0 -10
  209. package/src/core/config/config.schema.ts +0 -58
  210. package/src/core/config/config.service.ts +0 -43
  211. package/src/core/config/prompt.service.ts +0 -80
  212. package/src/core/file-system/fs.module.ts +0 -8
  213. package/src/core/file-system/fs.service.ts +0 -248
  214. package/src/core/registry/registry.module.ts +0 -9
  215. package/src/core/registry/registry.schema.ts +0 -46
  216. package/src/core/registry/registry.service.ts +0 -128
  217. package/src/core/ui/ui.module.ts +0 -9
  218. package/src/core/ui/ui.service.ts +0 -39
  219. package/src/main.ts +0 -12
  220. package/src/shared/cleaner/cleaner.service.ts +0 -289
  221. package/src/shared/cleaner/cleaner.types.ts +0 -23
  222. package/src/shared/constants.ts +0 -118
  223. package/src/shared/deps/deps.module.ts +0 -8
  224. package/src/shared/deps/deps.service.ts +0 -175
  225. package/src/shared/git/git.module.ts +0 -8
  226. package/src/shared/git/git.service.ts +0 -47
  227. package/src/shared/runbook/runbook.module.ts +0 -9
  228. package/src/shared/runbook/runbook.service.ts +0 -164
  229. package/src/shared/runbook/runbook.templates.ts +0 -66
  230. package/src/shared/tokenizer/tokenizer.module.ts +0 -8
  231. package/src/shared/tokenizer/tokenizer.service.ts +0 -30
  232. package/tsconfig.build.json +0 -7
  233. package/tsconfig.json +0 -28
@@ -1,248 +0,0 @@
1
- import { promises as fs, type Stats } from 'node:fs';
2
- import path from 'node:path';
3
- import { Injectable } from '@nestjs/common';
4
- import ignore from 'ignore';
5
- import { glob } from 'tinyglobby';
6
- import {
7
- BINARY_EXTENSIONS,
8
- KNOWN_TEXT_EXTENSIONS,
9
- MAX_FILE_SIZE_BYTES,
10
- } from '../../shared/constants';
11
- import { ConfigService } from '../config/config.service';
12
- import { UiService } from '../ui/ui.service';
13
-
14
- const BINARY_PROBE_SIZE = 8192;
15
- const GLOB_IGNORE = ['.git/**'];
16
-
17
- type FindProjectFilesOptions = {
18
- ignore?: string[];
19
- useGitignore?: boolean;
20
- excludeBinary?: boolean;
21
- contentBasedBinaryDetection?: boolean;
22
- maxFileSizeBytes?: number;
23
- rootPaths?: string[];
24
- };
25
-
26
- @Injectable()
27
- export class FsService {
28
- constructor(
29
- private readonly configService: ConfigService,
30
- private readonly ui: UiService,
31
- ) {}
32
-
33
- async findProjectFiles(
34
- options: FindProjectFilesOptions = {},
35
- ): Promise<string[]> {
36
- const { packer } = this.configService.getConfig();
37
- const shouldUseGitignore = options.useGitignore ?? packer.useGitignore;
38
- const gitignorePatterns = shouldUseGitignore
39
- ? await this.readIgnoreFile('.gitignore')
40
- : [];
41
- const koduignorePatterns = await this.readIgnoreFile('.koduignore');
42
-
43
- const baseIgnore = options.ignore ?? packer.ignore ?? [];
44
- const normalizedBase = this.normalizeIgnorePatterns(baseIgnore);
45
- const combinedIgnore = [
46
- ...normalizedBase,
47
- ...gitignorePatterns,
48
- ...koduignorePatterns,
49
- ].map((pattern) => pattern.replace(/\\/g, '/'));
50
-
51
- const ig = ignore();
52
- if (combinedIgnore.length > 0) {
53
- ig.add(combinedIgnore);
54
- }
55
-
56
- const globIgnore = this.buildGlobIgnorePatterns(combinedIgnore);
57
-
58
- const patterns = options.rootPaths?.length
59
- ? options.rootPaths.map((p) => `${p}/**`)
60
- : ['**/*'];
61
-
62
- const entries = await glob(patterns, {
63
- onlyFiles: true,
64
- absolute: true,
65
- dot: true,
66
- ignore: [...GLOB_IGNORE, ...globIgnore],
67
- });
68
-
69
- const relativePaths = entries
70
- .map((entry) => path.relative(process.cwd(), entry))
71
- .map((relative) => this.toPosixPath(relative))
72
- .filter((relative) => relative.length > 0);
73
-
74
- const filtered = ig
75
- .filter(relativePaths)
76
- .sort((a, b) => a.localeCompare(b));
77
-
78
- // By default exclude binary files when collecting project files (so pack will skip them).
79
- // Consumers can override with options.excludeBinary = false.
80
- const excludeBinary = options.excludeBinary ?? true;
81
- const useContentDetection =
82
- options.contentBasedBinaryDetection ??
83
- packer.contentBasedBinaryDetection ??
84
- false;
85
- const maxFileSize = options.maxFileSizeBytes ?? MAX_FILE_SIZE_BYTES;
86
-
87
- const textFiles: string[] = [];
88
-
89
- for (const rel of filtered) {
90
- const abs = path.resolve(process.cwd(), rel);
91
- let stats: Stats;
92
-
93
- try {
94
- stats = await fs.stat(abs);
95
- } catch {
96
- continue;
97
- }
98
-
99
- if (stats.size > maxFileSize) {
100
- this.ui.log.warn(
101
- `Skipping large file: ${rel} (>${(maxFileSize / (1024 * 1024)).toFixed(0)}MB)`,
102
- );
103
- continue;
104
- }
105
-
106
- if (
107
- excludeBinary &&
108
- (await this.shouldExcludeBinary(rel, abs, useContentDetection))
109
- ) {
110
- continue;
111
- }
112
-
113
- textFiles.push(rel);
114
- }
115
-
116
- return textFiles;
117
- }
118
-
119
- async readFileRelative(relativePath: string): Promise<string> {
120
- const absolute = path.resolve(process.cwd(), relativePath);
121
- return fs.readFile(absolute, 'utf8');
122
- }
123
-
124
- private toPosixPath(relativePath: string): string {
125
- return relativePath.split(path.sep).join(path.posix.sep);
126
- }
127
-
128
- private normalizeIgnorePatterns(patterns: string[]): string[] {
129
- return patterns
130
- .map((pattern) => pattern.trim())
131
- .filter((pattern) => pattern.length > 0 && !pattern.startsWith('#'));
132
- }
133
-
134
- private buildGlobIgnorePatterns(patterns: string[]): string[] {
135
- const normalized = patterns
136
- .map((pattern) => pattern.trim())
137
- .filter(
138
- (pattern) =>
139
- pattern.length > 0 &&
140
- !pattern.startsWith('#') &&
141
- !pattern.startsWith('!'),
142
- )
143
- .map((pattern) => pattern.replace(/\\/g, '/'));
144
-
145
- const result = new Set<string>();
146
-
147
- for (const pattern of normalized) {
148
- const trimmed = pattern.replace(/\/+$/, '');
149
- result.add(pattern);
150
-
151
- if (trimmed.length === 0) {
152
- continue;
153
- }
154
-
155
- if (!pattern.includes('*')) {
156
- result.add(`${trimmed}/**`);
157
- result.add(`**/${trimmed}/**`);
158
- }
159
-
160
- if (!pattern.startsWith('**/')) {
161
- result.add(`**/${trimmed}`);
162
- }
163
-
164
- if (pattern.endsWith('/')) {
165
- result.add(`${trimmed}/**`);
166
- }
167
- }
168
-
169
- return [...result];
170
- }
171
-
172
- private async readIgnoreFile(fileName: string): Promise<string[]> {
173
- const target = path.join(process.cwd(), fileName);
174
-
175
- try {
176
- const content = await fs.readFile(target, 'utf8');
177
- return this.parseIgnoreContent(content);
178
- } catch {
179
- return [];
180
- }
181
- }
182
-
183
- private parseIgnoreContent(content: string): string[] {
184
- return content
185
- .split(/\r?\n/)
186
- .map((line) => line.trim())
187
- .filter((line) => line.length > 0 && !line.startsWith('#'));
188
- }
189
-
190
- private isBinaryExtension(relativePath: string): boolean {
191
- const ext = path.extname(relativePath).toLowerCase();
192
- return ext.length > 0 && BINARY_EXTENSIONS.has(ext);
193
- }
194
-
195
- private isKnownTextFile(relativePath: string): boolean {
196
- const ext = path.extname(relativePath).toLowerCase();
197
- if (ext && KNOWN_TEXT_EXTENSIONS.has(ext)) {
198
- return true;
199
- }
200
-
201
- const baseName = path.basename(relativePath).toLowerCase();
202
- return KNOWN_TEXT_EXTENSIONS.has(baseName);
203
- }
204
-
205
- private async shouldExcludeBinary(
206
- relativePath: string,
207
- absolutePath: string,
208
- detectByContent: boolean,
209
- ): Promise<boolean> {
210
- if (this.isKnownTextFile(relativePath)) {
211
- return false;
212
- }
213
-
214
- if (this.isBinaryExtension(relativePath)) {
215
- return true;
216
- }
217
-
218
- if (!detectByContent) {
219
- return false;
220
- }
221
-
222
- return this.hasNullByte(absolutePath);
223
- }
224
-
225
- private async hasNullByte(absolutePath: string): Promise<boolean> {
226
- let handle: fs.FileHandle | undefined;
227
-
228
- try {
229
- handle = await fs.open(absolutePath, 'r');
230
- const buffer = Buffer.alloc(BINARY_PROBE_SIZE);
231
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
232
-
233
- for (let i = 0; i < bytesRead; i += 1) {
234
- if (buffer[i] === 0) {
235
- return true;
236
- }
237
- }
238
-
239
- return false;
240
- } catch {
241
- return true;
242
- } finally {
243
- if (handle) {
244
- await handle.close();
245
- }
246
- }
247
- }
248
- }
@@ -1,9 +0,0 @@
1
- import { Global, Module } from '@nestjs/common';
2
- import { RegistryService } from './registry.service';
3
-
4
- @Global()
5
- @Module({
6
- providers: [RegistryService],
7
- exports: [RegistryService],
8
- })
9
- export class RegistryModule {}
@@ -1,46 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * Стандартный набор стендов. Можно использовать и любые другие имена —
5
- * стенды хранятся как обычные строки, это лишь значение по умолчанию.
6
- */
7
- export const DEFAULT_STANDS = ['local', 'dev', 'stage', 'prod'] as const;
8
-
9
- /** Один проект в глобальном реестре `~/.config/kodu/registry.json`. */
10
- export const projectEntrySchema = z.object({
11
- /** Абсолютный путь к репозиторию проекта на этой машине. */
12
- path: z.string().min(1),
13
- /** URL репозитория (git clone), необязательно. */
14
- repo: z.string().optional(),
15
- /** Доступные стенды проекта. */
16
- stands: z.array(z.string()).default([...DEFAULT_STANDS]),
17
- });
18
-
19
- export type ProjectEntry = z.infer<typeof projectEntrySchema>;
20
-
21
- /**
22
- * Глобальный реестр всех проектов. Ключ объекта `projects` — уникальное имя
23
- * проекта, по которому агент/CLI понимает, с каким репозиторием работать.
24
- */
25
- export const registrySchema = z.object({
26
- $schema: z.string().optional(),
27
- projects: z.record(z.string(), projectEntrySchema).default({}),
28
- });
29
-
30
- export type Registry = z.infer<typeof registrySchema>;
31
-
32
- /**
33
- * Per-project конфиг `.runbook/config.json` (лежит в `.gitignore`).
34
- * Хранит текущий активный стенд конкретного разработчика.
35
- */
36
- export const projectConfigSchema = z.object({
37
- $schema: z.string().optional(),
38
- /** Имя проекта — должно совпадать с ключом в глобальном реестре. */
39
- project: z.string().min(1),
40
- /** Текущий активный стенд. */
41
- activeStand: z.string().default('local'),
42
- /** Доступные стенды проекта. */
43
- stands: z.array(z.string()).default([...DEFAULT_STANDS]),
44
- });
45
-
46
- export type ProjectConfig = z.infer<typeof projectConfigSchema>;
@@ -1,128 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { Injectable } from '@nestjs/common';
5
- import {
6
- type ProjectEntry,
7
- type Registry,
8
- registrySchema,
9
- } from './registry.schema';
10
-
11
- /**
12
- * Читает и пишет глобальный реестр проектов `~/.config/kodu/registry.json`
13
- * (учитывает `$XDG_CONFIG_HOME`). Файл создаётся при первой записи — пока
14
- * проектов нет, ничего в системе не создаётся.
15
- */
16
- @Injectable()
17
- export class RegistryService {
18
- private readonly dir = path.join(
19
- process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config'),
20
- 'kodu',
21
- );
22
- private readonly file = path.join(this.dir, 'registry.json');
23
-
24
- /** Путь к файлу реестра (для подсказок пользователю). */
25
- getFilePath(): string {
26
- return this.file;
27
- }
28
-
29
- /** Загрузить реестр. Если файла ещё нет — вернуть пустой реестр. */
30
- async load(): Promise<Registry> {
31
- let raw: unknown;
32
-
33
- try {
34
- const content = await fs.readFile(this.file, 'utf8');
35
- raw = JSON.parse(content);
36
- } catch (error) {
37
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
38
- return registrySchema.parse({});
39
- }
40
- throw new Error(
41
- `Не удалось прочитать реестр ${this.file}: ${(error as Error).message}`,
42
- );
43
- }
44
-
45
- const parsed = registrySchema.safeParse(raw);
46
-
47
- if (!parsed.success) {
48
- const issues = parsed.error.issues
49
- .map(
50
- (issue) => `- ${issue.path.join('.') || '(root)'}: ${issue.message}`,
51
- )
52
- .join('\n');
53
- throw new Error(`Реестр ${this.file} невалиден:\n${issues}`);
54
- }
55
-
56
- return parsed.data;
57
- }
58
-
59
- /** Сохранить реестр на диск (создаёт директорию при необходимости). */
60
- async save(registry: Registry): Promise<void> {
61
- const validated = registrySchema.parse(registry);
62
- await fs.mkdir(this.dir, { recursive: true });
63
- await fs.writeFile(
64
- this.file,
65
- `${JSON.stringify(validated, null, 2)}\n`,
66
- 'utf8',
67
- );
68
- }
69
-
70
- async list(): Promise<Registry['projects']> {
71
- return (await this.load()).projects;
72
- }
73
-
74
- async get(name: string): Promise<ProjectEntry | undefined> {
75
- return (await this.load()).projects[name];
76
- }
77
-
78
- async has(name: string): Promise<boolean> {
79
- return Boolean(await this.get(name));
80
- }
81
-
82
- /** Добавить проект. По умолчанию запрещает перезапись существующего имени. */
83
- async add(
84
- name: string,
85
- entry: ProjectEntry,
86
- options: { overwrite?: boolean } = {},
87
- ): Promise<void> {
88
- const registry = await this.load();
89
-
90
- if (registry.projects[name] && !options.overwrite) {
91
- throw new Error(
92
- `Проект с именем "${name}" уже есть в реестре. Выбери другое имя или обнови существующий проект.`,
93
- );
94
- }
95
-
96
- registry.projects[name] = entry;
97
- await this.save(registry);
98
- }
99
-
100
- /** Обновить поля существующего проекта. */
101
- async update(
102
- name: string,
103
- patch: Partial<ProjectEntry>,
104
- ): Promise<ProjectEntry> {
105
- const registry = await this.load();
106
- const existing = registry.projects[name];
107
-
108
- if (!existing) {
109
- throw new Error(`Проект "${name}" не найден в реестре.`);
110
- }
111
-
112
- const next: ProjectEntry = { ...existing, ...patch };
113
- registry.projects[name] = next;
114
- await this.save(registry);
115
- return next;
116
- }
117
-
118
- async remove(name: string): Promise<void> {
119
- const registry = await this.load();
120
-
121
- if (!registry.projects[name]) {
122
- throw new Error(`Проект "${name}" не найден в реестре.`);
123
- }
124
-
125
- delete registry.projects[name];
126
- await this.save(registry);
127
- }
128
- }
@@ -1,9 +0,0 @@
1
- import { Global, Module } from '@nestjs/common';
2
- import { UiService } from './ui.service';
3
-
4
- @Global()
5
- @Module({
6
- providers: [UiService],
7
- exports: [UiService],
8
- })
9
- export class UiModule {}
@@ -1,39 +0,0 @@
1
- import confirm from '@inquirer/confirm';
2
- import input from '@inquirer/input';
3
- import select from '@inquirer/select';
4
- import { Injectable } from '@nestjs/common';
5
- import pc from 'picocolors';
6
- import yoctoSpinner, {
7
- type Spinner,
8
- type Options as SpinnerOptions,
9
- } from 'yocto-spinner';
10
-
11
- type InputOptions = Parameters<typeof input>[0];
12
- type ConfirmOptions = Parameters<typeof confirm>[0];
13
- type SelectOptions<TValue> = Parameters<typeof select<TValue>>[0];
14
-
15
- @Injectable()
16
- export class UiService {
17
- readonly log = {
18
- success: (message: string) => console.log(pc.green(`✔ ${message}`)),
19
- warn: (message: string) => console.log(pc.yellow(`⚠ ${message}`)),
20
- error: (message: string) => console.log(pc.red(`✖ ${message}`)),
21
- info: (message: string) => console.log(pc.cyan(`ℹ ${message}`)),
22
- };
23
-
24
- createSpinner(options?: SpinnerOptions & { text?: string }): Spinner {
25
- return yoctoSpinner({ text: options?.text ?? '', ...options });
26
- }
27
-
28
- promptInput(options: InputOptions): Promise<string> {
29
- return input(options);
30
- }
31
-
32
- promptConfirm(options: ConfirmOptions): Promise<boolean> {
33
- return confirm(options);
34
- }
35
-
36
- promptSelect<TValue>(options: SelectOptions<TValue>): Promise<TValue> {
37
- return select<TValue>(options);
38
- }
39
- }
package/src/main.ts DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { CommandFactory } from 'nest-commander';
4
- import packageJson from '../package.json';
5
- import { AppModule } from './app.module';
6
-
7
- async function bootstrap() {
8
- await CommandFactory.run(AppModule, {
9
- version: packageJson.version,
10
- });
11
- }
12
- bootstrap();