@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.
- 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 +88 -65
- 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 +1442 -1787
- 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
package/README.md
CHANGED
|
@@ -1,64 +1,89 @@
|
|
|
1
1
|
# perk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
*exterior*) plus a TypeScript Pi extension (the session *interior*).
|
|
3
|
+
perk is a [Pi](https://pi.dev)-native workflow for plan-driven software engineering.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
It combines a Python CLI that manages the **session exterior**, a TypeScript Pi extension
|
|
6
|
+
that governs the **session interior**, and an **issue backend** for durable workflow state,
|
|
7
|
+
GitHub by default. The workflow is organized around _planning_, _implementation_,
|
|
8
|
+
_code review_, and _learning_.
|
|
7
9
|
|
|
8
|
-
perk
|
|
10
|
+
Workflow stages and surfaces can use perk’s built-in defaults or pluggable third-party
|
|
11
|
+
extensions, such as [plannonator](https://github.com/backnotprop/plannotator) for plan
|
|
12
|
+
review.
|
|
13
|
+
|
|
14
|
+
perk is built for work that may be interrupted, resumed elsewhere, superseded, or moved
|
|
15
|
+
across machines. Resumability, human feedback, and remote execution are not special cases;
|
|
16
|
+
they are part of the core model.
|
|
17
|
+
|
|
18
|
+
`perk` is inspired by `erk`, formerly maintained by the team at
|
|
19
|
+
[Dagster](https://github.com/dagster-io/dagster).
|
|
9
20
|
|
|
10
21
|
## What perk is
|
|
11
22
|
|
|
12
|
-
perk
|
|
13
|
-
|
|
23
|
+
perk's unit of work is a **plan**: a reviewed, durable description of a change,
|
|
24
|
+
written before files are edited and saved as canonical workflow state. The core spine is
|
|
25
|
+
`plan -> save -> implement -> submit -> address -> land -> learn`; `address` only happens
|
|
26
|
+
when review feedback needs a response. Longer-running **objectives** can emit bounded plans
|
|
27
|
+
into the same spine.
|
|
28
|
+
|
|
29
|
+
The implementation is split across two planes:
|
|
14
30
|
|
|
15
|
-
-
|
|
16
|
-
run ids, and launches primed `pi` sessions
|
|
17
|
-
-
|
|
18
|
-
|
|
31
|
+
- **exterior** — the Python `perk` CLI, which scaffolds repos, manages worktrees,
|
|
32
|
+
mints run ids, and launches primed `pi` sessions;
|
|
33
|
+
- **interior** — a TypeScript Pi extension, which owns in-session stage transitions,
|
|
34
|
+
tool gates, context, and warm `/...` commands.
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
is
|
|
36
|
+
Both planes read the language-neutral [`shared/`](shared/) registry and contracts directly.
|
|
37
|
+
There is no codegen layer to drift.
|
|
38
|
+
|
|
39
|
+
The operating model is deliberately simple: canonical state lives in GitHub by default
|
|
40
|
+
(Linear is supported), `.perk/workflow/` is a local cache, and session state is throwaway.
|
|
41
|
+
Cold doors launch fresh context from a shell (`perk implement`, `perk plan resume`); warm
|
|
42
|
+
doors keep context inside a session (`/submit`, `/land`, `/learn`). `implement` and
|
|
43
|
+
`address` can dispatch to CI; review, merge, and learning stay local.
|
|
22
44
|
|
|
23
45
|
## Quickstart
|
|
24
46
|
|
|
25
|
-
|
|
47
|
+
From the git repo you want to wire:
|
|
26
48
|
|
|
27
49
|
```bash
|
|
28
|
-
uv tool install perk
|
|
29
|
-
perk init
|
|
30
|
-
perk doctor
|
|
50
|
+
uv tool install perk
|
|
51
|
+
perk init
|
|
52
|
+
perk doctor
|
|
53
|
+
perk plan
|
|
31
54
|
```
|
|
32
55
|
|
|
33
|
-
`perk init`
|
|
34
|
-
|
|
56
|
+
`perk init` is idempotent and safe to re-run. `perk doctor` reports health;
|
|
57
|
+
`perk doctor --fix` repairs managed drift.
|
|
58
|
+
|
|
59
|
+
You need a git repo plus `git`, `gh`, `node >= 22`, `pi`, and `uv`. `ast-grep` is preferred
|
|
60
|
+
for structural search and reported when absent, but it is advisory. GitHub auth is verified
|
|
61
|
+
and reported by init; it becomes required when you drive real plans, PRs, and merges.
|
|
35
62
|
|
|
36
63
|
For the guided first run, follow
|
|
37
64
|
[Get started with perk](docs/user-docs/tutorials/get-started.md).
|
|
38
65
|
|
|
39
66
|
## Documentation
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
Operator docs live under [`docs/user-docs/`](docs/user-docs/index.md):
|
|
42
69
|
|
|
43
|
-
- **[Tutorials](docs/user-docs/tutorials/index.md)** —
|
|
70
|
+
- **[Tutorials](docs/user-docs/tutorials/index.md)** — learn by doing; start with
|
|
44
71
|
[Get started with perk](docs/user-docs/tutorials/get-started.md).
|
|
45
|
-
- **[How-to guides](docs/user-docs/how-to/index.md)** —
|
|
46
|
-
address review feedback, switch to Linear, attach a skill,
|
|
47
|
-
- **[Reference](docs/user-docs/reference/index.md)** —
|
|
48
|
-
tools,
|
|
49
|
-
- **[Explanation](docs/user-docs/explanation/index.md)** —
|
|
50
|
-
maturity.
|
|
72
|
+
- **[How-to guides](docs/user-docs/how-to/index.md)** — recipes for specific jobs:
|
|
73
|
+
resume a plan, address review feedback, switch to Linear, attach a skill, dispatch to CI.
|
|
74
|
+
- **[Reference](docs/user-docs/reference/index.md)** — CLI commands, in-session commands
|
|
75
|
+
and tools, objectives, configuration, providers, backends, and schemas.
|
|
76
|
+
- **[Explanation](docs/user-docs/explanation/index.md)** — the mental model: plans,
|
|
77
|
+
planes, state tiers, doors, and remote maturity.
|
|
51
78
|
|
|
52
|
-
|
|
53
|
-
perk's own developers.
|
|
79
|
+
Internal research, design notes, and durable learnings live under [`docs/`](docs/index.md).
|
|
54
80
|
|
|
55
81
|
## Layout
|
|
56
82
|
|
|
57
|
-
- `perk/` —
|
|
58
|
-
- `extension/` —
|
|
59
|
-
- `shared/` —
|
|
60
|
-
|
|
61
|
-
- `docs/` — research inputs, design notes, and durable learnings.
|
|
83
|
+
- `perk/` — Python CLI, the session exterior.
|
|
84
|
+
- `extension/` — TypeScript Pi extension, the session interior.
|
|
85
|
+
- `shared/` — stage registry and cross-plane contracts.
|
|
86
|
+
- `docs/` — operator docs, research, design notes, and learnings.
|
|
62
87
|
|
|
63
88
|
## Develop
|
|
64
89
|
|
|
@@ -72,19 +97,18 @@ Two pinned toolchains:
|
|
|
72
97
|
With [`just`](https://github.com/casey/just):
|
|
73
98
|
|
|
74
99
|
```bash
|
|
75
|
-
just setup
|
|
76
|
-
just install-cli
|
|
77
|
-
just fmt
|
|
78
|
-
just lint
|
|
79
|
-
just typecheck
|
|
80
|
-
just test
|
|
81
|
-
just ci
|
|
100
|
+
just setup
|
|
101
|
+
just install-cli
|
|
102
|
+
just fmt
|
|
103
|
+
just lint
|
|
104
|
+
just typecheck
|
|
105
|
+
just test
|
|
106
|
+
just ci
|
|
82
107
|
```
|
|
83
108
|
|
|
84
|
-
`just setup`
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
system/`.venv` ruff). Re-run `just hooks` after a fresh clone.
|
|
109
|
+
`just setup` runs `uv sync`, `npm install`, `just hooks`, and `just install-cli`. The hook is
|
|
110
|
+
managed by [prek](https://prek.j178.dev) and runs `ruff check` on staged Python. Re-run
|
|
111
|
+
`just hooks` after a fresh clone if hooks are missing.
|
|
88
112
|
|
|
89
113
|
Without `just`: `uv run …` for Python (`uv run perk init`, `uv run pytest`,
|
|
90
114
|
`uv run ruff check perk tests`, `uv run ty check`) and `npm run …` for TypeScript
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// INJECTION + BRIDGE ONLY: the `plan_review` TOOL lives in `extension/factories/planReview.ts`
|
|
8
8
|
// (perk's backend-neutral review door); this module is the injection-only adapter shape. It owns
|
|
9
9
|
// (1) the plannotator review-step authoring context (injected while the gate is active AND
|
|
10
|
-
// plannotator is selected — TWO content flavors, one customType
|
|
10
|
+
// plannotator is selected — TWO content flavors, one customType, each once-only: branch-scan
|
|
11
|
+
// dedup'd on the flavor's marker: the plan bridge context, or the
|
|
11
12
|
// objective flavor when the stage is `objective-author`) and (2) the pure event-bus bridge
|
|
12
13
|
// (`createPlannotatorBridge`) that planReview.ts dispatches to when plannotator is the
|
|
13
14
|
// selected plan provider. The bridge speaks plannotator's published `plannotator:request` event
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
// gate exit all live behind planReview.ts's seams; the injection's gate-active check reads the
|
|
25
26
|
// persisted `perk:workflow-state.mode`, the gate's own state twin.
|
|
26
27
|
//
|
|
27
|
-
// EVENT ENVELOPE (pinned against `@plannotator/pi-extension@0.20.0`, `plannotator-events.ts`
|
|
28
|
+
// EVENT ENVELOPE (pinned against `@plannotator/pi-extension@0.20.0`, `plannotator-events.ts` —
|
|
29
|
+
// verified unchanged through 0.22.0):
|
|
28
30
|
// request — pi.events.emit("plannotator:request", { requestId, action: "plan-review",
|
|
29
31
|
// payload: { planContent, origin? }, respond }) // respond = in-payload callback
|
|
30
32
|
// handshake — respond({ status: "handled", result: { status: "pending", reviewId } })
|
|
@@ -37,8 +39,9 @@ import { OBJECTIVE_AUTHOR_STAGE } from "../factories/objectiveAuthor.ts";
|
|
|
37
39
|
import { resolvedPlanProviderId } from "../factories/planMode.ts";
|
|
38
40
|
// Type-only (erased at runtime — no cycle): the outcome vocabulary lives with the review door.
|
|
39
41
|
import type { ReviewOutcome } from "../factories/planReview.ts";
|
|
42
|
+
import { render } from "../substrate/prompts.ts";
|
|
40
43
|
import { PLANNOTATOR_PLAN_PROVIDER_ID } from "../substrate/providers.ts";
|
|
41
|
-
import { branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
44
|
+
import { branchCarries, branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
42
45
|
|
|
43
46
|
/** The plannotator plan-adapter bridge customType (distinct from planMode's `perk:plan-context`). */
|
|
44
47
|
export const PLAN_ADAPTER_PLANNOTATOR_CONTEXT_TYPE = "perk:plan-adapter-plannotator";
|
|
@@ -61,21 +64,9 @@ function handshakeTimeoutMs(): number {
|
|
|
61
64
|
* step. Prompting, NOT enforcement (perk's own gate is the read-only authority). Durable anchors
|
|
62
65
|
* only — mirrors PLAN_AUTHORING_CONTEXT, which is also injected (perk's plan mode stays).
|
|
63
66
|
*/
|
|
64
|
-
export const PLAN_ADAPTER_PLANNOTATOR_CONTEXT =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- Keep the working draft current with plan_draft — the validated plan-draft artifact is what gets
|
|
69
|
-
reviewed AND auto-saved; the plan param is only a fallback for sessions that never wrote a draft.
|
|
70
|
-
- When the plan is decision-complete, call the plan_review tool. The Plannotator browser UI opens
|
|
71
|
-
for the human reviewer.
|
|
72
|
-
- If the review is DENIED: revise per the returned annotations/feedback, rewrite the working draft
|
|
73
|
-
with plan_draft, then call plan_review again.
|
|
74
|
-
- If the review is APPROVED: the plan is auto-saved to GitHub and the session leaves read-only.
|
|
75
|
-
Do NOT re-dump the plan as a final message and do NOT tell the user to run /plan-save — relay
|
|
76
|
-
the save outcome (and any reviewer feedback) instead.
|
|
77
|
-
- If plan_review reports it was skipped or no review surface is available: fall back to presenting
|
|
78
|
-
the complete plan to the user; the human runs /plan-save (the manual failsafe).`;
|
|
67
|
+
export const PLAN_ADAPTER_PLANNOTATOR_CONTEXT = render("contexts/adapters/plannotator-plan.md", {
|
|
68
|
+
marker: PLAN_ADAPTER_PLANNOTATOR_MARKER,
|
|
69
|
+
});
|
|
79
70
|
|
|
80
71
|
/**
|
|
81
72
|
* The objective flavor of the bridge prompt, injected in an
|
|
@@ -83,24 +74,10 @@ read-only exactly as the plan-authoring contract describes; then add one review
|
|
|
83
74
|
* objective via the `objectiveApprovalSave` seam; `/objective-save` is the manual failsafe on
|
|
84
75
|
* the skipped/unavailable arms.
|
|
85
76
|
*/
|
|
86
|
-
export const OBJECTIVE_ADAPTER_PLANNOTATOR_CONTEXT =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- Keep the working objective current with objective_draft — pass the FULL prose and the FULL
|
|
92
|
-
structured roadmap each call (it rewrites the whole draft); never hand-write roadmap YAML.
|
|
93
|
-
- When the objective + roadmap are decision-complete, call the plan_review tool. The Plannotator
|
|
94
|
-
browser UI shows the RENDERED objective (the prose + a roadmap table) derived from the draft
|
|
95
|
-
artifact — never raw JSON.
|
|
96
|
-
- If the review is DENIED: revise per the returned annotations/feedback, rewrite the working
|
|
97
|
-
draft with objective_draft, then call plan_review again.
|
|
98
|
-
- If the review is APPROVED: the objective is auto-saved to GitHub and the session leaves
|
|
99
|
-
read-only — do NOT re-dump the objective as a final message and do NOT tell the user to run
|
|
100
|
-
/objective-save; relay the save outcome (and any reviewer feedback) instead.
|
|
101
|
-
- If plan_review reports it was skipped or no review surface is available: present the complete
|
|
102
|
-
objective + structured roadmap to the user; the human runs /objective-save (the manual
|
|
103
|
-
failsafe).`;
|
|
77
|
+
export const OBJECTIVE_ADAPTER_PLANNOTATOR_CONTEXT = render(
|
|
78
|
+
"contexts/adapters/plannotator-objective.md",
|
|
79
|
+
{ marker: OBJECTIVE_ADAPTER_PLANNOTATOR_MARKER },
|
|
80
|
+
);
|
|
104
81
|
|
|
105
82
|
/** Whether the foreign `plannotator-plan` provider is the selected plan provider for `cwd`. */
|
|
106
83
|
export function isPlannotatorPlanSelected(cwd: string): boolean {
|
|
@@ -226,12 +203,21 @@ export function registerPlanAdapterPlannotator(pi: ExtensionAPI): void {
|
|
|
226
203
|
// `perk:workflow-state.mode` (the gate's state twin) — never the gate itself.
|
|
227
204
|
pi.on("before_agent_start", async (_event, ctx) => {
|
|
228
205
|
if (!isPlannotatorPlanSelected(ctx.cwd)) return;
|
|
229
|
-
const
|
|
206
|
+
const branch = branchOf(ctx);
|
|
207
|
+
const state = rebuildWorkflowState(branch);
|
|
230
208
|
if (state.mode !== "read-only") return;
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
209
|
+
const objectiveFlavor = state.stage === OBJECTIVE_AUTHOR_STAGE;
|
|
210
|
+
const content = objectiveFlavor
|
|
211
|
+
? OBJECTIVE_ADAPTER_PLANNOTATOR_CONTEXT
|
|
212
|
+
: PLAN_ADAPTER_PLANNOTATOR_CONTEXT;
|
|
213
|
+
// Once-only PER FLAVOR: the dedup key is the flavor's marker (not the shared customType), so
|
|
214
|
+
// a stage change still delivers the missing flavor while a prior copy of the other flavor
|
|
215
|
+
// sits on the branch. Injected customs persist, so a live copy suppresses re-injection;
|
|
216
|
+
// compaction dropping it makes the scan come up clean and the next turn re-injects.
|
|
217
|
+
const marker = objectiveFlavor
|
|
218
|
+
? OBJECTIVE_ADAPTER_PLANNOTATOR_MARKER
|
|
219
|
+
: PLAN_ADAPTER_PLANNOTATOR_MARKER;
|
|
220
|
+
if (branchCarries(branch, marker)) return;
|
|
235
221
|
return {
|
|
236
222
|
message: {
|
|
237
223
|
customType: PLAN_ADAPTER_PLANNOTATOR_CONTEXT_TYPE,
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// change on the default path.
|
|
9
9
|
//
|
|
10
10
|
// WHAT IT DOES (and does NOT do):
|
|
11
|
-
// - It injects a hidden (`display:false
|
|
11
|
+
// - It injects a hidden (`display:false`, once-only: branch-scan dedup'd on the marker)
|
|
12
|
+
// `perk:plan-adapter-tombell` context that tells the model
|
|
12
13
|
// the foreign `/plan` surface authors a FREE-FORM PROSE plan, and directs it through perk's
|
|
13
14
|
// review-first discipline: keep the draft current with `plan_draft`, then call
|
|
14
15
|
// `plan_review` — which (for any non-plannotator selection, tombell included) runs the
|
|
@@ -36,8 +37,14 @@
|
|
|
36
37
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
37
38
|
import { OBJECTIVE_AUTHOR_STAGE } from "../factories/objectiveAuthor.ts";
|
|
38
39
|
import { resolvedPlanProviderId } from "../factories/planMode.ts";
|
|
40
|
+
import { render } from "../substrate/prompts.ts";
|
|
39
41
|
import { TOMBELL_PLAN_PROVIDER_ID } from "../substrate/providers.ts";
|
|
40
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
type BranchEntry,
|
|
44
|
+
branchCarries,
|
|
45
|
+
branchOf,
|
|
46
|
+
rebuildWorkflowState,
|
|
47
|
+
} from "../substrate/workflowState.ts";
|
|
41
48
|
|
|
42
49
|
/** The tombell plan-adapter bridge customType (distinct from planMode's `perk:plan-context`). */
|
|
43
50
|
export const PLAN_ADAPTER_TOMBELL_CONTEXT_TYPE = "perk:plan-adapter-tombell";
|
|
@@ -50,32 +57,9 @@ const PLAN_ADAPTER_TOMBELL_MARKER = "[PLAN ADAPTER: TOMBELL]";
|
|
|
50
57
|
* engaged by the cold-door launch, is the read-only authority). Durable anchors only, no line
|
|
51
58
|
* numbers.
|
|
52
59
|
*/
|
|
53
|
-
export const PLAN_ADAPTER_TOMBELL_CONTEXT =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Gather before you plan, then write the plan so an executor with zero prior context can implement it
|
|
58
|
-
without guessing: anchor every change durably — function/class names, behavioral descriptions,
|
|
59
|
-
structural locations — never line numbers, and resolve every open choice before you save.
|
|
60
|
-
|
|
61
|
-
perk persists the plan to the provider-agnostic plan reference (cache.plan-ref); the objective/node
|
|
62
|
-
linkage and any consumed-learn numbers are recovered automatically from the launch handoff — never
|
|
63
|
-
try to write the plan reference yourself.
|
|
64
|
-
|
|
65
|
-
- Keep the working draft current with the plan_draft tool — the validated plan-draft artifact is
|
|
66
|
-
what gets reviewed AND auto-saved.
|
|
67
|
-
- When the plan is decision-complete, call the plan_review tool — the human reviews the draft in
|
|
68
|
-
perk's in-TUI editor review.
|
|
69
|
-
- If the review is DENIED: revise per the feedback, rewrite the draft with plan_draft, then call
|
|
70
|
-
plan_review again.
|
|
71
|
-
- If the review is APPROVED: the plan is auto-saved and the session leaves read-only. Relay the
|
|
72
|
-
save outcome — do NOT re-dump the plan as a final message and do NOT tell the user to run
|
|
73
|
-
/plan-save.
|
|
74
|
-
- If plan_review reports it was skipped or unavailable, OR the plan_draft/plan_review tools are not
|
|
75
|
-
in your tool set (this plan surface restricts tools): write the COMPLETE final plan as your last
|
|
76
|
-
message and present it to the user — the human runs the /plan-save command when satisfied (it
|
|
77
|
-
prefers the validated draft artifact and falls back to scraping your latest message, so that
|
|
78
|
-
final message must be the clean, complete plan and nothing else).`;
|
|
60
|
+
export const PLAN_ADAPTER_TOMBELL_CONTEXT = render("contexts/adapters/tombell-plan.md", {
|
|
61
|
+
marker: PLAN_ADAPTER_TOMBELL_MARKER,
|
|
62
|
+
});
|
|
79
63
|
|
|
80
64
|
/** Whether the foreign `tombell-plan` provider is the selected plan provider for `cwd`. */
|
|
81
65
|
export function isTombellPlanSelected(cwd: string): boolean {
|
|
@@ -113,6 +97,9 @@ export function registerPlanAdapterTombell(pi: ExtensionAPI): void {
|
|
|
113
97
|
const state = rebuildWorkflowState(branch);
|
|
114
98
|
if (state.stage === OBJECTIVE_AUTHOR_STAGE) return;
|
|
115
99
|
if (state.mode !== "read-only" && !isTombellPlanModeEnabled(branch)) return;
|
|
100
|
+
// Once-only: injected customs persist to the branch, so a live copy suppresses re-injection;
|
|
101
|
+
// compaction dropping it makes the scan come up clean and the next turn re-injects.
|
|
102
|
+
if (branchCarries(branch, PLAN_ADAPTER_TOMBELL_MARKER)) return;
|
|
116
103
|
return {
|
|
117
104
|
message: {
|
|
118
105
|
customType: PLAN_ADAPTER_TOMBELL_CONTEXT_TYPE,
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// behavior change on the default path.
|
|
12
12
|
//
|
|
13
13
|
// WHAT IT DOES (and does NOT do):
|
|
14
|
-
// - It injects a hidden (`display:false
|
|
14
|
+
// - It injects a hidden (`display:false`, once-only: branch-scan dedup'd on the marker)
|
|
15
|
+
// `perk:todo-adapter-juicesharp` context that tells the
|
|
15
16
|
// model the foreign `@juicesharp/rpiv-todo` checklist overlay is the sole progress surface here
|
|
16
17
|
// (perk's own checkpoints stepped aside) and directs it to carry perk's
|
|
17
18
|
// implement-progress DISCIPLINE onto that overlay: seed it from the plan body's `## Steps` and
|
|
@@ -29,8 +30,9 @@
|
|
|
29
30
|
|
|
30
31
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
31
32
|
import { resolvedTodoProviderId } from "../checkpoints/checkpoints.ts";
|
|
33
|
+
import { render } from "../substrate/prompts.ts";
|
|
32
34
|
import { JUICESHARP_TODO_PROVIDER_ID } from "../substrate/providers.ts";
|
|
33
|
-
import { branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
35
|
+
import { branchCarries, branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
34
36
|
|
|
35
37
|
/** The juicesharp todo-adapter bridge customType (distinct from checkpoints' `perk:checkpoint`). */
|
|
36
38
|
export const TODO_ADAPTER_JUICESHARP_CONTEXT_TYPE = "perk:todo-adapter-juicesharp";
|
|
@@ -41,17 +43,9 @@ const TODO_ADAPTER_JUICESHARP_MARKER = "[TODO ADAPTER: JUICESHARP]";
|
|
|
41
43
|
* overlay. Prompting, NOT enforcement (perk's checkpoint scanner is deferred under this selection).
|
|
42
44
|
* Durable anchors only, no line numbers.
|
|
43
45
|
*/
|
|
44
|
-
export const TODO_ADAPTER_JUICESHARP_CONTEXT =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
stepped aside (Node 3.1), so the foreign overlay is the sole progress surface here.
|
|
48
|
-
|
|
49
|
-
Carry perk's implement-progress discipline onto that overlay: seed it from the plan body's
|
|
50
|
-
\`## Steps\` numbered list — one checklist item per step, in order — then mark each item complete as
|
|
51
|
-
you finish the corresponding step, the same gather-then-advance flow perk's checkpoints embody. Use
|
|
52
|
-
the overlay's own controls to add and complete items; you do not need perk's \`[WIP:n]\`/\`[DONE:n]\`
|
|
53
|
-
markers here (perk's checkpoint scanner is deferred under this selection). If the plan has no
|
|
54
|
-
\`## Steps\` list, there is nothing to seed — let the overlay behave as its defaults suggest.`;
|
|
46
|
+
export const TODO_ADAPTER_JUICESHARP_CONTEXT = render("contexts/adapters/juicesharp-todo.md", {
|
|
47
|
+
marker: TODO_ADAPTER_JUICESHARP_MARKER,
|
|
48
|
+
});
|
|
55
49
|
|
|
56
50
|
/** Whether the foreign `juicesharp-todo` provider is the selected todo provider for `cwd`. */
|
|
57
51
|
export function isJuicesharpTodoSelected(cwd: string): boolean {
|
|
@@ -71,6 +65,9 @@ export function registerTodoAdapterJuicesharp(pi: ExtensionAPI): void {
|
|
|
71
65
|
if (!isJuicesharpTodoSelected(ctx.cwd)) return;
|
|
72
66
|
const branch = branchOf(ctx);
|
|
73
67
|
if (rebuildWorkflowState(branch).active_plan_ref == null) return;
|
|
68
|
+
// Once-only: injected customs persist to the branch, so a live copy suppresses re-injection;
|
|
69
|
+
// compaction dropping it makes the scan come up clean and the next turn re-injects.
|
|
70
|
+
if (branchCarries(branch, TODO_ADAPTER_JUICESHARP_MARKER)) return;
|
|
74
71
|
return {
|
|
75
72
|
message: {
|
|
76
73
|
customType: TODO_ADAPTER_JUICESHARP_CONTEXT_TYPE,
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
// config-read error → treated as the reference → zero change on the default selection.
|
|
28
28
|
|
|
29
29
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
30
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
31
30
|
import { readHandoff, readPlanBody } from "../substrate/cache.ts";
|
|
32
31
|
import { registerPerkCommand } from "../substrate/command.ts";
|
|
33
32
|
import { loadPerkConfig } from "../substrate/config.ts";
|
|
@@ -42,11 +41,14 @@ import {
|
|
|
42
41
|
writeSessionArtifact,
|
|
43
42
|
} from "../substrate/sessionData.ts";
|
|
44
43
|
import type { BranchEntry } from "../substrate/workflowState.ts";
|
|
45
|
-
import { branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
44
|
+
import { branchCarries, branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
46
45
|
import {
|
|
46
|
+
checkpointEntryRenderer,
|
|
47
47
|
MARK_CHECKPOINTS,
|
|
48
48
|
type PerkStatusHandle,
|
|
49
49
|
progressLine,
|
|
50
|
+
registerTranscriptRenderer,
|
|
51
|
+
renderCoarsePlanLines,
|
|
50
52
|
renderProgressLines,
|
|
51
53
|
report,
|
|
52
54
|
setStandingWidget,
|
|
@@ -67,13 +69,17 @@ export const STEPS_ARTIFACT_NAME = "plan-steps.json";
|
|
|
67
69
|
/**
|
|
68
70
|
* The resolved `[providers] todo` selection id for `cwd`, read fresh per-event (no static state —
|
|
69
71
|
* the same per-event-read shape `resolvedPlanProviderId` uses in planMode.ts). Fail-safe to the
|
|
70
|
-
* perk-checkpoints reference: any load/resolution failure
|
|
71
|
-
*
|
|
72
|
+
* perk-checkpoints reference: any load/resolution failure returns the reference id so perk's own
|
|
73
|
+
* checkpoints keep working — the default path is the hard guarantee. The catch narrows to genuine
|
|
74
|
+
* file-read/parse failures (the resolver is per-seam fail-open) and is logged, never silent.
|
|
72
75
|
*/
|
|
73
76
|
export function resolvedTodoProviderId(cwd: string): string {
|
|
74
77
|
try {
|
|
75
78
|
return resolveProviders(loadPerkConfig(cwd).providers, loadProviders()).todo.id;
|
|
76
|
-
} catch {
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(
|
|
81
|
+
`perk: todo provider resolution failed — falling back to ${PERK_CHECKPOINTS_PROVIDER_ID}: ${error}`,
|
|
82
|
+
);
|
|
77
83
|
return PERK_CHECKPOINTS_PROVIDER_ID;
|
|
78
84
|
}
|
|
79
85
|
}
|
|
@@ -339,12 +345,7 @@ function renderStatus(
|
|
|
339
345
|
ctx,
|
|
340
346
|
WIDGET_SLOT_CHECKPOINTS,
|
|
341
347
|
(_tui: unknown, theme: ThemeLike) => ({
|
|
342
|
-
render: (width: number) =>
|
|
343
|
-
truncateToWidth(
|
|
344
|
-
theme.fg("dim", `Plan #${coarse.planId}: prose plan — no \`## Steps\` checklist`),
|
|
345
|
-
width,
|
|
346
|
-
),
|
|
347
|
-
],
|
|
348
|
+
render: (width: number) => renderCoarsePlanLines(coarse.planId, theme, width),
|
|
348
349
|
invalidate: () => {},
|
|
349
350
|
}),
|
|
350
351
|
{ placement: "belowEditor" },
|
|
@@ -376,6 +377,12 @@ function renderStatus(
|
|
|
376
377
|
* `session_start` AND `session_tree`; advances on `turn_end`; lists via `/checkpoints`.
|
|
377
378
|
*/
|
|
378
379
|
export function registerCheckpoints(pi: ExtensionAPI, status: PerkStatusHandle): void {
|
|
380
|
+
// Transcript marker for `perk:checkpoint` snapshots (audit §2.3): renderer body in surfaces.ts,
|
|
381
|
+
// registration = wiring, feature-detect inside the seam (pre-0.80.4 hosts stay inert). No
|
|
382
|
+
// todo-provider deferral here: entries exist only when perk's checkpoints appended them, so
|
|
383
|
+
// rendering history stays correct under any later provider selection.
|
|
384
|
+
registerTranscriptRenderer(pi, CHECKPOINT_TYPE, checkpointEntryRenderer);
|
|
385
|
+
|
|
379
386
|
pi.on("session_start", async (_event, ctx) => {
|
|
380
387
|
try {
|
|
381
388
|
// Todo-provider deferral: when a foreign `[providers] todo` is selected, step the progress
|
|
@@ -502,7 +509,7 @@ export function registerCheckpoints(pi: ExtensionAPI, status: PerkStatusHandle):
|
|
|
502
509
|
try {
|
|
503
510
|
if (!isPerkCheckpointsReferenceSelected(ctx.cwd)) return;
|
|
504
511
|
const branch = branchOf(ctx);
|
|
505
|
-
if (branch
|
|
512
|
+
if (branchCarries(branch, STEPS_CONTEXT_TYPE)) return;
|
|
506
513
|
const state = rebuildCheckpoint(branch);
|
|
507
514
|
if (isInert(state) || !isGeneratedState(ctx.cwd, state)) return;
|
|
508
515
|
return {
|
|
@@ -227,8 +227,8 @@ export async function resolveReviewThreads(
|
|
|
227
227
|
|
|
228
228
|
const TOOL_GUIDELINES = [
|
|
229
229
|
"Call resolve_review_threads only AFTER you have applied (and committed) fixes for the actionable items — it replies-then-resolves the threads you pass.",
|
|
230
|
-
"Pass threads as [{thread_id, comment?}] using the thread_id values from the perk.review-classifier child's structured output; the optional comment is posted as a reply before resolving.",
|
|
231
|
-
"Judgment and edits stay with you (the parent) — never delegate the fix; the
|
|
230
|
+
"Pass resolve_review_threads `threads` as [{thread_id, comment?}] using the thread_id values from the perk.review-classifier child's structured output; the optional comment is posted as a reply before resolving.",
|
|
231
|
+
"Judgment and edits stay with you (the parent) — never delegate the fix; the classifier child that feeds resolve_review_threads is read-only and classification-only.",
|
|
232
232
|
];
|
|
233
233
|
|
|
234
234
|
/** Resolve the active plan-ref (worktree first, then the rebuilt workflow-state). The converged
|
|
@@ -248,7 +248,7 @@ function activePlanRef(ctx: ExtensionContext): PlanRef | null {
|
|
|
248
248
|
|
|
249
249
|
/** Inject the address-workflow guidance the model follows (the perk-address skill pointer is
|
|
250
250
|
* delivered by the skill-binding suffix — not hardcoded here). When `model` is set, the
|
|
251
|
-
* `perk.review-classifier` spawn carries an inline `model` override ([subagents] review-classifier);
|
|
251
|
+
* `perk.review-classifier` spawn carries an inline `model` override ([models.subagents] review-classifier);
|
|
252
252
|
* otherwise the agent's frontmatter default is used.
|
|
253
253
|
*
|
|
254
254
|
* The wording lives in the shared canonical templates `prompts/stages/address/*` rendered via the
|
|
@@ -258,7 +258,7 @@ function activePlanRef(ctx: ExtensionContext): PlanRef | null {
|
|
|
258
258
|
* `model_clause` render var. */
|
|
259
259
|
export function addressGuidance(ref: PlanRef, preview: boolean, model?: string): string {
|
|
260
260
|
const modelClause = model
|
|
261
|
-
? `, passing \`model: "${model}"\` on that call (the configured [subagents] review-classifier model)`
|
|
261
|
+
? `, passing \`model: "${model}"\` on that call (the configured [models.subagents] review-classifier model)`
|
|
262
262
|
: "";
|
|
263
263
|
const variables = {
|
|
264
264
|
provider: ref.provider,
|
|
@@ -120,22 +120,26 @@ export function decodeAskUserParams(params: unknown): { question: string; option
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const TOOL_GUIDELINES = [
|
|
123
|
-
"Prefer
|
|
124
|
-
"Ask ONE focused question
|
|
125
|
-
"Provide `options` when the answer is a choice — a free-text escape is always added.",
|
|
126
|
-
"Explore the codebase
|
|
123
|
+
"Prefer ask_user_question during planning to resolve genuine ambiguity rather than guessing.",
|
|
124
|
+
"Ask ONE focused question per ask_user_question call, and wait for the answer before the next.",
|
|
125
|
+
"Provide ask_user_question `options` when the answer is a choice — a free-text escape is always added.",
|
|
126
|
+
"Explore the codebase before calling ask_user_question: if a question is answerable from the code, read it instead of asking.",
|
|
127
127
|
];
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* The resolved `[providers] askuser` selection id for `cwd`. Fail-safe to the perk-ask-user
|
|
131
|
-
* reference: any load/resolution failure
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* reference: any load/resolution failure returns the reference id so perk's own tool keeps
|
|
132
|
+
* registering — the default path is the hard guarantee. Mirror of `resolvedPlanProviderId`: the
|
|
133
|
+
* catch narrows to genuine file-read/parse failures (the resolver is per-seam fail-open) and is
|
|
134
|
+
* logged, never silent.
|
|
134
135
|
*/
|
|
135
136
|
export function resolvedAskUserProviderId(cwd: string): string {
|
|
136
137
|
try {
|
|
137
138
|
return resolveProviders(loadPerkConfig(cwd).providers, loadProviders()).askuser.id;
|
|
138
|
-
} catch {
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(
|
|
141
|
+
`perk: askuser provider resolution failed — falling back to ${PERK_ASK_USER_PROVIDER_ID}: ${error}`,
|
|
142
|
+
);
|
|
139
143
|
return PERK_ASK_USER_PROVIDER_ID;
|
|
140
144
|
}
|
|
141
145
|
}
|