@chigichan24/crune 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.
- package/LICENSE +201 -0
- package/README.md +155 -0
- package/bin/crune.js +2 -0
- package/dist-cli/__tests__/cli.test.js +63 -0
- package/dist-cli/__tests__/clustering.test.js +200 -0
- package/dist-cli/__tests__/community.test.js +115 -0
- package/dist-cli/__tests__/edges.test.js +130 -0
- package/dist-cli/__tests__/feature-extraction.test.js +66 -0
- package/dist-cli/__tests__/fixtures.js +192 -0
- package/dist-cli/__tests__/orchestrator.test.js +253 -0
- package/dist-cli/__tests__/session-parser.test.js +335 -0
- package/dist-cli/__tests__/session-summarizer.test.js +117 -0
- package/dist-cli/__tests__/skill-server.test.js +191 -0
- package/dist-cli/__tests__/svd.test.js +112 -0
- package/dist-cli/__tests__/tfidf.test.js +88 -0
- package/dist-cli/__tests__/tokenizer.test.js +125 -0
- package/dist-cli/__tests__/topic-nodes.test.js +184 -0
- package/dist-cli/analyze-sessions.js +476 -0
- package/dist-cli/cli.js +215 -0
- package/dist-cli/knowledge-graph/clustering.js +174 -0
- package/dist-cli/knowledge-graph/community.js +220 -0
- package/dist-cli/knowledge-graph/constants.js +58 -0
- package/dist-cli/knowledge-graph/edges.js +193 -0
- package/dist-cli/knowledge-graph/feature-extraction.js +124 -0
- package/dist-cli/knowledge-graph/index.js +235 -0
- package/dist-cli/knowledge-graph/reusability.js +51 -0
- package/dist-cli/knowledge-graph/similarity.js +13 -0
- package/dist-cli/knowledge-graph/skill-generator.js +203 -0
- package/dist-cli/knowledge-graph/svd.js +195 -0
- package/dist-cli/knowledge-graph/tfidf.js +54 -0
- package/dist-cli/knowledge-graph/tokenizer.js +66 -0
- package/dist-cli/knowledge-graph/tool-pattern.js +173 -0
- package/dist-cli/knowledge-graph/topic-nodes.js +199 -0
- package/dist-cli/knowledge-graph/types.js +4 -0
- package/dist-cli/knowledge-graph-builder.js +27 -0
- package/dist-cli/session-parser.js +360 -0
- package/dist-cli/session-summarizer.js +133 -0
- package/dist-cli/skill-server.js +62 -0
- package/dist-cli/skill-synthesizer.js +189 -0
- package/package.json +47 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { louvainDetection, brandesBetweenness } from "../knowledge-graph-builder.js";
|
|
3
|
+
function makeTopicNode(id, project = "default-project") {
|
|
4
|
+
return {
|
|
5
|
+
id,
|
|
6
|
+
label: `Topic ${id}`,
|
|
7
|
+
keywords: [],
|
|
8
|
+
project,
|
|
9
|
+
projects: [project],
|
|
10
|
+
sessionIds: [],
|
|
11
|
+
sessionCount: 0,
|
|
12
|
+
totalDurationMinutes: 0,
|
|
13
|
+
totalToolCalls: 0,
|
|
14
|
+
firstSeen: "2025-01-01T00:00:00Z",
|
|
15
|
+
lastSeen: "2025-01-01T00:00:00Z",
|
|
16
|
+
betweennessCentrality: 0,
|
|
17
|
+
degreeCentrality: 0,
|
|
18
|
+
communityId: -1,
|
|
19
|
+
representativePrompts: [],
|
|
20
|
+
suggestedPrompt: "",
|
|
21
|
+
toolSignature: [],
|
|
22
|
+
dominantRole: "user-driven",
|
|
23
|
+
reusabilityScore: { overall: 0, frequency: 0, timeCost: 0, crossProjectScore: 0, recency: 0 },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function makeTopicEdge(source, target, strength = 1.0) {
|
|
27
|
+
return {
|
|
28
|
+
source,
|
|
29
|
+
target,
|
|
30
|
+
type: "semantic-similarity",
|
|
31
|
+
strength,
|
|
32
|
+
label: `${source}-${target}`,
|
|
33
|
+
signals: {
|
|
34
|
+
semanticSimilarity: strength,
|
|
35
|
+
fileOverlap: 0,
|
|
36
|
+
sessionOverlap: 0,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
describe("louvainDetection", () => {
|
|
41
|
+
it("returns empty communities and modularity 0 for 0 topics", () => {
|
|
42
|
+
const result = louvainDetection([], []);
|
|
43
|
+
expect(result).toEqual({ communities: [], modularity: 0 });
|
|
44
|
+
});
|
|
45
|
+
it("assigns each isolated topic its own community when there are no edges", () => {
|
|
46
|
+
const topics = [makeTopicNode("A"), makeTopicNode("B"), makeTopicNode("C")];
|
|
47
|
+
const result = louvainDetection(topics, []);
|
|
48
|
+
expect(result.communities).toHaveLength(3);
|
|
49
|
+
// Each community should contain exactly one topic
|
|
50
|
+
const allTopicIds = result.communities.flatMap((c) => c.topicIds);
|
|
51
|
+
expect(allTopicIds.sort()).toEqual(["A", "B", "C"]);
|
|
52
|
+
for (const community of result.communities) {
|
|
53
|
+
expect(community.topicIds).toHaveLength(1);
|
|
54
|
+
}
|
|
55
|
+
// When there are no edges (totalWeight === 0), the function returns
|
|
56
|
+
// individual communities but does not mutate topics[i].communityId.
|
|
57
|
+
// Verify via the returned communities structure instead.
|
|
58
|
+
expect(result.modularity).toBe(0);
|
|
59
|
+
for (const community of result.communities) {
|
|
60
|
+
expect(community).toHaveProperty("id");
|
|
61
|
+
expect(community).toHaveProperty("topicIds");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
it("detects 2 communities for 2 disconnected pairs", () => {
|
|
65
|
+
const topics = [
|
|
66
|
+
makeTopicNode("A"),
|
|
67
|
+
makeTopicNode("B"),
|
|
68
|
+
makeTopicNode("C"),
|
|
69
|
+
makeTopicNode("D"),
|
|
70
|
+
];
|
|
71
|
+
const edges = [makeTopicEdge("A", "B"), makeTopicEdge("C", "D")];
|
|
72
|
+
const result = louvainDetection(topics, edges);
|
|
73
|
+
expect(result.communities).toHaveLength(2);
|
|
74
|
+
// A and B should be in the same community
|
|
75
|
+
const communityOfA = topics.find((t) => t.id === "A").communityId;
|
|
76
|
+
const communityOfB = topics.find((t) => t.id === "B").communityId;
|
|
77
|
+
const communityOfC = topics.find((t) => t.id === "C").communityId;
|
|
78
|
+
const communityOfD = topics.find((t) => t.id === "D").communityId;
|
|
79
|
+
expect(communityOfA).toBe(communityOfB);
|
|
80
|
+
expect(communityOfC).toBe(communityOfD);
|
|
81
|
+
expect(communityOfA).not.toBe(communityOfC);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("brandesBetweenness", () => {
|
|
85
|
+
it("does not crash and keeps centralities at 0 when n <= 2", () => {
|
|
86
|
+
const topics = [makeTopicNode("A"), makeTopicNode("B")];
|
|
87
|
+
const edges = [makeTopicEdge("A", "B")];
|
|
88
|
+
brandesBetweenness(topics, edges);
|
|
89
|
+
expect(topics[0].betweennessCentrality).toBe(0);
|
|
90
|
+
expect(topics[1].betweennessCentrality).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
it("gives highest betweenness to the middle node in a linear graph A-B-C", () => {
|
|
93
|
+
const topics = [makeTopicNode("A"), makeTopicNode("B"), makeTopicNode("C")];
|
|
94
|
+
const edges = [makeTopicEdge("A", "B"), makeTopicEdge("B", "C")];
|
|
95
|
+
brandesBetweenness(topics, edges);
|
|
96
|
+
const bcA = topics.find((t) => t.id === "A").betweennessCentrality;
|
|
97
|
+
const bcB = topics.find((t) => t.id === "B").betweennessCentrality;
|
|
98
|
+
const bcC = topics.find((t) => t.id === "C").betweennessCentrality;
|
|
99
|
+
expect(bcB).toBeGreaterThan(bcA);
|
|
100
|
+
expect(bcB).toBeGreaterThan(bcC);
|
|
101
|
+
});
|
|
102
|
+
it("sets degree centrality correctly for a linear graph A-B-C", () => {
|
|
103
|
+
const topics = [makeTopicNode("A"), makeTopicNode("B"), makeTopicNode("C")];
|
|
104
|
+
const edges = [makeTopicEdge("A", "B"), makeTopicEdge("B", "C")];
|
|
105
|
+
brandesBetweenness(topics, edges);
|
|
106
|
+
const dcA = topics.find((t) => t.id === "A").degreeCentrality;
|
|
107
|
+
const dcB = topics.find((t) => t.id === "B").degreeCentrality;
|
|
108
|
+
const dcC = topics.find((t) => t.id === "C").degreeCentrality;
|
|
109
|
+
// B has degree 2, normalized: 2 / (3 - 1) = 1.0
|
|
110
|
+
expect(dcB).toBeCloseTo(1.0);
|
|
111
|
+
// A and C have degree 1, normalized: 1 / (3 - 1) = 0.5
|
|
112
|
+
expect(dcA).toBeCloseTo(0.5);
|
|
113
|
+
expect(dcC).toBeCloseTo(0.5);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { findCommonPathPrefix, findSharedKeywords, classifyEdge, } from "../knowledge-graph-builder.js";
|
|
3
|
+
function makeTopicNode(overrides = {}) {
|
|
4
|
+
return {
|
|
5
|
+
id: "topic-1",
|
|
6
|
+
label: "test topic",
|
|
7
|
+
keywords: [],
|
|
8
|
+
project: "projectA",
|
|
9
|
+
projects: ["projectA"],
|
|
10
|
+
sessionIds: ["s1"],
|
|
11
|
+
sessionCount: 1,
|
|
12
|
+
totalDurationMinutes: 10,
|
|
13
|
+
totalToolCalls: 5,
|
|
14
|
+
firstSeen: "2025-01-01",
|
|
15
|
+
lastSeen: "2025-01-02",
|
|
16
|
+
betweennessCentrality: 0,
|
|
17
|
+
degreeCentrality: 0,
|
|
18
|
+
communityId: 0,
|
|
19
|
+
representativePrompts: [],
|
|
20
|
+
suggestedPrompt: "",
|
|
21
|
+
toolSignature: [],
|
|
22
|
+
dominantRole: "user-driven",
|
|
23
|
+
reusabilityScore: { overall: 0, frequency: 0, timeCost: 0, crossProjectScore: 0, recency: 0 },
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function makeTfidf(vocabulary, vectors) {
|
|
28
|
+
const vocabIndex = new Map();
|
|
29
|
+
vocabulary.forEach((v, i) => vocabIndex.set(v, i));
|
|
30
|
+
return { vocabulary, vocabIndex, vectors };
|
|
31
|
+
}
|
|
32
|
+
describe("findCommonPathPrefix", () => {
|
|
33
|
+
it("returns common prefix for paths sharing a directory", () => {
|
|
34
|
+
expect(findCommonPathPrefix(["/src/a/b.ts", "/src/a/c.ts"])).toBe("/src/a");
|
|
35
|
+
});
|
|
36
|
+
it("returns empty string when prefix is too short (<=1 segment)", () => {
|
|
37
|
+
expect(findCommonPathPrefix(["/a/b", "/c/d"])).toBe("");
|
|
38
|
+
});
|
|
39
|
+
it("returns empty string for empty input", () => {
|
|
40
|
+
expect(findCommonPathPrefix([])).toBe("");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("findSharedKeywords", () => {
|
|
44
|
+
it("returns terms with high weight in both centroids", () => {
|
|
45
|
+
const vocabulary = ["react", "typescript", "testing", "css"];
|
|
46
|
+
const centroids = new Map([
|
|
47
|
+
["t1", new Float64Array([0.5, 0.8, 0.02, 0.001])],
|
|
48
|
+
["t2", new Float64Array([0.3, 0.6, 0.9, 0.005])],
|
|
49
|
+
]);
|
|
50
|
+
const tfidf = makeTfidf(vocabulary, new Map());
|
|
51
|
+
const shared = findSharedKeywords("t1", "t2", tfidf, centroids, 3);
|
|
52
|
+
// "react" (0.5 > 0.01 & 0.3 > 0.01) and "typescript" (0.8 > 0.01 & 0.6 > 0.01)
|
|
53
|
+
// "testing" only in t1 is 0.02 and t2 is 0.9, both > 0.01 → included
|
|
54
|
+
// "css" is below 0.01 in both → excluded
|
|
55
|
+
expect(shared).toContain("react");
|
|
56
|
+
expect(shared).toContain("typescript");
|
|
57
|
+
expect(shared).toContain("testing");
|
|
58
|
+
expect(shared).not.toContain("css");
|
|
59
|
+
});
|
|
60
|
+
it("returns empty array when one centroid is missing", () => {
|
|
61
|
+
const tfidf = makeTfidf(["a"], new Map());
|
|
62
|
+
const centroids = new Map([
|
|
63
|
+
["t1", new Float64Array([0.5])],
|
|
64
|
+
]);
|
|
65
|
+
expect(findSharedKeywords("t1", "t_missing", tfidf, centroids, 3)).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("classifyEdge", () => {
|
|
69
|
+
it("returns cross-project-bridge when topics are from different projects with no overlap", () => {
|
|
70
|
+
const ti = makeTopicNode({
|
|
71
|
+
id: "t1",
|
|
72
|
+
project: "projectA",
|
|
73
|
+
projects: ["projectA"],
|
|
74
|
+
});
|
|
75
|
+
const tj = makeTopicNode({
|
|
76
|
+
id: "t2",
|
|
77
|
+
project: "projectB",
|
|
78
|
+
projects: ["projectB"],
|
|
79
|
+
});
|
|
80
|
+
const signals = { semanticSimilarity: 0.5, fileOverlap: 0.1, sessionOverlap: 0.1 };
|
|
81
|
+
const tfidf = makeTfidf(["shared"], new Map());
|
|
82
|
+
const centroids = new Map([
|
|
83
|
+
["t1", new Float64Array([0.5])],
|
|
84
|
+
["t2", new Float64Array([0.5])],
|
|
85
|
+
]);
|
|
86
|
+
const result = classifyEdge(ti, tj, signals, [], tfidf, centroids);
|
|
87
|
+
expect(result.type).toBe("cross-project-bridge");
|
|
88
|
+
expect(result.label).toContain("cross-project");
|
|
89
|
+
});
|
|
90
|
+
it("returns shared-module when fileOverlap is the dominant signal and shared files exist", () => {
|
|
91
|
+
const ti = makeTopicNode({
|
|
92
|
+
id: "t1",
|
|
93
|
+
project: "projectA",
|
|
94
|
+
projects: ["projectA"],
|
|
95
|
+
});
|
|
96
|
+
const tj = makeTopicNode({
|
|
97
|
+
id: "t2",
|
|
98
|
+
project: "projectA",
|
|
99
|
+
projects: ["projectA"],
|
|
100
|
+
});
|
|
101
|
+
// fileOverlap * 0.3 must be the max signal
|
|
102
|
+
// semanticSimilarity * 0.4 < fileOverlap * 0.3 → need fileOverlap high, semantic low
|
|
103
|
+
const signals = { semanticSimilarity: 0.1, fileOverlap: 0.9, sessionOverlap: 0.0 };
|
|
104
|
+
const sharedFiles = ["/src/components/Button.tsx", "/src/components/Card.tsx"];
|
|
105
|
+
const tfidf = makeTfidf([], new Map());
|
|
106
|
+
const centroids = new Map();
|
|
107
|
+
const result = classifyEdge(ti, tj, signals, sharedFiles, tfidf, centroids);
|
|
108
|
+
expect(result.type).toBe("shared-module");
|
|
109
|
+
expect(result.label).toContain("shared");
|
|
110
|
+
});
|
|
111
|
+
it("returns workflow-continuation when sessionOverlap is the dominant signal", () => {
|
|
112
|
+
const ti = makeTopicNode({
|
|
113
|
+
id: "t1",
|
|
114
|
+
project: "projectA",
|
|
115
|
+
projects: ["projectA"],
|
|
116
|
+
});
|
|
117
|
+
const tj = makeTopicNode({
|
|
118
|
+
id: "t2",
|
|
119
|
+
project: "projectA",
|
|
120
|
+
projects: ["projectA"],
|
|
121
|
+
});
|
|
122
|
+
// sessionOverlap * 0.3 must be the max, fileOverlap * 0.3 must be less or no shared files
|
|
123
|
+
const signals = { semanticSimilarity: 0.1, fileOverlap: 0.0, sessionOverlap: 0.9 };
|
|
124
|
+
const tfidf = makeTfidf([], new Map());
|
|
125
|
+
const centroids = new Map();
|
|
126
|
+
const result = classifyEdge(ti, tj, signals, [], tfidf, centroids);
|
|
127
|
+
expect(result.type).toBe("workflow-continuation");
|
|
128
|
+
expect(result.label).toBe("workflow continuation");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildToolIdf, buildStructuralVectors } from "../knowledge-graph-builder.js";
|
|
3
|
+
import { editHeavySession, readHeavySession, subagentSession, emptySession, allSessions } from "./fixtures.js";
|
|
4
|
+
describe("buildToolIdf", () => {
|
|
5
|
+
it("gives tool in 1 of 3 sessions higher IDF than tool in all 3", () => {
|
|
6
|
+
// Use editHeavy, readHeavy, subagent sessions
|
|
7
|
+
const sessions = [editHeavySession, readHeavySession, subagentSession];
|
|
8
|
+
const result = buildToolIdf(sessions);
|
|
9
|
+
// "Edit" appears in editHeavy (10) and readHeavy (1) => df=2
|
|
10
|
+
// "Bash" appears in subagent only => df=1
|
|
11
|
+
// "Glob" appears in readHeavy only => df=1
|
|
12
|
+
// "Read" appears in editHeavy (1) and readHeavy (15) => df=2
|
|
13
|
+
// All tools appear, but tools in fewer sessions get higher IDF
|
|
14
|
+
// IDF = log(n/df): log(3/1) > log(3/2)
|
|
15
|
+
const idfBash = result.toolIdfWeights.get("Bash"); // df=1
|
|
16
|
+
const idfEdit = result.toolIdfWeights.get("Edit"); // df=2
|
|
17
|
+
expect(idfBash).toBeGreaterThan(idfEdit);
|
|
18
|
+
});
|
|
19
|
+
it("produces L2-normalized vectors", () => {
|
|
20
|
+
const sessions = [editHeavySession, readHeavySession, subagentSession];
|
|
21
|
+
const result = buildToolIdf(sessions);
|
|
22
|
+
for (const [, vec] of result.vectors) {
|
|
23
|
+
let dotProduct = 0;
|
|
24
|
+
for (let i = 0; i < vec.length; i++) {
|
|
25
|
+
dotProduct += vec[i] * vec[i];
|
|
26
|
+
}
|
|
27
|
+
if (dotProduct > 0) {
|
|
28
|
+
expect(dotProduct).toBeCloseTo(1.0, 10);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("buildStructuralVectors", () => {
|
|
34
|
+
it("returns zero vector for session with 0 turns", () => {
|
|
35
|
+
const vectors = buildStructuralVectors([emptySession]);
|
|
36
|
+
const vec = vectors.get("empty");
|
|
37
|
+
expect(vec.length).toBe(7);
|
|
38
|
+
for (let i = 0; i < vec.length; i++) {
|
|
39
|
+
expect(vec[i]).toBe(0);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it("edit-heavy session has editHeaviness > readHeaviness", () => {
|
|
43
|
+
const vectors = buildStructuralVectors(allSessions);
|
|
44
|
+
const vec = vectors.get("edit-heavy");
|
|
45
|
+
// vec[5] = editHeaviness, vec[6] = readHeaviness (before normalization ratios preserved)
|
|
46
|
+
expect(vec[5]).toBeGreaterThan(vec[6]);
|
|
47
|
+
});
|
|
48
|
+
it("read-heavy session has readHeaviness > editHeaviness", () => {
|
|
49
|
+
const vectors = buildStructuralVectors(allSessions);
|
|
50
|
+
const vec = vectors.get("read-heavy");
|
|
51
|
+
// vec[6] = readHeaviness, vec[5] = editHeaviness
|
|
52
|
+
expect(vec[6]).toBeGreaterThan(vec[5]);
|
|
53
|
+
});
|
|
54
|
+
it("produces L2-normalized vectors for non-empty sessions", () => {
|
|
55
|
+
const vectors = buildStructuralVectors(allSessions);
|
|
56
|
+
for (const [sessionId, vec] of vectors) {
|
|
57
|
+
if (sessionId === "empty")
|
|
58
|
+
continue; // zero vector, skip
|
|
59
|
+
let dotProduct = 0;
|
|
60
|
+
for (let i = 0; i < vec.length; i++) {
|
|
61
|
+
dotProduct += vec[i] * vec[i];
|
|
62
|
+
}
|
|
63
|
+
expect(dotProduct).toBeCloseTo(1.0, 10);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export function makeSession(overrides) {
|
|
2
|
+
return {
|
|
3
|
+
sessionId: overrides.sessionId,
|
|
4
|
+
projectDisplayName: overrides.projectDisplayName ?? "test-project",
|
|
5
|
+
turns: overrides.turns ?? [],
|
|
6
|
+
subagents: overrides.subagents ?? {},
|
|
7
|
+
meta: {
|
|
8
|
+
sessionId: overrides.sessionId,
|
|
9
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
10
|
+
lastActiveAt: "2025-01-01T01:00:00Z",
|
|
11
|
+
durationMinutes: 60,
|
|
12
|
+
filesEdited: [],
|
|
13
|
+
gitBranch: "main",
|
|
14
|
+
toolBreakdown: {},
|
|
15
|
+
subagentCount: 0,
|
|
16
|
+
...overrides.meta,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Session with edit-heavy tool usage */
|
|
21
|
+
export const editHeavySession = makeSession({
|
|
22
|
+
sessionId: "edit-heavy",
|
|
23
|
+
projectDisplayName: "crune",
|
|
24
|
+
turns: [
|
|
25
|
+
{
|
|
26
|
+
userPrompt: "Fix the camelCase parser bug in /src/utils/tokenizer.ts",
|
|
27
|
+
assistantTexts: ["I'll fix the tokenizer bug"],
|
|
28
|
+
toolCalls: [
|
|
29
|
+
{ toolName: "Read", input: { file: "/src/utils/tokenizer.ts" } },
|
|
30
|
+
{ toolName: "Edit", input: { file: "/src/utils/tokenizer.ts" } },
|
|
31
|
+
{ toolName: "Edit", input: { file: "/src/utils/tokenizer.ts" } },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
userPrompt: "Also update the tests",
|
|
36
|
+
assistantTexts: ["Updated the tests"],
|
|
37
|
+
toolCalls: [
|
|
38
|
+
{ toolName: "Read", input: { file: "/src/utils/tokenizer.test.ts" } },
|
|
39
|
+
{ toolName: "Write", input: { file: "/src/utils/tokenizer.test.ts" } },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
subagents: {},
|
|
44
|
+
meta: {
|
|
45
|
+
sessionId: "edit-heavy",
|
|
46
|
+
createdAt: "2025-01-01T10:00:00Z",
|
|
47
|
+
lastActiveAt: "2025-01-01T11:00:00Z",
|
|
48
|
+
durationMinutes: 60,
|
|
49
|
+
filesEdited: ["/src/utils/tokenizer.ts", "/src/utils/tokenizer.test.ts"],
|
|
50
|
+
gitBranch: "fix/tokenizer",
|
|
51
|
+
toolBreakdown: { Read: 2, Edit: 2, Write: 1 },
|
|
52
|
+
subagentCount: 0,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
/** Session with read-heavy tool usage */
|
|
56
|
+
export const readHeavySession = makeSession({
|
|
57
|
+
sessionId: "read-heavy",
|
|
58
|
+
projectDisplayName: "crune",
|
|
59
|
+
turns: [
|
|
60
|
+
{
|
|
61
|
+
userPrompt: "Investigate the logging setup in /src/components/App.tsx",
|
|
62
|
+
assistantTexts: ["Let me look at the logging setup"],
|
|
63
|
+
toolCalls: [
|
|
64
|
+
{ toolName: "Grep", input: { pattern: "console.log" } },
|
|
65
|
+
{ toolName: "Read", input: { file: "/src/components/App.tsx" } },
|
|
66
|
+
{ toolName: "Glob", input: { pattern: "*.log" } },
|
|
67
|
+
{ toolName: "Read", input: { file: "/src/config/logging.ts" } },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
subagents: {},
|
|
72
|
+
meta: {
|
|
73
|
+
sessionId: "read-heavy",
|
|
74
|
+
createdAt: "2025-01-02T10:00:00Z",
|
|
75
|
+
lastActiveAt: "2025-01-02T10:30:00Z",
|
|
76
|
+
durationMinutes: 30,
|
|
77
|
+
filesEdited: [],
|
|
78
|
+
gitBranch: "main",
|
|
79
|
+
toolBreakdown: { Read: 2, Grep: 1, Glob: 1 },
|
|
80
|
+
subagentCount: 0,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
/** Session with subagent delegation */
|
|
84
|
+
export const subagentSession = makeSession({
|
|
85
|
+
sessionId: "subagent-heavy",
|
|
86
|
+
projectDisplayName: "other-project",
|
|
87
|
+
turns: [
|
|
88
|
+
{
|
|
89
|
+
userPrompt: "Refactor the authentication module in /src/auth/handler.ts",
|
|
90
|
+
assistantTexts: ["I'll delegate this to subagents"],
|
|
91
|
+
toolCalls: [
|
|
92
|
+
{ toolName: "Agent", input: { task: "refactor auth" } },
|
|
93
|
+
{ toolName: "Agent", input: { task: "update tests" } },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
userPrompt: "Deploy the changes",
|
|
98
|
+
assistantTexts: ["Deploying now"],
|
|
99
|
+
toolCalls: [
|
|
100
|
+
{ toolName: "Bash", input: { command: "npm run deploy" } },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
subagents: {
|
|
105
|
+
"agent-1": {
|
|
106
|
+
agentId: "agent-1",
|
|
107
|
+
agentType: "code",
|
|
108
|
+
turns: [
|
|
109
|
+
{
|
|
110
|
+
userPrompt: "refactor auth",
|
|
111
|
+
assistantTexts: ["Done"],
|
|
112
|
+
toolCalls: [
|
|
113
|
+
{ toolName: "Edit", input: { file: "/src/auth/handler.ts" } },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
"agent-2": {
|
|
119
|
+
agentId: "agent-2",
|
|
120
|
+
agentType: "test",
|
|
121
|
+
turns: [
|
|
122
|
+
{
|
|
123
|
+
userPrompt: "update tests",
|
|
124
|
+
assistantTexts: ["Tests updated"],
|
|
125
|
+
toolCalls: [
|
|
126
|
+
{ toolName: "Write", input: { file: "/src/auth/handler.test.ts" } },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
meta: {
|
|
133
|
+
sessionId: "subagent-heavy",
|
|
134
|
+
createdAt: "2025-01-03T10:00:00Z",
|
|
135
|
+
lastActiveAt: "2025-01-03T11:30:00Z",
|
|
136
|
+
durationMinutes: 90,
|
|
137
|
+
filesEdited: ["/src/auth/handler.ts", "/src/auth/handler.test.ts"],
|
|
138
|
+
gitBranch: "refactor/auth",
|
|
139
|
+
toolBreakdown: { Agent: 2, Bash: 1, Edit: 1, Write: 1 },
|
|
140
|
+
subagentCount: 2,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
/** Session with Japanese prompts */
|
|
144
|
+
export const japaneseSession = makeSession({
|
|
145
|
+
sessionId: "japanese",
|
|
146
|
+
projectDisplayName: "crune",
|
|
147
|
+
turns: [
|
|
148
|
+
{
|
|
149
|
+
userPrompt: "修正してください: /src/components/Overview.tsx のバグ",
|
|
150
|
+
assistantTexts: ["バグを修正します"],
|
|
151
|
+
toolCalls: [
|
|
152
|
+
{ toolName: "Read", input: { file: "/src/components/Overview.tsx" } },
|
|
153
|
+
{ toolName: "Edit", input: { file: "/src/components/Overview.tsx" } },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
subagents: {},
|
|
158
|
+
meta: {
|
|
159
|
+
sessionId: "japanese",
|
|
160
|
+
createdAt: "2025-01-01T12:00:00Z",
|
|
161
|
+
lastActiveAt: "2025-01-01T12:30:00Z",
|
|
162
|
+
durationMinutes: 30,
|
|
163
|
+
filesEdited: ["/src/components/Overview.tsx"],
|
|
164
|
+
gitBranch: "fix/overview",
|
|
165
|
+
toolBreakdown: { Read: 1, Edit: 1 },
|
|
166
|
+
subagentCount: 0,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
/** Minimal empty session */
|
|
170
|
+
export const emptySession = makeSession({
|
|
171
|
+
sessionId: "empty",
|
|
172
|
+
projectDisplayName: "crune",
|
|
173
|
+
turns: [],
|
|
174
|
+
meta: {
|
|
175
|
+
sessionId: "empty",
|
|
176
|
+
createdAt: "2025-01-05T00:00:00Z",
|
|
177
|
+
lastActiveAt: "2025-01-05T00:00:00Z",
|
|
178
|
+
durationMinutes: 0,
|
|
179
|
+
filesEdited: [],
|
|
180
|
+
gitBranch: "",
|
|
181
|
+
toolBreakdown: {},
|
|
182
|
+
subagentCount: 0,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
/** All fixture sessions */
|
|
186
|
+
export const allSessions = [
|
|
187
|
+
editHeavySession,
|
|
188
|
+
readHeavySession,
|
|
189
|
+
subagentSession,
|
|
190
|
+
japaneseSession,
|
|
191
|
+
emptySession,
|
|
192
|
+
];
|