tracegist-mcp-bridge 0.2.0 → 0.2.2
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/README.md +6 -6
- package/bin/tracegist-mcp-bridge.mjs +101 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,13 +48,13 @@ Set `TRACEGIST_DIR` to change where the bridge looks for packages (defaults to `
|
|
|
48
48
|
|
|
49
49
|
Each session ZIP may include:
|
|
50
50
|
|
|
51
|
-
| Path
|
|
52
|
-
|
|
53
|
-
| `*-coding-agent-handoff.md`
|
|
54
|
-
| `*-manifest.json`
|
|
55
|
-
| `*-playwright-repro.spec.ts` | Auto-generated Playwright repro script skeleton
|
|
51
|
+
| Path | Description |
|
|
52
|
+
| ---------------------------- | -------------------------------------------------------------------------------------- |
|
|
53
|
+
| `*-coding-agent-handoff.md` | Structured handoff for the coding agent |
|
|
54
|
+
| `*-manifest.json` | Session metadata and file index |
|
|
55
|
+
| `*-playwright-repro.spec.ts` | Auto-generated Playwright repro script skeleton |
|
|
56
56
|
| `network/api-requests.jsonl` | API request/response bodies (opt-in, requires Deep Diagnostics + body capture enabled) |
|
|
57
|
-
| `markers/marker-NN-*/`
|
|
57
|
+
| `markers/marker-NN-*/` | Per-marker screenshots, voice notes, highlight captures |
|
|
58
58
|
|
|
59
59
|
## Prerequisites for transcription
|
|
60
60
|
|
|
@@ -18,9 +18,7 @@ const { version: BRIDGE_VERSION } = JSON.parse(
|
|
|
18
18
|
function resolveDefaultDirectory() {
|
|
19
19
|
const envDir = process.env.TRACEGIST_DIR;
|
|
20
20
|
if (envDir) {
|
|
21
|
-
const resolved = envDir.startsWith("~")
|
|
22
|
-
? path.join(os.homedir(), envDir.slice(1))
|
|
23
|
-
: envDir;
|
|
21
|
+
const resolved = envDir.startsWith("~") ? path.join(os.homedir(), envDir.slice(1)) : envDir;
|
|
24
22
|
return path.resolve(resolved);
|
|
25
23
|
}
|
|
26
24
|
return path.join(os.homedir(), "Downloads");
|
|
@@ -128,7 +126,9 @@ async function readZipEntries(zipPath) {
|
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
async function tryReadManifest(zip) {
|
|
131
|
-
const manifestEntry = Object.values(zip.files).find((entry) =>
|
|
129
|
+
const manifestEntry = Object.values(zip.files).find((entry) =>
|
|
130
|
+
entry.name.endsWith("-manifest.json"),
|
|
131
|
+
);
|
|
132
132
|
if (!manifestEntry) return { manifest: null, manifestEntryName: null };
|
|
133
133
|
const manifestText = await manifestEntry.async("text");
|
|
134
134
|
try {
|
|
@@ -149,9 +149,10 @@ function normalizeZipBaseName(entryName) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
async function tryReadHandoffMarkdown(zip, entryNames, manifest = null) {
|
|
152
|
-
const manifestNamedHandoff =
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
const manifestNamedHandoff =
|
|
153
|
+
typeof manifest?.handoffMarkdownFilename === "string"
|
|
154
|
+
? normalizeZipBaseName(manifest.handoffMarkdownFilename)
|
|
155
|
+
: null;
|
|
155
156
|
const byBaseName = new Map();
|
|
156
157
|
for (const name of entryNames) {
|
|
157
158
|
byBaseName.set(normalizeZipBaseName(name), name);
|
|
@@ -162,11 +163,12 @@ async function tryReadHandoffMarkdown(zip, entryNames, manifest = null) {
|
|
|
162
163
|
selectedEntryName = byBaseName.get(manifestNamedHandoff) || null;
|
|
163
164
|
}
|
|
164
165
|
if (!selectedEntryName) {
|
|
165
|
-
selectedEntryName =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
selectedEntryName =
|
|
167
|
+
entryNames.find((name) => {
|
|
168
|
+
const baseName = normalizeZipBaseName(name);
|
|
169
|
+
if (HANDOFF_SUFFIXES.some((suffix) => baseName.endsWith(suffix))) return true;
|
|
170
|
+
return /handoff.*\.md$/i.test(baseName);
|
|
171
|
+
}) || null;
|
|
170
172
|
}
|
|
171
173
|
if (!selectedEntryName) {
|
|
172
174
|
return { handoffMarkdown: null, handoffEntryName: null, debugCandidates: [] };
|
|
@@ -356,10 +358,7 @@ async function injectTranscriptsIntoMarkdown(markdown, zip, entryNames) {
|
|
|
356
358
|
} else {
|
|
357
359
|
const hint = whisperDependencyWarnings.join(" | ");
|
|
358
360
|
for (const shortId of voiceByShortId.keys()) {
|
|
359
|
-
transcriptByShortId.set(
|
|
360
|
-
shortId,
|
|
361
|
-
`[TRANSCRIPTION REQUIRED — Whisper not available: ${hint}]`,
|
|
362
|
-
);
|
|
361
|
+
transcriptByShortId.set(shortId, `[TRANSCRIPTION REQUIRED — Whisper not available: ${hint}]`);
|
|
363
362
|
}
|
|
364
363
|
}
|
|
365
364
|
|
|
@@ -416,7 +415,7 @@ server.registerPrompt(
|
|
|
416
415
|
"",
|
|
417
416
|
"1. `list_tracegist_packages` — find available packages (searches TRACEGIST_DIR env var, or ~/Downloads by default).",
|
|
418
417
|
"2. `get_tracegist_handoff_markdown` — call without a `section` parameter to get a table of contents",
|
|
419
|
-
|
|
418
|
+
' showing all sections and their sizes. Then request specific sections by name (e.g. `section: "Marker Timeline"`).',
|
|
420
419
|
' Use `section: "all"` to load the full document at once (can be large for deep exports).',
|
|
421
420
|
" **Voice note transcripts are injected automatically** into the marker sections — no separate step needed.",
|
|
422
421
|
" If Whisper is unavailable, a placeholder is injected with setup instructions.",
|
|
@@ -464,23 +463,25 @@ server.registerTool(
|
|
|
464
463
|
async ({ zipPath, model = "base", language }) => {
|
|
465
464
|
if (whisperDependencyWarnings.length > 0) {
|
|
466
465
|
return {
|
|
467
|
-
content: [
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: JSON.stringify(
|
|
470
|
+
{
|
|
471
|
+
zipPath,
|
|
472
|
+
model,
|
|
473
|
+
language: language || null,
|
|
474
|
+
transcriptions: [],
|
|
475
|
+
failures: [],
|
|
476
|
+
warning:
|
|
477
|
+
"Local Whisper dependencies are missing. Fix the dependency warnings and retry transcription.",
|
|
478
|
+
dependencyWarnings: whisperDependencyWarnings,
|
|
479
|
+
},
|
|
480
|
+
null,
|
|
481
|
+
2,
|
|
482
|
+
),
|
|
483
|
+
},
|
|
484
|
+
],
|
|
484
485
|
isError: true,
|
|
485
486
|
};
|
|
486
487
|
}
|
|
@@ -489,20 +490,22 @@ server.registerTool(
|
|
|
489
490
|
const voiceEntries = entryNames.filter((entryName) => looksLikeVoiceNote(entryName));
|
|
490
491
|
if (voiceEntries.length === 0) {
|
|
491
492
|
return {
|
|
492
|
-
content: [
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
493
|
+
content: [
|
|
494
|
+
{
|
|
495
|
+
type: "text",
|
|
496
|
+
text: JSON.stringify(
|
|
497
|
+
{
|
|
498
|
+
zipPath,
|
|
499
|
+
model,
|
|
500
|
+
language: language || null,
|
|
501
|
+
transcriptions: [],
|
|
502
|
+
warning: "No voice-note files found in package.",
|
|
503
|
+
},
|
|
504
|
+
null,
|
|
505
|
+
2,
|
|
506
|
+
),
|
|
507
|
+
},
|
|
508
|
+
],
|
|
506
509
|
};
|
|
507
510
|
}
|
|
508
511
|
|
|
@@ -564,7 +567,8 @@ server.registerTool(
|
|
|
564
567
|
server.registerTool(
|
|
565
568
|
"list_tracegist_packages",
|
|
566
569
|
{
|
|
567
|
-
description:
|
|
570
|
+
description:
|
|
571
|
+
"List TraceGist marker package zip files. Searches TRACEGIST_DIR (env var), falls back to ~/Downloads, or uses the given directory.",
|
|
568
572
|
inputSchema: z.object({
|
|
569
573
|
directory: z.string().optional(),
|
|
570
574
|
limit: z.number().int().min(1).max(200).optional(),
|
|
@@ -593,7 +597,11 @@ server.registerTool(
|
|
|
593
597
|
async ({ zipPath }) => {
|
|
594
598
|
const { zip, entryNames } = await readZipEntries(zipPath);
|
|
595
599
|
const { manifest, manifestEntryName } = await tryReadManifest(zip);
|
|
596
|
-
const { handoffMarkdown, handoffEntryName } = await tryReadHandoffMarkdown(
|
|
600
|
+
const { handoffMarkdown, handoffEntryName } = await tryReadHandoffMarkdown(
|
|
601
|
+
zip,
|
|
602
|
+
entryNames,
|
|
603
|
+
manifest,
|
|
604
|
+
);
|
|
597
605
|
|
|
598
606
|
const overview = {
|
|
599
607
|
zipPath,
|
|
@@ -639,17 +647,19 @@ server.registerTool(
|
|
|
639
647
|
.filter((name) => /handoff|coding-agent/i.test(path.posix.basename(name)))
|
|
640
648
|
.slice(0, 5);
|
|
641
649
|
return {
|
|
642
|
-
content: [
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
content: [
|
|
651
|
+
{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: [
|
|
654
|
+
`No handoff markdown entry found in ${zipPath}.`,
|
|
655
|
+
manifest?.handoffMarkdownFilename
|
|
656
|
+
? `Manifest handoff filename: ${manifest.handoffMarkdownFilename}`
|
|
657
|
+
: "Manifest handoff filename: (missing)",
|
|
658
|
+
`Matching entry candidates: ${hintedEntries.length > 0 ? hintedEntries.join(", ") : "(none)"}`,
|
|
659
|
+
`Debug candidates: ${debugCandidates.length > 0 ? debugCandidates.join(", ") : "(none)"}`,
|
|
660
|
+
].join("\n"),
|
|
661
|
+
},
|
|
662
|
+
],
|
|
653
663
|
isError: true,
|
|
654
664
|
};
|
|
655
665
|
}
|
|
@@ -659,10 +669,12 @@ server.registerTool(
|
|
|
659
669
|
// Full document retrieval
|
|
660
670
|
if (section && section.toLowerCase() === "all") {
|
|
661
671
|
return {
|
|
662
|
-
content: [
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
672
|
+
content: [
|
|
673
|
+
{
|
|
674
|
+
type: "text",
|
|
675
|
+
text: `# Source: ${handoffEntryName}\n\n${enrichedMarkdown}`,
|
|
676
|
+
},
|
|
677
|
+
],
|
|
666
678
|
};
|
|
667
679
|
}
|
|
668
680
|
|
|
@@ -671,10 +683,12 @@ server.registerTool(
|
|
|
671
683
|
// No section requested — return table of contents
|
|
672
684
|
if (!section) {
|
|
673
685
|
return {
|
|
674
|
-
content: [
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
686
|
+
content: [
|
|
687
|
+
{
|
|
688
|
+
type: "text",
|
|
689
|
+
text: `# Source: ${handoffEntryName}\n\n${renderSectionToc(sections, zipPath)}`,
|
|
690
|
+
},
|
|
691
|
+
],
|
|
678
692
|
};
|
|
679
693
|
}
|
|
680
694
|
|
|
@@ -684,18 +698,22 @@ server.registerTool(
|
|
|
684
698
|
if (!match) {
|
|
685
699
|
const available = sections.map((s) => s.name).join(", ");
|
|
686
700
|
return {
|
|
687
|
-
content: [
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
701
|
+
content: [
|
|
702
|
+
{
|
|
703
|
+
type: "text",
|
|
704
|
+
text: `Section "${section}" not found. Available sections: ${available}`,
|
|
705
|
+
},
|
|
706
|
+
],
|
|
691
707
|
isError: true,
|
|
692
708
|
};
|
|
693
709
|
}
|
|
694
710
|
return {
|
|
695
|
-
content: [
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
711
|
+
content: [
|
|
712
|
+
{
|
|
713
|
+
type: "text",
|
|
714
|
+
text: match.content,
|
|
715
|
+
},
|
|
716
|
+
],
|
|
699
717
|
};
|
|
700
718
|
},
|
|
701
719
|
);
|
|
@@ -751,7 +769,9 @@ server.registerTool(
|
|
|
751
769
|
const outputPath = path.resolve(outDir, path.basename(entryName));
|
|
752
770
|
if (!outputPath.startsWith(outDir + path.sep) && outputPath !== outDir) {
|
|
753
771
|
return {
|
|
754
|
-
content: [
|
|
772
|
+
content: [
|
|
773
|
+
{ type: "text", text: `Refusing to write outside output directory: ${outputPath}` },
|
|
774
|
+
],
|
|
755
775
|
isError: true,
|
|
756
776
|
};
|
|
757
777
|
}
|
|
@@ -765,7 +785,9 @@ server.registerTool(
|
|
|
765
785
|
);
|
|
766
786
|
|
|
767
787
|
whisperDependencyWarnings = await checkWhisperDependencies();
|
|
768
|
-
console.error(
|
|
788
|
+
console.error(
|
|
789
|
+
`[${BRIDGE_NAME}] Default package directory: ${DEFAULT_DOWNLOADS_DIR}${process.env.TRACEGIST_DIR ? " (from TRACEGIST_DIR)" : ""}`,
|
|
790
|
+
);
|
|
769
791
|
if (whisperDependencyWarnings.length > 0) {
|
|
770
792
|
for (const warning of whisperDependencyWarnings) {
|
|
771
793
|
console.error(`[${BRIDGE_NAME}] WARNING: ${warning}`);
|