kachow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +77 -0
  2. package/_server/dist/app.js +130 -0
  3. package/_server/dist/db/index.js +50 -0
  4. package/_server/dist/db/schema.js +247 -0
  5. package/_server/dist/queues/ingestQueue.js +49 -0
  6. package/_server/dist/queues/redis.js +58 -0
  7. package/_server/dist/routes/agents.js +162 -0
  8. package/_server/dist/routes/architecture.js +88 -0
  9. package/_server/dist/routes/config.js +24 -0
  10. package/_server/dist/routes/github.js +158 -0
  11. package/_server/dist/routes/graph.js +112 -0
  12. package/_server/dist/routes/healing.js +137 -0
  13. package/_server/dist/routes/impact.js +100 -0
  14. package/_server/dist/routes/ingest.js +182 -0
  15. package/_server/dist/routes/manager.js +179 -0
  16. package/_server/dist/routes/notifications.js +85 -0
  17. package/_server/dist/routes/qa.js +68 -0
  18. package/_server/dist/routes/scanner.js +221 -0
  19. package/_server/dist/routes/stream.js +179 -0
  20. package/_server/dist/routes/webhooks.js +168 -0
  21. package/_server/dist/server.js +46 -0
  22. package/_server/dist/services/agentService.js +715 -0
  23. package/_server/dist/services/architectureService.js +172 -0
  24. package/_server/dist/services/demoSeed.js +181 -0
  25. package/_server/dist/services/graphLayout.js +102 -0
  26. package/_server/dist/services/graphService.js +532 -0
  27. package/_server/dist/services/healingService.js +253 -0
  28. package/_server/dist/services/impactService.js +304 -0
  29. package/_server/dist/services/ingestService.js +129 -0
  30. package/_server/dist/services/managerService.js +260 -0
  31. package/_server/dist/services/notificationService.js +283 -0
  32. package/_server/dist/services/qaService.js +413 -0
  33. package/_server/dist/services/scannerService.js +748 -0
  34. package/_server/dist/services/seedService.js +215 -0
  35. package/_server/dist/sse/sseManager.js +101 -0
  36. package/_server/dist/types/index.js +38 -0
  37. package/_server/dist/workers/ingestWorker.js +274 -0
  38. package/_server/public/assets/index-BTkbB_YF.js +4546 -0
  39. package/_server/public/assets/index-Bmh3jWBm.css +1 -0
  40. package/_server/public/favicon.ico +0 -0
  41. package/_server/public/images/glass-waves-bg.png +0 -0
  42. package/_server/public/index.html +29 -0
  43. package/_server/public/placeholder.svg +1 -0
  44. package/_server/public/robots.txt +14 -0
  45. package/dist/config.js +133 -0
  46. package/dist/index.js +510 -0
  47. package/dist/setup.js +223 -0
  48. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,510 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * KA-CHOW CLI — single entry point for the living knowledge graph.
5
+ *
6
+ * Primary command: kachow init
7
+ * 1. Runs credential wizard → saves .env
8
+ * 2. Starts backend automatically
9
+ * 3. Creates default team
10
+ * 4. Scans current repo and assigns it to team
11
+ * 5. Opens browser at localhost:3000
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ const commander_1 = require("commander");
18
+ const dotenv_1 = __importDefault(require("dotenv"));
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const http_1 = __importDefault(require("http"));
22
+ const child_process_1 = require("child_process");
23
+ const js_yaml_1 = __importDefault(require("js-yaml"));
24
+ const config_js_1 = require("./config.js");
25
+ const setup_js_1 = require("./setup.js");
26
+ // ── Data directory ────────────────────────────────────────────────────────────
27
+ const KACHOW_DIR_NAME = '.kachow';
28
+ function getDataDir(dir) {
29
+ return path_1.default.join(dir, KACHOW_DIR_NAME);
30
+ }
31
+ function getEnvPath(dir) {
32
+ return path_1.default.join(getDataDir(dir), '.env');
33
+ }
34
+ function getConfigJsonPath(dir) {
35
+ return path_1.default.join(getDataDir(dir), 'config.json');
36
+ }
37
+ function readLocalConfig(dir) {
38
+ const p = getConfigJsonPath(dir);
39
+ if (!fs_1.default.existsSync(p))
40
+ return {};
41
+ try {
42
+ return JSON.parse(fs_1.default.readFileSync(p, 'utf-8'));
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ function writeLocalConfig(dir, data) {
49
+ const d = getDataDir(dir);
50
+ if (!fs_1.default.existsSync(d))
51
+ fs_1.default.mkdirSync(d, { recursive: true });
52
+ fs_1.default.writeFileSync(getConfigJsonPath(dir), JSON.stringify(data, null, 2));
53
+ }
54
+ // ── Load .env from .kachow dir if it exists ───────────────────────────────────
55
+ const cwd = process.cwd();
56
+ const kachowEnv = getEnvPath(cwd);
57
+ if (fs_1.default.existsSync(kachowEnv)) {
58
+ dotenv_1.default.config({ path: kachowEnv });
59
+ }
60
+ else {
61
+ dotenv_1.default.config();
62
+ }
63
+ // ── Locate server package (npm-installed or monorepo) ──────────────────────────
64
+ function getServerDir() {
65
+ // When installed via npm: __dirname is <pkg>/dist/, _server/ is at <pkg>/_server/
66
+ const bundled = path_1.default.resolve(__dirname, '../_server');
67
+ if (fs_1.default.existsSync(path_1.default.join(bundled, 'dist/server.js')))
68
+ return bundled;
69
+ // Monorepo dev: __dirname is packages/cli/dist/, server is at packages/server/
70
+ const mono = path_1.default.resolve(__dirname, '../../server');
71
+ if (fs_1.default.existsSync(path_1.default.join(mono, 'dist/server.js')))
72
+ return mono;
73
+ // Last resort: check for unbuilt monorepo with tsx
74
+ if (fs_1.default.existsSync(path_1.default.join(mono, 'src/server.ts')))
75
+ return mono;
76
+ return mono; // will fail later with a helpful message
77
+ }
78
+ const program = new commander_1.Command();
79
+ program.name('kachow').description('⚡ KA-CHOW — Living knowledge graph').version('0.1.0');
80
+ // ── Helpers ───────────────────────────────────────────────────────────────────
81
+ function apiGet(port, apiPath) {
82
+ return new Promise((resolve, reject) => {
83
+ http_1.default.get(`http://localhost:${port}${apiPath}`, (r) => {
84
+ let b = '';
85
+ r.on('data', (c) => { b += c.toString(); });
86
+ r.on('end', () => { try {
87
+ resolve(JSON.parse(b));
88
+ }
89
+ catch {
90
+ reject(new Error('Invalid JSON'));
91
+ } });
92
+ }).on('error', reject);
93
+ });
94
+ }
95
+ function apiPost(port, apiPath, data) {
96
+ return new Promise((resolve, reject) => {
97
+ const body = JSON.stringify(data);
98
+ const req = http_1.default.request({
99
+ hostname: 'localhost', port, path: apiPath, method: 'POST',
100
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
101
+ }, (r) => {
102
+ let buf = '';
103
+ r.on('data', (c) => { buf += c.toString(); });
104
+ r.on('end', () => { try {
105
+ resolve(JSON.parse(buf));
106
+ }
107
+ catch {
108
+ reject(new Error('Invalid JSON'));
109
+ } });
110
+ });
111
+ req.on('error', reject);
112
+ req.write(body);
113
+ req.end();
114
+ });
115
+ }
116
+ async function waitForServer(port, retries = 25) {
117
+ for (let i = 0; i < retries; i++) {
118
+ await new Promise(r => setTimeout(r, 1000));
119
+ try {
120
+ await apiGet(port, '/health');
121
+ return true;
122
+ }
123
+ catch {
124
+ process.stdout.write('.');
125
+ }
126
+ }
127
+ return false;
128
+ }
129
+ function openBrowser(url) {
130
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
131
+ (0, child_process_1.spawn)(cmd, [url], { detached: true, stdio: 'ignore' }).unref();
132
+ }
133
+ function detectRepos(cwd) {
134
+ const repos = [];
135
+ const langMarkers = [
136
+ { file: 'package.json', lang: 'typescript' },
137
+ { file: 'setup.py', lang: 'python' },
138
+ { file: 'pyproject.toml', lang: 'python' },
139
+ { file: 'pom.xml', lang: 'java' },
140
+ { file: 'go.mod', lang: 'go' },
141
+ { file: 'Cargo.toml', lang: 'rust' },
142
+ ];
143
+ // Check CWD itself
144
+ const cwdLang = langMarkers.find(m => fs_1.default.existsSync(path_1.default.join(cwd, m.file)))?.lang;
145
+ if (cwdLang)
146
+ repos.push({ name: path_1.default.basename(cwd), path: cwd, language: cwdLang });
147
+ // Scan common monorepo subdirs
148
+ for (const subdir of ['services', 'packages', 'apps']) {
149
+ const subdirPath = path_1.default.join(cwd, subdir);
150
+ if (!fs_1.default.existsSync(subdirPath))
151
+ continue;
152
+ for (const entry of fs_1.default.readdirSync(subdirPath, { withFileTypes: true })) {
153
+ if (!entry.isDirectory())
154
+ continue;
155
+ const ep = path_1.default.join(subdirPath, entry.name);
156
+ const lang = langMarkers.find(m => fs_1.default.existsSync(path_1.default.join(ep, m.file)))?.lang;
157
+ if (lang)
158
+ repos.push({ name: entry.name, path: ep, language: lang });
159
+ }
160
+ }
161
+ return repos;
162
+ }
163
+ function generateConfig(dir, repos) {
164
+ return js_yaml_1.default.dump({
165
+ version: 1,
166
+ project: { name: path_1.default.basename(dir), description: `Auto-detected from ${dir}` },
167
+ repos: repos.map(r => ({ name: r.name, path: path_1.default.relative(dir, r.path) || '.', language: r.language })),
168
+ standards: { require_readme: true, require_error_handling: true, min_test_coverage: 70 },
169
+ llm: { anthropic_key: '${ANTHROPIC_API_KEY}', qa_model: 'claude-sonnet-4-6', healing_model: 'gpt-4o' },
170
+ }, { lineWidth: 80 });
171
+ }
172
+ // ── init ──────────────────────────────────────────────────────────────────────
173
+ program.command('init')
174
+ .description('Setup KA-CHOW: credentials → server → team → scan → browser')
175
+ .option('-p, --port <number>', 'Server port', '3000')
176
+ .option('--no-browser', 'Skip opening browser')
177
+ .option('--skip-wizard', 'Skip credential wizard (use existing .env)')
178
+ .action(async (opts) => {
179
+ const port = parseInt(opts.port, 10);
180
+ const projectDir = process.cwd();
181
+ const dataDir = getDataDir(projectDir);
182
+ const envPath = getEnvPath(projectDir);
183
+ console.log('\n⚡ KA-CHOW init\n');
184
+ // ── 1. Ensure data directory ──────────────────────────────────────
185
+ if (!fs_1.default.existsSync(dataDir)) {
186
+ fs_1.default.mkdirSync(dataDir, { recursive: true });
187
+ const gitignore = path_1.default.join(projectDir, '.gitignore');
188
+ if (fs_1.default.existsSync(gitignore)) {
189
+ const content = fs_1.default.readFileSync(gitignore, 'utf-8');
190
+ if (!content.includes('.kachow')) {
191
+ fs_1.default.appendFileSync(gitignore, '\n# KA-CHOW local data\n.kachow/\n');
192
+ console.log(' ✓ Added .kachow/ to .gitignore');
193
+ }
194
+ }
195
+ }
196
+ // ── 2. Credential wizard ──────────────────────────────────────────
197
+ if (opts.skipWizard && fs_1.default.existsSync(envPath)) {
198
+ console.log(' ✓ Using existing credentials from .kachow/.env');
199
+ dotenv_1.default.config({ path: envPath, override: true });
200
+ }
201
+ else {
202
+ const creds = await (0, setup_js_1.runSetupWizard)(envPath);
203
+ (0, setup_js_1.writeEnvFile)(envPath, creds);
204
+ console.log(` ✓ Saved credentials to .kachow/.env`);
205
+ dotenv_1.default.config({ path: envPath, override: true });
206
+ }
207
+ // ── 3. Find / create kachow.config.yaml ──────────────────────────
208
+ const yamlPath = path_1.default.join(projectDir, 'kachow.config.yaml');
209
+ let repos = [];
210
+ if (fs_1.default.existsSync(yamlPath)) {
211
+ console.log(' ✓ Found kachow.config.yaml');
212
+ try {
213
+ const cfg = (0, config_js_1.readConfig)(projectDir);
214
+ repos = (cfg.repos ?? []).map(r => ({
215
+ name: r.name,
216
+ path: path_1.default.resolve(projectDir, r.path ?? '.'),
217
+ language: r.language ?? 'unknown',
218
+ }));
219
+ }
220
+ catch { /* fallback to auto-detect */ }
221
+ }
222
+ if (repos.length === 0) {
223
+ console.log(' 🔍 Auto-detecting repos...');
224
+ repos = detectRepos(projectDir);
225
+ if (repos.length === 0) {
226
+ repos = [{ name: path_1.default.basename(projectDir), path: projectDir, language: 'unknown' }];
227
+ }
228
+ console.log(` Found: ${repos.map(r => r.name).join(', ')}`);
229
+ fs_1.default.writeFileSync(yamlPath, generateConfig(projectDir, repos));
230
+ console.log(' ✓ Created kachow.config.yaml');
231
+ }
232
+ // ── 4. Start server ───────────────────────────────────────────────
233
+ let serverAlreadyRunning = false;
234
+ try {
235
+ await apiGet(port, '/health');
236
+ serverAlreadyRunning = true;
237
+ console.log(` ✓ Server already running on :${port}`);
238
+ }
239
+ catch { /* not running */ }
240
+ if (!serverAlreadyRunning) {
241
+ console.log(` 🚀 Starting server on :${port}...`);
242
+ const serverPkg = getServerDir();
243
+ const distEntry = path_1.default.join(serverPkg, 'dist/server.js');
244
+ const srcEntry = path_1.default.join(serverPkg, 'src/server.ts');
245
+ const useTs = !fs_1.default.existsSync(distEntry) && fs_1.default.existsSync(srcEntry);
246
+ const entry = useTs ? srcEntry : distEntry;
247
+ if (!fs_1.default.existsSync(entry)) {
248
+ console.error(` ✗ Server not found at ${entry}`);
249
+ console.error(' If installed via npm, try reinstalling: npm i -g kachow');
250
+ console.error(' If in monorepo, run: cd packages/server && pnpm build');
251
+ process.exit(1);
252
+ }
253
+ const childEnv = {
254
+ ...process.env,
255
+ PORT: String(port),
256
+ DB_PATH: path_1.default.join(dataDir, 'kachow.db'),
257
+ };
258
+ if (fs_1.default.existsSync(envPath)) {
259
+ const envContent = fs_1.default.readFileSync(envPath, 'utf-8');
260
+ for (const line of envContent.split('\n')) {
261
+ const t = line.trim();
262
+ if (!t || t.startsWith('#'))
263
+ continue;
264
+ const eq = t.indexOf('=');
265
+ if (eq > 0)
266
+ childEnv[t.slice(0, eq)] = t.slice(eq + 1);
267
+ }
268
+ }
269
+ const args = useTs ? ['tsx', entry] : [entry];
270
+ const cmd = useTs ? 'npx' : 'node';
271
+ const child = (0, child_process_1.spawn)(cmd, args, {
272
+ env: childEnv,
273
+ stdio: 'ignore',
274
+ detached: true,
275
+ cwd: serverPkg,
276
+ });
277
+ child.unref();
278
+ process.stdout.write(' Waiting for server');
279
+ const ready = await waitForServer(port);
280
+ console.log('');
281
+ if (!ready) {
282
+ console.error(' ✗ Server failed to start after 30s.');
283
+ console.error(' Try manually: cd packages/server && npx tsx src/server.ts');
284
+ process.exit(1);
285
+ }
286
+ console.log(' ✓ Server ready');
287
+ }
288
+ // ── 5. Create or reuse team ──────────────────────────────────────
289
+ let teamId;
290
+ const localCfg = readLocalConfig(projectDir);
291
+ if (localCfg.teamId) {
292
+ teamId = localCfg.teamId;
293
+ console.log(` ✓ Using saved team: ${teamId.slice(0, 8)}...`);
294
+ }
295
+ else {
296
+ console.log(' 🏗️ Creating default team...');
297
+ try {
298
+ const teamsRes = await apiGet(port, '/api/manager/teams');
299
+ const teams = (teamsRes.data ?? []);
300
+ if (teams.length > 0) {
301
+ teamId = teams[0].id;
302
+ console.log(` ✓ Found existing team: ${teamId.slice(0, 8)}...`);
303
+ }
304
+ else {
305
+ const projectName = path_1.default.basename(projectDir);
306
+ const createRes = await apiPost(port, '/api/manager/teams', {
307
+ name: projectName,
308
+ description: `Default team for ${projectName}`,
309
+ });
310
+ const created = (createRes.data ?? {});
311
+ teamId = created.id ?? '';
312
+ if (!teamId) {
313
+ console.error(' ✗ Failed to create team — no ID returned');
314
+ process.exit(1);
315
+ }
316
+ console.log(` ✓ Created team "${projectName}" → ${teamId.slice(0, 8)}...`);
317
+ }
318
+ }
319
+ catch (e) {
320
+ console.error(` ✗ Team creation failed: ${e instanceof Error ? e.message : String(e)}`);
321
+ process.exit(1);
322
+ }
323
+ writeLocalConfig(projectDir, { ...localCfg, teamId });
324
+ (0, setup_js_1.appendToEnv)(envPath, 'DEFAULT_TEAM_ID', teamId);
325
+ }
326
+ // ── 6. Ingest repos ──────────────────────────────────────────────
327
+ console.log(`\n 📦 Scanning ${repos.length} repo(s)...\n`);
328
+ for (const repo of repos) {
329
+ if (!fs_1.default.existsSync(repo.path)) {
330
+ console.log(` ⚠ ${repo.name}: path not found — skipping`);
331
+ continue;
332
+ }
333
+ try {
334
+ const r = await apiPost(port, '/api/ingest/repo', {
335
+ repoPath: repo.path,
336
+ repoName: repo.name,
337
+ team: repo.name,
338
+ });
339
+ const jobId = (r.data ?? {}).jobId;
340
+ console.log(` ✓ ${repo.name} → job ${jobId?.slice(0, 8) ?? 'started'}`);
341
+ }
342
+ catch (e) {
343
+ console.log(` ✗ ${repo.name}: ${e instanceof Error ? e.message : String(e)}`);
344
+ }
345
+ }
346
+ // ── 7. Open browser ──────────────────────────────────────────────
347
+ const url = `http://localhost:${port}`;
348
+ if (opts.browser !== false) {
349
+ console.log(`\n 🌐 Opening ${url} ...`);
350
+ setTimeout(() => openBrowser(url), 2000);
351
+ }
352
+ console.log(`\n ⚡ KA-CHOW is live at ${url}`);
353
+ console.log(` Team ID: ${teamId}`);
354
+ console.log(` Data dir: ${dataDir}`);
355
+ console.log(`\n Press Ctrl+C to stop the server.\n`);
356
+ // Keep process alive
357
+ await new Promise(() => { });
358
+ });
359
+ // ── serve ─────────────────────────────────────────────────────────────────────
360
+ program.command('serve')
361
+ .description('Start the KA-CHOW server without re-scanning')
362
+ .option('-p, --port <number>', 'Port', '3000')
363
+ .action((opts) => {
364
+ const port = parseInt(opts.port, 10);
365
+ const projectDir = process.cwd();
366
+ const envPath = getEnvPath(projectDir);
367
+ const dataDir = getDataDir(projectDir);
368
+ if (fs_1.default.existsSync(envPath))
369
+ dotenv_1.default.config({ path: envPath, override: true });
370
+ process.env.PORT = String(port);
371
+ process.env.DB_PATH = process.env.DB_PATH ?? path_1.default.join(dataDir, 'kachow.db');
372
+ console.log(`🚀 KA-CHOW server on :${port}`);
373
+ const serverPkg = getServerDir();
374
+ const entry = path_1.default.join(serverPkg, 'dist/server.js');
375
+ if (!fs_1.default.existsSync(entry)) {
376
+ console.error('Server not found. If in monorepo: cd packages/server && pnpm build');
377
+ process.exit(1);
378
+ }
379
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
380
+ require(entry);
381
+ });
382
+ // ── scan ──────────────────────────────────────────────────────────────────────
383
+ program.command('scan')
384
+ .description('Scan a repo and add it to the knowledge graph')
385
+ .requiredOption('-p, --path <path>', 'Repository path')
386
+ .option('--port <number>', 'Server port', '3000')
387
+ .option('--name <name>', 'Service name')
388
+ .option('--team <team>', 'Team name')
389
+ .action(async (opts) => {
390
+ const port = parseInt(opts.port, 10);
391
+ const repoPath = path_1.default.resolve(opts.path);
392
+ const repoName = opts.name ?? path_1.default.basename(repoPath);
393
+ if (!fs_1.default.existsSync(repoPath)) {
394
+ console.error(`✗ Path not found: ${repoPath}`);
395
+ process.exit(1);
396
+ }
397
+ console.log(`🔍 Scanning ${repoName}...`);
398
+ try {
399
+ const r = await apiPost(port, '/api/ingest/repo', { repoPath, repoName, team: opts.team ?? null });
400
+ const jobId = (r.data ?? {}).jobId;
401
+ console.log(`✓ Job ${jobId} started — watch: GET http://localhost:${port}/api/ingest/status/${jobId}`);
402
+ }
403
+ catch {
404
+ console.error(`✗ Server not running on :${port}. Start with: kachow serve`);
405
+ process.exit(1);
406
+ }
407
+ });
408
+ // ── health ────────────────────────────────────────────────────────────────────
409
+ program.command('health')
410
+ .description('Print system health')
411
+ .option('--port <number>', 'Server port', '3000')
412
+ .option('--json', 'Output JSON')
413
+ .action(async (opts) => {
414
+ try {
415
+ const r = await apiGet(parseInt(opts.port, 10), '/api/graph/health');
416
+ const h = r.data;
417
+ if (opts.json) {
418
+ console.log(JSON.stringify(h, null, 2));
419
+ return;
420
+ }
421
+ const icon = h.overallScore >= 75 ? '💚' : h.overallScore >= 50 ? '🟡' : '🔴';
422
+ console.log(`\n${icon} KA-CHOW Health — ${h.overallScore}/100`);
423
+ console.log(` Services: ${h.totalServices} | Healthy: ${h.healthy} | Warning: ${h.warning} | Critical: ${h.critical}`);
424
+ console.log(` Edges: ${h.totalEdges}\n`);
425
+ }
426
+ catch {
427
+ console.error(`✗ Server not running on :${opts.port}`);
428
+ process.exit(1);
429
+ }
430
+ });
431
+ // ── onboard ───────────────────────────────────────────────────────────────────
432
+ program.command('onboard')
433
+ .description('Print onboarding path for a new engineer')
434
+ .requiredOption('-r, --role <role>', 'backend-engineer | sre | engineering-manager')
435
+ .option('--port <number>', 'Server port', '3000')
436
+ .action(async (opts) => {
437
+ try {
438
+ const r = await apiGet(parseInt(opts.port, 10), `/api/manager/onboarding/path/${opts.role}`);
439
+ const d = r.data;
440
+ console.log(`\n🎓 Onboarding: ${opts.role}\n`);
441
+ for (const s of d.steps) {
442
+ console.log(` ${s.step}. ${s.title} (~${s.estimatedMinutes}min)\n ${s.description}\n`);
443
+ }
444
+ }
445
+ catch {
446
+ console.error(`✗ Server not running on :${opts.port}`);
447
+ process.exit(1);
448
+ }
449
+ });
450
+ // ── blueprint ─────────────────────────────────────────────────────────────────
451
+ program.command('blueprint')
452
+ .description('Print service architecture blueprint')
453
+ .requiredOption('-s, --service <id>', 'Service ID')
454
+ .option('--port <number>', 'Server port', '3000')
455
+ .action(async (opts) => {
456
+ try {
457
+ const r = await apiGet(parseInt(opts.port, 10), `/api/architecture/blueprint/${opts.service}`);
458
+ const bp = r.data;
459
+ console.log(`\n📐 ${bp.service.name} — ${bp.service.healthScore}/100 (${bp.service.healthTier})`);
460
+ for (const s of bp.standardsCompliance)
461
+ console.log(` ${s.passing ? '✓' : '✗'} ${s.standard}${s.violation ? ` — ${s.violation}` : ''}`);
462
+ console.log('');
463
+ }
464
+ catch {
465
+ console.error(`✗ Server not running on :${opts.port}`);
466
+ process.exit(1);
467
+ }
468
+ });
469
+ // ── reset ─────────────────────────────────────────────────────────────────────
470
+ program.command('reset')
471
+ .description('Wipe the KA-CHOW database and local config')
472
+ .option('--force', 'Skip confirmation')
473
+ .action((opts) => {
474
+ if (!opts.force) {
475
+ console.log('⚠️ This will delete the KA-CHOW database and config.');
476
+ console.log(' Re-run with --force to confirm.');
477
+ return;
478
+ }
479
+ const projectDir = process.cwd();
480
+ const dataDir = getDataDir(projectDir);
481
+ const dbPath = path_1.default.join(dataDir, 'kachow.db');
482
+ if (fs_1.default.existsSync(dbPath)) {
483
+ fs_1.default.unlinkSync(dbPath);
484
+ console.log(`🗑️ Deleted ${dbPath}`);
485
+ }
486
+ const cfgPath = getConfigJsonPath(projectDir);
487
+ if (fs_1.default.existsSync(cfgPath)) {
488
+ fs_1.default.unlinkSync(cfgPath);
489
+ console.log(`🗑️ Deleted ${cfgPath}`);
490
+ }
491
+ console.log('✓ Reset complete. Run `kachow init` to start fresh.');
492
+ });
493
+ // ── analyze (CI mode) ─────────────────────────────────────────────────────────
494
+ program.command('analyze')
495
+ .description('Run PR impact analysis in CI mode')
496
+ .requiredOption('--pr <number>', 'PR number')
497
+ .requiredOption('--repo <owner/repo>', 'GitHub repo')
498
+ .option('--port <number>', 'Server port', '3000')
499
+ .action(async (opts) => {
500
+ try {
501
+ const r = await apiPost(parseInt(opts.port, 10), '/api/ci/analyze-pr', { prNumber: parseInt(opts.pr, 10), repoFullName: opts.repo });
502
+ console.log(JSON.stringify(r, null, 2));
503
+ }
504
+ catch {
505
+ console.error(`✗ Server not running on :${opts.port}`);
506
+ process.exit(1);
507
+ }
508
+ });
509
+ program.parse(process.argv);
510
+ //# sourceMappingURL=index.js.map