@hobin/developer 0.1.4 → 0.1.5
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/README.md +149 -38
- package/extensions/developer.ts +483 -68
- package/extensions/machine.ts +344 -0
- package/extensions/state.ts +128 -98
- package/extensions/tool-policy.ts +54 -25
- package/extensions/tui.ts +263 -124
- package/package.json +7 -2
- package/skills/abstraction-review/SKILL.md +6 -5
- package/skills/adversarial-eval/SKILL.md +7 -3
- package/skills/model/SKILL.md +13 -4
- package/skills/naming-judgment/SKILL.md +9 -3
- package/skills/schedule/SKILL.md +6 -3
- package/skills/signal/SKILL.md +6 -3
- package/skills/sketch/SKILL.md +29 -10
- package/skills/specify/SKILL.md +11 -2
- package/skills/verify/SKILL.md +10 -4
- package/skills/visualize/SKILL.md +5 -3
package/extensions/tui.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Container,
|
|
9
9
|
matchesKey,
|
|
10
10
|
type SelectItem,
|
|
11
|
+
type SizeValue,
|
|
11
12
|
Text,
|
|
12
13
|
truncateToWidth,
|
|
13
14
|
visibleWidth,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
protocolState,
|
|
19
20
|
type DeveloperMode,
|
|
20
21
|
type DeveloperState,
|
|
22
|
+
type JudgmentStatus,
|
|
21
23
|
type PendingQuestion,
|
|
22
24
|
type ProtocolState,
|
|
23
25
|
} from "./state.ts";
|
|
@@ -31,7 +33,12 @@ function modeName(mode: DeveloperMode): string {
|
|
|
31
33
|
|
|
32
34
|
function protocolColor(value: ProtocolState): ThemeColor {
|
|
33
35
|
if (value === "blocked") return "error";
|
|
34
|
-
if (
|
|
36
|
+
if (
|
|
37
|
+
value === "needs-evidence" ||
|
|
38
|
+
value === "needs-answer" ||
|
|
39
|
+
value === "needs-routing" ||
|
|
40
|
+
value === "needs-verification"
|
|
41
|
+
) return "warning";
|
|
35
42
|
if (value === "needs-judgment") return "accent";
|
|
36
43
|
return "dim";
|
|
37
44
|
}
|
|
@@ -42,6 +49,22 @@ function modeColor(mode: DeveloperMode): ThemeColor {
|
|
|
42
49
|
return "dim";
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
function judgmentColor(status: JudgmentStatus): ThemeColor {
|
|
53
|
+
if (status === "blocked") return "error";
|
|
54
|
+
if (status === "needs-evidence") return "warning";
|
|
55
|
+
if (status === "resolved") return "success";
|
|
56
|
+
return "muted";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function judgmentSummary(result: string): string {
|
|
60
|
+
return (
|
|
61
|
+
result
|
|
62
|
+
.split(/\r?\n/)
|
|
63
|
+
.map((line) => line.trim())
|
|
64
|
+
.find((line) => line && !line.startsWith("```")) ?? result
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
export function renderDeveloperFooter(state: DeveloperState, theme: Theme): string {
|
|
46
69
|
const currentProtocol = protocolState(state);
|
|
47
70
|
const target = state.activeRoute?.owner ?? "none";
|
|
@@ -80,7 +103,7 @@ export function developerActionItems(state: DeveloperState): SelectItem[] {
|
|
|
80
103
|
{
|
|
81
104
|
value: "strict",
|
|
82
105
|
label: state.mode === "strict" ? "Strict mode (active)" : "Strict mode",
|
|
83
|
-
description: "
|
|
106
|
+
description: "Allow bash on judgment routes; require direct for Pi built-in edit/write",
|
|
84
107
|
},
|
|
85
108
|
{
|
|
86
109
|
value: "off",
|
|
@@ -91,19 +114,62 @@ export function developerActionItems(state: DeveloperState): SelectItem[] {
|
|
|
91
114
|
return items;
|
|
92
115
|
}
|
|
93
116
|
|
|
117
|
+
function questionAction(owner: PendingQuestion["resolutionOwner"]): string {
|
|
118
|
+
if (owner === "user") return "enter to provide the required answer";
|
|
119
|
+
if (owner === "environment") return "enter to provide access or external evidence";
|
|
120
|
+
if (owner === "agent") return "enter to ask Pi to investigate";
|
|
121
|
+
return "enter to classify and resolve this legacy question";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolutionRequestLabel(owner: PendingQuestion["resolutionOwner"]): string {
|
|
125
|
+
if (owner === "user") return "Required answer or product decision:";
|
|
126
|
+
if (owner === "environment") return "External access, observation, or environment evidence:";
|
|
127
|
+
return "Evidence or investigation request for Pi:";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ENABLE_MOUSE_SCROLL = "\u001b[?1000h\u001b[?1006h";
|
|
131
|
+
const DISABLE_MOUSE_SCROLL = "\u001b[?1006l\u001b[?1000l";
|
|
132
|
+
|
|
133
|
+
interface WritableTerminal {
|
|
134
|
+
write(data: string): void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function enableMouseScroll(tui: { terminal?: WritableTerminal }): WritableTerminal | undefined {
|
|
138
|
+
const terminal = tui.terminal;
|
|
139
|
+
if (!terminal || typeof terminal.write !== "function") return undefined;
|
|
140
|
+
terminal.write(ENABLE_MOUSE_SCROLL);
|
|
141
|
+
return terminal;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function disableMouseScroll(terminal: WritableTerminal | undefined): void {
|
|
145
|
+
terminal?.write(DISABLE_MOUSE_SCROLL);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function mouseWheelDirection(data: string): -1 | 1 | undefined {
|
|
149
|
+
const match = /^\u001b\[<(\d+);\d+;\d+M$/.exec(data);
|
|
150
|
+
if (!match?.[1]) return undefined;
|
|
151
|
+
const button = Number(match[1]);
|
|
152
|
+
if ((button & 64) === 0 || (button & 3) > 1) return undefined;
|
|
153
|
+
return (button & 1) === 0 ? -1 : 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
94
156
|
export function pendingQuestionItems(questions: PendingQuestion[]): SelectItem[] {
|
|
95
|
-
return questions.map((question) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
157
|
+
return questions.map((question) => {
|
|
158
|
+
const action = questionAction(question.resolutionOwner);
|
|
159
|
+
return {
|
|
160
|
+
value: question.id,
|
|
161
|
+
label: question.question,
|
|
162
|
+
description: `${question.status} · ${question.resolutionOwner} · ${question.gate} · ${action}`,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
100
165
|
}
|
|
101
166
|
|
|
102
167
|
interface SelectDialogOptions {
|
|
103
168
|
title: string;
|
|
104
169
|
subtitle: string;
|
|
105
170
|
items: SelectItem[];
|
|
106
|
-
width:
|
|
171
|
+
width: SizeValue;
|
|
172
|
+
minWidth: number;
|
|
107
173
|
maxVisible: number;
|
|
108
174
|
selectedLabelMaxLines: number;
|
|
109
175
|
selectedDescriptionMaxLines: number;
|
|
@@ -136,6 +202,7 @@ class WrappedSelectList {
|
|
|
136
202
|
private readonly items: SelectItem[];
|
|
137
203
|
private readonly keybindings: KeybindingsManager;
|
|
138
204
|
private readonly maxVisible: number;
|
|
205
|
+
private readonly renderHeight: number;
|
|
139
206
|
private readonly selectedDescriptionMaxLines: number;
|
|
140
207
|
private readonly selectedLabelMaxLines: number;
|
|
141
208
|
private readonly theme: Theme;
|
|
@@ -158,6 +225,10 @@ class WrappedSelectList {
|
|
|
158
225
|
this.keybindings = keybindings;
|
|
159
226
|
this.selectedLabelMaxLines = options.selectedLabelMaxLines;
|
|
160
227
|
this.selectedDescriptionMaxLines = options.selectedDescriptionMaxLines;
|
|
228
|
+
this.renderHeight = Math.max(
|
|
229
|
+
1,
|
|
230
|
+
maxVisible - 1 + options.selectedLabelMaxLines + options.selectedDescriptionMaxLines + 1,
|
|
231
|
+
);
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
render(width: number): string[] {
|
|
@@ -206,14 +277,27 @@ class WrappedSelectList {
|
|
|
206
277
|
),
|
|
207
278
|
);
|
|
208
279
|
}
|
|
209
|
-
|
|
280
|
+
const visible = lines.slice(0, this.renderHeight);
|
|
281
|
+
while (visible.length < this.renderHeight) visible.push("");
|
|
282
|
+
return visible;
|
|
210
283
|
}
|
|
211
284
|
|
|
212
285
|
handleInput(data: string): void {
|
|
213
|
-
|
|
286
|
+
const wheelDirection = mouseWheelDirection(data);
|
|
287
|
+
if (wheelDirection !== undefined) {
|
|
288
|
+
this.moveSelection(wheelDirection * 3);
|
|
289
|
+
} else if (this.keybindings.matches(data, "tui.select.up")) {
|
|
214
290
|
this.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;
|
|
215
291
|
} else if (this.keybindings.matches(data, "tui.select.down")) {
|
|
216
292
|
this.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;
|
|
293
|
+
} else if (this.keybindings.matches(data, "tui.select.pageUp")) {
|
|
294
|
+
this.moveSelection(-Math.max(1, this.maxVisible - 1));
|
|
295
|
+
} else if (this.keybindings.matches(data, "tui.select.pageDown")) {
|
|
296
|
+
this.moveSelection(Math.max(1, this.maxVisible - 1));
|
|
297
|
+
} else if (matchesKey(data, "home")) {
|
|
298
|
+
this.selectedIndex = 0;
|
|
299
|
+
} else if (matchesKey(data, "end")) {
|
|
300
|
+
this.selectedIndex = Math.max(0, this.items.length - 1);
|
|
217
301
|
} else if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
218
302
|
const selected = this.items[this.selectedIndex];
|
|
219
303
|
if (selected) this.onSelect?.(selected);
|
|
@@ -222,6 +306,10 @@ class WrappedSelectList {
|
|
|
222
306
|
}
|
|
223
307
|
}
|
|
224
308
|
|
|
309
|
+
private moveSelection(delta: number): void {
|
|
310
|
+
this.selectedIndex = Math.max(0, Math.min(this.items.length - 1, this.selectedIndex + delta));
|
|
311
|
+
}
|
|
312
|
+
|
|
225
313
|
invalidate(): void {}
|
|
226
314
|
}
|
|
227
315
|
|
|
@@ -229,65 +317,69 @@ async function showSelectDialog(
|
|
|
229
317
|
ctx: ExtensionCommandContext,
|
|
230
318
|
options: SelectDialogOptions,
|
|
231
319
|
): Promise<string | undefined> {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
320
|
+
let mouseTerminal: WritableTerminal | undefined;
|
|
321
|
+
try {
|
|
322
|
+
const result = await ctx.ui.custom<string | null>(
|
|
323
|
+
(tui, theme, keybindings, done) => {
|
|
324
|
+
mouseTerminal = enableMouseScroll(tui);
|
|
325
|
+
const container = new Container();
|
|
326
|
+
const title = new Text("", 1, 0);
|
|
327
|
+
const subtitle = new Text("", 1, 0);
|
|
328
|
+
const hint = new Text("", 1, 0);
|
|
329
|
+
const updateText = () => {
|
|
330
|
+
title.setText(theme.fg("accent", theme.bold(`◆ ${options.title}`)));
|
|
331
|
+
subtitle.setText(theme.fg("muted", options.subtitle));
|
|
332
|
+
hint.setText(theme.fg("dim", "wheel/↑↓ · PgUp/PgDn · Home/End · enter select · esc cancel"));
|
|
333
|
+
};
|
|
334
|
+
updateText();
|
|
335
|
+
|
|
336
|
+
const list = new WrappedSelectList(
|
|
337
|
+
options.items,
|
|
338
|
+
Math.min(options.items.length, options.maxVisible),
|
|
339
|
+
theme,
|
|
340
|
+
keybindings,
|
|
341
|
+
{
|
|
342
|
+
selectedLabelMaxLines: options.selectedLabelMaxLines,
|
|
343
|
+
selectedDescriptionMaxLines: options.selectedDescriptionMaxLines,
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
list.onSelect = (item) => done(item.value);
|
|
347
|
+
list.onCancel = () => done(null);
|
|
348
|
+
|
|
349
|
+
container.addChild(title);
|
|
350
|
+
container.addChild(subtitle);
|
|
351
|
+
container.addChild(list);
|
|
352
|
+
container.addChild(hint);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
render(width: number) {
|
|
356
|
+
return renderModalFrame(container, theme, width);
|
|
357
|
+
},
|
|
358
|
+
invalidate() {
|
|
359
|
+
updateText();
|
|
360
|
+
container.invalidate();
|
|
361
|
+
},
|
|
362
|
+
handleInput(data: string) {
|
|
363
|
+
list.handleInput(data);
|
|
364
|
+
tui.requestRender();
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
overlay: true,
|
|
370
|
+
overlayOptions: {
|
|
371
|
+
anchor: "center",
|
|
372
|
+
width: options.width,
|
|
373
|
+
minWidth: options.minWidth,
|
|
374
|
+
maxHeight: "88%",
|
|
375
|
+
margin: 1,
|
|
274
376
|
},
|
|
275
|
-
};
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
overlay: true,
|
|
279
|
-
overlayOptions: {
|
|
280
|
-
anchor: "center",
|
|
281
|
-
width: options.width,
|
|
282
|
-
maxHeight: Math.min(
|
|
283
|
-
options.maxVisible + options.selectedLabelMaxLines + options.selectedDescriptionMaxLines + 7,
|
|
284
|
-
24,
|
|
285
|
-
),
|
|
286
|
-
margin: 1,
|
|
287
377
|
},
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
378
|
+
);
|
|
379
|
+
return result ?? undefined;
|
|
380
|
+
} finally {
|
|
381
|
+
disableMouseScroll(mouseTerminal);
|
|
382
|
+
}
|
|
291
383
|
}
|
|
292
384
|
|
|
293
385
|
export async function showDeveloperActionSelector(
|
|
@@ -298,10 +390,11 @@ export async function showDeveloperActionSelector(
|
|
|
298
390
|
title: "Developer control",
|
|
299
391
|
subtitle: `Current · ${modeName(state.mode)} · ${protocolState(state)}`,
|
|
300
392
|
items: developerActionItems(state),
|
|
301
|
-
width:
|
|
393
|
+
width: "84%",
|
|
394
|
+
minWidth: 78,
|
|
302
395
|
maxVisible: 6,
|
|
303
|
-
selectedLabelMaxLines:
|
|
304
|
-
selectedDescriptionMaxLines:
|
|
396
|
+
selectedLabelMaxLines: 3,
|
|
397
|
+
selectedDescriptionMaxLines: 3,
|
|
305
398
|
});
|
|
306
399
|
if (result === "status" || result === "questions" || result === "on" || result === "strict" || result === "off") {
|
|
307
400
|
return result;
|
|
@@ -309,19 +402,20 @@ export async function showDeveloperActionSelector(
|
|
|
309
402
|
return undefined;
|
|
310
403
|
}
|
|
311
404
|
|
|
312
|
-
export
|
|
405
|
+
export function showPendingQuestionSelector(
|
|
313
406
|
ctx: ExtensionCommandContext,
|
|
314
407
|
questions: PendingQuestion[],
|
|
315
408
|
): Promise<string | undefined> {
|
|
316
|
-
if (questions.length === 0) return undefined;
|
|
409
|
+
if (questions.length === 0) return Promise.resolve(undefined);
|
|
317
410
|
return showSelectDialog(ctx, {
|
|
318
|
-
title: "
|
|
319
|
-
subtitle: "
|
|
411
|
+
title: "Resolve an open Developer question",
|
|
412
|
+
subtitle: "Enter opens an answer/evidence editor; the question closes after a resolved or not-applicable judgment",
|
|
320
413
|
items: pendingQuestionItems(questions),
|
|
321
|
-
width:
|
|
414
|
+
width: "92%",
|
|
415
|
+
minWidth: 88,
|
|
322
416
|
maxVisible: 10,
|
|
323
417
|
selectedLabelMaxLines: 5,
|
|
324
|
-
selectedDescriptionMaxLines:
|
|
418
|
+
selectedDescriptionMaxLines: 3,
|
|
325
419
|
});
|
|
326
420
|
}
|
|
327
421
|
|
|
@@ -346,9 +440,10 @@ export class DeveloperWidget {
|
|
|
346
440
|
);
|
|
347
441
|
}
|
|
348
442
|
for (const question of this.state.pendingQuestions.slice(0, 3)) {
|
|
443
|
+
const label = question.resolutionOwner === "user" ? "? answer" : "? evidence";
|
|
349
444
|
lines.push(
|
|
350
445
|
truncateToWidth(
|
|
351
|
-
`${this.theme.fg(question.status === "blocked" ? "error" : "warning",
|
|
446
|
+
`${this.theme.fg(question.status === "blocked" ? "error" : "warning", label)} ${this.theme.fg("dim", `· ${question.gate} ·`)} ${this.theme.fg("muted", question.question)}`,
|
|
352
447
|
width,
|
|
353
448
|
"…",
|
|
354
449
|
),
|
|
@@ -360,6 +455,9 @@ export class DeveloperWidget {
|
|
|
360
455
|
if (this.state.implementationFramingRequired) {
|
|
361
456
|
lines.push(this.theme.fg("warning", "→ next · sketch feature shape or signal structural movement"));
|
|
362
457
|
}
|
|
458
|
+
if (this.state.rerouteRequired) {
|
|
459
|
+
lines.push(this.theme.fg("warning", "→ next · reroute from the latest direct landing"));
|
|
460
|
+
}
|
|
363
461
|
if (this.state.verificationRequired) {
|
|
364
462
|
lines.push(this.theme.fg("warning", "→ next · verify changed artifacts before completion"));
|
|
365
463
|
}
|
|
@@ -404,14 +502,24 @@ export class DeveloperStatusPanel {
|
|
|
404
502
|
this.onClose();
|
|
405
503
|
return;
|
|
406
504
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
505
|
+
const wheelDirection = mouseWheelDirection(data);
|
|
506
|
+
const pageSize = Math.max(1, this.viewportHeight - 6);
|
|
507
|
+
if (wheelDirection !== undefined) this.moveScroll(wheelDirection * 3);
|
|
508
|
+
else if (matchesKey(data, "up")) this.moveScroll(-1);
|
|
509
|
+
else if (matchesKey(data, "down")) this.moveScroll(1);
|
|
510
|
+
else if (matchesKey(data, "pageUp")) this.moveScroll(-pageSize);
|
|
511
|
+
else if (matchesKey(data, "pageDown")) this.moveScroll(pageSize);
|
|
512
|
+
else if (matchesKey(data, "home")) this.scrollOffset = 0;
|
|
513
|
+
else if (matchesKey(data, "end")) this.scrollOffset = this.maxScrollOffset;
|
|
514
|
+
else return;
|
|
411
515
|
this.invalidate();
|
|
412
516
|
this.requestRender();
|
|
413
517
|
}
|
|
414
518
|
|
|
519
|
+
private moveScroll(delta: number): void {
|
|
520
|
+
this.scrollOffset = Math.max(0, Math.min(this.maxScrollOffset, this.scrollOffset + delta));
|
|
521
|
+
}
|
|
522
|
+
|
|
415
523
|
render(width: number): string[] {
|
|
416
524
|
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
417
525
|
|
|
@@ -471,11 +579,12 @@ export class DeveloperStatusPanel {
|
|
|
471
579
|
} else {
|
|
472
580
|
for (const question of state.pendingQuestions.slice(0, 4)) {
|
|
473
581
|
addWrapped(
|
|
474
|
-
question.status
|
|
582
|
+
`${question.status} · ${question.resolutionOwner} · ${question.gate}`,
|
|
475
583
|
question.question,
|
|
476
584
|
question.status === "blocked" ? "error" : "warning",
|
|
477
585
|
2,
|
|
478
586
|
);
|
|
587
|
+
addWrapped("resolves when", question.resolutionCriteria, "dim", 2);
|
|
479
588
|
}
|
|
480
589
|
if (state.pendingQuestions.length > 4) {
|
|
481
590
|
addWrapped("more", `${state.pendingQuestions.length - 4} additional open questions`, "dim", 1);
|
|
@@ -483,29 +592,30 @@ export class DeveloperStatusPanel {
|
|
|
483
592
|
}
|
|
484
593
|
|
|
485
594
|
rows.push(row());
|
|
486
|
-
section(
|
|
487
|
-
if (state.
|
|
488
|
-
addWrapped(
|
|
489
|
-
"status",
|
|
490
|
-
state.lastJudgment.status,
|
|
491
|
-
protocolColor(
|
|
492
|
-
state.lastJudgment.status === "blocked"
|
|
493
|
-
? "blocked"
|
|
494
|
-
: state.lastJudgment.status === "needs-evidence"
|
|
495
|
-
? "needs-evidence"
|
|
496
|
-
: "idle",
|
|
497
|
-
),
|
|
498
|
-
1,
|
|
499
|
-
);
|
|
500
|
-
addWrapped("result", state.lastJudgment.result, "muted", 3);
|
|
501
|
-
addWrapped(
|
|
502
|
-
"evidence",
|
|
503
|
-
`${state.lastJudgment.basis.length} basis · ${state.lastJudgment.artifacts.length} artifacts`,
|
|
504
|
-
"dim",
|
|
505
|
-
1,
|
|
506
|
-
);
|
|
507
|
-
} else {
|
|
595
|
+
section(`Judgment history · ${state.judgmentHistory.length}`);
|
|
596
|
+
if (state.judgmentHistory.length === 0) {
|
|
508
597
|
addWrapped("state", "No judgment has been recorded on this branch.", "dim", 2);
|
|
598
|
+
} else {
|
|
599
|
+
const recentJudgments = state.judgmentHistory.slice(-10).toReversed();
|
|
600
|
+
for (const judgment of recentJudgments) {
|
|
601
|
+
const route = state.routeHistory.find((candidate) => candidate.routeId === judgment.routeId);
|
|
602
|
+
addWrapped(
|
|
603
|
+
`${judgment.owner} ${judgment.status}`,
|
|
604
|
+
`${judgment.question} → ${judgmentSummary(judgment.result)}`,
|
|
605
|
+
judgmentColor(judgment.status),
|
|
606
|
+
3,
|
|
607
|
+
);
|
|
608
|
+
if (route) addWrapped("route reason", route.reason, "dim", 2);
|
|
609
|
+
for (const alternative of route?.consideredAlternatives ?? []) {
|
|
610
|
+
addWrapped(`considered ${alternative.owner}`, alternative.reason, "dim", 2);
|
|
611
|
+
}
|
|
612
|
+
for (const update of judgment.questionUpdates ?? []) {
|
|
613
|
+
addWrapped(`question ${update.status}`, `${update.questionId} → ${update.result}`, "accent", 2);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (state.judgmentHistory.length > recentJudgments.length) {
|
|
617
|
+
addWrapped("earlier", `${state.judgmentHistory.length - recentJudgments.length} earlier judgments`, "dim", 1);
|
|
618
|
+
}
|
|
509
619
|
}
|
|
510
620
|
|
|
511
621
|
rows.push(row());
|
|
@@ -523,9 +633,10 @@ export class DeveloperStatusPanel {
|
|
|
523
633
|
this.maxScrollOffset = Math.max(0, body.length - bodyCapacity);
|
|
524
634
|
this.scrollOffset = Math.min(this.scrollOffset, this.maxScrollOffset);
|
|
525
635
|
const visibleBody = body.slice(this.scrollOffset, this.scrollOffset + bodyCapacity);
|
|
636
|
+
while (visibleBody.length < bodyCapacity) visibleBody.push(row());
|
|
526
637
|
const position =
|
|
527
638
|
body.length > bodyCapacity
|
|
528
|
-
?
|
|
639
|
+
? `wheel/↑↓ · PgUp/PgDn · Home/End · ${this.scrollOffset + 1}–${Math.min(body.length, this.scrollOffset + bodyCapacity)}/${body.length} · enter/esc close`
|
|
529
640
|
: "enter/esc close · /develop questions revisits open work";
|
|
530
641
|
const visibleRows = [
|
|
531
642
|
...header,
|
|
@@ -546,27 +657,55 @@ export class DeveloperStatusPanel {
|
|
|
546
657
|
}
|
|
547
658
|
|
|
548
659
|
export async function showDeveloperStatus(ctx: ExtensionCommandContext, view: DeveloperStatusView): Promise<void> {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
anchor: "center",
|
|
559
|
-
width: 92,
|
|
560
|
-
minWidth: 56,
|
|
561
|
-
maxHeight: 28,
|
|
562
|
-
margin: 1,
|
|
660
|
+
let mouseTerminal: WritableTerminal | undefined;
|
|
661
|
+
try {
|
|
662
|
+
await ctx.ui.custom<void>(
|
|
663
|
+
(tui, theme, _keybindings, done) => {
|
|
664
|
+
mouseTerminal = enableMouseScroll(tui);
|
|
665
|
+
return new DeveloperStatusPanel(view, theme, () => done(), {
|
|
666
|
+
viewportHeight: Math.max(10, Math.min(36, Math.floor(tui.terminal.rows * 0.88))),
|
|
667
|
+
requestRender: () => tui.requestRender(),
|
|
668
|
+
});
|
|
563
669
|
},
|
|
564
|
-
|
|
565
|
-
|
|
670
|
+
{
|
|
671
|
+
overlay: true,
|
|
672
|
+
overlayOptions: {
|
|
673
|
+
anchor: "center",
|
|
674
|
+
width: "90%",
|
|
675
|
+
minWidth: 72,
|
|
676
|
+
maxHeight: "88%",
|
|
677
|
+
margin: 1,
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
} finally {
|
|
682
|
+
disableMouseScroll(mouseTerminal);
|
|
683
|
+
}
|
|
566
684
|
}
|
|
567
685
|
|
|
568
|
-
export function
|
|
569
|
-
const
|
|
686
|
+
export function questionResolutionPrompt(question: PendingQuestion): string {
|
|
687
|
+
const requestLabel = resolutionRequestLabel(question.resolutionOwner);
|
|
688
|
+
return [
|
|
689
|
+
"Resolve this open Developer question.",
|
|
690
|
+
"",
|
|
691
|
+
`Question: ${question.question}`,
|
|
692
|
+
`Resolution owner: ${question.resolutionOwner}`,
|
|
693
|
+
`Gate: ${question.gate}`,
|
|
694
|
+
`Resolution criteria: ${question.resolutionCriteria}`,
|
|
695
|
+
"",
|
|
696
|
+
requestLabel,
|
|
697
|
+
"",
|
|
698
|
+
"Use the supplied answer as new evidence, or investigate with available tools when the owner is agent.",
|
|
699
|
+
"Route the focused question, then record resolved/not-applicable with question_updates when it is settled; otherwise retain it with the specific remaining evidence gap.",
|
|
700
|
+
].join("\n");
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export function editQuestionResolutionRequest(
|
|
704
|
+
ctx: ExtensionCommandContext,
|
|
705
|
+
question: PendingQuestion,
|
|
706
|
+
): Promise<string | undefined> {
|
|
570
707
|
const current = ctx.ui.getEditorText();
|
|
571
|
-
|
|
708
|
+
const request = questionResolutionPrompt(question);
|
|
709
|
+
const initial = current.trim() ? `${current.trimEnd()}\n\n${request}` : request;
|
|
710
|
+
return ctx.ui.editor("Answer or investigate the selected Developer question", initial);
|
|
572
711
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hobin/developer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Adaptive product-development reasoning and evidence for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["pi-package", "pi", "developer", "skills"],
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"check": "node scripts/check-package.mjs && node --test tests/*.test.ts",
|
|
30
30
|
"eval": "node scripts/eval-rpc.mjs",
|
|
31
|
-
"eval:json": "node scripts/eval-json.mjs"
|
|
31
|
+
"eval:json": "node scripts/eval-json.mjs",
|
|
32
|
+
"eval:live": "node scripts/eval-live.mjs --transport rpc",
|
|
33
|
+
"eval:live:json": "node scripts/eval-live.mjs --transport json"
|
|
32
34
|
},
|
|
33
35
|
"pi": {
|
|
34
36
|
"extensions": ["./extensions/developer.ts"],
|
|
@@ -39,5 +41,8 @@
|
|
|
39
41
|
"@earendil-works/pi-coding-agent": "*",
|
|
40
42
|
"@earendil-works/pi-tui": "*",
|
|
41
43
|
"typebox": "*"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"xstate": "5.32.5"
|
|
42
47
|
}
|
|
43
48
|
}
|
|
@@ -21,11 +21,12 @@ rejected, or deferred?
|
|
|
21
21
|
|
|
22
22
|
## Output
|
|
23
23
|
|
|
24
|
-
Lead with the caller's concrete failure mode; keep review labels secondary.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
Lead with the caller's concrete failure mode; keep review labels secondary. Use
|
|
25
|
+
a review card or table with `Candidate`, `Pressure`, `Caller contract`, `Hidden
|
|
26
|
+
detail`, `Failure`, `Stop check`, and `Evidence gap`. Add a boundary diagram or
|
|
27
|
+
before/after interface snippet when the candidate changes layers or callers.
|
|
28
|
+
End with one prominent decision: `keep`, `revise-surface`, `revise-model`,
|
|
29
|
+
`split`, `reject`, or `defer`. When used inside a larger task, return:
|
|
29
30
|
|
|
30
31
|
```text
|
|
31
32
|
Status: resolved | needs-evidence | not-applicable | blocked
|
|
@@ -21,9 +21,13 @@ What adversarial evidence would expose the target's real failure modes?
|
|
|
21
21
|
## Output
|
|
22
22
|
|
|
23
23
|
Lead with the target claim and observed failure mode; keep eval labels secondary.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
Render the escalation ladder as an ordered matrix with source/version,
|
|
25
|
+
fixture, verifier, expected falsifier, mutation scope, and stop condition. After
|
|
26
|
+
execution, append observations to the evidence table below and show round
|
|
27
|
+
progression as a compact `smoke → visible → … → stop` line. Do not replace exact
|
|
28
|
+
commands or fixture provenance with narrative summaries. Produce target
|
|
29
|
+
provenance, claims and risk model, observed feedback, failure taxonomy, stop
|
|
30
|
+
rule, and residual risks. When used inside a larger task, return:
|
|
27
31
|
|
|
28
32
|
```text
|
|
29
33
|
Status: resolved | needs-evidence | not-applicable | blocked
|
package/skills/model/SKILL.md
CHANGED
|
@@ -19,10 +19,19 @@ Which cases, rules, states, or transitions make the claim precise?
|
|
|
19
19
|
|
|
20
20
|
## Output
|
|
21
21
|
|
|
22
|
-
Lead with the user's product language; keep modeling labels secondary.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
Lead with the user's product language; keep modeling labels secondary. Render
|
|
23
|
+
the lightest executable model, not a prose-only explanation. Choose the surface
|
|
24
|
+
that matches the condition space:
|
|
25
|
+
|
|
26
|
+
- case, decision, or truth table for finite combinations;
|
|
27
|
+
- state-transition table or ASCII state diagram when order matters;
|
|
28
|
+
- rules plus forbidden-state table for policy and invariants;
|
|
29
|
+
- guarantee map linking each rule to its owner and verification target.
|
|
30
|
+
|
|
31
|
+
Show predicates, data shapes, equations, or solver-like rules in a fenced code
|
|
32
|
+
block when they are part of the model. Produce domain, facts, rules, forbidden
|
|
33
|
+
cases, transitions or objectives when relevant, guarantee placement, and
|
|
34
|
+
verification targets. When used inside a larger task, return:
|
|
26
35
|
|
|
27
36
|
```text
|
|
28
37
|
Status: resolved | needs-evidence | not-applicable | blocked
|
|
@@ -21,9 +21,15 @@ change boundary should it preserve?
|
|
|
21
21
|
|
|
22
22
|
## Output
|
|
23
23
|
|
|
24
|
-
Lead with the user's product language; keep naming theory secondary.
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
Lead with the user's product language; keep naming theory secondary. Use a
|
|
25
|
+
rename map as the primary inspection surface:
|
|
26
|
+
|
|
27
|
+
| Current | What it says | What callers rely on | Proposed | Scope |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
|
|
30
|
+
For callable APIs or placement moves, add a minimal before/after caller snippet
|
|
31
|
+
showing how the proposed name reads in context. Keep prose for the decisive
|
|
32
|
+
sense boundary, hidden or overexposed detail, failure mode, and intentionally
|
|
27
33
|
deferred names. When used inside a larger task, return:
|
|
28
34
|
|
|
29
35
|
```text
|