@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.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +335 -0
- package/dist/cli.js.map +1 -0
- package/dist/db/queries.d.ts +69 -0
- package/dist/db/queries.js +109 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.js +84 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/detect/engine.d.ts +18 -0
- package/dist/detect/engine.js +214 -0
- package/dist/detect/engine.js.map +1 -0
- package/dist/export/formats.d.ts +26 -0
- package/dist/export/formats.js +273 -0
- package/dist/export/formats.js.map +1 -0
- package/dist/git/hooks.d.ts +4 -0
- package/dist/git/hooks.js +48 -0
- package/dist/git/hooks.js.map +1 -0
- package/dist/git/parser.d.ts +15 -0
- package/dist/git/parser.js +114 -0
- package/dist/git/parser.js.map +1 -0
- package/dist/scan/index.d.ts +4 -0
- package/dist/scan/index.js +92 -0
- package/dist/scan/index.js.map +1 -0
- package/dist/scan/store.d.ts +10 -0
- package/dist/scan/store.js +87 -0
- package/dist/scan/store.js.map +1 -0
- package/dist/server/dashboard.d.ts +1 -0
- package/dist/server/dashboard.js +115 -0
- package/dist/server/dashboard.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +44 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/otel.d.ts +2 -0
- package/dist/server/otel.js +67 -0
- package/dist/server/otel.js.map +1 -0
- package/dist/server/webhook.d.ts +2 -0
- package/dist/server/webhook.js +22 -0
- package/dist/server/webhook.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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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"}
|