ultimate-pi 0.10.0 → 0.11.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/.agents/skills/harness-decisions/SKILL.md +3 -3
- package/.agents/skills/harness-orchestration/SKILL.md +19 -11
- package/.agents/skills/harness-plan/SKILL.md +15 -9
- package/.pi/agents/harness/planner.md +6 -47
- package/.pi/agents/harness/planning/decompose.md +84 -0
- package/.pi/agents/harness/planning/hypothesis-eval.md +59 -0
- package/.pi/agents/harness/planning/hypothesis.md +90 -0
- package/.pi/agents/harness/planning/plan-adversary.md +50 -0
- package/.pi/agents/harness/planning/planner.md +20 -0
- package/.pi/agents/harness/planning/scout-graphify.md +48 -0
- package/.pi/agents/harness/planning/scout-semantic.md +42 -0
- package/.pi/agents/harness/planning/scout-structure.md +44 -0
- package/.pi/extensions/harness-ask-user.ts +5 -0
- package/.pi/extensions/harness-live-widget.ts +48 -28
- package/.pi/extensions/harness-plan-approval.ts +192 -24
- package/.pi/extensions/harness-run-context.ts +24 -15
- package/.pi/extensions/harness-subagents.ts +8 -3
- package/.pi/extensions/harness-web-tools.ts +2 -0
- package/.pi/extensions/lib/extension-load-guard.ts +39 -0
- package/.pi/extensions/lib/harness-subagents/harness-subagent-policy.ts +33 -5
- package/.pi/extensions/lib/harness-subagents/parent-harness-ui-bridge.ts +2 -171
- package/.pi/extensions/lib/harness-subagents/parent-harness-ui-hooks.ts +18 -0
- package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +1 -5
- package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +0 -18
- package/.pi/extensions/lib/harness-subagents/vendored/index.ts +4 -36
- package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +2 -0
- package/.pi/extensions/lib/plan-approval/create-plan.ts +5 -0
- package/.pi/extensions/lib/plan-approval/dialog.ts +231 -147
- package/.pi/extensions/lib/plan-approval/plan-review.ts +393 -0
- package/.pi/extensions/lib/plan-approval/schema.ts +16 -1
- package/.pi/extensions/lib/plan-approval/types.ts +10 -0
- package/.pi/extensions/lib/plan-approval/validate.ts +2 -0
- package/.pi/extensions/policy-gate.ts +1 -1
- package/.pi/extensions/ultimate-pi-vcc.ts +5 -0
- package/.pi/harness/agents.manifest.json +114 -82
- package/.pi/harness/docs/adrs/0032-harness-command-orchestration.md +3 -3
- package/.pi/harness/docs/adrs/0033-parent-orchestrated-planning.md +34 -0
- package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +41 -0
- package/.pi/harness/docs/adrs/README.md +2 -0
- package/.pi/harness/specs/README.md +1 -1
- package/.pi/harness/specs/harness-spawn-context.schema.json +2 -1
- package/.pi/harness/specs/plan-adversary-brief.schema.json +45 -0
- package/.pi/harness/specs/plan-decomposition-brief.schema.json +108 -0
- package/.pi/harness/specs/plan-hypothesis-brief.schema.json +96 -0
- package/.pi/harness/specs/plan-hypothesis-eval.schema.json +61 -0
- package/.pi/lib/harness-run-context.ts +12 -0
- package/.pi/prompts/harness-auto.md +1 -1
- package/.pi/prompts/harness-plan.md +116 -20
- package/.pi/prompts/harness-setup.md +1 -1
- package/.pi/scripts/harness-resolve-up-pkg.mjs +13 -0
- package/CHANGELOG.md +18 -0
- package/biome.json +4 -1
- package/package.json +2 -2
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
canonicalPlanPath,
|
|
5
|
+
canonicalPlanReviewPath,
|
|
6
|
+
type HarnessRunContext,
|
|
7
|
+
type PlanPacketLike,
|
|
8
|
+
} from "../../../lib/harness-run-context.js";
|
|
9
|
+
import { formatPlanPacketLines } from "./format-plan.js";
|
|
10
|
+
import type { PlanResearchBrief } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
canonicalPlanReviewPath,
|
|
14
|
+
PLAN_REVIEW_BASENAME,
|
|
15
|
+
} from "../../../lib/harness-run-context.js";
|
|
16
|
+
|
|
17
|
+
export type PlanReviewStatus = "draft" | "approved" | "committed";
|
|
18
|
+
|
|
19
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
20
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
21
|
+
? (value as Record<string, unknown>)
|
|
22
|
+
: null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function str(value: unknown): string | null {
|
|
26
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function strList(value: unknown): string[] {
|
|
30
|
+
if (!Array.isArray(value)) return [];
|
|
31
|
+
return value
|
|
32
|
+
.map((item) => (typeof item === "string" ? item.trim() : null))
|
|
33
|
+
.filter((item): item is string => Boolean(item));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Render Darwin research sections for plan-review.md. */
|
|
37
|
+
export function formatResearchBriefMarkdown(
|
|
38
|
+
research: PlanResearchBrief | null | undefined,
|
|
39
|
+
): string {
|
|
40
|
+
if (!research) return "";
|
|
41
|
+
const lines: string[] = [];
|
|
42
|
+
const decomp = asRecord(research.decomposition);
|
|
43
|
+
const hyp = asRecord(research.hypothesis);
|
|
44
|
+
const evalBrief = asRecord(research.eval);
|
|
45
|
+
|
|
46
|
+
if (decomp) {
|
|
47
|
+
lines.push("## Phase 1 — Problem decomposition");
|
|
48
|
+
lines.push("");
|
|
49
|
+
const restate = str(decomp.problem_restatement);
|
|
50
|
+
if (restate) {
|
|
51
|
+
lines.push("**What is being asked?**");
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(restate);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
const types = strList(decomp.problem_types);
|
|
57
|
+
if (types.length) {
|
|
58
|
+
lines.push(`**Problem type(s):** ${types.join(", ")}`);
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
const scope = asRecord(decomp.scope);
|
|
62
|
+
if (scope) {
|
|
63
|
+
const focus = str(scope.narrowed_focus);
|
|
64
|
+
if (focus) {
|
|
65
|
+
lines.push("**Scope:**");
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push(focus);
|
|
68
|
+
lines.push("");
|
|
69
|
+
}
|
|
70
|
+
const excluded = strList(scope.excluded);
|
|
71
|
+
if (excluded.length) {
|
|
72
|
+
lines.push("**Excluded:**");
|
|
73
|
+
for (const item of excluded) lines.push(`- ${item}`);
|
|
74
|
+
lines.push("");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const [label, key] of [
|
|
78
|
+
["Hard constraints", "hard_constraints"],
|
|
79
|
+
["Soft constraints", "soft_constraints"],
|
|
80
|
+
["Success metrics", "success_metrics"],
|
|
81
|
+
] as const) {
|
|
82
|
+
const items = strList(decomp[key]);
|
|
83
|
+
if (items.length) {
|
|
84
|
+
lines.push(`**${label}:**`);
|
|
85
|
+
for (const item of items) lines.push(`- ${item}`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const prior = asRecord(decomp.prior_art);
|
|
90
|
+
if (prior) {
|
|
91
|
+
lines.push("**Prior art:**");
|
|
92
|
+
lines.push("");
|
|
93
|
+
const best = str(prior.best_approach);
|
|
94
|
+
const gap = str(prior.gap);
|
|
95
|
+
if (best) lines.push(`- Best approach: ${best}`);
|
|
96
|
+
if (gap) lines.push(`- Gap: ${gap}`);
|
|
97
|
+
for (const dead of strList(prior.dead_ends)) {
|
|
98
|
+
lines.push(`- Dead end: ${dead}`);
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
const core = str(decomp.core_tension);
|
|
103
|
+
if (core) {
|
|
104
|
+
lines.push("**Core tension:**");
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(core);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (hyp) {
|
|
112
|
+
lines.push("## Phase 2 — DARWIN hypothesis");
|
|
113
|
+
lines.push("");
|
|
114
|
+
const primary = asRecord(hyp.primary);
|
|
115
|
+
if (primary) {
|
|
116
|
+
for (const [label, key] of [
|
|
117
|
+
["Claim", "claim"],
|
|
118
|
+
["Mechanism", "mechanism"],
|
|
119
|
+
["Prediction", "prediction"],
|
|
120
|
+
["Experiment", "experiment"],
|
|
121
|
+
["Resolves tension", "tension_resolution"],
|
|
122
|
+
] as const) {
|
|
123
|
+
const text = str(primary[key]);
|
|
124
|
+
if (text) {
|
|
125
|
+
lines.push(`**${label}:** ${text}`);
|
|
126
|
+
lines.push("");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const fork = asRecord(hyp.dialectical_fork);
|
|
131
|
+
if (fork) {
|
|
132
|
+
const forkText = str(fork.fork);
|
|
133
|
+
if (forkText) {
|
|
134
|
+
lines.push(`**Dialectical fork:** ${forkText}`);
|
|
135
|
+
lines.push("");
|
|
136
|
+
}
|
|
137
|
+
const pathA = str(fork.path_a);
|
|
138
|
+
const pathB = str(fork.path_b);
|
|
139
|
+
if (pathA) lines.push(`- **Path A:** ${pathA}`);
|
|
140
|
+
if (pathB) lines.push(`- **Path B:** ${pathB}`);
|
|
141
|
+
lines.push("");
|
|
142
|
+
}
|
|
143
|
+
const alts = Array.isArray(hyp.alternatives) ? hyp.alternatives : [];
|
|
144
|
+
if (alts.length) {
|
|
145
|
+
lines.push("**Alternatives:**");
|
|
146
|
+
for (const alt of alts) {
|
|
147
|
+
const rec = asRecord(alt);
|
|
148
|
+
if (!rec) continue;
|
|
149
|
+
const claim = str(rec.claim);
|
|
150
|
+
const bet = str(rec.key_bet);
|
|
151
|
+
if (claim) lines.push(`- ${claim}${bet ? ` (bet: ${bet})` : ""}`);
|
|
152
|
+
}
|
|
153
|
+
lines.push("");
|
|
154
|
+
}
|
|
155
|
+
const steps = strList(hyp.recommended_next_steps);
|
|
156
|
+
if (steps.length) {
|
|
157
|
+
lines.push("**Recommended next steps:**");
|
|
158
|
+
for (const step of steps) lines.push(`1. ${step}`);
|
|
159
|
+
lines.push("");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (evalBrief) {
|
|
164
|
+
lines.push("## Self-evaluation");
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push("| Dimension | Score | Rationale |");
|
|
167
|
+
lines.push("|-----------|-------|-----------|");
|
|
168
|
+
const dims = asRecord(evalBrief.dimensions);
|
|
169
|
+
if (dims) {
|
|
170
|
+
for (const name of [
|
|
171
|
+
"novelty",
|
|
172
|
+
"coherence",
|
|
173
|
+
"testability",
|
|
174
|
+
"impact",
|
|
175
|
+
] as const) {
|
|
176
|
+
const dim = asRecord(dims[name]);
|
|
177
|
+
if (!dim) continue;
|
|
178
|
+
const score = typeof dim.score === "number" ? String(dim.score) : "?";
|
|
179
|
+
const rationale = str(dim.rationale) ?? "";
|
|
180
|
+
lines.push(`| ${name} | ${score}/100 | ${rationale} |`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const rel = asRecord(evalBrief.relevance);
|
|
184
|
+
if (rel) {
|
|
185
|
+
const passes = rel.passes === true ? "✓" : "✗";
|
|
186
|
+
const rationale = str(rel.rationale) ?? "";
|
|
187
|
+
lines.push(`| Relevance | ${passes} | ${rationale} |`);
|
|
188
|
+
}
|
|
189
|
+
lines.push("");
|
|
190
|
+
const summary = str(evalBrief.human_summary);
|
|
191
|
+
if (summary) {
|
|
192
|
+
lines.push(summary);
|
|
193
|
+
lines.push("");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function formatPlanPacketMarkdown(
|
|
201
|
+
packet: PlanPacketLike,
|
|
202
|
+
opts?: {
|
|
203
|
+
human_summary?: string | null;
|
|
204
|
+
status?: PlanReviewStatus;
|
|
205
|
+
plan_packet_path?: string | null;
|
|
206
|
+
research_brief?: PlanResearchBrief | null;
|
|
207
|
+
},
|
|
208
|
+
): string {
|
|
209
|
+
const lines: string[] = [];
|
|
210
|
+
const status = opts?.status ?? "draft";
|
|
211
|
+
lines.push("# Harness plan");
|
|
212
|
+
lines.push("");
|
|
213
|
+
lines.push(`- **Status:** ${status}`);
|
|
214
|
+
lines.push(`- **plan_id:** ${packet.plan_id ?? "?"}`);
|
|
215
|
+
lines.push(`- **task_id:** ${packet.task_id ?? "?"}`);
|
|
216
|
+
lines.push(
|
|
217
|
+
`- **risk_level:** ${typeof packet.risk_level === "string" ? packet.risk_level : "med"}`,
|
|
218
|
+
);
|
|
219
|
+
if (opts?.plan_packet_path) {
|
|
220
|
+
lines.push(`- **canonical JSON:** \`${opts.plan_packet_path}\``);
|
|
221
|
+
}
|
|
222
|
+
lines.push("");
|
|
223
|
+
if (opts?.human_summary?.trim()) {
|
|
224
|
+
lines.push("## Summary");
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push(opts.human_summary.trim());
|
|
227
|
+
lines.push("");
|
|
228
|
+
}
|
|
229
|
+
const researchMd = formatResearchBriefMarkdown(opts?.research_brief);
|
|
230
|
+
if (researchMd) {
|
|
231
|
+
lines.push(researchMd.trimEnd());
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
lines.push("## Plan packet");
|
|
235
|
+
lines.push("");
|
|
236
|
+
lines.push("```text");
|
|
237
|
+
for (const line of formatPlanPacketLines(packet, 100)) {
|
|
238
|
+
lines.push(line);
|
|
239
|
+
}
|
|
240
|
+
lines.push("```");
|
|
241
|
+
lines.push("");
|
|
242
|
+
if (status === "draft") {
|
|
243
|
+
lines.push(
|
|
244
|
+
"Review this file in your editor, then return to the harness TUI to **Approve**, **Request changes**, or **Cancel**.",
|
|
245
|
+
);
|
|
246
|
+
} else if (status === "approved") {
|
|
247
|
+
lines.push(
|
|
248
|
+
"Approved in the harness TUI. Waiting for `create_plan` to write `plan-packet.json`, or run `/harness-plan-commit` if that step failed.",
|
|
249
|
+
);
|
|
250
|
+
} else {
|
|
251
|
+
lines.push(
|
|
252
|
+
"Plan committed. Next: `/harness-run` to execute (do not pass `--plan` on the happy path).",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
lines.push("");
|
|
256
|
+
return `${lines.join("\n")}\n`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function writePlanReviewMarkdown(
|
|
260
|
+
projectRoot: string,
|
|
261
|
+
runCtx: HarnessRunContext | null,
|
|
262
|
+
packet: PlanPacketLike,
|
|
263
|
+
opts?: {
|
|
264
|
+
human_summary?: string | null;
|
|
265
|
+
status?: PlanReviewStatus;
|
|
266
|
+
research_brief?: PlanResearchBrief | null;
|
|
267
|
+
},
|
|
268
|
+
): Promise<string | null> {
|
|
269
|
+
const runId = runCtx?.run_id;
|
|
270
|
+
if (!runId) return null;
|
|
271
|
+
const reviewPath = canonicalPlanReviewPath(runId, projectRoot);
|
|
272
|
+
const planPacketPath =
|
|
273
|
+
runCtx.plan_packet_path ?? canonicalPlanPath(runId, projectRoot);
|
|
274
|
+
const body = formatPlanPacketMarkdown(packet, {
|
|
275
|
+
human_summary: opts?.human_summary,
|
|
276
|
+
status: opts?.status ?? "draft",
|
|
277
|
+
plan_packet_path: planPacketPath,
|
|
278
|
+
research_brief: opts?.research_brief,
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
await mkdir(dirname(reviewPath), { recursive: true });
|
|
282
|
+
await writeFile(reviewPath, body, "utf-8");
|
|
283
|
+
return reviewPath;
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
interface SessionEntryLike {
|
|
290
|
+
type?: string;
|
|
291
|
+
customType?: string;
|
|
292
|
+
data?: unknown;
|
|
293
|
+
message?: {
|
|
294
|
+
role?: string;
|
|
295
|
+
toolName?: string;
|
|
296
|
+
details?: unknown;
|
|
297
|
+
content?: { type?: string; text?: string }[];
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Latest plan_packet from drafts, approve_plan tool results, or assistant JSON blocks. */
|
|
302
|
+
export function extractLatestPlanPacketFromEntries(
|
|
303
|
+
entries: unknown[],
|
|
304
|
+
): { packet: PlanPacketLike; human_summary?: string | null } | null {
|
|
305
|
+
let found: { packet: PlanPacketLike; human_summary?: string | null } | null =
|
|
306
|
+
null;
|
|
307
|
+
|
|
308
|
+
const consider = (
|
|
309
|
+
packet: PlanPacketLike | undefined,
|
|
310
|
+
human_summary?: string | null,
|
|
311
|
+
) => {
|
|
312
|
+
if (!packet || typeof packet !== "object") return;
|
|
313
|
+
if (!packet.plan_id && !packet.scope) return;
|
|
314
|
+
found = { packet, human_summary: human_summary ?? null };
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
318
|
+
const entry = entries[i] as SessionEntryLike;
|
|
319
|
+
if (entry.type === "custom" && entry.customType === "harness-plan-draft") {
|
|
320
|
+
const data = entry.data as {
|
|
321
|
+
plan_packet?: PlanPacketLike;
|
|
322
|
+
human_summary?: string | null;
|
|
323
|
+
};
|
|
324
|
+
consider(data.plan_packet, data.human_summary);
|
|
325
|
+
if (found) return found;
|
|
326
|
+
}
|
|
327
|
+
if (entry.type === "message" && entry.message?.role === "toolResult") {
|
|
328
|
+
const toolName = entry.message.toolName;
|
|
329
|
+
const details = entry.message.details as
|
|
330
|
+
| {
|
|
331
|
+
plan_packet?: PlanPacketLike;
|
|
332
|
+
human_summary?: string;
|
|
333
|
+
}
|
|
334
|
+
| undefined;
|
|
335
|
+
if (toolName === "approve_plan" && details?.plan_packet) {
|
|
336
|
+
consider(details.plan_packet, details.human_summary);
|
|
337
|
+
if (found) return found;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
343
|
+
const entry = entries[i] as SessionEntryLike;
|
|
344
|
+
if (entry.type !== "message" || entry.message?.role !== "assistant") {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const blocks = entry.message.content ?? [];
|
|
348
|
+
for (const block of blocks) {
|
|
349
|
+
if (block.type !== "text" || !block.text) continue;
|
|
350
|
+
const match = block.text.match(/```json\s*([\s\S]*?)```/i);
|
|
351
|
+
if (!match) continue;
|
|
352
|
+
try {
|
|
353
|
+
const parsed = JSON.parse(match[1]) as {
|
|
354
|
+
plan_packet?: PlanPacketLike;
|
|
355
|
+
human_summary?: string;
|
|
356
|
+
};
|
|
357
|
+
if (parsed.plan_packet) {
|
|
358
|
+
consider(parsed.plan_packet, parsed.human_summary);
|
|
359
|
+
if (found) return found;
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
/* ignore malformed assistant JSON */
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return found;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function syncPlannerPlanReviewToDisk(
|
|
371
|
+
projectRoot: string,
|
|
372
|
+
runCtx: HarnessRunContext | null,
|
|
373
|
+
entries: unknown[],
|
|
374
|
+
_opts?: { agentStatus?: string },
|
|
375
|
+
): Promise<string | null> {
|
|
376
|
+
const draft = extractLatestPlanPacketFromEntries(entries);
|
|
377
|
+
if (!draft) return null;
|
|
378
|
+
return writePlanReviewMarkdown(projectRoot, runCtx, draft.packet, {
|
|
379
|
+
human_summary: draft.human_summary,
|
|
380
|
+
status: "draft",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function formatPlanReviewUserHint(reviewPath: string | null): string {
|
|
385
|
+
if (!reviewPath) {
|
|
386
|
+
return "No plan draft was captured yet. If the planner is still clarifying, answer in the subagent or re-run /harness-plan.";
|
|
387
|
+
}
|
|
388
|
+
const abs = resolve(reviewPath);
|
|
389
|
+
return (
|
|
390
|
+
`Full plan for editor review: ${abs}\n` +
|
|
391
|
+
`Open this markdown file in VS Code (or your editor), read the scope and acceptance checks, then return to the harness TUI to Approve / Request changes / Cancel.`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
@@ -13,6 +13,21 @@ export const ApprovePlanParamsSchema = Type.Object({
|
|
|
13
13
|
description: "Short summary shown above the plan body.",
|
|
14
14
|
}),
|
|
15
15
|
),
|
|
16
|
+
research_brief: Type.Optional(
|
|
17
|
+
Type.Object(
|
|
18
|
+
{
|
|
19
|
+
decomposition: Type.Optional(
|
|
20
|
+
Type.Union([Type.Object({}), Type.Null()]),
|
|
21
|
+
),
|
|
22
|
+
hypothesis: Type.Optional(Type.Union([Type.Object({}), Type.Null()])),
|
|
23
|
+
eval: Type.Optional(Type.Union([Type.Object({}), Type.Null()])),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
description:
|
|
27
|
+
"Optional Darwin research: decomposition, hypothesis, eval (plan-review.md only).",
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
),
|
|
16
31
|
options: Type.Optional(
|
|
17
32
|
Type.Array(
|
|
18
33
|
Type.Union([
|
|
@@ -30,7 +45,7 @@ export const ApprovePlanParamsSchema = Type.Object({
|
|
|
30
45
|
});
|
|
31
46
|
|
|
32
47
|
export const PROMPT_SNIPPET =
|
|
33
|
-
"approve_plan({ plan_packet: { ...PlanPacket fields... }, human_summary?: string })";
|
|
48
|
+
"approve_plan({ plan_packet: { ...PlanPacket fields... }, human_summary?: string, research_brief?: { decomposition, hypothesis, eval } })";
|
|
34
49
|
|
|
35
50
|
export const PROMPT_GUIDELINES = [
|
|
36
51
|
"Call approve_plan once with the complete plan_packet when ready for user approval.",
|
|
@@ -7,9 +7,17 @@ export const DEFAULT_PLAN_APPROVAL_OPTIONS = [
|
|
|
7
7
|
"Cancel",
|
|
8
8
|
] as const;
|
|
9
9
|
|
|
10
|
+
/** Optional Darwin research artifacts from /harness-plan (not persisted in plan-packet.json). */
|
|
11
|
+
export interface PlanResearchBrief {
|
|
12
|
+
decomposition?: Record<string, unknown> | null;
|
|
13
|
+
hypothesis?: Record<string, unknown> | null;
|
|
14
|
+
eval?: Record<string, unknown> | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export interface ApprovePlanParams {
|
|
11
18
|
plan_packet: PlanPacketLike;
|
|
12
19
|
human_summary?: string;
|
|
20
|
+
research_brief?: PlanResearchBrief | null;
|
|
13
21
|
options?: Array<string | { title: string; description?: string }>;
|
|
14
22
|
displayMode?: "overlay" | "inline";
|
|
15
23
|
}
|
|
@@ -17,6 +25,7 @@ export interface ApprovePlanParams {
|
|
|
17
25
|
export interface ValidatedApprovePlanParams {
|
|
18
26
|
plan_packet: PlanPacketLike;
|
|
19
27
|
human_summary?: string;
|
|
28
|
+
research_brief?: PlanResearchBrief | null;
|
|
20
29
|
options: { title: string; description?: string }[];
|
|
21
30
|
displayMode: "overlay" | "inline";
|
|
22
31
|
}
|
|
@@ -24,6 +33,7 @@ export interface ValidatedApprovePlanParams {
|
|
|
24
33
|
export interface ApprovePlanToolDetails {
|
|
25
34
|
plan_packet: PlanPacketLike;
|
|
26
35
|
human_summary?: string;
|
|
36
|
+
research_brief?: PlanResearchBrief | null;
|
|
27
37
|
options: string[];
|
|
28
38
|
response: AskResponse | null;
|
|
29
39
|
cancelled: boolean;
|
|
@@ -34,6 +34,7 @@ export function validateApprovePlanParams(
|
|
|
34
34
|
return {
|
|
35
35
|
plan_packet: packet as PlanPacketLike,
|
|
36
36
|
human_summary: params.human_summary?.trim() || undefined,
|
|
37
|
+
research_brief: params.research_brief ?? undefined,
|
|
37
38
|
options,
|
|
38
39
|
displayMode: params.displayMode ?? "overlay",
|
|
39
40
|
};
|
|
@@ -47,6 +48,7 @@ export function toApprovePlanToolDetails(
|
|
|
47
48
|
return {
|
|
48
49
|
plan_packet: validated.plan_packet,
|
|
49
50
|
human_summary: validated.human_summary,
|
|
51
|
+
research_brief: validated.research_brief ?? null,
|
|
50
52
|
options: validated.options.map((o) => o.title),
|
|
51
53
|
response,
|
|
52
54
|
cancelled,
|
|
@@ -243,7 +243,7 @@ export default function policyGate(pi: ExtensionAPI) {
|
|
|
243
243
|
|
|
244
244
|
const planPhaseHint =
|
|
245
245
|
state.phase === "plan"
|
|
246
|
-
? "\nPlan phase:
|
|
246
|
+
? "\nPlan phase: scouts → decompose → hypothesis via harness/planning/*; parent builds PlanPacket, ask_user on fork, parallel plan-adversary + hypothesis-eval, approve_plan (optional research_brief), then create_plan (never write plan-packet.json directly)."
|
|
247
247
|
: "";
|
|
248
248
|
|
|
249
249
|
return {
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import registerVcc from "../../vendor/pi-vcc/index.js";
|
|
14
|
+
import { claimExtensionLoad } from "./lib/extension-load-guard.js";
|
|
15
|
+
|
|
16
|
+
// @ts-expect-error pi extensions run as ESM
|
|
17
|
+
const MODULE_URL = import.meta.url;
|
|
14
18
|
|
|
15
19
|
export default function ultimatePiVcc(pi: ExtensionAPI): void {
|
|
20
|
+
if (!claimExtensionLoad("ultimate-pi-vcc", MODULE_URL)) return;
|
|
16
21
|
registerVcc(pi);
|
|
17
22
|
}
|