pi-harness-runtime 1.1.49 → 1.1.71-3

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 (203) hide show
  1. package/CHANGELOG.md +30 -9
  2. package/README.md +119 -0
  3. package/harness/langchain/agents.d.ts +143 -0
  4. package/harness/langchain/agents.test.d.ts +2 -0
  5. package/harness/langchain/agents.test.ts +0 -1
  6. package/harness/langchain/agents.ts +196 -13
  7. package/harness/langchain/checkpointer.d.ts +48 -0
  8. package/harness/langchain/checkpointer.test.d.ts +8 -0
  9. package/harness/langchain/cron-cli.d.ts +12 -0
  10. package/harness/langchain/cron-cli.ts +169 -0
  11. package/harness/langchain/daemon.d.ts +161 -0
  12. package/harness/langchain/daemon.test.d.ts +11 -0
  13. package/harness/langchain/daemon.test.ts +62 -5
  14. package/harness/langchain/daemon.ts +231 -12
  15. package/harness/langchain/file-tools.d.ts +97 -0
  16. package/harness/langchain/file-tools.ts +29 -11
  17. package/harness/langchain/graph.d.ts +706 -0
  18. package/harness/langchain/graph.ts +184 -26
  19. package/harness/langchain/model-router.d.ts +63 -0
  20. package/harness/langchain/model-router.test.d.ts +2 -0
  21. package/harness/langchain/model-router.test.ts +191 -0
  22. package/harness/langchain/model-router.ts +188 -0
  23. package/harness/langchain/models.d.ts +72 -0
  24. package/harness/langchain/models.ts +111 -49
  25. package/harness/langchain/ping-pong-middleware.d.ts +28 -0
  26. package/harness/langchain/ping-pong-middleware.test.d.ts +2 -0
  27. package/harness/langchain/ping-pong-middleware.test.ts +69 -0
  28. package/harness/langchain/ping-pong-middleware.ts +204 -0
  29. package/harness/langchain/run.d.ts +15 -0
  30. package/harness/langchain/run.ts +15 -3
  31. package/harness/langchain/scoreboard.test.d.ts +13 -0
  32. package/harness/langchain/scoreboard.test.ts +28 -5
  33. package/harness/langchain/status-line.d.ts +91 -0
  34. package/harness/langchain/status-line.test.d.ts +8 -0
  35. package/harness/langchain/surge.d.ts +103 -0
  36. package/harness/langchain/surge.test.d.ts +10 -0
  37. package/harness/langchain/surge.test.ts +179 -0
  38. package/harness/langchain/surge.ts +188 -19
  39. package/harness/langchain/widget.d.ts +105 -0
  40. package/harness/langchain/widget.test.d.ts +8 -0
  41. package/harness/ssh-detach-interceptor.test.ts +263 -0
  42. package/harness/ssh-detach-interceptor.ts +438 -0
  43. package/index.ts +212 -47
  44. package/package.json +4 -2
  45. package/packages/a2a-adapter/package.json +1 -1
  46. package/packages/architecture-generator/package.json +1 -1
  47. package/packages/auth/package.json +1 -1
  48. package/packages/autonomous-refactor/package.json +1 -1
  49. package/packages/autonomous-runtime/package.json +1 -1
  50. package/packages/cache-strategy/package.json +1 -1
  51. package/packages/capability-registry/package.json +1 -1
  52. package/packages/checkpoint/package.json +1 -1
  53. package/packages/cli-plugin-sdk/package.json +1 -1
  54. package/packages/clipboard/package.json +1 -1
  55. package/packages/clipboard-plugin/package.json +1 -1
  56. package/packages/code-generation/package.json +1 -1
  57. package/packages/code-review/package.json +1 -1
  58. package/packages/codex-adapter/package.json +1 -1
  59. package/packages/config-capture/package.json +1 -1
  60. package/packages/context-compiler/package.json +1 -1
  61. package/packages/context-discovery/package.json +1 -1
  62. package/packages/context-manager/package.json +1 -1
  63. package/packages/cookie-sanitizer/package.json +1 -1
  64. package/packages/cost-optimizer/package.json +1 -1
  65. package/packages/dependency-analyzer/package.json +1 -1
  66. package/packages/django-plugin/package.json +1 -1
  67. package/packages/doc-generator/package.json +1 -1
  68. package/packages/evaluation-engine/package.json +1 -1
  69. package/packages/evaluation-runner/package.json +1 -1
  70. package/packages/event-bus/package.json +1 -1
  71. package/packages/event-store/package.json +1 -1
  72. package/packages/experience-replay/package.json +1 -1
  73. package/packages/feedback-collector/package.json +1 -1
  74. package/packages/file-copy-helper/package.json +1 -1
  75. package/packages/framework-detector/package.json +1 -1
  76. package/packages/framework-plugin-sdk/package.json +1 -1
  77. package/packages/frappe-plugin/package.json +1 -1
  78. package/packages/generic-web-plugin/package.json +1 -1
  79. package/packages/health-monitor/package.json +1 -1
  80. package/packages/intent-analyzer/dist/src/index.d.ts +2 -0
  81. package/packages/intent-analyzer/dist/src/index.d.ts.map +1 -1
  82. package/packages/intent-analyzer/dist/src/index.js +1 -0
  83. package/packages/intent-analyzer/dist/src/index.js.map +1 -1
  84. package/packages/intent-analyzer/dist/src/ping-pong.d.ts +56 -0
  85. package/packages/intent-analyzer/dist/src/ping-pong.d.ts.map +1 -0
  86. package/packages/intent-analyzer/dist/src/ping-pong.js +286 -0
  87. package/packages/intent-analyzer/dist/src/ping-pong.js.map +1 -0
  88. package/packages/intent-analyzer/package.json +1 -1
  89. package/packages/intent-analyzer/src/index.ts +13 -0
  90. package/packages/intent-analyzer/src/ping-pong.ts +387 -0
  91. package/packages/intent-analyzer/test/ping-pong.test.ts +96 -0
  92. package/packages/jev-judge/README.md +234 -0
  93. package/packages/jev-judge/dist/decision-engine.d.ts +100 -0
  94. package/packages/jev-judge/dist/decision-engine.d.ts.map +1 -0
  95. package/packages/jev-judge/dist/decision-engine.js +151 -0
  96. package/packages/jev-judge/dist/decision-engine.js.map +1 -0
  97. package/packages/jev-judge/dist/e2e-judge.d.ts +61 -0
  98. package/packages/jev-judge/dist/e2e-judge.d.ts.map +1 -0
  99. package/packages/jev-judge/dist/e2e-judge.js +268 -0
  100. package/packages/jev-judge/dist/e2e-judge.js.map +1 -0
  101. package/packages/jev-judge/dist/flaky-detector.d.ts +34 -0
  102. package/packages/jev-judge/dist/flaky-detector.d.ts.map +1 -0
  103. package/packages/jev-judge/dist/flaky-detector.js +154 -0
  104. package/packages/jev-judge/dist/flaky-detector.js.map +1 -0
  105. package/packages/jev-judge/dist/index.d.ts +77 -0
  106. package/packages/jev-judge/dist/index.d.ts.map +1 -0
  107. package/packages/jev-judge/dist/index.js +271 -0
  108. package/packages/jev-judge/dist/index.js.map +1 -0
  109. package/packages/jev-judge/dist/types.d.ts +97 -0
  110. package/packages/jev-judge/dist/types.d.ts.map +1 -0
  111. package/packages/jev-judge/dist/types.js +8 -0
  112. package/packages/jev-judge/dist/types.js.map +1 -0
  113. package/packages/jev-judge/package.json +46 -0
  114. package/packages/jev-judge/src/decision-engine.ts +231 -0
  115. package/packages/jev-judge/src/e2e-judge.ts +364 -0
  116. package/packages/jev-judge/src/flaky-detector.ts +190 -0
  117. package/packages/jev-judge/src/index.ts +345 -0
  118. package/packages/jev-judge/src/types.ts +131 -0
  119. package/packages/knowledge-graph/package.json +1 -1
  120. package/packages/knowledge-retrieval/package.json +1 -1
  121. package/packages/laravel-plugin/package.json +1 -1
  122. package/packages/learning-engine/package.json +1 -1
  123. package/packages/mcp-adapter/package.json +1 -1
  124. package/packages/memory-engine/package.json +1 -1
  125. package/packages/milestone-manager/package.json +1 -1
  126. package/packages/model-registry/package.json +1 -1
  127. package/packages/nextjs-plugin/package.json +1 -1
  128. package/packages/notification/package.json +1 -1
  129. package/packages/observability/package.json +1 -1
  130. package/packages/okf-indexer/package.json +1 -1
  131. package/packages/performance-optimizer/package.json +1 -1
  132. package/packages/privilege-broker/package.json +1 -1
  133. package/packages/project-analyzer/package.json +1 -1
  134. package/packages/project-bootstrap/package.json +1 -1
  135. package/packages/projection-engine/package.json +1 -1
  136. package/packages/prompt-compiler/package.json +1 -1
  137. package/packages/prompt-versioning/package.json +1 -1
  138. package/packages/provider-adapter-sdk/package.json +1 -1
  139. package/packages/provider-router/package.json +1 -1
  140. package/packages/provider-selector/package.json +1 -1
  141. package/packages/providers/package.json +1 -1
  142. package/packages/quota-manager/package.json +1 -1
  143. package/packages/rate-limiter/package.json +1 -1
  144. package/packages/react-vite-plugin/package.json +1 -1
  145. package/packages/release-manager/package.json +1 -1
  146. package/packages/requirement-compiler/package.json +1 -1
  147. package/packages/runtime/package.json +1 -1
  148. package/packages/scheduler/package.json +1 -1
  149. package/packages/scheduler-adapter/package.json +1 -1
  150. package/packages/session/package.json +1 -1
  151. package/packages/session-api/package.json +1 -1
  152. package/packages/session-export/package.json +1 -1
  153. package/packages/shared-context/package.json +1 -1
  154. package/packages/skill-mcp-client/package.json +1 -1
  155. package/packages/skill-registry/package.json +1 -1
  156. package/packages/sprint-planner/package.json +1 -1
  157. package/packages/subscription-engine/package.json +1 -1
  158. package/packages/task-compiler/package.json +1 -1
  159. package/packages/tencentdb-memory/package.json +1 -1
  160. package/packages/tencentdb-sync/package.json +1 -1
  161. package/packages/test-data-generator/package.json +1 -1
  162. package/packages/test-generator/package.json +1 -1
  163. package/packages/todo-bd-sync/README.md +71 -0
  164. package/packages/todo-bd-sync/package.json +1 -1
  165. package/packages/todo-bd-sync/src/extension.ts +16 -1
  166. package/packages/token-estimation/package.json +1 -1
  167. package/packages/token-optimizer/package.json +1 -1
  168. package/packages/trajectory/dist/src/approved-patterns.d.ts +51 -0
  169. package/packages/trajectory/dist/src/approved-patterns.d.ts.map +1 -0
  170. package/packages/trajectory/dist/src/approved-patterns.js +134 -0
  171. package/packages/trajectory/dist/src/approved-patterns.js.map +1 -0
  172. package/packages/trajectory/dist/src/index.d.ts +13 -0
  173. package/packages/trajectory/dist/src/index.d.ts.map +1 -0
  174. package/packages/trajectory/dist/src/index.js +12 -0
  175. package/packages/trajectory/dist/src/index.js.map +1 -0
  176. package/packages/trajectory/dist/src/store.d.ts +69 -0
  177. package/packages/trajectory/dist/src/store.d.ts.map +1 -0
  178. package/packages/trajectory/dist/src/store.js +253 -0
  179. package/packages/trajectory/dist/src/store.js.map +1 -0
  180. package/packages/trajectory/dist/src/types.d.ts +73 -0
  181. package/packages/trajectory/dist/src/types.d.ts.map +1 -0
  182. package/packages/trajectory/dist/src/types.js +9 -0
  183. package/packages/trajectory/dist/src/types.js.map +1 -0
  184. package/packages/trajectory/dist/test/store.test.d.ts +2 -0
  185. package/packages/trajectory/dist/test/store.test.d.ts.map +1 -0
  186. package/packages/trajectory/dist/test/store.test.js +228 -0
  187. package/packages/trajectory/dist/test/store.test.js.map +1 -0
  188. package/packages/trajectory/package.json +29 -0
  189. package/packages/trajectory/src/approved-patterns.ts +180 -0
  190. package/packages/trajectory/src/index.ts +26 -0
  191. package/packages/trajectory/src/store.ts +285 -0
  192. package/packages/trajectory/src/types.ts +84 -0
  193. package/packages/trajectory/test/store.test.d.ts +2 -0
  194. package/packages/trajectory/test/store.test.ts +250 -0
  195. package/packages/tui/package.json +1 -1
  196. package/packages/types/package.json +1 -1
  197. package/packages/workflow-events/package.json +1 -1
  198. package/packages/workspace-scanner/package.json +1 -1
  199. package/packages/worktree/package.json +1 -1
  200. package/packages/write-review/package.json +1 -1
  201. package/packages/write-review/src/blackboard.ts +38 -5
  202. package/packages/write-review/src/types.ts +13 -0
  203. package/skills/harness-runtime/SKILL.md +67 -4
@@ -0,0 +1,285 @@
1
+ /**
2
+ * TrajectoryStore — persists Trajectory records to `~/.pi-harness/trajectories/`.
3
+ *
4
+ * Format: newline-delimited JSON (NDJSON), one Trajectory per line.
5
+ * - Append-only writes (no locking needed for single-daemon writes)
6
+ * - Grep-friendly for manual inspection
7
+ * - Compact: no document overhead like JSON-array-with-commas
8
+ *
9
+ * File naming: `YYYY-MM/YYYY-MM-DD.ndjson`
10
+ * - Daily rotation keeps files small and manageable
11
+ * - Directory by month for easy archival
12
+ *
13
+ * Directory structure:
14
+ * ~/.pi-harness/trajectories/
15
+ * 2025-07/
16
+ * 2025-07-14.ndjson
17
+ * 2025-07-15.ndjson
18
+ */
19
+
20
+ import {
21
+ appendFileSync,
22
+ existsSync,
23
+ mkdirSync,
24
+ readdirSync,
25
+ readFileSync,
26
+ } from "node:fs";
27
+ import { dirname, join } from "node:path";
28
+ import { homedir } from "node:os";
29
+ import { randomUUID } from "node:crypto";
30
+ import type {
31
+ Trajectory,
32
+ TrajectorySummary,
33
+ TrajectoryStats,
34
+ TrajectoryClassification,
35
+ TrajectoryLabel,
36
+ } from "./types.js";
37
+ export type {
38
+ Trajectory,
39
+ TrajectorySummary,
40
+ TrajectoryStats,
41
+ TrajectoryClassification,
42
+ TrajectoryLabel,
43
+ } from "./types.js";
44
+
45
+ const DEFAULT_TRAJ_DIR = ".pi-harness/trajectories";
46
+
47
+ function getTrajDir(dir?: string): string {
48
+ const base = dir ?? DEFAULT_TRAJ_DIR;
49
+ // Absolute paths (starting with / or ~) are used as-is
50
+ if (base.startsWith("/") || base.startsWith("~")) return base;
51
+ return join(homedir(), base);
52
+ }
53
+
54
+ function getDatePath(
55
+ dir: string,
56
+ timestamp: Date = new Date(),
57
+ ): { dir: string; file: string } {
58
+ const y = timestamp.getFullYear();
59
+ const m = String(timestamp.getMonth() + 1).padStart(2, "0");
60
+ const d = String(timestamp.getDate()).padStart(2, "0");
61
+ return {
62
+ dir: join(dir, `${y}-${m}`),
63
+ file: join(dir, `${y}-${m}`, `${y}-${m}-${d}.ndjson`),
64
+ };
65
+ }
66
+
67
+ /** Ensure the directory and daily file exist. */
68
+ function ensureFile(file: string): void {
69
+ mkdirSync(dirname(file), { recursive: true });
70
+ }
71
+
72
+ /** Parse a single NDJSON line, skipping malformed lines. */
73
+ function parseLine(line: string): Trajectory | null {
74
+ try {
75
+ return JSON.parse(line) as Trajectory;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ /** Read all trajectory files for a given directory. */
82
+ function readTrajDir(dir: string): Trajectory[] {
83
+ if (!existsSync(dir)) return [];
84
+ const trajectories: Trajectory[] = [];
85
+ for (const entry of readdirSync(dir)) {
86
+ if (!entry.endsWith(".ndjson")) continue;
87
+ const content = readFileSync(join(dir, entry), "utf8");
88
+ for (const line of content.split("\n")) {
89
+ if (!line.trim()) continue;
90
+ const t = parseLine(line);
91
+ if (t) trajectories.push(t);
92
+ }
93
+ }
94
+ return trajectories;
95
+ }
96
+
97
+ /**
98
+ * TrajectoryStore — append-only persistence for write-review cycle records.
99
+ *
100
+ * Usage:
101
+ * ```ts
102
+ * const store = new TrajectoryStore();
103
+ *
104
+ * // At cycle start
105
+ * const id = store.start("fix the login bug");
106
+ *
107
+ * // At cycle finish
108
+ * store.append({
109
+ * id,
110
+ * taskRequest: "fix the login bug",
111
+ * createdAt: new Date(startMs).toISOString(),
112
+ * durationMs: Date.now() - startMs,
113
+ * iterations: 2,
114
+ * verdict: "approved",
115
+ * reason: "reviewer approved",
116
+ * plan: "...",
117
+ * code: "...",
118
+ * files: ["auth.ts"],
119
+ * comments: [],
120
+ * summary: "looks good",
121
+ * classified: false,
122
+ * });
123
+ * ```
124
+ */
125
+ export class TrajectoryStore {
126
+ private readonly _trajDir: string;
127
+
128
+ constructor(trajDir?: string) {
129
+ this._trajDir = getTrajDir(trajDir);
130
+ }
131
+
132
+ /** List all trajectories, newest first. */
133
+ list(): Trajectory[] {
134
+ if (!existsSync(this._trajDir)) return [];
135
+
136
+ const all: Trajectory[] = [];
137
+ for (const monthDir of readdirSync(this._trajDir)) {
138
+ const monthPath = join(this._trajDir, monthDir);
139
+ if (!existsSync(monthPath)) continue;
140
+ all.push(...readTrajDir(monthPath));
141
+ }
142
+ // Sort newest first
143
+ all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
144
+ return all;
145
+ }
146
+
147
+ /** Summaries only (lightweight list). */
148
+ listSummaries(): TrajectorySummary[] {
149
+ return this.list().map((t) => ({
150
+ id: t.id,
151
+ taskRequest: t.taskRequest,
152
+ createdAt: t.createdAt,
153
+ durationMs: t.durationMs,
154
+ iterations: t.iterations,
155
+ verdict: t.verdict,
156
+ reason: t.reason,
157
+ files: t.files,
158
+ classified: t.classified,
159
+ }));
160
+ }
161
+
162
+ /** Append a completed trajectory record. */
163
+ append(trajectory: Trajectory): void {
164
+ const { file } = getDatePath(this._trajDir, new Date(trajectory.createdAt));
165
+ ensureFile(file);
166
+ appendFileSync(file, JSON.stringify(trajectory) + "\n", "utf8");
167
+ }
168
+
169
+ /** Start a new trajectory — returns a UUID for the cycle. */
170
+ start(_request: string): string {
171
+ return randomUUID();
172
+ }
173
+
174
+ /** Build aggregate statistics. */
175
+ stats(): TrajectoryStats {
176
+ const all = this.list();
177
+ if (all.length === 0) {
178
+ return {
179
+ total: 0,
180
+ byVerdict: { approved: 0, blocked: 0, changes_requested: 0 },
181
+ byLabel: { converged: 0, stuck: 0, blocked: 0, "max-iterations": 0 },
182
+ avgIterations: 0,
183
+ avgDurationMs: 0,
184
+ byFile: {} as Record<string, number>,
185
+ };
186
+ }
187
+
188
+ const byVerdict: TrajectoryStats["byVerdict"] = {
189
+ approved: 0,
190
+ blocked: 0,
191
+ changes_requested: 0,
192
+ };
193
+ const byLabel: TrajectoryStats["byLabel"] = {
194
+ converged: 0,
195
+ stuck: 0,
196
+ blocked: 0,
197
+ "max-iterations": 0,
198
+ };
199
+ const byFile = {} as Record<string, number>;
200
+ let totalIterations = 0;
201
+ let totalDuration = 0;
202
+
203
+ for (const t of all) {
204
+ byVerdict[t.verdict] = (byVerdict[t.verdict] ?? 0) + 1;
205
+ totalIterations += t.iterations;
206
+ totalDuration += t.durationMs;
207
+
208
+ // Derive label from reason string
209
+ const label = classifyLabel(t);
210
+ byLabel[label] = (byLabel[label] ?? 0) + 1;
211
+
212
+ // Count files
213
+ for (const file of t.files) {
214
+ byFile[file] = (byFile[file] ?? 0) + 1;
215
+ }
216
+ }
217
+
218
+ return {
219
+ total: all.length,
220
+ byVerdict,
221
+ byLabel,
222
+ avgIterations: Math.round(totalIterations / all.length),
223
+ avgDurationMs: Math.round(totalDuration / all.length),
224
+ byFile,
225
+ };
226
+ }
227
+
228
+ /** Classify a single trajectory. */
229
+ classify(trajectory: Trajectory): TrajectoryClassification {
230
+ const label = classifyLabel(trajectory);
231
+ let confidence = 0.9;
232
+ let pattern: string | undefined;
233
+ let recommendation: string;
234
+
235
+ if (label === "converged") {
236
+ recommendation =
237
+ trajectory.iterations <= 2
238
+ ? "Fast convergence — good pattern"
239
+ : "Slow but successful — consider review depth";
240
+ } else if (label === "stuck") {
241
+ confidence = 0.7;
242
+ pattern = "same-file-repeated";
243
+ recommendation =
244
+ "Loop stuck on same file. Consider pre-flight file analysis or splitting the task.";
245
+ } else if (label === "max-iterations") {
246
+ recommendation =
247
+ "Hit iteration cap. Increase maxIterations or simplify the task scope.";
248
+ } else {
249
+ recommendation = "Reviewer blocked — escalate to human.";
250
+ }
251
+
252
+ return {
253
+ trajectoryId: trajectory.id,
254
+ label,
255
+ confidence,
256
+ pattern,
257
+ recommendation,
258
+ };
259
+ }
260
+
261
+ /** Get trajectories by verdict (for training set building). */
262
+ byVerdict(verdict: Trajectory["verdict"]): Trajectory[] {
263
+ return this.list().filter((t) => t.verdict === verdict);
264
+ }
265
+ }
266
+
267
+ /** Derive a TrajectoryLabel from a Trajectory's reason string. */
268
+ function classifyLabel(t: Trajectory): TrajectoryLabel {
269
+ const r = t.reason.toLowerCase();
270
+ if (r.includes("converged")) return "converged";
271
+ if (r.includes("max iterations")) return "max-iterations";
272
+ if (r.includes("stuck")) return "stuck";
273
+ if (t.verdict === "blocked") return "blocked";
274
+ return "converged"; // fallback
275
+ }
276
+
277
+ /** Singleton factory — one store per process. */
278
+ let _store: TrajectoryStore | undefined;
279
+ export function getTrajectoryStore(): TrajectoryStore {
280
+ if (!_store) _store = new TrajectoryStore();
281
+ return _store;
282
+ }
283
+ export function resetTrajectoryStore(): void {
284
+ _store = undefined;
285
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Trajectory types — structured write-review cycle records for learning.
3
+ *
4
+ * Each completed loop cycle produces one Trajectory entry. These are persisted
5
+ * to `~/.pi-harness/trajectories/` as newline-delimited JSON (NDJSON), one
6
+ * record per line. Simple, append-only, and grep-friendly.
7
+ */
8
+
9
+ // Note: Verdict/Comment types imported in store.ts from write-review
10
+
11
+ export interface Comment {
12
+ file?: string;
13
+ comment: string;
14
+ severity: "minor" | "major" | "critical";
15
+ }
16
+
17
+ /** A completed write-review loop cycle. */
18
+ export interface Trajectory {
19
+ /** Stable UUID — generated at cycle start, persisted in state. */
20
+ id: string;
21
+ /** Original user request that triggered the loop. */
22
+ taskRequest: string;
23
+ /** ISO-8601 timestamp when the cycle started. */
24
+ createdAt: string;
25
+ /** Wall-clock duration in milliseconds. */
26
+ durationMs: number;
27
+ /** Number of write-review rounds completed. */
28
+ iterations: number;
29
+ /** Final verdict from the GPT reviewer. */
30
+ verdict: "approved" | "blocked" | "changes_requested";
31
+ /** Human-readable termination reason (from finishNode). */
32
+ reason: string;
33
+ /** Plan produced by the GPT planner. */
34
+ plan: string;
35
+ /** Final code output from the MiniMax coder. */
36
+ code: string;
37
+ /** Files mentioned in comments across all review iterations. */
38
+ files: string[];
39
+ /** All comments from the final review iteration. */
40
+ comments: Comment[];
41
+ /** Final review summary. */
42
+ summary: string;
43
+ /** Whether this trajectory was used for training/classification. */
44
+ classified: boolean;
45
+ }
46
+
47
+ /** Lightweight summary for list/aggregate queries. */
48
+ export interface TrajectorySummary {
49
+ id: string;
50
+ taskRequest: string;
51
+ createdAt: string;
52
+ durationMs: number;
53
+ iterations: number;
54
+ verdict: Trajectory["verdict"];
55
+ reason: string;
56
+ files: string[];
57
+ classified: boolean;
58
+ }
59
+
60
+ /** Classification label applied by the trajectory classifier. */
61
+ export type TrajectoryLabel =
62
+ | "converged"
63
+ | "stuck"
64
+ | "blocked"
65
+ | "max-iterations";
66
+
67
+ /** Classification result for a trajectory. */
68
+ export interface TrajectoryClassification {
69
+ trajectoryId: string;
70
+ label: TrajectoryLabel;
71
+ confidence: number; // 0–1
72
+ pattern?: string; // e.g. "same-file-repeated"
73
+ recommendation: string;
74
+ }
75
+
76
+ /** Aggregated statistics across a set of trajectories. */
77
+ export interface TrajectoryStats {
78
+ total: number;
79
+ byVerdict: Record<Trajectory["verdict"], number>;
80
+ byLabel: Record<TrajectoryLabel, number>;
81
+ avgIterations: number;
82
+ avgDurationMs: number;
83
+ byFile: Record<string, number>; // file → comment count
84
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1,250 @@
1
+ import { describe, it, beforeEach } from "node:test";
2
+ import assert from "node:assert";
3
+ import { rmSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { TrajectoryStore, resetTrajectoryStore } from "../src/store.js";
7
+
8
+ const TEST_DIR = join(homedir(), ".pi-harness-test-trajectories");
9
+
10
+ function freshStore(): TrajectoryStore {
11
+ rmSync(TEST_DIR, { force: true, recursive: true });
12
+ mkdirSync(TEST_DIR, { recursive: true });
13
+ return new TrajectoryStore(TEST_DIR);
14
+ }
15
+
16
+ describe("TrajectoryStore", () => {
17
+ beforeEach(() => {
18
+ rmSync(TEST_DIR, { force: true, recursive: true });
19
+ mkdirSync(TEST_DIR, { recursive: true });
20
+ resetTrajectoryStore();
21
+ });
22
+
23
+ it("start() returns a UUID", () => {
24
+ const store = freshStore();
25
+ const id = store.start("test task");
26
+ assert.match(id, /^[0-9a-f-]{36}$/);
27
+ });
28
+
29
+ it("append() writes NDJSON to the correct daily file", () => {
30
+ const store = freshStore();
31
+ const id = "test-id-123";
32
+ const now = new Date();
33
+
34
+ store.append({
35
+ id,
36
+ taskRequest: "fix the login bug",
37
+ createdAt: now.toISOString(),
38
+ durationMs: 5000,
39
+ iterations: 2,
40
+ verdict: "approved",
41
+ reason: "reviewer approved",
42
+ plan: "# Plan\n1. Fix it",
43
+ code: "// fixed",
44
+ files: ["auth.ts"],
45
+ comments: [{ file: "auth.ts", comment: "looks good", severity: "minor" }],
46
+ summary: "looks good",
47
+ classified: false,
48
+ });
49
+
50
+ // Should have created a daily file
51
+ const files = readdirSync(TEST_DIR);
52
+ assert.ok(files.length > 0, "should create at least one directory");
53
+
54
+ // Navigate to the daily file
55
+ const monthDir = files[0];
56
+ const monthFiles = readdirSync(join(TEST_DIR, monthDir));
57
+ assert.ok(monthFiles.length > 0, "should create daily ndjson file");
58
+
59
+ const content = readFileSync(join(TEST_DIR, monthDir, monthFiles[0]), "utf8");
60
+ const record = JSON.parse(content.split("\n").find((l) => l.trim())!);
61
+ assert.strictEqual(record.id, id);
62
+ assert.strictEqual(record.verdict, "approved");
63
+ assert.strictEqual(record.iterations, 2);
64
+ });
65
+
66
+ it("list() returns records newest-first", () => {
67
+ const store = freshStore();
68
+
69
+ const id1 = store.start("task 1");
70
+ store.append({
71
+ id: id1,
72
+ taskRequest: "task 1",
73
+ createdAt: "2025-07-01T10:00:00.000Z",
74
+ durationMs: 1000,
75
+ iterations: 1,
76
+ verdict: "approved",
77
+ reason: "ok",
78
+ plan: "p1",
79
+ code: "c1",
80
+ files: [],
81
+ comments: [],
82
+ summary: "s1",
83
+ classified: false,
84
+ });
85
+
86
+ const id2 = store.start("task 2");
87
+ store.append({
88
+ id: id2,
89
+ taskRequest: "task 2",
90
+ createdAt: "2025-07-02T10:00:00.000Z",
91
+ durationMs: 2000,
92
+ iterations: 2,
93
+ verdict: "changes_requested",
94
+ reason: "converged: only minor comments",
95
+ plan: "p2",
96
+ code: "c2",
97
+ files: [],
98
+ comments: [],
99
+ summary: "s2",
100
+ classified: false,
101
+ });
102
+
103
+ const all = store.list();
104
+ assert.strictEqual(all.length, 2);
105
+ assert.strictEqual(all[0].id, id2, "newest first");
106
+ assert.strictEqual(all[1].id, id1);
107
+ });
108
+
109
+ it("stats() aggregates correctly", () => {
110
+ const store = freshStore();
111
+
112
+ store.append({
113
+ id: "s1",
114
+ taskRequest: "t1",
115
+ createdAt: "2025-07-01T10:00:00.000Z",
116
+ durationMs: 1000,
117
+ iterations: 1,
118
+ verdict: "approved",
119
+ reason: "reviewer approved",
120
+ plan: "p",
121
+ code: "c",
122
+ files: ["a.ts"],
123
+ comments: [],
124
+ summary: "s",
125
+ classified: false,
126
+ });
127
+
128
+ store.append({
129
+ id: "s2",
130
+ taskRequest: "t2",
131
+ createdAt: "2025-07-01T11:00:00.000Z",
132
+ durationMs: 3000,
133
+ iterations: 3,
134
+ verdict: "changes_requested",
135
+ reason: "max iterations (3) reached with changes still requested",
136
+ plan: "p",
137
+ code: "c",
138
+ files: ["a.ts", "b.ts"],
139
+ comments: [],
140
+ summary: "s",
141
+ classified: false,
142
+ });
143
+
144
+ const stats = store.stats();
145
+ assert.strictEqual(stats.total, 2);
146
+ assert.strictEqual(stats.byVerdict.approved, 1);
147
+ assert.strictEqual(stats.byVerdict.changes_requested, 1);
148
+ assert.strictEqual(stats.avgIterations, 2);
149
+ assert.strictEqual(stats.avgDurationMs, 2000);
150
+ assert.strictEqual(stats.byFile["a.ts"], 2);
151
+ assert.strictEqual(stats.byFile["b.ts"], 1);
152
+ assert.strictEqual(stats.byLabel["max-iterations"], 1);
153
+ assert.strictEqual(stats.byLabel["converged"], 1);
154
+ });
155
+
156
+ it("classify() returns correct label for converged", () => {
157
+ const store = freshStore();
158
+ const cls = store.classify({
159
+ id: "x",
160
+ taskRequest: "x",
161
+ createdAt: new Date().toISOString(),
162
+ durationMs: 1000,
163
+ iterations: 1,
164
+ verdict: "approved",
165
+ reason: "reviewer approved",
166
+ plan: "",
167
+ code: "",
168
+ files: [],
169
+ comments: [],
170
+ summary: "",
171
+ classified: false,
172
+ });
173
+ assert.strictEqual(cls.label, "converged");
174
+ assert.strictEqual(cls.trajectoryId, "x");
175
+ });
176
+
177
+ it("classify() returns stuck for same-file reason", () => {
178
+ const store = freshStore();
179
+ const cls = store.classify({
180
+ id: "y",
181
+ taskRequest: "y",
182
+ createdAt: new Date().toISOString(),
183
+ durationMs: 1000,
184
+ iterations: 2,
185
+ verdict: "changes_requested",
186
+ reason: "stuck: same file (auth.ts) flagged for 3 comment(s)",
187
+ plan: "",
188
+ code: "",
189
+ files: ["auth.ts"],
190
+ comments: [],
191
+ summary: "",
192
+ classified: false,
193
+ });
194
+ assert.strictEqual(cls.label, "stuck");
195
+ assert.strictEqual(cls.pattern, "same-file-repeated");
196
+ assert.ok(cls.confidence < 1.0);
197
+ });
198
+
199
+ it("byVerdict() filters correctly", () => {
200
+ const store = freshStore();
201
+ store.append({
202
+ id: "v1",
203
+ taskRequest: "v1",
204
+ createdAt: "2025-07-01T10:00:00.000Z",
205
+ durationMs: 1000,
206
+ iterations: 1,
207
+ verdict: "approved",
208
+ reason: "ok",
209
+ plan: "",
210
+ code: "",
211
+ files: [],
212
+ comments: [],
213
+ summary: "",
214
+ classified: false,
215
+ });
216
+ store.append({
217
+ id: "v2",
218
+ taskRequest: "v2",
219
+ createdAt: "2025-07-02T10:00:00.000Z",
220
+ durationMs: 1000,
221
+ iterations: 1,
222
+ verdict: "blocked",
223
+ reason: "blocked",
224
+ plan: "",
225
+ code: "",
226
+ files: [],
227
+ comments: [],
228
+ summary: "",
229
+ classified: false,
230
+ });
231
+
232
+ const approved = store.byVerdict("approved");
233
+ assert.strictEqual(approved.length, 1);
234
+ assert.strictEqual(approved[0].id, "v1");
235
+
236
+ const blocked = store.byVerdict("blocked");
237
+ assert.strictEqual(blocked.length, 1);
238
+ assert.strictEqual(blocked[0].id, "v2");
239
+ });
240
+
241
+ it("stats() handles empty store gracefully", () => {
242
+ rmSync(TEST_DIR, { force: true, recursive: true });
243
+ mkdirSync(TEST_DIR, { recursive: true });
244
+ // Remove the month dir so the store sees an empty directory
245
+ const store = freshStore();
246
+ const stats = store.stats();
247
+ assert.strictEqual(stats.total, 0);
248
+ assert.strictEqual(stats.avgIterations, 0);
249
+ });
250
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tui",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "description": "pi-harness-runtime — tui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/types",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "description": "Shared TypeScript types for pi-harness-runtime packages",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workflow-events",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workspace-scanner",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/worktree",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/write-review",
3
- "version": "1.1.49",
3
+ "version": "1.1.71-3",
4
4
  "description": "Two-agent write-review loop with blackboard coordination",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",