🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

tracebug

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tracebug - npm Package Compare versions

Comparing version
1.8.0
to
1.9.0
+337
-29
bin.mjs

@@ -12,2 +12,167 @@ #!/usr/bin/env node

// cli/source-map.ts
import * as fs from "fs";
import * as path from "path";
function parseStackFrames(stack) {
const frames = [];
for (const line of String(stack || "").split("\n")) {
const m = V8_FRAME.exec(line) || FF_FRAME.exec(line);
if (!m) continue;
frames.push({
fn: (m[1] || "").trim() || "(anonymous)",
file: m[2],
line: parseInt(m[3], 10),
column: parseInt(m[4], 10),
raw: line.trim()
});
}
return frames;
}
function decodeVlq(s, pos) {
let result = 0;
let shift = 0;
let digit;
do {
digit = B64_MAP[s[pos++]];
if (digit === void 0) throw new Error("bad VLQ");
result += (digit & 31) << shift;
shift += 5;
} while (digit & 32);
const negative = result & 1;
result >>= 1;
return [negative ? -result : result, pos];
}
function decodeMappings(map) {
const cached = _decodeCache.get(map);
if (cached) return cached;
const lines = map.mappings.split(";");
const out = [];
let srcIdx = 0, srcLine = 0, srcCol = 0, nameIdx = 0;
for (const segs of lines) {
const lineSegs = [];
let genCol = 0, pos = 0;
while (pos < segs.length) {
if (segs[pos] === ",") {
pos++;
continue;
}
let v;
try {
[v, pos] = decodeVlq(segs, pos);
} catch {
break;
}
genCol += v;
if (pos < segs.length && segs[pos] !== ",") {
try {
[v, pos] = decodeVlq(segs, pos);
srcIdx += v;
[v, pos] = decodeVlq(segs, pos);
srcLine += v;
[v, pos] = decodeVlq(segs, pos);
srcCol += v;
let hasName = false;
if (pos < segs.length && segs[pos] !== ",") {
[v, pos] = decodeVlq(segs, pos);
nameIdx += v;
hasName = true;
}
lineSegs.push({ genCol, srcIdx, srcLine, srcCol, nameIdx, hasName });
} catch {
break;
}
}
}
out.push(lineSegs);
}
_decodeCache.set(map, out);
return out;
}
function resolvePosition(map, genLine, genColumn) {
const decoded = decodeMappings(map);
const targetLine = genLine - 1;
if (targetLine < 0 || targetLine >= decoded.length) return null;
let best = null;
for (const s of decoded[targetLine]) {
if (s.genCol <= genColumn - 1) best = s;
else {
if (best === null) best = s;
break;
}
}
if (!best) return null;
return {
source: (map.sourceRoot ? map.sourceRoot.replace(/\/?$/, "/") : "") + (map.sources[best.srcIdx] ?? "?"),
line: best.srcLine + 1,
column: best.srcCol,
name: best.hasName ? map.names?.[best.nameIdx] : void 0
};
}
function findMapFile(searchDir, bundleBasename) {
const target = bundleBasename + ".map";
const walk = (dir, depth) => {
if (depth > MAP_SCAN_DEPTH) return null;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
return null;
}
for (const e of entries) {
if (!e.isDirectory() && e.name === target) return path.join(dir, e.name);
}
for (const e of entries) {
if (e.isDirectory() && !MAP_SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) {
const hit = walk(path.join(dir, e.name), depth + 1);
if (hit) return hit;
}
}
return null;
};
return walk(searchDir, 0);
}
function resolveStackWithMaps(stack, searchDir) {
const frames = parseStackFrames(stack);
return frames.map((frame) => {
const basename3 = path.basename(frame.file.split("?")[0].split("#")[0]);
if (!/\.(m?js|cjs)$/.test(basename3)) return frame;
const pathKey = searchDir + "\0" + basename3;
let mapPath = _mapPathCache.get(pathKey);
if (mapPath === void 0) {
mapPath = findMapFile(searchDir, basename3);
_mapPathCache.set(pathKey, mapPath);
}
if (!mapPath) return frame;
let map = _mapDataCache.get(mapPath);
if (map === void 0) {
try {
const parsed = JSON.parse(fs.readFileSync(mapPath, "utf8"));
map = parsed && parsed.version === 3 && typeof parsed.mappings === "string" ? parsed : null;
} catch {
map = null;
}
_mapDataCache.set(mapPath, map);
}
if (!map) return frame;
const original = resolvePosition(map, frame.line, frame.column);
return original ? { ...frame, original, mapFile: mapPath } : { ...frame, mapFile: mapPath };
});
}
var V8_FRAME, FF_FRAME, B64, B64_MAP, _decodeCache, MAP_SKIP_DIRS, MAP_SCAN_DEPTH, _mapPathCache, _mapDataCache;
var init_source_map = __esm({
"cli/source-map.ts"() {
"use strict";
V8_FRAME = /^\s*at\s+(?:(.*?)\s+\()?((?:https?|file|webpack):\/\/[^\s)]+|[^\s)]+?):(\d+):(\d+)\)?\s*$/;
FF_FRAME = /^\s*(.*?)@((?:https?|file):\/\/[^\s]+|[^\s]+?):(\d+):(\d+)\s*$/;
B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
B64_MAP = {};
for (let i = 0; i < B64.length; i++) B64_MAP[B64[i]] = i;
_decodeCache = /* @__PURE__ */ new WeakMap();
MAP_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next/cache", "coverage"]);
MAP_SCAN_DEPTH = 6;
_mapPathCache = /* @__PURE__ */ new Map();
_mapDataCache = /* @__PURE__ */ new Map();
}
});
// cli/mcp-server.ts

@@ -29,9 +194,12 @@ var mcp_server_exports = {};

toolGetConsoleErrors: () => toolGetConsoleErrors,
toolGetFixContext: () => toolGetFixContext,
toolGetNetworkActivity: () => toolGetNetworkActivity,
toolGetPlaywrightTest: () => toolGetPlaywrightTest,
toolGetReproSteps: () => toolGetReproSteps,
toolGetScreenshot: () => toolGetScreenshot,
toolListBugReports: () => toolListBugReports
toolListBugReports: () => toolListBugReports,
toolResolveStack: () => toolResolveStack
});
import * as fs from "fs";
import * as path from "path";
import * as fs2 from "fs";
import * as path2 from "path";
import * as os from "os";

@@ -41,3 +209,3 @@ function parseReportFile(filePath) {

try {
raw = fs.readFileSync(filePath, "utf8");
raw = fs2.readFileSync(filePath, "utf8");
} catch {

@@ -70,3 +238,3 @@ return null;

try {
entries = fs.readdirSync(dir, { withFileTypes: true });
entries = fs2.readdirSync(dir, { withFileTypes: true });
} catch {

@@ -77,3 +245,3 @@ return [];

for (const entry of entries) {
const full = path.join(dir, entry.name);
const full = path2.join(dir, entry.name);
if (entry.isDirectory()) {

@@ -94,3 +262,3 @@ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {

const home = os.homedir();
raw.push(path.join(home, "Downloads"), path.join(home, "Desktop"));
raw.push(path2.join(home, "Downloads"), path2.join(home, "Desktop"));
} catch {

@@ -103,3 +271,3 @@ }

try {
key = path.resolve(d);
key = path2.resolve(d);
} catch {

@@ -111,3 +279,3 @@ continue;

try {
if (fs.statSync(d).isDirectory()) out.push(d);
if (fs2.statSync(d).isDirectory()) out.push(d);
} catch {

@@ -122,3 +290,3 @@ }

const add = (f) => {
const key = path.resolve(f);
const key = path2.resolve(f);
if (!seen.has(key)) {

@@ -130,5 +298,5 @@ seen.add(key);

for (const f of scanReportFiles(baseDir)) add(f);
const baseKey = path.resolve(baseDir);
const baseKey = path2.resolve(baseDir);
for (const dir of knownReportDirs(baseDir)) {
if (path.resolve(dir) === baseKey) continue;
if (path2.resolve(dir) === baseKey) continue;
for (const f of scanReportFiles(dir, 0, { maxDepth: 1, nameFilter: (n) => TB_EXPORT_NAME_RE.test(n) })) add(f);

@@ -139,7 +307,7 @@ }

function displayReportName(baseDir, file) {
const rel = path.relative(baseDir, file);
return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? rel : path.basename(file);
const rel = path2.relative(baseDir, file);
return rel && !rel.startsWith("..") && !path2.isAbsolute(rel) ? rel : path2.basename(file);
}
function requireReport(baseDir, file) {
const directCandidates = path.isAbsolute(file) ? [file] : [path.join(baseDir, file), path.resolve(process.cwd(), file)];
const directCandidates = path2.isAbsolute(file) ? [file] : [path2.join(baseDir, file), path2.resolve(process.cwd(), file)];
for (const direct of directCandidates) {

@@ -151,6 +319,6 @@ const p = parseReportFile(direct);

const query = file.toLowerCase();
let match = candidates.find((c) => path.basename(c).toLowerCase() === query);
let match = candidates.find((c) => path2.basename(c).toLowerCase() === query);
if (!match) {
match = candidates.find((c) => {
if (path.basename(c).toLowerCase().includes(query)) return true;
if (path2.basename(c).toLowerCase().includes(query)) return true;
const p = parseReportFile(c);

@@ -193,4 +361,20 @@ return p !== null && p.meta.title.toLowerCase().includes(query);

}
if (p.playwrightTest) {
steps.push(
"[HIGH] get_playwright_test \u2014 this report includes a generated Playwright spec that REPLAYS the session and asserts the captured failure is gone. Save it, run it to reproduce (red), then use it to verify your fix (green)."
);
}
if ((p.elementAnnotations ?? []).length > 0) {
const n = p.elementAnnotations.length;
steps.push(
`[HIGH] This report carries ${n} element annotation${n === 1 ? "" : "s"} with computed-style evidence (included in get_bug_report) \u2014 for visual bugs, diff the captured typography/colors/spacing against the design tokens or CSS in this codebase.`
);
}
if ((p.consoleErrors ?? []).some((e) => e.stack) || (p.consoleLogs ?? []).some((e) => e.stack)) {
steps.push(
"[MEDIUM] resolve_stack \u2014 maps minified stack frames to original source files/lines using .map files found in this repo (run from the project that built the app)."
);
}
steps.push(
"Finally: cross-reference the findings with the codebase \u2014 search for the failing endpoint path, the symbols in the stack trace, or the UI text near the error \u2014 to locate the root cause and propose a fix."
"Finally: cross-reference the findings with the codebase \u2014 search for the failing endpoint path, the symbols in the stack trace, or the UI text near the error \u2014 to locate the root cause and propose a fix. get_fix_context bundles the failing request + triggering action + resolved stack in one call."
);

@@ -200,3 +384,3 @@ return steps;

function toolListBugReports(baseDir, args2) {
const scanDir = args2.dir ? path.isAbsolute(args2.dir) ? args2.dir : path.join(baseDir, args2.dir) : baseDir;
const scanDir = args2.dir ? path2.isAbsolute(args2.dir) ? args2.dir : path2.join(baseDir, args2.dir) : baseDir;
const files = args2.dir ? scanReportFiles(scanDir) : AUTO_DISCOVER ? collectReports(baseDir) : scanReportFiles(baseDir);

@@ -245,2 +429,5 @@ const reports = files.map((file) => {

annotations: p.annotations ?? [],
// Computed-style receipts for design-QA bugs (selector, typography,
// colors, box model, WCAG contrast) — diff these against the codebase.
elementAnnotations: p.elementAnnotations ?? [],
consoleErrorCount: p.consoleErrors?.length ?? 0,

@@ -325,2 +512,87 @@ networkFailureCount: p.networkErrors?.length ?? 0,

}
function toolGetPlaywrightTest(baseDir, args2) {
const { payload: p, resolved } = requireReport(baseDir, args2.file);
if (!p.playwrightTest) {
throw new Error(
"This report was exported before TraceBug 1.9 and has no generated test. Re-export the session from the TraceBug widget (Export .html) to include one."
);
}
const filename = p.playwrightTestFilename || "tracebug-bug.spec.ts";
return {
filename,
spec: p.playwrightTest,
sourceReport: path2.basename(resolved),
howToUse: [
`1. Save the spec as tests/${filename} (or your e2e folder).`,
"2. Set BASE_URL to your running dev server if it differs from the captured origin.",
`3. Run: npx playwright test ${filename} \u2014 the test FAILS while the bug exists.`,
"4. Fix the bug, re-run \u2014 green means the captured failure is gone."
]
};
}
function collectStacks(p) {
const out = [];
for (const e of p.consoleLogs ?? []) {
if (e.stack) out.push({ message: e.message, stack: e.stack });
}
if (!out.length) {
for (const e of p.consoleErrors ?? []) {
if (e.stack) out.push({ message: e.message, stack: e.stack });
}
}
return out;
}
function frameSummary(f) {
return {
fn: f.fn,
bundled: `${f.file}:${f.line}:${f.column}`,
original: f.original ? `${f.original.source}:${f.original.line}:${f.original.column}${f.original.name ? ` (${f.original.name})` : ""}` : null,
mapFile: f.mapFile ?? null
};
}
function toolResolveStack(baseDir, args2) {
const { payload: p } = requireReport(baseDir, args2.file);
const searchDir = args2.searchDir ? path2.resolve(process.cwd(), args2.searchDir) : process.cwd();
const stacks = collectStacks(p);
if (!stacks.length) throw new Error("This report contains no stack traces to resolve.");
const resolved = stacks.map((s) => {
const frames = resolveStackWithMaps(s.stack, searchDir).map(frameSummary);
return { error: s.message.slice(0, 200), frames };
});
const anyResolved = resolved.some((r) => r.frames.some((f) => f.original));
return {
searchDir,
note: anyResolved ? "original = source file:line:column from the matching .map file \u2014 search the codebase there." : `No .map files matched under ${searchDir}. Build with sourcemaps enabled, or pass searchDir pointing at the build output.`,
stacks: resolved
};
}
function toolGetFixContext(baseDir, args2) {
const { payload: p } = requireReport(baseDir, args2.file);
const failingRequest = (p.networkErrors ?? [])[0] ?? null;
const USER_KINDS = /* @__PURE__ */ new Set(["click", "input", "select", "submit", "navigate"]);
const failureTs = failingRequest?.timestamp ?? (p.consoleErrors ?? [])[0]?.timestamp ?? Infinity;
const userChips = (p.actionChips ?? []).filter((c) => USER_KINDS.has(c.kind));
const triggeringAction = [...userChips].reverse().find((c) => c.timestamp <= failureTs) ?? userChips[userChips.length - 1] ?? null;
const firstError = (p.consoleLogs ?? []).find((l) => l.level === "error") ?? (p.consoleErrors ?? [])[0] ?? null;
const searchDir = args2.searchDir ? path2.resolve(process.cwd(), args2.searchDir) : process.cwd();
const topFrames = firstError?.stack ? resolveStackWithMaps(firstError.stack, searchDir).slice(0, 5).map(frameSummary) : [];
return {
title: p.meta.title,
rootCause: p.rootCauseHint ?? p.meta.rootCause ?? null,
failingRequest: failingRequest ? {
method: failingRequest.method,
url: failingRequest.url,
status: failingRequest.status,
responseSnippet: failingRequest.response ?? null
} : null,
triggeringAction: triggeringAction ? {
action: [triggeringAction.verb, triggeringAction.nounLabel ?? triggeringAction.target].filter(Boolean).join(" "),
detail: triggeringAction.detail ?? null,
at: new Date(triggeringAction.timestamp).toISOString()
} : null,
error: firstError ? { message: firstError.message, topFrames } : null,
failingTest: p.playwrightTest ? { available: true, tool: "get_playwright_test", filename: p.playwrightTestFilename ?? "tracebug-bug.spec.ts" } : { available: false, tool: null, filename: null },
nextStep: "Search the codebase for the failing endpoint path and the original source locations above; " + (p.playwrightTest ? "then save the generated test (get_playwright_test) and iterate: run \u2192 fix \u2192 run until green." : "reproduce manually using get_repro_steps, then fix and verify.")
};
}
function buildDebugPrompt(file) {

@@ -359,2 +631,8 @@ const target = file?.trim();

}
case "get_playwright_test":
return textResult(toolGetPlaywrightTest(baseDir, args2));
case "resolve_stack":
return textResult(toolResolveStack(baseDir, args2));
case "get_fix_context":
return textResult(toolGetFixContext(baseDir, args2));
default:

@@ -448,2 +726,3 @@ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };

"use strict";
init_source_map();
TB_DATA_RE = /<script id="tb-data" type="application\/json">([\s\S]*?)<\/script>/;

@@ -508,2 +787,31 @@ MAX_SCAN_DEPTH = 3;

}
},
{
name: "get_playwright_test",
description: "Get the generated Playwright spec that REPLAYS this bug report's session and asserts the captured failure is gone \u2014 the test fails while the bug exists and passes once fixed. Save it, run it to reproduce, then use it to verify your fix.",
inputSchema: { type: "object", properties: { ...FILE_PROP }, required: ["file"] }
},
{
name: "resolve_stack",
description: "Map the minified stack traces in a TraceBug bug report to original source files/lines using .map files found in the current project (run from the repo that built the app). Returns bundled and original positions per frame.",
inputSchema: {
type: "object",
properties: {
...FILE_PROP,
searchDir: { type: "string", description: "Directory to search for .map files (default: the current working directory). Point at your build output if maps aren't found." }
},
required: ["file"]
}
},
{
name: "get_fix_context",
description: "One-call fix starter for a TraceBug bug report: the failing request (with response snippet), the user action that triggered it, the first error with source-map-resolved top stack frames, and whether a generated failing test is available. Call this when you're ready to locate and fix the bug.",
inputSchema: {
type: "object",
properties: {
...FILE_PROP,
searchDir: { type: "string", description: "Directory to search for .map files when resolving stack frames (default: current working directory)." }
},
required: ["file"]
}
}

@@ -555,11 +863,11 @@ ];

const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp_server(), mcp_server_exports));
const fs2 = await import("fs");
const path2 = await import("path");
const fs3 = await import("fs");
const path3 = await import("path");
const dirFlag = args.indexOf("--dir");
const hasExplicitDir = dirFlag !== -1 && !!args[dirFlag + 1];
const baseDir = hasExplicitDir ? path2.resolve(args[dirFlag + 1]) : process.cwd();
const baseDir = hasExplicitDir ? path3.resolve(args[dirFlag + 1]) : process.cwd();
let version = "0.0.0";
for (const rel of ["./package.json", "../package.json"]) {
try {
const pkg = JSON.parse(fs2.readFileSync(new URL(rel, import.meta.url), "utf8"));
const pkg = JSON.parse(fs3.readFileSync(new URL(rel, import.meta.url), "utf8"));
if (pkg.version) {

@@ -592,6 +900,6 @@ version = pkg.version;

async function initProject() {
const fs2 = await import("fs");
const path2 = await import("path");
const fs3 = await import("fs");
const path3 = await import("path");
const cwd = process.cwd();
const projectId = path2.basename(cwd) || "my-app";
const projectId = path3.basename(cwd) || "my-app";
console.log(`

@@ -602,6 +910,6 @@ ${BOLD}${CYAN}TraceBug${RESET} \u2014 the exact setup for your framework`);

let framework = "vanilla";
const pkgPath = path2.join(cwd, "package.json");
if (fs2.existsSync(pkgPath)) {
const pkgPath = path3.join(cwd, "package.json");
if (fs3.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };

@@ -608,0 +916,0 @@ if (deps["next"]) framework = "nextjs";

+1
-1
{
"name": "tracebug",
"version": "1.8.0",
"version": "1.9.0",
"mcpName": "io.github.prashantsinghmangat/tracebug",

@@ -5,0 +5,0 @@ "description": "TraceBug CLI — local MCP server that lets AI coding agents (Claude Code, Cursor, VS Code) debug TraceBug bug-report exports, plus framework setup via `tracebug init`. Zero dependencies.",

@@ -17,4 +17,16 @@ # tracebug

The agent gets six read tools — `list_bug_reports`, `get_bug_report` (with a prioritized investigation guide), `get_console_errors`, `get_network_activity`, `get_repro_steps`, `get_screenshot` — and a `/tracebug:debug_bug_report` prompt.
The agent gets nine read tools and a `/tracebug:debug_bug_report` prompt:
- `list_bug_reports` — scan a folder and summarize every export
- `get_bug_report` — report overview plus a prioritized investigation guide
- `get_console_errors` — captured console output with stack traces
- `get_network_activity` — captured requests, failed ones first, with response snippets
- `get_repro_steps` — plain-English steps, user actions, full session timeline
- `get_screenshot` — screenshots as real image content
- `get_playwright_test` — a generated failing Playwright spec that replays the session and asserts the failure is gone; red until fixed, green after
- `resolve_stack` — maps minified stack frames to original source via `.map` files found in the repo the server runs from
- `get_fix_context` — one-call fix starter: failing request, triggering user action, source-map-resolved stack, failing-test availability
The last three close the fix loop: pull the failing test, patch, re-run until green.
**Everything stays on your machine.** The server binds to stdio only, opens zero network connections, and reads the report files from your disk. No account, no cloud, no upload.

@@ -21,0 +33,0 @@