@james-wall/codegov 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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +335 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/db/queries.d.ts +69 -0
  7. package/dist/db/queries.js +109 -0
  8. package/dist/db/queries.js.map +1 -0
  9. package/dist/db/schema.d.ts +3 -0
  10. package/dist/db/schema.js +84 -0
  11. package/dist/db/schema.js.map +1 -0
  12. package/dist/detect/engine.d.ts +18 -0
  13. package/dist/detect/engine.js +214 -0
  14. package/dist/detect/engine.js.map +1 -0
  15. package/dist/export/formats.d.ts +26 -0
  16. package/dist/export/formats.js +273 -0
  17. package/dist/export/formats.js.map +1 -0
  18. package/dist/git/hooks.d.ts +4 -0
  19. package/dist/git/hooks.js +48 -0
  20. package/dist/git/hooks.js.map +1 -0
  21. package/dist/git/parser.d.ts +15 -0
  22. package/dist/git/parser.js +114 -0
  23. package/dist/git/parser.js.map +1 -0
  24. package/dist/scan/index.d.ts +4 -0
  25. package/dist/scan/index.js +92 -0
  26. package/dist/scan/index.js.map +1 -0
  27. package/dist/scan/store.d.ts +10 -0
  28. package/dist/scan/store.js +87 -0
  29. package/dist/scan/store.js.map +1 -0
  30. package/dist/server/dashboard.d.ts +1 -0
  31. package/dist/server/dashboard.js +115 -0
  32. package/dist/server/dashboard.js.map +1 -0
  33. package/dist/server/index.d.ts +1 -0
  34. package/dist/server/index.js +44 -0
  35. package/dist/server/index.js.map +1 -0
  36. package/dist/server/otel.d.ts +2 -0
  37. package/dist/server/otel.js +67 -0
  38. package/dist/server/otel.js.map +1 -0
  39. package/dist/server/webhook.d.ts +2 -0
  40. package/dist/server/webhook.js +22 -0
  41. package/dist/server/webhook.js.map +1 -0
  42. package/dist/types.d.ts +86 -0
  43. package/dist/types.js +2 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +54 -0
@@ -0,0 +1,48 @@
1
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ const POST_COMMIT_HOOK = `#!/bin/sh
5
+ # CodeGov post-commit hook — records AI provenance for each commit
6
+ codegov record-commit HEAD
7
+ `;
8
+ function getGitDir() {
9
+ return execFileSync("git", ["rev-parse", "--git-dir"], {
10
+ encoding: "utf-8",
11
+ }).trim();
12
+ }
13
+ export function installHooks() {
14
+ const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
15
+ encoding: "utf-8",
16
+ }).trim();
17
+ const hooksDir = join(getGitDir(), "hooks");
18
+ if (!existsSync(hooksDir)) {
19
+ mkdirSync(hooksDir, { recursive: true });
20
+ }
21
+ const installed = [];
22
+ const skipped = [];
23
+ const postCommitPath = join(hooksDir, "post-commit");
24
+ if (existsSync(postCommitPath)) {
25
+ skipped.push("post-commit (already exists)");
26
+ }
27
+ else {
28
+ writeFileSync(postCommitPath, POST_COMMIT_HOOK);
29
+ chmodSync(postCommitPath, 0o755);
30
+ installed.push("post-commit");
31
+ }
32
+ const codgovDir = join(repoRoot, ".codegov");
33
+ if (!existsSync(codgovDir)) {
34
+ mkdirSync(codgovDir, { recursive: true });
35
+ }
36
+ const gitignorePath = join(repoRoot, ".gitignore");
37
+ if (existsSync(gitignorePath)) {
38
+ const content = readFileSync(gitignorePath, "utf-8");
39
+ if (!content.includes(".codegov")) {
40
+ writeFileSync(gitignorePath, content.trimEnd() + "\n.codegov/\n");
41
+ }
42
+ }
43
+ else {
44
+ writeFileSync(gitignorePath, ".codegov/\n");
45
+ }
46
+ return { installed, skipped };
47
+ }
48
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/git/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,gBAAgB,GAAG;;;CAGxB,CAAC;AAEF,SAAS,SAAS;IAChB,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE;QACrD,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE;QACrE,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAC,IAAI,EAAE,CAAC;IAEV,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACrD,IAAI,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QAChD,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACjC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,aAAa,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,eAAe,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,aAAa,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface RawCommit {
2
+ hash: string;
3
+ timestamp: string;
4
+ author: string;
5
+ email: string;
6
+ message: string;
7
+ body: string;
8
+ trailers: string;
9
+ filesChanged: string[];
10
+ linesAdded: number;
11
+ linesRemoved: number;
12
+ }
13
+ export declare function getCommitLog(since?: string, path?: string, cwd?: string): RawCommit[];
14
+ export declare function parseOutput(output: string): RawCommit[];
15
+ export declare function getCommitDetail(hash: string, cwd?: string): RawCommit | null;
@@ -0,0 +1,114 @@
1
+ import { execFileSync } from "node:child_process";
2
+ const RECORD_START = "<<CODEGOV_RS>>";
3
+ const RECORD_END = "<<CODEGOV_RE>>";
4
+ const FIELD_SEP = "<<CODEGOV_FS>>";
5
+ const BODY_SEP = "<<CODEGOV_BS>>";
6
+ function buildFormat() {
7
+ return RECORD_START +
8
+ ["%H", "%aI", "%an", "%ae", "%s"].join(FIELD_SEP) +
9
+ BODY_SEP + "%b" + BODY_SEP + "%(trailers)" +
10
+ RECORD_END;
11
+ }
12
+ export function getCommitLog(since, path, cwd) {
13
+ const args = [
14
+ "log",
15
+ `--format=${buildFormat()}`,
16
+ "--numstat",
17
+ ];
18
+ if (since) {
19
+ args.push(`--since=${expandSince(since)}`);
20
+ }
21
+ if (path) {
22
+ args.push("--", path);
23
+ }
24
+ let output;
25
+ try {
26
+ output = execFileSync("git", args, {
27
+ encoding: "utf-8",
28
+ maxBuffer: 50 * 1024 * 1024,
29
+ ...(cwd ? { cwd } : {}),
30
+ });
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ return parseOutput(output);
36
+ }
37
+ export function parseOutput(output) {
38
+ const commits = [];
39
+ const blocks = output.split(RECORD_START);
40
+ for (const block of blocks) {
41
+ if (!block.includes(RECORD_END))
42
+ continue;
43
+ const [formatPart, afterEnd] = block.split(RECORD_END);
44
+ const bodyParts = formatPart.split(BODY_SEP);
45
+ if (bodyParts.length < 3)
46
+ continue;
47
+ const headerStr = bodyParts[0].trim();
48
+ const body = (bodyParts[1] || "").trim();
49
+ const trailers = (bodyParts[2] || "").trim();
50
+ const fields = headerStr.split(FIELD_SEP);
51
+ if (fields.length < 5)
52
+ continue;
53
+ const [hash, timestamp, author, email, message] = fields;
54
+ let linesAdded = 0;
55
+ let linesRemoved = 0;
56
+ const filesChanged = [];
57
+ if (afterEnd) {
58
+ for (const line of afterEnd.split("\n")) {
59
+ const m = line.match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
60
+ if (m) {
61
+ linesAdded += m[1] === "-" ? 0 : parseInt(m[1]);
62
+ linesRemoved += m[2] === "-" ? 0 : parseInt(m[2]);
63
+ filesChanged.push(m[3]);
64
+ }
65
+ }
66
+ }
67
+ commits.push({
68
+ hash: hash,
69
+ timestamp: timestamp,
70
+ author: author,
71
+ email: email,
72
+ message: message,
73
+ body,
74
+ trailers,
75
+ filesChanged,
76
+ linesAdded,
77
+ linesRemoved,
78
+ });
79
+ }
80
+ return commits;
81
+ }
82
+ function expandSince(since) {
83
+ const match = since.match(/^(\d+)([dhm])$/);
84
+ if (!match)
85
+ return since;
86
+ const value = match[1];
87
+ const unit = match[2];
88
+ switch (unit) {
89
+ case "d": return `${value} days ago`;
90
+ case "h": return `${value} hours ago`;
91
+ case "m": return `${value} months ago`;
92
+ default: return since;
93
+ }
94
+ }
95
+ export function getCommitDetail(hash, cwd) {
96
+ let output;
97
+ try {
98
+ output = execFileSync("git", [
99
+ "log", "-1",
100
+ `--format=${buildFormat()}`,
101
+ "--numstat",
102
+ hash,
103
+ ], {
104
+ encoding: "utf-8",
105
+ ...(cwd ? { cwd } : {}),
106
+ });
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ const commits = parseOutput(output);
112
+ return commits[0] || null;
113
+ }
114
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/git/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { ProvenanceRecord, ScanResult, StatsResult } from "../types.js";
2
+ export declare function scanHistory(since?: string, path?: string): ScanResult;
3
+ export declare function forensics(hash: string): ProvenanceRecord | null;
4
+ export declare function computeStats(): StatsResult;
@@ -0,0 +1,92 @@
1
+ import { getCommitLog, getCommitDetail } from "../git/parser.js";
2
+ import { detectAgent } from "../detect/engine.js";
3
+ import { appendRecord, readRecords } from "./store.js";
4
+ export function scanHistory(since, path) {
5
+ const commits = getCommitLog(since, path);
6
+ const existing = new Set(readRecords().map(r => r.commitHash));
7
+ const records = [];
8
+ for (const commit of commits) {
9
+ if (existing.has(commit.hash))
10
+ continue;
11
+ const detection = detectAgent(commit);
12
+ if (detection.confidence > 0.3) {
13
+ const record = {
14
+ commitHash: commit.hash,
15
+ timestamp: commit.timestamp,
16
+ agentId: detection.agentId,
17
+ modelVersion: detection.modelVersion,
18
+ promptSummary: detection.promptSummary,
19
+ filesChanged: commit.filesChanged,
20
+ linesAdded: commit.linesAdded,
21
+ linesRemoved: commit.linesRemoved,
22
+ reviewStatus: "unreviewed",
23
+ reviewer: null,
24
+ confidence: detection.confidence,
25
+ signals: detection.signals,
26
+ };
27
+ records.push(record);
28
+ appendRecord(record);
29
+ }
30
+ }
31
+ return {
32
+ totalCommits: commits.length,
33
+ aiCommits: records.length + existing.size,
34
+ newRecords: records.length,
35
+ records,
36
+ };
37
+ }
38
+ export function forensics(hash) {
39
+ const stored = readRecords().find((r) => r.commitHash === hash);
40
+ if (stored)
41
+ return stored;
42
+ const commit = getCommitDetail(hash);
43
+ if (!commit)
44
+ return null;
45
+ const detection = detectAgent(commit);
46
+ return {
47
+ commitHash: commit.hash,
48
+ timestamp: commit.timestamp,
49
+ agentId: detection.agentId,
50
+ modelVersion: detection.modelVersion,
51
+ promptSummary: detection.promptSummary,
52
+ filesChanged: commit.filesChanged,
53
+ linesAdded: commit.linesAdded,
54
+ linesRemoved: commit.linesRemoved,
55
+ reviewStatus: "unreviewed",
56
+ reviewer: null,
57
+ confidence: detection.confidence,
58
+ signals: detection.signals,
59
+ };
60
+ }
61
+ export function computeStats() {
62
+ const records = readRecords();
63
+ const commits = getCommitLog();
64
+ const byAgent = {};
65
+ const byMonth = {};
66
+ for (const record of records) {
67
+ byAgent[record.agentId] = (byAgent[record.agentId] || 0) + 1;
68
+ }
69
+ for (const commit of commits) {
70
+ const month = commit.timestamp.slice(0, 7);
71
+ if (!byMonth[month]) {
72
+ byMonth[month] = { total: 0, ai: 0 };
73
+ }
74
+ byMonth[month].total++;
75
+ }
76
+ for (const record of records) {
77
+ const month = record.timestamp.slice(0, 7);
78
+ if (byMonth[month]) {
79
+ byMonth[month].ai++;
80
+ }
81
+ }
82
+ const aiCommits = records.length;
83
+ const totalCommits = commits.length;
84
+ return {
85
+ totalCommits,
86
+ aiCommits,
87
+ aiPercentage: totalCommits > 0 ? (aiCommits / totalCommits) * 100 : 0,
88
+ byAgent,
89
+ byMonth,
90
+ };
91
+ }
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scan/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { ProvenanceRecord } from "../types.js";
2
+ export declare function ensureStoreExists(): void;
3
+ export declare function appendRecord(record: ProvenanceRecord): void;
4
+ export declare function writeRecords(records: ProvenanceRecord[]): void;
5
+ export declare function readRecords(): ProvenanceRecord[];
6
+ export declare function queryRecords(opts: {
7
+ path?: string;
8
+ agent?: string;
9
+ since?: string;
10
+ }): ProvenanceRecord[];
@@ -0,0 +1,87 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ function getRepoRoot() {
5
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
6
+ }
7
+ function getLogPath() {
8
+ const root = getRepoRoot();
9
+ return join(root, ".codegov", "events.jsonl");
10
+ }
11
+ export function ensureStoreExists() {
12
+ const logPath = getLogPath();
13
+ const dir = join(logPath, "..");
14
+ if (!existsSync(dir)) {
15
+ mkdirSync(dir, { recursive: true });
16
+ }
17
+ if (!existsSync(logPath)) {
18
+ writeFileSync(logPath, "");
19
+ }
20
+ }
21
+ export function appendRecord(record) {
22
+ ensureStoreExists();
23
+ const logPath = getLogPath();
24
+ const line = JSON.stringify(record) + "\n";
25
+ writeFileSync(logPath, line, { flag: "a" });
26
+ }
27
+ export function writeRecords(records) {
28
+ ensureStoreExists();
29
+ const logPath = getLogPath();
30
+ const content = records.map((r) => JSON.stringify(r)).join("\n") + "\n";
31
+ writeFileSync(logPath, content);
32
+ }
33
+ export function readRecords() {
34
+ const logPath = getLogPath();
35
+ if (!existsSync(logPath))
36
+ return [];
37
+ const content = readFileSync(logPath, "utf-8");
38
+ const all = content
39
+ .split("\n")
40
+ .filter((line) => line.trim())
41
+ .map((line) => JSON.parse(line));
42
+ // Deduplicate by commit hash, keeping the latest entry
43
+ const seen = new Map();
44
+ for (const r of all) {
45
+ seen.set(r.commitHash, r);
46
+ }
47
+ return Array.from(seen.values());
48
+ }
49
+ export function queryRecords(opts) {
50
+ let records = readRecords();
51
+ if (opts.path) {
52
+ records = records.filter((r) => r.filesChanged.some((f) => f.startsWith(opts.path)));
53
+ }
54
+ if (opts.agent) {
55
+ records = records.filter((r) => r.agentId === opts.agent);
56
+ }
57
+ if (opts.since) {
58
+ const sinceDate = parseSinceDate(opts.since);
59
+ if (sinceDate) {
60
+ records = records.filter((r) => new Date(r.timestamp) >= sinceDate);
61
+ }
62
+ }
63
+ return records;
64
+ }
65
+ function parseSinceDate(since) {
66
+ const match = since.match(/^(\d+)([dhm])$/);
67
+ if (!match) {
68
+ const date = new Date(since);
69
+ return isNaN(date.getTime()) ? null : date;
70
+ }
71
+ const value = parseInt(match[1]);
72
+ const unit = match[2];
73
+ const now = new Date();
74
+ switch (unit) {
75
+ case "d":
76
+ now.setDate(now.getDate() - value);
77
+ break;
78
+ case "h":
79
+ now.setHours(now.getHours() - value);
80
+ break;
81
+ case "m":
82
+ now.setMonth(now.getMonth() - value);
83
+ break;
84
+ }
85
+ return now;
86
+ }
87
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/scan/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function renderDashboard(days?: number): string;
@@ -0,0 +1,115 @@
1
+ import { getDashboardMetrics } from "../db/queries.js";
2
+ export function renderDashboard(days = 30) {
3
+ const m = getDashboardMetrics(days);
4
+ const toolRows = m.eventsByTool.map(t => `<tr><td>${esc(t.tool)}</td><td>${t.count}</td></tr>`).join("");
5
+ const eventRows = m.recentEvents.map(e => `<tr>
6
+ <td>${esc(e.tool)}</td>
7
+ <td>${esc(e.developer)}</td>
8
+ <td>${esc(e.event_type)}</td>
9
+ <td>${esc(e.file_path ?? "—")}</td>
10
+ <td>${formatTime(e.timestamp)}</td>
11
+ </tr>`).join("");
12
+ const commitRows = m.recentCommits.map(c => `<tr>
13
+ <td><code>${esc(c.hash.slice(0, 8))}</code></td>
14
+ <td>${esc(c.author)}</td>
15
+ <td>${esc((c.message ?? "").slice(0, 60))}</td>
16
+ <td>+${c.total_additions} / -${c.total_deletions}</td>
17
+ <td>${formatTime(c.timestamp)}</td>
18
+ </tr>`).join("");
19
+ const attrRows = m.attributionsByTool.map(a => `<tr><td>${esc(a.tool)}</td><td>${esc(a.confidence)}</td><td>${a.count}</td></tr>`).join("");
20
+ const devRows = m.devActivity.map(d => `<tr><td>${esc(d.developer)}</td><td>${d.events}</td><td>${d.commits}</td></tr>`).join("");
21
+ return `<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head>
24
+ <meta charset="utf-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1">
26
+ <title>CodeGov Dashboard</title>
27
+ <style>
28
+ * { box-sizing: border-box; margin: 0; padding: 0; }
29
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f1117; color: #e1e4e8; padding: 24px; }
30
+ h1 { font-size: 24px; margin-bottom: 8px; color: #f0f6fc; }
31
+ .subtitle { color: #8b949e; margin-bottom: 32px; }
32
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 32px; }
33
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; }
34
+ .card .label { font-size: 12px; text-transform: uppercase; color: #8b949e; margin-bottom: 4px; }
35
+ .card .value { font-size: 32px; font-weight: 600; color: #58a6ff; }
36
+ h2 { font-size: 18px; margin: 32px 0 12px; color: #f0f6fc; }
37
+ table { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
38
+ th { text-align: left; padding: 10px 14px; background: #1c2128; color: #8b949e; font-size: 12px; text-transform: uppercase; border-bottom: 1px solid #30363d; }
39
+ td { padding: 10px 14px; border-bottom: 1px solid #21262d; font-size: 14px; }
40
+ tr:last-child td { border-bottom: none; }
41
+ code { background: #1c2128; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
42
+ .empty { color: #8b949e; padding: 40px; text-align: center; }
43
+ .refresh { float: right; font-size: 13px; color: #58a6ff; text-decoration: none; }
44
+ .refresh:hover { text-decoration: underline; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <a class="refresh" href="/dashboard">Refresh</a>
49
+ <h1>CodeGov</h1>
50
+ <p class="subtitle">AI Dev-Tool Governance Dashboard — last ${days} days</p>
51
+
52
+ <div class="grid">
53
+ <div class="card">
54
+ <div class="label">Tool Events</div>
55
+ <div class="value">${m.totalEvents}</div>
56
+ </div>
57
+ <div class="card">
58
+ <div class="label">Commits</div>
59
+ <div class="value">${m.totalCommits}</div>
60
+ </div>
61
+ <div class="card">
62
+ <div class="label">Attributions</div>
63
+ <div class="value">${m.totalAttributions}</div>
64
+ </div>
65
+ </div>
66
+
67
+ ${m.eventsByTool.length > 0 ? `
68
+ <h2>Events by Tool</h2>
69
+ <table>
70
+ <thead><tr><th>Tool</th><th>Events</th></tr></thead>
71
+ <tbody>${toolRows}</tbody>
72
+ </table>` : ""}
73
+
74
+ ${m.devActivity.length > 0 ? `
75
+ <h2>Developer Activity</h2>
76
+ <table>
77
+ <thead><tr><th>Developer</th><th>Tool Events</th><th>Commits</th></tr></thead>
78
+ <tbody>${devRows}</tbody>
79
+ </table>` : ""}
80
+
81
+ ${m.attributionsByTool.length > 0 ? `
82
+ <h2>Attributions</h2>
83
+ <table>
84
+ <thead><tr><th>Tool</th><th>Confidence</th><th>Count</th></tr></thead>
85
+ <tbody>${attrRows}</tbody>
86
+ </table>` : ""}
87
+
88
+ <h2>Recent Tool Events</h2>
89
+ ${m.recentEvents.length > 0 ? `
90
+ <table>
91
+ <thead><tr><th>Tool</th><th>Developer</th><th>Type</th><th>File</th><th>Time</th></tr></thead>
92
+ <tbody>${eventRows}</tbody>
93
+ </table>` : `<div class="empty">No tool events yet. Point your OTEL exporter at POST /v1/traces to start collecting data.</div>`}
94
+
95
+ <h2>Recent Commits</h2>
96
+ ${m.recentCommits.length > 0 ? `
97
+ <table>
98
+ <thead><tr><th>Hash</th><th>Author</th><th>Message</th><th>Changes</th><th>Time</th></tr></thead>
99
+ <tbody>${commitRows}</tbody>
100
+ </table>` : `<div class="empty">No commits yet. Run <code>codegov init</code> to install the post-commit hook.</div>`}
101
+ </body>
102
+ </html>`;
103
+ }
104
+ function esc(s) {
105
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
106
+ }
107
+ function formatTime(iso) {
108
+ try {
109
+ return new Date(iso).toLocaleString();
110
+ }
111
+ catch {
112
+ return iso;
113
+ }
114
+ }
115
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/server/dashboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function startServer(port?: number): void;
@@ -0,0 +1,44 @@
1
+ import express from "express";
2
+ import { handleOtelTraces } from "./otel.js";
3
+ import { handleCommitHook } from "./webhook.js";
4
+ import { renderDashboard } from "./dashboard.js";
5
+ import { getDashboardMetrics } from "../db/queries.js";
6
+ import { getDb, closeDb } from "../db/schema.js";
7
+ const PORT = parseInt(process.env["CODEGOV_PORT"] ?? "4318", 10);
8
+ export function startServer(port = PORT) {
9
+ getDb();
10
+ const app = express();
11
+ app.use(express.json({ limit: "10mb" }));
12
+ app.post("/v1/traces", handleOtelTraces);
13
+ app.post("/v1/hooks/commit", handleCommitHook);
14
+ app.get("/dashboard", (req, res) => {
15
+ const days = parseInt(req.query["days"], 10) || 30;
16
+ res.type("html").send(renderDashboard(days));
17
+ });
18
+ app.get("/api/metrics", (req, res) => {
19
+ const days = parseInt(req.query["days"], 10) || 30;
20
+ res.json(getDashboardMetrics(days));
21
+ });
22
+ app.get("/health", (_req, res) => {
23
+ res.json({ status: "ok", uptime: process.uptime() });
24
+ });
25
+ app.get("/", (_req, res) => {
26
+ res.redirect("/dashboard");
27
+ });
28
+ const server = app.listen(port, () => {
29
+ console.log(`CodeGov server running on http://localhost:${port}`);
30
+ console.log(` Dashboard: http://localhost:${port}/dashboard`);
31
+ console.log(` OTEL traces: POST http://localhost:${port}/v1/traces`);
32
+ console.log(` Git hooks: POST http://localhost:${port}/v1/hooks/commit`);
33
+ console.log(` API: GET http://localhost:${port}/api/metrics`);
34
+ });
35
+ const shutdown = () => {
36
+ console.log("\nShutting down...");
37
+ server.close();
38
+ closeDb();
39
+ process.exit(0);
40
+ };
41
+ process.on("SIGINT", shutdown);
42
+ process.on("SIGTERM", shutdown);
43
+ }
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Request, Response } from "express";
2
+ export declare function handleOtelTraces(req: Request, res: Response): void;