ultimate-pi 0.3.1 → 0.4.1

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 (184) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +37 -0
  2. package/.agents/skills/harness-governor/SKILL.md +1 -1
  3. package/.agents/skills/harness-orchestration/SKILL.md +54 -0
  4. package/.agents/skills/harness-plan/SKILL.md +4 -3
  5. package/.agents/skills/harness-sentrux-setup/SKILL.md +57 -0
  6. package/.agents/skills/scrapling-web/SKILL.md +93 -0
  7. package/.pi/PACKAGING.md +1 -0
  8. package/.pi/SYSTEM.md +13 -15
  9. package/.pi/agents/harness/adversary.md +3 -0
  10. package/.pi/agents/harness/evaluator.md +3 -0
  11. package/.pi/agents/harness/executor.md +4 -1
  12. package/.pi/agents/harness/meta-optimizer.md +2 -1
  13. package/.pi/agents/harness/planner.md +22 -1
  14. package/.pi/agents/harness/sentrux-bootstrap.md +42 -0
  15. package/.pi/agents/harness/tie-breaker.md +2 -0
  16. package/.pi/extensions/harness-ask-user.ts +74 -0
  17. package/.pi/extensions/harness-subagents.ts +9 -0
  18. package/.pi/extensions/lib/ask-user/dialog.ts +260 -0
  19. package/.pi/extensions/lib/ask-user/fallback.ts +78 -0
  20. package/.pi/extensions/lib/ask-user/render.ts +66 -0
  21. package/.pi/extensions/lib/ask-user/schema.ts +69 -0
  22. package/.pi/extensions/lib/ask-user/types.ts +41 -0
  23. package/.pi/extensions/lib/ask-user/validate-core.mjs +79 -0
  24. package/.pi/extensions/lib/ask-user/validate.ts +92 -0
  25. package/.pi/extensions/lib/harness-subagents/agent-loader.ts +126 -0
  26. package/.pi/extensions/lib/harness-subagents/agent-manifest.ts +119 -0
  27. package/.pi/extensions/lib/harness-subagents/agent-parser.ts +87 -0
  28. package/.pi/extensions/lib/harness-subagents/blackboard-tool.ts +118 -0
  29. package/.pi/extensions/lib/harness-subagents/blackboard.ts +175 -0
  30. package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +27 -0
  31. package/.pi/extensions/lib/harness-subagents/types-blackboard.ts +27 -0
  32. package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +553 -0
  33. package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +637 -0
  34. package/.pi/extensions/lib/harness-subagents/vendored/agent-types.ts +175 -0
  35. package/.pi/extensions/lib/harness-subagents/vendored/context.ts +59 -0
  36. package/.pi/extensions/lib/harness-subagents/vendored/cross-extension-rpc.ts +134 -0
  37. package/.pi/extensions/lib/harness-subagents/vendored/custom-agents.ts +5 -0
  38. package/.pi/extensions/lib/harness-subagents/vendored/default-agents.ts +123 -0
  39. package/.pi/extensions/lib/harness-subagents/vendored/env.ts +43 -0
  40. package/.pi/extensions/lib/harness-subagents/vendored/group-join.ts +144 -0
  41. package/.pi/extensions/lib/harness-subagents/vendored/index.ts +2447 -0
  42. package/.pi/extensions/lib/harness-subagents/vendored/invocation-config.ts +52 -0
  43. package/.pi/extensions/lib/harness-subagents/vendored/memory.ts +182 -0
  44. package/.pi/extensions/lib/harness-subagents/vendored/model-resolver.ts +92 -0
  45. package/.pi/extensions/lib/harness-subagents/vendored/output-file.ts +115 -0
  46. package/.pi/extensions/lib/harness-subagents/vendored/prompts.ts +103 -0
  47. package/.pi/extensions/lib/harness-subagents/vendored/schedule-store.ts +177 -0
  48. package/.pi/extensions/lib/harness-subagents/vendored/schedule.ts +416 -0
  49. package/.pi/extensions/lib/harness-subagents/vendored/settings.ts +210 -0
  50. package/.pi/extensions/lib/harness-subagents/vendored/skill-loader.ts +108 -0
  51. package/.pi/extensions/lib/harness-subagents/vendored/types.ts +187 -0
  52. package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +637 -0
  53. package/.pi/extensions/lib/harness-subagents/vendored/ui/conversation-viewer.ts +324 -0
  54. package/.pi/extensions/lib/harness-subagents/vendored/ui/schedule-menu.ts +110 -0
  55. package/.pi/extensions/lib/harness-subagents/vendored/usage.ts +71 -0
  56. package/.pi/extensions/lib/harness-subagents/vendored/worktree.ts +195 -0
  57. package/.pi/extensions/lib/harness-vcc-settings.ts +50 -0
  58. package/.pi/extensions/ultimate-pi-vcc.ts +17 -0
  59. package/.pi/harness/README.md +2 -1
  60. package/.pi/harness/agents.manifest.json +80 -0
  61. package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +9 -5
  62. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +40 -0
  63. package/.pi/harness/docs/adrs/README.md +1 -0
  64. package/.pi/harness/env.harness.template +28 -0
  65. package/.pi/harness/sentrux/architecture.manifest.json +6 -1
  66. package/.pi/prompts/harness-auto.md +2 -2
  67. package/.pi/prompts/harness-plan.md +2 -2
  68. package/.pi/prompts/harness-router-tune.md +2 -2
  69. package/.pi/prompts/harness-run.md +1 -0
  70. package/.pi/prompts/harness-setup.md +179 -340
  71. package/.pi/scripts/README.md +6 -1
  72. package/.pi/scripts/harness-agents-manifest.mjs +123 -0
  73. package/.pi/scripts/harness-cli-verify.sh +60 -11
  74. package/.pi/scripts/harness-generate-model-router.mjs +242 -0
  75. package/.pi/scripts/harness-graphify-bootstrap.sh +1 -6
  76. package/.pi/scripts/harness-resolve-up-pkg.mjs +71 -0
  77. package/.pi/scripts/harness-seed-project-contracts.mjs +33 -1
  78. package/.pi/scripts/harness-sentrux-bootstrap.mjs +146 -0
  79. package/.pi/scripts/harness-sync-env.mjs +148 -0
  80. package/.pi/scripts/harness-verify.mjs +19 -0
  81. package/.pi/scripts/harness-web-search.md +33 -0
  82. package/.pi/scripts/harness-web.py +177 -0
  83. package/.pi/scripts/harness_web/__init__.py +1 -0
  84. package/.pi/scripts/harness_web/config.py +80 -0
  85. package/.pi/scripts/harness_web/output.py +55 -0
  86. package/.pi/scripts/harness_web/scrape.py +120 -0
  87. package/.pi/scripts/harness_web/search_ddg.py +106 -0
  88. package/.pi/scripts/release.sh +338 -0
  89. package/.pi/scripts/sentrux-rules-sync.mjs +29 -7
  90. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +8 -0
  91. package/.pi/scripts/vendor-sync-pi-vcc.sh +40 -0
  92. package/.pi/settings.example.json +1 -7
  93. package/.sentrux/rules.toml +1 -1
  94. package/AGENTS.md +1 -1
  95. package/CHANGELOG.md +14 -0
  96. package/THIRD_PARTY_NOTICES.md +8 -0
  97. package/package.json +16 -12
  98. package/vendor/pi-vcc/README.md +215 -0
  99. package/vendor/pi-vcc/UPSTREAM_PIN.md +12 -0
  100. package/vendor/pi-vcc/demo.gif +0 -0
  101. package/vendor/pi-vcc/index.ts +12 -0
  102. package/vendor/pi-vcc/package.json +26 -0
  103. package/vendor/pi-vcc/scripts/audit-sessions.ts +88 -0
  104. package/vendor/pi-vcc/scripts/benchmark-real-sessions.ts +25 -0
  105. package/vendor/pi-vcc/scripts/compare-before-after.ts +36 -0
  106. package/vendor/pi-vcc/scripts/dump-branch-output.ts +20 -0
  107. package/vendor/pi-vcc/src/commands/pi-vcc.ts +36 -0
  108. package/vendor/pi-vcc/src/commands/vcc-recall.ts +65 -0
  109. package/vendor/pi-vcc/src/core/brief.ts +381 -0
  110. package/vendor/pi-vcc/src/core/build-sections.ts +79 -0
  111. package/vendor/pi-vcc/src/core/content.ts +60 -0
  112. package/vendor/pi-vcc/src/core/filter-noise.ts +42 -0
  113. package/vendor/pi-vcc/src/core/format-recall.ts +27 -0
  114. package/vendor/pi-vcc/src/core/format.ts +49 -0
  115. package/vendor/pi-vcc/src/core/lineage.ts +26 -0
  116. package/vendor/pi-vcc/src/core/load-messages.ts +41 -0
  117. package/vendor/pi-vcc/src/core/normalize.ts +66 -0
  118. package/vendor/pi-vcc/src/core/recall-scope.ts +14 -0
  119. package/vendor/pi-vcc/src/core/render-entries.ts +55 -0
  120. package/vendor/pi-vcc/src/core/report.ts +237 -0
  121. package/vendor/pi-vcc/src/core/sanitize.ts +5 -0
  122. package/vendor/pi-vcc/src/core/search-entries.ts +221 -0
  123. package/vendor/pi-vcc/src/core/settings.ts +8 -0
  124. package/vendor/pi-vcc/src/core/skill-collapse.ts +35 -0
  125. package/vendor/pi-vcc/src/core/summarize.ts +157 -0
  126. package/vendor/pi-vcc/src/core/tool-args.ts +14 -0
  127. package/vendor/pi-vcc/src/details.ts +7 -0
  128. package/vendor/pi-vcc/src/extract/commits.ts +69 -0
  129. package/vendor/pi-vcc/src/extract/files.ts +80 -0
  130. package/vendor/pi-vcc/src/extract/goals.ts +79 -0
  131. package/vendor/pi-vcc/src/extract/preferences.ts +55 -0
  132. package/vendor/pi-vcc/src/hooks/before-compact.ts +314 -0
  133. package/vendor/pi-vcc/src/sections.ts +12 -0
  134. package/vendor/pi-vcc/src/tools/recall.ts +109 -0
  135. package/vendor/pi-vcc/src/types.ts +14 -0
  136. package/vendor/pi-vcc/tests/before-compact-hook.test.ts +204 -0
  137. package/vendor/pi-vcc/tests/before-compact.test.ts +145 -0
  138. package/vendor/pi-vcc/tests/brief.test.ts +206 -0
  139. package/vendor/pi-vcc/tests/build-sections.test.ts +59 -0
  140. package/vendor/pi-vcc/tests/compile.test.ts +80 -0
  141. package/vendor/pi-vcc/tests/content.test.ts +31 -0
  142. package/vendor/pi-vcc/tests/extract-goals.test.ts +86 -0
  143. package/vendor/pi-vcc/tests/extract-preferences.test.ts +30 -0
  144. package/vendor/pi-vcc/tests/filter-noise.test.ts +61 -0
  145. package/vendor/pi-vcc/tests/fixtures.ts +61 -0
  146. package/vendor/pi-vcc/tests/format-recall.test.ts +30 -0
  147. package/vendor/pi-vcc/tests/format.test.ts +62 -0
  148. package/vendor/pi-vcc/tests/lineage.test.ts +33 -0
  149. package/vendor/pi-vcc/tests/load-messages.test.ts +51 -0
  150. package/vendor/pi-vcc/tests/normalize.test.ts +97 -0
  151. package/vendor/pi-vcc/tests/real-sessions.test.ts +38 -0
  152. package/vendor/pi-vcc/tests/recall-expand.test.ts +15 -0
  153. package/vendor/pi-vcc/tests/recall-scope.test.ts +32 -0
  154. package/vendor/pi-vcc/tests/recall-tool-scope.test.ts +67 -0
  155. package/vendor/pi-vcc/tests/render-entries.test.ts +62 -0
  156. package/vendor/pi-vcc/tests/report.test.ts +44 -0
  157. package/vendor/pi-vcc/tests/sanitize.test.ts +24 -0
  158. package/vendor/pi-vcc/tests/search-entries.test.ts +144 -0
  159. package/vendor/pi-vcc/tests/support/load-session.ts +23 -0
  160. package/vendor/pi-vcc/tests/support/real-sessions.ts +51 -0
  161. package/.agents/skills/firecrawl/SKILL.md +0 -150
  162. package/.agents/skills/firecrawl/rules/install.md +0 -82
  163. package/.agents/skills/firecrawl/rules/security.md +0 -26
  164. package/.agents/skills/firecrawl-agent/SKILL.md +0 -57
  165. package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -67
  166. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -102
  167. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
  168. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
  169. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
  170. package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -68
  171. package/.agents/skills/firecrawl-build-search/SKILL.md +0 -68
  172. package/.agents/skills/firecrawl-crawl/SKILL.md +0 -58
  173. package/.agents/skills/firecrawl-download/SKILL.md +0 -69
  174. package/.agents/skills/firecrawl-interact/SKILL.md +0 -83
  175. package/.agents/skills/firecrawl-map/SKILL.md +0 -50
  176. package/.agents/skills/firecrawl-parse/SKILL.md +0 -61
  177. package/.agents/skills/firecrawl-scrape/SKILL.md +0 -68
  178. package/.agents/skills/firecrawl-search/SKILL.md +0 -59
  179. package/.pi/pi-vcc-config.json +0 -4
  180. package/firecrawl/.env.template +0 -62
  181. package/firecrawl/README.md +0 -49
  182. package/firecrawl/docker-compose.yaml +0 -201
  183. package/firecrawl/searxng/searxng.env +0 -3
  184. package/firecrawl/searxng/settings.yml +0 -85
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { compile } from "../src/core/summarize";
3
+ import {
4
+ userMsg,
5
+ assistantText,
6
+ assistantWithToolCall,
7
+ toolResult,
8
+ } from "./fixtures";
9
+
10
+ describe("compile", () => {
11
+ it("returns empty string for no messages", () => {
12
+ expect(compile({ messages: [] })).toBe("");
13
+ });
14
+
15
+ it("produces hybrid output with header + brief transcript", () => {
16
+ const r = compile({
17
+ messages: [
18
+ userMsg("Fix login bug"),
19
+ assistantWithToolCall("Read", { path: "auth.ts" }),
20
+ assistantText("Found the issue.\n1. Fix validation"),
21
+ ],
22
+ });
23
+ expect(r).toContain("[Session Goal]");
24
+ expect(r).toContain("Fix login bug");
25
+ expect(r).toContain("---");
26
+ expect(r).toContain("[user]\nFix login bug");
27
+ expect(r).toContain('* Read "auth.ts"');
28
+ expect(r).toContain("Found the issue.");
29
+ });
30
+
31
+ it("merges previous summary goals", () => {
32
+ const r = compile({
33
+ messages: [userMsg("New task")],
34
+ previousSummary: "[Session Goal]\n- Original goal\n\n---\n\n[user]\nOriginal goal",
35
+ });
36
+ expect(r).toContain("- Original goal");
37
+ expect(r).toContain("- New task");
38
+ });
39
+
40
+ it("appends brief transcript on merge", () => {
41
+ const previousSummary = [
42
+ "[Session Goal]\n- Original goal",
43
+ "---",
44
+ "[user]\nOriginal goal\n\n[assistant]\n* Read \"old.ts\"",
45
+ ].join("\n\n");
46
+ const r = compile({
47
+ previousSummary,
48
+ messages: [
49
+ userMsg("Next step"),
50
+ assistantWithToolCall("Read", { path: "new.ts" }),
51
+ ],
52
+ });
53
+ expect(r).toContain('* Read "old.ts"');
54
+ expect(r).toContain('* Read "new.ts"');
55
+ expect(r).toContain("Next step");
56
+ });
57
+
58
+ it("outstanding context is volatile (fresh only)", () => {
59
+ const previousSummary = "[Outstanding Context]\n- old blocker\n\n---\n\n[user]\nhi";
60
+ const r = compile({
61
+ previousSummary,
62
+ messages: [userMsg("continue")],
63
+ });
64
+ expect(r).not.toContain("old blocker");
65
+ });
66
+
67
+ it("caps long brief transcript with rolling window", () => {
68
+ // Build a very long previous transcript
69
+ const longTranscript = Array.from({ length: 200 }, (_, i) =>
70
+ `[user]\nmessage ${i}`
71
+ ).join("\n\n");
72
+ const previousSummary = `[Session Goal]\n- goal\n\n---\n\n${longTranscript}`;
73
+ const r = compile({
74
+ previousSummary,
75
+ messages: [userMsg("latest")],
76
+ });
77
+ expect(r).toContain("earlier lines omitted");
78
+ expect(r).toContain("latest");
79
+ });
80
+ });
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { textParts, textOf, clip, firstLine } from "../src/core/content";
3
+
4
+ describe("textParts", () => {
5
+ it("returns [] for undefined content", () => {
6
+ expect(textParts(undefined as any)).toEqual([]);
7
+ });
8
+
9
+ it("returns [] for null content", () => {
10
+ expect(textParts(null as any)).toEqual([]);
11
+ });
12
+
13
+ it("wraps string content", () => {
14
+ expect(textParts("hello")).toEqual(["hello"]);
15
+ });
16
+
17
+ it("extracts text parts from array content", () => {
18
+ const content = [
19
+ { type: "text" as const, text: "first" },
20
+ { type: "toolCall" as const, name: "x", id: "1", arguments: {} },
21
+ { type: "text" as const, text: "second" },
22
+ ];
23
+ expect(textParts(content)).toEqual(["first", "second"]);
24
+ });
25
+ });
26
+
27
+ describe("textOf", () => {
28
+ it("returns empty string for undefined content", () => {
29
+ expect(textOf(undefined as any)).toBe("");
30
+ });
31
+ });
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { extractGoals } from "../src/extract/goals";
3
+ import type { NormalizedBlock } from "../src/types";
4
+
5
+ describe("extractGoals", () => {
6
+ it("returns empty for no blocks", () => {
7
+ expect(extractGoals([])).toEqual([]);
8
+ });
9
+
10
+ it("returns empty when no user blocks", () => {
11
+ const blocks: NormalizedBlock[] = [
12
+ { kind: "assistant", text: "hello" },
13
+ ];
14
+ expect(extractGoals(blocks)).toEqual([]);
15
+ });
16
+
17
+ it("extracts first user message lines as goals", () => {
18
+ const blocks: NormalizedBlock[] = [
19
+ { kind: "user", text: "Fix login bug\nCheck auth flow" },
20
+ ];
21
+ const goals = extractGoals(blocks);
22
+ expect(goals).toEqual(["Fix login bug", "Check auth flow"]);
23
+ });
24
+
25
+ it("takes up to 6 lines from first user block", () => {
26
+ const blocks: NormalizedBlock[] = [
27
+ { kind: "user", text: "fix the login bug\ncheck auth flow\nupdate the tests\nrefactor utils\nclean up" },
28
+ ];
29
+ expect(extractGoals(blocks)).toHaveLength(5);
30
+ });
31
+
32
+ it("ignores subsequent user blocks", () => {
33
+ const blocks: NormalizedBlock[] = [
34
+ { kind: "user", text: "first goal" },
35
+ { kind: "assistant", text: "ok" },
36
+ { kind: "user", text: "second request" },
37
+ ];
38
+ expect(extractGoals(blocks)).toEqual(["first goal"]);
39
+ });
40
+
41
+ it("detects scope change with explicit pivot keywords", () => {
42
+ const blocks: NormalizedBlock[] = [
43
+ { kind: "user", text: "Fix login bug" },
44
+ { kind: "assistant", text: "ok" },
45
+ { kind: "user", text: "Actually, instead let's refactor the auth module" },
46
+ ];
47
+ const goals = extractGoals(blocks);
48
+ expect(goals).toContain("Fix login bug");
49
+ expect(goals).toContain("[Scope change]");
50
+ expect(goals.some((g) => g.includes("refactor"))).toBe(true);
51
+ });
52
+
53
+ it("detects scope change from new task statements", () => {
54
+ const blocks: NormalizedBlock[] = [
55
+ { kind: "user", text: "Fix login bug" },
56
+ { kind: "assistant", text: "done" },
57
+ { kind: "user", text: "Now implement the user registration flow" },
58
+ ];
59
+ const goals = extractGoals(blocks);
60
+ expect(goals).toContain("[Scope change]");
61
+ });
62
+
63
+ it("keeps latest scope change only", () => {
64
+ const blocks: NormalizedBlock[] = [
65
+ { kind: "user", text: "Fix login bug" },
66
+ { kind: "assistant", text: "done" },
67
+ { kind: "user", text: "Actually, fix the signup page instead" },
68
+ { kind: "assistant", text: "ok" },
69
+ { kind: "user", text: "Change of plan, implement password reset" },
70
+ ];
71
+ const goals = extractGoals(blocks);
72
+ const scopeIdx = goals.indexOf("[Scope change]");
73
+ expect(goals[scopeIdx + 1]).toContain("password reset");
74
+ });
75
+
76
+ it("skips noise short user messages as goals", () => {
77
+ const blocks: NormalizedBlock[] = [
78
+ { kind: "user", text: "ok" },
79
+ { kind: "assistant", text: "hello" },
80
+ { kind: "user", text: "Fix the authentication module" },
81
+ ];
82
+ const goals = extractGoals(blocks);
83
+ expect(goals[0]).toContain("Fix the authentication");
84
+ expect(goals.some((g) => g === "ok")).toBe(false);
85
+ });
86
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { extractPreferences } from "../src/extract/preferences";
3
+ import type { NormalizedBlock } from "../src/types";
4
+
5
+ describe("extractPreferences", () => {
6
+ it("returns empty for no blocks", () => {
7
+ expect(extractPreferences([])).toEqual([]);
8
+ });
9
+
10
+ it("captures preference patterns from user", () => {
11
+ const blocks: NormalizedBlock[] = [
12
+ { kind: "user", text: "I prefer TypeScript over JavaScript" },
13
+ ];
14
+ expect(extractPreferences(blocks).length).toBe(1);
15
+ });
16
+
17
+ it("ignores assistant blocks", () => {
18
+ const blocks: NormalizedBlock[] = [
19
+ { kind: "assistant", text: "I always use best practices" },
20
+ ];
21
+ expect(extractPreferences(blocks)).toEqual([]);
22
+ });
23
+
24
+ it("captures please use pattern", () => {
25
+ const blocks: NormalizedBlock[] = [
26
+ { kind: "user", text: "please use bun instead of node" },
27
+ ];
28
+ expect(extractPreferences(blocks).length).toBe(1);
29
+ });
30
+ });
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { filterNoise } from "../src/core/filter-noise";
3
+ import type { NormalizedBlock } from "../src/types";
4
+
5
+ describe("filterNoise", () => {
6
+ it("removes thinking blocks", () => {
7
+ const blocks: NormalizedBlock[] = [
8
+ { kind: "thinking", text: "hmm", redacted: false },
9
+ { kind: "assistant", text: "hello" },
10
+ ];
11
+ expect(filterNoise(blocks)).toEqual([{ kind: "assistant", text: "hello" }]);
12
+ });
13
+
14
+ it("removes noise tool calls and results", () => {
15
+ const blocks: NormalizedBlock[] = [
16
+ { kind: "tool_call", name: "TodoWrite", args: {} },
17
+ { kind: "tool_result", name: "TodoWrite", text: "ok", isError: false },
18
+ { kind: "tool_call", name: "Read", args: { path: "x.ts" } },
19
+ ];
20
+ const result = filterNoise(blocks);
21
+ expect(result).toHaveLength(1);
22
+ expect(result[0]).toEqual({ kind: "tool_call", name: "Read", args: { path: "x.ts" } });
23
+ });
24
+
25
+ it("removes user blocks that are pure XML wrappers", () => {
26
+ const blocks: NormalizedBlock[] = [
27
+ { kind: "user", text: "<system-reminder>some noise</system-reminder>" },
28
+ { kind: "user", text: "Fix the bug" },
29
+ ];
30
+ const result = filterNoise(blocks);
31
+ expect(result).toHaveLength(1);
32
+ expect(result[0]).toEqual({ kind: "user", text: "Fix the bug" });
33
+ });
34
+
35
+ it("cleans XML wrappers from user text but keeps real content", () => {
36
+ const blocks: NormalizedBlock[] = [
37
+ { kind: "user", text: "<system-reminder>noise</system-reminder>\nFix the login" },
38
+ ];
39
+ const result = filterNoise(blocks);
40
+ expect(result).toHaveLength(1);
41
+ expect((result[0] as any).text).toBe("Fix the login");
42
+ });
43
+
44
+ it("removes known noise strings", () => {
45
+ const blocks: NormalizedBlock[] = [
46
+ { kind: "user", text: "Continue from where you left off." },
47
+ { kind: "user", text: "real task" },
48
+ ];
49
+ const result = filterNoise(blocks);
50
+ expect(result).toHaveLength(1);
51
+ expect((result[0] as any).text).toBe("real task");
52
+ });
53
+
54
+ it("preserves non-noise tool calls", () => {
55
+ const blocks: NormalizedBlock[] = [
56
+ { kind: "tool_call", name: "Edit", args: { path: "a.ts" } },
57
+ { kind: "tool_result", name: "Edit", text: "ok", isError: false },
58
+ ];
59
+ expect(filterNoise(blocks)).toHaveLength(2);
60
+ });
61
+ });
@@ -0,0 +1,61 @@
1
+ import type { Message } from "@mariozechner/pi-ai";
2
+
3
+ const ts = Date.now();
4
+ const assistBase = {
5
+ api: "messages" as any,
6
+ provider: "anthropic" as any,
7
+ model: "test",
8
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
9
+ timestamp: ts,
10
+ };
11
+
12
+ export const userMsg = (text: string): Message => ({
13
+ role: "user",
14
+ content: text,
15
+ timestamp: ts,
16
+ });
17
+
18
+ export const assistantText = (text: string): Message => ({
19
+ role: "assistant",
20
+ content: [{ type: "text", text }],
21
+ ...assistBase,
22
+ stopReason: "stop",
23
+ });
24
+
25
+ export const assistantWithThinking = (
26
+ text: string,
27
+ thinking: string,
28
+ ): Message => ({
29
+ role: "assistant",
30
+ content: [
31
+ { type: "thinking", thinking },
32
+ { type: "text", text },
33
+ ],
34
+ ...assistBase,
35
+ stopReason: "stop",
36
+ });
37
+
38
+ export const assistantWithToolCall = (
39
+ name: string,
40
+ args: Record<string, unknown>,
41
+ ): Message => ({
42
+ role: "assistant",
43
+ content: [{ type: "toolCall", id: "tc_1", name, arguments: args }],
44
+ ...assistBase,
45
+ stopReason: "toolUse",
46
+ });
47
+
48
+ export const toolResult = (
49
+ name: string,
50
+ text: string,
51
+ isError = false,
52
+ ): Message => ({
53
+ role: "toolResult",
54
+ toolCallId: "tc_1",
55
+ toolName: name,
56
+ content: [{ type: "text", text }],
57
+ isError,
58
+ timestamp: ts,
59
+ });
60
+
61
+
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { formatRecallOutput } from "../src/core/format-recall";
3
+ import type { RenderedEntry } from "../src/core/render-entries";
4
+
5
+ describe("formatRecallOutput", () => {
6
+ it("shows no-match message with query", () => {
7
+ const r = formatRecallOutput([], "xyz");
8
+ expect(r).toContain('No matches for "xyz"');
9
+ });
10
+
11
+ it("shows no-entries message without query", () => {
12
+ expect(formatRecallOutput([])).toContain("No entries");
13
+ });
14
+
15
+ it("formats entries with index and role", () => {
16
+ const entries: RenderedEntry[] = [
17
+ { index: 0, role: "user", summary: "hello" },
18
+ ];
19
+ const r = formatRecallOutput(entries);
20
+ expect(r).toContain("#0 [user] hello");
21
+ });
22
+
23
+ it("shows match count with query", () => {
24
+ const entries: RenderedEntry[] = [
25
+ { index: 2, role: "assistant", summary: "done" },
26
+ ];
27
+ const r = formatRecallOutput(entries, "done");
28
+ expect(r).toContain('Found 1 matches for "done"');
29
+ });
30
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { formatSummary } from "../src/core/format";
3
+ import type { SectionData } from "../src/sections";
4
+
5
+ const empty: SectionData = {
6
+ sessionGoal: [],
7
+ outstandingContext: [],
8
+ filesAndChanges: [],
9
+ commits: [],
10
+ userPreferences: [],
11
+ briefTranscript: "",
12
+ transcriptEntries: [],
13
+ };
14
+
15
+ describe("formatSummary", () => {
16
+ it("returns empty string for all-empty sections", () => {
17
+ expect(formatSummary(empty)).toBe("");
18
+ });
19
+
20
+ it("formats a single header section", () => {
21
+ const data = {
22
+ ...empty,
23
+ sessionGoal: ["fix auth bug"],
24
+ };
25
+ const r = formatSummary(data);
26
+ expect(r).toContain("[Session Goal]");
27
+ expect(r).toContain("- fix auth bug");
28
+ });
29
+
30
+ it("separates header and brief transcript with ---", () => {
31
+ const data = {
32
+ ...empty,
33
+ sessionGoal: ["goal"],
34
+ briefTranscript: "[user]\ndo something",
35
+ };
36
+ const r = formatSummary(data);
37
+ expect(r).toContain("[Session Goal]");
38
+ expect(r).toContain("---");
39
+ expect(r).toContain("[user]\ndo something");
40
+ });
41
+
42
+ it("renders brief transcript alone when no header sections", () => {
43
+ const data = {
44
+ ...empty,
45
+ briefTranscript: "[user]\nhi\n\n[assistant]\nhello",
46
+ };
47
+ const r = formatSummary(data);
48
+ expect(r).toContain("[user]\nhi\n\n[assistant]\nhello");
49
+ });
50
+
51
+ it("joins multiple header sections with blank line", () => {
52
+ const data = {
53
+ ...empty,
54
+ sessionGoal: ["goal"],
55
+ outstandingContext: ["blocker"],
56
+ };
57
+ const r = formatSummary(data);
58
+ expect(r).toContain("[Session Goal]");
59
+ expect(r).toContain("[Outstanding Context]");
60
+ expect(r).toContain("\n\n");
61
+ });
62
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { getActiveLineageEntryIds } from "../src/core/lineage";
3
+
4
+ describe("getActiveLineageEntryIds", () => {
5
+ it("returns IDs from active branch", () => {
6
+ const ids = getActiveLineageEntryIds({
7
+ getBranch: () => [{ id: "a" }, { id: "b" }, { id: "c" }],
8
+ });
9
+ expect([...ids]).toEqual(["a", "b", "c"]);
10
+ });
11
+
12
+ it("falls back to getEntries when getBranch throws", () => {
13
+ const ids = getActiveLineageEntryIds({
14
+ getBranch: () => {
15
+ throw new Error("boom");
16
+ },
17
+ getEntries: () => [{ id: "x" }, { id: "y" }],
18
+ });
19
+ expect([...ids]).toEqual(["x", "y"]);
20
+ });
21
+
22
+ it("returns empty set when both branch and entries are unavailable", () => {
23
+ const ids = getActiveLineageEntryIds({
24
+ getBranch: () => {
25
+ throw new Error("boom");
26
+ },
27
+ getEntries: () => {
28
+ throw new Error("boom2");
29
+ },
30
+ });
31
+ expect(ids.size).toBe(0);
32
+ });
33
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { mkdtempSync, writeFileSync, rmSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ import { loadAllMessages } from "../src/core/load-messages";
6
+
7
+ describe("loadAllMessages", () => {
8
+ it("loads all message entries when no lineage filter is provided", () => {
9
+ const dir = mkdtempSync(join(tmpdir(), "pi-vcc-load-all-"));
10
+ const file = join(dir, "session.jsonl");
11
+ try {
12
+ const lines = [
13
+ JSON.stringify({ type: "session", id: "s1" }),
14
+ JSON.stringify({ type: "message", id: "m1", message: { role: "user", content: "u1" } }),
15
+ JSON.stringify({ type: "custom", id: "c1", customType: "x", data: {} }),
16
+ JSON.stringify({ type: "message", id: "m2", message: { role: "assistant", content: [{ type: "text", text: "a1" }] } }),
17
+ JSON.stringify({ type: "message", id: "m3", message: { role: "toolResult", toolName: "read", content: [{ type: "text", text: "ok" }] } }),
18
+ ];
19
+ writeFileSync(file, lines.join("\n") + "\n", "utf8");
20
+
21
+ const loaded = loadAllMessages(file, false);
22
+ expect(loaded.rendered).toHaveLength(3);
23
+ expect(loaded.rawMessages).toHaveLength(3);
24
+ expect(loaded.entryIds).toEqual(["m1", "m2", "m3"]);
25
+ expect(loaded.rendered.map((e) => e.index)).toEqual([0, 1, 2]);
26
+ } finally {
27
+ rmSync(dir, { recursive: true, force: true });
28
+ }
29
+ });
30
+
31
+ it("filters messages by allowed lineage entry IDs and preserves original message index", () => {
32
+ const dir = mkdtempSync(join(tmpdir(), "pi-vcc-load-filter-"));
33
+ const file = join(dir, "session.jsonl");
34
+ try {
35
+ const lines = [
36
+ JSON.stringify({ type: "message", id: "m1", message: { role: "user", content: "u1" } }),
37
+ JSON.stringify({ type: "message", id: "m2", message: { role: "assistant", content: [{ type: "text", text: "a1" }] } }),
38
+ JSON.stringify({ type: "message", id: "m3", message: { role: "user", content: "u2" } }),
39
+ ];
40
+ writeFileSync(file, lines.join("\n") + "\n", "utf8");
41
+
42
+ const loaded = loadAllMessages(file, false, new Set(["m2"]));
43
+ expect(loaded.rendered).toHaveLength(1);
44
+ expect(loaded.rawMessages).toHaveLength(1);
45
+ expect(loaded.entryIds).toEqual(["m2"]);
46
+ expect(loaded.rendered[0].index).toBe(1);
47
+ } finally {
48
+ rmSync(dir, { recursive: true, force: true });
49
+ }
50
+ });
51
+ });
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { normalize } from "../src/core/normalize";
3
+ import {
4
+ userMsg,
5
+ assistantText,
6
+ assistantWithThinking,
7
+ assistantWithToolCall,
8
+ toolResult,
9
+ } from "./fixtures";
10
+
11
+ describe("normalize", () => {
12
+ it("returns empty for empty input", () => {
13
+ expect(normalize([])).toEqual([]);
14
+ });
15
+
16
+ it("normalizes user message (string content)", () => {
17
+ const blocks = normalize([userMsg("fix the bug")]);
18
+ expect(blocks).toEqual([{ kind: "user", text: "fix the bug", sourceIndex: 0 }]);
19
+ });
20
+
21
+ it("normalizes assistant text message", () => {
22
+ const blocks = normalize([assistantText("done")]);
23
+ expect(blocks).toEqual([{ kind: "assistant", text: "done", sourceIndex: 0 }]);
24
+ });
25
+
26
+ it("normalizes assistant string content", () => {
27
+ const msg = { ...assistantText("done"), content: "plain text" } as any;
28
+ expect(normalize([msg])).toEqual([{ kind: "assistant", text: "plain text", sourceIndex: 0 }]);
29
+ });
30
+
31
+ it("splits assistant thinking + text", () => {
32
+ const blocks = normalize([assistantWithThinking("result", "hmm")]);
33
+ expect(blocks).toHaveLength(2);
34
+ expect(blocks[0]).toEqual({
35
+ kind: "thinking", text: "hmm", redacted: false, sourceIndex: 0,
36
+ });
37
+ expect(blocks[1]).toEqual({ kind: "assistant", text: "result", sourceIndex: 0 });
38
+ });
39
+
40
+ it("normalizes tool call", () => {
41
+ const blocks = normalize([assistantWithToolCall("Read", { path: "a.ts" })]);
42
+ expect(blocks).toEqual([{
43
+ kind: "tool_call", name: "Read", args: { path: "a.ts" }, sourceIndex: 0,
44
+ }]);
45
+ });
46
+
47
+ it("normalizes tool result", () => {
48
+ const blocks = normalize([toolResult("Read", "file contents")]);
49
+ expect(blocks).toEqual([{
50
+ kind: "tool_result", name: "Read",
51
+ text: "file contents", isError: false, sourceIndex: 0,
52
+ }]);
53
+ });
54
+
55
+ it("normalizes error tool result", () => {
56
+ const blocks = normalize([toolResult("Edit", "not found", true)]);
57
+ expect(blocks[0]).toMatchObject({
58
+ kind: "tool_result", isError: true,
59
+ });
60
+ });
61
+
62
+ it("handles mixed message sequence", () => {
63
+ const blocks = normalize([
64
+ userMsg("fix it"),
65
+ assistantWithToolCall("Read", { path: "x.ts" }),
66
+ toolResult("Read", "code"),
67
+ assistantText("done"),
68
+ ]);
69
+ expect(blocks).toHaveLength(4);
70
+ expect(blocks.map((b) => b.kind)).toEqual([
71
+ "user", "tool_call", "tool_result", "assistant",
72
+ ]);
73
+ });
74
+
75
+ it("produces image placeholder for user image content", () => {
76
+ const msg = {
77
+ role: "user" as const,
78
+ content: [
79
+ { type: "text" as const, text: "look at this" },
80
+ { type: "image" as const, data: "abc", mimeType: "image/png" },
81
+ ],
82
+ timestamp: Date.now(),
83
+ };
84
+ const blocks = normalize([msg]);
85
+ expect(blocks).toHaveLength(2);
86
+ expect(blocks[0]).toEqual({ kind: "user", text: "look at this", sourceIndex: 0 });
87
+ expect(blocks[1]).toEqual({ kind: "user", text: "[image: image/png]", sourceIndex: 0 });
88
+ });
89
+
90
+ it("skips unknown message roles gracefully", () => {
91
+ const weird = { role: "bashExecution", command: "ls", output: "files", exitCode: 0 } as any;
92
+ const blocks = normalize([weird]);
93
+ expect(blocks).toEqual([]);
94
+ });
95
+ });
96
+
97
+
@@ -0,0 +1,38 @@
1
+ import { beforeAll, describe, expect, it } from "bun:test";
2
+ import { buildCompactReport } from "../src/core/report";
3
+ import { prepareSessionSamples, readSourceStat, type SessionSample } from "./support/real-sessions";
4
+ import { loadSessionMessages } from "./support/load-session";
5
+
6
+ let samples: SessionSample[] = [];
7
+
8
+ beforeAll(async () => {
9
+ samples = await prepareSessionSamples(2);
10
+ });
11
+
12
+ describe("real session integration", () => {
13
+ it("compiles copied large sessions without mutating originals", async () => {
14
+ for (const sample of samples) {
15
+ const before = await readSourceStat(sample);
16
+ const loaded = loadSessionMessages(sample.copy);
17
+ const report = buildCompactReport({ messages: loaded.messages });
18
+ const after = await readSourceStat(sample);
19
+
20
+ expect(loaded.messageCount).toBeGreaterThan(0);
21
+ expect(loaded.skippedCount).toBeGreaterThanOrEqual(0);
22
+ expect(report.summary.length).toBeGreaterThan(0);
23
+ expect(report.summary).toContain("[");
24
+ expect(report.before.preview.length).toBeGreaterThan(0);
25
+ expect(report.after.summaryPreview.length).toBeGreaterThan(0);
26
+ expect(report.compression.charsBefore).toBeGreaterThan(0);
27
+ expect(report.recall.probes.length).toBeGreaterThan(0);
28
+ expect(after).toEqual(before);
29
+ }
30
+ });
31
+
32
+ it("uses read-only copied fixtures", () => {
33
+ for (const sample of samples) {
34
+ expect(sample.copy).not.toBe(sample.source);
35
+ expect(sample.copy.includes("pi-vcc-sessions-")).toBe(true);
36
+ }
37
+ });
38
+ });