@reneturcios/tracewatch 0.0.0-stage → 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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -2
  3. package/dist/adapters.d.ts +31 -0
  4. package/dist/adapters.js +128 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/blame.d.ts +15 -0
  7. package/dist/blame.js +165 -0
  8. package/dist/blame.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +236 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/core.d.ts +18 -0
  13. package/dist/core.js +19 -0
  14. package/dist/core.js.map +1 -0
  15. package/dist/diff.d.ts +21 -0
  16. package/dist/diff.js +131 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/git.d.ts +16 -0
  19. package/dist/git.js +80 -0
  20. package/dist/git.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +3 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/ingest.d.ts +54 -0
  25. package/dist/ingest.js +182 -0
  26. package/dist/ingest.js.map +1 -0
  27. package/dist/mcp.d.ts +1 -0
  28. package/dist/mcp.js +80 -0
  29. package/dist/mcp.js.map +1 -0
  30. package/dist/render.d.ts +13 -0
  31. package/dist/render.js +151 -0
  32. package/dist/render.js.map +1 -0
  33. package/dist/report.d.ts +3 -0
  34. package/dist/report.js +61 -0
  35. package/dist/report.js.map +1 -0
  36. package/dist/store.d.ts +31 -0
  37. package/dist/store.js +163 -0
  38. package/dist/store.js.map +1 -0
  39. package/dist/types.d.ts +215 -0
  40. package/dist/types.js +13 -0
  41. package/dist/types.js.map +1 -0
  42. package/dist/util.d.ts +16 -0
  43. package/dist/util.js +65 -0
  44. package/dist/util.js.map +1 -0
  45. package/dist/watch.d.ts +62 -0
  46. package/dist/watch.js +401 -0
  47. package/dist/watch.js.map +1 -0
  48. package/package.json +1 -6
  49. package/server.json +1 -0
package/dist/ingest.js ADDED
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Ingest: run the published tranche 1 tools against a target, or import
3
+ * report files, and store the result as one scan record plus one
4
+ * observation per finding.
5
+ *
6
+ * Tools are child processes resolved in this order: the
7
+ * TRACEWATCH_<TOOL>_CMD override, the tool on PATH, then
8
+ * `npx -y @reneturcios/<tool>`. Their stdout is collected through a pipe
9
+ * and parsed as JSON; a tool's exit code of 0 or 1 both mean "report
10
+ * produced" (the tools exit 1 when their verdict is not clean), anything
11
+ * else, or unparseable output, is recorded as a per-tool error on the
12
+ * scan record. One tool failing never blocks the others.
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { existsSync } from "node:fs";
16
+ import { basename, join, resolve } from "node:path";
17
+ import { adaptReport } from "./adapters.js";
18
+ import { headSha, remoteSlug } from "./git.js";
19
+ import { TOOLS } from "./types.js";
20
+ import { makeScanId } from "./util.js";
21
+ export function resolveToolCommand(tool) {
22
+ const override = process.env[`TRACEWATCH_${tool.toUpperCase()}_CMD`];
23
+ if (override) {
24
+ const parts = override.split(/\s+/).filter(Boolean);
25
+ if (parts.length > 0)
26
+ return { cmd: parts[0], args: parts.slice(1) };
27
+ }
28
+ for (const dir of (process.env.PATH ?? "").split(":")) {
29
+ if (dir && existsSync(join(dir, tool)))
30
+ return { cmd: tool, args: [] };
31
+ }
32
+ return { cmd: "npx", args: ["-y", `@reneturcios/${tool}`] };
33
+ }
34
+ export function runCapture(cmd, args, timeoutMs = 180000) {
35
+ return new Promise((resolvePromise) => {
36
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
37
+ let stdout = "";
38
+ let stderr = "";
39
+ let timedOut = false;
40
+ const timer = setTimeout(() => {
41
+ timedOut = true;
42
+ child.kill("SIGKILL");
43
+ }, timeoutMs);
44
+ child.stdout.on("data", (d) => {
45
+ if (stdout.length < 64 * 1024 * 1024)
46
+ stdout += d.toString("utf8");
47
+ });
48
+ child.stderr.on("data", (d) => {
49
+ if (stderr.length < 1024 * 1024)
50
+ stderr += d.toString("utf8");
51
+ });
52
+ child.on("error", (err) => {
53
+ clearTimeout(timer);
54
+ resolvePromise({ code: null, stdout, stderr: `${stderr}${err.message}`, timedOut });
55
+ });
56
+ child.on("close", (code) => {
57
+ clearTimeout(timer);
58
+ resolvePromise({ code, stdout, stderr, timedOut });
59
+ });
60
+ });
61
+ }
62
+ const versionCache = new Map();
63
+ export async function toolVersion(tool) {
64
+ const cached = versionCache.get(tool);
65
+ if (cached)
66
+ return cached;
67
+ const { cmd, args } = resolveToolCommand(tool);
68
+ const res = await runCapture(cmd, [...args, "--version"], 120000);
69
+ const first = res.stdout.split("\n").map((s) => s.trim()).find((s) => /\d+\.\d+\.\d+/.test(s));
70
+ const version = first ? (first.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? first) : "unknown";
71
+ versionCache.set(tool, version);
72
+ return version;
73
+ }
74
+ const SCAN_ARGS = {
75
+ hookcheck: (t) => ["scan", t, "--json"],
76
+ actionpin: (t) => ["audit", t, "--json"],
77
+ injectcheck: (t) => ["scan", t, "--json"],
78
+ };
79
+ export async function runTool(tool, target) {
80
+ const version = await toolVersion(tool);
81
+ const { cmd, args } = resolveToolCommand(tool);
82
+ const res = await runCapture(cmd, [...args, ...SCAN_ARGS[tool](target)]);
83
+ const fail = (error) => ({
84
+ tool,
85
+ info: { version, verdict: "ERROR", counts: {}, status: "error", error },
86
+ adapted: null,
87
+ });
88
+ if (res.timedOut)
89
+ return fail(`${tool} timed out`);
90
+ const start = res.stdout.indexOf("{");
91
+ if (start === -1)
92
+ return fail(`${tool} produced no JSON report (exit ${res.code ?? "spawn error"})`);
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(res.stdout.slice(start));
96
+ }
97
+ catch {
98
+ return fail(`${tool} report did not parse as JSON (exit ${res.code ?? "spawn error"})`);
99
+ }
100
+ const adapted = adaptReport(tool, parsed, target);
101
+ return {
102
+ tool,
103
+ info: { version, verdict: adapted.verdict, counts: adapted.counts, status: "ok" },
104
+ adapted,
105
+ };
106
+ }
107
+ /** Run injectcheck against a single text file (watch mode issue/file scans). */
108
+ export async function runInjectcheckFile(file) {
109
+ const { cmd, args } = resolveToolCommand("injectcheck");
110
+ const res = await runCapture(cmd, [...args, "file", file, "--json"], 120000);
111
+ const start = res.stdout.indexOf("{");
112
+ if (start === -1)
113
+ return null;
114
+ try {
115
+ return adaptReport("injectcheck", JSON.parse(res.stdout.slice(start)), file);
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ }
121
+ /** Stamp adapted reports into the store as one scan record. Exported for watch mode. */
122
+ export function recordScan(store, repo, source, adaptedList, infos, extra) {
123
+ const at = extra.at ?? new Date().toISOString();
124
+ const flat = adaptedList.flatMap((a) => a.findings);
125
+ const scanId = makeScanId(repo, at, flat.map((f) => f.findingId));
126
+ const record = {
127
+ scanId,
128
+ repo,
129
+ at,
130
+ source,
131
+ path: extra.path,
132
+ github: extra.github,
133
+ headSha: extra.headSha ?? null,
134
+ tools: infos,
135
+ warnings: [...(extra.warnings ?? []), ...adaptedList.flatMap((a) => a.warnings)],
136
+ };
137
+ const observations = flat.map((f) => ({ ...f, scanId, repo }));
138
+ store.appendScan(record);
139
+ store.appendFindings(observations);
140
+ return { record, observations, runs: [] };
141
+ }
142
+ export async function ingestPath(store, target, opts = {}) {
143
+ const abs = resolve(target);
144
+ const github = opts.github ?? (await remoteSlug(abs)) ?? undefined;
145
+ const repo = opts.name ?? github ?? basename(abs);
146
+ const head = await headSha(abs);
147
+ const wanted = opts.tools ?? TOOLS;
148
+ const runs = await Promise.all(wanted.map((t) => runTool(t, abs)));
149
+ const infos = {};
150
+ const adaptedList = [];
151
+ const warnings = [];
152
+ for (const run of runs) {
153
+ infos[run.tool] = run.info;
154
+ if (run.adapted)
155
+ adaptedList.push(run.adapted);
156
+ if (run.info.status === "error")
157
+ warnings.push(run.info.error ?? `${run.tool} failed`);
158
+ }
159
+ const result = recordScan(store, repo, opts.source ?? "run", adaptedList, infos, {
160
+ path: abs,
161
+ github,
162
+ headSha: head,
163
+ warnings,
164
+ });
165
+ return { ...result, runs };
166
+ }
167
+ export function importReports(store, opts) {
168
+ const root = opts.path ? resolve(opts.path) : "";
169
+ const adaptedList = [];
170
+ const infos = {};
171
+ for (const { tool, report } of opts.reports) {
172
+ const adapted = adaptReport(tool, report, root);
173
+ adaptedList.push(adapted);
174
+ infos[tool] = { version: "imported", verdict: adapted.verdict, counts: adapted.counts, status: "ok" };
175
+ }
176
+ return recordScan(store, opts.repo, "import", adaptedList, infos, {
177
+ path: opts.path ? resolve(opts.path) : undefined,
178
+ github: opts.github,
179
+ at: opts.at,
180
+ });
181
+ }
182
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"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"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/mcp.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * tracewatch MCP server. Tools: ingest_scan, diff_scans,
3
+ * finding_provenance, repo_health, watch_status, list_findings,
4
+ * explain_finding. The store is TRACEWATCH_STORE or ~/.tracewatch.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { z } from "zod";
9
+ import { VERSION, Store, defaultStoreDir, diffScans, explainFinding, ingestPath, repoHealth, } from "./core.js";
10
+ const store = new Store(defaultStoreDir());
11
+ const server = new McpServer({ name: "tracewatch", version: VERSION });
12
+ function text(value) {
13
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
14
+ }
15
+ server.tool("ingest_scan", "Run hookcheck, actionpin, and injectcheck against a local repo path and store the results as a timestamped scan record with stable finding IDs. Returns the scan record and every finding observation. Use this to start or refresh tracking for a repo.", {
16
+ path: z.string().describe("Local path of the repo or directory to scan"),
17
+ name: z.string().optional().describe("Repo key to file the scan under (default: GitHub slug from the git remote, else the directory name)"),
18
+ tools: z.array(z.enum(["hookcheck", "actionpin", "injectcheck"])).optional().describe("Subset of tools to run (default: all three)"),
19
+ }, async ({ path, name, tools }) => {
20
+ const result = await ingestPath(store, path, { name, tools });
21
+ return text({ scan: result.record, findings: result.observations });
22
+ });
23
+ server.tool("diff_scans", "Diff two scans of a repo (default: the last two). Reports NEW, RESOLVED, and CHANGED findings, verdict moves, and action ref events with both refs spelled out, including pin regressions (PINNED to MUTABLE or BRANCH) and bare ref moves on pinned actions.", {
24
+ repo: z.string().describe("Repo key used at ingest"),
25
+ from: z.string().optional().describe("Earlier scanId (default: the scan before `to`)"),
26
+ to: z.string().optional().describe("Later scanId (default: the latest scan)"),
27
+ }, async ({ repo, from, to }) => text(diffScans(store, repo, from, to)));
28
+ server.tool("finding_provenance", "Root-cause provenance for one finding: the introducing commit from git pickaxe on the finding's content, the current-line commit from git blame, the associated GitHub PR when the API answers, and the finding's ref history from the scan store. Confirmed evidence, inferences, and unestablished items are reported separately; nothing is guessed.", {
29
+ repo: z.string().describe("Repo key used at ingest"),
30
+ findingId: z.string().describe("Full findingId (tw_...) or a prefix of at least 8 characters"),
31
+ path: z.string().optional().describe("Local clone path to run git evidence against (default: the path recorded at ingest)"),
32
+ }, async ({ repo, findingId, path }) => {
33
+ const explained = await explainFinding(store, repo, findingId, { path });
34
+ if (!explained)
35
+ return text({ error: `finding "${findingId}" not found on repo "${repo}"` });
36
+ return text(explained.provenance);
37
+ });
38
+ server.tool("repo_health", "Health report for a repo across all its scans: verdict trend, open findings by tool and severity, resolved count, mean time open for resolved findings, all action ref events, and watch detection latency stats (count, median, max time-to-detect).", { repo: z.string().describe("Repo key used at ingest") }, async ({ repo }) => text(repoHealth(store, repo)));
39
+ server.tool("watch_status", "Summarize the tracewatch store: every tracked repo with its scan count, last scan time, open finding count, detection count, and median time-to-detect. Use this to see what is being watched and tracked before drilling into one repo.", {}, async () => {
40
+ const repos = store.repos().map((repo) => {
41
+ const health = repoHealth(store, repo);
42
+ return {
43
+ repo,
44
+ scans: health.scanCount,
45
+ lastScanAt: health.lastScanAt,
46
+ openFindings: health.open.total,
47
+ detections: health.detections.count,
48
+ medianTimeToDetectMs: health.detections.medianMs,
49
+ };
50
+ });
51
+ return text({ storeDir: store.dir, repos });
52
+ });
53
+ server.tool("list_findings", "List tracked findings for a repo with derived status. Filter by tool, severity, and status (open, resolved, or all; default open). Each entry carries the stable findingId usable with finding_provenance and explain_finding.", {
54
+ repo: z.string().describe("Repo key used at ingest"),
55
+ tool: z.enum(["hookcheck", "actionpin", "injectcheck"]).optional(),
56
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).optional(),
57
+ status: z.enum(["open", "resolved", "all"]).optional(),
58
+ }, async ({ repo, tool, severity, status }) => {
59
+ const tracked = store
60
+ .trackedFindings(repo)
61
+ .filter((t) => (tool ? t.latest.tool === tool : true))
62
+ .filter((t) => (severity ? t.latest.severity === severity : true))
63
+ .filter((t) => (status && status !== "all" ? t.status === status : status === "all" ? true : t.status === "open"));
64
+ return text(tracked);
65
+ });
66
+ server.tool("explain_finding", "The full evidence bundle for one finding, written to act on: the tracked finding (status, first and last seen, content versions), its scan history is summarized in the provenance record, and the provenance itself (introducing commit, PR, ref history, confirmed versus inferred versus not established).", {
67
+ repo: z.string().describe("Repo key used at ingest"),
68
+ findingId: z.string().describe("Full findingId (tw_...) or a prefix of at least 8 characters"),
69
+ path: z.string().optional().describe("Local clone path for git evidence (default: the path recorded at ingest)"),
70
+ }, async ({ repo, findingId, path }) => {
71
+ const explained = await explainFinding(store, repo, findingId, { path });
72
+ if (!explained)
73
+ return text({ error: `finding "${findingId}" not found on repo "${repo}"` });
74
+ return text(explained);
75
+ });
76
+ async function main() {
77
+ await server.connect(new StdioServerTransport());
78
+ }
79
+ void main();
80
+ //# sourceMappingURL=mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,GACX,MAAM,WAAW,CAAC;AAEnB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;AAC3C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEvE,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,IAAI,CACT,aAAa,EACb,0PAA0P,EAC1P;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;IACxE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qGAAqG,CAAC;IAC3I,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;CACrI,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE;IAC9B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;AACtE,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,YAAY,EACZ,+PAA+P,EAC/P;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACpD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IACtF,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;CAC9E,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CACrE,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,yVAAyV,EACzV;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACpD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC;IAC9F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qFAAqF,CAAC;CAC5H,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE;IAClC,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,SAAS,wBAAwB,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7F,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,aAAa,EACb,uPAAuP,EACvP,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,EACxD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAClD,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,cAAc,EACd,0OAA0O,EAC1O,EAAE,EACF,KAAK,IAAI,EAAE;IACT,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACvC,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,MAAM,CAAC,SAAS;YACvB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK;YAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK;YACnC,oBAAoB,EAAE,MAAM,CAAC,UAAU,CAAC,QAAQ;SACjD,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9C,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,eAAe,EACf,gOAAgO,EAChO;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACpD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1E,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE;CACvD,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;IACzC,MAAM,OAAO,GAAG,KAAK;SAClB,eAAe,CAAC,IAAI,CAAC;SACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SACrD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SACjE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;IACrH,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,+SAA+S,EAC/S;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACpD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC;IAC9F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;CACjH,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE;IAClC,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,SAAS,wBAAwB,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7F,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC;AACzB,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Human renderers. Deterministic: every line derives from store data,
3
+ * sorted where the underlying structure is a set, and no wall-clock
4
+ * "generated at" text appears anywhere, so identical store state renders
5
+ * byte-identical output (acceptance T6).
6
+ */
7
+ import type { IngestResult } from "./ingest.js";
8
+ import { type HealthReport, type Provenance, type ScanDiff, type TrackedFinding } from "./types.js";
9
+ export declare function renderScanSummary(result: IngestResult): string;
10
+ export declare function renderDiff(diff: ScanDiff): string;
11
+ export declare function renderProvenance(prov: Provenance): string;
12
+ export declare function renderHealth(h: HealthReport): string;
13
+ export declare function renderFindings(repo: string, tracked: TrackedFinding[]): string;
package/dist/render.js ADDED
@@ -0,0 +1,151 @@
1
+ import { SEVERITY_ORDER } from "./types.js";
2
+ import { formatDuration } from "./util.js";
3
+ export function renderScanSummary(result) {
4
+ const r = result.record;
5
+ const lines = [];
6
+ lines.push(`scan ${r.scanId} repo ${r.repo} at ${r.at} (source: ${r.source})`);
7
+ if (r.headSha)
8
+ lines.push(`head: ${r.headSha}`);
9
+ const tools = Object.entries(r.tools).sort(([a], [b]) => a.localeCompare(b));
10
+ if (tools.length === 0)
11
+ lines.push("tools: none recorded");
12
+ for (const [tool, info] of tools) {
13
+ if (!info)
14
+ continue;
15
+ const counts = Object.entries(info.counts)
16
+ .sort(([a], [b]) => a.localeCompare(b))
17
+ .map(([k, v]) => `${k}=${v}`)
18
+ .join(" ");
19
+ lines.push(` ${tool} ${info.version}: ${info.verdict}${info.status === "error" ? ` (ERROR: ${info.error ?? "failed"})` : ""}${counts ? ` [${counts}]` : ""}`);
20
+ }
21
+ lines.push(`findings recorded: ${result.observations.length}`);
22
+ for (const w of r.warnings)
23
+ lines.push(`warning: ${w}`);
24
+ return lines.join("\n");
25
+ }
26
+ function obsLine(o) {
27
+ const loc = o.line !== undefined ? `${o.file}:${o.line}` : o.file;
28
+ return ` [${o.severity}] ${o.findingId} ${o.tool}/${o.rule} ${loc} - ${o.detail}`;
29
+ }
30
+ export function renderDiff(diff) {
31
+ const lines = [];
32
+ lines.push(`diff repo ${diff.repo}`);
33
+ lines.push(` from: ${diff.from ? `${diff.from.scanId} at ${diff.from.at}` : "(no earlier scan)"}`);
34
+ lines.push(` to: ${diff.to.scanId} at ${diff.to.at}`);
35
+ if (diff.verdictMoves.length > 0) {
36
+ lines.push("verdict moves:");
37
+ for (const m of diff.verdictMoves)
38
+ lines.push(` ${m.tool}: ${m.from ?? "(none)"} -> ${m.to ?? "(none)"}`);
39
+ }
40
+ lines.push(`new findings (${diff.added.length}):`);
41
+ for (const o of diff.added)
42
+ lines.push(obsLine(o));
43
+ lines.push(`resolved findings (${diff.resolved.length}):`);
44
+ for (const o of diff.resolved)
45
+ lines.push(obsLine(o));
46
+ lines.push(`changed findings (${diff.changed.length}):`);
47
+ for (const c of diff.changed) {
48
+ lines.push(obsLine(c.after));
49
+ for (const change of c.changes)
50
+ lines.push(` ${change}`);
51
+ }
52
+ if (diff.refEvents.length > 0) {
53
+ lines.push(`ref events (${diff.refEvents.length}):`);
54
+ for (const ev of diff.refEvents) {
55
+ lines.push(` ${ev.kind}: ${ev.action} in ${ev.file}: ${ev.oldRef} -> ${ev.newRef} (${ev.oldClassification} -> ${ev.newClassification})`);
56
+ }
57
+ }
58
+ lines.push(`unchanged: ${diff.unchangedCount}`);
59
+ if (diff.added.length === 0 && diff.resolved.length === 0 && diff.changed.length === 0) {
60
+ lines.push("no changes between these scans");
61
+ }
62
+ return lines.join("\n");
63
+ }
64
+ export function renderProvenance(prov) {
65
+ const o = prov.finding;
66
+ const lines = [];
67
+ lines.push(`provenance for ${o.findingId} (${o.tool}/${o.rule})`);
68
+ lines.push(` repo: ${prov.repo} status: ${prov.status}`);
69
+ lines.push(` location: ${o.line !== undefined ? `${o.file}:${o.line}` : o.file}`);
70
+ lines.push(` detail: ${o.detail}`);
71
+ lines.push(` first seen: ${prov.firstSeenAt} last seen: ${prov.lastSeenAt}`);
72
+ const c = prov.confirmed;
73
+ lines.push("confirmed (evidence):");
74
+ if (c.introducingCommit) {
75
+ lines.push(` introducing commit: ${c.introducingCommit.sha} by ${c.introducingCommit.author} on ${c.introducingCommit.date}`);
76
+ lines.push(` subject: ${c.introducingCommit.subject}`);
77
+ }
78
+ if (c.currentLineCommit) {
79
+ lines.push(` current line commit (git blame): ${c.currentLineCommit.sha} by ${c.currentLineCommit.author} on ${c.currentLineCommit.date}`);
80
+ lines.push(` subject: ${c.currentLineCommit.subject}`);
81
+ }
82
+ if (c.pullRequests) {
83
+ if (c.pullRequests.length === 0)
84
+ lines.push(" pull requests: none listed by the GitHub API for the introducing commit");
85
+ for (const pr of c.pullRequests)
86
+ lines.push(` pull request: #${pr.number} ${pr.title} (${pr.state}) ${pr.url}`);
87
+ }
88
+ if (c.refHistory) {
89
+ lines.push(" ref history (from the scan store):");
90
+ for (const h of c.refHistory) {
91
+ lines.push(` ${h.ref} (${h.classification}) first ${h.firstAt} [${h.firstScanId}] last ${h.lastAt} [${h.lastScanId}]`);
92
+ }
93
+ }
94
+ if (c.source)
95
+ lines.push(` content source: ${c.source.kind} ${c.source.ref}${c.source.url ? ` ${c.source.url}` : ""}`);
96
+ if (!c.introducingCommit && !c.currentLineCommit && !c.pullRequests && !c.refHistory && !c.source) {
97
+ lines.push(" (none)");
98
+ }
99
+ if (prov.inferred.length > 0) {
100
+ lines.push("inferred (circumstantial, labeled as such):");
101
+ for (const s of prov.inferred)
102
+ lines.push(` ${s}`);
103
+ }
104
+ if (prov.notEstablished.length > 0) {
105
+ lines.push("not established:");
106
+ for (const s of prov.notEstablished)
107
+ lines.push(` ${s}`);
108
+ }
109
+ return lines.join("\n");
110
+ }
111
+ export function renderHealth(h) {
112
+ const lines = [];
113
+ lines.push(`health report: ${h.repo}`);
114
+ lines.push(`scans: ${h.scanCount}${h.firstScanAt ? ` (${h.firstScanAt} to ${h.lastScanAt})` : ""}`);
115
+ lines.push("verdict trend:");
116
+ for (const t of h.verdictTrend) {
117
+ const vs = Object.entries(t.verdicts)
118
+ .sort(([a], [b]) => a.localeCompare(b))
119
+ .map(([k, v]) => `${k}=${v}`)
120
+ .join(" ");
121
+ lines.push(` ${t.at} ${t.scanId} ${vs}`);
122
+ }
123
+ lines.push(`open findings: ${h.open.total}`);
124
+ for (const tool of Object.keys(h.open.byTool).sort())
125
+ lines.push(` by tool ${tool}: ${h.open.byTool[tool]}`);
126
+ for (const sev of SEVERITY_ORDER) {
127
+ if (h.open.bySeverity[sev])
128
+ lines.push(` by severity ${sev}: ${h.open.bySeverity[sev]}`);
129
+ }
130
+ lines.push(`resolved findings: ${h.resolvedCount}`);
131
+ lines.push(`mean time open (resolved): ${h.meanTimeOpenMs === null ? "n/a (nothing resolved yet)" : formatDuration(h.meanTimeOpenMs)}`);
132
+ lines.push(`ref events: ${h.refEvents.length}`);
133
+ for (const ev of h.refEvents) {
134
+ lines.push(` ${ev.at} ${ev.kind}: ${ev.action} in ${ev.file}: ${ev.oldRef} -> ${ev.newRef}`);
135
+ }
136
+ lines.push(`detections: ${h.detections.count} (median time-to-detect ${formatDuration(h.detections.medianMs)}, max ${formatDuration(h.detections.maxMs)})`);
137
+ for (const [kind, s] of Object.entries(h.detections.byKind).sort(([a], [b]) => a.localeCompare(b))) {
138
+ lines.push(` ${kind}: ${s.count} (median ${formatDuration(s.medianMs)}, max ${formatDuration(s.maxMs)})`);
139
+ }
140
+ return lines.join("\n");
141
+ }
142
+ export function renderFindings(repo, tracked) {
143
+ const lines = [`findings for ${repo}: ${tracked.length}`];
144
+ for (const t of tracked) {
145
+ const o = t.latest;
146
+ const loc = o.line !== undefined ? `${o.file}:${o.line}` : o.file;
147
+ lines.push(`[${t.status}] ${o.findingId} ${o.tool}/${o.rule} (${o.severity}) ${loc} first ${t.firstSeenAt} last ${t.lastSeenAt}${t.versions > 1 ? ` versions ${t.versions}` : ""} - ${o.detail}`);
148
+ }
149
+ return lines.join("\n");
150
+ }
151
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Store } from "./store.js";
2
+ import { type HealthReport } from "./types.js";
3
+ export declare function repoHealth(store: Store, repo: string): HealthReport;
package/dist/report.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Health report: what the store knows about one repo over time.
3
+ * Verdict trend, open findings by tool and severity, resolution speed
4
+ * (mean time open, first-seen scan to the scan where a finding was
5
+ * first absent), all ref events, and watch detection latency stats.
6
+ * Pure derivation from the store, sorted everywhere, so identical store
7
+ * state renders identical reports.
8
+ */
9
+ import { allRefEvents } from "./diff.js";
10
+ import { TOOLS } from "./types.js";
11
+ import { median } from "./util.js";
12
+ export function repoHealth(store, repo) {
13
+ const scans = store.scans(repo);
14
+ const tracked = store.trackedFindings(repo);
15
+ const open = tracked.filter((t) => t.status === "open");
16
+ const resolved = tracked.filter((t) => t.status === "resolved");
17
+ const byTool = {};
18
+ const bySeverity = {};
19
+ for (const t of open) {
20
+ byTool[t.latest.tool] = (byTool[t.latest.tool] ?? 0) + 1;
21
+ bySeverity[t.latest.severity] = (bySeverity[t.latest.severity] ?? 0) + 1;
22
+ }
23
+ const openDurations = [];
24
+ for (const t of resolved) {
25
+ if (!t.resolvedAt)
26
+ continue;
27
+ const ms = Date.parse(t.resolvedAt) - Date.parse(t.firstSeenAt);
28
+ if (Number.isFinite(ms) && ms >= 0)
29
+ openDurations.push(ms);
30
+ }
31
+ const meanTimeOpenMs = openDurations.length > 0 ? Math.round(openDurations.reduce((a, b) => a + b, 0) / openDurations.length) : null;
32
+ const detections = store.detections(repo);
33
+ const ttd = detections.map((d) => d.timeToDetectMs);
34
+ const byKind = {};
35
+ for (const kind of [...new Set(detections.map((d) => d.kind))].sort()) {
36
+ const vals = detections.filter((d) => d.kind === kind).map((d) => d.timeToDetectMs);
37
+ byKind[kind] = { count: vals.length, medianMs: median(vals), maxMs: vals.length ? Math.max(...vals) : null };
38
+ }
39
+ return {
40
+ repo,
41
+ scanCount: scans.length,
42
+ firstScanAt: scans[0]?.at ?? null,
43
+ lastScanAt: scans[scans.length - 1]?.at ?? null,
44
+ verdictTrend: scans.map((s) => ({
45
+ scanId: s.scanId,
46
+ at: s.at,
47
+ verdicts: Object.fromEntries(TOOLS.filter((t) => s.tools[t]).map((t) => [t, s.tools[t]?.verdict ?? ""])),
48
+ })),
49
+ open: { total: open.length, byTool, bySeverity },
50
+ resolvedCount: resolved.length,
51
+ meanTimeOpenMs,
52
+ refEvents: allRefEvents(store, repo),
53
+ detections: {
54
+ count: detections.length,
55
+ medianMs: median(ttd),
56
+ maxMs: ttd.length ? Math.max(...ttd) : null,
57
+ byKind,
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { AlertRecord, DetectionRecord, FindingObs, ScanRecord, TrackedFinding } from "./types.js";
2
+ export declare function defaultStoreDir(): string;
3
+ export declare class Store {
4
+ readonly dir: string;
5
+ constructor(dir: string);
6
+ private file;
7
+ private ensure;
8
+ private append;
9
+ appendScan(rec: ScanRecord): void;
10
+ appendFindings(obs: FindingObs[]): void;
11
+ appendDetection(rec: DetectionRecord): void;
12
+ appendAlert(rec: AlertRecord): void;
13
+ scans(repo?: string): ScanRecord[];
14
+ findings(repo?: string): FindingObs[];
15
+ detections(repo?: string): DetectionRecord[];
16
+ alerts(repo?: string): AlertRecord[];
17
+ repos(): string[];
18
+ scanById(scanId: string): ScanRecord | undefined;
19
+ latestScan(repo: string): ScanRecord | undefined;
20
+ observations(scanId: string): FindingObs[];
21
+ /**
22
+ * Derive every tracked finding for a repo: status, first/last seen,
23
+ * resolution time, and content version count. Deterministic: output is
24
+ * sorted by identity, and scan order is store append order.
25
+ */
26
+ trackedFindings(repo: string): TrackedFinding[];
27
+ /** Find one tracked finding by full or prefix findingId, or by identity. */
28
+ findFinding(repo: string, idOrPrefix: string): TrackedFinding | undefined;
29
+ readWatchState<T>(): T | null;
30
+ writeWatchState(state: unknown): void;
31
+ }