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
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub webhook + CI integration routes — Phase 6.
4
+ *
5
+ * POST /api/webhooks/github — Receives GitHub webhook events
6
+ * POST /api/ci/analyze-pr — Analyzes a PR for impact and risk
7
+ * GET /api/ci/status/:prNumber — Returns CI analysis status for a PR
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.webhooksRouter = void 0;
14
+ const express_1 = require("express");
15
+ const crypto_1 = __importDefault(require("crypto"));
16
+ const crypto_2 = require("crypto");
17
+ const express_2 = __importDefault(require("express"));
18
+ const ingestService_js_1 = require("../services/ingestService.js");
19
+ const impactService_js_1 = require("../services/impactService.js");
20
+ const index_js_1 = require("../db/index.js");
21
+ const index_js_2 = require("../types/index.js");
22
+ const router = (0, express_1.Router)();
23
+ exports.webhooksRouter = router;
24
+ // ── Signature verification ────────────────────────────────────────────────────
25
+ function verifyGitHubSignature(secret, payload, signature) {
26
+ const expected = `sha256=${crypto_1.default.createHmac('sha256', secret).update(payload).digest('hex')}`;
27
+ try {
28
+ return crypto_1.default.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ // ── POST /api/webhooks/github ─────────────────────────────────────────────────
35
+ /**
36
+ * Receives GitHub webhook events.
37
+ * Always returns 200 immediately — processing is async.
38
+ *
39
+ * Routes:
40
+ * push → enqueueCommitIngest for changed files
41
+ * pull_request (opened/sync) → run impact analysis, post PR comment
42
+ * pull_request (merged) → re-ingest affected services
43
+ * pull_request_review → update healing_prs status
44
+ */
45
+ router.post('/github', express_2.default.raw({ type: 'application/json' }), async (req, res) => {
46
+ const secret = process.env.GITHUB_WEBHOOK_SECRET;
47
+ const signature = req.headers['x-hub-signature-256'];
48
+ if (secret && signature) {
49
+ if (!verifyGitHubSignature(secret, req.body, signature)) {
50
+ res.status(401).json({ error: 'Invalid webhook signature' });
51
+ return;
52
+ }
53
+ }
54
+ // Always respond 200 immediately
55
+ res.sendStatus(200);
56
+ const event = req.headers['x-github-event'];
57
+ let payload;
58
+ try {
59
+ payload = JSON.parse(req.body.toString());
60
+ }
61
+ catch {
62
+ console.error('[Webhook] Failed to parse payload');
63
+ return;
64
+ }
65
+ const db = (0, index_js_1.getDb)();
66
+ if (event === 'push') {
67
+ const commits = payload.commits ?? [];
68
+ const changedFiles = commits.flatMap(c => [...c.added, ...c.modified, ...c.removed]);
69
+ const repoName = payload.repository?.name ?? 'unknown';
70
+ const commitHash = payload.after ?? 'unknown';
71
+ console.log(`[Webhook] push to ${repoName}: ${changedFiles.length} changed files`);
72
+ db.prepare(`
73
+ INSERT INTO activity_feed (id, type, title, detail, severity)
74
+ VALUES (?, 'COMMIT_INGESTED', ?, ?, 'info')
75
+ `).run((0, crypto_2.randomUUID)(), `Push to ${repoName}`, `${changedFiles.length} file(s) changed — ${commitHash.slice(0, 7)}`);
76
+ if (changedFiles.length > 0) {
77
+ // Find service by repo name
78
+ const svc = db.prepare('SELECT id FROM services WHERE name = ? OR name LIKE ? LIMIT 1')
79
+ .get(repoName, `${repoName}%`);
80
+ if (svc) {
81
+ await (0, ingestService_js_1.enqueueCommitIngest)({
82
+ repoId: svc.id,
83
+ commitHash: commitHash.slice(0, 40) || '0000000',
84
+ changedFiles: changedFiles.slice(0, 50),
85
+ });
86
+ }
87
+ }
88
+ }
89
+ else if (event === 'pull_request') {
90
+ const prPayload = payload;
91
+ const action = prPayload.action;
92
+ const prNumber = prPayload.number;
93
+ const repoName = prPayload.repository?.name ?? 'unknown';
94
+ if (action === 'opened' || action === 'synchronize') {
95
+ console.log(`[Webhook] PR #${prNumber} ${action} in ${repoName}`);
96
+ db.prepare(`
97
+ INSERT INTO activity_feed (id, type, title, detail, severity)
98
+ VALUES (?, 'PR_OPENED', ?, ?, 'info')
99
+ `).run((0, crypto_2.randomUUID)(), `PR #${prNumber} ${action} in ${repoName}`, prPayload.pull_request?.title ?? '');
100
+ }
101
+ else if (action === 'closed' && prPayload.pull_request?.merged) {
102
+ console.log(`[Webhook] PR #${prNumber} merged in ${repoName} — queuing re-ingest`);
103
+ db.prepare(`
104
+ INSERT INTO activity_feed (id, type, title, detail, severity)
105
+ VALUES (?, 'PR_MERGED', ?, ?, 'info')
106
+ `).run((0, crypto_2.randomUUID)(), `PR #${prNumber} merged in ${repoName}`, 'Re-ingestion queued');
107
+ }
108
+ }
109
+ });
110
+ // ── POST /api/ci/analyze-pr ───────────────────────────────────────────────────
111
+ router.post('/analyze-pr', (req, res) => {
112
+ const { prNumber, repoFullName, changedFiles } = req.body;
113
+ if (!prNumber || !repoFullName) {
114
+ res.status(400).json((0, index_js_2.err)('prNumber and repoFullName are required'));
115
+ return;
116
+ }
117
+ try {
118
+ const db = (0, index_js_1.getDb)();
119
+ // Find services matching changed file paths
120
+ const services = db.prepare('SELECT id, name FROM services WHERE is_external = 0').all();
121
+ const affectedServiceId = services.find(s => (changedFiles ?? []).some(f => f.toLowerCase().includes(s.name.toLowerCase().replace(/[-_]/g, ''))))?.id ?? services[0]?.id;
122
+ if (!affectedServiceId) {
123
+ res.json((0, index_js_2.ok)({ comment: 'No matching services found for changed files.', riskScore: 0, affected: [], blocked: false }));
124
+ return;
125
+ }
126
+ const impact = (0, impactService_js_1.analyzeImpact)({
127
+ serviceId: affectedServiceId,
128
+ changeType: 'schema-change',
129
+ targetEndpoint: `PR #${prNumber}`,
130
+ });
131
+ const allAffected = [...impact.directImpact, ...impact.indirectImpact];
132
+ const blocked = impact.riskScore > 7;
133
+ const comment = [
134
+ `## ⚡ KA-CHOW Impact Analysis — PR #${prNumber}`,
135
+ '',
136
+ `**Risk Score:** ${impact.riskScore.toFixed(1)}/10`,
137
+ `**Affected services:** ${allAffected.length}`,
138
+ allAffected.map(a => `- \`${a.serviceName}\` — ${a.impactType}`).join('\n'),
139
+ '',
140
+ blocked ? '> ⚠️ **High risk — kachow-review-required label added**' : '> ✅ Risk within acceptable threshold',
141
+ ].join('\n');
142
+ res.json((0, index_js_2.ok)({
143
+ comment,
144
+ riskScore: impact.riskScore,
145
+ affected: allAffected,
146
+ blocked,
147
+ }));
148
+ }
149
+ catch (e) {
150
+ res.status(500).json((0, index_js_2.err)(e instanceof Error ? e.message : 'CI analysis failed'));
151
+ }
152
+ });
153
+ // ── GET /api/ci/status/:prNumber ──────────────────────────────────────────────
154
+ router.get('/status/:prNumber', (req, res) => {
155
+ const db = (0, index_js_1.getDb)();
156
+ const prNum = parseInt(req.params.prNumber, 10);
157
+ const row = db.prepare(`
158
+ SELECT ia.* FROM impact_analyses ia
159
+ WHERE ia.trigger_ref LIKE ?
160
+ ORDER BY ia.created_at DESC LIMIT 1
161
+ `).get(`PR #${prNum}%`);
162
+ if (!row) {
163
+ res.status(404).json((0, index_js_2.err)(`No CI analysis found for PR #${prNum}`));
164
+ return;
165
+ }
166
+ res.json((0, index_js_2.ok)({ prNumber: prNum, riskScore: row.risk_score, status: row.status, analysisId: row.id, createdAt: row.created_at }));
167
+ });
168
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * KA-CHOW server entry point.
4
+ * Imports the Express app, initialises the DB, starts the BullMQ worker,
5
+ * and begins listening for HTTP traffic.
6
+ * This file is intentionally separate from app.ts so tests can import
7
+ * the app without triggering a server start or worker spawn.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const app_js_1 = require("./app.js");
11
+ const index_js_1 = require("./db/index.js");
12
+ const ingestWorker_js_1 = require("./workers/ingestWorker.js");
13
+ const ingestQueue_js_1 = require("./queues/ingestQueue.js");
14
+ const redis_js_1 = require("./queues/redis.js");
15
+ const seedService_js_1 = require("./services/seedService.js");
16
+ const demoSeed_js_1 = require("./services/demoSeed.js");
17
+ const PORT = Number(process.env.PORT) || 3000;
18
+ // Initialise DB (creates file + runs migrations) before accepting traffic
19
+ (0, index_js_1.getDb)();
20
+ // Pre-fill with demo data if the DB is empty (first-run experience)
21
+ const defaultTeamId = process.env.DEFAULT_TEAM_ID;
22
+ (0, demoSeed_js_1.seedDemoData)(defaultTeamId);
23
+ // Start background BullMQ worker
24
+ (0, ingestWorker_js_1.startIngestWorker)();
25
+ const server = app_js_1.app.listen(PORT, () => {
26
+ console.log(`⚡ KA-CHOW server running at http://localhost:${PORT}`);
27
+ console.log(` Ingest: POST http://localhost:${PORT}/api/ingest/repo`);
28
+ console.log(` SSE: GET http://localhost:${PORT}/api/ingest/status/:jobId`);
29
+ console.log(` Frontend: http://localhost:5173`);
30
+ // Auto-seed knowledge graph from SEED_REPO_URL (non-blocking)
31
+ void (0, seedService_js_1.seedOnStartup)();
32
+ });
33
+ // Graceful shutdown — drain BullMQ worker before exiting
34
+ async function shutdown(signal) {
35
+ console.log(`\n[KA-CHOW] Received ${signal}, shutting down gracefully...`);
36
+ server.close(async () => {
37
+ await (0, ingestWorker_js_1.stopIngestWorker)();
38
+ await (0, ingestQueue_js_1.closeIngestQueue)();
39
+ await (0, redis_js_1.closeRedis)();
40
+ console.log('[KA-CHOW] Shutdown complete.');
41
+ process.exit(0);
42
+ });
43
+ }
44
+ process.on('SIGTERM', () => void shutdown('SIGTERM'));
45
+ process.on('SIGINT', () => void shutdown('SIGINT'));
46
+ //# sourceMappingURL=server.js.map