@hobin/developer 0.1.2 → 0.1.4
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 +43 -11
- package/extensions/developer.ts +136 -21
- package/extensions/references/behavior-preserving-structural-change.md +14 -10
- package/extensions/state.ts +88 -10
- package/extensions/tui.ts +246 -65
- package/package.json +11 -26
package/README.md
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
Adaptive product-development judgment for [Pi](https://pi.dev).
|
|
4
4
|
|
|
5
5
|
Developer combines a small, branch-aware coordination extension with ten
|
|
6
|
-
independent skills. It routes one concrete question
|
|
7
|
-
resulting evidence, and preserves unresolved questions
|
|
8
|
-
|
|
6
|
+
independent skills. It routes one concrete question or green-to-green movement
|
|
7
|
+
at a time, records the resulting evidence, and preserves unresolved questions.
|
|
8
|
+
Its default topology gives feature work a useful backbone without turning every
|
|
9
|
+
task into a fixed lifecycle.
|
|
9
10
|
|
|
10
11
|
## Install
|
|
11
12
|
|
|
@@ -86,11 +87,26 @@ Developer adds two model-facing protocol tools:
|
|
|
86
87
|
1. `developer_route_question` opens one route for one concrete question.
|
|
87
88
|
2. The route target is `direct` or one currently available Developer skill.
|
|
88
89
|
3. A skill route returns the exact Pi-discovered `SKILL.md` instructions and canonical
|
|
89
|
-
path; a direct route
|
|
90
|
-
|
|
91
|
-
execution profile
|
|
90
|
+
path; a direct route declares one movement, its stable landing, and its narrow
|
|
91
|
+
verification. A behavior-preserving structural route also loads the focused
|
|
92
|
+
execution profile based on flocking-style movement and small tidyings.
|
|
92
93
|
4. `developer_record_judgment` closes the route with a status, result, evidence,
|
|
93
|
-
artifacts, and any newly opened questions.
|
|
94
|
+
artifacts, and any newly opened questions. Developer then routes again from
|
|
95
|
+
the stable landing before another movement.
|
|
96
|
+
|
|
97
|
+
The conditional default topology is:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
clarify when needed → model consequential cases
|
|
101
|
+
→ sketch the first feature implementation surface OR signal existing-code movement
|
|
102
|
+
→ one direct green-to-green movement → re-route from evidence
|
|
103
|
+
→ verify before completion
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This is not a mandatory phase sequence. A stage may be not applicable and new
|
|
107
|
+
evidence may route backward or sideways. One guard is deliberate: a resolved
|
|
108
|
+
model cannot flow straight into mutation. New feature work receives an initial
|
|
109
|
+
`sketch`; existing-code structural work receives a `signal` first.
|
|
94
110
|
|
|
95
111
|
Product code is still read, edited, executed, and tested with Pi's normal tools.
|
|
96
112
|
Developer's tools only route and record judgment; they do not implement product
|
|
@@ -130,8 +146,12 @@ These are routing states, not product-completion claims. In particular:
|
|
|
130
146
|
- A resolved `specify` judgment is not user acceptance.
|
|
131
147
|
- A resolved `verify` judgment is not timeless proof after later changes.
|
|
132
148
|
|
|
133
|
-
Pending questions receive stable IDs
|
|
134
|
-
|
|
149
|
+
Pending questions receive stable internal IDs, but users do not type them.
|
|
150
|
+
Selecting `/develop questions` focuses the question in branch state; the next
|
|
151
|
+
route automatically associates that focus. A sole pending question or an exact
|
|
152
|
+
question match is also associated automatically. Resolved and not-applicable
|
|
153
|
+
judgments remove the associated question immediately; needs-evidence and blocked
|
|
154
|
+
judgments retain the same identity.
|
|
135
155
|
|
|
136
156
|
## Skills
|
|
137
157
|
|
|
@@ -184,8 +204,9 @@ stored in tool-result details. Developer reconstructs state from the current
|
|
|
184
204
|
session branch on startup and tree navigation, so a fork inherits only the
|
|
185
205
|
events on its branch.
|
|
186
206
|
|
|
187
|
-
The current event contract is `developer/
|
|
188
|
-
history can be replayed, but
|
|
207
|
+
The current event contract is `developer/v3`. Legacy `developer/v1` and
|
|
208
|
+
`developer/v2` routing history can be replayed, but new framing and verification
|
|
209
|
+
obligations are not retroactively inferred.
|
|
189
210
|
|
|
190
211
|
Developer uses Pi's normal compaction. Each new agent turn receives current
|
|
191
212
|
protocol state, and route results place identity and recovery metadata before
|
|
@@ -246,6 +267,17 @@ Load the workspace package into Pi without installing it:
|
|
|
246
267
|
pi -e ./packages/developer
|
|
247
268
|
```
|
|
248
269
|
|
|
270
|
+
Launch the isolated modal fixture in a new Ghostty window for visual QA:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
./packages/developer/scripts/ghostty-tui-qa.sh
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Choose **Inspect status** or **Revisit an open question**, then resize the window
|
|
277
|
+
while the modal is open. The fixture disables discovered extensions, skills,
|
|
278
|
+
tools, sessions, and network startup so it cannot mutate Developer state or call
|
|
279
|
+
a model.
|
|
280
|
+
|
|
249
281
|
`check` validates package structure and deterministic behavior. `eval` launches
|
|
250
282
|
the real Pi RPC surface without a model and covers package resources, commands,
|
|
251
283
|
mode state, and strict tool gating. Maintainers can use `eval:json` and the live
|
package/extensions/developer.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { Type } from "typebox";
|
|
|
17
17
|
|
|
18
18
|
import { availablePackageSkills, renderSkillMethod } from "./skills.ts";
|
|
19
19
|
import {
|
|
20
|
+
FOCUS_ENTRY,
|
|
20
21
|
JUDGMENT_TOOL,
|
|
21
22
|
MODE_ENTRY,
|
|
22
23
|
PROTOCOL,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
type DeveloperMode,
|
|
30
31
|
type DeveloperState,
|
|
31
32
|
type DirectExecutionProfile,
|
|
33
|
+
type FocusEvent,
|
|
32
34
|
type JudgmentEvent,
|
|
33
35
|
type ModeEvent,
|
|
34
36
|
type RouteEvent,
|
|
@@ -107,6 +109,25 @@ function compactLine(value: string, maxChars = 160): string {
|
|
|
107
109
|
return line.length <= maxChars ? line : `${line.slice(0, maxChars - 1)}…`;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
function normalizedQuestion(value: string): string {
|
|
113
|
+
return value.toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function inferredQuestionId(
|
|
117
|
+
state: DeveloperState,
|
|
118
|
+
question: string,
|
|
119
|
+
explicitQuestionId?: string,
|
|
120
|
+
): string | undefined {
|
|
121
|
+
if (explicitQuestionId) return explicitQuestionId;
|
|
122
|
+
if (state.focusedQuestionId) return state.focusedQuestionId;
|
|
123
|
+
const normalized = normalizedQuestion(question);
|
|
124
|
+
const exact = state.pendingQuestions.filter(
|
|
125
|
+
(pending) => normalizedQuestion(pending.question) === normalized,
|
|
126
|
+
);
|
|
127
|
+
if (exact.length === 1) return exact[0]?.id;
|
|
128
|
+
return state.pendingQuestions.length === 1 ? state.pendingQuestions[0]?.id : undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
110
131
|
function summarizeState(state: DeveloperState): string {
|
|
111
132
|
if (state.mode === "off") return "developer: off";
|
|
112
133
|
const target = state.activeRoute ? state.activeRoute.owner : "none";
|
|
@@ -117,12 +138,14 @@ function protocolPrompt(state: DeveloperState, availableSkillNames: string[]): s
|
|
|
117
138
|
const lines = [
|
|
118
139
|
"",
|
|
119
140
|
`Developer protocol (${state.mode}) is active.`,
|
|
120
|
-
"-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
"- Use the default topology as a conditional backbone, not a rigid lifecycle: clarify meaning when needed -> model consequential cases -> sketch the first implementation surface for new behavior or signal the smallest structural movement in existing code -> execute one direct step -> verify current claims.",
|
|
142
|
+
"- Adapt away from that topology when evidence makes a stage not applicable, but never jump directly from a resolved model to mutation: use sketch for feature implementation or signal for existing-code structural movement first.",
|
|
143
|
+
`- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green direct movement.`,
|
|
144
|
+
"- Use owner=direct only when the next local movement, stable landing, and narrow verification are already justified; otherwise choose the focused skill whose scope fits the current question.",
|
|
145
|
+
"- A direct step has one observable difference and one structural or behavioral purpose. Stop when its failure is locally explainable and the repository is green, pausable, and reviewable.",
|
|
123
146
|
"- For direct structural work intended to preserve behavior, set execution_profile=behavior-preserving-structure; omit the field for other direct actions and every skill route.",
|
|
124
|
-
`- Follow the
|
|
125
|
-
"-
|
|
147
|
+
`- Follow the routed method, then close the route with ${JUDGMENT_TOOL}. After every direct stable landing, route again from the new evidence before selecting another movement.`,
|
|
148
|
+
"- Do not carry a predetermined implementation queue through multiple direct steps. Re-observe after each stable landing and reroute to a skill whenever meaning, cases, design, structural direction, timing, naming, or evidence becomes uncertain.",
|
|
126
149
|
"- Protocol state is routing bookkeeping. Idle never proves product completion, user acceptance, or current verification.",
|
|
127
150
|
"- Product files are changed with Pi implementation tools. Developer protocol tools only route and record judgments.",
|
|
128
151
|
];
|
|
@@ -133,6 +156,17 @@ function protocolPrompt(state: DeveloperState, availableSkillNames: string[]): s
|
|
|
133
156
|
);
|
|
134
157
|
}
|
|
135
158
|
|
|
159
|
+
if (state.implementationFramingRequired) {
|
|
160
|
+
lines.push(
|
|
161
|
+
"Required next framing: the latest resolved model exposed implementation work. Route sketch before new feature mutation, or signal before an existing-code structural movement; direct is not ready yet.",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (state.verificationRequired) {
|
|
165
|
+
lines.push(
|
|
166
|
+
"Verification debt: a direct route changed artifacts after the last resolved verify judgment. Route verify before claiming completion or handing off as done.",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
136
170
|
if (state.activeRoute) {
|
|
137
171
|
lines.push(
|
|
138
172
|
`Active route: ${state.activeRoute.routeId} · ${state.activeRoute.owner} · ${state.activeRoute.question}`,
|
|
@@ -149,7 +183,7 @@ function protocolPrompt(state: DeveloperState, availableSkillNames: string[]): s
|
|
|
149
183
|
lines.push(`- ${question.id} · ${question.status} · ${question.question}`);
|
|
150
184
|
}
|
|
151
185
|
lines.push(
|
|
152
|
-
|
|
186
|
+
"- Revisit questions naturally. Developer automatically associates a focused, sole, or exactly matching pending question; open_question_id is an internal disambiguator only when several questions remain.",
|
|
153
187
|
);
|
|
154
188
|
}
|
|
155
189
|
lines.push(
|
|
@@ -174,6 +208,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
174
208
|
let availableSkills = new Map<string, Skill>();
|
|
175
209
|
let state = initialState();
|
|
176
210
|
let routeOpening = false;
|
|
211
|
+
const routesWithMutation = new Set<string>();
|
|
177
212
|
let toolPolicyMemory: ToolPolicyMemory = { withheldBuiltins: new Set() };
|
|
178
213
|
|
|
179
214
|
pi.registerFlag("develop-mode", {
|
|
@@ -206,7 +241,12 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
206
241
|
"developer",
|
|
207
242
|
ctx.mode === "tui" ? renderDeveloperFooter(state, ctx.ui.theme) : summarizeState(state),
|
|
208
243
|
);
|
|
209
|
-
if (
|
|
244
|
+
if (
|
|
245
|
+
!state.activeRoute &&
|
|
246
|
+
state.pendingQuestions.length === 0 &&
|
|
247
|
+
!state.implementationFramingRequired &&
|
|
248
|
+
!state.verificationRequired
|
|
249
|
+
) {
|
|
210
250
|
ctx.ui.setWidget("developer", undefined);
|
|
211
251
|
return;
|
|
212
252
|
}
|
|
@@ -227,6 +267,8 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
227
267
|
.slice(0, 3)
|
|
228
268
|
.map((question) => `open · ${question.id} · ${compactLine(question.question)}`),
|
|
229
269
|
...(state.pendingQuestions.length > 3 ? [`open · +${state.pendingQuestions.length - 3} more`] : []),
|
|
270
|
+
...(state.implementationFramingRequired ? ["next · sketch feature shape or signal structural movement"] : []),
|
|
271
|
+
...(state.verificationRequired ? ["next · verify changed artifacts before completion"] : []),
|
|
230
272
|
];
|
|
231
273
|
ctx.ui.setWidget("developer", lines, { placement: "belowEditor" });
|
|
232
274
|
};
|
|
@@ -283,6 +325,21 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
283
325
|
{
|
|
284
326
|
...SharedRouteParams,
|
|
285
327
|
owner: Type.Literal("direct", { description: "Use Pi implementation tools for an already-justified action" }),
|
|
328
|
+
movement: Type.String({
|
|
329
|
+
minLength: 1,
|
|
330
|
+
maxLength: MAX_QUESTION_CHARS,
|
|
331
|
+
description: "One locally explainable behavioral or structural movement; not a multi-step implementation queue",
|
|
332
|
+
}),
|
|
333
|
+
stop_condition: Type.String({
|
|
334
|
+
minLength: 1,
|
|
335
|
+
maxLength: MAX_QUESTION_CHARS,
|
|
336
|
+
description: "The green, pausable, reviewable stable landing that ends this direct route",
|
|
337
|
+
}),
|
|
338
|
+
verification: Type.String({
|
|
339
|
+
minLength: 1,
|
|
340
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
341
|
+
description: "The narrowest check that can catch the likely break in this movement",
|
|
342
|
+
}),
|
|
286
343
|
execution_profile: Type.Optional(
|
|
287
344
|
Type.Literal("behavior-preserving-structure", {
|
|
288
345
|
description: "Load the focused structural-mutation protocol; omit for ordinary direct action",
|
|
@@ -297,11 +354,12 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
297
354
|
name: ROUTE_TOOL,
|
|
298
355
|
label: "Developer Route Question",
|
|
299
356
|
description:
|
|
300
|
-
"Route one concrete
|
|
357
|
+
"Route one concrete judgment or one green-to-green direct movement. Uses an adaptive default topology: model, then sketch for feature shape or signal for structural movement, direct stable landings, and verify before completion.",
|
|
301
358
|
promptSnippet: "Choose how to handle one development question",
|
|
302
359
|
promptGuidelines: [
|
|
303
360
|
`Call ${ROUTE_TOOL} only when there is no active Developer route.`,
|
|
304
|
-
`Use ${ROUTE_TOOL} with the most focused skill supported by current evidence;
|
|
361
|
+
`Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; owner=direct requires one movement, one stable landing, and one narrow verification.`,
|
|
362
|
+
`After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before direct mutation.`,
|
|
305
363
|
],
|
|
306
364
|
parameters: RouteParams,
|
|
307
365
|
executionMode: "sequential",
|
|
@@ -318,12 +376,22 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
318
376
|
const reason = params.reason.trim();
|
|
319
377
|
if (!question || !reason) fail("Question and reason must contain non-whitespace text.");
|
|
320
378
|
|
|
321
|
-
const
|
|
379
|
+
const explicitQuestionId = params.open_question_id?.trim() || undefined;
|
|
380
|
+
const targetQuestionId = inferredQuestionId(state, question, explicitQuestionId);
|
|
322
381
|
if (targetQuestionId && !state.pendingQuestions.some((item) => item.id === targetQuestionId)) {
|
|
323
382
|
fail(`Unknown pending question ID: ${targetQuestionId}`);
|
|
324
383
|
}
|
|
325
384
|
|
|
326
385
|
const owner = params.owner;
|
|
386
|
+
if (
|
|
387
|
+
owner === "direct" &&
|
|
388
|
+
state.implementationFramingRequired &&
|
|
389
|
+
(availableSkills.has("sketch") || availableSkills.has("signal"))
|
|
390
|
+
) {
|
|
391
|
+
fail(
|
|
392
|
+
"The latest resolved model requires implementation framing before direct work. Route sketch for new feature shape or signal for existing-code structural movement.",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
327
395
|
const skill = owner === "direct" ? undefined : availableSkills.get(owner);
|
|
328
396
|
if (owner !== "direct" && !skill) {
|
|
329
397
|
fail(`Developer skill ${owner} is unavailable or disabled in the current Pi resource configuration.`);
|
|
@@ -351,6 +419,23 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
351
419
|
"The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
|
|
352
420
|
].join("\n");
|
|
353
421
|
|
|
422
|
+
const directStep =
|
|
423
|
+
owner === "direct"
|
|
424
|
+
? {
|
|
425
|
+
movement: ("movement" in params ? params.movement : question).trim(),
|
|
426
|
+
stopCondition: (
|
|
427
|
+
"stop_condition" in params
|
|
428
|
+
? params.stop_condition
|
|
429
|
+
: "Reach a green, pausable, reviewable stable landing."
|
|
430
|
+
).trim(),
|
|
431
|
+
verification: (
|
|
432
|
+
"verification" in params
|
|
433
|
+
? params.verification
|
|
434
|
+
: "Run the narrowest relevant check and inspect the resulting diff or output."
|
|
435
|
+
).trim(),
|
|
436
|
+
}
|
|
437
|
+
: undefined;
|
|
438
|
+
|
|
354
439
|
const event: RouteEvent = {
|
|
355
440
|
protocol: PROTOCOL,
|
|
356
441
|
kind: "route",
|
|
@@ -362,6 +447,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
362
447
|
targetQuestionId,
|
|
363
448
|
methodLocation: skill?.filePath,
|
|
364
449
|
executionProfile,
|
|
450
|
+
directStep,
|
|
365
451
|
};
|
|
366
452
|
const response = [
|
|
367
453
|
`Route ID: ${event.routeId}`,
|
|
@@ -369,6 +455,14 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
369
455
|
`Target: ${event.owner}`,
|
|
370
456
|
skill ? `Skill location: ${skill.filePath}` : "Skill location: direct action; no skill file",
|
|
371
457
|
executionProfile ? `Execution profile: ${executionProfile}` : "Execution profile: skill judgment",
|
|
458
|
+
...(directStep
|
|
459
|
+
? [
|
|
460
|
+
`Movement: ${directStep.movement}`,
|
|
461
|
+
`Stable landing: ${directStep.stopCondition}`,
|
|
462
|
+
`Narrow verification: ${directStep.verification}`,
|
|
463
|
+
"Stop this direct route at that landing, record the evidence, and route again before another movement.",
|
|
464
|
+
]
|
|
465
|
+
: []),
|
|
372
466
|
`Reason: ${event.reason}`,
|
|
373
467
|
`Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
|
|
374
468
|
targetQuestionId ? `Revisits pending question: ${targetQuestionId}` : "Revisits pending question: none",
|
|
@@ -397,11 +491,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
397
491
|
context.lastComponent,
|
|
398
492
|
);
|
|
399
493
|
},
|
|
400
|
-
renderResult(result, { expanded, isPartial
|
|
494
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
401
495
|
if (isPartial) {
|
|
402
496
|
return reusableText(theme.fg("warning", "routing development question…"), context.lastComponent);
|
|
403
497
|
}
|
|
404
|
-
if (isError) {
|
|
498
|
+
if (context.isError) {
|
|
405
499
|
return reusableText(
|
|
406
500
|
theme.fg("error", resultText(result) || "Developer route failed"),
|
|
407
501
|
context.lastComponent,
|
|
@@ -425,6 +519,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
425
519
|
if (event.executionProfile) {
|
|
426
520
|
text += `\n${theme.fg("dim", `profile · ${event.executionProfile}`)}`;
|
|
427
521
|
}
|
|
522
|
+
if (event.directStep) {
|
|
523
|
+
text += `\n${theme.fg("dim", `movement · ${event.directStep.movement}`)}`;
|
|
524
|
+
text += `\n${theme.fg("dim", `landing · ${event.directStep.stopCondition}`)}`;
|
|
525
|
+
text += `\n${theme.fg("dim", `verify · ${event.directStep.verification}`)}`;
|
|
526
|
+
}
|
|
428
527
|
}
|
|
429
528
|
if (!expanded && event) text += ` · ${keyHint("app.tools.expand", "details")}`;
|
|
430
529
|
return reusableText(text, context.lastComponent);
|
|
@@ -503,6 +602,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
503
602
|
basis,
|
|
504
603
|
openedQuestions,
|
|
505
604
|
artifacts: (params.artifacts ?? []).map((item) => item.trim()).filter(Boolean),
|
|
605
|
+
changedArtifacts: routesWithMutation.has(params.route_id),
|
|
506
606
|
};
|
|
507
607
|
const nextState = applyDeveloperEvent(state, event);
|
|
508
608
|
if (nextState.pendingQuestions.length > MAX_PENDING_QUESTIONS) {
|
|
@@ -513,12 +613,17 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
513
613
|
|
|
514
614
|
const next = protocolState(nextState);
|
|
515
615
|
const nextMessage =
|
|
516
|
-
|
|
517
|
-
?
|
|
518
|
-
|
|
616
|
+
event.owner === "direct"
|
|
617
|
+
? nextState.verificationRequired
|
|
618
|
+
? "Stable landing recorded. Route again from the new evidence; verify is required before claiming completion."
|
|
619
|
+
: "Stable landing recorded. Route again from the new evidence before selecting another movement."
|
|
620
|
+
: next === "idle"
|
|
621
|
+
? "Developer protocol is idle. This is routing state only and does not prove task completion."
|
|
622
|
+
: `Developer protocol is ${next}. Address the current routing obligation before handoff.`;
|
|
519
623
|
const response = `Recorded ${event.status} judgment for ${event.routeId}: ${event.result}\n${nextMessage}`;
|
|
520
624
|
ensureSafeToolText(response, "Developer judgment result");
|
|
521
625
|
|
|
626
|
+
routesWithMutation.delete(params.route_id);
|
|
522
627
|
state = nextState;
|
|
523
628
|
syncProtocolTools();
|
|
524
629
|
refreshUI(ctx);
|
|
@@ -541,11 +646,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
541
646
|
context.lastComponent,
|
|
542
647
|
);
|
|
543
648
|
},
|
|
544
|
-
renderResult(result, { expanded, isPartial
|
|
649
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
545
650
|
if (isPartial) {
|
|
546
651
|
return reusableText(theme.fg("warning", "recording development judgment…"), context.lastComponent);
|
|
547
652
|
}
|
|
548
|
-
if (isError) {
|
|
653
|
+
if (context.isError) {
|
|
549
654
|
return reusableText(
|
|
550
655
|
theme.fg("error", resultText(result) || "Developer judgment failed"),
|
|
551
656
|
context.lastComponent,
|
|
@@ -668,8 +773,16 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
668
773
|
if (!questionId) return;
|
|
669
774
|
const question = state.pendingQuestions.find((item) => item.id === questionId);
|
|
670
775
|
if (!question) return;
|
|
776
|
+
const focusEvent: FocusEvent = {
|
|
777
|
+
protocol: PROTOCOL,
|
|
778
|
+
kind: "focus",
|
|
779
|
+
questionId: question.id,
|
|
780
|
+
};
|
|
781
|
+
pi.appendEntry(FOCUS_ENTRY, focusEvent);
|
|
782
|
+
state = applyDeveloperEvent(state, focusEvent);
|
|
783
|
+
refreshUI(ctx);
|
|
671
784
|
prepareQuestionPrompt(ctx, question);
|
|
672
|
-
ctx.ui.notify("Open question loaded into the editor for review.", "info");
|
|
785
|
+
ctx.ui.notify("Open question focused and loaded into the editor for review.", "info");
|
|
673
786
|
return;
|
|
674
787
|
}
|
|
675
788
|
ctx.ui.notify(
|
|
@@ -717,10 +830,12 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
717
830
|
});
|
|
718
831
|
|
|
719
832
|
pi.on("tool_call", (event) => {
|
|
720
|
-
if (state.mode !== "strict") return;
|
|
721
833
|
const mutationBuiltins = builtinMutationToolNames(pi.getAllTools());
|
|
722
|
-
if (
|
|
723
|
-
|
|
834
|
+
if (mutationBuiltins.has(event.toolName) && state.activeRoute?.owner === "direct") {
|
|
835
|
+
routesWithMutation.add(state.activeRoute.routeId);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (state.mode !== "strict" || !mutationBuiltins.has(event.toolName)) return;
|
|
724
839
|
return {
|
|
725
840
|
block: true,
|
|
726
841
|
reason: `Developer strict mode requires an active ${ROUTE_TOOL} targeting direct action (owner=direct) before Pi built-in edit, write, or bash.`,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Behavior-Preserving Structural Change
|
|
2
2
|
|
|
3
|
-
Use this protocol only after a `direct` route has justified
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Use this protocol only after a `direct` route has justified one concrete structural
|
|
4
|
+
movement and its stable landing. It governs that single green-to-green mutation;
|
|
5
|
+
it does not discover the move, approve an abstraction, decide its timing, or carry
|
|
6
|
+
a multi-step implementation queue across landings.
|
|
6
7
|
|
|
7
8
|
## Entry Contract
|
|
8
9
|
|
|
@@ -42,13 +43,14 @@ valid. Do not rely on a final large diff to explain which step changed behavior.
|
|
|
42
43
|
|
|
43
44
|
## Evidence Rhythm
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
Within the routed movement:
|
|
46
47
|
|
|
47
48
|
1. run the narrowest check that can catch the likely break;
|
|
48
49
|
2. inspect the diff for mixed behavior and structural changes;
|
|
49
50
|
3. reduce the step when the failure cannot be explained locally;
|
|
50
|
-
4. return to
|
|
51
|
-
5.
|
|
51
|
+
4. return to the declared green, deployable stable landing;
|
|
52
|
+
5. close the direct route there and let Developer re-observe and route the next
|
|
53
|
+
question instead of following a predetermined final design.
|
|
52
54
|
|
|
53
55
|
A passing test is useful only when it exercises the preserved behavior. When no
|
|
54
56
|
cheap verifier exists, keep the movement smaller and record the residual risk.
|
|
@@ -63,13 +65,15 @@ A stable landing is not merely a green command. It is a state where:
|
|
|
63
65
|
- the diff has one explainable structural purpose;
|
|
64
66
|
- the next decision can be made from the new evidence.
|
|
65
67
|
|
|
66
|
-
Stop there when the accepted
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
Stop and close the direct route there even when the wider accepted task still has
|
|
69
|
+
work remaining. Developer must select the next movement or focused judgment from
|
|
70
|
+
the new evidence. Further caller movement, cleanup, polymorphism, factory design,
|
|
71
|
+
or public abstraction requires another route and its own current pressure.
|
|
69
72
|
|
|
70
73
|
## Reroute Conditions
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
Close direct execution at the latest stable landing and open a focused judgment
|
|
76
|
+
when the movement reveals:
|
|
73
77
|
|
|
74
78
|
- an unexpected product behavior or policy choice: `specify`;
|
|
75
79
|
- missing cases, transition rules, or replacement obligations: `model`;
|
package/extensions/state.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export const PROTOCOL = "developer/
|
|
1
|
+
export const PROTOCOL = "developer/v3" as const;
|
|
2
|
+
export const PREVIOUS_PROTOCOL = "developer/v2" as const;
|
|
2
3
|
export const LEGACY_PROTOCOL = "developer/v1" as const;
|
|
3
4
|
export const MODE_ENTRY = "developer.mode" as const;
|
|
5
|
+
export const FOCUS_ENTRY = "developer.question-focus" as const;
|
|
4
6
|
export const ROUTE_TOOL = "developer_route_question" as const;
|
|
5
7
|
export const JUDGMENT_TOOL = "developer_record_judgment" as const;
|
|
6
8
|
export const LEGACY_ROUTE_TOOL = "route_question" as const;
|
|
@@ -17,6 +19,18 @@ export interface ModeEvent {
|
|
|
17
19
|
mode: DeveloperMode;
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
export interface FocusEvent {
|
|
23
|
+
protocol: typeof PROTOCOL;
|
|
24
|
+
kind: "focus";
|
|
25
|
+
questionId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DirectStepContract {
|
|
29
|
+
movement: string;
|
|
30
|
+
stopCondition: string;
|
|
31
|
+
verification: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
export interface RouteEvent {
|
|
21
35
|
protocol: typeof PROTOCOL;
|
|
22
36
|
kind: "route";
|
|
@@ -28,6 +42,7 @@ export interface RouteEvent {
|
|
|
28
42
|
targetQuestionId?: string;
|
|
29
43
|
methodLocation?: string;
|
|
30
44
|
executionProfile?: DirectExecutionProfile;
|
|
45
|
+
directStep?: DirectStepContract;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
export interface PendingQuestion {
|
|
@@ -48,9 +63,10 @@ export interface JudgmentEvent {
|
|
|
48
63
|
basis: string[];
|
|
49
64
|
openedQuestions: PendingQuestion[];
|
|
50
65
|
artifacts: string[];
|
|
66
|
+
changedArtifacts: boolean;
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
export type DeveloperEvent = ModeEvent | RouteEvent | JudgmentEvent;
|
|
69
|
+
export type DeveloperEvent = ModeEvent | FocusEvent | RouteEvent | JudgmentEvent;
|
|
54
70
|
|
|
55
71
|
export interface DeveloperState {
|
|
56
72
|
mode: DeveloperMode;
|
|
@@ -58,19 +74,25 @@ export interface DeveloperState {
|
|
|
58
74
|
lastRoute?: RouteEvent;
|
|
59
75
|
lastJudgment?: JudgmentEvent;
|
|
60
76
|
pendingQuestions: PendingQuestion[];
|
|
77
|
+
focusedQuestionId?: string;
|
|
78
|
+
implementationFramingRequired: boolean;
|
|
79
|
+
verificationRequired: boolean;
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
export const initialState = (): DeveloperState => ({
|
|
64
83
|
mode: "off",
|
|
65
84
|
pendingQuestions: [],
|
|
85
|
+
implementationFramingRequired: false,
|
|
86
|
+
verificationRequired: false,
|
|
66
87
|
});
|
|
67
88
|
|
|
68
|
-
export type ProtocolState = "idle" | "needs-judgment" | "needs-evidence" | "blocked";
|
|
89
|
+
export type ProtocolState = "idle" | "needs-judgment" | "needs-evidence" | "needs-verification" | "blocked";
|
|
69
90
|
|
|
70
91
|
export function protocolState(state: DeveloperState): ProtocolState {
|
|
71
92
|
if (state.activeRoute) return "needs-judgment";
|
|
72
93
|
if (state.pendingQuestions.some((question) => question.status === "blocked")) return "blocked";
|
|
73
94
|
if (state.pendingQuestions.length > 0) return "needs-evidence";
|
|
95
|
+
if (state.verificationRequired) return "needs-verification";
|
|
74
96
|
return "idle";
|
|
75
97
|
}
|
|
76
98
|
|
|
@@ -84,12 +106,19 @@ export function applyDeveloperEvent(state: DeveloperState, event: DeveloperEvent
|
|
|
84
106
|
return { ...state, mode: event.mode };
|
|
85
107
|
}
|
|
86
108
|
|
|
109
|
+
if (event.kind === "focus") {
|
|
110
|
+
if (!state.pendingQuestions.some((question) => question.id === event.questionId)) return state;
|
|
111
|
+
return { ...state, focusedQuestionId: event.questionId };
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
if (event.kind === "route") {
|
|
88
115
|
if (state.activeRoute) return state;
|
|
89
116
|
return {
|
|
90
117
|
...state,
|
|
91
118
|
activeRoute: event,
|
|
92
119
|
lastRoute: event,
|
|
120
|
+
focusedQuestionId:
|
|
121
|
+
event.targetQuestionId === state.focusedQuestionId ? undefined : state.focusedQuestionId,
|
|
93
122
|
};
|
|
94
123
|
}
|
|
95
124
|
|
|
@@ -106,7 +135,7 @@ export function applyDeveloperEvent(state: DeveloperState, event: DeveloperEvent
|
|
|
106
135
|
pending = upsertQuestion(pending, {
|
|
107
136
|
id: route.targetQuestionId,
|
|
108
137
|
question: route.question,
|
|
109
|
-
status: event.status,
|
|
138
|
+
status: event.status === "blocked" ? "blocked" : "needs-evidence",
|
|
110
139
|
sourceRouteId: route.routeId,
|
|
111
140
|
});
|
|
112
141
|
}
|
|
@@ -114,18 +143,35 @@ export function applyDeveloperEvent(state: DeveloperState, event: DeveloperEvent
|
|
|
114
143
|
pending = upsertQuestion(pending, {
|
|
115
144
|
id: `question:${route.routeId}`,
|
|
116
145
|
question: route.question,
|
|
117
|
-
status: event.status,
|
|
146
|
+
status: event.status === "blocked" ? "blocked" : "needs-evidence",
|
|
118
147
|
sourceRouteId: route.routeId,
|
|
119
148
|
});
|
|
120
149
|
}
|
|
121
150
|
|
|
122
151
|
for (const question of event.openedQuestions) pending = upsertQuestion(pending, question);
|
|
123
152
|
|
|
153
|
+
const closesFramingGate =
|
|
154
|
+
(route.owner === "sketch" || route.owner === "signal") &&
|
|
155
|
+
(event.status === "resolved" || event.status === "not-applicable");
|
|
156
|
+
const opensFramingGate = route.owner === "model" && event.status === "resolved";
|
|
157
|
+
let implementationFramingRequired = state.implementationFramingRequired;
|
|
158
|
+
if (opensFramingGate) implementationFramingRequired = true;
|
|
159
|
+
if (closesFramingGate) implementationFramingRequired = false;
|
|
160
|
+
|
|
161
|
+
let verificationRequired = state.verificationRequired;
|
|
162
|
+
if (route.owner === "direct" && event.changedArtifacts) verificationRequired = true;
|
|
163
|
+
if (route.owner === "verify" && event.status === "resolved") verificationRequired = false;
|
|
164
|
+
|
|
124
165
|
return {
|
|
125
166
|
...state,
|
|
126
167
|
activeRoute: undefined,
|
|
127
168
|
lastJudgment: judgment,
|
|
128
169
|
pendingQuestions: pending,
|
|
170
|
+
focusedQuestionId: pending.some((question) => question.id === state.focusedQuestionId)
|
|
171
|
+
? state.focusedQuestionId
|
|
172
|
+
: undefined,
|
|
173
|
+
implementationFramingRequired,
|
|
174
|
+
verificationRequired,
|
|
129
175
|
};
|
|
130
176
|
}
|
|
131
177
|
|
|
@@ -149,6 +195,22 @@ function isDirectExecutionProfile(value: unknown): value is DirectExecutionProfi
|
|
|
149
195
|
return value === "ordinary" || value === "behavior-preserving-structure";
|
|
150
196
|
}
|
|
151
197
|
|
|
198
|
+
function parseDirectStep(value: unknown): DirectStepContract | undefined {
|
|
199
|
+
if (!isObject(value)) return undefined;
|
|
200
|
+
if (
|
|
201
|
+
typeof value.movement !== "string" ||
|
|
202
|
+
typeof value.stopCondition !== "string" ||
|
|
203
|
+
typeof value.verification !== "string"
|
|
204
|
+
) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
movement: value.movement,
|
|
209
|
+
stopCondition: value.stopCondition,
|
|
210
|
+
verification: value.verification,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
152
214
|
function parsePendingQuestion(value: unknown): PendingQuestion | undefined {
|
|
153
215
|
if (!isObject(value)) return undefined;
|
|
154
216
|
if (
|
|
@@ -169,13 +231,22 @@ function parsePendingQuestion(value: unknown): PendingQuestion | undefined {
|
|
|
169
231
|
|
|
170
232
|
export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefined {
|
|
171
233
|
if (!isObject(value)) return undefined;
|
|
172
|
-
if (
|
|
234
|
+
if (
|
|
235
|
+
value.protocol !== PROTOCOL &&
|
|
236
|
+
value.protocol !== PREVIOUS_PROTOCOL &&
|
|
237
|
+
value.protocol !== LEGACY_PROTOCOL
|
|
238
|
+
) return undefined;
|
|
173
239
|
|
|
174
240
|
if (value.kind === "mode") {
|
|
175
241
|
if (!isMode(value.mode)) return undefined;
|
|
176
242
|
return { protocol: PROTOCOL, kind: "mode", mode: value.mode };
|
|
177
243
|
}
|
|
178
244
|
|
|
245
|
+
if (value.kind === "focus") {
|
|
246
|
+
if (value.protocol !== PROTOCOL || typeof value.questionId !== "string") return undefined;
|
|
247
|
+
return { protocol: PROTOCOL, kind: "focus", questionId: value.questionId };
|
|
248
|
+
}
|
|
249
|
+
|
|
179
250
|
if (value.kind === "route") {
|
|
180
251
|
if (
|
|
181
252
|
typeof value.routeId !== "string" ||
|
|
@@ -185,7 +256,8 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
185
256
|
!isStringArray(value.knownEvidence) ||
|
|
186
257
|
(value.targetQuestionId !== undefined && typeof value.targetQuestionId !== "string") ||
|
|
187
258
|
(value.methodLocation !== undefined && typeof value.methodLocation !== "string") ||
|
|
188
|
-
(value.executionProfile !== undefined && !isDirectExecutionProfile(value.executionProfile))
|
|
259
|
+
(value.executionProfile !== undefined && !isDirectExecutionProfile(value.executionProfile)) ||
|
|
260
|
+
(value.directStep !== undefined && !parseDirectStep(value.directStep))
|
|
189
261
|
) {
|
|
190
262
|
return undefined;
|
|
191
263
|
}
|
|
@@ -200,6 +272,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
200
272
|
targetQuestionId: value.targetQuestionId,
|
|
201
273
|
methodLocation: value.methodLocation,
|
|
202
274
|
executionProfile: value.executionProfile,
|
|
275
|
+
directStep: value.directStep === undefined ? undefined : parseDirectStep(value.directStep),
|
|
203
276
|
};
|
|
204
277
|
}
|
|
205
278
|
|
|
@@ -231,9 +304,10 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
231
304
|
id: `question:${value.routeId}:legacy:${index + 1}`,
|
|
232
305
|
question,
|
|
233
306
|
status: "needs-evidence",
|
|
234
|
-
sourceRouteId: value.routeId,
|
|
307
|
+
sourceRouteId: String(value.routeId),
|
|
235
308
|
})),
|
|
236
309
|
artifacts: value.artifacts,
|
|
310
|
+
changedArtifacts: false,
|
|
237
311
|
};
|
|
238
312
|
}
|
|
239
313
|
|
|
@@ -251,6 +325,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
251
325
|
basis: value.basis,
|
|
252
326
|
openedQuestions: openedQuestions as PendingQuestion[],
|
|
253
327
|
artifacts: value.artifacts,
|
|
328
|
+
changedArtifacts: typeof value.changedArtifacts === "boolean" ? value.changedArtifacts : false,
|
|
254
329
|
};
|
|
255
330
|
}
|
|
256
331
|
|
|
@@ -261,7 +336,7 @@ interface BranchEntryLike {
|
|
|
261
336
|
message?: { role?: string; toolName?: string; details?: unknown };
|
|
262
337
|
}
|
|
263
338
|
|
|
264
|
-
const DEVELOPER_TOOL_NAMES = new Set([
|
|
339
|
+
const DEVELOPER_TOOL_NAMES = new Set<string>([
|
|
265
340
|
ROUTE_TOOL,
|
|
266
341
|
JUDGMENT_TOOL,
|
|
267
342
|
LEGACY_ROUTE_TOOL,
|
|
@@ -269,7 +344,10 @@ const DEVELOPER_TOOL_NAMES = new Set([
|
|
|
269
344
|
]);
|
|
270
345
|
|
|
271
346
|
export function eventFromBranchEntry(entry: BranchEntryLike): DeveloperEvent | undefined {
|
|
272
|
-
if (
|
|
347
|
+
if (
|
|
348
|
+
entry.type === "custom" &&
|
|
349
|
+
(entry.customType === MODE_ENTRY || entry.customType === FOCUS_ENTRY)
|
|
350
|
+
) {
|
|
273
351
|
return normalizeDeveloperEvent(entry.data);
|
|
274
352
|
}
|
|
275
353
|
if (
|
package/extensions/tui.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DynamicBorder,
|
|
3
2
|
type ExtensionCommandContext,
|
|
3
|
+
type KeybindingsManager,
|
|
4
4
|
type Theme,
|
|
5
5
|
type ThemeColor,
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Container,
|
|
9
9
|
matchesKey,
|
|
10
10
|
type SelectItem,
|
|
11
|
-
SelectList,
|
|
12
11
|
Text,
|
|
13
12
|
truncateToWidth,
|
|
14
13
|
visibleWidth,
|
|
@@ -32,7 +31,7 @@ function modeName(mode: DeveloperMode): string {
|
|
|
32
31
|
|
|
33
32
|
function protocolColor(value: ProtocolState): ThemeColor {
|
|
34
33
|
if (value === "blocked") return "error";
|
|
35
|
-
if (value === "needs-evidence") return "warning";
|
|
34
|
+
if (value === "needs-evidence" || value === "needs-verification") return "warning";
|
|
36
35
|
if (value === "needs-judgment") return "accent";
|
|
37
36
|
return "dim";
|
|
38
37
|
}
|
|
@@ -96,7 +95,7 @@ export function pendingQuestionItems(questions: PendingQuestion[]): SelectItem[]
|
|
|
96
95
|
return questions.map((question) => ({
|
|
97
96
|
value: question.id,
|
|
98
97
|
label: question.question,
|
|
99
|
-
description:
|
|
98
|
+
description: question.status,
|
|
100
99
|
}));
|
|
101
100
|
}
|
|
102
101
|
|
|
@@ -106,7 +105,124 @@ interface SelectDialogOptions {
|
|
|
106
105
|
items: SelectItem[];
|
|
107
106
|
width: number;
|
|
108
107
|
maxVisible: number;
|
|
109
|
-
|
|
108
|
+
selectedLabelMaxLines: number;
|
|
109
|
+
selectedDescriptionMaxLines: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function boundedWrappedLines(content: string, width: number, maxLines: number, ellipsis: string): string[] {
|
|
113
|
+
const contentWidth = Math.max(1, width);
|
|
114
|
+
const wrapped = wrapTextWithAnsi(content, contentWidth);
|
|
115
|
+
const visible = wrapped.slice(0, Math.max(1, maxLines));
|
|
116
|
+
if (wrapped.length > visible.length && visible.length > 0) {
|
|
117
|
+
const last = visible.length - 1;
|
|
118
|
+
visible[last] = truncateToWidth(visible[last] ?? "", Math.max(1, contentWidth - 1), "") + ellipsis;
|
|
119
|
+
}
|
|
120
|
+
return visible;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderModalFrame(container: Container, theme: Theme, width: number): string[] {
|
|
124
|
+
if (width < 3) return container.render(Math.max(1, width));
|
|
125
|
+
|
|
126
|
+
const innerWidth = width - 2;
|
|
127
|
+
const border = (text: string) => theme.fg("borderAccent", text);
|
|
128
|
+
const rows = container
|
|
129
|
+
.render(innerWidth)
|
|
130
|
+
.map((line) => `${border("│")}${truncateToWidth(line, innerWidth, "…", true)}${border("│")}`);
|
|
131
|
+
return [border(`╭${"─".repeat(innerWidth)}╮`), ...rows, border(`╰${"─".repeat(innerWidth)}╯`)];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class WrappedSelectList {
|
|
135
|
+
private selectedIndex = 0;
|
|
136
|
+
private readonly items: SelectItem[];
|
|
137
|
+
private readonly keybindings: KeybindingsManager;
|
|
138
|
+
private readonly maxVisible: number;
|
|
139
|
+
private readonly selectedDescriptionMaxLines: number;
|
|
140
|
+
private readonly selectedLabelMaxLines: number;
|
|
141
|
+
private readonly theme: Theme;
|
|
142
|
+
onSelect?: (item: SelectItem) => void;
|
|
143
|
+
onCancel?: () => void;
|
|
144
|
+
|
|
145
|
+
constructor(
|
|
146
|
+
items: SelectItem[],
|
|
147
|
+
maxVisible: number,
|
|
148
|
+
theme: Theme,
|
|
149
|
+
keybindings: KeybindingsManager,
|
|
150
|
+
options: {
|
|
151
|
+
selectedLabelMaxLines: number;
|
|
152
|
+
selectedDescriptionMaxLines: number;
|
|
153
|
+
},
|
|
154
|
+
) {
|
|
155
|
+
this.items = items;
|
|
156
|
+
this.maxVisible = maxVisible;
|
|
157
|
+
this.theme = theme;
|
|
158
|
+
this.keybindings = keybindings;
|
|
159
|
+
this.selectedLabelMaxLines = options.selectedLabelMaxLines;
|
|
160
|
+
this.selectedDescriptionMaxLines = options.selectedDescriptionMaxLines;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
render(width: number): string[] {
|
|
164
|
+
if (this.items.length === 0) return [this.theme.fg("warning", " No items")];
|
|
165
|
+
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
const startIndex = Math.max(
|
|
168
|
+
0,
|
|
169
|
+
Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.items.length - this.maxVisible),
|
|
170
|
+
);
|
|
171
|
+
const endIndex = Math.min(startIndex + this.maxVisible, this.items.length);
|
|
172
|
+
|
|
173
|
+
for (let index = startIndex; index < endIndex; index += 1) {
|
|
174
|
+
const item = this.items[index];
|
|
175
|
+
if (!item) continue;
|
|
176
|
+
if (index !== this.selectedIndex) {
|
|
177
|
+
lines.push(` ${truncateToWidth(item.label, Math.max(1, width - 2), "…")}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const labels = boundedWrappedLines(
|
|
182
|
+
this.theme.fg("accent", item.label),
|
|
183
|
+
width - 2,
|
|
184
|
+
this.selectedLabelMaxLines,
|
|
185
|
+
this.theme.fg("dim", "…"),
|
|
186
|
+
);
|
|
187
|
+
lines.push(`${this.theme.fg("accent", "→ ")}${labels[0] ?? ""}`);
|
|
188
|
+
for (const line of labels.slice(1)) lines.push(` ${line}`);
|
|
189
|
+
|
|
190
|
+
if (item.description) {
|
|
191
|
+
const descriptions = boundedWrappedLines(
|
|
192
|
+
this.theme.fg("muted", item.description),
|
|
193
|
+
width - 4,
|
|
194
|
+
this.selectedDescriptionMaxLines,
|
|
195
|
+
this.theme.fg("dim", "…"),
|
|
196
|
+
);
|
|
197
|
+
for (const line of descriptions) lines.push(` ${line}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (startIndex > 0 || endIndex < this.items.length) {
|
|
202
|
+
lines.push(
|
|
203
|
+
this.theme.fg(
|
|
204
|
+
"dim",
|
|
205
|
+
truncateToWidth(` (${this.selectedIndex + 1}/${this.items.length})`, Math.max(1, width - 2), ""),
|
|
206
|
+
),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return lines;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
handleInput(data: string): void {
|
|
213
|
+
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
214
|
+
this.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;
|
|
215
|
+
} else if (this.keybindings.matches(data, "tui.select.down")) {
|
|
216
|
+
this.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;
|
|
217
|
+
} else if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
218
|
+
const selected = this.items[this.selectedIndex];
|
|
219
|
+
if (selected) this.onSelect?.(selected);
|
|
220
|
+
} else if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
221
|
+
this.onCancel?.();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
invalidate(): void {}
|
|
110
226
|
}
|
|
111
227
|
|
|
112
228
|
async function showSelectDialog(
|
|
@@ -114,45 +230,39 @@ async function showSelectDialog(
|
|
|
114
230
|
options: SelectDialogOptions,
|
|
115
231
|
): Promise<string | undefined> {
|
|
116
232
|
const result = await ctx.ui.custom<string | null>(
|
|
117
|
-
(tui, theme,
|
|
233
|
+
(tui, theme, keybindings, done) => {
|
|
118
234
|
const container = new Container();
|
|
119
235
|
const title = new Text("", 1, 0);
|
|
120
236
|
const subtitle = new Text("", 1, 0);
|
|
121
237
|
const hint = new Text("", 1, 0);
|
|
122
238
|
const updateText = () => {
|
|
123
|
-
title.setText(theme.fg("accent", theme.bold(options.title)));
|
|
239
|
+
title.setText(theme.fg("accent", theme.bold(`◆ ${options.title}`)));
|
|
124
240
|
subtitle.setText(theme.fg("muted", options.subtitle));
|
|
125
241
|
hint.setText(theme.fg("dim", "↑↓ navigate · enter select · esc cancel"));
|
|
126
242
|
};
|
|
127
243
|
updateText();
|
|
128
244
|
|
|
129
|
-
const list = new
|
|
245
|
+
const list = new WrappedSelectList(
|
|
130
246
|
options.items,
|
|
131
247
|
Math.min(options.items.length, options.maxVisible),
|
|
248
|
+
theme,
|
|
249
|
+
keybindings,
|
|
132
250
|
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
description: (text) => theme.fg("muted", text),
|
|
136
|
-
scrollInfo: (text) => theme.fg("dim", text),
|
|
137
|
-
noMatch: (text) => theme.fg("warning", text),
|
|
251
|
+
selectedLabelMaxLines: options.selectedLabelMaxLines,
|
|
252
|
+
selectedDescriptionMaxLines: options.selectedDescriptionMaxLines,
|
|
138
253
|
},
|
|
139
|
-
options.maxPrimaryColumnWidth
|
|
140
|
-
? { minPrimaryColumnWidth: 24, maxPrimaryColumnWidth: options.maxPrimaryColumnWidth }
|
|
141
|
-
: undefined,
|
|
142
254
|
);
|
|
143
255
|
list.onSelect = (item) => done(item.value);
|
|
144
256
|
list.onCancel = () => done(null);
|
|
145
257
|
|
|
146
|
-
container.addChild(new DynamicBorder((text) => theme.fg("borderAccent", text)));
|
|
147
258
|
container.addChild(title);
|
|
148
259
|
container.addChild(subtitle);
|
|
149
260
|
container.addChild(list);
|
|
150
261
|
container.addChild(hint);
|
|
151
|
-
container.addChild(new DynamicBorder((text) => theme.fg("borderAccent", text)));
|
|
152
262
|
|
|
153
263
|
return {
|
|
154
264
|
render(width: number) {
|
|
155
|
-
return container
|
|
265
|
+
return renderModalFrame(container, theme, width);
|
|
156
266
|
},
|
|
157
267
|
invalidate() {
|
|
158
268
|
updateText();
|
|
@@ -169,7 +279,10 @@ async function showSelectDialog(
|
|
|
169
279
|
overlayOptions: {
|
|
170
280
|
anchor: "center",
|
|
171
281
|
width: options.width,
|
|
172
|
-
maxHeight: Math.min(
|
|
282
|
+
maxHeight: Math.min(
|
|
283
|
+
options.maxVisible + options.selectedLabelMaxLines + options.selectedDescriptionMaxLines + 7,
|
|
284
|
+
24,
|
|
285
|
+
),
|
|
173
286
|
margin: 1,
|
|
174
287
|
},
|
|
175
288
|
},
|
|
@@ -187,6 +300,8 @@ export async function showDeveloperActionSelector(
|
|
|
187
300
|
items: developerActionItems(state),
|
|
188
301
|
width: 78,
|
|
189
302
|
maxVisible: 6,
|
|
303
|
+
selectedLabelMaxLines: 2,
|
|
304
|
+
selectedDescriptionMaxLines: 2,
|
|
190
305
|
});
|
|
191
306
|
if (result === "status" || result === "questions" || result === "on" || result === "strict" || result === "off") {
|
|
192
307
|
return result;
|
|
@@ -205,7 +320,8 @@ export async function showPendingQuestionSelector(
|
|
|
205
320
|
items: pendingQuestionItems(questions),
|
|
206
321
|
width: 96,
|
|
207
322
|
maxVisible: 10,
|
|
208
|
-
|
|
323
|
+
selectedLabelMaxLines: 5,
|
|
324
|
+
selectedDescriptionMaxLines: 1,
|
|
209
325
|
});
|
|
210
326
|
}
|
|
211
327
|
|
|
@@ -241,6 +357,12 @@ export class DeveloperWidget {
|
|
|
241
357
|
if (this.state.pendingQuestions.length > 3) {
|
|
242
358
|
lines.push(this.theme.fg("dim", ` +${this.state.pendingQuestions.length - 3} more open questions`));
|
|
243
359
|
}
|
|
360
|
+
if (this.state.implementationFramingRequired) {
|
|
361
|
+
lines.push(this.theme.fg("warning", "→ next · sketch feature shape or signal structural movement"));
|
|
362
|
+
}
|
|
363
|
+
if (this.state.verificationRequired) {
|
|
364
|
+
lines.push(this.theme.fg("warning", "→ next · verify changed artifacts before completion"));
|
|
365
|
+
}
|
|
244
366
|
return lines;
|
|
245
367
|
}
|
|
246
368
|
|
|
@@ -259,91 +381,123 @@ export class DeveloperStatusPanel {
|
|
|
259
381
|
private readonly view: DeveloperStatusView;
|
|
260
382
|
private readonly theme: Theme;
|
|
261
383
|
private readonly onClose: () => void;
|
|
262
|
-
|
|
263
|
-
|
|
384
|
+
private readonly requestRender: () => void;
|
|
385
|
+
private readonly viewportHeight: number;
|
|
386
|
+
private scrollOffset = 0;
|
|
387
|
+
private maxScrollOffset = 0;
|
|
388
|
+
|
|
389
|
+
constructor(
|
|
390
|
+
view: DeveloperStatusView,
|
|
391
|
+
theme: Theme,
|
|
392
|
+
onClose: () => void,
|
|
393
|
+
options: { viewportHeight?: number; requestRender?: () => void } = {},
|
|
394
|
+
) {
|
|
264
395
|
this.view = view;
|
|
265
396
|
this.theme = theme;
|
|
266
397
|
this.onClose = onClose;
|
|
398
|
+
this.viewportHeight = options.viewportHeight ?? 24;
|
|
399
|
+
this.requestRender = options.requestRender ?? (() => {});
|
|
267
400
|
}
|
|
268
401
|
|
|
269
402
|
handleInput(data: string): void {
|
|
270
403
|
if (matchesKey(data, "escape") || matchesKey(data, "enter") || matchesKey(data, "ctrl+c")) {
|
|
271
404
|
this.onClose();
|
|
405
|
+
return;
|
|
272
406
|
}
|
|
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;
|
|
411
|
+
this.invalidate();
|
|
412
|
+
this.requestRender();
|
|
273
413
|
}
|
|
274
414
|
|
|
275
415
|
render(width: number): string[] {
|
|
276
416
|
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
277
417
|
|
|
278
|
-
const panelWidth = Math.max(
|
|
279
|
-
const innerWidth = Math.max(
|
|
418
|
+
const panelWidth = Math.max(1, width);
|
|
419
|
+
const innerWidth = Math.max(1, panelWidth - 2);
|
|
280
420
|
const rows: string[] = [];
|
|
281
|
-
const border = (text: string) => this.theme.fg("
|
|
421
|
+
const border = (text: string) => this.theme.fg("borderAccent", text);
|
|
282
422
|
const row = (content = "") => `${border("│")}${truncateToWidth(content, innerWidth, "…", true)}${border("│")}`;
|
|
283
423
|
const addWrapped = (label: string, value: string, color: ThemeColor = "muted", maxLines = 2) => {
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
|
|
424
|
+
const labelText = `${label} ·`;
|
|
425
|
+
const plainPrefix = ` ${labelText} `;
|
|
426
|
+
const styledPrefix = ` ${this.theme.fg("dim", labelText)} `;
|
|
427
|
+
const contentWidth = Math.max(1, innerWidth - visibleWidth(plainPrefix));
|
|
428
|
+
const wrapped = wrapTextWithAnsi(this.theme.fg(color, value.trim()), contentWidth);
|
|
429
|
+
const visible = wrapped.slice(0, Math.max(1, maxLines));
|
|
430
|
+
if (wrapped.length > visible.length && visible.length > 0) {
|
|
431
|
+
const last = visible.length - 1;
|
|
432
|
+
visible[last] =
|
|
433
|
+
truncateToWidth(visible[last] ?? "", Math.max(1, contentWidth - 1), "") + this.theme.fg("dim", "…");
|
|
434
|
+
}
|
|
435
|
+
rows.push(row(styledPrefix + (visible[0] ?? "")));
|
|
436
|
+
const hangingIndent = " ".repeat(visibleWidth(plainPrefix));
|
|
437
|
+
for (const line of visible.slice(1)) rows.push(row(hangingIndent + line));
|
|
287
438
|
};
|
|
288
439
|
const section = (title: string) => rows.push(row(` ${this.theme.fg("accent", this.theme.bold(title))}`));
|
|
289
440
|
|
|
290
441
|
rows.push(border(`╭${"─".repeat(innerWidth)}╮`));
|
|
291
|
-
rows.push(row(` ${this.theme.fg("accent", this.theme.bold("Developer status"))}`));
|
|
442
|
+
rows.push(row(` ${this.theme.fg("accent", this.theme.bold("◆ Developer status"))}`));
|
|
292
443
|
rows.push(row());
|
|
293
444
|
|
|
294
445
|
const state = this.view.state;
|
|
295
446
|
const currentProtocol = protocolState(state);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
),
|
|
304
|
-
);
|
|
447
|
+
const summary =
|
|
448
|
+
`mode ${this.theme.fg(modeColor(state.mode), modeName(state.mode))}` +
|
|
449
|
+
this.theme.fg("dim", " · ") +
|
|
450
|
+
`protocol ${this.theme.fg(protocolColor(currentProtocol), currentProtocol)}` +
|
|
451
|
+
this.theme.fg("dim", " · ") +
|
|
452
|
+
`target ${this.theme.fg("muted", state.activeRoute?.owner ?? "none")}`;
|
|
453
|
+
for (const line of wrapTextWithAnsi(summary, Math.max(1, innerWidth - 2))) rows.push(row(` ${line}`));
|
|
305
454
|
rows.push(row());
|
|
306
455
|
|
|
307
456
|
section("Active route");
|
|
308
457
|
if (state.activeRoute) {
|
|
309
458
|
addWrapped("id", state.activeRoute.routeId, "dim", 1);
|
|
310
|
-
addWrapped("question", state.activeRoute.question, "text");
|
|
311
|
-
addWrapped("reason", state.activeRoute.reason);
|
|
459
|
+
addWrapped("question", state.activeRoute.question, "text", 3);
|
|
460
|
+
addWrapped("reason", state.activeRoute.reason, "muted", 2);
|
|
312
461
|
addWrapped("skill", state.activeRoute.methodLocation ?? "direct action", "dim", 1);
|
|
313
462
|
addWrapped("known evidence", String(state.activeRoute.knownEvidence.length), "muted", 1);
|
|
314
463
|
} else {
|
|
315
|
-
addWrapped("state", "No route is currently waiting for judgment.", "dim",
|
|
464
|
+
addWrapped("state", "No route is currently waiting for judgment.", "dim", 2);
|
|
316
465
|
}
|
|
317
466
|
|
|
318
467
|
rows.push(row());
|
|
319
468
|
section(`Open questions · ${state.pendingQuestions.length}`);
|
|
320
469
|
if (state.pendingQuestions.length === 0) {
|
|
321
|
-
addWrapped("state", "No unresolved Developer questions.", "dim",
|
|
470
|
+
addWrapped("state", "No unresolved Developer questions.", "dim", 2);
|
|
322
471
|
} else {
|
|
323
472
|
for (const question of state.pendingQuestions.slice(0, 4)) {
|
|
324
473
|
addWrapped(
|
|
325
474
|
question.status === "blocked" ? "blocked" : "needs evidence",
|
|
326
475
|
question.question,
|
|
327
476
|
question.status === "blocked" ? "error" : "warning",
|
|
328
|
-
|
|
477
|
+
2,
|
|
329
478
|
);
|
|
330
479
|
}
|
|
331
480
|
if (state.pendingQuestions.length > 4) {
|
|
332
|
-
addWrapped("more",
|
|
481
|
+
addWrapped("more", `${state.pendingQuestions.length - 4} additional open questions`, "dim", 1);
|
|
333
482
|
}
|
|
334
483
|
}
|
|
335
484
|
|
|
336
485
|
rows.push(row());
|
|
337
486
|
section("Last judgment");
|
|
338
487
|
if (state.lastJudgment) {
|
|
339
|
-
addWrapped(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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);
|
|
347
501
|
addWrapped(
|
|
348
502
|
"evidence",
|
|
349
503
|
`${state.lastJudgment.basis.length} basis · ${state.lastJudgment.artifacts.length} artifacts`,
|
|
@@ -351,7 +505,7 @@ export class DeveloperStatusPanel {
|
|
|
351
505
|
1,
|
|
352
506
|
);
|
|
353
507
|
} else {
|
|
354
|
-
addWrapped("state", "No judgment has been recorded on this branch.", "dim",
|
|
508
|
+
addWrapped("state", "No judgment has been recorded on this branch.", "dim", 2);
|
|
355
509
|
}
|
|
356
510
|
|
|
357
511
|
rows.push(row());
|
|
@@ -362,12 +516,27 @@ export class DeveloperStatusPanel {
|
|
|
362
516
|
1,
|
|
363
517
|
);
|
|
364
518
|
rows.push(row());
|
|
365
|
-
|
|
366
|
-
rows.
|
|
519
|
+
|
|
520
|
+
const header = rows.slice(0, 2);
|
|
521
|
+
const body = rows.slice(2);
|
|
522
|
+
const bodyCapacity = Math.max(1, this.viewportHeight - 4);
|
|
523
|
+
this.maxScrollOffset = Math.max(0, body.length - bodyCapacity);
|
|
524
|
+
this.scrollOffset = Math.min(this.scrollOffset, this.maxScrollOffset);
|
|
525
|
+
const visibleBody = body.slice(this.scrollOffset, this.scrollOffset + bodyCapacity);
|
|
526
|
+
const position =
|
|
527
|
+
body.length > bodyCapacity
|
|
528
|
+
? `↑↓ scroll · ${this.scrollOffset + 1}–${Math.min(body.length, this.scrollOffset + bodyCapacity)}/${body.length} · enter/esc close`
|
|
529
|
+
: "enter/esc close · /develop questions revisits open work";
|
|
530
|
+
const visibleRows = [
|
|
531
|
+
...header,
|
|
532
|
+
...visibleBody,
|
|
533
|
+
row(` ${this.theme.fg("dim", position)}`),
|
|
534
|
+
border(`╰${"─".repeat(innerWidth)}╯`),
|
|
535
|
+
];
|
|
367
536
|
|
|
368
537
|
this.cachedWidth = width;
|
|
369
|
-
this.cachedLines =
|
|
370
|
-
return
|
|
538
|
+
this.cachedLines = visibleRows;
|
|
539
|
+
return visibleRows;
|
|
371
540
|
}
|
|
372
541
|
|
|
373
542
|
invalidate(): void {
|
|
@@ -376,16 +545,28 @@ export class DeveloperStatusPanel {
|
|
|
376
545
|
}
|
|
377
546
|
}
|
|
378
547
|
|
|
379
|
-
export async function showDeveloperStatus(
|
|
380
|
-
ctx
|
|
381
|
-
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
|
|
548
|
+
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,
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
);
|
|
385
566
|
}
|
|
386
567
|
|
|
387
568
|
export function prepareQuestionPrompt(ctx: ExtensionCommandContext, question: PendingQuestion): void {
|
|
388
|
-
const prompt = `Revisit Developer question
|
|
569
|
+
const prompt = `Revisit this Developer question: ${question.question}`;
|
|
389
570
|
const current = ctx.ui.getEditorText();
|
|
390
571
|
ctx.ui.setEditorText(current.trim() ? `${current.trimEnd()}\n\n${prompt}` : prompt);
|
|
391
572
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hobin/developer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Adaptive product-development reasoning and evidence for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"pi-package",
|
|
8
|
-
"pi",
|
|
9
|
-
"developer",
|
|
10
|
-
"skills"
|
|
11
|
-
],
|
|
6
|
+
"keywords": ["pi-package", "pi", "developer", "skills"],
|
|
12
7
|
"license": "MIT",
|
|
13
8
|
"author": {
|
|
14
9
|
"name": "dev-hobin",
|
|
@@ -26,33 +21,23 @@
|
|
|
26
21
|
"publishConfig": {
|
|
27
22
|
"access": "public"
|
|
28
23
|
},
|
|
29
|
-
"files": [
|
|
30
|
-
"extensions",
|
|
31
|
-
"skills",
|
|
32
|
-
"README.md",
|
|
33
|
-
"SOURCES.md",
|
|
34
|
-
"LICENSE"
|
|
35
|
-
],
|
|
24
|
+
"files": ["extensions", "skills", "README.md", "SOURCES.md", "LICENSE"],
|
|
36
25
|
"engines": {
|
|
37
26
|
"node": ">=22.19.0"
|
|
38
27
|
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"check": "node scripts/check-package.mjs && node --test tests/*.test.ts",
|
|
30
|
+
"eval": "node scripts/eval-rpc.mjs",
|
|
31
|
+
"eval:json": "node scripts/eval-json.mjs"
|
|
32
|
+
},
|
|
39
33
|
"pi": {
|
|
40
|
-
"extensions": [
|
|
41
|
-
|
|
42
|
-
],
|
|
43
|
-
"skills": [
|
|
44
|
-
"./skills"
|
|
45
|
-
]
|
|
34
|
+
"extensions": ["./extensions/developer.ts"],
|
|
35
|
+
"skills": ["./skills"]
|
|
46
36
|
},
|
|
47
37
|
"peerDependencies": {
|
|
48
38
|
"@earendil-works/pi-ai": "*",
|
|
49
39
|
"@earendil-works/pi-coding-agent": "*",
|
|
50
40
|
"@earendil-works/pi-tui": "*",
|
|
51
41
|
"typebox": "*"
|
|
52
|
-
},
|
|
53
|
-
"scripts": {
|
|
54
|
-
"check": "node scripts/check-package.mjs && node --test tests/*.test.ts",
|
|
55
|
-
"eval": "node scripts/eval-rpc.mjs",
|
|
56
|
-
"eval:json": "node scripts/eval-json.mjs"
|
|
57
42
|
}
|
|
58
|
-
}
|
|
43
|
+
}
|