@mgiles/perk 3.1.0 → 3.2.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/extension/doors/address.ts +11 -0
- package/extension/doors/dreamWaveTools.ts +29 -15
- package/extension/doors/land.ts +6 -0
- package/extension/doors/learn.ts +16 -3
- package/extension/doors/lifecycleGates.ts +36 -1
- package/extension/doors/objectiveStack.ts +423 -23
- package/extension/doors/plannotatorHandoff.ts +80 -8
- package/extension/doors/prReview.ts +2 -1
- package/extension/doors/prReviewBrowser.ts +75 -27
- package/extension/doors/ready.ts +209 -17
- package/extension/doors/reviewWaveTools.ts +24 -3
- package/extension/doors/stackReviewBrowser.ts +573 -0
- package/extension/doors/submit.ts +36 -10
- package/extension/doors/submitPrReview.ts +116 -19
- package/extension/factories/objectivePlan.ts +12 -6
- package/extension/factories/objectiveSave.ts +5 -2
- package/extension/index.ts +26 -1
- package/extension/substrate/config.ts +4 -2
- package/extension/substrate/paths.ts +2 -7
- package/extension/substrate/resolverLease.ts +363 -0
- package/extension/substrate/toolGating.ts +16 -0
- package/extension/substrate/workflowState.ts +13 -3
- package/extension/waves/adversarialReviewWave.ts +16 -2
- package/package.json +1 -1
- package/prompts/_fixtures/live.yaml +63 -0
- package/prompts/contexts/adapters/tombell-plan.md +4 -0
- package/prompts/contexts/plan-authoring.md +6 -5
- package/prompts/stages/conflict-resolution-continuation.md +6 -0
- package/prompts/stages/conflict-resolution.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-author/seed.md +1 -1
- package/prompts/stages/objective-reconcile-ready.md +7 -0
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/stack-review/cold.md +1 -0
- package/prompts/stages/stack-review-browser/stack.md +23 -0
- package/shared/README.md +0 -3
- package/shared/bindings.yaml +3 -0
- package/shared/contracts.md +2010 -1753
- package/shared/registry.yaml +16 -1
- package/shared/schemas/outputs/objective-stack-status.schema.json +172 -1
- package/shared/schemas/outputs/pr-ready.schema.json +110 -2
- package/shared/contracts-history.md +0 -605
|
@@ -1,605 +0,0 @@
|
|
|
1
|
-
# perk cross-plane contracts — history
|
|
2
|
-
|
|
3
|
-
The changelog sibling of [`contracts.md`](./contracts.md). It carries the relocated chronological
|
|
4
|
-
`Status (…)` history so the spec file stays a compact current-spec document — the durable `## §N.M`
|
|
5
|
-
contract bodies live in `contracts.md`; the present-tense-of-a-past-node landing notes live here.
|
|
6
|
-
This file ships in **both** build artifacts alongside `contracts.md` (the whole `shared/` dir is
|
|
7
|
-
bundled — the Python wheel as package data `perk/_shared/`, the npm package under `shared/`).
|
|
8
|
-
|
|
9
|
-
## Entry convention
|
|
10
|
-
|
|
11
|
-
- Entries are **grouped by the originating `§N.M` anchor**, in `contracts.md`'s section order.
|
|
12
|
-
- **Chronological within** each group (oldest landing first).
|
|
13
|
-
- Each entry is the original `Status (…)` blockquote **verbatim** — keep-and-annotate, never
|
|
14
|
-
reword, never "fix" a now-stale claim (the relocation is mechanical; reconciliation judgment
|
|
15
|
-
stays out).
|
|
16
|
-
- Each group's `§N.M` heading **is** the cross-reference anchor.
|
|
17
|
-
- **Exception:** document-opening statuses not bound to a single section live under the leading
|
|
18
|
-
**"General / opening"** group below.
|
|
19
|
-
|
|
20
|
-
## General / opening
|
|
21
|
-
|
|
22
|
-
> **Status (T2):** specs locked. Implementations land later — state helpers in **T3**, the
|
|
23
|
-
> launch/`PERK_RUN_ID` emit in **T4**, the gateway verification ops in **T5** (Python) /
|
|
24
|
-
> Phase 1 (TS). Gateway *mutation* ops are named here but **not authored** (payloads land in
|
|
25
|
-
> Phase 1, when `/plan-save` knows their shape — `Q7`/`Q9`).
|
|
26
|
-
>
|
|
27
|
-
> **Status (T5):** the §8.4 **verification ops are implemented in the Python plane**
|
|
28
|
-
> (`perk/github/auth.py` — `check_auth` / `check_repo_access`, verification-only, never mutating);
|
|
29
|
-
> the TS plane authors the same shapes in Phase 1. The §8.5 init machine-surface contract is
|
|
30
|
-
> live (`perk init --json`).
|
|
31
|
-
>
|
|
32
|
-
> **Status (P1.T2a):** the §8.4 **plan-write mutations are implemented in the Python plane**
|
|
33
|
-
> (`perk/github/plans.py` `create_label` / `create_plan_issue` / `add_issue_comment` /
|
|
34
|
-
> `find_plan_issue` + `perk/plan.py` storage) — the **cold/worker** save door
|
|
35
|
-
> (`perk plan-save`). The warm in-session twin (the TS `/plan-save` tool) is T3. Both planes
|
|
36
|
-
> use **REST `gh api`** (never porcelain — porcelain's GraphQL has a separate, often-exhausted
|
|
37
|
-
> rate-limit quota) and pass large bodies via `-F body=@file`.
|
|
38
|
-
|
|
39
|
-
## §8.4 · The GitHub gateway contract (Q9/Q10)
|
|
40
|
-
|
|
41
|
-
> **Status (P1.T2b):** the plan-ref is **materialized**. T2a emits it (`--json`); T2b persists
|
|
42
|
-
> it as the `cache.plan-ref` file (`.pi/workflow/plan-ref.json`, written by the cold door,
|
|
43
|
-
> read by both planes) and reconciles it into the `active_plan_ref` session field on
|
|
44
|
-
> `session_start` (§8.3).
|
|
45
|
-
>
|
|
46
|
-
> **Status (P1.T3):** the **warm door** is built. The in-session `plan_save` tool + `/plan-save`
|
|
47
|
-
> command **wrap** this cold `--json` write (via process launch + the §3.2 machine-JSON surface —
|
|
48
|
-
> **not** a TS reimplementation): they delegate to `perk plan-save --json`, then append
|
|
49
|
-
> `active_plan_ref` to link the live session. This is the read-only → read-write boundary; the
|
|
50
|
-
> plan→implement transition is the **cold door** (T4, fresh context). `save.writes` is now
|
|
51
|
-
> `[github.plan, cache.plan-ref, session.workflow-state]`.
|
|
52
|
-
>
|
|
53
|
-
> **Status (P1.T4a):** the **cold door** consumes the plan-ref. `perk implement` (no positional —
|
|
54
|
-
> the *active* ref; arbitrary `#N` is `perk resume`, T5c) reads `cache.plan-ref` from the repo root,
|
|
55
|
-
> **derives a deterministic worktree/branch name `plan-<pr_id>`** (`pr_id` stays a string), creates
|
|
56
|
-
> the worktree **idempotently** (an existing one is reused — resume), and **materializes the
|
|
57
|
-
> handoff + plan-ref into the worktree** so the launched `pi` (cwd = worktree) reconciles
|
|
58
|
-
> `active_plan_ref` on `session_start` (§8.3) with no extension change. The plan-header's `branch`
|
|
59
|
-
> field stays `null` until it is recorded at **submit** (T5a). `implement` reads `cache.plan-ref`
|
|
60
|
-
> and writes `session.workflow-state` (the worktree link).
|
|
61
|
-
>
|
|
62
|
-
> **Status (origin-aware create base).** On **create** (not reuse), `perk implement` does a
|
|
63
|
-
> **best-effort `git fetch origin`** and bases the new `plan-<pr_id>` branch on **`origin/<trunk>`**
|
|
64
|
-
> (trunk via `git symbolic-ref refs/remotes/origin/HEAD`, fallback `main`/`master`, final `main`) —
|
|
65
|
-
> so work starts on up-to-date trunk, not stale local HEAD. If the plan's branch already exists on
|
|
66
|
-
> the remote it bases off **`origin/<branch>`** (tracking the resumed/remote branch). A
|
|
67
|
-
> **`--base <ref>` override wins verbatim** (deliberate stacking on an unlanded branch, even a
|
|
68
|
-
> non-origin ref). An **offline fetch failure is non-fatal but warns loudly** and falls back to the
|
|
69
|
-
> last-known origin ref (or local HEAD when there is no remote — `base: null`). The
|
|
70
|
-
> **reuse/resume** path (an existing worktree) never fetches or re-bases (D4). `--dry-run`/`--json`
|
|
71
|
-
> surfaces the resolved start-point as a `base` field (resolved from local refs, no fetch). No
|
|
72
|
-
> registry I/O change.
|
|
73
|
-
>
|
|
74
|
-
> **Status (P1.T4c) — implement gains a plan arg + session priming.** The Phase-1 dogfood run
|
|
75
|
-
> surfaced two cold-door gaps and corrected them forward (T4a's no-positional D2 was the deviation
|
|
76
|
-
> from phase-1-plan §P1.T4's `perk implement <plan>`): (1) **`perk implement [PLAN]`** is now a
|
|
77
|
-
> *dedicated* command — an optional issue number (`perk implement 42`) resolves the plan via
|
|
78
|
-
> `github.get_plan`, writes it as the active `cache.plan-ref` (mirroring `perk resume`), then
|
|
79
|
-
> launches; omitting it uses the active ref (the T4a behavior). (2) The launcher **primes the
|
|
80
|
-
> implement session** — `launch_stage` passes an initial prompt to `pi` (read the plan via
|
|
81
|
-
> `gh issue view <n> --comments`, implement on the branch, `/submit` when committed) so the session
|
|
82
|
-
> starts working instead of opening idle. Only the `implement` stage is primed; `plan` stays
|
|
83
|
-
> user-driven. No registry I/O change (still `reads:[cache.plan-ref]`, `writes:[session.workflow-state]`).
|
|
84
|
-
>
|
|
85
|
-
> **Status (P1.T5a) + the delegation decision.** The §8.4 opening's "one contract, implemented
|
|
86
|
-
> **once per plane**" (a Python gateway *and* a TS gateway, same shapes) was a Phase-0 hypothesis.
|
|
87
|
-
> **T3 deviated** (the warm `/plan-save` delegates to `perk plan-save` via `pi.exec`), and T5
|
|
88
|
-
> **confirms delegation as the standing pattern for GitHub mutations**: the **Python gateway is
|
|
89
|
-
> canonical**; the TS warm doors (`/submit`, and `/land` in T5b) **delegate** to thin Python workers
|
|
90
|
-
> (`perk pr submit`/`perk pr land --json`) over the §3.2 machine-JSON channel — they do **not**
|
|
91
|
-
> reimplement the writes. (Cache/session tiers keep their per-plane I/O — `cache.ts`/`cache.py` —
|
|
92
|
-
> because those are *files*, not GitHub.) The "two gh gateways" idea is retired; there is **one
|
|
93
|
-
> canonical Python GitHub gateway**. So **T5a** opens a **draft** PR (`Closes #<issue>` so the
|
|
94
|
-
> squash-merge closes the plan), then `update_plan_header` populates the staged `branch=plan-<pr_id>`,
|
|
95
|
-
> `pr=<number>`, `lifecycle_stage=impl`. `submit` reads `cache.plan-ref` + `github.plan` and writes
|
|
96
|
-
> `github.pr` + `github.plan`.
|
|
97
|
-
>
|
|
98
|
-
> **Status (P1.T5b):** the **land path** is built. `land` (warm `/land` + cold `perk pr land`)
|
|
99
|
-
> marks the PR ready (if draft), **squash-merges** it (idempotent — `already merged` ⇒ success), and
|
|
100
|
-
> sets the **`pending-learn`** marker; `learn` (warm `/learn`, TS-only) clears it. The cold worker
|
|
101
|
-
> sets the marker on its real run; the warm door also sets it post-delegate (idempotent existence
|
|
102
|
-
> file), so each plane's path is independently correct. `land` reads `cache.plan-ref` + `github.pr`
|
|
103
|
-
> and writes `github.pr` + `cache.markers`; `learn` reads/writes `cache.markers`. Reconciliation
|
|
104
|
-
> typing + the review/`address` loop + deep learn tooling stay Phase 2.
|
|
105
|
-
>
|
|
106
|
-
> **Status (P1.T5c):** `perk resume <plan>` is built — the cross-stage verb. It reads the plan via
|
|
107
|
-
> `get_plan`, **reconstructs `cache.plan-ref`** from the GitHub state, derives the **current
|
|
108
|
-
> actionable stage** (no PR → `implement`; PR open → `submit`; PR merged + `pending-learn` →
|
|
109
|
-
> `learn`; merged + learned → nothing), then reuses T4a's `launch_stage` (idempotent worktree +
|
|
110
|
-
> materialize + `exec pi`). `--dry-run`/`--json` resolve + print without launching (no ref write).
|
|
111
|
-
> The resolution is a **pure, unit-tested** function (`perk/run/resume.py`). For `reuse` stages
|
|
112
|
-
> (`submit`/`land`/`learn`) it assumes a **local** worktree; recreating one from a remote branch on
|
|
113
|
-
> a fresh clone is Phase 2. This closes the spine: `plan → save → implement → submit → land →
|
|
114
|
-
> learn`, resumable at any stage.
|
|
115
|
-
>
|
|
116
|
-
> **Status (P1.T6 — the Phase-1 gate; + T4c/T3b corrections).** The spine is **closed end-to-end and
|
|
117
|
-
> dogfooded** — perk shipped a real change (`prek` + a ruff hook) through its own loop on its own
|
|
118
|
-
> repo (plan #1 → PR #2 merged → learned; `perk resume 1` reports "nothing to resume"). The gate run
|
|
119
|
-
> is recorded in [`phase-1-gate.md`](../docs/planning/phase-1-gate.md). Two dogfood-surfaced fixes
|
|
120
|
-
> converged forward: **T4c** — `perk implement [PLAN]` takes a plan arg and `launch_stage` **primes**
|
|
121
|
-
> the implement session (it launched bare/idle before); **T3b** — `save` fails fast while plan mode
|
|
122
|
-
> is active and the `plan_save` tool (explicit `plan` param) is the canonical save (the borrowed
|
|
123
|
-
> `pi-plan` emits no structured plan, so the `<proposed_plan>` scrape was dropped). Neither changed
|
|
124
|
-
> any stage's state-I/O. The registry per-stage `requires`/`reads`/`writes` + `doors` are filled for
|
|
125
|
-
> all six spine stages.
|
|
126
|
-
>
|
|
127
|
-
> **Status (P2.T8a):** the **submit body is deepened + the issue-numbered-footer bug is fixed**.
|
|
128
|
-
> `perk pr submit` composes an HTML-enhanced GitHub PR body (best-effort verbatim-plan `<details>`
|
|
129
|
-
> embed via `get_plan_body`) and appends the checkout footer via **create-then-update**
|
|
130
|
-
> (`update_pr_body`) carrying the **PR** number, then runs `validate_pr_body` as a post-write
|
|
131
|
-
> self-check (`pr_check_failed` on failure). A thin `perk pr check --json` is the supervisor surface.
|
|
132
|
-
> Submit keeps the PR **draft**; the new `perk pr ready` (warm `/ready`) is the deliberate review
|
|
133
|
-
> gate. The two-target split is explicit: HTML in the GitHub body, plain text in the squash commit
|
|
134
|
-
> (deepened at T8b). `submit`'s registry I/O is unchanged.
|
|
135
|
-
>
|
|
136
|
-
> **Status (P2.T8b):** `/land` + `/learn` are **deepened**. Land's squash commit message is now
|
|
137
|
-
> plain `"<plan title>\n\nCloses #N"` (fallback on empty title) — the second of the two PR targets.
|
|
138
|
-
> `/learn` graduates to a real knowledge-capture pass: with a `summary` it creates a `perk:learn`
|
|
139
|
-
> issue (idempotent via the **`perk:learn`-scoped `find_learn_issue`** — label + `learn-header`
|
|
140
|
-
> block, so it never matches the plan issue) + a back-link comment, then clears `pending-learn`;
|
|
141
|
-
> without one it stays the thin marker-clear. `learn` reads `[cache.markers, cache.plan-ref]` and
|
|
142
|
-
> writes `[cache.markers, github.learn, github.comments]` (the new `github.learn` key). The
|
|
143
|
-
> reconciliation-typing vocabulary (Mechanical/Reconcilable/Immutable) is established; only the
|
|
144
|
-
> deterministic **Mechanical** type is applied this turn (Reconcilable + objective reconciliation are
|
|
145
|
-
> **implemented in P2.T11** — see the P2.T11 subsection of §8.4).
|
|
146
|
-
>
|
|
147
|
-
> **Status (P2.T8c — the CLI plumbing slice).** The `--remote` stub graduates to a real **target
|
|
148
|
-
> resolver** (`launch.resolve_target(stage, remote) -> Target`, pure + unit-tested): `None` → local
|
|
149
|
-
> (unchanged); a `cold_remote:false` stage → `UserFacingCliError`/`remote_blocked`; a
|
|
150
|
-
> `cold_remote:true` stage → a `RemoteTarget` descriptor (runner ref + run_id→plan linkage) surfaced
|
|
151
|
-
> in `--dry-run`/`--json`, then a stable `UserFacingCliError`/`remote_not_driven` exit (it does **not**
|
|
152
|
-
> persist intent or trigger a runner — the Phase-3 consumer is not built, cli-vs-pi §4.5). The
|
|
153
|
-
> registry now records `doors.cold_remote: true` on **`implement` + `address`** (the agentic,
|
|
154
|
-
> headless-runnable stages a Phase-3 CI worker drives) and `false` on the other five — the reused
|
|
155
|
-
> seam = resolver + validated registry doors + the `--json` target descriptor. **Phase 2 builds and
|
|
156
|
-
> resolves the target; Phase 3 drives it.** The `--remote` help text on the three launchers is
|
|
157
|
-
> reconciled from "Phase 3; currently blocked" to "Local (default) or a remote runner; remote
|
|
158
|
-
> dispatch is driven by the Phase-3 worker."
|
|
159
|
-
|
|
160
|
-
## §8.9 · Skill bindings (the trigger→skill delivery contract)
|
|
161
|
-
|
|
162
|
-
> **Status (Node 2.3):** cold-door (Python) **and** warm-door (TS) delivery landed, **and** perk's own
|
|
163
|
-
> hardcoded "Follow the … skill" strings are migrated onto the mechanism + deleted (Node 2.3) — the
|
|
164
|
-
> skill-binding mechanism is now the single delivery path for perk's own nudges. The render header
|
|
165
|
-
> was neutralized to `"The following skill binding(s) apply here:"` (the `.pi/perk.toml` parenthetical
|
|
166
|
-
> was false for the delivered perk defaults). Known residual (out of scope, documented): in a cold
|
|
167
|
-
> `learn-docs` session, after compaction Mechanism A re-renders the borrowed `stage:plan` and injects
|
|
168
|
-
> `perk-plan` rather than `perk-learn-docs` — benign (learn-docs *is* a planning factory); a
|
|
169
|
-
> pre-existing stage-vs-command `binding_trigger` quirk. Deferred: `doctor` target-existence
|
|
170
|
-
> validation → **Node 3.1**; `init` `[[bindings]]` template + user docs → **Node 3.2**.
|
|
171
|
-
>
|
|
172
|
-
> **Status (Node 3.1):** `doctor` target-existence/skill-presence validation landed (the non-fatal
|
|
173
|
-
> `bindings` check), plus the injection-time skill-presence mirror (the `nudge` path warns;
|
|
174
|
-
> `bindingSuffix` now logs its warnings). Deferred: `init` `[[bindings]]` commented template + user
|
|
175
|
-
> docs → **Node 3.2**.
|
|
176
|
-
>
|
|
177
|
-
> **Status (Node 3.2):** the `init` `[[bindings]]` commented template + user docs landed, resolving
|
|
178
|
-
> the deferral above. `PERK_TOML_TEMPLATE` now seeds a comment-only `[[bindings]]` block documenting
|
|
179
|
-
> `trigger` / `skill` / `mode` and the nudge-vs-transclude choice; `PERK_LOCAL_TOML_TEMPLATE` records
|
|
180
|
-
> the whole-array-replace override rule. README gains a `## Skill bindings` user section. The seeded
|
|
181
|
-
> block is inert (comment-only) — a fresh repo still resolves to zero user bindings and `doctor`
|
|
182
|
-
> stays exit-0 (pinned by a `tests/test_config.py` regression).
|
|
183
|
-
|
|
184
|
-
## §8.10 · Provider selection (the supported-set registry + the `[providers]` selection)
|
|
185
|
-
|
|
186
|
-
> **Status (Node 2.1):** ships the selection **substrate** only — `shared/providers.yaml`, the two
|
|
187
|
-
> shape-only loaders + the pure resolver, the `[providers]` config-reading in both planes, the
|
|
188
|
-
> two-directional `init` wiring, and the `doctor` selection cross-check. The concrete adapter shims
|
|
189
|
-
> (`planAdapterTombell`, `todoAdapterJuicesharp`) are **Nodes 2.3 / 3.2**; the read-only tool-gate
|
|
190
|
-
> (`extension/substrate/toolGating.ts`, Invariant 1) is untouched.
|
|
191
|
-
>
|
|
192
|
-
> **Status (Node 2.2):** lands the TS resolver (`resolveProviders`) and the **plan-seam runtime
|
|
193
|
-
> deferral** — perk's `planMode` authoring surface (`/plan`, `Ctrl+Alt+P`, `--plan`, the
|
|
194
|
-
> `perk:plan-context` injection) steps aside when the resolved `[providers] plan` ≠ `perk-plan`
|
|
195
|
-
> (fail-safe to the reference). `savePlan`/`plan_save`/`/plan-save`/the read-only gate are
|
|
196
|
-
> seam-shared substrate — always-registered, the produced-contract landing the Node 2.3 adapter
|
|
197
|
-
> bridges to — and do **not** defer. The **todo**-seam deferral (`checkpoints`) is still **Node 3.1**.
|
|
198
|
-
>
|
|
199
|
-
> **Status (Node 2.3):** the **first 3rd-party plan adapter** lands `tombell-plan` as a real,
|
|
200
|
-
> selectable plan provider. (1) The shipped entry drops `package_filter` (the illustrative
|
|
201
|
-
> `extensions/*.ts` matched nothing — `@tombell/pi-plan`'s sole extension is its root `index.ts`,
|
|
202
|
-
> so omitting the filter loads exactly that one extension); the `package_filter` field stays in the
|
|
203
|
-
> vocabulary for future providers. (2) perk's plan surface now **vacates at REGISTRATION time** (not
|
|
204
|
-
> just handler-time): `registerPlanMode` resolves the plan provider once at factory time and, under a
|
|
205
|
-
> foreign selection, registers NONE of `/plan` / `Ctrl+Alt+P` / `--plan` / the injection — so the
|
|
206
|
-
> foreign surfaces are the sole registrants (Pi suffixes duplicate command names, so handler-time
|
|
207
|
-
> deferral alone is insufficient once the foreign package is loaded). Fail-safe to the reference
|
|
208
|
-
> registers everything. (3) The new `extension/adapters/planAdapterTombell.ts` shim is an **injection-only**
|
|
209
|
-
> bridge — always registered, inert unless `[providers] plan = "tombell-plan"`, injecting a hidden
|
|
210
|
-
> `perk:plan-adapter-tombell` context that directs the foreign free-form prose `/plan` output into
|
|
211
|
-
> perk's canonical save. The prose→plan-ref bridge **reuses the existing** `/plan-save`
|
|
212
|
-
> `extractPlanMarkdown` scrape (planSave.ts); no new save machinery. The shim **never** owns or
|
|
213
|
-
> duplicates the read-only gate and **never** calls `setActiveTools` (Invariant 1 — the gate stays
|
|
214
|
-
> perk's, engaged by the cold-door launch; the foreign package self-enforces ad-hoc). (4) The adapter
|
|
215
|
-
> does **NOT** restamp `cache.plan-ref.provider` — a tombell-authored prose plan lands with
|
|
216
|
-
> `provider="github"` exactly like a perk-authored plan; the authoring-provider id lives only in the
|
|
217
|
-
> `[providers] plan` selection, and all downstream stages bind only to the provider-agnostic
|
|
218
|
-
> plan-ref (unchanged).
|
|
219
|
-
>
|
|
220
|
-
> **Status (Node 3.1):** lands the **todo-seam runtime deferral** — perk's `checkpoints` reference
|
|
221
|
-
> surface (`session_start`/`session_tree`/`turn_end` render + the `/checkpoints` command) steps
|
|
222
|
-
> aside when the resolved `[providers] todo` ≠ `perk-checkpoints` (`resolvedTodoProviderId` /
|
|
223
|
-
> `isPerkCheckpointsReferenceSelected`, fail-safe to the reference) — the exact todo-seam mirror of
|
|
224
|
-
> the Node 2.2 plan-seam deferral: silent early-returns on the event handlers, an announced deferral
|
|
225
|
-
> on `/checkpoints`. The pure checkpoint helpers + the `perk:checkpoint` entry + `## Steps` seeding
|
|
226
|
-
> are seam-shared substrate (untouched). **Runtime** deferral only — the concrete
|
|
227
|
-
> `@juicesharp/rpiv-todo` adapter is **Node 3.2** (which, per Correction 1 below, adds **no**
|
|
228
|
-
> registration-time vacating: the todo seam has no command-name collision, so runtime deferral is
|
|
229
|
-
> already sufficient — the forward-assumption here that registration-time vacating would be needed
|
|
230
|
-
> turned out not to transfer from the plan seam).
|
|
231
|
-
>
|
|
232
|
-
> **Status (Node 3.2):** the **first 3rd-party todo adapter** lands `juicesharp-todo` as a real,
|
|
233
|
-
> selectable todo provider (no longer illustrative); the todo seam is **behavior-complete**. (1) The
|
|
234
|
-
> shipped entry carries no `package_filter` (single-concern checklist overlay — mirrors the tombell
|
|
235
|
-
> case). (2) **NO registration-time vacating** (an explicit deviation from the Node 3.1
|
|
236
|
-
> forward-assumption): the plan seam needed it only because perk and `@tombell/pi-plan` both register
|
|
237
|
-
> `/plan` (Pi suffixes duplicate names); the todo seam has **no command-name collision** — perk
|
|
238
|
-
> registers `/checkpoints`, the foreign overlay registers its own differently-named command(s) — so
|
|
239
|
-
> Node 3.1's runtime deferral is already sufficient. (3) The new
|
|
240
|
-
> `extension/adapters/todoAdapterJuicesharp.ts` shim is an **injection-only**, **active-workflow-gated**
|
|
241
|
-
> (`active_plan_ref != null`) bridge — always registered, inert unless `[providers] todo =
|
|
242
|
-
> "juicesharp-todo"`, injecting a hidden `perk:todo-adapter-juicesharp` context that carries perk's
|
|
243
|
-
> implement-progress **discipline** (seed from `## Steps`, mark each item complete in order) onto the
|
|
244
|
-
> foreign overlay. (4) It does **NOT** write `perk:checkpoint` or revive the deferred marker scanner
|
|
245
|
-
> (Correction 2): that entry is a transient TS-only overlay nothing downstream consumes, and perk's
|
|
246
|
-
> render + scanner are already deferred (Node 3.1), so re-populating it would be dead duplication —
|
|
247
|
-
> the lighter bridge the todo seam's lack of a downstream consumer permits. The shim **never** owns
|
|
248
|
-
> the read-only gate, **never** `setActiveTools`, and **never** restamps any provider field.
|
|
249
|
-
> Validation record: `docs/design/provider-smoke-juicesharp-todo.md`.
|
|
250
|
-
>
|
|
251
|
-
> **Status (plannotator-plan):** the **second 3rd-party plan adapter** lands `plannotator-plan`
|
|
252
|
-
> (→ `npm:@plannotator/pi-extension`, `adapter: planAdapterPlannotator`) — the first provider with
|
|
253
|
-
> the **AUGMENT posture** (contrast tombell's REPLACE posture). (1) Plannotator does **not** replace
|
|
254
|
-
> perk's plan surface: perk's `/plan` command, the `perk:plan-context` authoring injection, and the
|
|
255
|
-
> read-only gate **stay registered**; `registerPlanMode` is now a **three-tier** branch — full
|
|
256
|
-
> registration for `perk-plan` (and the fail-safe error path), a **partial vacate** under
|
|
257
|
-
> `plannotator-plan` (skip only the `--plan` flag + the `Ctrl+Alt+P` shortcut + the `--plan`
|
|
258
|
-
> session_start handler — the two real registration collisions; duplicate flag/shortcut
|
|
259
|
-
> registration is the potentially-fatal Pi behavior), and the full vacate for any other foreign id
|
|
260
|
-
> (tombell, unchanged). (2) **`plan_review` is the backend-neutral review door** (Node 2.5,
|
|
261
|
-
> `extension/factories/planReview.ts`): the `plan` param is **optional/fallback** — the reviewed plan
|
|
262
|
-
> resolves **file-first** via `resolvePlanSource` (the validated `plan-draft.md` artifact → the
|
|
263
|
-
> param; the **transcript tier is explicitly excluded from review** — no draft + no param
|
|
264
|
-
> soft-skips with `reason: "no_plan"` and a `plan_draft` redirect, since an approval would
|
|
265
|
-
> otherwise auto-save scraped conversation bytes). **Dispatch:** when `plannotator-plan` is
|
|
266
|
-
> selected the door runs the **event-bus bridge** (`createPlannotatorBridge`, kept in
|
|
267
|
-
> `extension/adapters/planAdapterPlannotator.ts`): it emits plannotator's published `plannotator:request`
|
|
268
|
-
> plan-review envelope on the in-process `pi.events` bus (pinned against
|
|
269
|
-
> `@plannotator/pi-extension@0.20.0`), awaits the in-payload `respond` handshake bounded at 5s,
|
|
270
|
-
> then awaits the human decision on `plannotator:review-result` (no decision timeout; honors the
|
|
271
|
-
> turn-abort signal). On **ANY other selection** (perk-plan, tombell, unknown ids) the door runs
|
|
272
|
-
> the **first-party in-TUI editor review** (`runFirstPartyReview`): the plan is displayed in pi's
|
|
273
|
-
> built-in `ctx.ui.editor` dialog (scrollable; Ctrl+G opens the user's external `$EDITOR`); a
|
|
274
|
-
> non-blank human edit differing from the displayed plan is **written back to the draft via
|
|
275
|
-
> `writePlanDraft` BEFORE the verdict** (reviewed bytes == artifact bytes == saved bytes — a
|
|
276
|
-
> failed write-back **aborts the review fail-open** with a loud `unavailable` warning, nothing
|
|
277
|
-
> saved); then a 3-option approve/deny/skip `ctx.ui.select` verdict, with optional deny feedback
|
|
278
|
-
> via a second editor dialog. **Esc anywhere = fail-open skip** (`reason: "dismissed"`,
|
|
279
|
-
> mirroring `ask_user_question`'s dismissal — deny is always explicit); `ctx.ui.editor` takes no
|
|
280
|
-
> AbortSignal, so `signal?.aborted` is checked between dialogs (the aborted arm wins over an
|
|
281
|
-
> in-flight dialog's result). An **APPROVED** decision (either backend) wires into the
|
|
282
|
-
> **`approvalSave` seam** (auto-save → D1a gate exit on success → a **terminating** result; on
|
|
283
|
-
> the first-party path the saved bytes carry any write-back edits and the result flags
|
|
284
|
-
> `edited: true`; the objective node link is recovered from the `objective_node_claim` carrier
|
|
285
|
-
> inside `savePlan`; a failed save is non-terminating, leaves the gate read-only, and directs
|
|
286
|
-
> the human `/plan-save` failsafe). A human **DENY** is strict: feedback returned with a
|
|
287
|
-
> directive to rewrite the working draft via `plan_draft` + re-review. **The objective-author
|
|
288
|
-
> arm (#352 Node 2.2):** in an objective-author session the door routes to
|
|
289
|
-
> `executeObjectiveReview` — the review subject is the **rendered objective draft** (§8.1's
|
|
290
|
-
> `readObjectiveDraft` + `renderObjectiveDraft`; the `plan` param is decoded first — a mistyped
|
|
291
|
-
> param still `bad_input` — but never a source), dispatched to the same backends; the
|
|
292
|
-
> first-party editor runs **view-only** (edits are never written back — deny+feedback is the
|
|
293
|
-
> change channel) with objective verdict labels. An **APPROVED** outcome (#352 Node 2.3) wires
|
|
294
|
-
> into the **`objectiveApprovalSave` seam** (the structured artifact is re-read at save time —
|
|
295
|
-
> never the rendered bytes → `saveObjective` → D1a gate exit on success): a successful save is a
|
|
296
|
-
> **terminating** result (`details.subject: "objective"`, `saved: true`, `gateExited`,
|
|
297
|
-
> `terminate: true`); a failed save is non-terminating, leaves the gate read-only, and directs
|
|
298
|
-
> the human `/objective-save` failsafe.
|
|
299
|
-
> **Fail-open semantics:** headless (`!ctx.hasUI`) / dismissed / handshake-timeout /
|
|
300
|
-
> `unavailable` / `error` all **soft-skip** with a result instructing the model to present the
|
|
301
|
-
> plan to the user directly — plan authoring never wedges. `plan_review` is in
|
|
302
|
-
> `READ_ONLY_TOOLS` so review happens **inside** plan mode, before the gate ever comes off.
|
|
303
|
-
> (3) The plannotator adapter shim is **injection-only again** (Node 2.5): it owns the hidden
|
|
304
|
-
> `perk:plan-adapter-plannotator` context (gate-active AND selected; **two content flavors, one
|
|
305
|
-
> customType** — the plan bridge context, or `OBJECTIVE_ADAPTER_PLANNOTATOR_CONTEXT` in an
|
|
306
|
-
> objective-author session, whose marker `[OBJECTIVE ADAPTER: PLANNOTATOR]` the strip handler
|
|
307
|
-
> also covers)
|
|
308
|
-
> plus the bridge core, and otherwise keeps the standard adapter hygiene: never
|
|
309
|
-
> `setActiveTools`, never a `tool_call` handler, never restamps `cache.plan-ref.provider` (stays
|
|
310
|
-
> `"github"`); the door composes the gate and the save **only** through the `approvalSave` seam
|
|
311
|
-
> (never owns the gate, never writes GitHub itself). The catalog entry carries no
|
|
312
|
-
> `package_filter` (`pi.extensions: ["./"]` — the sole extension is the package root).
|
|
313
|
-
>
|
|
314
|
-
> **Status (Node 2.6):** the tombell bridge context is **re-aimed to review-first**
|
|
315
|
-
> (`plan_draft` → `plan_review` → the first-party in-TUI review → `approvalSave` auto-save), with
|
|
316
|
-
> the present + `/plan-save` flow as its explicit fail-open arm (see §8.10's interactive save
|
|
317
|
-
> discipline). The injection is now **conditioned** — it fires only when perk's gate is read-only
|
|
318
|
-
> (per the persisted `perk:workflow-state.mode`) **or** tombell's own persisted `plan-mode-state`
|
|
319
|
-
> entry has `enabled: true` (latest wins), and never in an objective-author session — replacing
|
|
320
|
-
> Node 2.3's unconditional-on-selection injection.
|
|
321
|
-
>
|
|
322
|
-
> **Status (askuser — the third seam, an INTERFACE seam):** a third seam, **`askuser`**, lets a repo
|
|
323
|
-
> swap perk's first-party `ask_user_question` tool (`extension/doors/askUser.ts`) for the foreign
|
|
324
|
-
> `@juicesharp/rpiv-ask-user-question` extension, which registers a tool with the **identical name**
|
|
325
|
-
> `ask_user_question` (a richer multi-question dialog). (1) **Interface seam, not artifact seam:**
|
|
326
|
-
> ask-user produces **no** durable state key or session-entry vocabulary (no `cache.plan-ref` /
|
|
327
|
-
> `perk:checkpoint` analogue); its stable contract is the **tool name `ask_user_question` + its
|
|
328
|
-
> non-terminating-answer semantics**. (2) **Vacate-only adapter** (`adapter: null` in
|
|
329
|
-
> `providers.yaml`, **no shim module**, no injected context): the foreign tool self-documents via
|
|
330
|
-
> its own `promptGuidelines`, so there is nothing to bridge. (3) **Registration-time vacating** in
|
|
331
|
-
> `registerAskUser` (mirroring the plan seam's `registerPlanMode`): because the foreign tool shares
|
|
332
|
-
> the **exact** name `ask_user_question` and tools — unlike commands — are **not** `:N`-suffixed
|
|
333
|
-
> (they replace/warn by extension load order, non-deterministically), `registerAskUser` resolves the
|
|
334
|
-
> provider id once at factory time (`resolvedAskUserProviderId(process.cwd())`, fail-safe to
|
|
335
|
-
> `perk-ask-user`) and **early-returns before `pi.registerTool`** under any foreign selection,
|
|
336
|
-
> leaving exactly one `ask_user_question` standing. The default/fail-safe path registers exactly as
|
|
337
|
-
> before (zero behavior change). (4) The foreign package is **two-directionally** wired by
|
|
338
|
-
> `_converge_provider_packages` (installed only when selected, removed on deselect), so under the
|
|
339
|
-
> default the foreign package is never loaded and perk's tool is the sole registrant. (5) **No
|
|
340
|
-
> `READ_ONLY_TOOLS` / `SDK_READ_ONLY_TOOLS` change:** `ask_user_question` is already in
|
|
341
|
-
> `READ_ONLY_TOOLS` (`extension/substrate/toolGating.ts`), so the foreign same-named tool is
|
|
342
|
-
> allowlisted in read-only/plan mode automatically (the shared-name allowlist precedent); the
|
|
343
|
-
> read-only notice interpolates `READ_ONLY_TOOLS` so it self-updates. `SDK_READ_ONLY_TOOLS`
|
|
344
|
-
> (`extension/worker/readOnlySession.ts`) intentionally does **not** include `ask_user_question`
|
|
345
|
-
> (headless children never prompt a human) — unchanged. Catalog entry carries no `package_filter`
|
|
346
|
-
> (verified manifest `{"extensions": ["./index.ts"]}`). Validation record:
|
|
347
|
-
> `docs/design/provider-smoke-juicesharp-ask-user.md`.
|
|
348
|
-
>
|
|
349
|
-
> **Status (footer — the fourth seam, a SECOND INTERFACE seam):** a fourth seam, **`footer`**, lets a
|
|
350
|
-
> repo swap perk's own footer (`installPerkFooter`, `extension/surfaces/surfaces.ts`) for a foreign
|
|
351
|
-
> footer package — either `powerline-footer` (→ `npm:pi-powerline-footer`) or `pi-bar-footer`
|
|
352
|
-
> (→ `npm:pi-bar`). (1) **Interface seam, not artifact seam** (mirrors askuser): the footer produces
|
|
353
|
-
> **no** durable state key or session-entry vocabulary; its “contract” is purely the rendered footer
|
|
354
|
-
> surface. (2) **Vacate-only adapter** (`adapter: null` for **both** foreign entries, **no shim
|
|
355
|
-
> module**, no injected context): both foreign footers already **render extension statuses**, so
|
|
356
|
-
> perk's composed `perk` `setStatus` slot (the objective + checkpoints segments, published
|
|
357
|
-
> unconditionally by `createPerkStatus`/`checkpoints.ts` independent of footer ownership) appears in
|
|
358
|
-
> the foreign footer automatically — the bridge is automatic, there is nothing to shim. (3)
|
|
359
|
-
> **Install-site (runtime) vacating, NOT registration-time vacating** (the key divergence from
|
|
360
|
-
> askuser/plan): perk installs its footer inside the `session_start` event handler (not at
|
|
361
|
-
> factory-bind), so the natural mechanism is a **runtime guard at that single install site, keyed off
|
|
362
|
-
> `ctx.cwd`** — `index.ts` calls `installPerkFooter` only when
|
|
363
|
-
> `isPerkFooterReferenceSelected(ctx.cwd)` (`extension/surfaces/footerProvider.ts`,
|
|
364
|
-
> `resolvedFooterProviderId` fail-safe to `perk-footer`). The easier tier: `ctx.cwd` flows through the
|
|
365
|
-
> event, so tests need no `process.chdir`. (4) The foreign package is **two-directionally** wired by
|
|
366
|
-
> `_converge_provider_packages` (installed only when selected, removed on deselect), so under the
|
|
367
|
-
> default (`perk-footer`) the foreign package is never loaded and perk owns the footer exactly as
|
|
368
|
-
> before (zero behavior change — the hard guarantee). (5) **No `surfaces.ts` change:** `perkFooter` /
|
|
369
|
-
> `installPerkFooter` stay the reference footer; the only change is whether `index.ts` calls it.
|
|
370
|
-
> Catalog entries carry no `package_filter` (each package ships a single footer extension).
|
|
371
|
-
>
|
|
372
|
-
> **Status (web — the fifth seam, a THIRD INTERFACE seam with a NOVEL foreign default):** a fifth
|
|
373
|
-
> seam, **`web`**, lets a repo swap its web-research provider among three packages: `pi-web-access`
|
|
374
|
-
> (the **default** — zero-config Exa search + content fetch + the bundled `librarian` skill,
|
|
375
|
-
> exactly today's behavior), `ollama-web-search` (→ `npm:@ollama/pi-web-search`, needs a local
|
|
376
|
-
> Ollama daemon) and `juicesharp-web-tools` (→ `npm:@juicesharp/rpiv-web-tools`, needs an API key;
|
|
377
|
-
> default provider Brave; registers a `/web-tools` command — no perk collision). (1) **Interface
|
|
378
|
-
> seam, not artifact seam** (mirrors askuser/footer): web produces **no** durable state key or
|
|
379
|
-
> session-entry vocabulary; its “contract” is the loose “web search + fetch capability is
|
|
380
|
-
> available”. (2) **The NOVEL property — the first non-null-package default:** perk owns **no**
|
|
381
|
-
> native web implementation, so the behavior-preserving reference (`pi-web-access`) is itself a
|
|
382
|
-
> **foreign npm package** — its `default: true` entry carries a **non-null `package`** (every prior
|
|
383
|
-
> seam's default was `package: null`). This needs **no** substrate change: `_converge_provider_packages`
|
|
384
|
-
> already builds `desired` from every resolved provider's truthy `package` and the managed-identity
|
|
385
|
-
> set from every non-null `package`, and `validate()` enforces only exactly-one-default-per-seam
|
|
386
|
-
> (it never required a default to be `package: null`). (3) **Vacate-only adapter** (`adapter: null`
|
|
387
|
-
> for **all three** entries, **no shim module**, no injected context) with **no surface to vacate**
|
|
388
|
-
> at all: perk registers **no** web tools of its own, so unlike askuser (registration-time vacating)
|
|
389
|
-
> or footer (install-site vacating) there is **nothing** to step aside — selection simply **swaps**
|
|
390
|
-
> which web package `_converge_provider_packages` installs. The entire seam is Python convergence +
|
|
391
|
-
> the census widening + the read-only allowlist. (4) **Static union allowlist, no normalization:**
|
|
392
|
-
> the three packages expose **divergent** tool names (`web_search`/`code_search`/`fetch_content`/
|
|
393
|
-
> `get_search_content` vs `ollama_web_search`/`ollama_web_fetch` vs `web_search`/`web_fetch`), and
|
|
394
|
-
> perk does **not** normalize them — `READ_ONLY_TOOLS` (`extension/substrate/toolGating.ts`) carries
|
|
395
|
-
> the **union** of all known web tool names, inert when a package is absent (the shared-name
|
|
396
|
-
> allowlist precedent). `SDK_READ_ONLY_TOOLS` (`extension/worker/readOnlySession.ts`) intentionally
|
|
397
|
-
> omits them (headless children) — unchanged. (5) The foreign package is **two-directionally** wired
|
|
398
|
-
> by `_converge_provider_packages` (installed only when selected, removed on deselect); under the
|
|
399
|
-
> default the committed `npm:pi-web-access` entry stays installed (now **provider-managed**, no
|
|
400
|
-
> longer in `BORROWED_PACKAGES`). (6) **`librarian` is accepted as lost under a foreign web
|
|
401
|
-
> selection** — it is pi-web-access-specific (it depends on `fetch_content`'s GitHub-clone path),
|
|
402
|
-
> documented and not re-homed. Catalog entries carry no `package_filter` (each package's sole
|
|
403
|
-
> extension is its root `./index.ts`, verified via `npm view <pkg> pi`).
|
|
404
|
-
>
|
|
405
|
-
> **Status (review — the sixth seam, the first DISPATCH seam; RETIRED 2026-07, Objective #1261
|
|
406
|
-
> Node 4.1):** a sixth seam, **`review`**, existed from the toolbox objective's nodes 1.x through
|
|
407
|
-
> 4.1 and is now **retired**. Its shape while live: the first **DISPATCH posture** — no adapter
|
|
408
|
-
> (the seam produces no durable artifact) and nothing to vacate (perk owned no prior guest-review
|
|
409
|
-
> surface); the selection drove **protocol dispatch** inside a dispatching `/review` door — which
|
|
410
|
-
> review surface the door drove (`hunk`, the default: the hunk session CLI handshake, with perk's
|
|
411
|
-
> `submit_pr_review` tool as the only posting path; or `plannotator-review`: the `code-review`
|
|
412
|
-
> `pi.events` bridge behind an `open_plannotator_review` tool plus the agent-driven
|
|
413
|
-
> external-annotations HTTP stream, with the UI's native platform-posting as the GitHub path) and
|
|
414
|
-
> which posting path was primary. The default `hunk` was the first `default: true` entry whose
|
|
415
|
-
> substrate is an **external CLI** (npm `hunkdiff`) rather than a Pi package;
|
|
416
|
-
> `plannotator-review` shared `npm:@plannotator/pi-extension` with `plannotator-plan`, the
|
|
417
|
-
> cross-seam instance of the desired-**union** package convergence. **The retirement (Node 4.1):**
|
|
418
|
-
> once the surface-named doors (`/pr-review-terminal`, `/pr-review-browser`) absorbed both arms
|
|
419
|
-
> (nodes 2.1–3.1), the dispatch had no job left — the command IS the selection. `/review`, its
|
|
420
|
-
> dispatch, `open_plannotator_review`, and the seam rows were deleted; `SEAMS`/`PROVIDER_SEAMS`
|
|
421
|
-
> shrank back to five; the retired `[providers] review` key got the legacy-tripwire treatment
|
|
422
|
-
> (hard-fail on the Python plane, silent-ignore on the TS plane); the hunk-CLI init/doctor
|
|
423
|
-
> handling was already unconditional (a review *surface*, not a selection consequence) and is
|
|
424
|
-
> unchanged; the plannotator package is desired via `plannotator-plan` alone (the union mechanism
|
|
425
|
-
> stays generic, its only cross-seam instance retired). `submit_pr_review` re-homed to
|
|
426
|
-
> `extension/doors/submitPrReview.ts` with an unchanged contract; the `perk-review` skill split
|
|
427
|
-
> into `perk-pr-review-terminal` + `perk-pr-review-browser`.
|
|
428
|
-
>
|
|
429
|
-
> **Status (askuser + todo seams — RETIRED 2026-08, Objective #1416):** both seams are retired to
|
|
430
|
-
> **required borrows** (`npm:@juicesharp/rpiv-ask-user-question`, `npm:@juicesharp/rpiv-todo` in
|
|
431
|
-
> `BORROWED_PACKAGES`) after the first-party deletions left each seam exactly one selectable
|
|
432
|
-
> provider (the borrow-vs-seam criterion: a seam with a single implementation is a borrow, not a
|
|
433
|
-
> seam); the census narrowed to `plan`/`footer`/`web`. The first-party `ask_user_question` tool
|
|
434
|
-
> and the checkpoint substrate (`extension/checkpoints/`, the `[WIP:n]`/`[DONE:n]` grammar,
|
|
435
|
-
> `/checkpoints`, `setStandingWidget`) are deleted; `RETIRED_PROVIDER_KEYS` grew `askuser` +
|
|
436
|
-
> `todo` (hard-fail with removal guidance on the Python plane; TS ignores); `step_marker` is
|
|
437
|
-
> deprecated/never-emitted (§8.12); the per-worktree `plan.md` snapshot is repurposed to review
|
|
438
|
-
> fidelity (§8.1). Nodes 1.1/2.1/2.2/2.3/2.4.
|
|
439
|
-
|
|
440
|
-
## §8.31 · The prompt render seam + golden parity (Objective #791, Node 1.2)
|
|
441
|
-
|
|
442
|
-
**First prompt moved onto the seam — the plan-read instruction (Node 2.1).** The cross-plane
|
|
443
|
-
plan-read instruction (the "how do I read the saved plan" SSOT) is the first real (non-fixture)
|
|
444
|
-
consumer of the render seam. Its three arm templates live at
|
|
445
|
-
`prompts/common/plan-read/{github,linear,other}.md` — one file per provider arm, no
|
|
446
|
-
conditionals/loops in the frozen subset. **Branching stays in code**: `perk/run/launch/prompts.py::
|
|
447
|
-
_plan_read_instruction` and `extension/doors/lifecycleGates.ts::planReadInstruction` keep their
|
|
448
|
-
`(provider, pr_id/prId, url)` signature and the same if/elif/else, each arm now a `render(...)` call
|
|
449
|
-
selecting its arm template (passing `{pr_id, url}`; jinja2/nunjucks ignore unused vars). The helpers
|
|
450
|
-
still branch on `cache.plan-ref.provider` — only the **wording source** moved.
|
|
451
|
-
|
|
452
|
-
The arm templates (and their golden files) carry **no trailing newline** — the helper returns
|
|
453
|
-
single-line strings embedded mid-prompt, so the render output must equal the prior literal exactly
|
|
454
|
-
(a deliberate departure from the fixture convention of trailing newlines). The three `plan-read-*`
|
|
455
|
-
golden cases in `cases.yaml` prove cross-plane byte-identity for each arm; a thin per-arm selection
|
|
456
|
-
test in each plane (`tests/test_worker_prompt_parity.py`, `extension/doors/lifecycleGates.test.ts`)
|
|
457
|
-
proves the code picks the right arm and `render()` is wired. This golden-fixture parity (plus the
|
|
458
|
-
selection tests) **replaces the prior dedicated substring parity** for plan-read; the
|
|
459
|
-
implement/learn prompt parity suites are untouched (they embed the byte-identical helper output, so
|
|
460
|
-
they keep passing — the downstream prompts move in nodes 2.2/2.4).
|
|
461
|
-
|
|
462
|
-
**Second prompt moved — the implement primer (Node 2.2).** The implement-stage primer wording lives
|
|
463
|
-
at `prompts/stages/implement.md`, the second real consumer of the render seam. All three sites that
|
|
464
|
-
used to hand-duplicate it — cold `perk/run/launch/prompts.py::_implement_prompt`, worker
|
|
465
|
-
`extension/worker/worker.ts::initialPromptFor` (implement arm), and warm
|
|
466
|
-
`extension/doors/lifecycleGates.ts::implementHandoffPrompt` — are now thin `render("stages/
|
|
467
|
-
implement.md", {provider, pr_id, url, read_cmd})` calls. The prior warm/cold variance (the warm
|
|
468
|
-
handoff omitting the "Progress markers:" tail) is reconciled by **unifying**: all three render the
|
|
469
|
-
one template with the same vars, so they are **byte-identical** and the warm handoff now carries the
|
|
470
|
-
progress markers too. `read_cmd` is the provider-selected plan-read instruction computed in code via
|
|
471
|
-
the Node-2.1 helper — branching stays in code, no `{% if %}`/second template. The template and its
|
|
472
|
-
golden (`implement-github`) carry **no trailing newline** (matching the prior cold/worker literal).
|
|
473
|
-
One golden case proves the template renders identically in both planes; thin per-plane composition
|
|
474
|
-
tests (start-with / contains read_cmd / ends-with the progress tail) prove each helper wires the
|
|
475
|
-
right template + vars — together these **replace `IMPLEMENT_SUBSTRINGS`**. The pre-objective audit
|
|
476
|
-
`docs/design/prompt-language-audit.md` (still describing warm as a shorter near-copy) is left as a
|
|
477
|
-
frozen snapshot; this paragraph is the authoritative current-state note.
|
|
478
|
-
|
|
479
|
-
**The address prompt moved onto the seam — converging three consumers (Node 2.3).** The
|
|
480
|
-
address-stage wording lives in two canonical templates `prompts/stages/address/{action,preview}.md`
|
|
481
|
-
(each a complete body, no template logic; vars `{{ provider }}`, `{{ pr_id }}`, `{{ url }}`,
|
|
482
|
-
`{{ model_clause }}`), rendered identically by **all three** address consumers via the shared
|
|
483
|
-
render seam: the cold `perk/run/launch/prompts.py::_address_prompt`, the worker
|
|
484
|
-
`extension/worker/worker.ts::initialPromptFor("address")`, and the warm
|
|
485
|
-
`extension/doors/address.ts::addressGuidance`. Before this node the warm `/address` loop used a
|
|
486
|
-
*different* wording; the three were **converged** onto one canonical body — the cold/worker
|
|
487
|
-
structure (the PR-identity header a fresh headless worker needs) **plus** warm's Plan File Mode
|
|
488
|
-
step, which now upgrades the cold/worker path too; warm loses its divergent framing. This is a
|
|
489
|
-
deliberate wording change to all three surfaces; the *command/flag/config* surface of `/address`
|
|
490
|
-
and `perk pr address` is unchanged.
|
|
491
|
-
|
|
492
|
-
**Branching stays in code** (the frozen subset has no conditionals): preview vs action is a
|
|
493
|
-
template *selection* (`preview.md` for `--preview`, which omits the action steps including Plan
|
|
494
|
-
File Mode; `action.md` otherwise), and the classifier present/absent split builds the
|
|
495
|
-
`model_clause` render var in code (empty string when no `[subagents] review-classifier` model) —
|
|
496
|
-
the clause's own wording is deferred to node 3.3. The worker has **no preview path** (preview is a
|
|
497
|
-
warm/cold flag), so it always renders `action.md`.
|
|
498
|
-
|
|
499
|
-
**The warm door is now ref-aware and null-guarded.** The converged body carries the PR identity, so
|
|
500
|
-
`addressGuidance` takes the active `PlanRef`; the `/address` handler resolves it via the same
|
|
501
|
-
helper `doors/learn.ts` uses (`readPlanRef(ctx.cwd)` → fallback
|
|
502
|
-
`rebuildWorkflowState(branchOf(ctx)).active_plan_ref`). A null ref reports a `warning` (mirroring
|
|
503
|
-
the `/implement` guard) and sends no guidance — a strict improvement, since `/address` cannot
|
|
504
|
-
function without a plan-ref regardless (the classifier child's `perk pr feedback` hard-errors
|
|
505
|
-
`no_plan_ref`).
|
|
506
|
-
|
|
507
|
-
The two address templates (and their golden files) carry **no trailing newline** (the builders
|
|
508
|
-
return mid-prompt strings). Four `address-*` golden cases in `cases.yaml` (action/preview × model
|
|
509
|
-
present/absent) prove cross-plane byte-identity; thin per-plane selection tests prove each caller
|
|
510
|
-
picks the right template and injects/omits the model clause, and the warm null-ref guard is
|
|
511
|
-
covered. This golden-fixture parity **replaces the prior `ADDRESS_SUBSTRINGS` substring parity**.
|
|
512
|
-
|
|
513
|
-
**The learn primer moved onto the seam (Node 2.4).** The learn-stage primer wording moved off its
|
|
514
|
-
two hand-concatenated twins onto the render seam — one canonical `prompts/stages/learn.md` rendered
|
|
515
|
-
byte-identical by cold `perk/run/launch/prompts.py::_learn_prompt` and warm
|
|
516
|
-
`extension/doors/learn.ts::learnGuidance` (learn has **no worker twin** — only cold + warm). Cold
|
|
517
|
-
and warm are **unified onto the cold body**: warm `/learn` wording changed from its prior numbered
|
|
518
|
-
"perk /learn —" style to the cold bullet "You are in the learn step…" body, the `other` arm
|
|
519
|
-
collapsed to a single "Open the plan and its merged change" line (warm **lost** its prior `other`
|
|
520
|
-
merged-PR derivation — an accepted change for the effectively-unreachable provider arm), and warm's
|
|
521
|
-
no-plan-ref fallback folded into the same template. This node is the **first template to use
|
|
522
|
-
conditionals**: the `{% if pr_id %}` header split and the no-ref / github+linear / other structure
|
|
523
|
-
selection are the template's conditional on `provider` (+ `pr_id` presence); the provider read-line
|
|
524
|
-
text is supplied as the `read_cmd` var from the node-2.1 plan-read helper (`_plan_read_instruction`
|
|
525
|
-
/ `planReadInstruction`), `read_cmd` passed always (empty string when absent) so it is defined. The
|
|
526
|
-
template keeps each `{% if %}`/`{% elif %}`/`{% else %}`/`{% endif %}` tag on its **own line** (off
|
|
527
|
-
the content lines) — enabled by the `trim_blocks` env flip above, which swallows the single newline
|
|
528
|
-
after each block tag so the indented bullet content renders intact (whitespace-control `{%- -%}`
|
|
529
|
-
markers alone could not — they also strip the bullets' leading indentation). The
|
|
530
|
-
template and all four golden files carry **no trailing newline** (matching the cold literal). Four
|
|
531
|
-
`learn-*` golden cases in `cases.yaml` (`learn-github`, `learn-linear`, `learn-other`,
|
|
532
|
-
`learn-no-ref`) prove cross-plane byte-identity and **replace the dedicated learn substring parity**;
|
|
533
|
-
thin per-plane selection/composition tests remain. nunjucks stays the TS engine — the golden suite
|
|
534
|
-
is the byte-parity proof that jinja2 and nunjucks render the conditional template identically (the
|
|
535
|
-
tag-hugging whitespace discipline keeps them equal with `trim_blocks`/`lstrip_blocks` off).
|
|
536
|
-
|
|
537
|
-
**The objective-read instruction moved onto the seam (Node 2.5).** The cross-plane objective-read
|
|
538
|
-
clause (the supplemental wording telling the model how to inspect a Linear-Project-backed
|
|
539
|
-
objective's node-issues) moved off its two hand-duplicated twins onto the render seam, mirroring the
|
|
540
|
-
plan-read move. The wording lives in a subdirectory at `prompts/common/objective-read/linear.md` —
|
|
541
|
-
one arm file for the **linear** arm only (github and any non-linear backend return `""` directly in
|
|
542
|
-
code without rendering, since `perk objective show` already covers them). **Branching stays in
|
|
543
|
-
code**: `perk/cli/commands/objective/shared.py::objective_read_instruction` and
|
|
544
|
-
`extension/factories/objectivePlan.ts::objectiveReadInstruction` keep their `(backend,
|
|
545
|
-
objective_id/objectiveId, url)` signature and the `backend != "linear" → ""` early return; the
|
|
546
|
-
linear arm computes the two **url-presence** render vars `where`/`fallback` in code (the frozen
|
|
547
|
-
subset has no conditionals — mirroring the `model_clause` precedent) and renders the one template.
|
|
548
|
-
|
|
549
|
-
The template (and its golden files) carry **no trailing newline** — the helper returns a single-line
|
|
550
|
-
string embedded mid-prompt, so the render output must equal the prior literal exactly (the
|
|
551
|
-
`_seed_prompt`/`factoryGuidance`/`reconcileGuidance` composition tests embed it and keep passing).
|
|
552
|
-
Two `objective-read-*` golden cases in `cases.yaml` (the linear arm, both url sub-variants) prove
|
|
553
|
-
cross-plane byte-identity; the empty github/other arm stays code-only (no render → no golden) and is
|
|
554
|
-
covered by the per-plane selection tests. Per-plane selection tests in each plane
|
|
555
|
-
(`tests/test_objective_prompt_parity.py`, `extension/factories/objectivePlan.test.ts`) prove the
|
|
556
|
-
code picks the right arm + computes where/fallback. This golden-fixture parity **replaces the prior
|
|
557
|
-
`OBJECTIVE_LINEAR_SUBSTRINGS` substring lockstep** (which remains only as a local constant for the
|
|
558
|
-
per-plane + seed-composition tests, no longer a cross-plane invariant). The `_seed_prompt` /
|
|
559
|
-
`factoryGuidance` / `reconcileGuidance` body moves are deferred to Node 2.6.
|
|
560
|
-
|
|
561
|
-
**The objective-plan factory seed + warm guidance moved onto the seam (Node 2.6).** The two
|
|
562
|
-
hand-built objective-plan-factory prompt bodies — the **cold** seed
|
|
563
|
-
(`perk/cli/commands/objective/plan_cmd.py::_seed_prompt`) and the **warm** guidance
|
|
564
|
-
(`extension/factories/objectivePlan.ts::factoryGuidance`) — moved onto the render seam as the sixth
|
|
565
|
-
real consumer. Unlike the implement (2.2) / learn (2.4) moves, they are **NOT unified**: the cold
|
|
566
|
-
seed launches a *fresh* read-only session, so it **injects** the objective title + node description
|
|
567
|
-
(the `<untrusted_objective>` block) and the pre-planning node-engagement block as DATA, and its node
|
|
568
|
-
is already marked `planning` by the cold door; the warm guidance runs *in-session*, so it
|
|
569
|
-
**instructs** the model to fetch the objective + node engagement and to mark the node `planning`
|
|
570
|
-
itself. This **cold-injects / warm-instructs** asymmetry makes them genuinely different bodies, so
|
|
571
|
-
they become **two arm files in a subdirectory** — `prompts/stages/objective-plan/{seed,guidance}.md`
|
|
572
|
-
(filenames mirror the function names) — like 2.1/2.3/2.5 landed despite singular node titles. The
|
|
573
|
-
**branching moved INTO the templates** as `{% if %}` conditionals (the learn-2.4 pattern, enabled by
|
|
574
|
-
`trim_blocks`): block-level tags on their own lines (the cold engagement block, the warm
|
|
575
|
-
node-selection line) and inline tags mid-line (the read clause, the explorer/model clause). The
|
|
576
|
-
helpers now pass **raw** vars — `node_engagement` (the rendered block, `""` when absent),
|
|
577
|
-
`read_clause` (the rendered linear clause, `""` for github/other), `model` (`""` when unset), and
|
|
578
|
-
(warm) `node` (`""` → select-next) — while the in-code arm SELECTION
|
|
579
|
-
(`objective_read_instruction` / `objectiveReadInstruction` backend logic) is unchanged. Both
|
|
580
|
-
templates and their golden files carry **no trailing newline** (the prior literals had none). Four
|
|
581
|
-
`objective-plan-*` golden cases in `cases.yaml` (seed/guidance × github/linear) prove cross-plane
|
|
582
|
-
byte-parity across both arms of every conditional. The per-plane composition tests are **retained**
|
|
583
|
-
(`OBJECTIVE_LINEAR_SUBSTRINGS` survives as a local constant feeding the per-plane selection +
|
|
584
|
-
seed-composition tests); no cross-plane substring lockstep existed between the two different prompts,
|
|
585
|
-
so none is removed.
|
|
586
|
-
|
|
587
|
-
**The learned-docs factory seed + warm guidance moved onto the seam (Node 2.7).** The two
|
|
588
|
-
hand-built learned-docs-factory prompt bodies — the **cold** seed
|
|
589
|
-
(`perk/cli/commands/learn/docs_cmd.py::_seed_prompt`) and the **warm** guidance
|
|
590
|
-
(`extension/doors/learnDocs.ts::learnDocsGuidance`) — moved onto the render seam as the seventh real
|
|
591
|
-
consumer. **Unlike 2.6 they are UNIFIED** (the implement-2.2 / learn-2.4 pattern): the cold/warm
|
|
592
|
-
differences were all **superficial factory house-style** — header wording, a header blank line,
|
|
593
|
-
step-number indentation, a cold-only "from this read-only session" qualifier, and the
|
|
594
|
-
closing-paragraph phrasing — none load-bearing, so they were **converged away** onto the **cold-seed
|
|
595
|
-
orientation form** rather than preserved behind conditionals. The warm guidance gained the "You are
|
|
596
|
-
running…" header + the standalone closing paragraph ("Judgment, user interaction, and durable writes
|
|
597
|
-
stay with you — never delegate them."), and the cold seed lost the `" "` step indent + the "from
|
|
598
|
-
this read-only session" qualifier (the warm session is not read-only, so the qualifier was
|
|
599
|
-
cold-only-accurate anyway; the bare "NEVER write the docs directly" is correct in both planes). The
|
|
600
|
-
result is a single **flat** template `prompts/stages/learn-docs.md` with **zero `{% if %}`
|
|
601
|
-
conditionals**; both planes pass the same two vars (`inbox_path`, `num_list`). The template and its
|
|
602
|
-
golden carry **no trailing newline**. One `learn-docs` golden case in `cases.yaml` proves cross-plane
|
|
603
|
-
byte-parity. No cross-plane substring lockstep existed between cold and warm, so none is removed; the
|
|
604
|
-
per-plane composition tests are retained (one warm header assertion updated from `"perk /learn-docs"`
|
|
605
|
-
to `"learned-docs plan factory"`).
|