ultimate-pi 0.13.1 → 0.15.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-debate-plan/SKILL.md +42 -22
- package/.agents/skills/harness-orchestration/SKILL.md +3 -3
- package/.agents/skills/harness-plan/SKILL.md +10 -8
- package/.pi/agents/harness/planning/decompose.md +4 -2
- package/.pi/agents/harness/planning/execution-plan-author.md +25 -14
- package/.pi/agents/harness/planning/hypothesis-validator.md +21 -5
- package/.pi/agents/harness/planning/implementation-researcher.md +42 -0
- package/.pi/agents/harness/planning/plan-adversary.md +20 -4
- package/.pi/agents/harness/planning/plan-evaluator.md +28 -5
- package/.pi/agents/harness/planning/review-integrator.md +25 -9
- package/.pi/agents/harness/planning/scout-graphify.md +1 -1
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +19 -4
- package/.pi/agents/harness/planning/stack-researcher.md +19 -10
- package/.pi/extensions/debate-orchestrator.ts +39 -435
- package/.pi/extensions/harness-debate-tools.ts +741 -0
- package/.pi/extensions/harness-live-widget.ts +39 -159
- package/.pi/extensions/harness-plan-approval.ts +88 -22
- package/.pi/extensions/harness-run-context.ts +18 -0
- package/.pi/extensions/lib/debate-bus-core.ts +488 -0
- package/.pi/extensions/lib/debate-bus-state.ts +64 -0
- package/.pi/extensions/lib/harness-spawn-budget.ts +5 -25
- package/.pi/extensions/lib/plan-approval/dialog.ts +33 -272
- package/.pi/extensions/lib/plan-approval/format-plan.ts +12 -85
- package/.pi/extensions/lib/plan-approval/plan-review.ts +62 -6
- package/.pi/extensions/lib/plan-approval/render.ts +6 -0
- package/.pi/extensions/lib/plan-approval/types.ts +1 -0
- package/.pi/extensions/lib/plan-approval/validate.ts +1 -1
- package/.pi/extensions/lib/plan-debate-eligibility.ts +214 -0
- package/.pi/extensions/lib/plan-debate-envelope.ts +2 -0
- package/.pi/extensions/lib/plan-debate-focus.ts +151 -0
- package/.pi/extensions/lib/plan-debate-gate.ts +198 -0
- package/.pi/extensions/lib/plan-debate-id.ts +39 -0
- package/.pi/extensions/lib/plan-debate-lane.ts +220 -0
- package/.pi/extensions/lib/plan-debate-lanes.ts +44 -0
- package/.pi/extensions/lib/plan-debate-round-status.ts +137 -0
- package/.pi/extensions/lib/plan-debate-write-guard.ts +20 -0
- package/.pi/extensions/lib/plan-messenger.ts +352 -0
- package/.pi/extensions/lib/plan-review-integrator-rules.ts +119 -0
- package/.pi/extensions/lib/plan-scope-guard.ts +89 -0
- package/.pi/extensions/policy-gate.ts +1 -1
- package/.pi/harness/README.md +1 -1
- package/.pi/harness/agents.manifest.json +16 -12
- package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +1 -3
- package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +13 -5
- package/.pi/harness/docs/adrs/0036-implementation-research-and-selective-debate.md +51 -0
- package/.pi/harness/docs/adrs/README.md +2 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/implementation-research.yaml +28 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/review-round-r1.yaml +24 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/review-round-r2.yaml +25 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/plan-packet.yaml +196 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/plan-review.md +14 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/research-brief.yaml +62 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/implementation-research.yaml +28 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r2.yaml +24 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r3.yaml +24 -0
- package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/research-brief.yaml +29 -0
- package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +97 -16
- package/.pi/harness/specs/plan-implementation-research-brief.schema.json +128 -0
- package/.pi/harness/specs/plan-review-round-draft.schema.json +1 -1
- package/.pi/harness/specs/round-result.schema.json +15 -2
- package/.pi/lib/harness-ui-state.ts +92 -0
- package/.pi/prompts/harness-plan.md +90 -30
- package/.pi/prompts/planning-rubrics.md +31 -0
- package/CHANGELOG.md +23 -0
- package/package.json +3 -3
- package/.pi/extensions/lib/plan-approval/fallback.ts +0 -50
|
@@ -1,291 +1,52 @@
|
|
|
1
1
|
import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { runAskDialog } from "../ask-user/dialog.js";
|
|
3
|
+
import { runAskFallback } from "../ask-user/fallback.js";
|
|
4
|
+
import type { ValidatedAskParams } from "../ask-user/types.js";
|
|
5
|
+
import { formatPlanPacketMarkdown } from "./plan-review.js";
|
|
4
6
|
import type {
|
|
5
7
|
PlanApprovalDialogResult,
|
|
6
8
|
ValidatedApprovePlanParams,
|
|
7
9
|
} from "./types.js";
|
|
8
10
|
|
|
9
|
-
type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Lines reserved below overlay: harness-live widget + editor + footer. */
|
|
16
|
-
export const PLAN_APPROVAL_BOTTOM_RESERVE_LINES = 11;
|
|
17
|
-
/** Estimate agents widget height when stacking above harness live. */
|
|
18
|
-
export const PLAN_APPROVAL_AGENTS_TOP_RESERVE_LINES = 12;
|
|
19
|
-
export const PLAN_APPROVAL_MIN_VIEWPORT = 6;
|
|
20
|
-
|
|
21
|
-
export function computePlanViewport(
|
|
22
|
-
availableHeight: number,
|
|
23
|
-
chromeLines: number,
|
|
24
|
-
): number {
|
|
25
|
-
return Math.max(PLAN_APPROVAL_MIN_VIEWPORT, availableHeight - chromeLines);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function computePlanOverlayMaxHeight(termHeight: number): number {
|
|
29
|
-
return Math.max(
|
|
30
|
-
PLAN_APPROVAL_MIN_VIEWPORT + 8,
|
|
31
|
-
termHeight -
|
|
32
|
-
PLAN_APPROVAL_BOTTOM_RESERVE_LINES -
|
|
33
|
-
PLAN_APPROVAL_AGENTS_TOP_RESERVE_LINES,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
11
|
+
export type RunPlanApprovalDialogOptions = {
|
|
12
|
+
onMounted?: () => void;
|
|
13
|
+
hasUI?: boolean;
|
|
14
|
+
};
|
|
36
15
|
|
|
37
|
-
|
|
16
|
+
/** Full plan body shown in the transcript before the approval prompt. */
|
|
17
|
+
export function buildPlanApprovalMarkdown(
|
|
38
18
|
validated: ValidatedApprovePlanParams,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
chrome += validated.human_summary.split("\n").length;
|
|
46
|
-
}
|
|
47
|
-
chrome += 1; // blank before plan
|
|
48
|
-
chrome += 1; // plan label
|
|
49
|
-
chrome += 1; // blank before options
|
|
50
|
-
chrome += 1; // options label
|
|
51
|
-
for (const opt of displayOptions) {
|
|
52
|
-
chrome += 1;
|
|
53
|
-
if (opt.description) chrome += 1;
|
|
54
|
-
}
|
|
55
|
-
chrome += 1; // blank before hints
|
|
56
|
-
chrome += 1; // hints
|
|
57
|
-
return chrome;
|
|
19
|
+
): string {
|
|
20
|
+
return formatPlanPacketMarkdown(validated.plan_packet, {
|
|
21
|
+
human_summary: validated.human_summary,
|
|
22
|
+
status: "draft",
|
|
23
|
+
research_brief: validated.research_brief,
|
|
24
|
+
}).trim();
|
|
58
25
|
}
|
|
59
26
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
27
|
+
function toAskParams(
|
|
28
|
+
validated: ValidatedApprovePlanParams,
|
|
29
|
+
): ValidatedAskParams {
|
|
30
|
+
return {
|
|
31
|
+
question: "How would you like to proceed with this harness plan?",
|
|
32
|
+
context: buildPlanApprovalMarkdown(validated),
|
|
33
|
+
options: validated.options,
|
|
34
|
+
allowMultiple: false,
|
|
35
|
+
allowFreeform: false,
|
|
36
|
+
// Inline prompt below the plan — no full-screen overlay.
|
|
37
|
+
displayMode: "inline",
|
|
38
|
+
};
|
|
71
39
|
}
|
|
72
40
|
|
|
73
|
-
export type RunPlanApprovalDialogOptions = {
|
|
74
|
-
onMounted?: () => void;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
41
|
export async function runPlanApprovalDialog(
|
|
78
42
|
ui: ExtensionUIContext,
|
|
79
43
|
validated: ValidatedApprovePlanParams,
|
|
80
44
|
options?: RunPlanApprovalDialogOptions,
|
|
81
45
|
): Promise<PlanApprovalDialogResult> {
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const result = await withTimeout(
|
|
88
|
-
ui.custom<CustomAnswer | null>(
|
|
89
|
-
(tui, theme, _kb, done) => {
|
|
90
|
-
const tuiHeight = (tui as unknown as { height?: number }).height;
|
|
91
|
-
overlayTermHeight =
|
|
92
|
-
typeof tuiHeight === "number" && tuiHeight > 10 ? tuiHeight : 24;
|
|
93
|
-
options?.onMounted?.();
|
|
94
|
-
|
|
95
|
-
let scrollOffset = 0;
|
|
96
|
-
let optionIndex = 0;
|
|
97
|
-
let focus: FocusRegion = "plan";
|
|
98
|
-
let cachedLines: string[] | undefined;
|
|
99
|
-
|
|
100
|
-
function refresh() {
|
|
101
|
-
cachedLines = undefined;
|
|
102
|
-
tui.requestRender();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function submitSelection() {
|
|
106
|
-
const opt = displayOptions[optionIndex];
|
|
107
|
-
done({
|
|
108
|
-
response: { kind: "selection", selections: [opt.title] },
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function handleInput(data: string) {
|
|
113
|
-
if (focus === "plan") {
|
|
114
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
115
|
-
scrollOffset = Math.max(0, scrollOffset - 1);
|
|
116
|
-
refresh();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
120
|
-
scrollOffset += 1;
|
|
121
|
-
refresh();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (matchesKey(data, Key.pageUp)) {
|
|
125
|
-
scrollOffset = Math.max(0, scrollOffset - 8);
|
|
126
|
-
refresh();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (matchesKey(data, Key.pageDown)) {
|
|
130
|
-
scrollOffset += 8;
|
|
131
|
-
refresh();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (matchesKey(data, Key.tab)) {
|
|
135
|
-
focus = "options";
|
|
136
|
-
refresh();
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (focus === "options") {
|
|
142
|
-
if (matchesKey(data, Key.up)) {
|
|
143
|
-
optionIndex = Math.max(0, optionIndex - 1);
|
|
144
|
-
refresh();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (matchesKey(data, Key.down)) {
|
|
148
|
-
optionIndex = Math.min(
|
|
149
|
-
displayOptions.length - 1,
|
|
150
|
-
optionIndex + 1,
|
|
151
|
-
);
|
|
152
|
-
refresh();
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (matchesKey(data, Key.tab)) {
|
|
156
|
-
focus = "plan";
|
|
157
|
-
refresh();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (matchesKey(data, Key.enter)) {
|
|
161
|
-
submitSelection();
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (matchesKey(data, Key.escape)) {
|
|
167
|
-
done(null);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function render(width: number): string[] {
|
|
172
|
-
if (cachedLines) return cachedLines;
|
|
173
|
-
|
|
174
|
-
const lines: string[] = [];
|
|
175
|
-
const add = (s: string) => lines.push(truncateToWidth(s, width));
|
|
176
|
-
const dims = (tui as { height?: number }).height;
|
|
177
|
-
const termHeight = typeof dims === "number" && dims > 10 ? dims : 24;
|
|
178
|
-
const chromeLines = countPlanChromeLines(
|
|
179
|
-
validated,
|
|
180
|
-
displayOptions,
|
|
181
|
-
useOverlay,
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
let availableHeight: number;
|
|
185
|
-
if (useOverlay) {
|
|
186
|
-
const overlayMax = computePlanOverlayMaxHeight(termHeight);
|
|
187
|
-
availableHeight = overlayMax;
|
|
188
|
-
} else {
|
|
189
|
-
availableHeight = termHeight - PLAN_APPROVAL_BOTTOM_RESERVE_LINES;
|
|
190
|
-
}
|
|
191
|
-
const planViewport = computePlanViewport(
|
|
192
|
-
availableHeight,
|
|
193
|
-
chromeLines,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
if (useOverlay) {
|
|
197
|
-
add(theme.fg("accent", "─".repeat(width)));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
add(theme.fg("accent", " Plan approval"));
|
|
201
|
-
if (validated.human_summary) {
|
|
202
|
-
for (const line of validated.human_summary.split("\n")) {
|
|
203
|
-
add(theme.fg("muted", ` ${line}`));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
lines.push("");
|
|
207
|
-
|
|
208
|
-
const maxScroll = Math.max(0, planLines.length - planViewport);
|
|
209
|
-
scrollOffset = Math.min(scrollOffset, maxScroll);
|
|
210
|
-
const visible = planLines.slice(
|
|
211
|
-
scrollOffset,
|
|
212
|
-
scrollOffset + planViewport,
|
|
213
|
-
);
|
|
214
|
-
const planLabel =
|
|
215
|
-
focus === "plan"
|
|
216
|
-
? theme.fg("accent", " [plan — ↑↓/Pg scroll, Tab → options]")
|
|
217
|
-
: theme.fg("dim", " [plan]");
|
|
218
|
-
add(planLabel);
|
|
219
|
-
for (const line of visible) {
|
|
220
|
-
add(theme.fg("text", ` ${line}`));
|
|
221
|
-
}
|
|
222
|
-
if (planLines.length > planViewport) {
|
|
223
|
-
add(
|
|
224
|
-
theme.fg(
|
|
225
|
-
"dim",
|
|
226
|
-
` … ${scrollOffset + 1}-${scrollOffset + visible.length} of ${planLines.length}`,
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
lines.push("");
|
|
231
|
-
|
|
232
|
-
const optLabel =
|
|
233
|
-
focus === "options"
|
|
234
|
-
? theme.fg("accent", " Options (↑↓, Enter, Tab → plan):")
|
|
235
|
-
: theme.fg("dim", " Options (Tab to focus):");
|
|
236
|
-
add(optLabel);
|
|
237
|
-
for (let i = 0; i < displayOptions.length; i++) {
|
|
238
|
-
const opt = displayOptions[i];
|
|
239
|
-
const focused = focus === "options" && i === optionIndex;
|
|
240
|
-
const prefix = focused ? theme.fg("accent", "> ") : " ";
|
|
241
|
-
const num = `${i + 1}. `;
|
|
242
|
-
if (focused) {
|
|
243
|
-
add(prefix + theme.fg("accent", `${num}${opt.title}`));
|
|
244
|
-
} else {
|
|
245
|
-
add(`${prefix}${theme.fg("text", `${num}${opt.title}`)}`);
|
|
246
|
-
}
|
|
247
|
-
if (opt.description) {
|
|
248
|
-
add(` ${theme.fg("muted", opt.description)}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
lines.push("");
|
|
253
|
-
add(theme.fg("dim", " Tab: plan ↔ options • Esc: cancel"));
|
|
254
|
-
|
|
255
|
-
if (useOverlay) {
|
|
256
|
-
add(theme.fg("accent", "─".repeat(width)));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
cachedLines = lines;
|
|
260
|
-
return lines;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
render,
|
|
265
|
-
invalidate: () => {
|
|
266
|
-
cachedLines = undefined;
|
|
267
|
-
},
|
|
268
|
-
handleInput,
|
|
269
|
-
};
|
|
270
|
-
},
|
|
271
|
-
useOverlay
|
|
272
|
-
? {
|
|
273
|
-
overlay: true,
|
|
274
|
-
overlayOptions: () => ({
|
|
275
|
-
anchor: "bottom-center",
|
|
276
|
-
width: "100%",
|
|
277
|
-
margin: { bottom: PLAN_APPROVAL_BOTTOM_RESERVE_LINES },
|
|
278
|
-
maxHeight: computePlanOverlayMaxHeight(overlayTermHeight),
|
|
279
|
-
}),
|
|
280
|
-
}
|
|
281
|
-
: undefined,
|
|
282
|
-
),
|
|
283
|
-
undefined,
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
if (!result) {
|
|
287
|
-
return { response: null, cancelled: true };
|
|
46
|
+
options?.onMounted?.();
|
|
47
|
+
const askParams = toAskParams(validated);
|
|
48
|
+
if (options?.hasUI === false) {
|
|
49
|
+
return runAskFallback(ui, askParams);
|
|
288
50
|
}
|
|
289
|
-
|
|
290
|
-
return { response: result.response, cancelled: false };
|
|
51
|
+
return runAskDialog(ui, askParams);
|
|
291
52
|
}
|
|
@@ -1,94 +1,21 @@
|
|
|
1
1
|
import type { PlanPacketLike } from "../../../lib/harness-run-context.js";
|
|
2
|
+
import { stringifyYaml } from "../../../lib/harness-yaml.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const lines: string[] = [];
|
|
7
|
-
let line = "";
|
|
8
|
-
for (const word of words) {
|
|
9
|
-
const next = line ? `${line} ${word}` : word;
|
|
10
|
-
if (next.length > width && line) {
|
|
11
|
-
lines.push(line);
|
|
12
|
-
line = word;
|
|
13
|
-
} else {
|
|
14
|
-
line = next;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
if (line) lines.push(line);
|
|
18
|
-
return lines.length > 0 ? lines : [""];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function riskBadge(risk: string | undefined): string {
|
|
22
|
-
const r = (risk ?? "med").toLowerCase();
|
|
23
|
-
return `[risk: ${r}]`;
|
|
4
|
+
/** Canonical YAML for plan_packet (same shape as plan-packet.yaml on disk). */
|
|
5
|
+
export function formatPlanPacketYaml(packet: PlanPacketLike): string {
|
|
6
|
+
return stringifyYaml(packet).trimEnd();
|
|
24
7
|
}
|
|
25
8
|
|
|
9
|
+
/** Line array for TUI renderers; preserves YAML structure with optional per-line width cap. */
|
|
26
10
|
export function formatPlanPacketLines(
|
|
27
11
|
packet: PlanPacketLike,
|
|
28
12
|
width: number,
|
|
29
13
|
): string[] {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
lines.push(`task_id: ${packet.task_id ?? "?"}`);
|
|
38
|
-
lines.push(
|
|
39
|
-
riskBadge(
|
|
40
|
-
typeof packet.risk_level === "string" ? packet.risk_level : undefined,
|
|
41
|
-
),
|
|
42
|
-
);
|
|
43
|
-
lines.push("");
|
|
44
|
-
lines.push("scope:");
|
|
45
|
-
add(String(packet.scope ?? ""));
|
|
46
|
-
lines.push("");
|
|
47
|
-
|
|
48
|
-
const assumptions = Array.isArray(packet.assumptions)
|
|
49
|
-
? (packet.assumptions as string[])
|
|
50
|
-
: [];
|
|
51
|
-
if (assumptions.length > 0) {
|
|
52
|
-
lines.push("assumptions:");
|
|
53
|
-
for (const a of assumptions) {
|
|
54
|
-
add(` • ${a}`);
|
|
55
|
-
}
|
|
56
|
-
lines.push("");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const checks = Array.isArray(packet.acceptance_checks)
|
|
60
|
-
? (packet.acceptance_checks as string[])
|
|
61
|
-
: [];
|
|
62
|
-
if (checks.length > 0) {
|
|
63
|
-
lines.push("acceptance_checks:");
|
|
64
|
-
for (let i = 0; i < checks.length; i++) {
|
|
65
|
-
add(` ${i + 1}. ${checks[i]}`);
|
|
66
|
-
}
|
|
67
|
-
lines.push("");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const rollback = packet.rollback_plan as
|
|
71
|
-
| {
|
|
72
|
-
rollback_artifacts?: {
|
|
73
|
-
revert_command?: string;
|
|
74
|
-
revert_branch?: string;
|
|
75
|
-
patch_bundle?: string;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
| undefined;
|
|
79
|
-
const artifacts = rollback?.rollback_artifacts;
|
|
80
|
-
if (artifacts) {
|
|
81
|
-
lines.push("rollback:");
|
|
82
|
-
if (artifacts.revert_command) {
|
|
83
|
-
add(` revert_command: ${artifacts.revert_command}`);
|
|
84
|
-
}
|
|
85
|
-
if (artifacts.revert_branch) {
|
|
86
|
-
add(` revert_branch: ${artifacts.revert_branch}`);
|
|
87
|
-
}
|
|
88
|
-
if (artifacts.patch_bundle) {
|
|
89
|
-
add(` patch_bundle: ${artifacts.patch_bundle}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return lines;
|
|
14
|
+
const w = Math.max(40, width);
|
|
15
|
+
return formatPlanPacketYaml(packet)
|
|
16
|
+
.split("\n")
|
|
17
|
+
.map((line) => {
|
|
18
|
+
if (line.length <= w) return line;
|
|
19
|
+
return `${line.slice(0, w - 1)}…`;
|
|
20
|
+
});
|
|
94
21
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type HarnessRunContext,
|
|
7
7
|
type PlanPacketLike,
|
|
8
8
|
} from "../../../lib/harness-run-context.js";
|
|
9
|
-
import {
|
|
9
|
+
import { formatPlanPacketYaml } from "./format-plan.js";
|
|
10
10
|
import type { PlanResearchBrief } from "./types.js";
|
|
11
11
|
|
|
12
12
|
export {
|
|
@@ -160,6 +160,62 @@ export function formatResearchBriefMarkdown(
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
const impl = asRecord(research.implementation);
|
|
164
|
+
if (impl) {
|
|
165
|
+
lines.push("## Phase 3.5 — Implementation research");
|
|
166
|
+
lines.push("");
|
|
167
|
+
const framing = str(impl.problem_framing);
|
|
168
|
+
if (framing) {
|
|
169
|
+
lines.push("**Problem framing:**");
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push(framing);
|
|
172
|
+
lines.push("");
|
|
173
|
+
}
|
|
174
|
+
const rec = asRecord(impl.recommended_approach);
|
|
175
|
+
if (rec) {
|
|
176
|
+
const summary = str(rec.summary);
|
|
177
|
+
const conf = str(rec.recommended_approach_confidence);
|
|
178
|
+
if (summary) {
|
|
179
|
+
lines.push(
|
|
180
|
+
`**Recommended approach**${conf ? ` (${conf} confidence)` : ""}:`,
|
|
181
|
+
);
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push(summary);
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
const rationale = str(rec.confidence_rationale);
|
|
187
|
+
if (rationale) {
|
|
188
|
+
lines.push(`*Rationale:* ${rationale}`);
|
|
189
|
+
lines.push("");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const patterns = Array.isArray(impl.solution_patterns)
|
|
193
|
+
? impl.solution_patterns
|
|
194
|
+
: [];
|
|
195
|
+
if (patterns.length) {
|
|
196
|
+
lines.push("**Solution patterns:**");
|
|
197
|
+
for (const p of patterns) {
|
|
198
|
+
const pat = asRecord(p);
|
|
199
|
+
const name = pat ? str(pat.name) : null;
|
|
200
|
+
const fit = pat ? str(pat.fit) : null;
|
|
201
|
+
if (name) lines.push(`- **${name}**${fit ? `: ${fit}` : ""}`);
|
|
202
|
+
}
|
|
203
|
+
lines.push("");
|
|
204
|
+
}
|
|
205
|
+
const openQs = strList(impl.open_questions);
|
|
206
|
+
if (openQs.length) {
|
|
207
|
+
lines.push("**Open questions:**");
|
|
208
|
+
for (const q of openQs) lines.push(`- ${q}`);
|
|
209
|
+
lines.push("");
|
|
210
|
+
}
|
|
211
|
+
const anti = strList(impl.anti_patterns);
|
|
212
|
+
if (anti.length) {
|
|
213
|
+
lines.push("**Anti-patterns:**");
|
|
214
|
+
for (const a of anti) lines.push(`- ${a}`);
|
|
215
|
+
lines.push("");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
163
219
|
if (evalBrief) {
|
|
164
220
|
lines.push("## Self-evaluation");
|
|
165
221
|
lines.push("");
|
|
@@ -217,7 +273,7 @@ export function formatPlanPacketMarkdown(
|
|
|
217
273
|
`- **risk_level:** ${typeof packet.risk_level === "string" ? packet.risk_level : "med"}`,
|
|
218
274
|
);
|
|
219
275
|
if (opts?.plan_packet_path) {
|
|
220
|
-
lines.push(`- **canonical
|
|
276
|
+
lines.push(`- **canonical YAML:** \`${opts.plan_packet_path}\``);
|
|
221
277
|
}
|
|
222
278
|
lines.push("");
|
|
223
279
|
if (opts?.human_summary?.trim()) {
|
|
@@ -233,15 +289,15 @@ export function formatPlanPacketMarkdown(
|
|
|
233
289
|
}
|
|
234
290
|
lines.push("## Plan packet");
|
|
235
291
|
lines.push("");
|
|
236
|
-
lines.push("```
|
|
237
|
-
for (const line of
|
|
292
|
+
lines.push("```yaml");
|
|
293
|
+
for (const line of formatPlanPacketYaml(packet).split("\n")) {
|
|
238
294
|
lines.push(line);
|
|
239
295
|
}
|
|
240
296
|
lines.push("```");
|
|
241
297
|
lines.push("");
|
|
242
298
|
if (status === "draft") {
|
|
243
299
|
lines.push(
|
|
244
|
-
"Review this
|
|
300
|
+
"Review this plan, then choose **Approve**, **Request changes**, or **Cancel** in the prompt below (same flow as `ask_user`).",
|
|
245
301
|
);
|
|
246
302
|
} else if (status === "approved") {
|
|
247
303
|
lines.push(
|
|
@@ -388,6 +444,6 @@ export function formatPlanReviewUserHint(reviewPath: string | null): string {
|
|
|
388
444
|
const abs = resolve(reviewPath);
|
|
389
445
|
return (
|
|
390
446
|
`Full plan for editor review: ${abs}\n` +
|
|
391
|
-
`Open this markdown file in
|
|
447
|
+
`Open this markdown file in your editor if you prefer; approval options appear in the harness prompt below.`
|
|
392
448
|
);
|
|
393
449
|
}
|
|
@@ -61,10 +61,16 @@ export function renderHarnessPlanDraft(
|
|
|
61
61
|
details: {
|
|
62
62
|
plan_packet?: PlanPacketLike;
|
|
63
63
|
human_summary?: string | null;
|
|
64
|
+
plan_markdown?: string | null;
|
|
64
65
|
},
|
|
65
66
|
width: number,
|
|
66
67
|
theme: Theme,
|
|
68
|
+
content?: string | null,
|
|
67
69
|
): string[] {
|
|
70
|
+
const markdown = content?.trim() || details.plan_markdown?.trim();
|
|
71
|
+
if (markdown) {
|
|
72
|
+
return markdown.split("\n").map((line) => truncateToWidth(line, width));
|
|
73
|
+
}
|
|
68
74
|
const lines: string[] = [];
|
|
69
75
|
lines.push(theme.fg("accent", "Harness plan (pending approval)"));
|
|
70
76
|
if (details.human_summary) {
|
|
@@ -13,6 +13,7 @@ export interface PlanResearchBrief {
|
|
|
13
13
|
hypothesis?: Record<string, unknown> | null;
|
|
14
14
|
eval?: Record<string, unknown> | null;
|
|
15
15
|
stack?: Record<string, unknown> | null;
|
|
16
|
+
implementation?: Record<string, unknown> | null;
|
|
16
17
|
debate?: {
|
|
17
18
|
rounds?: Record<string, unknown>[];
|
|
18
19
|
hypothesis_validations?: Record<string, unknown>[];
|
|
@@ -36,7 +36,7 @@ export function validateApprovePlanParams(
|
|
|
36
36
|
human_summary: params.human_summary?.trim() || undefined,
|
|
37
37
|
research_brief: params.research_brief ?? undefined,
|
|
38
38
|
options,
|
|
39
|
-
displayMode: params.displayMode ?? "
|
|
39
|
+
displayMode: params.displayMode ?? "inline",
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|