@mgiles/perk 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -44
- package/extension/adapters/planAdapterPlannotator.ts +27 -41
- package/extension/adapters/planAdapterTombell.ts +15 -28
- package/extension/adapters/todoAdapterJuicesharp.ts +10 -13
- package/extension/checkpoints/checkpoints.ts +19 -12
- package/extension/doors/address.ts +4 -4
- package/extension/doors/askUser.ts +12 -8
- package/extension/doors/ciExecutor.ts +21 -14
- package/extension/doors/hunkHandoff.ts +202 -0
- package/extension/doors/land.ts +31 -9
- package/extension/doors/learn.ts +2 -2
- package/extension/doors/learnFactory.ts +144 -0
- package/extension/doors/plannotatorHandoff.ts +509 -0
- package/extension/doors/prReview.ts +4 -4
- package/extension/doors/prReviewBrowser.ts +341 -0
- package/extension/doors/prReviewTerminal.ts +267 -0
- package/extension/doors/selfcheck.ts +238 -5
- package/extension/doors/submit.ts +20 -0
- package/extension/doors/submitPrReview.ts +408 -0
- package/extension/factories/objective.ts +15 -5
- package/extension/factories/objectiveAuthor.ts +15 -32
- package/extension/factories/objectiveDraft.ts +1 -1
- package/extension/factories/objectivePlan.ts +12 -10
- package/extension/factories/objectiveSave.ts +2 -2
- package/extension/factories/planMode.ts +22 -40
- package/extension/factories/planReview.ts +213 -191
- package/extension/factories/planSave.ts +7 -7
- package/extension/index.ts +83 -25
- package/extension/substrate/bindingDelivery.ts +32 -10
- package/extension/substrate/bindings.ts +4 -2
- package/extension/substrate/cache.ts +34 -7
- package/extension/substrate/clipboard.ts +81 -0
- package/extension/substrate/config.ts +97 -70
- package/extension/substrate/git.ts +43 -0
- package/extension/substrate/paths.ts +1 -1
- package/extension/substrate/prompts.ts +2 -2
- package/extension/substrate/providers.ts +62 -8
- package/extension/substrate/sessionPointers.ts +35 -6
- package/extension/substrate/structuredOutput.ts +3 -1
- package/extension/substrate/terminalLaunch.ts +178 -0
- package/extension/substrate/toolGating.ts +330 -79
- package/extension/substrate/toolParams.ts +7 -0
- package/extension/substrate/workflowState.ts +54 -2
- package/extension/surfaces/footerProvider.ts +8 -4
- package/extension/surfaces/surfaces.ts +330 -12
- package/extension/vendor/btw/btw.ts +10 -0
- package/extension/worker/readOnlySession.ts +19 -6
- package/extension/worker/worker.ts +77 -7
- package/extension/workerMain.ts +12 -13
- package/package.json +3 -3
- package/prompts/_fixtures/live.yaml +117 -2
- package/prompts/contexts/adapters/juicesharp-todo.md +7 -0
- package/prompts/contexts/adapters/plannotator-objective.md +7 -0
- package/prompts/contexts/adapters/plannotator-plan.md +6 -0
- package/prompts/contexts/adapters/tombell-plan.md +17 -0
- package/prompts/contexts/objective-authoring.md +20 -0
- package/prompts/contexts/plan-authoring.md +24 -0
- package/prompts/contexts/read-only.md +10 -0
- package/prompts/stages/conflict-resolution.md +1 -1
- package/prompts/stages/learn-code.md +1 -1
- package/prompts/stages/learn-docs.md +2 -2
- package/prompts/stages/learn-orchestrate.md +1 -1
- package/prompts/stages/objective-author/adopt.md +1 -1
- package/prompts/stages/objective-author/file.md +1 -1
- package/prompts/stages/objective-plan/guidance.md +1 -1
- package/prompts/stages/objective-plan/seed.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-replan.md +1 -1
- package/prompts/stages/plan-from/adopt.md +2 -2
- package/prompts/stages/plan-from/file.md +2 -2
- package/prompts/stages/pr-review-browser/active.md +11 -0
- package/prompts/stages/pr-review-browser/foreign.md +11 -0
- package/prompts/stages/pr-review-terminal/active.md +12 -0
- package/prompts/stages/pr-review-terminal/foreign.md +13 -0
- package/prompts/stages/pr-review-terminal/local.md +4 -0
- package/prompts/stages/pr-review.md +1 -1
- package/prompts/stages/replan.md +2 -2
- package/prompts/stages/skills/create-from.md +1 -1
- package/prompts/stages/skills/create.md +1 -1
- package/prompts/stages/skills/refine.md +1 -1
- package/shared/README.md +22 -18
- package/shared/bindings.yaml +10 -2
- package/shared/contracts-history.md +24 -0
- package/shared/contracts.md +1538 -1803
- package/shared/providers.yaml +8 -1
- package/shared/registry.yaml +7 -8
- package/shared/schemas/inputs/review-submit-batch.schema.json +66 -0
- package/shared/schemas/outputs/pr-review-checkout.schema.json +69 -0
- package/shared/schemas/outputs/pr-review-cleanup.schema.json +54 -0
- package/shared/schemas/outputs/pr-review-submit.schema.json +64 -0
- package/extension/doors/learnCode.ts +0 -100
- package/extension/doors/learnDocs.ts +0 -100
- package/extension/doors/prReviewLocal.ts +0 -229
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// The terminal review door's R7 terminal auto-launch ladder — open hunk in a terminal the human can
|
|
2
|
+
// see, since hunk's TUI is constitutively a human surface (a `pi.exec` child gets pipes, no TTY).
|
|
3
|
+
//
|
|
4
|
+
// A pure `resolveTerminalLaunch` (platform + env + request → an argv + the rung tag, or `null` for
|
|
5
|
+
// "no launcher matched") and a thin fail-soft `launchInTerminal` runner. Never throws, never
|
|
6
|
+
// notifies — the caller owns messaging, and the loud print + clipboard copy are the universal
|
|
7
|
+
// fallback when no rung matches or a rung fails. The `hunk session get` handshake — not a spawn
|
|
8
|
+
// success — remains the ONLY verification the surface is actually up.
|
|
9
|
+
//
|
|
10
|
+
// The `PERK_TERMINAL_LAUNCH` env seam: unset → the platform default ladder; empty → disabled (the
|
|
11
|
+
// test seam); non-empty → a custom launcher receiving the worktree as `$1` and the command as
|
|
12
|
+
// `$2`.
|
|
13
|
+
|
|
14
|
+
import type { ExecHost } from "./coldDoor.ts";
|
|
15
|
+
|
|
16
|
+
/** Which rung of the ladder launched (or would launch) the surface. */
|
|
17
|
+
export type LaunchVia = "custom" | "tmux" | "ghostty" | "iterm2" | "terminal-app";
|
|
18
|
+
|
|
19
|
+
/** A human-readable name for each rung, for the door's launched-in-a-<surface> message. */
|
|
20
|
+
export const LAUNCH_SURFACE: Record<LaunchVia, string> = {
|
|
21
|
+
custom: "terminal window",
|
|
22
|
+
tmux: "tmux pane",
|
|
23
|
+
ghostty: "Ghostty window",
|
|
24
|
+
iterm2: "iTerm2 window",
|
|
25
|
+
"terminal-app": "terminal window",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// The macOS AppleScript rungs are `on run argv` bodies: paths + commands ride argv (never
|
|
29
|
+
// interpolated into the script text), so no AppleScript string-quoting hazard exists.
|
|
30
|
+
|
|
31
|
+
/** Single-quote `s` for a POSIX shell line (the standard `'\''` embedded-quote escape). */
|
|
32
|
+
function shQuote(s: string): string {
|
|
33
|
+
return `'${s.replaceAll("'", `'\\''`)}'`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Wrap `command` in the human's interactive **login shell** (`$SHELL -i -l -c '<command>'`) for
|
|
38
|
+
* the rungs whose execution context is rc-less — Ghostty's surface `command` is argv-exec'd
|
|
39
|
+
* (quote-aware word split, a relative arg0 joined onto the working directory, never a shell
|
|
40
|
+
* line), and tmux commands run under the server environment. `-i -l` sources the human's rc
|
|
41
|
+
* files, so the launched window resolves binaries exactly like the human's own terminal — which
|
|
42
|
+
* is what the bare command needs: `hunk` AND the `node` its `#!/usr/bin/env node` shebang
|
|
43
|
+
* re-resolves are often on PATH only via rc activation (mise/nvm; both misses were hit live).
|
|
44
|
+
* `$SHELL` when absolute, else `/bin/zsh` on darwin (the macOS default) / `/bin/sh` elsewhere.
|
|
45
|
+
* The shell-line rungs (iTerm2/Terminal.app) type into an interactive login shell already and
|
|
46
|
+
* take the bare command; so does the custom launcher (it owns its own environment).
|
|
47
|
+
*/
|
|
48
|
+
function interactiveShellWrap(
|
|
49
|
+
platform: string,
|
|
50
|
+
env: Record<string, string | undefined>,
|
|
51
|
+
command: string,
|
|
52
|
+
): string {
|
|
53
|
+
const shell =
|
|
54
|
+
env.SHELL !== undefined && env.SHELL.startsWith("/")
|
|
55
|
+
? env.SHELL
|
|
56
|
+
: platform === "darwin"
|
|
57
|
+
? "/bin/zsh"
|
|
58
|
+
: "/bin/sh";
|
|
59
|
+
return `${shell} -i -l -c ${shQuote(command)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Ghostty ≥ 1.3: a native surface configuration (cwd + command as argv items 1/2). */
|
|
63
|
+
const GHOSTTY_SCRIPT = `on run argv
|
|
64
|
+
tell application "Ghostty"
|
|
65
|
+
set cfg to (new surface configuration)
|
|
66
|
+
set (initial working directory of cfg) to (item 1 of argv)
|
|
67
|
+
set (command of cfg) to (item 2 of argv)
|
|
68
|
+
set (wait after command of cfg) to true
|
|
69
|
+
new window with configuration cfg
|
|
70
|
+
activate
|
|
71
|
+
end tell
|
|
72
|
+
end run`;
|
|
73
|
+
|
|
74
|
+
/** iTerm2: a new window whose current session runs the composed shell line (argv item 1). */
|
|
75
|
+
const ITERM_SCRIPT = `on run argv
|
|
76
|
+
tell application "iTerm"
|
|
77
|
+
set w to (create window with default profile)
|
|
78
|
+
tell current session of w to write text (item 1 of argv)
|
|
79
|
+
activate
|
|
80
|
+
end tell
|
|
81
|
+
end run`;
|
|
82
|
+
|
|
83
|
+
/** Terminal.app: the universal macOS fallback — `do script` the composed shell line (argv item 1). */
|
|
84
|
+
const TERMINAL_SCRIPT = `on run argv
|
|
85
|
+
tell application "Terminal"
|
|
86
|
+
do script (item 1 of argv)
|
|
87
|
+
activate
|
|
88
|
+
end tell
|
|
89
|
+
end run`;
|
|
90
|
+
|
|
91
|
+
export interface LaunchRequest {
|
|
92
|
+
/** The review worktree — the terminal's working directory. */
|
|
93
|
+
cwd: string;
|
|
94
|
+
/**
|
|
95
|
+
* The bare surface command to run there, e.g. `hunk diff <sha12>`. The rc-less rungs wrap it
|
|
96
|
+
* in the human's interactive login shell (see `interactiveShellWrap`).
|
|
97
|
+
*/
|
|
98
|
+
command: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the terminal-launch argv + rung tag for `platform` + `env` + `req`, first match wins:
|
|
103
|
+
*
|
|
104
|
+
* 1. `PERK_TERMINAL_LAUNCH === ""` → `null` (the test seam / opt-out);
|
|
105
|
+
* 2. `PERK_TERMINAL_LAUNCH` non-empty → a custom `sh -c` launcher (worktree `$1`, command `$2`);
|
|
106
|
+
* 3. `TMUX` set → a `tmux split-window` pane beside pi (no consent prompts);
|
|
107
|
+
* 4. `darwin`, keyed off `TERM_PROGRAM` → the matching osascript rung (ghostty / iterm2 /
|
|
108
|
+
* terminal-app; anything unrecognized — vscode, WezTerm, absent — falls to terminal-app, the
|
|
109
|
+
* universal macOS fallback);
|
|
110
|
+
* 5. otherwise → `null` (no Linux emulator sniffing — tmux + the custom seam cover it).
|
|
111
|
+
*
|
|
112
|
+
* Rungs that set the working directory natively (ghostty's `initial working directory`, tmux's
|
|
113
|
+
* `-c`) receive the command wrapped in the human's interactive login shell (see
|
|
114
|
+
* `interactiveShellWrap` — their execution contexts are rc-less); the shell-line rungs
|
|
115
|
+
* (iterm2/terminal-app) receive `cd '<cwd>' && <command>` as one arg (the cwd single-quoted —
|
|
116
|
+
* worktree paths can carry spaces; the command is perk-composed, never quoted) typed into an
|
|
117
|
+
* interactive login shell the terminal itself opens.
|
|
118
|
+
*/
|
|
119
|
+
export function resolveTerminalLaunch(
|
|
120
|
+
platform: string,
|
|
121
|
+
env: Record<string, string | undefined>,
|
|
122
|
+
req: LaunchRequest,
|
|
123
|
+
): { argv: string[]; via: LaunchVia } | null {
|
|
124
|
+
const custom = env.PERK_TERMINAL_LAUNCH;
|
|
125
|
+
if (custom !== undefined) {
|
|
126
|
+
if (custom === "") return null;
|
|
127
|
+
return { argv: ["sh", "-c", custom, "sh", req.cwd, req.command], via: "custom" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (env.TMUX !== undefined && env.TMUX !== "") {
|
|
131
|
+
const wrapped = interactiveShellWrap(platform, env, req.command);
|
|
132
|
+
return { argv: ["tmux", "split-window", "-h", "-c", req.cwd, wrapped], via: "tmux" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (platform === "darwin") {
|
|
136
|
+
// The shell-line rungs run this through the terminal's shell — quote the cwd (paths can
|
|
137
|
+
// carry spaces/quotes); the command is perk-composed (`hunk diff <sha12>`), never quoted.
|
|
138
|
+
const shellLine = `cd ${shQuote(req.cwd)} && ${req.command}`;
|
|
139
|
+
const term = env.TERM_PROGRAM;
|
|
140
|
+
if (term === "ghostty") {
|
|
141
|
+
const wrapped = interactiveShellWrap(platform, env, req.command);
|
|
142
|
+
return { argv: ["osascript", "-e", GHOSTTY_SCRIPT, req.cwd, wrapped], via: "ghostty" };
|
|
143
|
+
}
|
|
144
|
+
if (term === "iTerm.app") {
|
|
145
|
+
return { argv: ["osascript", "-e", ITERM_SCRIPT, shellLine], via: "iterm2" };
|
|
146
|
+
}
|
|
147
|
+
// Apple_Terminal, vscode, WezTerm, or absent → Terminal.app (the universal macOS fallback).
|
|
148
|
+
return { argv: ["osascript", "-e", TERMINAL_SCRIPT, shellLine], via: "terminal-app" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Launch the surface command in a terminal the human can see, best-effort. Resolves the rung from
|
|
156
|
+
* `process.platform` + `process.env`; a `null` resolution (disabled seam, unmatched platform)
|
|
157
|
+
* short-circuits to `{launched: false}`. Otherwise shells the argv with a 15s cap (guards an
|
|
158
|
+
* unanswered macOS Automation/TCC consent dialog blocking `osascript`). Fail-soft on
|
|
159
|
+
* throw/nonzero/killed — a denied or unanswered consent is just another failed rung, and the
|
|
160
|
+
* print+clipboard fallback already covers the human. Never rejects, never notifies.
|
|
161
|
+
*/
|
|
162
|
+
export async function launchInTerminal(
|
|
163
|
+
pi: ExecHost,
|
|
164
|
+
ctx: { cwd: string; signal?: AbortSignal },
|
|
165
|
+
req: LaunchRequest,
|
|
166
|
+
): Promise<{ launched: boolean; via?: LaunchVia }> {
|
|
167
|
+
const resolved = resolveTerminalLaunch(process.platform, process.env, req);
|
|
168
|
+
if (resolved === null) return { launched: false };
|
|
169
|
+
const [cmd, ...rest] = resolved.argv;
|
|
170
|
+
if (cmd === undefined) return { launched: false };
|
|
171
|
+
try {
|
|
172
|
+
const res = await pi.exec(cmd, rest, { cwd: ctx.cwd, signal: ctx.signal, timeout: 15_000 });
|
|
173
|
+
if (res.killed || res.code !== 0) return { launched: false };
|
|
174
|
+
return { launched: true, via: resolved.via };
|
|
175
|
+
} catch {
|
|
176
|
+
return { launched: false };
|
|
177
|
+
}
|
|
178
|
+
}
|