@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/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 (value === "needs-evidence" || value === "needs-verification") return "warning";
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: "Require a direct route before Pi built-in mutation tools become active",
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
- value: question.id,
97
- label: question.question,
98
- description: question.status,
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: number;
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
- return lines;
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
- if (this.keybindings.matches(data, "tui.select.up")) {
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
- const result = await ctx.ui.custom<string | null>(
233
- (tui, theme, keybindings, done) => {
234
- const container = new Container();
235
- const title = new Text("", 1, 0);
236
- const subtitle = new Text("", 1, 0);
237
- const hint = new Text("", 1, 0);
238
- const updateText = () => {
239
- title.setText(theme.fg("accent", theme.bold(`◆ ${options.title}`)));
240
- subtitle.setText(theme.fg("muted", options.subtitle));
241
- hint.setText(theme.fg("dim", "↑↓ navigate · enter select · esc cancel"));
242
- };
243
- updateText();
244
-
245
- const list = new WrappedSelectList(
246
- options.items,
247
- Math.min(options.items.length, options.maxVisible),
248
- theme,
249
- keybindings,
250
- {
251
- selectedLabelMaxLines: options.selectedLabelMaxLines,
252
- selectedDescriptionMaxLines: options.selectedDescriptionMaxLines,
253
- },
254
- );
255
- list.onSelect = (item) => done(item.value);
256
- list.onCancel = () => done(null);
257
-
258
- container.addChild(title);
259
- container.addChild(subtitle);
260
- container.addChild(list);
261
- container.addChild(hint);
262
-
263
- return {
264
- render(width: number) {
265
- return renderModalFrame(container, theme, width);
266
- },
267
- invalidate() {
268
- updateText();
269
- container.invalidate();
270
- },
271
- handleInput(data: string) {
272
- list.handleInput(data);
273
- tui.requestRender();
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
- return result ?? undefined;
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: 78,
393
+ width: "84%",
394
+ minWidth: 78,
302
395
  maxVisible: 6,
303
- selectedLabelMaxLines: 2,
304
- selectedDescriptionMaxLines: 2,
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 async function showPendingQuestionSelector(
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: "Revisit an open Developer question",
319
- subtitle: "Selection prepares an editable prompt; protocol state changes only after the route is judged",
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: 96,
414
+ width: "92%",
415
+ minWidth: 88,
322
416
  maxVisible: 10,
323
417
  selectedLabelMaxLines: 5,
324
- selectedDescriptionMaxLines: 1,
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", "? open")} ${this.theme.fg("dim", "·")} ${this.theme.fg("muted", question.question)}`,
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
- if (matchesKey(data, "up")) this.scrollOffset = Math.max(0, this.scrollOffset - 1);
408
- else if (matchesKey(data, "down")) {
409
- this.scrollOffset = Math.min(this.maxScrollOffset, this.scrollOffset + 1);
410
- } else return;
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 === "blocked" ? "blocked" : "needs evidence",
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("Last judgment");
487
- if (state.lastJudgment) {
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
- ? `↑↓ scroll · ${this.scrollOffset + 1}–${Math.min(body.length, this.scrollOffset + bodyCapacity)}/${body.length} · enter/esc close`
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
- await ctx.ui.custom<void>(
550
- (tui, theme, _keybindings, done) =>
551
- new DeveloperStatusPanel(view, theme, () => done(), {
552
- viewportHeight: Math.max(6, Math.min(28, tui.terminal.rows - 2)),
553
- requestRender: () => tui.requestRender(),
554
- }),
555
- {
556
- overlay: true,
557
- overlayOptions: {
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 prepareQuestionPrompt(ctx: ExtensionCommandContext, question: PendingQuestion): void {
569
- const prompt = `Revisit this Developer question: ${question.question}`;
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
- ctx.ui.setEditorText(current.trim() ? `${current.trimEnd()}\n\n${prompt}` : prompt);
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.4",
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
- Produce review timing, layer, contract, hidden detail, output artifact, stop
26
- check, evidence and gaps, and one decision: `keep`, `revise-surface`,
27
- `revise-model`, `split`, `reject`, or `defer`. When used inside a larger task,
28
- return:
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
- Produce target provenance, claims and risk model, an escalating fixture ladder,
25
- execution matrix, observed feedback, failure taxonomy, stop rule, and residual
26
- risks. When used inside a larger task, return:
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
@@ -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
- Produce the lightest useful model: domain, predicates or cases, facts, rules,
24
- forbidden cases, transitions or objectives when relevant, guarantee placement,
25
- and verification targets. When used inside a larger task, return:
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
- Produce the naming pressure, current sense, hidden or overexposed detail,
26
- failure mode, proposed name or placement move, affected scope, and intentionally
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