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,162 @@
1
+ "use strict";
2
+ /**
3
+ * Agent routes — Phase 10: Deep Analysis Engine.
4
+ *
5
+ * POST /api/agents/analyze — Trigger deep analysis on a repo
6
+ * GET /api/agents/knowledge-graph — Get latest knowledge graph
7
+ * GET /api/agents/analyses — Get agent analysis history
8
+ * GET /api/agents/status — Quick status check
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.agentRouter = void 0;
12
+ const express_1 = require("express");
13
+ const zod_1 = require("zod");
14
+ const crypto_1 = require("crypto");
15
+ const agentService_js_1 = require("../services/agentService.js");
16
+ const scannerService_js_1 = require("../services/scannerService.js");
17
+ const graphService_js_1 = require("../services/graphService.js");
18
+ const index_js_1 = require("../db/index.js");
19
+ const index_js_2 = require("../types/index.js");
20
+ const router = (0, express_1.Router)();
21
+ exports.agentRouter = router;
22
+ // ── POST /api/agents/analyze ──────────────────────────────────────────────────
23
+ const AnalyzeBodySchema = zod_1.z.object({
24
+ repoPath: zod_1.z.string().min(1, 'repoPath is required'),
25
+ repoUrl: zod_1.z.string().url().optional(),
26
+ team: zod_1.z.string().optional(),
27
+ });
28
+ router.post('/analyze', async (req, res) => {
29
+ const parse = AnalyzeBodySchema.safeParse(req.body);
30
+ if (!parse.success) {
31
+ res.status(400).json((0, index_js_2.err)(parse.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')));
32
+ return;
33
+ }
34
+ const { repoPath, repoUrl, team } = parse.data;
35
+ try {
36
+ // Step 1: Run the normal scanner first to discover services
37
+ console.log('[Agents] Step 1: Running scanner...');
38
+ const scanResult = (0, scannerService_js_1.scanMonorepo)(repoPath, repoUrl, team);
39
+ // Persist scanner results to DB (same as scanner route)
40
+ const db = (0, index_js_1.getDb)();
41
+ const toTier = (s) => s >= 75 ? 'healthy' : s >= 50 ? 'warning' : 'critical';
42
+ const upsertService = db.prepare(`
43
+ INSERT INTO services (id, name, repo_url, repo_subpath, team, language,
44
+ health_score, health_tier, doc_coverage, test_coverage, last_commit, last_ingested)
45
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
46
+ ON CONFLICT(id) DO UPDATE SET
47
+ name=excluded.name, repo_url=excluded.repo_url, repo_subpath=excluded.repo_subpath,
48
+ team=excluded.team, language=excluded.language, health_score=excluded.health_score,
49
+ health_tier=excluded.health_tier, doc_coverage=excluded.doc_coverage,
50
+ test_coverage=excluded.test_coverage, last_commit=excluded.last_commit,
51
+ last_ingested=excluded.last_ingested, updated_at=datetime('now')
52
+ `);
53
+ const upsertEndpoint = db.prepare(`
54
+ INSERT INTO endpoints (id, service_id, path, method, deprecated)
55
+ VALUES (?, ?, ?, ?, ?)
56
+ ON CONFLICT(service_id, path, method) DO UPDATE SET deprecated=excluded.deprecated, updated_at=datetime('now')
57
+ `);
58
+ for (const svc of scanResult.services) {
59
+ upsertService.run(svc.id, svc.name, svc.repoUrl, svc.repoSubpath, svc.team ?? team ?? null, svc.language, svc.healthScore, toTier(svc.healthScore), svc.docCoverage, svc.testCoverage, svc.lastCommit);
60
+ for (const ep of svc.endpoints) {
61
+ upsertEndpoint.run((0, crypto_1.randomUUID)(), svc.id, ep.path, ep.method, ep.deprecated ? 1 : 0);
62
+ }
63
+ }
64
+ // Build service roots for deep analysis
65
+ const serviceRoots = scanResult.services
66
+ .filter((s) => s.language !== 'infra') // skip infra stubs from scanner
67
+ .map((s) => ({
68
+ id: s.id,
69
+ name: s.name,
70
+ language: s.language,
71
+ dir: s.repoPath,
72
+ }));
73
+ if (serviceRoots.length === 0) {
74
+ res.status(400).json((0, index_js_2.err)('No services found to analyze. Run /api/scanner/scan first.'));
75
+ return;
76
+ }
77
+ // Step 2: Run deep analysis with LLM agents
78
+ console.log(`[Agents] Step 2: Deep analysis on ${serviceRoots.length} services...`);
79
+ const analysisResult = await (0, agentService_js_1.runDeepAnalysis)(repoPath, serviceRoots);
80
+ // Save a fresh snapshot
81
+ try {
82
+ (0, graphService_js_1.saveSnapshot)('latest');
83
+ }
84
+ catch { /* non-fatal */ }
85
+ res.json((0, index_js_2.ok)({
86
+ analysisId: analysisResult.analysisId,
87
+ servicesAnalyzed: analysisResult.servicesAnalyzed,
88
+ totalFindings: analysisResult.totalFindings,
89
+ knowledgeGraph: {
90
+ nodes: analysisResult.orchestratorResult.nodes.length,
91
+ edges: analysisResult.orchestratorResult.edges.length,
92
+ layers: analysisResult.orchestratorResult.architecture.layers.length,
93
+ dataFlows: analysisResult.orchestratorResult.architecture.dataFlows.length,
94
+ selfHealingSuggestions: analysisResult.orchestratorResult.selfHealingSuggestions.length,
95
+ },
96
+ agentBreakdown: analysisResult.agentResults.map((r) => ({
97
+ agent: r.agentName,
98
+ service: r.serviceName,
99
+ findings: r.findings.length,
100
+ durationMs: r.durationMs,
101
+ })),
102
+ totalDurationMs: analysisResult.totalDurationMs,
103
+ summary: analysisResult.orchestratorResult.summary,
104
+ }));
105
+ }
106
+ catch (e) {
107
+ const msg = e instanceof Error ? e.message : 'Deep analysis failed';
108
+ console.error('[Agents] Error:', msg);
109
+ res.status(500).json((0, index_js_2.err)(msg));
110
+ }
111
+ });
112
+ // ── GET /api/agents/knowledge-graph ───────────────────────────────────────────
113
+ router.get('/knowledge-graph', (_req, res) => {
114
+ try {
115
+ const kg = (0, agentService_js_1.getLatestKnowledgeGraph)();
116
+ if (!kg) {
117
+ res.json((0, index_js_2.ok)({
118
+ nodes: [], edges: [],
119
+ architecture: { layers: [], dataFlows: [] },
120
+ selfHealingSuggestions: [],
121
+ summary: 'No deep analysis has been run yet. POST /api/agents/analyze to start.',
122
+ }));
123
+ return;
124
+ }
125
+ res.json((0, index_js_2.ok)(kg));
126
+ }
127
+ catch (e) {
128
+ res.status(500).json((0, index_js_2.err)(e instanceof Error ? e.message : 'Failed to get knowledge graph'));
129
+ }
130
+ });
131
+ // ── GET /api/agents/analyses ──────────────────────────────────────────────────
132
+ router.get('/analyses', (req, res) => {
133
+ try {
134
+ const analysisId = req.query.analysisId;
135
+ const analyses = (0, agentService_js_1.getAgentAnalyses)(analysisId);
136
+ res.json((0, index_js_2.ok)(analyses));
137
+ }
138
+ catch (e) {
139
+ res.status(500).json((0, index_js_2.err)(e instanceof Error ? e.message : 'Failed to get analyses'));
140
+ }
141
+ });
142
+ // ── GET /api/agents/status ────────────────────────────────────────────────────
143
+ router.get('/status', (_req, res) => {
144
+ try {
145
+ const hasKey = !!(process.env.OPENAI_API_KEY && process.env.OPENAI_API_KEY !== 'your_openai_key_here');
146
+ const kg = (0, agentService_js_1.getLatestKnowledgeGraph)();
147
+ res.json((0, index_js_2.ok)({
148
+ openaiConfigured: hasKey,
149
+ hasKnowledgeGraph: !!kg,
150
+ nodeCount: kg?.nodes.length ?? 0,
151
+ edgeCount: kg?.edges.length ?? 0,
152
+ agents: [
153
+ 'db-agent', 'routing-agent', 'dependency-agent', 'infra-agent',
154
+ 'cicd-agent', 'schema-agent', 'security-agent', 'test-agent',
155
+ ],
156
+ }));
157
+ }
158
+ catch (e) {
159
+ res.status(500).json((0, index_js_2.err)(e instanceof Error ? e.message : 'Failed to get status'));
160
+ }
161
+ });
162
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * Architecture diagram routes — Phase 9.
4
+ *
5
+ * GET /api/architecture/flow — Services in layers (INGRESS/CORE/DOMAIN/INFRA)
6
+ * GET /api/architecture/blueprint/:id — Full service blueprint
7
+ * POST /api/architecture/propose — AI-proposed new service (brownie points)
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.architectureRouter = void 0;
14
+ const express_1 = require("express");
15
+ const zod_1 = require("zod");
16
+ const openai_1 = __importDefault(require("openai"));
17
+ const architectureService_js_1 = require("../services/architectureService.js");
18
+ const index_js_1 = require("../types/index.js");
19
+ const router = (0, express_1.Router)();
20
+ exports.architectureRouter = router;
21
+ // ── GET /api/architecture/flow ────────────────────────────────────────────────
22
+ router.get('/flow', (_req, res) => {
23
+ try {
24
+ res.json((0, index_js_1.ok)((0, architectureService_js_1.getArchitectureFlow)()));
25
+ }
26
+ catch (e) {
27
+ res.status(500).json((0, index_js_1.err)(e instanceof Error ? e.message : 'Failed to get architecture flow'));
28
+ }
29
+ });
30
+ // ── GET /api/architecture/blueprint/:serviceId ────────────────────────────────
31
+ router.get('/blueprint/:serviceId', (req, res) => {
32
+ try {
33
+ const blueprint = (0, architectureService_js_1.getServiceBlueprint)(req.params.serviceId);
34
+ if (!blueprint) {
35
+ res.status(404).json((0, index_js_1.err)(`Service ${req.params.serviceId} not found`));
36
+ return;
37
+ }
38
+ res.json((0, index_js_1.ok)(blueprint));
39
+ }
40
+ catch (e) {
41
+ res.status(500).json((0, index_js_1.err)(e instanceof Error ? e.message : 'Failed to get blueprint'));
42
+ }
43
+ });
44
+ // ── POST /api/architecture/propose (brownie points) ───────────────────────────
45
+ const ProposeSchema = zod_1.z.object({
46
+ requirement: zod_1.z.string().min(10, 'Requirement must be at least 10 characters').max(500),
47
+ });
48
+ router.post('/propose', async (req, res) => {
49
+ const parse = ProposeSchema.safeParse(req.body);
50
+ if (!parse.success) {
51
+ res.status(400).json((0, index_js_1.err)(parse.error.errors.map(e => e.message).join('; ')));
52
+ return;
53
+ }
54
+ const apiKey = process.env.OPENAI_API_KEY;
55
+ if (!apiKey) {
56
+ res.status(503).json((0, index_js_1.err)('OPENAI_API_KEY not set'));
57
+ return;
58
+ }
59
+ try {
60
+ const openai = new openai_1.default({ apiKey });
61
+ const completion = await openai.chat.completions.create({
62
+ model: 'gpt-4o',
63
+ messages: [{
64
+ role: 'user',
65
+ content: `You are a senior software architect. Design a new microservice to fulfil this requirement: "${parse.data.requirement}"
66
+
67
+ Respond with ONLY valid JSON:
68
+ {
69
+ "name": "kebab-case-service-name",
70
+ "description": "what this service does",
71
+ "dependencies": ["existing-service-name"],
72
+ "techStack": ["Node.js", "TypeScript", "PostgreSQL"],
73
+ "folderStructure": ["src/index.ts", "src/routes.ts", "src/service.ts"],
74
+ "adraft": "one paragraph ADR context for this decision"
75
+ }`,
76
+ }],
77
+ response_format: { type: 'json_object' },
78
+ temperature: 0.4,
79
+ max_tokens: 600,
80
+ });
81
+ const proposed = JSON.parse(completion.choices[0]?.message?.content ?? '{}');
82
+ res.json((0, index_js_1.ok)({ proposedService: proposed, requirement: parse.data.requirement }));
83
+ }
84
+ catch (e) {
85
+ res.status(500).json((0, index_js_1.err)(e instanceof Error ? e.message : 'Proposal generation failed'));
86
+ }
87
+ });
88
+ //# sourceMappingURL=architecture.js.map
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Config router — exposes runtime configuration to the frontend.
4
+ * GET /api/config returns which integrations are active and the default team.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.configRouter = void 0;
8
+ const express_1 = require("express");
9
+ exports.configRouter = (0, express_1.Router)();
10
+ exports.configRouter.get('/', (_req, res) => {
11
+ res.json({
12
+ success: true,
13
+ data: {
14
+ defaultTeamId: process.env.DEFAULT_TEAM_ID || null,
15
+ hasGitHub: !!process.env.GITHUB_TOKEN,
16
+ hasSlack: !!process.env.SLACK_BOT_TOKEN,
17
+ hasJira: !!process.env.JIRA_BASE_URL,
18
+ hasOpenAI: !!process.env.OPENAI_API_KEY,
19
+ },
20
+ error: null,
21
+ meta: { timestamp: new Date().toISOString(), version: '1.0.0' },
22
+ });
23
+ });
24
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub webhook handler.
4
+ *
5
+ * POST /api/github/webhook
6
+ *
7
+ * GitHub sends a signed POST on every push event.
8
+ * We verify the HMAC-SHA256 signature using GITHUB_WEBHOOK_SECRET,
9
+ * then enqueue a commit-triggered partial re-scan via ingestService.
10
+ *
11
+ * Register this webhook at:
12
+ * https://github.com/organizations/<org>/settings/hooks
13
+ * or per-repo at Settings → Webhooks
14
+ *
15
+ * Required env vars:
16
+ * GITHUB_WEBHOOK_SECRET — must match the "Secret" field in GitHub's UI
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.githubRouter = void 0;
53
+ const crypto_1 = require("crypto");
54
+ const express_1 = __importStar(require("express"));
55
+ const ingestService_js_1 = require("../services/ingestService.js");
56
+ exports.githubRouter = (0, express_1.Router)();
57
+ // ── Signature verification ────────────────────────────────────────────────────
58
+ /**
59
+ * Verifies the X-Hub-Signature-256 header sent by GitHub.
60
+ * Uses timing-safe comparison to prevent timing attacks.
61
+ *
62
+ * @param secret - GITHUB_WEBHOOK_SECRET from env
63
+ * @param payload - Raw request body as a Buffer
64
+ * @param header - Value of X-Hub-Signature-256 header
65
+ * @returns true if the signature matches
66
+ */
67
+ function verifySignature(secret, payload, header) {
68
+ if (!header)
69
+ return false;
70
+ const expected = 'sha256=' + (0, crypto_1.createHmac)('sha256', secret).update(payload).digest('hex');
71
+ try {
72
+ return (0, crypto_1.timingSafeEqual)(Buffer.from(expected), Buffer.from(header));
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ // ── POST /api/github/webhook ──────────────────────────────────────────────────
79
+ /**
80
+ * Receives GitHub push events and enqueues a commit-triggered re-scan.
81
+ *
82
+ * GitHub push payload shape (relevant fields only):
83
+ * { ref, after, repository: { id, full_name, clone_url }, commits: [{ id, added, removed, modified }] }
84
+ */
85
+ exports.githubRouter.post('/webhook',
86
+ // Capture raw body for HMAC verification BEFORE JSON parsing strips it.
87
+ // We register this route with express.raw() so the body is a Buffer here.
88
+ express_1.default.raw({ type: 'application/json' }), async (req, res) => {
89
+ const secret = process.env.GITHUB_WEBHOOK_SECRET;
90
+ if (!secret) {
91
+ console.error('[GitHub Webhook] GITHUB_WEBHOOK_SECRET is not set');
92
+ res.status(500).json({ success: false, error: 'Webhook secret not configured' });
93
+ return;
94
+ }
95
+ // ── Signature check ───────────────────────────────────────────────────────
96
+ const signature = req.headers['x-hub-signature-256'];
97
+ const rawBody = req.body;
98
+ if (!verifySignature(secret, rawBody, signature)) {
99
+ console.warn('[GitHub Webhook] Invalid signature — request rejected');
100
+ res.status(401).json({ success: false, error: 'Invalid webhook signature' });
101
+ return;
102
+ }
103
+ // ── Parse payload ─────────────────────────────────────────────────────────
104
+ let payload;
105
+ try {
106
+ payload = JSON.parse(rawBody.toString('utf-8'));
107
+ }
108
+ catch {
109
+ res.status(400).json({ success: false, error: 'Could not parse JSON payload' });
110
+ return;
111
+ }
112
+ // Only process push events (not tag deletions, etc.)
113
+ const event = req.headers['x-github-event'];
114
+ if (event !== 'push') {
115
+ res.json({ success: true, data: { skipped: true, reason: `event '${String(event)}' not handled` } });
116
+ return;
117
+ }
118
+ const commitHash = payload.after;
119
+ const repoFullName = payload.repository?.full_name ?? 'unknown';
120
+ const repoId = String(payload.repository?.id ?? '');
121
+ if (!commitHash || commitHash === '0000000000000000000000000000000000000000') {
122
+ // Branch deletion — nothing to scan
123
+ res.json({ success: true, data: { skipped: true, reason: 'branch deletion event' } });
124
+ return;
125
+ }
126
+ // Collect all changed files across every commit in this push
127
+ const changedFiles = [
128
+ ...new Set((payload.commits ?? []).flatMap((c) => [
129
+ ...(c.added ?? []),
130
+ ...(c.modified ?? []),
131
+ ...(c.removed ?? []),
132
+ ])),
133
+ ];
134
+ if (changedFiles.length === 0) {
135
+ res.json({ success: true, data: { skipped: true, reason: 'no files changed' } });
136
+ return;
137
+ }
138
+ // ── Enqueue re-scan ───────────────────────────────────────────────────────
139
+ try {
140
+ const result = await (0, ingestService_js_1.enqueueCommitIngest)({ repoId, commitHash, changedFiles });
141
+ console.log(`[GitHub Webhook] Enqueued commit job for ${repoFullName}@${commitHash.slice(0, 7)}`);
142
+ res.status(201).json({ success: true, data: result });
143
+ }
144
+ catch (err) {
145
+ const e = err;
146
+ if (e.statusCode === 404) {
147
+ // Repo not yet ingested — that's fine, just skip
148
+ res.status(404).json({
149
+ success: false,
150
+ error: `Repository '${repoFullName}' (id: ${repoId}) not found in KA-CHOW. Run a full ingest first.`,
151
+ });
152
+ return;
153
+ }
154
+ console.error('[GitHub Webhook] Enqueue error:', e.message);
155
+ res.status(500).json({ success: false, error: e.message ?? 'Internal error' });
156
+ }
157
+ });
158
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * Graph router — Phase 2 read-only endpoints.
4
+ *
5
+ * GET /api/graph/nodes → all service nodes with layout coords
6
+ * GET /api/graph/edges → all dependency edges
7
+ * GET /api/graph/node/:id → full node detail for sidebar panel
8
+ * GET /api/graph/health → system-wide health aggregate (30-s poll)
9
+ * GET /api/graph/snapshot/:version → snapshot at a specific version/tag
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.graphRouter = void 0;
13
+ const express_1 = require("express");
14
+ const graphService_js_1 = require("../services/graphService.js");
15
+ exports.graphRouter = (0, express_1.Router)();
16
+ // ── GET /nodes ────────────────────────────────────────────────────────────────
17
+ exports.graphRouter.get('/nodes', (req, res) => {
18
+ try {
19
+ const team = typeof req.query.team === 'string' && req.query.team ? req.query.team : undefined;
20
+ const teamId = typeof req.query.teamId === 'string' && req.query.teamId ? req.query.teamId : undefined;
21
+ const nodes = (0, graphService_js_1.getNodes)(team, teamId);
22
+ res.json({ success: true, data: nodes, meta: { count: nodes.length } });
23
+ }
24
+ catch (err) {
25
+ const message = err instanceof Error ? err.message : 'Unknown error';
26
+ res.status(500).json({ success: false, error: message });
27
+ }
28
+ });
29
+ // ── GET /edges ────────────────────────────────────────────────────────────────
30
+ exports.graphRouter.get('/edges', (req, res) => {
31
+ try {
32
+ const team = typeof req.query.team === 'string' && req.query.team ? req.query.team : undefined;
33
+ const teamId = typeof req.query.teamId === 'string' && req.query.teamId ? req.query.teamId : undefined;
34
+ const edges = (0, graphService_js_1.getEdges)(team, teamId);
35
+ res.json({ success: true, data: edges, meta: { count: edges.length } });
36
+ }
37
+ catch (err) {
38
+ const message = err instanceof Error ? err.message : 'Unknown error';
39
+ res.status(500).json({ success: false, error: message });
40
+ }
41
+ });
42
+ // ── GET /health ───────────────────────────────────────────────────────────────
43
+ exports.graphRouter.get('/health', (req, res) => {
44
+ try {
45
+ const teamId = typeof req.query.teamId === 'string' && req.query.teamId ? req.query.teamId : undefined;
46
+ const health = (0, graphService_js_1.getSystemHealth)(teamId);
47
+ res.json({ success: true, data: health });
48
+ }
49
+ catch (err) {
50
+ const message = err instanceof Error ? err.message : 'Unknown error';
51
+ res.status(500).json({ success: false, error: message });
52
+ }
53
+ });
54
+ // ── GET /snapshot/:version ────────────────────────────────────────────────────
55
+ exports.graphRouter.get('/snapshot/:version', (req, res) => {
56
+ const { version } = req.params;
57
+ if (!version || version.trim().length === 0) {
58
+ res.status(400).json({ success: false, error: 'version is required' });
59
+ return;
60
+ }
61
+ try {
62
+ const snapshot = (0, graphService_js_1.getSnapshot)(version);
63
+ if (!snapshot) {
64
+ res.status(404).json({
65
+ success: false,
66
+ error: `Snapshot '${version}' not found`,
67
+ });
68
+ return;
69
+ }
70
+ res.json({ success: true, data: snapshot });
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : 'Unknown error';
74
+ res.status(500).json({ success: false, error: message });
75
+ }
76
+ });
77
+ // ── GET /critical-issues ──────────────────────────────────────────────────────
78
+ exports.graphRouter.get('/critical-issues', (req, res) => {
79
+ try {
80
+ const teamId = typeof req.query.teamId === 'string' && req.query.teamId ? req.query.teamId : undefined;
81
+ const issues = (0, graphService_js_1.getCriticalIssues)(teamId);
82
+ res.json({ success: true, data: issues, meta: { count: issues.length } });
83
+ }
84
+ catch (err) {
85
+ const message = err instanceof Error ? err.message : 'Unknown error';
86
+ res.status(500).json({ success: false, error: message });
87
+ }
88
+ });
89
+ // ── GET /node/:id (most specific last) ──────────────────────────────────────
90
+ exports.graphRouter.get('/node/:id', (req, res) => {
91
+ const { id } = req.params;
92
+ if (!id || id.trim().length === 0) {
93
+ res.status(400).json({ success: false, error: 'id is required' });
94
+ return;
95
+ }
96
+ try {
97
+ const detail = (0, graphService_js_1.getNodeDetail)(id);
98
+ if (!detail) {
99
+ res.status(404).json({
100
+ success: false,
101
+ error: `Service node '${id}' not found`,
102
+ });
103
+ return;
104
+ }
105
+ res.json({ success: true, data: detail });
106
+ }
107
+ catch (err) {
108
+ const message = err instanceof Error ? err.message : 'Unknown error';
109
+ res.status(500).json({ success: false, error: message });
110
+ }
111
+ });
112
+ //# sourceMappingURL=graph.js.map