ultimate-pi 0.4.0 → 0.5.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.
Files changed (94) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +15 -0
  2. package/.agents/skills/scrapling-web/SKILL.md +45 -40
  3. package/.agents/skills/wiki-autoresearch/SKILL.md +3 -3
  4. package/.pi/PACKAGING.md +3 -2
  5. package/.pi/SYSTEM.md +12 -13
  6. package/.pi/agents/pi-pi/agent-expert.md +3 -3
  7. package/.pi/extensions/harness-web-guard.ts +95 -0
  8. package/.pi/extensions/harness-web-tools.ts +209 -0
  9. package/.pi/extensions/lib/harness-vcc-settings.ts +50 -0
  10. package/.pi/extensions/lib/harness-web/run-cli.ts +92 -0
  11. package/.pi/extensions/ultimate-pi-vcc.ts +17 -0
  12. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +40 -0
  13. package/.pi/harness/docs/adrs/README.md +1 -0
  14. package/.pi/harness/env.harness.template +3 -1
  15. package/.pi/prompts/harness-setup.md +48 -2
  16. package/.pi/scripts/harness-cli-verify.sh +12 -3
  17. package/.pi/scripts/harness-searxng-bootstrap.mjs +270 -0
  18. package/.pi/scripts/harness-web-search.md +24 -5
  19. package/.pi/scripts/harness-web.py +24 -7
  20. package/.pi/scripts/harness_web/config.py +37 -3
  21. package/.pi/scripts/harness_web/output.py +8 -2
  22. package/.pi/scripts/harness_web/search.py +22 -0
  23. package/.pi/scripts/harness_web/search_ddg.py +1 -5
  24. package/.pi/scripts/harness_web/search_searxng.py +100 -0
  25. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +8 -0
  26. package/.pi/scripts/vendor-sync-pi-vcc.sh +40 -0
  27. package/.pi/settings.example.json +1 -6
  28. package/CHANGELOG.md +20 -6
  29. package/THIRD_PARTY_NOTICES.md +8 -22
  30. package/package.json +7 -6
  31. package/vendor/pi-vcc/README.md +215 -0
  32. package/vendor/pi-vcc/UPSTREAM_PIN.md +12 -0
  33. package/vendor/pi-vcc/demo.gif +0 -0
  34. package/vendor/pi-vcc/index.ts +12 -0
  35. package/vendor/pi-vcc/package.json +26 -0
  36. package/vendor/pi-vcc/scripts/audit-sessions.ts +88 -0
  37. package/vendor/pi-vcc/scripts/benchmark-real-sessions.ts +25 -0
  38. package/vendor/pi-vcc/scripts/compare-before-after.ts +36 -0
  39. package/vendor/pi-vcc/scripts/dump-branch-output.ts +20 -0
  40. package/vendor/pi-vcc/src/commands/pi-vcc.ts +36 -0
  41. package/vendor/pi-vcc/src/commands/vcc-recall.ts +65 -0
  42. package/vendor/pi-vcc/src/core/brief.ts +381 -0
  43. package/vendor/pi-vcc/src/core/build-sections.ts +79 -0
  44. package/vendor/pi-vcc/src/core/content.ts +60 -0
  45. package/vendor/pi-vcc/src/core/filter-noise.ts +42 -0
  46. package/vendor/pi-vcc/src/core/format-recall.ts +27 -0
  47. package/vendor/pi-vcc/src/core/format.ts +49 -0
  48. package/vendor/pi-vcc/src/core/lineage.ts +26 -0
  49. package/vendor/pi-vcc/src/core/load-messages.ts +41 -0
  50. package/vendor/pi-vcc/src/core/normalize.ts +66 -0
  51. package/vendor/pi-vcc/src/core/recall-scope.ts +14 -0
  52. package/vendor/pi-vcc/src/core/render-entries.ts +55 -0
  53. package/vendor/pi-vcc/src/core/report.ts +237 -0
  54. package/vendor/pi-vcc/src/core/sanitize.ts +5 -0
  55. package/vendor/pi-vcc/src/core/search-entries.ts +221 -0
  56. package/vendor/pi-vcc/src/core/settings.ts +8 -0
  57. package/vendor/pi-vcc/src/core/skill-collapse.ts +35 -0
  58. package/vendor/pi-vcc/src/core/summarize.ts +157 -0
  59. package/vendor/pi-vcc/src/core/tool-args.ts +14 -0
  60. package/vendor/pi-vcc/src/details.ts +7 -0
  61. package/vendor/pi-vcc/src/extract/commits.ts +69 -0
  62. package/vendor/pi-vcc/src/extract/files.ts +80 -0
  63. package/vendor/pi-vcc/src/extract/goals.ts +79 -0
  64. package/vendor/pi-vcc/src/extract/preferences.ts +55 -0
  65. package/vendor/pi-vcc/src/hooks/before-compact.ts +314 -0
  66. package/vendor/pi-vcc/src/sections.ts +12 -0
  67. package/vendor/pi-vcc/src/tools/recall.ts +109 -0
  68. package/vendor/pi-vcc/src/types.ts +14 -0
  69. package/vendor/pi-vcc/tests/before-compact-hook.test.ts +204 -0
  70. package/vendor/pi-vcc/tests/before-compact.test.ts +145 -0
  71. package/vendor/pi-vcc/tests/brief.test.ts +206 -0
  72. package/vendor/pi-vcc/tests/build-sections.test.ts +59 -0
  73. package/vendor/pi-vcc/tests/compile.test.ts +80 -0
  74. package/vendor/pi-vcc/tests/content.test.ts +31 -0
  75. package/vendor/pi-vcc/tests/extract-goals.test.ts +86 -0
  76. package/vendor/pi-vcc/tests/extract-preferences.test.ts +30 -0
  77. package/vendor/pi-vcc/tests/filter-noise.test.ts +61 -0
  78. package/vendor/pi-vcc/tests/fixtures.ts +61 -0
  79. package/vendor/pi-vcc/tests/format-recall.test.ts +30 -0
  80. package/vendor/pi-vcc/tests/format.test.ts +62 -0
  81. package/vendor/pi-vcc/tests/lineage.test.ts +33 -0
  82. package/vendor/pi-vcc/tests/load-messages.test.ts +51 -0
  83. package/vendor/pi-vcc/tests/normalize.test.ts +97 -0
  84. package/vendor/pi-vcc/tests/real-sessions.test.ts +38 -0
  85. package/vendor/pi-vcc/tests/recall-expand.test.ts +15 -0
  86. package/vendor/pi-vcc/tests/recall-scope.test.ts +32 -0
  87. package/vendor/pi-vcc/tests/recall-tool-scope.test.ts +67 -0
  88. package/vendor/pi-vcc/tests/render-entries.test.ts +62 -0
  89. package/vendor/pi-vcc/tests/report.test.ts +44 -0
  90. package/vendor/pi-vcc/tests/sanitize.test.ts +24 -0
  91. package/vendor/pi-vcc/tests/search-entries.test.ts +144 -0
  92. package/vendor/pi-vcc/tests/support/load-session.ts +23 -0
  93. package/vendor/pi-vcc/tests/support/real-sessions.ts +51 -0
  94. package/.pi/pi-vcc-config.json +0 -4
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { sanitize } from "../src/core/sanitize";
3
+
4
+ describe("sanitize", () => {
5
+ it("strips ANSI escape codes", () => {
6
+ expect(sanitize("\x1b[31mred\x1b[0m")).toBe("red");
7
+ });
8
+
9
+ it("normalizes CRLF to LF", () => {
10
+ expect(sanitize("a\r\nb\r\n")).toBe("a\nb\n");
11
+ });
12
+
13
+ it("strips bare CR", () => {
14
+ expect(sanitize("a\rb")).toBe("a\nb");
15
+ });
16
+
17
+ it("strips control characters but preserves newlines and tabs", () => {
18
+ expect(sanitize("a\x00b\tc\nd")).toBe("ab\tc\nd");
19
+ });
20
+
21
+ it("passes clean text unchanged", () => {
22
+ expect(sanitize("hello world")).toBe("hello world");
23
+ });
24
+ });
@@ -0,0 +1,144 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { searchEntries } from "../src/core/search-entries";
3
+ import type { RenderedEntry } from "../src/core/render-entries";
4
+ import type { Message } from "@mariozechner/pi-ai";
5
+
6
+ const entries: RenderedEntry[] = [
7
+ { index: 0, role: "user", summary: "Fix login bug" },
8
+ { index: 1, role: "assistant", summary: "Reading auth.ts" },
9
+ { index: 2, role: "tool_result", summary: "[Read] code here" },
10
+ { index: 3, role: "assistant", summary: "Found the root cause in auth module" },
11
+ ];
12
+
13
+ const messages: Message[] = [
14
+ { role: "user", content: "Fix login bug" } as any,
15
+ { role: "assistant", content: [{ type: "text", text: "Reading auth.ts" }] } as any,
16
+ { role: "toolResult", content: [{ type: "text", text: "[Read] code here" }] } as any,
17
+ { role: "assistant", content: [{ type: "text", text: "Found the root cause in auth module" }] } as any,
18
+ ];
19
+
20
+ describe("searchEntries", () => {
21
+ it("returns all for empty query", () => {
22
+ expect(searchEntries(entries, messages)).toEqual(entries);
23
+ expect(searchEntries(entries, messages, "")).toEqual(entries);
24
+ });
25
+
26
+ it("filters by single term", () => {
27
+ const r = searchEntries(entries, messages, "login");
28
+ expect(r).toHaveLength(1);
29
+ expect(r[0].index).toBe(0);
30
+ });
31
+
32
+ it("returns empty for no match", () => {
33
+ expect(searchEntries(entries, messages, "xyz123")).toEqual([]);
34
+ });
35
+
36
+ it("finds keyword beyond clip boundary in full content", () => {
37
+ const longText = "A".repeat(400) + " hidden_keyword here";
38
+ const longEntries: RenderedEntry[] = [
39
+ { index: 0, role: "user", summary: "A".repeat(300) },
40
+ ];
41
+ const longMsgs: Message[] = [
42
+ { role: "user", content: longText } as any,
43
+ ];
44
+ const r = searchEntries(longEntries, longMsgs, "hidden_keyword");
45
+ expect(r).toHaveLength(1);
46
+ expect(r[0].snippet).toContain("hidden_keyword");
47
+ });
48
+
49
+ it("returns snippet around matched term", () => {
50
+ const r = searchEntries(entries, messages, "root");
51
+ expect(r).toHaveLength(1);
52
+ expect(r[0].snippet).toBeDefined();
53
+ expect(r[0].snippet).toContain("root");
54
+ });
55
+
56
+ // ── regex support ──
57
+
58
+ it("supports regex pattern: alternation", () => {
59
+ const r = searchEntries(entries, messages, "login|auth");
60
+ expect(r).toHaveLength(3); // "login bug", "auth.ts", "auth module"
61
+ expect(r.map((h) => h.index).sort()).toEqual([0, 1, 3]);
62
+ });
63
+
64
+ it("supports regex pattern: wildcard", () => {
65
+ const r = searchEntries(entries, messages, "Read.*auth");
66
+ expect(r).toHaveLength(1);
67
+ expect(r[0].index).toBe(1);
68
+ });
69
+
70
+ it("falls back to escaped literal for invalid regex", () => {
71
+ const extraEntries: RenderedEntry[] = [
72
+ { index: 0, role: "user", summary: "test (foo" },
73
+ { index: 1, role: "assistant", summary: "no match here" },
74
+ ];
75
+ const extraMsgs: Message[] = [
76
+ { role: "user", content: "error with (foo pattern" } as any,
77
+ { role: "assistant", content: [{ type: "text", text: "no match here" }] } as any,
78
+ ];
79
+ const r = searchEntries(extraEntries, extraMsgs, "(foo");
80
+ expect(r).toHaveLength(1);
81
+ expect(r[0].index).toBe(0);
82
+ });
83
+
84
+ it("regex is case-insensitive", () => {
85
+ const r = searchEntries(entries, messages, "FIX|ROOT");
86
+ expect(r).toHaveLength(2);
87
+ });
88
+
89
+ // ── natural language queries (OR logic + ranking) ──
90
+
91
+ it("natural language query uses OR logic", () => {
92
+ // "root cause auth" -- matches entries containing ANY of these terms
93
+ const r = searchEntries(entries, messages, "root cause auth");
94
+ expect(r.length).toBeGreaterThanOrEqual(2); // #3 has all 3, #1 has auth
95
+ // Best match (highest BM25) should come first
96
+ expect(r[0].index).toBe(3); // "Found the root cause in auth module" matches all 3
97
+ });
98
+
99
+ it("natural language ranks by BM25 score", () => {
100
+ const r = searchEntries(entries, messages, "root cause auth");
101
+ // Top result has more terms matched = higher BM25 score
102
+ expect(r[0].matchCount!).toBeGreaterThanOrEqual(r[r.length - 1].matchCount!);
103
+ });
104
+
105
+ it("filters stopwords from queries", () => {
106
+ // "the root cause of it" → stopwords: the, of, it → meaningful: root, cause
107
+ const r = searchEntries(entries, messages, "the root cause of it");
108
+ expect(r).toHaveLength(1);
109
+ expect(r[0].index).toBe(3);
110
+ });
111
+
112
+ it("keeps all terms if all are stopwords", () => {
113
+ // When all terms are stopwords, keep them (don't drop everything)
114
+ // "the" appears in "Found the root cause" so it matches
115
+ const r = searchEntries(entries, messages, "the");
116
+ expect(r.length).toBeGreaterThan(0);
117
+ });
118
+
119
+ // ── line-based snippet ──
120
+
121
+ it("snippet shows context lines around match", () => {
122
+ const multiline = "line 0\nline 1\nline 2 TARGET\nline 3\nline 4\nline 5";
123
+ const e: RenderedEntry[] = [{ index: 0, role: "user", summary: "test" }];
124
+ const m: Message[] = [{ role: "user", content: multiline } as any];
125
+ const r = searchEntries(e, m, "TARGET");
126
+ expect(r).toHaveLength(1);
127
+ const snip = r[0].snippet!;
128
+ expect(snip).toContain("line 2 TARGET");
129
+ expect(snip).toContain("line 0");
130
+ expect(snip).toContain("line 4");
131
+ expect(snip).not.toContain("line 5");
132
+ });
133
+
134
+ it("snippet handles match at beginning", () => {
135
+ const multiline = "TARGET here\nline 1\nline 2\nline 3";
136
+ const e: RenderedEntry[] = [{ index: 0, role: "user", summary: "test" }];
137
+ const m: Message[] = [{ role: "user", content: multiline } as any];
138
+ const r = searchEntries(e, m, "TARGET");
139
+ const snip = r[0].snippet!;
140
+ expect(snip).toContain("TARGET here");
141
+ expect(snip).toContain("line 2");
142
+ expect(snip).not.toContain("line 3");
143
+ });
144
+ });
@@ -0,0 +1,23 @@
1
+ import { buildSessionContext, loadEntriesFromFile } from "../../node_modules/@mariozechner/pi-coding-agent/dist/core/session-manager.js";
2
+ import type { Message } from "@mariozechner/pi-ai";
3
+
4
+ export interface LoadedSession {
5
+ messageCount: number;
6
+ skippedCount: number;
7
+ messages: Message[];
8
+ }
9
+
10
+ export const loadSessionMessages = (file: string): LoadedSession => {
11
+ const entries = loadEntriesFromFile(file);
12
+ const sessionEntries = entries.filter((entry) => entry.type !== "header");
13
+ const context = buildSessionContext(sessionEntries as any);
14
+ const messages = (context.messages as any[]).filter(
15
+ (msg): msg is Message =>
16
+ msg && typeof msg.role === "string" && "content" in msg,
17
+ );
18
+ return {
19
+ messageCount: messages.length,
20
+ skippedCount: context.messages.length - messages.length,
21
+ messages,
22
+ };
23
+ };
@@ -0,0 +1,51 @@
1
+ import { mkdir, mkdtemp, copyFile, chmod, readdir, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join, basename } from "node:path";
4
+
5
+ const SESSION_ROOT = join(process.env.HOME ?? "", ".pi/agent/sessions");
6
+
7
+ export interface SessionSample {
8
+ source: string;
9
+ copy: string;
10
+ size: number;
11
+ mtimeMs: number;
12
+ }
13
+
14
+ const walk = async (dir: string): Promise<string[]> => {
15
+ const names = await readdir(dir, { withFileTypes: true });
16
+ const out: string[] = [];
17
+ for (const name of names) {
18
+ const path = join(dir, name.name);
19
+ if (name.isDirectory()) out.push(...await walk(path));
20
+ else if (name.isFile() && path.endsWith(".jsonl")) out.push(path);
21
+ }
22
+ return out;
23
+ };
24
+
25
+ const pickLargest = async (limit: number): Promise<string[]> => {
26
+ const files = await walk(SESSION_ROOT);
27
+ const sized = await Promise.all(
28
+ files.map(async (file) => ({ file, size: (await stat(file)).size })),
29
+ );
30
+ return sized.sort((a, b) => b.size - a.size).slice(0, limit).map((x) => x.file);
31
+ };
32
+
33
+ export const prepareSessionSamples = async (limit = 2): Promise<SessionSample[]> => {
34
+ const selected = await pickLargest(limit);
35
+ const dir = await mkdtemp(join(tmpdir(), "pi-vcc-sessions-"));
36
+ await mkdir(dir, { recursive: true });
37
+ const samples: SessionSample[] = [];
38
+ for (const source of selected) {
39
+ const srcStat = await stat(source);
40
+ const copy = join(dir, basename(source));
41
+ await copyFile(source, copy);
42
+ await chmod(copy, 0o444);
43
+ samples.push({ source, copy, size: srcStat.size, mtimeMs: srcStat.mtimeMs });
44
+ }
45
+ return samples;
46
+ };
47
+
48
+ export const readSourceStat = async (sample: SessionSample) => {
49
+ const s = await stat(sample.source);
50
+ return { size: s.size, mtimeMs: s.mtimeMs };
51
+ };
@@ -1,4 +0,0 @@
1
- {
2
- "overrideDefaultCompaction": true,
3
- "debug": false
4
- }