@mgiles/perk 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +68 -44
  2. package/extension/adapters/planAdapterPlannotator.ts +27 -41
  3. package/extension/adapters/planAdapterTombell.ts +15 -28
  4. package/extension/adapters/todoAdapterJuicesharp.ts +10 -13
  5. package/extension/checkpoints/checkpoints.ts +19 -12
  6. package/extension/doors/address.ts +4 -4
  7. package/extension/doors/askUser.ts +12 -8
  8. package/extension/doors/ciExecutor.ts +21 -14
  9. package/extension/doors/hunkHandoff.ts +202 -0
  10. package/extension/doors/land.ts +31 -9
  11. package/extension/doors/learn.ts +2 -2
  12. package/extension/doors/learnFactory.ts +144 -0
  13. package/extension/doors/plannotatorHandoff.ts +509 -0
  14. package/extension/doors/prReview.ts +4 -4
  15. package/extension/doors/prReviewBrowser.ts +341 -0
  16. package/extension/doors/prReviewTerminal.ts +267 -0
  17. package/extension/doors/selfcheck.ts +238 -5
  18. package/extension/doors/submit.ts +20 -0
  19. package/extension/doors/submitPrReview.ts +408 -0
  20. package/extension/factories/objective.ts +15 -5
  21. package/extension/factories/objectiveAuthor.ts +15 -32
  22. package/extension/factories/objectiveDraft.ts +1 -1
  23. package/extension/factories/objectivePlan.ts +12 -10
  24. package/extension/factories/objectiveSave.ts +2 -2
  25. package/extension/factories/planMode.ts +22 -40
  26. package/extension/factories/planReview.ts +213 -191
  27. package/extension/factories/planSave.ts +7 -7
  28. package/extension/index.ts +83 -25
  29. package/extension/substrate/bindingDelivery.ts +32 -10
  30. package/extension/substrate/bindings.ts +4 -2
  31. package/extension/substrate/cache.ts +34 -7
  32. package/extension/substrate/clipboard.ts +81 -0
  33. package/extension/substrate/config.ts +88 -65
  34. package/extension/substrate/git.ts +43 -0
  35. package/extension/substrate/paths.ts +1 -1
  36. package/extension/substrate/prompts.ts +2 -2
  37. package/extension/substrate/providers.ts +62 -8
  38. package/extension/substrate/sessionPointers.ts +35 -6
  39. package/extension/substrate/structuredOutput.ts +3 -1
  40. package/extension/substrate/terminalLaunch.ts +178 -0
  41. package/extension/substrate/toolGating.ts +330 -79
  42. package/extension/substrate/toolParams.ts +7 -0
  43. package/extension/substrate/workflowState.ts +54 -2
  44. package/extension/surfaces/footerProvider.ts +8 -4
  45. package/extension/surfaces/surfaces.ts +330 -12
  46. package/extension/vendor/btw/btw.ts +10 -0
  47. package/extension/worker/readOnlySession.ts +19 -6
  48. package/extension/worker/worker.ts +77 -7
  49. package/extension/workerMain.ts +12 -13
  50. package/package.json +3 -3
  51. package/prompts/_fixtures/live.yaml +117 -2
  52. package/prompts/contexts/adapters/juicesharp-todo.md +7 -0
  53. package/prompts/contexts/adapters/plannotator-objective.md +7 -0
  54. package/prompts/contexts/adapters/plannotator-plan.md +6 -0
  55. package/prompts/contexts/adapters/tombell-plan.md +17 -0
  56. package/prompts/contexts/objective-authoring.md +20 -0
  57. package/prompts/contexts/plan-authoring.md +24 -0
  58. package/prompts/contexts/read-only.md +10 -0
  59. package/prompts/stages/conflict-resolution.md +1 -1
  60. package/prompts/stages/learn-code.md +1 -1
  61. package/prompts/stages/learn-docs.md +2 -2
  62. package/prompts/stages/learn-orchestrate.md +1 -1
  63. package/prompts/stages/objective-author/adopt.md +1 -1
  64. package/prompts/stages/objective-author/file.md +1 -1
  65. package/prompts/stages/objective-plan/guidance.md +1 -1
  66. package/prompts/stages/objective-plan/seed.md +1 -1
  67. package/prompts/stages/objective-reconcile.md +1 -1
  68. package/prompts/stages/objective-replan.md +1 -1
  69. package/prompts/stages/plan-from/adopt.md +2 -2
  70. package/prompts/stages/plan-from/file.md +2 -2
  71. package/prompts/stages/pr-review-browser/active.md +11 -0
  72. package/prompts/stages/pr-review-browser/foreign.md +11 -0
  73. package/prompts/stages/pr-review-terminal/active.md +12 -0
  74. package/prompts/stages/pr-review-terminal/foreign.md +13 -0
  75. package/prompts/stages/pr-review-terminal/local.md +4 -0
  76. package/prompts/stages/pr-review.md +1 -1
  77. package/prompts/stages/replan.md +2 -2
  78. package/prompts/stages/skills/create-from.md +1 -1
  79. package/prompts/stages/skills/create.md +1 -1
  80. package/prompts/stages/skills/refine.md +1 -1
  81. package/shared/README.md +22 -18
  82. package/shared/bindings.yaml +10 -2
  83. package/shared/contracts-history.md +24 -0
  84. package/shared/contracts.md +1442 -1787
  85. package/shared/providers.yaml +8 -1
  86. package/shared/registry.yaml +7 -8
  87. package/shared/schemas/inputs/review-submit-batch.schema.json +66 -0
  88. package/shared/schemas/outputs/pr-review-checkout.schema.json +69 -0
  89. package/shared/schemas/outputs/pr-review-cleanup.schema.json +54 -0
  90. package/shared/schemas/outputs/pr-review-submit.schema.json +64 -0
  91. package/extension/doors/learnCode.ts +0 -100
  92. package/extension/doors/learnDocs.ts +0 -100
  93. package/extension/doors/prReviewLocal.ts +0 -229
@@ -1,229 +0,0 @@
1
- // The warm `/pr-review-local` command: open the plannotator browser code-review UI on the active
2
- // worktree's PR, with the GitHub PR URL filled in IMPLICITLY (no copy-paste). The end result is
3
- // identical to plannotator's own `/plannotator-review <pr-url>`.
4
- //
5
- // pi exposes NO API for one extension to invoke another's slash command (`sendUserMessage` sends
6
- // text to the model; `steer`/`followUp` ERROR on slash commands). So perk cannot literally call
7
- // `/plannotator-review`. Instead it speaks plannotator's published `pi.events` API — a
8
- // `plannotator:request` with `action: "code-review"` and a `prUrl` payload — which opens the EXACT
9
- // same browser UI. Same in-process bus perk already uses for plan review (createPlannotatorBridge).
10
- //
11
- // A tiny read-only `perk pr url --json` cold door resolves the active PR's URL from the worktree's
12
- // plan-ref branch (GitHub resolution stays canonical in Python).
13
- //
14
- // EVENT ENVELOPE (pinned against `@plannotator/pi-extension@0.21.2`, `plannotator-events.ts`):
15
- // request — pi.events.emit("plannotator:request", { requestId, action: "code-review",
16
- // payload: { prUrl, cwd }, respond }) // respond = in-payload callback
17
- // reply — respond({ status: "handled", result: { approved, feedback?, annotations? } })
18
- // | respond({ status: "unavailable" | "error", error? })
19
- // Unlike plan-review there is NO handshake / no `reviewId` channel and no timeout: for code-review
20
- // plannotator `await openCodeReview(...)` then responds ONCE with the final result.
21
-
22
- import { randomUUID } from "node:crypto";
23
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
24
- import type { PlannotatorBus } from "../adapters/planAdapterPlannotator.ts";
25
- import {
26
- type ColdJson,
27
- numberField,
28
- objectField,
29
- runColdDoor,
30
- stringField,
31
- } from "../substrate/coldDoor.ts";
32
- import { registerPerkCommand } from "../substrate/command.ts";
33
- import { interceptConsoleError } from "../substrate/consoleCapture.ts";
34
- import { failFor } from "../substrate/result.ts";
35
- import { report } from "../surfaces/report.ts";
36
-
37
- /** Plannotator's code-review slash command — its presence detects the extension is loaded. */
38
- export const PLANNOTATOR_REVIEW_COMMAND = "plannotator-review";
39
-
40
- /**
41
- * The short, perk-authored triage suffix appended to feedback ONLY when the reviewer left
42
- * annotations — mirrors plannotator's own "address these notes" routing, but perk-worded.
43
- */
44
- const TRIAGE_SUFFIX =
45
- "\n\nTriage these review notes first: decide which are actionable, then address the actionable ones.";
46
-
47
- /** The outcome of a plannotator code-review request — a small local discriminated union. */
48
- export type CodeReviewOutcome =
49
- | {
50
- status: "handled";
51
- approved: boolean;
52
- feedback: string | undefined;
53
- annotationCount: number;
54
- }
55
- | { status: "unavailable" | "error"; warning: string }
56
- | { status: "aborted" };
57
-
58
- /**
59
- * Whether plannotator is loaded — detected by its `plannotator-review` command being registered
60
- * (independent of the selected plan provider; code review is orthogonal to plan-review selection).
61
- * `getCommands()` returns `SlashCommandInfo[]` whose `name` is the bare command name.
62
- */
63
- export function plannotatorPresent(pi: ExtensionAPI): boolean {
64
- return pi.getCommands().some((c) => c.name === PLANNOTATOR_REVIEW_COMMAND);
65
- }
66
-
67
- /** Plannotator's `respond(...)` reply for a code-review request (pinned envelope, see header). */
68
- interface CodeReviewResponse {
69
- status?: string;
70
- error?: string;
71
- result?: { approved?: unknown; feedback?: unknown; annotations?: unknown };
72
- }
73
-
74
- /**
75
- * The pure, offline-testable bridge: emit ONE `plannotator:request` with `action: "code-review"`
76
- * and resolve when plannotator calls `respond(...)`. No handshake / no timeout (plannotator awaits
77
- * `openCodeReview(...)` then responds once). Honors a turn abort. Pure over the bus → unit-testable
78
- * with a fake plannotator listener.
79
- */
80
- export async function requestPlannotatorCodeReview(
81
- bus: PlannotatorBus,
82
- opts: { prUrl: string; cwd: string; signal?: AbortSignal },
83
- ): Promise<CodeReviewOutcome> {
84
- if (opts.signal?.aborted) return { status: "aborted" };
85
-
86
- return await new Promise<CodeReviewOutcome>((resolve) => {
87
- let settled = false;
88
- const finish = (outcome: CodeReviewOutcome): void => {
89
- if (settled) return;
90
- settled = true;
91
- opts.signal?.removeEventListener("abort", onAbort);
92
- resolve(outcome);
93
- };
94
- const onAbort = (): void => finish({ status: "aborted" });
95
- opts.signal?.addEventListener("abort", onAbort, { once: true });
96
-
97
- bus.emit("plannotator:request", {
98
- requestId: randomUUID(),
99
- action: "code-review",
100
- payload: { prUrl: opts.prUrl, cwd: opts.cwd },
101
- respond: (raw: unknown) => {
102
- const response = raw as CodeReviewResponse;
103
- if (response?.status === "handled") {
104
- const result = response.result ?? {};
105
- const feedback =
106
- typeof result.feedback === "string" && result.feedback.trim()
107
- ? result.feedback
108
- : undefined;
109
- finish({
110
- status: "handled",
111
- approved: result.approved === true,
112
- feedback,
113
- annotationCount: Array.isArray(result.annotations) ? result.annotations.length : 0,
114
- });
115
- return;
116
- }
117
- const status = response?.status === "error" ? "error" : "unavailable";
118
- const detail = response?.error ? `: ${response.error}` : "";
119
- finish({ status, warning: `plannotator reported ${response?.status ?? status}${detail}` });
120
- },
121
- });
122
- });
123
- }
124
-
125
- /** Narrow the `perk pr url --json` success payload; strict on `pr.{number,url}`. */
126
- function decodePrUrl(payload: ColdJson): { number: number; url: string } | null {
127
- const pr = objectField(payload, "pr");
128
- if (pr === undefined) return null;
129
- const number = numberField(pr, "number");
130
- const url = stringField(pr, "url");
131
- if (number === undefined || url === undefined) return null;
132
- return { number, url };
133
- }
134
-
135
- /** Route the code-review outcome back into the session — mirrors plannotator's own routing. */
136
- function routePrReviewOutcome(
137
- pi: ExtensionAPI,
138
- ctx: ExtensionContext,
139
- out: CodeReviewOutcome,
140
- ): void {
141
- if (out.status === "unavailable" || out.status === "error") {
142
- report(ctx, "pr-review-local", "error", out.warning, { alsoLog: true });
143
- return;
144
- }
145
- if (out.status !== "handled") return; // aborted: the turn was interrupted — no-op
146
-
147
- if (out.feedback === undefined) {
148
- report(ctx, "pr-review-local", "info", "Code review approved — no changes requested.");
149
- return;
150
- }
151
- const message = out.feedback + (out.annotationCount > 0 ? TRIAGE_SUFFIX : "");
152
- // Inject the feedback as a real turn (the submit.ts driveConflictResolution pattern): an
153
- // immediate turn when idle, else delivered after the current streaming batch.
154
- if (ctx.isIdle()) {
155
- pi.sendUserMessage(message);
156
- } else {
157
- pi.sendUserMessage(message, { deliverAs: "followUp" });
158
- }
159
- }
160
-
161
- /** Register the warm `/pr-review-local` command. */
162
- export function registerPrReviewLocal(pi: ExtensionAPI): void {
163
- registerPerkCommand(pi, "pr-review-local", {
164
- description:
165
- "Open the plannotator browser code review on the active PR (URL filled in automatically).",
166
- handler: async (_args, ctx) => {
167
- if (!ctx.hasUI) {
168
- report(
169
- ctx,
170
- "pr-review-local",
171
- "info",
172
- "/pr-review-local requires an interactive session (the plannotator browser review needs UI).",
173
- );
174
- return;
175
- }
176
- if (!plannotatorPresent(pi)) {
177
- report(
178
- ctx,
179
- "pr-review-local",
180
- "info",
181
- "/pr-review-local requires the @plannotator/pi-extension package (its /plannotator-review command was not found).",
182
- );
183
- return;
184
- }
185
-
186
- const r = await runColdDoor<{ number: number; url: string }>(
187
- pi,
188
- ctx,
189
- ["pr", "url", "--json"],
190
- { label: "perk pr url", decode: decodePrUrl },
191
- );
192
- if (!r.ok) {
193
- failFor(ctx, "pr-review-local")(r.message, r.errorType);
194
- return;
195
- }
196
-
197
- report(
198
- ctx,
199
- "pr-review-local",
200
- "info",
201
- `Opening plannotator code review for PR #${r.data.number} …`,
202
- );
203
-
204
- // Kick the long-running review in the BACKGROUND — do not block the session for the whole
205
- // review (plannotator responds once on completion). Mirrors plannotator's own `.then` route.
206
- // While setup runs, re-route plannotator's in-process `console.error` chatter through the
207
- // TUI-safe report() seam so it never clobbers the input box; the debounce restores once setup
208
- // goes quiet, with the `finally` as a backstop.
209
- void (async () => {
210
- const interceptor = interceptConsoleError(
211
- (line) => report(ctx, "pr-review-local", "info", line),
212
- // plannotator can pause up to ~4s between setup lines — keep the quiet window comfortably
213
- // above that so the debounce doesn't restore mid-setup and let the next line clobber.
214
- { quietMs: 6000 },
215
- );
216
- try {
217
- const out = await requestPlannotatorCodeReview(pi.events, {
218
- prUrl: r.data.url,
219
- cwd: ctx.cwd,
220
- signal: ctx.signal,
221
- });
222
- routePrReviewOutcome(pi, ctx, out);
223
- } finally {
224
- interceptor.restore();
225
- }
226
- })();
227
- },
228
- });
229
- }