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.
- package/README.md +77 -0
- package/_server/dist/app.js +130 -0
- package/_server/dist/db/index.js +50 -0
- package/_server/dist/db/schema.js +247 -0
- package/_server/dist/queues/ingestQueue.js +49 -0
- package/_server/dist/queues/redis.js +58 -0
- package/_server/dist/routes/agents.js +162 -0
- package/_server/dist/routes/architecture.js +88 -0
- package/_server/dist/routes/config.js +24 -0
- package/_server/dist/routes/github.js +158 -0
- package/_server/dist/routes/graph.js +112 -0
- package/_server/dist/routes/healing.js +137 -0
- package/_server/dist/routes/impact.js +100 -0
- package/_server/dist/routes/ingest.js +182 -0
- package/_server/dist/routes/manager.js +179 -0
- package/_server/dist/routes/notifications.js +85 -0
- package/_server/dist/routes/qa.js +68 -0
- package/_server/dist/routes/scanner.js +221 -0
- package/_server/dist/routes/stream.js +179 -0
- package/_server/dist/routes/webhooks.js +168 -0
- package/_server/dist/server.js +46 -0
- package/_server/dist/services/agentService.js +715 -0
- package/_server/dist/services/architectureService.js +172 -0
- package/_server/dist/services/demoSeed.js +181 -0
- package/_server/dist/services/graphLayout.js +102 -0
- package/_server/dist/services/graphService.js +532 -0
- package/_server/dist/services/healingService.js +253 -0
- package/_server/dist/services/impactService.js +304 -0
- package/_server/dist/services/ingestService.js +129 -0
- package/_server/dist/services/managerService.js +260 -0
- package/_server/dist/services/notificationService.js +283 -0
- package/_server/dist/services/qaService.js +413 -0
- package/_server/dist/services/scannerService.js +748 -0
- package/_server/dist/services/seedService.js +215 -0
- package/_server/dist/sse/sseManager.js +101 -0
- package/_server/dist/types/index.js +38 -0
- package/_server/dist/workers/ingestWorker.js +274 -0
- package/_server/public/assets/index-BTkbB_YF.js +4546 -0
- package/_server/public/assets/index-Bmh3jWBm.css +1 -0
- package/_server/public/favicon.ico +0 -0
- package/_server/public/images/glass-waves-bg.png +0 -0
- package/_server/public/index.html +29 -0
- package/_server/public/placeholder.svg +1 -0
- package/_server/public/robots.txt +14 -0
- package/dist/config.js +133 -0
- package/dist/index.js +510 -0
- package/dist/setup.js +223 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ⚡ KA-CHOW
|
|
2
|
+
|
|
3
|
+
**Living knowledge graph for your engineering system.**
|
|
4
|
+
|
|
5
|
+
Scan any codebase and get an interactive architecture dashboard — dependency graph, health scores, impact analysis, and AI-powered insights — all in your browser.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx kachow init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That's it. KA-CHOW will:
|
|
16
|
+
1. 🔑 Ask for your API keys (Anthropic required, others optional)
|
|
17
|
+
2. 🔍 Auto-detect and scan your repositories
|
|
18
|
+
3. 🚀 Start a local server with an interactive dashboard
|
|
19
|
+
4. 🌐 Open your browser at `http://localhost:3000`
|
|
20
|
+
|
|
21
|
+
## What You Get
|
|
22
|
+
|
|
23
|
+
- **Interactive Knowledge Graph** — Visualize every service, dependency, and data flow
|
|
24
|
+
- **Health Scores** — Per-service health with latency, error rate, and coverage metrics
|
|
25
|
+
- **Impact Analysis** — "What breaks if I change this?" with blast radius visualization
|
|
26
|
+
- **AI Q&A Terminal** — Ask questions about your architecture in natural language
|
|
27
|
+
- **Self-Healing Suggestions** — AI-detected issues with one-click fix proposals
|
|
28
|
+
- **Architecture Blueprints** — Per-service compliance reports against your standards
|
|
29
|
+
- **Manager Dashboard** — Team onboarding, critical issues, and deployment overview
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
kachow init # Full setup: credentials → scan → dashboard
|
|
35
|
+
kachow serve # Start server without re-scanning
|
|
36
|
+
kachow scan -p ./path # Scan a specific repo
|
|
37
|
+
kachow health # Print system health summary
|
|
38
|
+
kachow reset --force # Wipe local data and start fresh
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- **Node.js 18+**
|
|
44
|
+
- **Anthropic API key** (for AI features — get one at [console.anthropic.com](https://console.anthropic.com))
|
|
45
|
+
|
|
46
|
+
Optional: GitHub token (for private repos), OpenAI key (alternate models), Slack/Jira tokens (integrations).
|
|
47
|
+
|
|
48
|
+
## How It Works
|
|
49
|
+
|
|
50
|
+
KA-CHOW scans your codebase to build a knowledge graph:
|
|
51
|
+
- Detects services, packages, and modules
|
|
52
|
+
- Maps imports, API calls, and data flows into dependency edges
|
|
53
|
+
- Scores health based on test coverage, documentation, error handling, and more
|
|
54
|
+
- Stores everything in a local SQLite database (`.kachow/kachow.db`)
|
|
55
|
+
|
|
56
|
+
No data ever leaves your machine unless you explicitly configure cloud integrations.
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
Create a `kachow.config.yaml` in your project root (auto-generated on first `init`):
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
version: 1
|
|
64
|
+
project:
|
|
65
|
+
name: "My Platform"
|
|
66
|
+
repos:
|
|
67
|
+
- name: api-service
|
|
68
|
+
path: ./services/api
|
|
69
|
+
language: typescript
|
|
70
|
+
standards:
|
|
71
|
+
require_readme: true
|
|
72
|
+
min_test_coverage: 70
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* KA-CHOW Express application.
|
|
4
|
+
* Sets up middleware, mounts route handlers, and defines error handling.
|
|
5
|
+
* This file exports the `app` — it does NOT call app.listen().
|
|
6
|
+
* Server startup lives in server.ts.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.app = void 0;
|
|
13
|
+
const express_1 = __importDefault(require("express"));
|
|
14
|
+
const cors_1 = __importDefault(require("cors"));
|
|
15
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
16
|
+
const path_1 = require("path");
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const ingest_js_1 = require("./routes/ingest.js");
|
|
19
|
+
const graph_js_1 = require("./routes/graph.js");
|
|
20
|
+
const github_js_1 = require("./routes/github.js");
|
|
21
|
+
const impact_js_1 = require("./routes/impact.js");
|
|
22
|
+
const qa_js_1 = require("./routes/qa.js");
|
|
23
|
+
const stream_js_1 = require("./routes/stream.js");
|
|
24
|
+
const manager_js_1 = require("./routes/manager.js");
|
|
25
|
+
const architecture_js_1 = require("./routes/architecture.js");
|
|
26
|
+
const healing_js_1 = require("./routes/healing.js");
|
|
27
|
+
const webhooks_js_1 = require("./routes/webhooks.js");
|
|
28
|
+
const scanner_js_1 = require("./routes/scanner.js");
|
|
29
|
+
const agents_js_1 = require("./routes/agents.js");
|
|
30
|
+
const notifications_js_1 = require("./routes/notifications.js");
|
|
31
|
+
const config_js_1 = require("./routes/config.js");
|
|
32
|
+
// Load .env — try monorepo root first (packages/server → ../../), then local fallback
|
|
33
|
+
dotenv_1.default.config({ path: (0, path_1.resolve)(process.cwd(), '../../.env') });
|
|
34
|
+
dotenv_1.default.config(); // no-op if already loaded; handles local .env for non-monorepo setups
|
|
35
|
+
const app = (0, express_1.default)();
|
|
36
|
+
exports.app = app;
|
|
37
|
+
// ── Middleware ────────────────────────────────────────────────────────────────
|
|
38
|
+
const PORT = process.env.PORT || '3000';
|
|
39
|
+
const ALLOWED_ORIGINS = (process.env.CORS_ORIGIN || '')
|
|
40
|
+
.split(',')
|
|
41
|
+
.map(o => o.trim())
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.concat([
|
|
44
|
+
`http://localhost:${PORT}`, // same-origin when served by Express
|
|
45
|
+
`http://127.0.0.1:${PORT}`,
|
|
46
|
+
'http://localhost:5173', // Vite dev server
|
|
47
|
+
'http://localhost:8080',
|
|
48
|
+
'http://127.0.0.1:8080',
|
|
49
|
+
]);
|
|
50
|
+
app.use((0, cors_1.default)({
|
|
51
|
+
origin: (origin, cb) => {
|
|
52
|
+
// Allow requests with no origin (curl, Postman, server-to-server)
|
|
53
|
+
if (!origin)
|
|
54
|
+
return cb(null, true);
|
|
55
|
+
if (ALLOWED_ORIGINS.includes(origin))
|
|
56
|
+
return cb(null, true);
|
|
57
|
+
cb(new Error(`CORS: origin ${origin} not allowed`));
|
|
58
|
+
},
|
|
59
|
+
credentials: true,
|
|
60
|
+
}));
|
|
61
|
+
app.use(express_1.default.json());
|
|
62
|
+
app.use(express_1.default.urlencoded({ extended: true }));
|
|
63
|
+
// ── Static files — serve built React app ──────────────────────────────────────
|
|
64
|
+
// __dirname works in CJS (tsc compiles to CJS). In dev (tsx) we fallback.
|
|
65
|
+
const publicDir = (0, path_1.resolve)(__dirname, '../public');
|
|
66
|
+
if ((0, fs_1.existsSync)(publicDir)) {
|
|
67
|
+
app.use(express_1.default.static(publicDir));
|
|
68
|
+
}
|
|
69
|
+
// ── Routes ────────────────────────────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* GET /health
|
|
72
|
+
* Server health check used by load balancers and the frontend status strip.
|
|
73
|
+
*
|
|
74
|
+
* @returns Standard ApiResponse with server status and version
|
|
75
|
+
*/
|
|
76
|
+
app.get('/health', (_req, res) => {
|
|
77
|
+
res.json({
|
|
78
|
+
success: true,
|
|
79
|
+
data: { status: 'ok', version: '1.0.0' },
|
|
80
|
+
error: null,
|
|
81
|
+
meta: { timestamp: new Date().toISOString(), version: '1.0.0' },
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ── API Routes ───────────────────────────────────────────────────────────────
|
|
85
|
+
app.use('/api/ingest', ingest_js_1.ingestRouter);
|
|
86
|
+
app.use('/api/graph', graph_js_1.graphRouter);
|
|
87
|
+
app.use('/api/github', github_js_1.githubRouter);
|
|
88
|
+
app.use('/api/impact', impact_js_1.impactRouter);
|
|
89
|
+
app.use('/api/qa', qa_js_1.qaRouter);
|
|
90
|
+
app.use('/api/stream', stream_js_1.streamRouter);
|
|
91
|
+
app.use('/api/manager', manager_js_1.managerRouter);
|
|
92
|
+
app.use('/api/onboarding', manager_js_1.managerRouter); // brief: GET /api/onboarding/path/:role
|
|
93
|
+
app.use('/api/architecture', architecture_js_1.architectureRouter);
|
|
94
|
+
app.use('/api/heal', healing_js_1.healingRouter);
|
|
95
|
+
app.use('/api/webhooks', webhooks_js_1.webhooksRouter);
|
|
96
|
+
app.use('/api/scanner', scanner_js_1.scannerRouter);
|
|
97
|
+
app.use('/api/agents', agents_js_1.agentRouter);
|
|
98
|
+
app.use('/api/notifications', notifications_js_1.notificationRouter);
|
|
99
|
+
app.use('/api/ci', webhooks_js_1.webhooksRouter);
|
|
100
|
+
app.use('/api/config', config_js_1.configRouter);
|
|
101
|
+
// ── SPA catch-all — serve index.html for non-API routes ──────────────────────
|
|
102
|
+
const indexPath = (0, path_1.join)(publicDir, 'index.html');
|
|
103
|
+
if ((0, fs_1.existsSync)(indexPath)) {
|
|
104
|
+
app.use((req, res, next) => {
|
|
105
|
+
if (req.path.startsWith('/api') || req.path === '/health')
|
|
106
|
+
return next();
|
|
107
|
+
res.sendFile(indexPath);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// ── 404 handler (only hits for unmatched /api/* routes) ───────────────────────
|
|
111
|
+
app.use((req, res) => {
|
|
112
|
+
res.status(404).json({
|
|
113
|
+
success: false,
|
|
114
|
+
data: null,
|
|
115
|
+
error: `Route ${req.method} ${req.path} not found`,
|
|
116
|
+
meta: { timestamp: new Date().toISOString(), version: '1.0.0' },
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// ── Global error handler ──────────────────────────────────────────────────────
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
121
|
+
app.use((err, _req, res, _next) => {
|
|
122
|
+
console.error('[KA-CHOW Error]', err.stack);
|
|
123
|
+
res.status(500).json({
|
|
124
|
+
success: false,
|
|
125
|
+
data: null,
|
|
126
|
+
error: err.message || 'Internal Server Error',
|
|
127
|
+
meta: { timestamp: new Date().toISOString(), version: '1.0.0' },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* KA-CHOW database singleton.
|
|
4
|
+
* Returns the same better-sqlite3 instance for the lifetime of the process.
|
|
5
|
+
* In test mode (NODE_ENV=test) uses TEST_DB_PATH (defaults to :memory:).
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getDb = getDb;
|
|
12
|
+
exports.closeDb = closeDb;
|
|
13
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const schema_js_1 = require("./schema.js");
|
|
16
|
+
let _db = null;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the initialised SQLite database singleton.
|
|
19
|
+
* Creates the database file and runs all migrations on first call.
|
|
20
|
+
* Subsequent calls return the same cached instance.
|
|
21
|
+
*
|
|
22
|
+
* @returns The better-sqlite3 Database instance
|
|
23
|
+
* @throws If the database file cannot be created or migrations fail
|
|
24
|
+
*/
|
|
25
|
+
function getDb() {
|
|
26
|
+
if (_db)
|
|
27
|
+
return _db;
|
|
28
|
+
const isTest = process.env.NODE_ENV === 'test';
|
|
29
|
+
const dbPath = isTest
|
|
30
|
+
? (process.env.TEST_DB_PATH ?? ':memory:')
|
|
31
|
+
: (process.env.DB_PATH ?? path_1.default.resolve(process.cwd(), 'kachow.db'));
|
|
32
|
+
_db = new better_sqlite3_1.default(dbPath);
|
|
33
|
+
_db.pragma('journal_mode = WAL');
|
|
34
|
+
_db.pragma('foreign_keys = ON');
|
|
35
|
+
(0, schema_js_1.runMigrations)(_db);
|
|
36
|
+
return _db;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Closes the database connection and clears the singleton.
|
|
40
|
+
* Must be called on graceful shutdown and in test afterAll hooks.
|
|
41
|
+
*
|
|
42
|
+
* @returns void
|
|
43
|
+
*/
|
|
44
|
+
function closeDb() {
|
|
45
|
+
if (_db) {
|
|
46
|
+
_db.close();
|
|
47
|
+
_db = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* KA-CHOW SQLite schema.
|
|
4
|
+
* All CREATE TABLE statements live here.
|
|
5
|
+
* Called once by db/index.ts on first startup and by test-helpers in tests.
|
|
6
|
+
*
|
|
7
|
+
* Rule: Never DROP or ALTER tables directly — write a migration in
|
|
8
|
+
* packages/server/src/db/migrations/ instead.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.TABLES = void 0;
|
|
12
|
+
exports.runMigrations = runMigrations;
|
|
13
|
+
/**
|
|
14
|
+
* All table names in the KA-CHOW schema.
|
|
15
|
+
* Used by tests to assert every table was created.
|
|
16
|
+
*/
|
|
17
|
+
exports.TABLES = [
|
|
18
|
+
'services',
|
|
19
|
+
'dependencies',
|
|
20
|
+
'incidents',
|
|
21
|
+
'endpoints',
|
|
22
|
+
'impact_analyses',
|
|
23
|
+
'adrs',
|
|
24
|
+
'healing_prs',
|
|
25
|
+
'qa_sessions',
|
|
26
|
+
'activity_feed',
|
|
27
|
+
'snapshots',
|
|
28
|
+
'teams',
|
|
29
|
+
'team_members',
|
|
30
|
+
'agent_analyses',
|
|
31
|
+
'knowledge_graph',
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Runs all DDL statements against the provided database instance.
|
|
35
|
+
* Uses CREATE TABLE IF NOT EXISTS — idempotent and safe to call multiple times.
|
|
36
|
+
*
|
|
37
|
+
* @param db - An initialised better-sqlite3 Database instance
|
|
38
|
+
* @returns void
|
|
39
|
+
* @throws If SQLite reports a syntax or constraint error in the DDL
|
|
40
|
+
*/
|
|
41
|
+
function runMigrations(db) {
|
|
42
|
+
db.exec(`
|
|
43
|
+
-- ── TEAMS (manager-created, join-code gated) ─────────────────────
|
|
44
|
+
-- Created FIRST because services references teams(id)
|
|
45
|
+
CREATE TABLE IF NOT EXISTS teams (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
name TEXT NOT NULL,
|
|
48
|
+
repo TEXT DEFAULT '—',
|
|
49
|
+
join_code TEXT NOT NULL UNIQUE, -- 6-digit numeric code
|
|
50
|
+
description TEXT DEFAULT '',
|
|
51
|
+
color TEXT DEFAULT '#6366F1',
|
|
52
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
53
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
-- ── TEAM MEMBERS ─────────────────────────────────────────────────
|
|
57
|
+
CREATE TABLE IF NOT EXISTS team_members (
|
|
58
|
+
id TEXT PRIMARY KEY,
|
|
59
|
+
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
60
|
+
name TEXT NOT NULL,
|
|
61
|
+
role TEXT DEFAULT 'Engineer',
|
|
62
|
+
online INTEGER DEFAULT 0, -- 0 = offline, 1 = online
|
|
63
|
+
progress INTEGER DEFAULT 0, -- onboarding progress (0-7)
|
|
64
|
+
joined_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
-- ── SERVICES (graph nodes) ──────────────────────────────────────
|
|
68
|
+
CREATE TABLE IF NOT EXISTS services (
|
|
69
|
+
id TEXT PRIMARY KEY, -- slug: 'auth-service'
|
|
70
|
+
name TEXT NOT NULL, -- 'Auth Service'
|
|
71
|
+
repo_url TEXT, -- github.com/org/auth-service
|
|
72
|
+
repo_subpath TEXT, -- subdir within monorepo e.g. 'services/blog'
|
|
73
|
+
team TEXT, -- legacy sub-team label e.g. 'platform'
|
|
74
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE, -- FK to teams table for multi-tenancy
|
|
75
|
+
language TEXT, -- 'typescript'
|
|
76
|
+
health_score INTEGER DEFAULT 100, -- 0-100
|
|
77
|
+
health_tier TEXT DEFAULT 'healthy', -- healthy/warning/critical
|
|
78
|
+
doc_coverage INTEGER DEFAULT 0, -- % documented
|
|
79
|
+
test_coverage INTEGER DEFAULT 0, -- % tested
|
|
80
|
+
is_external INTEGER DEFAULT 0, -- 1 = unresolved external dep, not a real service
|
|
81
|
+
last_commit TEXT, -- ISO timestamp
|
|
82
|
+
last_ingested TEXT, -- ISO timestamp
|
|
83
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
-- ── DEPENDENCIES (graph edges) ──────────────────────────────────
|
|
88
|
+
CREATE TABLE IF NOT EXISTS dependencies (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
source_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
91
|
+
target_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
92
|
+
type TEXT NOT NULL, -- REST/Event/gRPC/DB
|
|
93
|
+
endpoints TEXT, -- JSON array of endpoint strings
|
|
94
|
+
strength INTEGER DEFAULT 1, -- call frequency weight
|
|
95
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
96
|
+
UNIQUE(source_id, target_id, type) -- one edge per direction+type pair
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
-- ── INCIDENTS ───────────────────────────────────────────────────
|
|
100
|
+
CREATE TABLE IF NOT EXISTS incidents (
|
|
101
|
+
id TEXT PRIMARY KEY,
|
|
102
|
+
service_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
103
|
+
severity TEXT NOT NULL, -- P0/P1/P2/P3
|
|
104
|
+
title TEXT NOT NULL,
|
|
105
|
+
root_cause TEXT,
|
|
106
|
+
status TEXT DEFAULT 'open', -- open/resolved/postmortem
|
|
107
|
+
occurred_at TEXT NOT NULL,
|
|
108
|
+
resolved_at TEXT,
|
|
109
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
-- ── ENDPOINTS ───────────────────────────────────────────────────
|
|
113
|
+
CREATE TABLE IF NOT EXISTS endpoints (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
service_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
116
|
+
method TEXT NOT NULL, -- GET/POST/PUT/DELETE
|
|
117
|
+
path TEXT NOT NULL, -- /api/v1/users/:id
|
|
118
|
+
deprecated INTEGER DEFAULT 0, -- 0 or 1
|
|
119
|
+
has_spec INTEGER DEFAULT 0, -- OpenAPI spec exists
|
|
120
|
+
consumers TEXT, -- JSON array of service ids
|
|
121
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
122
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
123
|
+
UNIQUE(service_id, path, method) -- one row per service+route+verb
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
-- ── IMPACT ANALYSES ─────────────────────────────────────────────
|
|
127
|
+
CREATE TABLE IF NOT EXISTS impact_analyses (
|
|
128
|
+
id TEXT PRIMARY KEY,
|
|
129
|
+
trigger_type TEXT NOT NULL, -- deprecation/schema-change/delete
|
|
130
|
+
trigger_ref TEXT NOT NULL, -- endpoint or schema name
|
|
131
|
+
service_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
132
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
133
|
+
affected TEXT NOT NULL, -- JSON: [{id, name, impact_type}]
|
|
134
|
+
risk_score REAL NOT NULL, -- 0.0-10.0
|
|
135
|
+
confidence REAL NOT NULL, -- 0.0-1.0
|
|
136
|
+
precedent TEXT, -- historical match description
|
|
137
|
+
status TEXT DEFAULT 'pending', -- pending/reviewed/actioned
|
|
138
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
-- ── ADR DRAFTS ──────────────────────────────────────────────────
|
|
142
|
+
CREATE TABLE IF NOT EXISTS adrs (
|
|
143
|
+
id TEXT PRIMARY KEY,
|
|
144
|
+
analysis_id TEXT REFERENCES impact_analyses(id) ON DELETE CASCADE,
|
|
145
|
+
title TEXT NOT NULL,
|
|
146
|
+
context TEXT NOT NULL,
|
|
147
|
+
decision TEXT NOT NULL,
|
|
148
|
+
consequences TEXT NOT NULL,
|
|
149
|
+
status TEXT DEFAULT 'draft', -- draft/approved/rejected
|
|
150
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
-- ── SELF-HEALING PRs ────────────────────────────────────────────
|
|
154
|
+
CREATE TABLE IF NOT EXISTS healing_prs (
|
|
155
|
+
id TEXT PRIMARY KEY,
|
|
156
|
+
service_id TEXT REFERENCES services(id) ON DELETE CASCADE,
|
|
157
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
158
|
+
issue_type TEXT NOT NULL, -- missing-retry/no-error-handling/etc
|
|
159
|
+
patch TEXT NOT NULL, -- the actual code diff
|
|
160
|
+
explanation TEXT NOT NULL,
|
|
161
|
+
github_pr_url TEXT,
|
|
162
|
+
github_pr_num INTEGER,
|
|
163
|
+
status TEXT DEFAULT 'open', -- open/merged/closed
|
|
164
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
-- ── Q&A HISTORY ─────────────────────────────────────────────────
|
|
168
|
+
CREATE TABLE IF NOT EXISTS qa_sessions (
|
|
169
|
+
id TEXT PRIMARY KEY,
|
|
170
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
171
|
+
question TEXT NOT NULL,
|
|
172
|
+
answer TEXT NOT NULL,
|
|
173
|
+
citations TEXT NOT NULL, -- JSON array of {file, line, snippet}
|
|
174
|
+
related_nodes TEXT NOT NULL, -- JSON array of service ids
|
|
175
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
-- ── ACTIVITY FEED ───────────────────────────────────────────────
|
|
179
|
+
CREATE TABLE IF NOT EXISTS activity_feed (
|
|
180
|
+
id TEXT PRIMARY KEY,
|
|
181
|
+
type TEXT NOT NULL, -- commit/health-drop/pr-opened/etc
|
|
182
|
+
service_id TEXT,
|
|
183
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
184
|
+
title TEXT NOT NULL,
|
|
185
|
+
detail TEXT,
|
|
186
|
+
severity TEXT DEFAULT 'info', -- info/warning/critical
|
|
187
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
-- ── GRAPH SNAPSHOTS ─────────────────────────────────────────────
|
|
191
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
192
|
+
id TEXT PRIMARY KEY,
|
|
193
|
+
version TEXT NOT NULL,
|
|
194
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
195
|
+
nodes TEXT NOT NULL, -- JSON: GraphNode[]
|
|
196
|
+
edges TEXT NOT NULL, -- JSON: GraphEdge[]
|
|
197
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
198
|
+
UNIQUE(version, team_id) -- one snapshot per version per team
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
-- ── AGENT ANALYSES ──────────────────────────────────────────────
|
|
202
|
+
CREATE TABLE IF NOT EXISTS agent_analyses (
|
|
203
|
+
id TEXT PRIMARY KEY,
|
|
204
|
+
analysis_id TEXT NOT NULL,
|
|
205
|
+
agent_name TEXT NOT NULL,
|
|
206
|
+
service_id TEXT,
|
|
207
|
+
service_name TEXT,
|
|
208
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
209
|
+
findings TEXT NOT NULL,
|
|
210
|
+
summary TEXT NOT NULL,
|
|
211
|
+
duration_ms INTEGER,
|
|
212
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
-- ── KNOWLEDGE GRAPH (agent-produced) ────────────────────────────
|
|
216
|
+
CREATE TABLE IF NOT EXISTS knowledge_graph (
|
|
217
|
+
id TEXT PRIMARY KEY,
|
|
218
|
+
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
|
219
|
+
nodes TEXT NOT NULL, -- JSON: KnowledgeNode[]
|
|
220
|
+
edges TEXT NOT NULL, -- JSON: KnowledgeEdge[]
|
|
221
|
+
architecture TEXT NOT NULL, -- JSON: { layers, dataFlows }
|
|
222
|
+
self_healing TEXT NOT NULL, -- JSON: SelfHealingSuggestion[]
|
|
223
|
+
summary TEXT NOT NULL,
|
|
224
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
225
|
+
);
|
|
226
|
+
`);
|
|
227
|
+
// ── Run additive migrations for existing databases ────────────────────────
|
|
228
|
+
// These are safe to run repeatedly — they only add columns if missing.
|
|
229
|
+
const safeAddColumn = (table, column, definition) => {
|
|
230
|
+
try {
|
|
231
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Column already exists — this is expected and safe to ignore
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
// Multi-tenancy columns for existing DBs
|
|
238
|
+
safeAddColumn('services', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
239
|
+
safeAddColumn('impact_analyses', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
240
|
+
safeAddColumn('healing_prs', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
241
|
+
safeAddColumn('qa_sessions', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
242
|
+
safeAddColumn('activity_feed', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
243
|
+
safeAddColumn('snapshots', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
244
|
+
safeAddColumn('agent_analyses', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
245
|
+
safeAddColumn('knowledge_graph', 'team_id', 'TEXT REFERENCES teams(id) ON DELETE CASCADE');
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BullMQ ingest queue definition.
|
|
4
|
+
* All repository ingestion jobs are enqueued here and processed by ingestWorker.ts.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getIngestQueue = getIngestQueue;
|
|
8
|
+
exports.closeIngestQueue = closeIngestQueue;
|
|
9
|
+
const bullmq_1 = require("bullmq");
|
|
10
|
+
const redis_js_1 = require("./redis.js");
|
|
11
|
+
let _ingestQueue = null;
|
|
12
|
+
/**
|
|
13
|
+
* Returns (and lazily creates) the ingest BullMQ queue.
|
|
14
|
+
* Returns null when Redis is disabled — callers should run jobs in-process instead.
|
|
15
|
+
*
|
|
16
|
+
* @returns BullMQ Queue for ingestion jobs, or null if Redis is disabled
|
|
17
|
+
*/
|
|
18
|
+
function getIngestQueue() {
|
|
19
|
+
if ((0, redis_js_1.isRedisDisabled)())
|
|
20
|
+
return null;
|
|
21
|
+
const redis = (0, redis_js_1.getRedis)();
|
|
22
|
+
if (!redis)
|
|
23
|
+
return null;
|
|
24
|
+
if (!_ingestQueue) {
|
|
25
|
+
_ingestQueue = new bullmq_1.Queue('ingest', {
|
|
26
|
+
connection: redis,
|
|
27
|
+
defaultJobOptions: {
|
|
28
|
+
attempts: 2,
|
|
29
|
+
backoff: { type: 'exponential', delay: 3000 },
|
|
30
|
+
removeOnComplete: { count: 100 },
|
|
31
|
+
removeOnFail: { count: 50 },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return _ingestQueue;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Closes the ingest queue connection.
|
|
39
|
+
* Call during graceful server shutdown.
|
|
40
|
+
*
|
|
41
|
+
* @returns Promise that resolves when queue is closed
|
|
42
|
+
*/
|
|
43
|
+
async function closeIngestQueue() {
|
|
44
|
+
if (_ingestQueue) {
|
|
45
|
+
await _ingestQueue.close();
|
|
46
|
+
_ingestQueue = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=ingestQueue.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redis connection for BullMQ.
|
|
4
|
+
* A single shared IORedis instance reused by all queues and workers.
|
|
5
|
+
* Connection parameters are read from environment variables.
|
|
6
|
+
*
|
|
7
|
+
* Set DISABLE_REDIS=true to skip Redis entirely (jobs run in-process).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.isRedisDisabled = isRedisDisabled;
|
|
11
|
+
exports.getRedis = getRedis;
|
|
12
|
+
exports.closeRedis = closeRedis;
|
|
13
|
+
const ioredis_1 = require("ioredis");
|
|
14
|
+
let _redis = null;
|
|
15
|
+
/** Returns true when Redis has been explicitly disabled via env var. */
|
|
16
|
+
function isRedisDisabled() {
|
|
17
|
+
return process.env.DISABLE_REDIS === 'true';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns (and lazily creates) the shared Redis client.
|
|
21
|
+
* Returns null when DISABLE_REDIS=true — callers must handle the null case.
|
|
22
|
+
*
|
|
23
|
+
* @returns Shared IORedis instance or null if Redis is disabled
|
|
24
|
+
*/
|
|
25
|
+
function getRedis() {
|
|
26
|
+
if (isRedisDisabled())
|
|
27
|
+
return null;
|
|
28
|
+
if (!_redis) {
|
|
29
|
+
_redis = new ioredis_1.Redis({
|
|
30
|
+
host: process.env.REDIS_HOST || '127.0.0.1',
|
|
31
|
+
port: Number(process.env.REDIS_PORT) || 6379,
|
|
32
|
+
password: process.env.REDIS_PASSWORD || undefined,
|
|
33
|
+
maxRetriesPerRequest: null, // required by BullMQ
|
|
34
|
+
enableReadyCheck: false,
|
|
35
|
+
// Stop hammering the console — log once, then back off silently
|
|
36
|
+
retryStrategy: (times) => {
|
|
37
|
+
if (times === 1)
|
|
38
|
+
console.warn('[Redis] Not available — ingest jobs will run in-process. Set DISABLE_REDIS=true to silence this.');
|
|
39
|
+
return Math.min(times * 500, 10_000);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
_redis.on('error', () => { });
|
|
43
|
+
}
|
|
44
|
+
return _redis;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Closes the shared Redis connection.
|
|
48
|
+
* Call during graceful server shutdown.
|
|
49
|
+
*
|
|
50
|
+
* @returns Promise that resolves when connection is fully closed
|
|
51
|
+
*/
|
|
52
|
+
async function closeRedis() {
|
|
53
|
+
if (_redis) {
|
|
54
|
+
await _redis.quit();
|
|
55
|
+
_redis = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=redis.js.map
|