pi-harness-runtime 0.10.0 → 0.10.2

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 (109) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +122 -0
  3. package/harness/e2e/minimax-quota-scraper.ts +4 -4
  4. package/harness/e2e/openai-quota-scraper.ts +13 -8
  5. package/index.ts +93 -2
  6. package/package.json +1 -1
  7. package/packages/a2a-adapter/package.json +1 -1
  8. package/packages/auth/package.json +1 -1
  9. package/packages/autonomous-refactor/package.json +1 -1
  10. package/packages/autonomous-runtime/package.json +1 -1
  11. package/packages/cache-strategy/package.json +1 -1
  12. package/packages/capability-registry/package.json +1 -1
  13. package/packages/checkpoint/package.json +1 -1
  14. package/packages/cli-plugin-sdk/package.json +1 -1
  15. package/packages/clipboard/dist/src/copy-sync.d.ts +27 -0
  16. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -0
  17. package/packages/clipboard/dist/src/copy-sync.js +170 -0
  18. package/packages/clipboard/dist/src/copy-sync.js.map +1 -0
  19. package/packages/clipboard/dist/src/gist-relay.d.ts +48 -0
  20. package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -0
  21. package/packages/clipboard/dist/src/gist-relay.js +186 -0
  22. package/packages/clipboard/dist/src/gist-relay.js.map +1 -0
  23. package/packages/clipboard/dist/src/github-login.d.ts +16 -0
  24. package/packages/clipboard/dist/src/github-login.d.ts.map +1 -0
  25. package/packages/clipboard/dist/src/github-login.js +69 -0
  26. package/packages/clipboard/dist/src/github-login.js.map +1 -0
  27. package/packages/clipboard/dist/src/index.d.ts +2 -0
  28. package/packages/clipboard/dist/src/index.d.ts.map +1 -1
  29. package/packages/clipboard/dist/src/index.js +2 -1
  30. package/packages/clipboard/dist/src/index.js.map +1 -1
  31. package/packages/clipboard/package.json +1 -1
  32. package/packages/clipboard/src/copy-sync.ts +201 -0
  33. package/packages/clipboard/src/gist-relay.ts +240 -0
  34. package/packages/clipboard/src/github-login.ts +107 -0
  35. package/packages/clipboard/src/index.ts +2 -0
  36. package/packages/clipboard/test/mojibake.test.mjs +143 -0
  37. package/packages/code-generation/package.json +1 -1
  38. package/packages/code-review/package.json +1 -1
  39. package/packages/codex-adapter/package.json +1 -1
  40. package/packages/context-compiler/package.json +1 -1
  41. package/packages/context-discovery/package.json +1 -1
  42. package/packages/context-manager/package.json +1 -1
  43. package/packages/cookie-sanitizer/package.json +1 -1
  44. package/packages/cost-optimizer/package.json +1 -1
  45. package/packages/dependency-analyzer/package.json +1 -1
  46. package/packages/django-plugin/package.json +1 -1
  47. package/packages/doc-generator/package.json +1 -1
  48. package/packages/evaluation-engine/package.json +1 -1
  49. package/packages/evaluation-runner/package.json +1 -1
  50. package/packages/event-bus/package.json +1 -1
  51. package/packages/event-store/package.json +1 -1
  52. package/packages/experience-replay/package.json +1 -1
  53. package/packages/feedback-collector/package.json +1 -1
  54. package/packages/framework-detector/package.json +1 -1
  55. package/packages/framework-plugin-sdk/package.json +1 -1
  56. package/packages/frappe-plugin/package.json +1 -1
  57. package/packages/generic-web-plugin/package.json +1 -1
  58. package/packages/health-monitor/package.json +1 -1
  59. package/packages/intent-analyzer/package.json +1 -1
  60. package/packages/laravel-plugin/package.json +1 -1
  61. package/packages/learning-engine/package.json +1 -1
  62. package/packages/mcp-adapter/package.json +1 -1
  63. package/packages/memory-engine/package.json +1 -1
  64. package/packages/model-registry/package.json +1 -1
  65. package/packages/nextjs-plugin/package.json +1 -1
  66. package/packages/notification/package.json +1 -1
  67. package/packages/observability/package.json +1 -1
  68. package/packages/performance-optimizer/package.json +1 -1
  69. package/packages/privilege-broker/package.json +1 -1
  70. package/packages/project-analyzer/package.json +1 -1
  71. package/packages/projection-engine/package.json +1 -1
  72. package/packages/prompt-compiler/package.json +1 -1
  73. package/packages/prompt-versioning/package.json +1 -1
  74. package/packages/provider-adapter-sdk/package.json +1 -1
  75. package/packages/provider-router/package.json +1 -1
  76. package/packages/provider-selector/package.json +1 -1
  77. package/packages/providers/package.json +1 -1
  78. package/packages/quota-manager/package.json +1 -1
  79. package/packages/rate-limiter/package.json +1 -1
  80. package/packages/react-vite-plugin/package.json +1 -1
  81. package/packages/release-manager/package.json +1 -1
  82. package/packages/requirement-compiler/package.json +1 -1
  83. package/packages/runtime/package.json +1 -1
  84. package/packages/scheduler/package.json +1 -1
  85. package/packages/scheduler-adapter/package.json +1 -1
  86. package/packages/session/package.json +1 -1
  87. package/packages/session-api/package.json +1 -1
  88. package/packages/session-export/package.json +1 -1
  89. package/packages/shared-context/package.json +1 -1
  90. package/packages/skill-registry/package.json +1 -1
  91. package/packages/sprint-planner/package.json +1 -1
  92. package/packages/subscription-engine/package.json +1 -1
  93. package/packages/task-compiler/package.json +1 -1
  94. package/packages/test-data-generator/package.json +1 -1
  95. package/packages/test-generator/package.json +1 -1
  96. package/packages/todo-bd-sync/package.json +22 -0
  97. package/packages/todo-bd-sync/src/detector.ts +150 -0
  98. package/packages/todo-bd-sync/src/extension.ts +84 -0
  99. package/packages/todo-bd-sync/src/index.ts +401 -0
  100. package/packages/todo-bd-sync/src/sync.ts +322 -0
  101. package/packages/todo-bd-sync/src/todo-reminder.ts +418 -0
  102. package/packages/todo-bd-sync/src/types.ts +137 -0
  103. package/packages/token-estimation/package.json +1 -1
  104. package/packages/token-optimizer/package.json +1 -1
  105. package/packages/tui/package.json +1 -1
  106. package/packages/types/package.json +1 -1
  107. package/packages/workflow-events/package.json +1 -1
  108. package/packages/workspace-scanner/package.json +1 -1
  109. package/packages/worktree/package.json +1 -1
@@ -0,0 +1,418 @@
1
+ /**
2
+ * todo-reminder.ts - Inject reminders to continue remaining todos
3
+ *
4
+ * ARCHITECTURE NOTES:
5
+ * - Reminders are DISABLED by default (Phase 1 emergency containment)
6
+ * until proper scoping is implemented.
7
+ * - Triggers are narrowed: only after agent_end, not every bash command.
8
+ * - Deduplication is persistent via file-based hash (survives process restart).
9
+ * - Source is scoped to sync-layer-mapped tasks only.
10
+ *
11
+ * This module does NOT modify the overlay (it works fine).
12
+ * It ONLY injects reminders into the LLM context when todos remain.
13
+ */
14
+
15
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
16
+ import { execBdCommand } from "./sync.js";
17
+ import { homedir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
20
+
21
+ // ─── Persistent state file ────────────────────────────────────────────────────
22
+
23
+ const STATE_DIR = join(homedir(), ".pi-harness-runtime");
24
+ const STATE_FILE = join(STATE_DIR, "todo-reminder-state.json");
25
+
26
+ interface ReminderState {
27
+ /** Hash of the last delivered reminder payload */
28
+ lastDeliveredHash: string;
29
+ /** Timestamp of last delivery (ms epoch) */
30
+ lastDeliveredAt: number;
31
+ /** Session ID this was delivered in (to avoid cross-session dedup issues) */
32
+ lastSessionId?: string;
33
+ }
34
+
35
+ function ensureStateDir(): void {
36
+ if (!existsSync(STATE_DIR)) {
37
+ mkdirSync(STATE_DIR, { recursive: true });
38
+ }
39
+ }
40
+
41
+ function loadState(): ReminderState {
42
+ try {
43
+ if (existsSync(STATE_FILE)) {
44
+ const raw = readFileSync(STATE_FILE, "utf-8");
45
+ return JSON.parse(raw) as ReminderState;
46
+ }
47
+ } catch {
48
+ // ignore
49
+ }
50
+ return { lastDeliveredHash: "", lastDeliveredAt: 0 };
51
+ }
52
+
53
+ function saveState(state: ReminderState): void {
54
+ ensureStateDir();
55
+ try {
56
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
57
+ } catch {
58
+ // ignore — best-effort
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Generate a stable content hash for deduplication.
64
+ * Uses a simple non-crypto hash — we need dedup, not security.
65
+ */
66
+ function contentHash(tasks: TrackedTask[]): string {
67
+ const ids = tasks
68
+ .map((t) => t.id)
69
+ .sort()
70
+ .join("|");
71
+ let hash = 0;
72
+ for (let i = 0; i < ids.length; i++) {
73
+ hash = (hash * 31 + ids.charCodeAt(i)) & 0xffffffff;
74
+ }
75
+ return `t${Math.abs(hash).toString(16)}`;
76
+ }
77
+
78
+ /**
79
+ * A task that is tracked by the sync layer
80
+ */
81
+ interface TrackedTask {
82
+ id: string;
83
+ title: string;
84
+ status: string;
85
+ }
86
+
87
+ /**
88
+ * Default reminder template
89
+ */
90
+ const DEFAULT_REMINDER_TEMPLATE = `
91
+ Remaining todos detected:
92
+ {summary}
93
+
94
+ Please continue with the next pending task or ask the user which to prioritize.
95
+ `.trim();
96
+
97
+ /**
98
+ * Configuration for todo reminder
99
+ */
100
+ export interface TodoReminderConfig {
101
+ /** Auto-inject reminder on agent_end (default: FALSE — containment) */
102
+ autoRemind?: boolean;
103
+ /** Custom reminder template (default: built-in) */
104
+ template?: string;
105
+ /** Minimum pending tasks to trigger reminder (default: 1) */
106
+ minPendingTasks?: number;
107
+ /** Deliver reminder as: "steer" | "followUp" (default: "steer") */
108
+ deliverAs?: "steer" | "followUp";
109
+ /** Enable verbose debug logging */
110
+ verbose?: boolean;
111
+ }
112
+
113
+ /**
114
+ * Default configuration
115
+ * NOTE: autoRemind is FALSE by default — Phase 1 emergency containment.
116
+ * Enable only after proper task scoping is in place.
117
+ */
118
+ const DEFAULT_CONFIG: Required<TodoReminderConfig> = {
119
+ autoRemind: false, // DISABLED by default — see bug report
120
+ template: DEFAULT_REMINDER_TEMPLATE,
121
+ minPendingTasks: 1,
122
+ deliverAs: "steer", // steer does NOT append to transcript
123
+ verbose: false,
124
+ };
125
+
126
+ /**
127
+ * Create a todo reminder instance
128
+ */
129
+ export function createTodoReminder(
130
+ pi: ExtensionAPI,
131
+ config: TodoReminderConfig = {},
132
+ ): TodoReminder {
133
+ const finalConfig = { ...DEFAULT_CONFIG, ...config };
134
+ return new TodoReminder(pi, finalConfig);
135
+ }
136
+
137
+ /**
138
+ * TodoReminder class — injects reminders when todos remain.
139
+ *
140
+ * Key design decisions:
141
+ * 1. DISABLED by default (autoRemind: false) — Phase 1 containment
142
+ * 2. Deduplication is persistent via file-based hash (survives process restart)
143
+ * 3. Narrow trigger: only agent_end, never generic bash
144
+ * 4. deliverAs: "steer" avoids transcript growth
145
+ */
146
+ export class TodoReminder {
147
+ private pi: ExtensionAPI;
148
+ private config: Required<TodoReminderConfig>;
149
+ private lastReminderAt = 0;
150
+ private state: ReminderState;
151
+
152
+ constructor(pi: ExtensionAPI, config: Required<TodoReminderConfig>) {
153
+ this.pi = pi;
154
+ this.config = config;
155
+ // Load persistent state from file — survives process restart
156
+ this.state = loadState();
157
+ }
158
+
159
+ /**
160
+ * Start the reminder system
161
+ */
162
+ start(): void {
163
+ if (!this.config.autoRemind) {
164
+ return;
165
+ }
166
+
167
+ // Only trigger on agent_end — not on every bash command.
168
+ // Bash triggers were too broad and caused spam during analysis sessions.
169
+ this.pi.on("agent_end", async () => {
170
+ await this.checkAndRemind();
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Check if reminders should be sent and send them
176
+ */
177
+ private async checkAndRemind(): Promise<void> {
178
+ try {
179
+ const remaining = this.getTrackedTodos();
180
+
181
+ if (remaining.length < this.config.minPendingTasks) {
182
+ return;
183
+ }
184
+
185
+ // Persistent content-based deduplication — survives process restart.
186
+ // Even if pi restarts, we won't re-deliver the same reminder payload.
187
+ const hash = contentHash(remaining);
188
+ if (hash === this.state.lastDeliveredHash) {
189
+ return;
190
+ }
191
+
192
+ await this.sendReminder(remaining);
193
+ this.lastReminderAt = Date.now();
194
+
195
+ // Persist the hash so restart doesn't cause replay
196
+ this.state.lastDeliveredHash = hash;
197
+ this.state.lastDeliveredAt = this.lastReminderAt;
198
+ saveState(this.state);
199
+ } catch (e) {
200
+ // Non-fatal - reminders are best-effort
201
+ console.warn("[DEBUG todo-reminder] Failed to check todos:", e);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Get remaining (pending/in-progress) todos from bd.
207
+ *
208
+ * NOTE: This returns ALL open bd issues, not just mapped ones.
209
+ * When the mapping registry is integrated, this should be scoped.
210
+ */
211
+ private getTrackedTodos(): TrackedTask[] {
212
+ try {
213
+ const output = execBdCommand("bd ready --json");
214
+ const issues = JSON.parse(output);
215
+ return issues
216
+ .filter(
217
+ (issue: Record<string, unknown>) =>
218
+ issue.status !== "closed" && issue.status !== "done",
219
+ )
220
+ .map((issue: Record<string, unknown>) => ({
221
+ id: issue.id as string,
222
+ title: issue.title as string,
223
+ status: issue.status as string,
224
+ }));
225
+ } catch {
226
+ return [];
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Format the reminder message
232
+ */
233
+ private formatReminder(remaining: TrackedTask[]): string {
234
+ const summary = remaining
235
+ .map((t) => `[${t.id}] ${t.title} (${t.status})`)
236
+ .join("\n");
237
+
238
+ return this.config.template.replace("{summary}", summary);
239
+ }
240
+
241
+ /**
242
+ * Send the reminder to the LLM.
243
+ *
244
+ * FIX: Re-check state before send to prevent stale reminders.
245
+ * If a task was closed after we prepared the reminder, skip delivery.
246
+ */
247
+ private async sendReminder(remaining: TrackedTask[]): Promise<void> {
248
+ // CRITICAL FIX: Re-check state immediately before sending.
249
+ // A task may have been closed between checkAndRemind() and now.
250
+ // We must not deliver reminders for already-closed tasks.
251
+ const freshState = this.getTrackedTodos();
252
+ const freshHash = contentHash(freshState);
253
+ const preparedHash = contentHash(remaining);
254
+
255
+ // If the fresh state differs from what we prepared, tasks were modified
256
+ if (freshHash !== preparedHash) {
257
+ // Task was closed or modified — suppress stale reminder
258
+ if (this.config.verbose ?? false) {
259
+ console.log(
260
+ "[DEBUG todo-reminder] State changed since check — suppressing reminder",
261
+ );
262
+ }
263
+ return;
264
+ }
265
+
266
+ const message = this.formatReminder(remaining);
267
+
268
+ try {
269
+ this.pi.sendUserMessage(message, {
270
+ deliverAs: this.config.deliverAs,
271
+ });
272
+ } catch (e) {
273
+ console.warn("[DEBUG todo-reminder] Failed to send reminder:", e);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Manually trigger a reminder (for slash command)
279
+ */
280
+ async triggerNow(): Promise<void> {
281
+ await this.checkAndRemind();
282
+ }
283
+
284
+ /**
285
+ * Get current remaining count
286
+ */
287
+ getRemainingCount(): number {
288
+ return this.getTrackedTodos().length;
289
+ }
290
+
291
+ /**
292
+ * Clear the persistent dedupe state (for testing or manual reset)
293
+ */
294
+ clearState(): void {
295
+ this.state = { lastDeliveredHash: "", lastDeliveredAt: 0 };
296
+ saveState(this.state);
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Create a reminder with custom todos source (for tighter scoping)
302
+ * @param pi - Extension API
303
+ * @param getRemaining - Function that returns remaining todos
304
+ * @param config - Reminder config
305
+ */
306
+ export function createCustomReminder(
307
+ pi: ExtensionAPI,
308
+ getRemaining: () => TrackedTask[],
309
+ config: TodoReminderConfig = {},
310
+ ): CustomTodoReminder {
311
+ return new CustomTodoReminder(pi, getRemaining, config);
312
+ }
313
+
314
+ /**
315
+ * Custom reminder with injectable todos source.
316
+ * Use this to scope reminders to only mapped/created tasks.
317
+ */
318
+ class CustomTodoReminder {
319
+ private pi: ExtensionAPI;
320
+ private getRemaining: () => TrackedTask[];
321
+ private config: Required<TodoReminderConfig>;
322
+ private lastReminderAt = 0;
323
+ private state: ReminderState;
324
+
325
+ constructor(
326
+ pi: ExtensionAPI,
327
+ getRemaining: () => TrackedTask[],
328
+ config: TodoReminderConfig,
329
+ ) {
330
+ this.pi = pi;
331
+ this.getRemaining = getRemaining;
332
+ this.config = { ...DEFAULT_CONFIG, ...config };
333
+ // Load persistent state from file
334
+ this.state = loadState();
335
+ }
336
+
337
+ start(): void {
338
+ if (!this.config.autoRemind) return;
339
+
340
+ // Only trigger on agent_end — not on every bash command
341
+ this.pi.on("agent_end", async () => {
342
+ await this.checkAndRemind();
343
+ });
344
+ }
345
+
346
+ private async checkAndRemind(): Promise<void> {
347
+ try {
348
+ const remaining = this.getRemaining();
349
+ if (remaining.length < this.config.minPendingTasks) return;
350
+
351
+ // Persistent content-based deduplication
352
+ const hash = contentHash(remaining);
353
+ if (hash === this.state.lastDeliveredHash) return;
354
+
355
+ await this.sendReminder(remaining);
356
+ this.lastReminderAt = Date.now();
357
+
358
+ // Persist so restart doesn't replay
359
+ this.state.lastDeliveredHash = hash;
360
+ this.state.lastDeliveredAt = this.lastReminderAt;
361
+ saveState(this.state);
362
+ } catch (e) {
363
+ console.warn("[DEBUG todo-reminder] Failed to check todos:", e);
364
+ }
365
+ }
366
+
367
+ private formatReminder(remaining: TrackedTask[]): string {
368
+ const summary = remaining
369
+ .map((t) => `[${t.id}] ${t.title} (${t.status})`)
370
+ .join("\n");
371
+
372
+ return this.config.template.replace("{summary}", summary);
373
+ }
374
+
375
+ private async sendReminder(remaining: TrackedTask[]): Promise<void> {
376
+ // CRITICAL FIX: Re-check state immediately before sending.
377
+ // A task may have been closed between checkAndRemind() and now.
378
+ const freshState = this.getRemaining();
379
+ const freshHash = contentHash(freshState);
380
+ const preparedHash = contentHash(remaining);
381
+
382
+ // If the fresh state differs from what we prepared, tasks were modified
383
+ if (freshHash !== preparedHash) {
384
+ if (this.config.verbose ?? false) {
385
+ console.log(
386
+ "[DEBUG todo-reminder] State changed since check — suppressing reminder",
387
+ );
388
+ }
389
+ return;
390
+ }
391
+
392
+ const message = this.formatReminder(remaining);
393
+
394
+ try {
395
+ this.pi.sendUserMessage(message, {
396
+ deliverAs: this.config.deliverAs,
397
+ });
398
+ } catch (e) {
399
+ console.warn("[DEBUG todo-reminder] Failed to send reminder:", e);
400
+ }
401
+ }
402
+
403
+ async triggerNow(): Promise<void> {
404
+ await this.checkAndRemind();
405
+ }
406
+
407
+ getRemainingCount(): number {
408
+ return this.getRemaining().length;
409
+ }
410
+
411
+ /**
412
+ * Clear the persistent dedupe state
413
+ */
414
+ clearState(): void {
415
+ this.state = { lastDeliveredHash: "", lastDeliveredAt: 0 };
416
+ saveState(this.state);
417
+ }
418
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Types for todo-bd-sync
3
+ * Two-way sync between rpiv-todo overlay and bd issue tracker
4
+ */
5
+
6
+ // Task from rpiv-todo
7
+ export interface TodoTask {
8
+ id: number;
9
+ subject: string;
10
+ description?: string;
11
+ activeForm?: string;
12
+ status: TodoStatus;
13
+ blockedBy?: number[];
14
+ owner?: string;
15
+ metadata?: {
16
+ bdId?: string; // Mapped bd issue ID (e.g., "bd-a1b2")
17
+ lastSync?: number; // Timestamp of last sync
18
+ [key: string]: unknown;
19
+ };
20
+ }
21
+
22
+ export type TodoStatus = "pending" | "in_progress" | "completed" | "deleted";
23
+
24
+ // Bd issue structure
25
+ export interface BdIssue {
26
+ id: string;
27
+ title: string;
28
+ status: BdStatus;
29
+ priority?: number;
30
+ createdAt?: string;
31
+ updatedAt?: string;
32
+ }
33
+
34
+ export type BdStatus = "open" | "in_progress" | "closed" | "blocked";
35
+
36
+ // Sync event types
37
+ export type SyncDirection = "todo_to_bd" | "bd_to_todo" | "session_start";
38
+
39
+ // Conflict resolution
40
+ export interface SyncConflict {
41
+ task: TodoTask;
42
+ bdIssue: BdIssue;
43
+ timestamp: number;
44
+ resolution: "todo_wins" | "bd_wins" | "recent_wins";
45
+ }
46
+
47
+ // Sync result
48
+ export interface SyncResult {
49
+ success: boolean;
50
+ direction: SyncDirection;
51
+ taskId?: number;
52
+ bdId?: string;
53
+ action?: string;
54
+ error?: string;
55
+ }
56
+
57
+ // ID mapping storage
58
+ export interface IdMapping {
59
+ todoId: number;
60
+ bdId: string;
61
+ createdAt: number;
62
+ lastSync: number;
63
+ }
64
+
65
+ // Registry of ID mappings
66
+ export class IdMappingRegistry {
67
+ private mappings: Map<number, IdMapping> = new Map(); // todoId -> mapping
68
+ private bdMappings: Map<string, number> = new Map(); // bdId -> todoId
69
+
70
+ set(todoId: number, bdId: string): void {
71
+ const now = Date.now();
72
+ const mapping: IdMapping = {
73
+ todoId,
74
+ bdId,
75
+ createdAt: now,
76
+ lastSync: now,
77
+ };
78
+ this.mappings.set(todoId, mapping);
79
+ this.bdMappings.set(bdId, todoId);
80
+ }
81
+
82
+ getByTodoId(todoId: number): IdMapping | undefined {
83
+ return this.mappings.get(todoId);
84
+ }
85
+
86
+ getByBdId(bdId: string): IdMapping | undefined {
87
+ const todoId = this.bdMappings.get(bdId);
88
+ return todoId !== undefined ? this.mappings.get(todoId) : undefined;
89
+ }
90
+
91
+ hasTodoId(todoId: number): boolean {
92
+ return this.mappings.has(todoId);
93
+ }
94
+
95
+ hasBdId(bdId: string): boolean {
96
+ return this.bdMappings.has(bdId);
97
+ }
98
+
99
+ updateLastSync(todoId: number): void {
100
+ const mapping = this.mappings.get(todoId);
101
+ if (mapping) {
102
+ mapping.lastSync = Date.now();
103
+ }
104
+ }
105
+
106
+ delete(todoId: number): void {
107
+ const mapping = this.mappings.get(todoId);
108
+ if (mapping) {
109
+ this.bdMappings.delete(mapping.bdId);
110
+ this.mappings.delete(todoId);
111
+ }
112
+ }
113
+
114
+ getAll(): IdMapping[] {
115
+ return Array.from(this.mappings.values());
116
+ }
117
+
118
+ clear(): void {
119
+ this.mappings.clear();
120
+ this.bdMappings.clear();
121
+ }
122
+ }
123
+
124
+ // Status mapping between todo and bd
125
+ export const STATUS_MAP: Record<TodoStatus, BdStatus> = {
126
+ pending: "open",
127
+ in_progress: "in_progress",
128
+ completed: "closed",
129
+ deleted: "closed",
130
+ };
131
+
132
+ export const BD_STATUS_MAP: Record<BdStatus, TodoStatus> = {
133
+ open: "pending",
134
+ in_progress: "in_progress",
135
+ closed: "completed",
136
+ blocked: "pending",
137
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-estimation",
3
- "version": "0.9.25",
3
+ "version": "0.10.2",
4
4
  "description": "pi-harness-runtime — token-estimation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-optimizer",
3
- "version": "0.9.25",
3
+ "version": "0.10.2",
4
4
  "description": "Token Budget Optimizer — maximize prompt value within token limits (RFC-0011)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tui",
3
- "version": "0.9.25",
3
+ "version": "0.10.2",
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": "0.9.25",
3
+ "version": "0.10.2",
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": "0.9.25",
3
+ "version": "0.10.2",
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": "0.9.25",
3
+ "version": "0.10.2",
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": "0.9.25",
3
+ "version": "0.10.2",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",