@ilya-lesikov/pi-pi 0.9.0 → 0.10.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.
- package/3p/pi-ask-user/index.ts +71 -124
- package/3p/pi-ask-user/single-select-layout.ts +3 -14
- package/3p/pi-subagents/package.json +13 -7
- package/3p/pi-subagents/src/agent-manager.ts +243 -79
- package/3p/pi-subagents/src/agent-runner.ts +501 -365
- package/3p/pi-subagents/src/agent-types.ts +46 -57
- package/3p/pi-subagents/src/cross-extension-rpc.ts +52 -46
- package/3p/pi-subagents/src/custom-agents.ts +30 -6
- package/3p/pi-subagents/src/default-agents.ts +25 -43
- package/3p/pi-subagents/src/enabled-models.ts +180 -0
- package/3p/pi-subagents/src/index.ts +599 -839
- package/3p/pi-subagents/src/model-resolver.ts +26 -7
- package/3p/pi-subagents/src/output-file.ts +21 -2
- package/3p/pi-subagents/src/prompts.ts +17 -3
- package/3p/pi-subagents/src/schedule-store.ts +153 -0
- package/3p/pi-subagents/src/schedule.ts +365 -0
- package/3p/pi-subagents/src/settings.ts +261 -0
- package/3p/pi-subagents/src/skill-loader.ts +77 -54
- package/3p/pi-subagents/src/status-note.ts +25 -0
- package/3p/pi-subagents/src/types.ts +97 -6
- package/3p/pi-subagents/src/ui/agent-widget.ts +94 -21
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +147 -35
- package/3p/pi-subagents/src/ui/schedule-menu.ts +104 -0
- package/3p/pi-subagents/src/ui/viewer-keys.ts +39 -0
- package/3p/pi-subagents/src/usage.ts +60 -0
- package/3p/pi-subagents/src/worktree.ts +49 -20
- package/extensions/orchestrator/agents/advisor.ts +13 -8
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/code-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/constraints.test.ts +26 -0
- package/extensions/orchestrator/agents/constraints.ts +12 -2
- package/extensions/orchestrator/agents/deep-debugger.ts +13 -8
- package/extensions/orchestrator/agents/explore.ts +12 -6
- package/extensions/orchestrator/agents/librarian.ts +13 -5
- package/extensions/orchestrator/agents/plan-reviewer.ts +8 -3
- package/extensions/orchestrator/agents/planner.ts +8 -3
- package/extensions/orchestrator/agents/pools.test.ts +56 -0
- package/extensions/orchestrator/agents/prompts.test.ts +52 -10
- package/extensions/orchestrator/agents/registry.test.ts +245 -0
- package/extensions/orchestrator/agents/registry.ts +145 -10
- package/extensions/orchestrator/agents/reviewer.ts +14 -8
- package/extensions/orchestrator/agents/task.ts +12 -6
- package/extensions/orchestrator/agents/tool-routing.ts +213 -85
- package/extensions/orchestrator/agents/wait-for-completion.test.ts +171 -0
- package/extensions/orchestrator/ast-search.test.ts +124 -0
- package/extensions/orchestrator/billing-spoof.test.ts +67 -0
- package/extensions/orchestrator/billing-spoof.ts +95 -0
- package/extensions/orchestrator/cbm.more.test.ts +358 -0
- package/extensions/orchestrator/command-handlers.ts +6 -0
- package/extensions/orchestrator/commands.test.ts +15 -2
- package/extensions/orchestrator/commands.ts +1 -1
- package/extensions/orchestrator/config.test.ts +89 -1
- package/extensions/orchestrator/config.ts +102 -19
- package/extensions/orchestrator/context.test.ts +46 -0
- package/extensions/orchestrator/context.ts +18 -5
- package/extensions/orchestrator/custom-footer.test.ts +24 -10
- package/extensions/orchestrator/custom-footer.ts +4 -2
- package/extensions/orchestrator/doctor.more.test.ts +315 -0
- package/extensions/orchestrator/doctor.ts +6 -2
- package/extensions/orchestrator/event-handlers.more.test.ts +561 -0
- package/extensions/orchestrator/event-handlers.test.ts +96 -9
- package/extensions/orchestrator/event-handlers.ts +344 -151
- package/extensions/orchestrator/exa.more.test.ts +118 -0
- package/extensions/orchestrator/flant-infra.more.test.ts +326 -0
- package/extensions/orchestrator/flant-infra.test.ts +127 -0
- package/extensions/orchestrator/flant-infra.ts +113 -39
- package/extensions/orchestrator/index.test.ts +76 -0
- package/extensions/orchestrator/index.ts +2 -0
- package/extensions/orchestrator/integration.test.ts +183 -65
- package/extensions/orchestrator/model-registry.test.ts +2 -1
- package/extensions/orchestrator/model-registry.ts +12 -2
- package/extensions/orchestrator/orchestrator.test.ts +67 -0
- package/extensions/orchestrator/orchestrator.ts +119 -27
- package/extensions/orchestrator/phases/brainstorm.test.ts +30 -1
- package/extensions/orchestrator/phases/brainstorm.ts +43 -6
- package/extensions/orchestrator/phases/implementation.ts +2 -0
- package/extensions/orchestrator/phases/machine.test.ts +17 -1
- package/extensions/orchestrator/phases/machine.ts +4 -1
- package/extensions/orchestrator/phases/planning.test.ts +47 -1
- package/extensions/orchestrator/phases/planning.ts +18 -3
- package/extensions/orchestrator/phases/review-task.ts +5 -0
- package/extensions/orchestrator/phases/review.test.ts +10 -0
- package/extensions/orchestrator/phases/review.ts +22 -7
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +1 -0
- package/extensions/orchestrator/phases/verdict.ts +6 -5
- package/extensions/orchestrator/plannotator-open-failure.test.ts +67 -0
- package/extensions/orchestrator/plannotator.test.ts +38 -1
- package/extensions/orchestrator/plannotator.ts +50 -3
- package/extensions/orchestrator/pp-menu.leaves.test.ts +590 -0
- package/extensions/orchestrator/pp-menu.more.test.ts +524 -0
- package/extensions/orchestrator/pp-menu.test.ts +114 -7
- package/extensions/orchestrator/pp-menu.ts +579 -90
- package/extensions/orchestrator/pp-state-tools.test.ts +80 -0
- package/extensions/orchestrator/pp-state-tools.ts +65 -0
- package/extensions/orchestrator/rate-limit-fallback.more.test.ts +241 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +20 -1
- package/extensions/orchestrator/rate-limit-fallback.ts +12 -0
- package/extensions/orchestrator/review-files.test.ts +26 -0
- package/extensions/orchestrator/review-files.ts +3 -0
- package/extensions/orchestrator/state.test.ts +73 -1
- package/extensions/orchestrator/state.ts +95 -23
- package/extensions/orchestrator/suppress-pierre-theme-spam.test.ts +56 -0
- package/extensions/orchestrator/suppress-pierre-theme-spam.ts +45 -0
- package/extensions/orchestrator/validate-artifacts.ts +1 -1
- package/package.json +6 -2
- package/scripts/test-3p.sh +52 -0
|
@@ -56,6 +56,20 @@ export interface TaskState {
|
|
|
56
56
|
mode?: TaskMode;
|
|
57
57
|
effectiveMode?: TaskMode;
|
|
58
58
|
autonomousConfig?: AutonomousConfig;
|
|
59
|
+
// Manual auto-review-until-approved (#5/#7): drives the same review-cycle loop
|
|
60
|
+
// as autonomous mode over ONE phase, independently of effectiveMode (so it
|
|
61
|
+
// works in force-guided brainstorm/debug/review). advanceOnComplete=false
|
|
62
|
+
// (item 5) stops in-phase on approve/max-passes; =true (item 7) finalizes and
|
|
63
|
+
// transitions to the next phase. deferredAdvance carries the next-phase inputs
|
|
64
|
+
// the plain-continue path would have collected, captured up front because the
|
|
65
|
+
// headless pp_phase_complete branch cannot prompt.
|
|
66
|
+
manualAutoReview?: {
|
|
67
|
+
phase: string;
|
|
68
|
+
preset: string;
|
|
69
|
+
maxPasses: number;
|
|
70
|
+
advanceOnComplete: boolean;
|
|
71
|
+
deferredAdvance?: { mode?: TaskMode; autonomousConfig?: AutonomousConfig; plannerPreset?: string };
|
|
72
|
+
};
|
|
59
73
|
plannerFailureAutoRetried?: boolean;
|
|
60
74
|
reviewerFailureAutoRetried?: boolean;
|
|
61
75
|
// Set once afterImplement has run for the current implement phase (either at
|
|
@@ -66,6 +80,10 @@ export interface TaskState {
|
|
|
66
80
|
// set of repos to review; index is the next repo. Persisted so the loop resumes
|
|
67
81
|
// on the next /pp after the agent fixes one repo's feedback. Undefined = no loop.
|
|
68
82
|
plannotatorCursor?: { repoPaths: string[]; index: number };
|
|
83
|
+
// The phase the task was in when it was marked done. Recorded so the Resume
|
|
84
|
+
// "reopen a done task" flow (#2) can restore the actual last working phase
|
|
85
|
+
// (done itself carries no phase history). Absent on legacy done tasks.
|
|
86
|
+
completedFrom?: Phase;
|
|
69
87
|
}
|
|
70
88
|
|
|
71
89
|
export interface TaskInfo {
|
|
@@ -174,11 +192,18 @@ export function saveTask(taskDir: string, state: TaskState): void {
|
|
|
174
192
|
writeFileSync(taskStatePath(taskDir), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
175
193
|
}
|
|
176
194
|
|
|
177
|
-
export
|
|
195
|
+
export interface ListTasksOptions {
|
|
196
|
+
type?: TaskType;
|
|
197
|
+
includeDone?: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function listTasks(cwd: string, typeOrOptions?: TaskType | ListTasksOptions): TaskInfo[] {
|
|
201
|
+
const options: ListTasksOptions =
|
|
202
|
+
typeof typeOrOptions === "string" ? { type: typeOrOptions } : typeOrOptions ?? {};
|
|
178
203
|
const base = stateDir(cwd);
|
|
179
204
|
if (!existsSync(base)) return [];
|
|
180
205
|
|
|
181
|
-
const types: TaskType[] = type ? [type] : ["implement", "debug", "brainstorm", "review", "quick"];
|
|
206
|
+
const types: TaskType[] = options.type ? [options.type] : ["implement", "debug", "brainstorm", "review", "quick"];
|
|
182
207
|
const results: TaskInfo[] = [];
|
|
183
208
|
|
|
184
209
|
for (const t of types) {
|
|
@@ -193,7 +218,7 @@ export function listTasks(cwd: string, type?: TaskType): TaskInfo[] {
|
|
|
193
218
|
|
|
194
219
|
try {
|
|
195
220
|
const state = loadTask(dir);
|
|
196
|
-
if (state.phase !== "done") {
|
|
221
|
+
if (state.phase !== "done" || options.includeDone) {
|
|
197
222
|
results.push({ dir, state, type: t });
|
|
198
223
|
}
|
|
199
224
|
} catch {
|
|
@@ -229,9 +254,9 @@ export async function lockTask(taskDir: string, timeouts: TimeoutConfig): Promis
|
|
|
229
254
|
return release;
|
|
230
255
|
}
|
|
231
256
|
|
|
232
|
-
export function
|
|
257
|
+
export function getUnlockedTasks(cwd: string, lockStale?: number): TaskInfo[] {
|
|
233
258
|
const tasks = listTasks(cwd);
|
|
234
|
-
if (tasks.length === 0) return
|
|
259
|
+
if (tasks.length === 0) return [];
|
|
235
260
|
|
|
236
261
|
const stale = lockStale ?? 600000;
|
|
237
262
|
const unlocked: TaskInfo[] = [];
|
|
@@ -245,9 +270,24 @@ export function getActiveTask(cwd: string, lockStale?: number): TaskInfo | null
|
|
|
245
270
|
getLogger().warn({ s: "state", dir: task.dir }, "failed to check lock");
|
|
246
271
|
}
|
|
247
272
|
}
|
|
273
|
+
return unlocked;
|
|
274
|
+
}
|
|
248
275
|
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
export type ActiveTaskStatus =
|
|
277
|
+
| { kind: "none" }
|
|
278
|
+
| { kind: "single"; task: TaskInfo }
|
|
279
|
+
| { kind: "ambiguous"; tasks: TaskInfo[] };
|
|
280
|
+
|
|
281
|
+
export function getActiveTaskStatus(cwd: string, lockStale?: number): ActiveTaskStatus {
|
|
282
|
+
const unlocked = getUnlockedTasks(cwd, lockStale);
|
|
283
|
+
if (unlocked.length === 0) return { kind: "none" };
|
|
284
|
+
if (unlocked.length === 1) return { kind: "single", task: unlocked[0]! };
|
|
285
|
+
return { kind: "ambiguous", tasks: unlocked };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function getActiveTask(cwd: string, lockStale?: number): TaskInfo | null {
|
|
289
|
+
const status = getActiveTaskStatus(cwd, lockStale);
|
|
290
|
+
return status.kind === "single" ? status.task : null;
|
|
251
291
|
}
|
|
252
292
|
|
|
253
293
|
export function validateFromPath(cwd: string, fromPath: string): { ok: true; dir: string } | { ok: false; reason: string } {
|
|
@@ -284,28 +324,60 @@ export function taskName(taskDir: string): string {
|
|
|
284
324
|
}
|
|
285
325
|
}
|
|
286
326
|
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
327
|
+
// Non-heading, non-empty content of a markdown file, collapsed onto one line
|
|
328
|
+
// (multiple content lines joined with a space), or null. Spanning multiple
|
|
329
|
+
// lines lets a multi-line request survive the later truncate-with-ellipsis
|
|
330
|
+
// instead of being cut off at the first content line. Bounded so a large file
|
|
331
|
+
// (e.g. RESEARCH.md) never builds an unbounded string.
|
|
332
|
+
function firstMarkdownContent(path: string): string | null {
|
|
333
|
+
if (!existsSync(path)) return null;
|
|
334
|
+
const content = readFileSync(path, "utf-8");
|
|
335
|
+
const lines = content.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
336
|
+
if (lines.length === 0) return null;
|
|
337
|
+
return lines.join(" ").slice(0, 700);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// The full, untrimmed task name (used for the Resume right-pane description).
|
|
341
|
+
// Resolves a real intent for generic-description tasks via a fallback chain:
|
|
342
|
+
// USER_REQUEST.md → RESEARCH.md → first artifact → dir slug.
|
|
343
|
+
export function taskFullName(taskDir: string, state: TaskState): string {
|
|
291
344
|
let desc = state.description ?? "";
|
|
292
345
|
|
|
293
346
|
if (["implement", "debug", "brainstorm", "review", "quick"].includes(desc)) {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (firstContent) desc = firstContent;
|
|
300
|
-
}
|
|
347
|
+
const fallback =
|
|
348
|
+
firstMarkdownContent(join(taskDir, "USER_REQUEST.md")) ??
|
|
349
|
+
firstMarkdownContent(join(taskDir, "RESEARCH.md")) ??
|
|
350
|
+
firstArtifactTitle(taskDir);
|
|
351
|
+
if (fallback) desc = fallback;
|
|
301
352
|
}
|
|
302
353
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
354
|
+
desc = desc.replace(/\s+/g, " ").trim();
|
|
355
|
+
return desc || dirSlugName(taskDir);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function firstArtifactTitle(taskDir: string): string | null {
|
|
359
|
+
const artifactsDir = join(taskDir, "artifacts");
|
|
360
|
+
if (!existsSync(artifactsDir)) return null;
|
|
361
|
+
const files = readdirSync(artifactsDir).filter((f) => f.endsWith(".md")).sort();
|
|
362
|
+
for (const file of files) {
|
|
363
|
+
const content = readFileSync(join(artifactsDir, file), "utf-8");
|
|
364
|
+
const lines = content.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
365
|
+
const heading = lines.find((l) => l.startsWith("# "));
|
|
366
|
+
if (heading) return heading.replace(/^#\s+/, "");
|
|
367
|
+
const firstContent = lines.find((l) => !l.startsWith("#"));
|
|
368
|
+
if (firstContent) return firstContent;
|
|
307
369
|
}
|
|
308
|
-
return
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Same as taskName but reuses an already-loaded TaskState (avoids re-reading
|
|
374
|
+
// state.json per resume-menu entry). Returns a TITLE trimmed to fit one line.
|
|
375
|
+
// The cap is conservative (fits within ~80 cols after the `→ N. ` selection
|
|
376
|
+
// prefix and a ` — <age>` suffix are added) so a Resume item never wraps.
|
|
377
|
+
export function taskNameFromState(taskDir: string, state: TaskState): string {
|
|
378
|
+
const desc = taskFullName(taskDir, state);
|
|
379
|
+
if (desc.length > 60) return desc.slice(0, 57) + "...";
|
|
380
|
+
return desc;
|
|
309
381
|
}
|
|
310
382
|
|
|
311
383
|
function dirSlugName(taskDir: string): string {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { suppressPierreThemeSpam } from "./suppress-pierre-theme-spam.js";
|
|
3
|
+
|
|
4
|
+
const INSTALLED_KEY = Symbol.for("pi-pi:pierre-theme-spam-suppressed");
|
|
5
|
+
const DUP = "SharedHighlight.registerCustomTheme: theme name already registered";
|
|
6
|
+
|
|
7
|
+
describe("suppressPierreThemeSpam", () => {
|
|
8
|
+
let original: typeof console.error;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
original = console.error;
|
|
12
|
+
(globalThis as any)[INSTALLED_KEY] = undefined;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
console.error = original;
|
|
17
|
+
(globalThis as any)[INSTALLED_KEY] = undefined;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("drops the benign duplicate-theme error for each known pierre theme", () => {
|
|
21
|
+
const spy = vi.fn();
|
|
22
|
+
console.error = spy;
|
|
23
|
+
suppressPierreThemeSpam();
|
|
24
|
+
|
|
25
|
+
for (const name of ["pierre-dark", "pierre-dark-soft", "pierre-light", "pierre-light-soft"]) {
|
|
26
|
+
console.error(DUP, name);
|
|
27
|
+
}
|
|
28
|
+
expect(spy).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("passes through unrelated console.error calls untouched", () => {
|
|
32
|
+
const spy = vi.fn();
|
|
33
|
+
console.error = spy;
|
|
34
|
+
suppressPierreThemeSpam();
|
|
35
|
+
|
|
36
|
+
console.error("something else entirely");
|
|
37
|
+
console.error(DUP, "some-other-theme"); // same prefix, unknown theme → not ours
|
|
38
|
+
console.error("real error", new Error("boom"));
|
|
39
|
+
|
|
40
|
+
expect(spy).toHaveBeenCalledTimes(3);
|
|
41
|
+
expect(spy).toHaveBeenNthCalledWith(1, "something else entirely");
|
|
42
|
+
expect(spy).toHaveBeenNthCalledWith(2, DUP, "some-other-theme");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("installs the filter only once (idempotent, no wrapper stacking)", () => {
|
|
46
|
+
const spy = vi.fn();
|
|
47
|
+
console.error = spy;
|
|
48
|
+
suppressPierreThemeSpam();
|
|
49
|
+
const afterFirst = console.error;
|
|
50
|
+
suppressPierreThemeSpam();
|
|
51
|
+
expect(console.error).toBe(afterFirst);
|
|
52
|
+
|
|
53
|
+
console.error("passthrough");
|
|
54
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Silences a benign, TUI-corrupting `console.error` from `@pierre/diffs`.
|
|
3
|
+
*
|
|
4
|
+
* `@pierre/diffs/dist/highlighter/shared_highlighter.js` registers four custom
|
|
5
|
+
* themes (`pierre-dark`, `pierre-dark-soft`, `pierre-light`, `pierre-light-soft`)
|
|
6
|
+
* as an import-time side effect, against a module-singleton Map. When that module
|
|
7
|
+
* graph is evaluated more than once — which it is here, because pi loads
|
|
8
|
+
* extensions through jiti with `moduleCache: false` and the /pp menu is pulled in
|
|
9
|
+
* via a dynamic `import()` — each re-registration trips a
|
|
10
|
+
* `console.error("SharedHighlight.registerCustomTheme: theme name already registered", name)`.
|
|
11
|
+
*
|
|
12
|
+
* pi takes over stdout during the interactive TUI (its output-guard reroutes
|
|
13
|
+
* stdout→stderr) but does not guard stderr, so that stray error visibly corrupts
|
|
14
|
+
* the rendered UI (seen when opening/closing /pp).
|
|
15
|
+
*
|
|
16
|
+
* We can't dedupe the registration (jiti's cache is off) and there's no
|
|
17
|
+
* patch-package infra in this repo, so we drop exactly this one third-party
|
|
18
|
+
* message and delegate everything else untouched. Installed once, process-wide.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const DUP_THEME_PREFIX = "SharedHighlight.registerCustomTheme: theme name already registered";
|
|
22
|
+
const PIERRE_THEME_NAMES = new Set(["pierre-dark", "pierre-dark-soft", "pierre-light", "pierre-light-soft"]);
|
|
23
|
+
|
|
24
|
+
// Marked on globalThis rather than a module-local flag: pi loads extensions via
|
|
25
|
+
// jiti with `moduleCache: false`, so this module can be re-evaluated (resetting
|
|
26
|
+
// module scope) — without a process-wide marker we'd stack console.error wrappers.
|
|
27
|
+
const INSTALLED_KEY = Symbol.for("pi-pi:pierre-theme-spam-suppressed");
|
|
28
|
+
|
|
29
|
+
export function suppressPierreThemeSpam(): void {
|
|
30
|
+
if ((globalThis as any)[INSTALLED_KEY]) return;
|
|
31
|
+
(globalThis as any)[INSTALLED_KEY] = true;
|
|
32
|
+
|
|
33
|
+
const original = console.error.bind(console);
|
|
34
|
+
console.error = (...args: unknown[]) => {
|
|
35
|
+
if (
|
|
36
|
+
args.length >= 2 &&
|
|
37
|
+
args[0] === DUP_THEME_PREFIX &&
|
|
38
|
+
typeof args[1] === "string" &&
|
|
39
|
+
PIERRE_THEME_NAMES.has(args[1])
|
|
40
|
+
) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
original(...args);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -5,7 +5,7 @@ const USER_REQUEST_ALLOWED_SECTIONS = ["Problem", "Constraints"] as const;
|
|
|
5
5
|
const RESEARCH_REQUIRED_SECTIONS = ["Affected Code", "Architecture Context", "Constraints & Edge Cases"] as const;
|
|
6
6
|
const RESEARCH_ALLOWED_SECTIONS = ["Affected Code", "Architecture Context", "Constraints & Edge Cases", "Open Questions"] as const;
|
|
7
7
|
const PLAN_REQUIRED_SECTIONS = ["Scope", "Checklist"] as const;
|
|
8
|
-
const PLAN_ALLOWED_SECTIONS = ["Scope", "Checklist", "Pattern constraints", "Blockers"] as const;
|
|
8
|
+
export const PLAN_ALLOWED_SECTIONS = ["Scope", "Checklist", "Pattern constraints", "Blockers"] as const;
|
|
9
9
|
const PLACEHOLDER_PATTERNS = /^(?:[-*.…—]|tbd|todo|n\/a|na|none|\.{2,})$/i;
|
|
10
10
|
|
|
11
11
|
function splitLines(content: string): string[] {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilya-lesikov/pi-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"postinstall": "bash scripts/postinstall.sh",
|
|
37
37
|
"test": "vitest run --exclude '3p/**'",
|
|
38
|
-
"test:watch": "vitest --exclude '3p/**'"
|
|
38
|
+
"test:watch": "vitest --exclude '3p/**'",
|
|
39
|
+
"test:3p": "bash scripts/test-3p.sh",
|
|
40
|
+
"test:all": "vitest run --exclude '3p/**' && bash scripts/test-3p.sh",
|
|
41
|
+
"coverage": "vitest run --exclude '3p/**' --coverage"
|
|
39
42
|
},
|
|
40
43
|
"pi": {
|
|
41
44
|
"extensions": [
|
|
@@ -68,6 +71,7 @@
|
|
|
68
71
|
"@earendil-works/pi-coding-agent": "^0.75.5",
|
|
69
72
|
"@earendil-works/pi-tui": "^0.75.5",
|
|
70
73
|
"@sinclair/typebox": "^0.34.0",
|
|
74
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
71
75
|
"typescript": "^5.8.0",
|
|
72
76
|
"vitest": "^4.1.4"
|
|
73
77
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Aggregates the test suites of the vendored 3p/ extensions, each run with its OWN native runner
|
|
3
|
+
# (vitest for pi-subagents/pi-tasks, bun for pi-lsp/pi-ask-user/pi-plannotator). The root
|
|
4
|
+
# `npm test` excludes 3p/**, so without this the fork-delta tests never gate CI.
|
|
5
|
+
#
|
|
6
|
+
# Exit non-zero if any package's suite fails. Runs every package even if an earlier one fails, so
|
|
7
|
+
# one invocation reports all breakage.
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
cd "$(dirname "$0")/.."
|
|
10
|
+
ROOT="$(pwd)"
|
|
11
|
+
|
|
12
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
13
|
+
echo "ERROR: 'bun' is not on PATH — pi-lsp, pi-ask-user and pi-plannotator gate on it." >&2
|
|
14
|
+
echo "Install bun (https://bun.sh) and re-run, or run the vitest 3p suites individually." >&2
|
|
15
|
+
exit 127
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
failures=()
|
|
19
|
+
|
|
20
|
+
run() {
|
|
21
|
+
local name="$1"; shift
|
|
22
|
+
local dir="$1"; shift
|
|
23
|
+
echo ""
|
|
24
|
+
echo "──────────────────────────────────────────────"
|
|
25
|
+
echo "▶ 3p/$name ($*)"
|
|
26
|
+
echo "──────────────────────────────────────────────"
|
|
27
|
+
( cd "$ROOT/3p/$dir" && "$@" )
|
|
28
|
+
if [ $? -ne 0 ]; then
|
|
29
|
+
failures+=("$name")
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# vitest packages (use the package's own vitest.config.ts via npx vitest run).
|
|
34
|
+
run "pi-subagents" "pi-subagents" npx vitest run
|
|
35
|
+
run "pi-tasks" "pi-tasks" npx vitest run
|
|
36
|
+
|
|
37
|
+
# bun packages. pi-ask-user has no `test` npm script, so invoke `bun test` directly.
|
|
38
|
+
run "pi-lsp" "pi-lsp" bun test
|
|
39
|
+
run "pi-ask-user" "pi-ask-user" bun test
|
|
40
|
+
|
|
41
|
+
# pi-plannotator: scope explicitly to the shipped pi-extension app + the fork-parity guards.
|
|
42
|
+
# Bare `bun test` would reach unshipped upstream workspace packages that need alias setup.
|
|
43
|
+
run "pi-plannotator" "pi-plannotator" bun test apps/pi-extension tests/parity
|
|
44
|
+
|
|
45
|
+
echo ""
|
|
46
|
+
echo "──────────────────────────────────────────────"
|
|
47
|
+
if [ ${#failures[@]} -eq 0 ]; then
|
|
48
|
+
echo "✓ all 3p suites passed"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
echo "✗ 3p suites FAILED: ${failures[*]}"
|
|
52
|
+
exit 1
|