@ilya-lesikov/pi-pi 0.5.0 → 0.7.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 +65 -49
- package/3p/pi-subagents/src/agent-manager.ts +8 -0
- package/3p/pi-subagents/src/agent-runner.ts +112 -19
- package/3p/pi-subagents/src/index.ts +3 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +32 -19
- package/extensions/orchestrator/agents/code-reviewer.ts +31 -17
- package/extensions/orchestrator/agents/constraints.ts +55 -0
- package/extensions/orchestrator/agents/explore.ts +13 -13
- package/extensions/orchestrator/agents/librarian.ts +12 -9
- package/extensions/orchestrator/agents/plan-reviewer.ts +31 -19
- package/extensions/orchestrator/agents/planner.ts +28 -23
- package/extensions/orchestrator/agents/registry.ts +2 -1
- package/extensions/orchestrator/agents/repo-context.ts +11 -0
- package/extensions/orchestrator/agents/task.ts +14 -11
- package/extensions/orchestrator/agents/tool-routing.ts +17 -32
- package/extensions/orchestrator/ast-search.ts +2 -1
- package/extensions/orchestrator/cbm.test.ts +35 -0
- package/extensions/orchestrator/cbm.ts +43 -13
- package/extensions/orchestrator/command-handlers.test.ts +390 -19
- package/extensions/orchestrator/command-handlers.ts +68 -28
- package/extensions/orchestrator/commands.test.ts +255 -2
- package/extensions/orchestrator/commands.ts +108 -10
- package/extensions/orchestrator/config.test.ts +289 -68
- package/extensions/orchestrator/config.ts +631 -121
- package/extensions/orchestrator/context.test.ts +177 -10
- package/extensions/orchestrator/context.ts +115 -14
- package/extensions/orchestrator/custom-footer.ts +7 -3
- package/extensions/orchestrator/doctor.test.ts +561 -0
- package/extensions/orchestrator/doctor.ts +702 -0
- package/extensions/orchestrator/event-handlers.test.ts +84 -22
- package/extensions/orchestrator/event-handlers.ts +1220 -343
- package/extensions/orchestrator/exa.test.ts +46 -0
- package/extensions/orchestrator/exa.ts +16 -10
- package/extensions/orchestrator/flant-infra.test.ts +511 -0
- package/extensions/orchestrator/flant-infra.ts +390 -49
- package/extensions/orchestrator/index.ts +13 -2
- package/extensions/orchestrator/integration.test.ts +3065 -118
- package/extensions/orchestrator/log.test.ts +219 -0
- package/extensions/orchestrator/log.ts +153 -0
- package/extensions/orchestrator/model-registry.test.ts +302 -0
- package/extensions/orchestrator/model-registry.ts +298 -0
- package/extensions/orchestrator/model-version.test.ts +27 -0
- package/extensions/orchestrator/model-version.ts +19 -0
- package/extensions/orchestrator/orchestrator.test.ts +206 -56
- package/extensions/orchestrator/orchestrator.ts +298 -140
- package/extensions/orchestrator/phases/brainstorm.test.ts +10 -7
- package/extensions/orchestrator/phases/brainstorm.ts +41 -36
- package/extensions/orchestrator/phases/implementation.ts +7 -11
- package/extensions/orchestrator/phases/machine.test.ts +27 -8
- package/extensions/orchestrator/phases/machine.ts +13 -0
- package/extensions/orchestrator/phases/planning.ts +57 -31
- package/extensions/orchestrator/phases/review-task.ts +3 -7
- package/extensions/orchestrator/phases/review.test.ts +54 -0
- package/extensions/orchestrator/phases/review.ts +89 -48
- package/extensions/orchestrator/phases/spawn-blocking.test.ts +69 -0
- package/extensions/orchestrator/phases/verdict.test.ts +139 -0
- package/extensions/orchestrator/phases/verdict.ts +82 -0
- package/extensions/orchestrator/plannotator.test.ts +85 -0
- package/extensions/orchestrator/plannotator.ts +24 -6
- package/extensions/orchestrator/pp-menu.test.ts +207 -0
- package/extensions/orchestrator/pp-menu.ts +2741 -373
- package/extensions/orchestrator/repo-utils.test.ts +151 -0
- package/extensions/orchestrator/repo-utils.ts +67 -0
- package/extensions/orchestrator/spawn-cleanup.test.ts +57 -0
- package/extensions/orchestrator/spawn-cleanup.ts +35 -0
- package/extensions/orchestrator/state.test.ts +76 -6
- package/extensions/orchestrator/state.ts +128 -44
- package/extensions/orchestrator/subagent-session-marker.test.ts +36 -0
- package/extensions/orchestrator/test-helpers.ts +229 -0
- package/extensions/orchestrator/tracer.test.ts +132 -0
- package/extensions/orchestrator/tracer.ts +127 -0
- package/extensions/orchestrator/transition-controller.test.ts +207 -0
- package/extensions/orchestrator/transition-controller.ts +259 -0
- package/extensions/orchestrator/usage-tracker.test.ts +563 -0
- package/extensions/orchestrator/usage-tracker.ts +96 -12
- package/extensions/orchestrator/validate-artifacts.test.ts +83 -1
- package/package.json +2 -1
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { SUB_MODEL_PREFIX, SUB_PROVIDER } from "./flant-infra.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A usage unit is subscription-routed (flat-rate personal Claude subscription,
|
|
8
|
+
* not per-token billed) when its main-turn provider is the subscription
|
|
9
|
+
* provider, or when the registered model id carries the `sub/` prefix. The
|
|
10
|
+
* prefix is the only signal available for subagents (subagents:completed has no
|
|
11
|
+
* provider field), so we always check it in addition to the provider.
|
|
12
|
+
*/
|
|
13
|
+
export function isSubscriptionRouted(modelId?: string, provider?: string): boolean {
|
|
14
|
+
if (provider === SUB_PROVIDER) return true;
|
|
15
|
+
return typeof modelId === "string" && modelId.startsWith(SUB_MODEL_PREFIX);
|
|
16
|
+
}
|
|
4
17
|
|
|
5
18
|
export interface ModelUsage {
|
|
6
19
|
inputTokens: number;
|
|
7
20
|
outputTokens: number;
|
|
8
21
|
cacheReadTokens: number;
|
|
9
22
|
cacheWriteTokens: number;
|
|
23
|
+
cacheSupported: boolean;
|
|
10
24
|
turns: number;
|
|
25
|
+
/** Flat-rate personal subscription: dollars are excluded from cost totals, tokens are not. */
|
|
26
|
+
subscription: boolean;
|
|
11
27
|
}
|
|
12
28
|
|
|
13
29
|
export interface UsageTracker {
|
|
14
|
-
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number): void;
|
|
30
|
+
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number, cacheSupported?: boolean): void;
|
|
15
31
|
recordSubagentCompletion(tokens: { input?: number; output?: number; total?: number }, cost?: number, meta?: { description?: string; agentType?: string; modelId?: string; durationMs?: number; toolUses?: number }): void;
|
|
16
32
|
loadFromSummary(summary: Record<string, unknown>): void;
|
|
17
33
|
getTotalInputTokens(): number;
|
|
18
34
|
getTotalOutputTokens(): number;
|
|
19
35
|
getTotalCacheReadTokens(): number;
|
|
20
36
|
getTotalCacheWriteTokens(): number;
|
|
37
|
+
/** Total input the model actually processed: uncached input + cache read + cache write (main + subagents). */
|
|
38
|
+
getTotalProcessedInputTokens(): number;
|
|
21
39
|
getTotalCost(): number;
|
|
22
40
|
getMainInputTokens(): number;
|
|
23
41
|
getMainOutputTokens(): number;
|
|
@@ -25,6 +43,7 @@ export interface UsageTracker {
|
|
|
25
43
|
getMainCacheWriteTokens(): number;
|
|
26
44
|
getMainCost(): number;
|
|
27
45
|
getCacheHitRate(): number;
|
|
46
|
+
isCacheSupported(): boolean;
|
|
28
47
|
getPerModelUsage(): Record<string, ModelUsage>;
|
|
29
48
|
getSubagentTotals(): { inputTokens: number; outputTokens: number; cost: number };
|
|
30
49
|
getSubagentList(): SubagentUsage[];
|
|
@@ -40,9 +59,12 @@ export interface SubagentUsage {
|
|
|
40
59
|
outputTokens: number;
|
|
41
60
|
cacheReadTokens: number;
|
|
42
61
|
cacheWriteTokens: number;
|
|
62
|
+
cacheSupported: boolean;
|
|
43
63
|
cost: number;
|
|
44
64
|
durationMs: number;
|
|
45
65
|
toolUses: number;
|
|
66
|
+
/** Flat-rate personal subscription: dollars are excluded from cost totals, tokens are not. */
|
|
67
|
+
subscription: boolean;
|
|
46
68
|
}
|
|
47
69
|
|
|
48
70
|
interface TrackerState {
|
|
@@ -55,6 +77,8 @@ interface TrackerState {
|
|
|
55
77
|
totalTurns: number;
|
|
56
78
|
subagentInputTokens: number;
|
|
57
79
|
subagentOutputTokens: number;
|
|
80
|
+
subagentCacheReadTokens: number;
|
|
81
|
+
subagentCacheWriteTokens: number;
|
|
58
82
|
subagentCost: number;
|
|
59
83
|
models: Map<string, ModelUsage>;
|
|
60
84
|
subagents: SubagentUsage[];
|
|
@@ -86,6 +110,8 @@ function createInitialState(): TrackerState {
|
|
|
86
110
|
totalTurns: 0,
|
|
87
111
|
subagentInputTokens: 0,
|
|
88
112
|
subagentOutputTokens: 0,
|
|
113
|
+
subagentCacheReadTokens: 0,
|
|
114
|
+
subagentCacheWriteTokens: 0,
|
|
89
115
|
subagentCost: 0,
|
|
90
116
|
models: new Map<string, ModelUsage>(),
|
|
91
117
|
subagents: [],
|
|
@@ -96,12 +122,15 @@ export function createUsageTracker(): UsageTracker {
|
|
|
96
122
|
const state = createInitialState();
|
|
97
123
|
|
|
98
124
|
return {
|
|
99
|
-
recordTurn(modelId: string,
|
|
125
|
+
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number, cacheSupported?: boolean): void {
|
|
100
126
|
const safeInput = toFiniteNumber(input);
|
|
101
127
|
const safeOutput = toFiniteNumber(output);
|
|
102
128
|
const safeCacheRead = toFiniteNumber(cacheRead);
|
|
103
129
|
const safeCacheWrite = toFiniteNumber(cacheWrite);
|
|
104
|
-
const
|
|
130
|
+
const subscription = isSubscriptionRouted(modelId, provider);
|
|
131
|
+
// Subscription-routed turns are flat-rate: keep the tokens but exclude the
|
|
132
|
+
// fictitious per-token dollars so totals stay paid-only by construction.
|
|
133
|
+
const safeCost = subscription ? 0 : toFiniteNumber(cost);
|
|
105
134
|
|
|
106
135
|
state.totalInputTokens += safeInput;
|
|
107
136
|
state.totalOutputTokens += safeOutput;
|
|
@@ -110,19 +139,27 @@ export function createUsageTracker(): UsageTracker {
|
|
|
110
139
|
state.totalCost += safeCost;
|
|
111
140
|
state.totalTurns += 1;
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
// Key subscription turns under the sub/ prefix even when detected only by
|
|
143
|
+
// provider (bare model id), so a paid and a subscription turn for the same
|
|
144
|
+
// underlying model never share a row and the paid dollars stay visible.
|
|
145
|
+
const baseKey = modelId || "unknown-model";
|
|
146
|
+
const key = subscription && !baseKey.startsWith(SUB_MODEL_PREFIX) ? `${SUB_MODEL_PREFIX}${baseKey}` : baseKey;
|
|
114
147
|
const usage = state.models.get(key) ?? {
|
|
115
148
|
inputTokens: 0,
|
|
116
149
|
outputTokens: 0,
|
|
117
150
|
cacheReadTokens: 0,
|
|
118
151
|
cacheWriteTokens: 0,
|
|
152
|
+
cacheSupported: false,
|
|
119
153
|
turns: 0,
|
|
154
|
+
subscription: false,
|
|
120
155
|
};
|
|
121
156
|
|
|
122
157
|
usage.inputTokens += safeInput;
|
|
123
158
|
usage.outputTokens += safeOutput;
|
|
124
159
|
usage.cacheReadTokens += safeCacheRead;
|
|
125
160
|
usage.cacheWriteTokens += safeCacheWrite;
|
|
161
|
+
if (cacheSupported) usage.cacheSupported = true;
|
|
162
|
+
if (subscription) usage.subscription = true;
|
|
126
163
|
usage.turns += 1;
|
|
127
164
|
state.models.set(key, usage);
|
|
128
165
|
},
|
|
@@ -137,14 +174,21 @@ export function createUsageTracker(): UsageTracker {
|
|
|
137
174
|
const safeTotal = toFiniteNumber(tokens.total);
|
|
138
175
|
const safeCacheRead = toFiniteNumber(tokens.cacheRead);
|
|
139
176
|
const safeCacheWrite = toFiniteNumber(tokens.cacheWrite);
|
|
140
|
-
|
|
177
|
+
// Subagents carry no provider field, so detect subscription routing from
|
|
178
|
+
// the registered model id prefix. Zero the dollars when subscription.
|
|
179
|
+
const subscription = isSubscriptionRouted(meta?.modelId);
|
|
180
|
+
const safeCost = subscription ? 0 : toFiniteNumber(tokens.cost ?? cost);
|
|
141
181
|
|
|
142
182
|
const effectiveInput = safeInput === 0 && safeOutput === 0 ? safeTotal : safeInput;
|
|
143
183
|
|
|
144
184
|
state.subagentInputTokens += effectiveInput;
|
|
145
185
|
state.subagentOutputTokens += safeOutput;
|
|
186
|
+
state.subagentCacheReadTokens += safeCacheRead;
|
|
187
|
+
state.subagentCacheWriteTokens += safeCacheWrite;
|
|
146
188
|
state.subagentCost += safeCost;
|
|
147
189
|
|
|
190
|
+
const cacheSupported = typeof tokens.cacheRead === "number" || typeof tokens.cacheWrite === "number";
|
|
191
|
+
|
|
148
192
|
state.subagents.push({
|
|
149
193
|
description: meta?.description ?? "unknown",
|
|
150
194
|
agentType: meta?.agentType ?? "unknown",
|
|
@@ -153,9 +197,11 @@ export function createUsageTracker(): UsageTracker {
|
|
|
153
197
|
outputTokens: safeOutput,
|
|
154
198
|
cacheReadTokens: safeCacheRead,
|
|
155
199
|
cacheWriteTokens: safeCacheWrite,
|
|
200
|
+
cacheSupported,
|
|
156
201
|
cost: safeCost,
|
|
157
202
|
durationMs: toFiniteNumber(meta?.durationMs),
|
|
158
203
|
toolUses: toFiniteNumber(meta?.toolUses),
|
|
204
|
+
subscription,
|
|
159
205
|
});
|
|
160
206
|
},
|
|
161
207
|
|
|
@@ -172,22 +218,36 @@ export function createUsageTracker(): UsageTracker {
|
|
|
172
218
|
state.totalTurns = toFiniteNumber(totals.turns);
|
|
173
219
|
}
|
|
174
220
|
if (Array.isArray(summary.subagents)) {
|
|
221
|
+
state.subagents = [];
|
|
222
|
+
state.subagentInputTokens = 0;
|
|
223
|
+
state.subagentOutputTokens = 0;
|
|
224
|
+
state.subagentCacheReadTokens = 0;
|
|
225
|
+
state.subagentCacheWriteTokens = 0;
|
|
226
|
+
state.subagentCost = 0;
|
|
175
227
|
for (const sa of summary.subagents as Record<string, unknown>[]) {
|
|
228
|
+
const modelId = typeof sa.modelId === "string" ? sa.modelId : "unknown";
|
|
229
|
+
// Pre-change summaries lack the flag; recover it from the sub/ prefix
|
|
230
|
+
// so legacy subscription rows are not restored as paid.
|
|
231
|
+
const subscription = sa.subscription === true || isSubscriptionRouted(modelId);
|
|
176
232
|
const entry: SubagentUsage = {
|
|
177
233
|
description: typeof sa.description === "string" ? sa.description : "unknown",
|
|
178
234
|
agentType: typeof sa.agentType === "string" ? sa.agentType : "unknown",
|
|
179
|
-
modelId
|
|
235
|
+
modelId,
|
|
180
236
|
inputTokens: toFiniteNumber(sa.inputTokens),
|
|
181
237
|
outputTokens: toFiniteNumber(sa.outputTokens),
|
|
182
238
|
cacheReadTokens: toFiniteNumber(sa.cacheReadTokens),
|
|
183
239
|
cacheWriteTokens: toFiniteNumber(sa.cacheWriteTokens),
|
|
184
|
-
|
|
240
|
+
cacheSupported: sa.cacheSupported === true,
|
|
241
|
+
cost: subscription ? 0 : toFiniteNumber(sa.cost),
|
|
185
242
|
durationMs: toFiniteNumber(sa.durationMs),
|
|
186
243
|
toolUses: toFiniteNumber(sa.toolUses),
|
|
244
|
+
subscription,
|
|
187
245
|
};
|
|
188
246
|
state.subagents.push(entry);
|
|
189
247
|
state.subagentInputTokens += entry.inputTokens;
|
|
190
248
|
state.subagentOutputTokens += entry.outputTokens;
|
|
249
|
+
state.subagentCacheReadTokens += entry.cacheReadTokens;
|
|
250
|
+
state.subagentCacheWriteTokens += entry.cacheWriteTokens;
|
|
191
251
|
state.subagentCost += entry.cost;
|
|
192
252
|
}
|
|
193
253
|
}
|
|
@@ -198,7 +258,9 @@ export function createUsageTracker(): UsageTracker {
|
|
|
198
258
|
outputTokens: toFiniteNumber(usage.outputTokens),
|
|
199
259
|
cacheReadTokens: toFiniteNumber(usage.cacheReadTokens),
|
|
200
260
|
cacheWriteTokens: toFiniteNumber(usage.cacheWriteTokens),
|
|
261
|
+
cacheSupported: (usage as any).cacheSupported === true,
|
|
201
262
|
turns: toFiniteNumber(usage.turns),
|
|
263
|
+
subscription: (usage as any).subscription === true || isSubscriptionRouted(modelId),
|
|
202
264
|
});
|
|
203
265
|
}
|
|
204
266
|
}
|
|
@@ -216,11 +278,22 @@ export function createUsageTracker(): UsageTracker {
|
|
|
216
278
|
},
|
|
217
279
|
|
|
218
280
|
getTotalCacheReadTokens(): number {
|
|
219
|
-
return state.totalCacheReadTokens;
|
|
281
|
+
return state.totalCacheReadTokens + state.subagentCacheReadTokens;
|
|
220
282
|
},
|
|
221
283
|
|
|
222
284
|
getTotalCacheWriteTokens(): number {
|
|
223
|
-
return state.totalCacheWriteTokens;
|
|
285
|
+
return state.totalCacheWriteTokens + state.subagentCacheWriteTokens;
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
getTotalProcessedInputTokens(): number {
|
|
289
|
+
return (
|
|
290
|
+
state.totalInputTokens +
|
|
291
|
+
state.subagentInputTokens +
|
|
292
|
+
state.totalCacheReadTokens +
|
|
293
|
+
state.subagentCacheReadTokens +
|
|
294
|
+
state.totalCacheWriteTokens +
|
|
295
|
+
state.subagentCacheWriteTokens
|
|
296
|
+
);
|
|
224
297
|
},
|
|
225
298
|
|
|
226
299
|
getTotalCost(): number {
|
|
@@ -248,13 +321,22 @@ export function createUsageTracker(): UsageTracker {
|
|
|
248
321
|
},
|
|
249
322
|
|
|
250
323
|
getCacheHitRate(): number {
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
324
|
+
const totalCacheRead = state.totalCacheReadTokens + state.subagentCacheReadTokens;
|
|
325
|
+
// Session-wide, token-weighted average: cache reads over all processed
|
|
326
|
+
// input (uncached input + cache read + cache write). Including cache
|
|
327
|
+
// writes keeps first-touch (uncached) content from inflating the rate.
|
|
328
|
+
const denominator = this.getTotalProcessedInputTokens();
|
|
254
329
|
if (denominator <= 0) return 0;
|
|
255
330
|
return totalCacheRead / denominator;
|
|
256
331
|
},
|
|
257
332
|
|
|
333
|
+
isCacheSupported(): boolean {
|
|
334
|
+
for (const usage of state.models.values()) {
|
|
335
|
+
if (usage.cacheSupported) return true;
|
|
336
|
+
}
|
|
337
|
+
return state.subagents.some((sa) => sa.cacheSupported);
|
|
338
|
+
},
|
|
339
|
+
|
|
258
340
|
getPerModelUsage(): Record<string, ModelUsage> {
|
|
259
341
|
const out: Record<string, ModelUsage> = {};
|
|
260
342
|
for (const [model, usage] of state.models.entries()) {
|
|
@@ -303,6 +385,8 @@ export function createUsageTracker(): UsageTracker {
|
|
|
303
385
|
state.totalTurns = 0;
|
|
304
386
|
state.subagentInputTokens = 0;
|
|
305
387
|
state.subagentOutputTokens = 0;
|
|
388
|
+
state.subagentCacheReadTokens = 0;
|
|
389
|
+
state.subagentCacheWriteTokens = 0;
|
|
306
390
|
state.subagentCost = 0;
|
|
307
391
|
state.models.clear();
|
|
308
392
|
state.subagents = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { validatePlan, validateUserRequest } from "./validate-artifacts.js";
|
|
2
|
+
import { validateArtifact, validatePlan, validateResearch, validateUserRequest } from "./validate-artifacts.js";
|
|
3
3
|
|
|
4
4
|
describe("validateUserRequest", () => {
|
|
5
5
|
it("rejects placeholder distillation and constraints content", () => {
|
|
@@ -86,3 +86,85 @@ Ship minimal fix.
|
|
|
86
86
|
expect(result.errors).toContain("Section ## Scope is empty. Expected 2-4 lines summarizing scope and constraints.");
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
describe("validateResearch", () => {
|
|
91
|
+
it("accepts valid research content", () => {
|
|
92
|
+
const content = `## Affected Code
|
|
93
|
+
src/main.ts:run — entry point
|
|
94
|
+
|
|
95
|
+
## Architecture Context
|
|
96
|
+
- Main flow calls run and dispatches handlers
|
|
97
|
+
|
|
98
|
+
## Constraints & Edge Cases
|
|
99
|
+
- MUST: Keep behavior backward compatible
|
|
100
|
+
- RISK: Regression in startup path
|
|
101
|
+
|
|
102
|
+
## Open Questions
|
|
103
|
+
Need confirmation about deprecated flag
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const result = validateResearch(content);
|
|
107
|
+
|
|
108
|
+
expect(result.ok).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("fails when required sections are missing", () => {
|
|
112
|
+
const content = `## Affected Code
|
|
113
|
+
src/main.ts:run — entry point
|
|
114
|
+
|
|
115
|
+
## Constraints & Edge Cases
|
|
116
|
+
- MUST: Keep behavior backward compatible
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const result = validateResearch(content);
|
|
120
|
+
|
|
121
|
+
expect(result.ok).toBe(false);
|
|
122
|
+
if (result.ok) return;
|
|
123
|
+
expect(result.errors.some((error) => error.includes("Missing required section: ## Architecture Context"))).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("fails when affected code section is empty", () => {
|
|
127
|
+
const content = `## Affected Code
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
## Architecture Context
|
|
131
|
+
- Main flow calls run and dispatches handlers
|
|
132
|
+
|
|
133
|
+
## Constraints & Edge Cases
|
|
134
|
+
- MUST: Keep behavior backward compatible
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const result = validateResearch(content);
|
|
138
|
+
|
|
139
|
+
expect(result.ok).toBe(false);
|
|
140
|
+
if (result.ok) return;
|
|
141
|
+
expect(result.errors).toContain("Section ## Affected Code is empty. Expected non-empty content.");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("validateArtifact", () => {
|
|
146
|
+
it("accepts valid artifact with top-level title", () => {
|
|
147
|
+
const content = `# Risk Analysis
|
|
148
|
+
|
|
149
|
+
- First risk
|
|
150
|
+
- Second risk
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const result = validateArtifact(content);
|
|
154
|
+
|
|
155
|
+
expect(result.ok).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("fails when top-level title heading is missing", () => {
|
|
159
|
+
const content = `## Risk Analysis
|
|
160
|
+
|
|
161
|
+
- First risk
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const result = validateArtifact(content);
|
|
165
|
+
|
|
166
|
+
expect(result.ok).toBe(false);
|
|
167
|
+
if (result.ok) return;
|
|
168
|
+
expect(result.errors.some((error) => error.includes("Expected a top-level heading"))).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilya-lesikov/pi-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"@pierre/diffs": "^1.2.0",
|
|
53
53
|
"effect": "^3.0.0",
|
|
54
54
|
"minimatch": "^10.0.0",
|
|
55
|
+
"pino": "^10.3.1",
|
|
55
56
|
"proper-lockfile": "^4.1.2",
|
|
56
57
|
"turndown": "^7.2.0",
|
|
57
58
|
"typebox": "^1.0.0"
|