Zion Boggan
repos/TreeTrace/src/render-json.js
zionboggan.com ↗
101 lines · javascript
History for this file →
1
import { REPO_URL, SCHEMA_VERSION } from './config.js';
2
import { analyzeTree } from './analyze.js';
3
 
4
const RELATIONSHIP_BY_KIND = {
5
  direction: 'refines',
6
  correction: 'corrects',
7
  'scope-change': 'expands',
8
  checkpoint: 'checkpoints',
9
  question: 'asks',
10
  rejection: 'rejects',
11
  root: 'refines',
12
};
13
 
14
export function renderJson(tree, opts = {}) {
15
  const { projectName, generatedBy = 'treetrace', version = '0.1.0', sourceType = 'claude-code-jsonl' } = opts;
16
  const { nodes, sessions, stats } = tree;
17
  const analysis = analyzeTree(tree);
18
 
19
  return {
20
    schemaVersion: SCHEMA_VERSION,
21
    generator: { name: generatedBy, version, url: REPO_URL },
22
    project: {
23
      name: projectName,
24
      generatedAt: opts.generatedAt || null,
25
      sourceType,
26
    },
27
    stats: {
28
      prompts: stats.promptCount,
29
      rawPrompts: stats.rawPromptCount,
30
      sessions: stats.sessionCount,
31
      days: stats.days,
32
      corrections: stats.corrections,
33
      scopeChanges: stats.scopeChanges,
34
      checkpoints: stats.checkpoints,
35
      abandonedBranches: stats.abandonedBranches,
36
      rejections: stats.rejections || 0,
37
      rejectionsByKind: stats.rejectionsByKind || {},
38
      toolUses: stats.toolUses,
39
      filesTouched: stats.filesTouched,
40
      inputTokens: stats.inputTokens || 0,
41
      outputTokens: stats.outputTokens || 0,
42
      models: stats.models,
43
      firstTs: stats.firstTs,
44
      lastTs: stats.lastTs,
45
    },
46
    analysis: {
47
      failureSignals: analysis.summary.totalFailureSignals,
48
      correctionChains: analysis.summary.correctionChains,
49
      evalCandidates: analysis.summary.evalCandidates,
50
      lessons: analysis.summary.lessons,
51
    },
52
    sessions: sessions
53
      .filter((s) => s.prompts.length)
54
      .map((s) => ({
55
        id: s.sessionId,
56
        title: s.title,
57
        firstTs: s.firstTs,
58
        lastTs: s.lastTs,
59
        promptCount: s.prompts.length,
60
        isContinuation: s.isContinuation,
61
        inputTokens: s.stats.inputTokens || 0,
62
        outputTokens: s.stats.outputTokens || 0,
63
      })),
64
    nodes: nodes.map((n) => ({
65
      id: n.id,
66
      parentId: n.parent ? n.parent.id : null,
67
      role: 'user',
68
      kind: n.kind,
69
      title: n.title,
70
      text: n.text,
71
      status: n.status,
72
      nudges: n.nudges || 0,
73
      reruns: n.reruns || 0,
74
      session: n.sessionId,
75
      timestamp: n.ts,
76
      model: n.model || null,
77
      actions: (n.actions || []).map((a) => ({
78
        tool: a.tool || null,
79
        file: a.file || null,
80
        command: a.command || null,
81
        model: a.model || null,
82
      })),
83
      failureSignals: n.failureSignals || [],
84
      evalCandidate: Boolean(n.evalCandidate),
85
      lessonIds: n.lessonIds || [],
86
      rejections: n.rejections || [],
87
 
88
      sourceEventIds: n.uuid ? [n.uuid] : [],
89
    })),
90
    edges: nodes
91
      .filter((n) => n.parent)
92
      .map((n) => ({
93
        from: n.parent.id,
94
        to: n.id,
95
        relationship: RELATIONSHIP_BY_KIND[n.kind] || 'refines',
96
      })),
97
    correctionChains: analysis.correctionChains,
98
    lessons: analysis.lessons,
99
    evalCandidates: analysis.evalCandidates,
100
  };
101
}