pi-orchestrator-config 4.2.0 → 4.3.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 (100) hide show
  1. package/README.md +76 -67
  2. package/agents/code-reviewer-docs.md +11 -1
  3. package/agents/code-reviewer-guidelines.md +11 -1
  4. package/agents/code-reviewer-quality.md +11 -1
  5. package/agents/code-reviewer-security.md +11 -1
  6. package/agents/code-reviewer-spec.md +11 -1
  7. package/agents/git-expert.md +42 -3
  8. package/agents/github-expert.md +11 -0
  9. package/extensions/acpx-provider/runtime-models.ts +18 -6
  10. package/extensions/cli-provider/agents/claude.ts +3 -2
  11. package/extensions/cli-provider/agents/cursor.ts +3 -5
  12. package/extensions/cli-provider/agents/gemini.ts +3 -2
  13. package/extensions/cli-provider/discover.ts +7 -7
  14. package/extensions/cli-provider/index.ts +5 -0
  15. package/extensions/cli-provider/runtime-models.ts +18 -6
  16. package/extensions/cli-provider/session-reaper.ts +5 -7
  17. package/extensions/cli-provider/sessions.ts +4 -0
  18. package/extensions/cli-provider/shared/discover-cache.ts +17 -176
  19. package/extensions/coms/coms-p2p.ts +1719 -423
  20. package/extensions/coms/coms-shared.ts +261 -39
  21. package/extensions/coms/coms-wrapper.ts +100 -62
  22. package/extensions/coms/index.ts +8 -4
  23. package/extensions/coms/mixed-turn.ts +26 -0
  24. package/extensions/coms/probe-socket.ts +42 -0
  25. package/extensions/coms/themeMap.ts +0 -1
  26. package/extensions/image-gen/image-gen.ts +19 -1
  27. package/extensions/orchestrator/async-agents.ts +563 -102
  28. package/extensions/orchestrator/async-capability.ts +8 -8
  29. package/extensions/orchestrator/cron.ts +15 -9
  30. package/extensions/orchestrator/dreaming.ts +32 -25
  31. package/extensions/orchestrator/enforcement-helpers.ts +459 -17
  32. package/extensions/orchestrator/enforcement-rules.ts +5 -4
  33. package/extensions/orchestrator/enforcement.ts +80 -27
  34. package/extensions/orchestrator/extended-autocomplete.ts +7 -0
  35. package/extensions/orchestrator/git-helpers.ts +2 -2
  36. package/extensions/orchestrator/index.ts +78 -10
  37. package/extensions/orchestrator/markdown-transformer.ts +313 -0
  38. package/extensions/orchestrator/memory-embeddings.ts +36 -34
  39. package/extensions/orchestrator/memory-provenance.ts +29 -25
  40. package/extensions/orchestrator/memory-scoring.ts +23 -18
  41. package/extensions/orchestrator/memory-tools.ts +10 -3
  42. package/extensions/orchestrator/overlay-dashboard.ts +17 -5
  43. package/extensions/orchestrator/parse-model-override.ts +29 -0
  44. package/extensions/orchestrator/pi-config-review-state.ts +57 -33
  45. package/extensions/orchestrator/project-settings.ts +333 -127
  46. package/extensions/orchestrator/resolve-agent-model.ts +24 -13
  47. package/extensions/orchestrator/review-porcelain.ts +4 -0
  48. package/extensions/orchestrator/review-ui.ts +63 -15
  49. package/extensions/orchestrator/rule-placeholders.ts +411 -2
  50. package/extensions/orchestrator/rules.ts +80 -132
  51. package/extensions/orchestrator/settings-tui-helpers.ts +380 -0
  52. package/extensions/orchestrator/settings-tui-submenus.ts +647 -0
  53. package/extensions/orchestrator/settings-tui.ts +636 -0
  54. package/extensions/orchestrator/state-jsonl.ts +431 -0
  55. package/extensions/orchestrator/subagent-tool.ts +157 -59
  56. package/extensions/orchestrator/sync-limit.ts +23 -0
  57. package/extensions/orchestrator/task-lifecycle.ts +77 -67
  58. package/extensions/orchestrator/utils.ts +8 -5
  59. package/extensions/pidash/pidash.ts +193 -156
  60. package/extensions/pidiff/pidiff.ts +96 -38
  61. package/extensions/pidiff/spawn-lock.ts +62 -0
  62. package/extensions/pitasks/index.ts +430 -0
  63. package/extensions/pitasks/reminders.ts +37 -0
  64. package/extensions/pitasks/task-store.ts +467 -0
  65. package/extensions/pitasks/task-tools.ts +375 -0
  66. package/extensions/pitasks/task-widget.ts +181 -0
  67. package/extensions/pitasks/types.ts +38 -0
  68. package/extensions/providers/acpx-driver.ts +12 -2
  69. package/extensions/providers/built-in-drivers.ts +6 -6
  70. package/extensions/providers/claude-driver.ts +1 -1
  71. package/extensions/providers/cursor-cli-driver.ts +1 -1
  72. package/extensions/providers/gemini-driver.ts +1 -1
  73. package/extensions/providers/index.ts +144 -26
  74. package/extensions/providers/initialized-guard.ts +27 -0
  75. package/extensions/providers/restore-default-model.ts +596 -0
  76. package/extensions/providers/session-shutdown.ts +39 -0
  77. package/extensions/shared/coms-active.ts +23 -0
  78. package/extensions/shared/create-runtime-provider.ts +2 -0
  79. package/extensions/shared/daemon-manager.ts +0 -8
  80. package/extensions/shared/file-logger.ts +132 -25
  81. package/extensions/shared/logger.ts +50 -0
  82. package/extensions/shared/models-dev.ts +421 -0
  83. package/extensions/shared/oneshot.ts +116 -0
  84. package/extensions/shared/resolve-binary.ts +191 -0
  85. package/extensions/shared/types.ts +3 -0
  86. package/package.json +9 -3
  87. package/prompts/coderabbit-rate-limit.md +2 -0
  88. package/rules/00-orchestrator-core.md +3 -0
  89. package/rules/10-agent-routing.md +12 -1
  90. package/rules/20-code-review-loop.md +52 -25
  91. package/rules/30-prompt-templates.md +4 -0
  92. package/rules/35-memory.md +3 -3
  93. package/rules/40-critical-rules.md +3 -0
  94. package/rules/55-coms-protocol.md +84 -18
  95. package/rules/60-task-tracking.md +2 -0
  96. package/settings-keys.json +563 -0
  97. package/templates/coms-feature-manager-prompt.md +4 -6
  98. package/extensions/coms/coms-net-server.ts +0 -1642
  99. package/extensions/coms/coms-net-wrapper.ts +0 -521
  100. package/extensions/coms/coms-net.ts +0 -1589
@@ -109,6 +109,8 @@ export class OverlayListDashboard<
109
109
  > implements Component {
110
110
  private closed = false;
111
111
  private ticker: ReturnType<typeof setInterval>;
112
+ private cachedWidth: number | undefined;
113
+ private cachedLines: string[] | undefined;
112
114
 
113
115
  constructor(
114
116
  private tui: TUI,
@@ -118,7 +120,7 @@ export class OverlayListDashboard<
118
120
  private selection: OverlaySelection<TId>,
119
121
  private done: (value: TId | null) => void,
120
122
  ) {
121
- this.ticker = setInterval(() => this.tui.requestRender(), 1000);
123
+ this.ticker = setInterval(() => { this.invalidate(); this.tui.requestRender(); }, 1000);
122
124
  }
123
125
 
124
126
  private items(): TItem[] {
@@ -169,7 +171,8 @@ export class OverlayListDashboard<
169
171
  this.selection.index =
170
172
  (this.selection.index - 1 + items.length) % items.length;
171
173
  this.selection.id = items[this.selection.index]?.id;
172
- this.tui.requestRender();
174
+ this.invalidate();
175
+ this.tui.requestRender(true);
173
176
  }
174
177
  return;
175
178
  }
@@ -181,7 +184,8 @@ export class OverlayListDashboard<
181
184
  if (items.length > 0) {
182
185
  this.selection.index = (this.selection.index + 1) % items.length;
183
186
  this.selection.id = items[this.selection.index]?.id;
184
- this.tui.requestRender();
187
+ this.invalidate();
188
+ this.tui.requestRender(true);
185
189
  }
186
190
  return;
187
191
  }
@@ -193,12 +197,15 @@ export class OverlayListDashboard<
193
197
  this.close(null);
194
198
  return;
195
199
  }
196
- this.tui.requestRender();
200
+ this.invalidate();
201
+ this.tui.requestRender(true);
197
202
  }
198
203
  }
199
204
  }
200
205
 
201
206
  render(width: number): string[] {
207
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
208
+
202
209
  const theme = this.theme;
203
210
  const items = this.items();
204
211
  reconcileSelection(this.selection, items);
@@ -243,6 +250,8 @@ export class OverlayListDashboard<
243
250
  truncateToWidth(theme.fg("dim", ` ${this.spec.footerHints}`), width),
244
251
  );
245
252
 
253
+ this.cachedLines = lines;
254
+ this.cachedWidth = width;
246
255
  return lines;
247
256
  }
248
257
 
@@ -290,7 +299,10 @@ export class OverlayListDashboard<
290
299
  return out;
291
300
  }
292
301
 
293
- invalidate(): void {}
302
+ invalidate(): void {
303
+ this.cachedWidth = undefined;
304
+ this.cachedLines = undefined;
305
+ }
294
306
  }
295
307
 
296
308
  export class OverlayScrollDetail implements Component {
@@ -0,0 +1,29 @@
1
+ /** Parse 'provider/model-id' into separate provider and model. Plain 'model-id' returns undefined provider. */
2
+ export function parseModelOverride(modelStr: string | undefined): { model?: string; provider?: string } | undefined {
3
+ if (!modelStr) return undefined;
4
+ const trimmed = modelStr.trim();
5
+ if (!trimmed) return undefined;
6
+ const slashIdx = trimmed.indexOf("/");
7
+ if (slashIdx > 0) {
8
+ const provider = trimmed.slice(0, slashIdx).trim();
9
+ const model = trimmed.slice(slashIdx + 1).trim();
10
+ if (!provider || !model) return undefined;
11
+ return { provider, model };
12
+ }
13
+ return { model: trimmed };
14
+ }
15
+
16
+ /** Merge task-level model override with top-level explicit fallback. Task fields win. */
17
+ export function mergeModelOverride(
18
+ taskModel: string | undefined,
19
+ explicit: { model?: string; provider?: string } | undefined,
20
+ ): { model?: string; provider?: string } | undefined {
21
+ const taskParsed = parseModelOverride(taskModel);
22
+ if (taskParsed) {
23
+ return {
24
+ model: taskParsed.model || explicit?.model,
25
+ provider: taskParsed.provider || explicit?.provider,
26
+ };
27
+ }
28
+ return explicit;
29
+ }
@@ -1,12 +1,20 @@
1
1
  /**
2
2
  * Review state machine — tracks code review loop status.
3
- * State stored in <worktree-root>/.pi/data/pi-config-review-state.json (per-worktree, not shared).
3
+ * State stored in <worktree-root>/.pi/data/pi-config-review-state.jsonl (per-worktree, not shared).
4
4
  * Each worktree gets its own state file via resolveWorktreeRoot (--show-toplevel).
5
5
  * Used by enforcement to block git commit until all reviewers approve.
6
+ *
7
+ * Persistence: append-only JSONL via JsonlStateStore (issue #724).
8
+ * Each state mutation appends a single JSON line. The latest valid line
9
+ * is the current state. Auto-compaction prevents unbounded growth.
10
+ * On first access, migrates any legacy pi-config-review-state.json automatically.
6
11
  */
7
12
 
8
- import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "node:fs";
13
+ import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
9
14
  import { join } from "node:path";
15
+ import { getSetting } from "./project-settings.js";
16
+ import { createCachedStore } from "./state-jsonl.js";
17
+ import type { JsonlStateStore } from "./state-jsonl.js";
10
18
  import { resolveWorktreeRoot, getProjectDataDir } from "./utils.js";
11
19
 
12
20
  type StateTransitionCallback = (state: ReviewState) => void;
@@ -22,7 +30,7 @@ export function onStateTransition(cb: StateTransitionCallback): void {
22
30
  function notifyTransition(state: ReviewState): void {
23
31
  if (!onTransitionCb) return;
24
32
  try { onTransitionCb({ ...state, reviewers_pending: [...state.reviewers_pending] }); }
25
- catch (e: any) { console.debug("[pi-config-review-state] transition callback failed:", e?.message); }
33
+ catch { /* transition callback failed — fire-and-forget, never propagate */ }
26
34
  }
27
35
 
28
36
  export interface ReviewState {
@@ -37,14 +45,16 @@ export interface ReviewState {
37
45
  tests_passed: boolean;
38
46
  }
39
47
 
40
- const STATE_FILE = "pi-config-review-state.json";
48
+ const STATE_FILE_JSONL = "pi-config-review-state.jsonl";
49
+ const LEGACY_STATE_FILE = "pi-config-review-state.json";
41
50
 
42
- export function statePath(cwd: string): string {
43
- return join(getProjectDataDir(resolveWorktreeRoot(cwd)), STATE_FILE);
51
+ function getStore(cwd: string): JsonlStateStore<ReviewState> {
52
+ const dataDir = getProjectDataDir(resolveWorktreeRoot(cwd));
53
+ return createCachedStore<ReviewState>(dataDir, STATE_FILE_JSONL, LEGACY_STATE_FILE);
44
54
  }
45
55
 
46
- function ensureDataDir(cwd: string): void {
47
- getProjectDataDir(resolveWorktreeRoot(cwd));
56
+ export function statePath(cwd: string): string {
57
+ return join(getProjectDataDir(resolveWorktreeRoot(cwd)), STATE_FILE_JSONL);
48
58
  }
49
59
 
50
60
  function defaultState(): ReviewState {
@@ -61,29 +71,35 @@ function defaultState(): ReviewState {
61
71
  };
62
72
  }
63
73
 
74
+ /** Normalize a raw parsed object into a valid ReviewState with defaults for missing fields. */
75
+ function normalizeRawState(raw: any): ReviewState {
76
+ return {
77
+ status: raw.status ?? "none",
78
+ cycle: typeof raw.cycle === "number" ? raw.cycle : 0,
79
+ reviewers_pending: Array.isArray(raw.reviewers_pending) ? raw.reviewers_pending : [],
80
+ reviewers_total: typeof raw.reviewers_total === "number" ? raw.reviewers_total : 0,
81
+ findings_count: typeof raw.findings_count === "number" ? raw.findings_count : 0,
82
+ last_edit_at: typeof raw.last_edit_at === "string" ? raw.last_edit_at : null,
83
+ last_clean_at: typeof raw.last_clean_at === "string" ? raw.last_clean_at : null,
84
+ edited_during_cycle: raw.edited_during_cycle === true,
85
+ tests_passed: raw.tests_passed === true,
86
+ };
87
+ }
88
+
64
89
  export function readReviewState(cwd: string): ReviewState {
65
- const p = statePath(cwd);
66
- if (!existsSync(p)) return defaultState();
90
+ const store = getStore(cwd);
91
+ const raw = store.read();
92
+ if (raw === null) return defaultState();
67
93
  try {
68
- const raw = JSON.parse(readFileSync(p, "utf-8"));
69
- return {
70
- status: raw.status ?? "none",
71
- cycle: typeof raw.cycle === "number" ? raw.cycle : 0,
72
- reviewers_pending: Array.isArray(raw.reviewers_pending) ? raw.reviewers_pending : [],
73
- reviewers_total: typeof raw.reviewers_total === "number" ? raw.reviewers_total : 0,
74
- findings_count: typeof raw.findings_count === "number" ? raw.findings_count : 0,
75
- last_edit_at: typeof raw.last_edit_at === "string" ? raw.last_edit_at : null,
76
- last_clean_at: typeof raw.last_clean_at === "string" ? raw.last_clean_at : null,
77
- edited_during_cycle: raw.edited_during_cycle === true,
78
- tests_passed: raw.tests_passed === true,
79
- };
80
- } catch (e: any) {
81
- console.debug("[pi-config-review-state] failed to parse state:", e?.message);
94
+ return normalizeRawState(raw);
95
+ } catch {
96
+ // failed to parse state — return defaults
82
97
  return defaultState();
83
98
  }
84
99
  }
85
100
 
86
101
  function lockPath(cwd: string): string {
102
+ // Lock file sits next to the JSONL state file
87
103
  return statePath(cwd) + ".lock";
88
104
  }
89
105
 
@@ -92,7 +108,7 @@ function acquireLock(cwd: string): boolean {
92
108
  const maxRetries = 100;
93
109
  for (let i = 0; i < maxRetries; i++) {
94
110
  try {
95
- ensureDataDir(cwd);
111
+ getProjectDataDir(resolveWorktreeRoot(cwd));
96
112
  writeFileSync(lock, String(process.pid), { flag: "wx" });
97
113
  return true;
98
114
  } catch {
@@ -125,15 +141,11 @@ function releaseLock(cwd: string): void {
125
141
  }
126
142
 
127
143
  function writeState(cwd: string, state: ReviewState): void {
128
- const p = statePath(cwd);
129
144
  try {
130
- ensureDataDir(cwd);
131
- // Atomic write: temp file + rename
132
- const tmp = `${p}.${process.pid}.tmp`;
133
- writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
134
- renameSync(tmp, p);
135
- } catch (e: any) {
136
- console.debug("[pi-config-review-state] write failed:", e?.message);
145
+ const store = getStore(cwd);
146
+ store.write(state);
147
+ } catch {
148
+ // write failed — non-fatal, state file may be stale
137
149
  }
138
150
  }
139
151
 
@@ -254,6 +266,18 @@ export function isReviewClean(cwd: string): boolean {
254
266
  return true;
255
267
  }
256
268
 
269
+ /** Check if commit is allowed — clean review, no tracking, or max cycles exhausted. */
270
+ export function isCommitAllowed(cwd: string): boolean {
271
+ if (isReviewClean(cwd)) return true;
272
+ const state = readReviewState(cwd);
273
+ // Allow commit when max review cycles exhausted — cap reached, no reviewers running
274
+ if ((state.status === "has_findings" || state.status === "clean" || state.status === "needs_review") && state.reviewers_pending.length === 0) {
275
+ const maxCycles = getSetting(cwd, "review_loop_max_cycles");
276
+ if (state.cycle >= maxCycles) return true;
277
+ }
278
+ return false;
279
+ }
280
+
257
281
  /** Mark that tests have passed. Only meaningful when review status is being tracked. */
258
282
  export function markTestsPassed(cwd: string): void {
259
283
  withStateLock(cwd, (state) => {