@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,67 @@
1
+ import { insertToolEvent } from "../db/queries.js";
2
+ function getAttr(attrs, key) {
3
+ const attr = attrs?.find(a => a.key === key);
4
+ return attr?.value?.stringValue ?? attr?.value?.intValue ?? attr?.value?.doubleValue?.toString();
5
+ }
6
+ function parseTimestamp(nanos) {
7
+ if (!nanos)
8
+ return new Date().toISOString();
9
+ return new Date(Number(BigInt(nanos) / BigInt(1_000_000))).toISOString();
10
+ }
11
+ export function handleOtelTraces(req, res) {
12
+ const payload = req.body;
13
+ if (!payload.resourceSpans?.length) {
14
+ res.status(200).json({ message: "no spans" });
15
+ return;
16
+ }
17
+ let ingested = 0;
18
+ for (const resourceSpan of payload.resourceSpans) {
19
+ const resourceAttrs = resourceSpan.resource?.attributes;
20
+ const tool = getAttr(resourceAttrs, "service.name")
21
+ ?? getAttr(resourceAttrs, "telemetry.sdk.name")
22
+ ?? "unknown";
23
+ for (const scopeSpan of resourceSpan.scopeSpans ?? []) {
24
+ for (const span of scopeSpan.spans ?? []) {
25
+ const attrs = span.attributes;
26
+ const developer = getAttr(attrs, "user.id")
27
+ ?? getAttr(attrs, "developer")
28
+ ?? getAttr(attrs, "user.name")
29
+ ?? "unknown";
30
+ const filePath = getAttr(attrs, "file.path")
31
+ ?? getAttr(attrs, "code.filepath")
32
+ ?? undefined;
33
+ const eventType = getAttr(attrs, "event.type")
34
+ ?? span.name
35
+ ?? "span";
36
+ insertToolEvent({
37
+ tool: normalizeToolName(tool),
38
+ developer,
39
+ timestamp: parseTimestamp(span.startTimeUnixNano),
40
+ event_type: eventType,
41
+ file_path: filePath,
42
+ lines_added: parseInt(getAttr(attrs, "lines.added") ?? "0", 10),
43
+ lines_removed: parseInt(getAttr(attrs, "lines.removed") ?? "0", 10),
44
+ model: getAttr(attrs, "gen_ai.request.model") ?? getAttr(attrs, "model") ?? undefined,
45
+ tokens_in: parseInt(getAttr(attrs, "gen_ai.usage.input_tokens") ?? getAttr(attrs, "tokens.input") ?? "0", 10),
46
+ tokens_out: parseInt(getAttr(attrs, "gen_ai.usage.output_tokens") ?? getAttr(attrs, "tokens.output") ?? "0", 10),
47
+ cost_usd: parseFloat(getAttr(attrs, "cost.usd") ?? "0"),
48
+ session_id: getAttr(attrs, "session.id") ?? span.traceId,
49
+ raw_span: JSON.stringify(span),
50
+ });
51
+ ingested++;
52
+ }
53
+ }
54
+ }
55
+ res.status(200).json({ ingested });
56
+ }
57
+ function normalizeToolName(raw) {
58
+ const lower = raw.toLowerCase();
59
+ if (lower.includes("claude") || lower.includes("anthropic"))
60
+ return "claude-code";
61
+ if (lower.includes("cursor"))
62
+ return "cursor";
63
+ if (lower.includes("copilot"))
64
+ return "copilot";
65
+ return raw;
66
+ }
67
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.js","sourceRoot":"","sources":["../../src/server/otel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Request, Response } from "express";
2
+ export declare function handleCommitHook(req: Request, res: Response): void;
@@ -0,0 +1,22 @@
1
+ import { insertCommit, correlateCommit } from "../db/queries.js";
2
+ export function handleCommitHook(req, res) {
3
+ const payload = req.body;
4
+ if (!payload.hash || !payload.author || !payload.timestamp) {
5
+ res.status(400).json({ error: "Missing required fields: hash, author, timestamp" });
6
+ return;
7
+ }
8
+ insertCommit({
9
+ hash: payload.hash,
10
+ author: payload.author,
11
+ timestamp: payload.timestamp,
12
+ repo: payload.repo,
13
+ branch: payload.branch,
14
+ message: payload.message,
15
+ total_additions: payload.total_additions,
16
+ total_deletions: payload.total_deletions,
17
+ files: payload.files ?? [],
18
+ });
19
+ correlateCommit(payload.hash);
20
+ res.status(200).json({ stored: true, hash: payload.hash });
21
+ }
22
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAkBjE,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,GAAa;IAC1D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAqB,CAAC;IAE1C,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAC3D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kDAAkD,EAAE,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IAED,YAAY,CAAC;QACX,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;KAC3B,CAAC,CAAC;IAEH,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,86 @@
1
+ export type AgentId = "claude-code" | "cursor" | "copilot" | "devin" | "aider" | "unknown-ai" | "human";
2
+ export type ReviewStatus = "unreviewed" | "approved" | "modified";
3
+ export interface ProvenanceRecord {
4
+ commitHash: string;
5
+ timestamp: string;
6
+ agentId: AgentId;
7
+ modelVersion: string | null;
8
+ promptSummary: string | null;
9
+ filesChanged: string[];
10
+ linesAdded: number;
11
+ linesRemoved: number;
12
+ reviewStatus: ReviewStatus;
13
+ reviewer: string | null;
14
+ confidence: number;
15
+ signals: string[];
16
+ }
17
+ export interface ScanResult {
18
+ totalCommits: number;
19
+ aiCommits: number;
20
+ newRecords: number;
21
+ records: ProvenanceRecord[];
22
+ }
23
+ export interface StatsResult {
24
+ totalCommits: number;
25
+ aiCommits: number;
26
+ aiPercentage: number;
27
+ byAgent: Record<string, number>;
28
+ byMonth: Record<string, {
29
+ total: number;
30
+ ai: number;
31
+ }>;
32
+ }
33
+ export type Tool = "copilot" | "cursor" | "claude-code";
34
+ export interface ToolUsageRecord {
35
+ developer: string;
36
+ tool: Tool;
37
+ date: string;
38
+ suggestions: number;
39
+ acceptedLines: number;
40
+ rejectedLines: number;
41
+ costUsd: number;
42
+ }
43
+ export interface TeamMapping {
44
+ [team: string]: string[];
45
+ }
46
+ export interface DevMetrics {
47
+ developer: string;
48
+ tool: string;
49
+ totalSpend: number;
50
+ totalSuggestions: number;
51
+ totalAcceptedLines: number;
52
+ totalRejectedLines: number;
53
+ acceptanceRate: number;
54
+ days: number;
55
+ }
56
+ export interface ToolComparison {
57
+ tool: Tool;
58
+ totalSpend: number;
59
+ totalAcceptedLines: number;
60
+ costPerLine: number;
61
+ acceptanceRate: number;
62
+ devCount: number;
63
+ }
64
+ export interface TeamMetrics {
65
+ team: string;
66
+ totalSpend: number;
67
+ totalAcceptedLines: number;
68
+ totalRejectedLines: number;
69
+ acceptanceRate: number;
70
+ avgSpendPerDev: number;
71
+ devCount: number;
72
+ }
73
+ export interface Anomaly {
74
+ developer: string;
75
+ team: string;
76
+ tool: string;
77
+ spend: number;
78
+ teamMean: number;
79
+ teamStdDev: number;
80
+ deviations: number;
81
+ }
82
+ export interface PRRecord {
83
+ number: number;
84
+ author: string;
85
+ mergedAt: string | null;
86
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@james-wall/codegov",
3
+ "version": "0.1.0",
4
+ "description": "AI code governance — attribution, telemetry, and ROI for AI-assisted development",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "codegov": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/cli.js",
14
+ "test": "vitest run",
15
+ "prepublishOnly": "tsc"
16
+ },
17
+ "keywords": [
18
+ "ai",
19
+ "governance",
20
+ "provenance",
21
+ "telemetry",
22
+ "copilot",
23
+ "cursor",
24
+ "claude"
25
+ ],
26
+ "author": "James Wall",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "commander": "^14.0.3"
30
+ },
31
+ "optionalDependencies": {
32
+ "better-sqlite3": "^12.9.0",
33
+ "express": "^5.2.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/better-sqlite3": "^7.6.13",
37
+ "@types/express": "^5.0.6",
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^3.2.1"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "https://github.com/james-wall/codegov"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ }
54
+ }