@dpeek/codeless 0.1.2 → 0.1.3

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 CHANGED
@@ -12,8 +12,11 @@ consumer application code. Its tests use independent temporary repositories.
12
12
 
13
13
  ## Installation
14
14
 
15
- Codeless requires Bun, Git, Herdr, and Pi. The scoped package is configured for
16
- public npm access. Once published, install it with either package manager:
15
+ Codeless requires Bun, Git, Herdr, and Pi. Planner activation requires Herdr's
16
+ official Pi lifecycle integration; install it with `herdr integration install pi`
17
+ and restart existing Pi processes after updating it. The managed launch and
18
+ session-replacement contract is verified against Herdr 0.8.2 and Pi 0.85.1.
19
+ The scoped package is configured for public npm access. Once published, install it with either package manager:
17
20
 
18
21
  ```sh
19
22
  bun add --global @dpeek/codeless
@@ -91,20 +94,30 @@ The configured prompt directory provides `change.md`, `implement.md`,
91
94
 
92
95
  Path arguments are JSON-quoted. Each direction is `<directions>/<slug>.md`.
93
96
  The project owns prompt contents, approval rules, review criteria, and the
94
- transition from review to commit. Required prompts and directions must exist;
95
- the runner does not generate or copy project instructions.
97
+ transition from review to commit. `codeless init` creates missing copies of the
98
+ package's four generic starters, but never replaces existing prompt bytes.
99
+ Review and commit those local prompt edits in the invoking checkout, then bring
100
+ that commit onto the configured integration branch before creating streams.
101
+ Required prompts and directions must exist for stream creation and opening.
96
102
 
97
103
  The package-owned planner extension activates every planner session. Before its
98
- first project prompt, it requires the exact `<slug>-planner` Pi name, establishes
99
- and verifies the `<slug>_planner` Herdr identity, and confirms `approve_stream_change`,
104
+ first project prompt, it requires the exact `<slug>-planner` Pi name and verifies
105
+ the `<slug>_planner` Herdr identity, managed interactive readiness, foreground
106
+ worktree, and matching native session reference from Herdr's Pi lifecycle
107
+ integration. It confirms `approve_stream_change`,
100
108
  `dispatch_stream_implementer`, `rework_stream_implementer`, `finish_stream_implementer`, and `next_stream_change` are active. Missing or
101
109
  incompatible activation, identity mismatch, or inactive tools stops before
102
- `/change`; global Pi extension installation is unnecessary.
110
+ `/change`. During session replacement, activation allows a brief bounded wait for
111
+ an otherwise-valid Herdr identity to publish the current native Pi reference;
112
+ it never waits on a wrong name, process, lifecycle source, or worktree. Global
113
+ installation of Codeless's extension is unnecessary.
103
114
  The approval tool has no arguments. Its extension derives the active
104
115
  `<slug>-planner` Pi session and passes it to the backing CLI, which requires it
105
- to match the worktree and branch. The CLI validates the clean current-integration
106
- baseline and proposal, exclusively creates the next monotonic `changes/NNN.md`,
107
- and records one hash-backed journal approval. Exact retries reconcile a missing
116
+ to match the clean stream worktree and branch. That stream commit is the
117
+ proposal's base; approval does not compare it with a concurrently advancing
118
+ integration branch. The CLI validates the proposal, exclusively creates the next
119
+ monotonic `changes/NNN.md`, and records one hash-backed journal approval. Landing
120
+ later owns locked synchronization and any required rebase onto integration. Exact retries reconcile a missing
108
121
  file or journal step; conflicting state stops. Approval returns the numbered path
109
122
  but does not dispatch it. Dispatch uses the package executable, inherits the
110
123
  planner's worktree, and queues review only on success. Pi loads the extension
@@ -121,7 +134,7 @@ sending `/change` after resources reload. The previous conversation is not copie
121
134
  journal and project files carry context.
122
135
 
123
136
  This uses Pi's `newSession({ setup, withSession })` command API, verified with
124
- Pi 0.84.3. Only the replacement context activates the selection and sends the
137
+ Pi 0.85.1. Only the replacement context activates the selection and sends the
125
138
  new prompt. Configuration changes take effect at the next role-session boundary,
126
139
  not during an active planner or implementer. Review and remediation therefore
127
140
  continue with their existing session's selection. Duplicate requests
@@ -157,8 +170,15 @@ planners or worktree shells are running.
157
170
  metrics/<slug>/NNN.json # dispatch/landing times and deduplicated implementer attempts
158
171
  ```
159
172
 
160
- Run `codeless init` once after configuring a project. It creates the shared state
161
- layout and the dedicated integration worktree at
173
+ Run `codeless init` once after configuring a project. It creates missing generic
174
+ `change.md`, `implement.md`, `review.md`, and `commit.md` starters in the
175
+ configured in-project prompt directory, reporting each as created or preserved.
176
+ Existing prompt files are never replaced; review and commit generated files from
177
+ the editable invoking checkout, then bring that commit onto the configured
178
+ integration branch before creating streams. Init refuses to generate missing
179
+ prompts when invoked from the dedicated integration checkout.
180
+
181
+ It also creates the shared state layout and dedicated integration worktree at
162
182
  `<workspace>/worktree/<integration-branch>` without requiring Herdr. For the
163
183
  default workspace it adds only `/.codeless/state/` to the primary checkout's
164
184
  `.gitignore`; an absolute workspace override does not modify repository ignores.
@@ -172,14 +192,14 @@ inspect its targets before using it.
172
192
 
173
193
  ## Commands
174
194
 
175
- Run creation, opening, and planner launch from a Herdr-managed shell. Landing
176
- needs no Herdr session.
195
+ Run creation and opening from a Herdr-managed operator shell outside the target
196
+ planner/implementer panes. Herdr requires an available interactive shell in the
197
+ target pane before it can start Pi. Landing needs no Herdr session.
177
198
 
178
199
  ```sh
179
200
  codeless init
180
201
  codeless create <slug>
181
202
  codeless open <slug>
182
- codeless planner <slug>
183
203
  codeless approve <planner-session>
184
204
  codeless dispatch <numbered-change-file>
185
205
  codeless rework <numbered-change-file> <feedback>
@@ -190,15 +210,27 @@ codeless metrics
190
210
  ```
191
211
 
192
212
  Slugs are lowercase kebab-case, at most 24 characters. `init` validates the
193
- existing configuration and integration branch, reports the branch, primary
194
- checkout, workspace, and integration worktree, then creates only the shared
195
- state directories and canonical integration worktree when absent. All other
213
+ existing configuration and integration branch, validates all configured prompt
214
+ destinations before mutation, reports created or preserved starters plus the
215
+ branch, primary checkout, workspace, and integration worktree, then creates the
216
+ shared state directories and canonical integration worktree when absent. All other
196
217
  commands validate their prerequisites and never bootstrap this setup. `create` starts
197
218
  `stream/<slug>` from the integration branch and creates its local documents;
198
- it refuses existing streams. `open` resumes a stream. Both run the configured
199
- install command, then validate and open a planner beside an idle shell. `planner`
200
- starts Pi in an existing stream's lone shell after the same role preflight. Its
201
- activation establishes the same identity as creation and reopening.
219
+ it refuses existing streams. `open` reuses the existing stream workspace and
220
+ one- or two-pane layout. It focuses an existing managed planner without installing
221
+ dependencies, sending another prompt, or replacing its conversation. To restart,
222
+ exit Pi deliberately, leave both stream panes at their shell prompts in the
223
+ stream worktree, then run `codeless open <slug>` from another Herdr shell.
224
+
225
+ When a planner must be started, creation/opening validates the available shells,
226
+ runs the configured install command and role preflight, adds a right-hand shell
227
+ only when absent, and uses `herdr agent start` for named, readiness-checked Pi
228
+ startup. It verifies the result before sending activation. An occupied or
229
+ mismatched pane, unmanaged agent, ambiguous layout, or failed startup stops;
230
+ Codeless never takes over an existing agent. Pi's display name is separate from
231
+ Herdr's managed agent name. Activation verifies names and native session binding;
232
+ it never renames an unmanaged process. There is no direct `planner` command.
233
+
202
234
  Dispatch validates the implementer selection before touching the planner's
203
235
  right-hand pane, starts a fresh ephemeral implementer with Codeless's reporting
204
236
  extension and its explicit Pi extension flag, and waits for completion. Rework verifies
@@ -262,8 +294,8 @@ control Pi itself. Preparation can be repeated safely after inspecting a failure
262
294
 
263
295
  If validation or session replacement fails, the planner stops for operator
264
296
  attention. Landing is already complete, and any preparation fast-forward remains
265
- applied. There is no background retry; restarting the planner recovers from the
266
- journal and Git state.
297
+ applied. There is no background retry; exit Pi and reopen the stream from another
298
+ Herdr shell to recover from the journal and Git state.
267
299
 
268
300
  ## Package development
269
301
 
@@ -1,8 +1,11 @@
1
1
  import { fileURLToPath } from "node:url";
2
+ import { resolve } from "node:path";
2
3
  import { validAttempt } from "../src/attempt.ts";
3
4
 
4
5
  const codeless = fileURLToPath(new URL("../bin/codeless", import.meta.url));
5
6
  const thinkingLevels = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
7
+ const nativeSessionAttempts = 20;
8
+ const nativeSessionRetryDelayMs = 50;
6
9
  const requiredTools = [
7
10
  "approve_stream_change",
8
11
  "dispatch_stream_implementer",
@@ -93,24 +96,47 @@ export default function plannerExtension(pi) {
93
96
  throw new Error("Herdr returned an invalid planner identity response");
94
97
  }
95
98
  };
96
- let agent = await plannerIdentity();
97
- if (agent?.name === "pi") {
98
- const renamed = await pi.exec("herdr", ["agent", "rename", pane, expectedPlanner], {
99
- timeout: 30_000,
100
- });
101
- if (renamed.code !== 0) {
99
+ const attempts = activation ? nativeSessionAttempts : 1;
100
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
101
+ const agent = await plannerIdentity();
102
+ if (agent?.name !== expectedPlanner) {
102
103
  throw new Error(
103
- renamed.stderr.trim() ||
104
- renamed.stdout.trim() ||
105
- "Codeless could not establish Herdr planner identity",
104
+ `Codeless planner identity is ${agent?.name ?? "missing"}, expected ${expectedPlanner}; exit this agent and run codeless open ${match[1]} from another Herdr shell`,
106
105
  );
107
106
  }
108
- agent = await plannerIdentity();
109
- }
110
- if (agent?.name !== expectedPlanner) {
111
- throw new Error(
112
- `Codeless planner identity is ${agent?.name ?? "missing"}, expected ${expectedPlanner}`,
113
- );
107
+ if (agent.agent !== "pi" || agent.interactive_ready !== true) {
108
+ throw new Error("Codeless requires a Herdr-managed Pi planner started by codeless open");
109
+ }
110
+ if (
111
+ typeof agent.foreground_cwd !== "string" ||
112
+ resolve(agent.foreground_cwd) !== resolve(ctx.cwd)
113
+ ) {
114
+ throw new Error("Codeless planner worktree does not match Herdr's foreground cwd");
115
+ }
116
+ const session = agent.agent_session;
117
+ const expectedSession =
118
+ session?.kind === "path"
119
+ ? ctx.sessionManager.getSessionFile()
120
+ : session?.kind === "id"
121
+ ? ctx.sessionManager.getSessionId()
122
+ : undefined;
123
+ if (
124
+ agent.screen_detection_skipped !== true ||
125
+ session?.source !== "herdr:pi" ||
126
+ session.agent !== "pi" ||
127
+ !expectedSession
128
+ ) {
129
+ throw new Error(
130
+ "Codeless planner native session does not match Herdr's Pi lifecycle integration",
131
+ );
132
+ }
133
+ if (session.value === expectedSession) break;
134
+ if (attempt === attempts - 1) {
135
+ throw new Error(
136
+ "Codeless planner native session does not match Herdr's Pi lifecycle integration",
137
+ );
138
+ }
139
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, nativeSessionRetryDelayMs));
114
140
  }
115
141
  const activeTools = ctx.getSystemPromptOptions().selectedTools ?? [];
116
142
  const missing = requiredTools.filter((tool) => !activeTools.includes(tool));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dpeek/codeless",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "An attended planner and implementer workflow for parallel capability development",
5
5
  "homepage": "https://github.com/dpeek/codeless#readme",
6
6
  "bugs": {
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "bin",
19
19
  "extension",
20
+ "prompts",
20
21
  "src",
21
22
  "README.md",
22
23
  "spec"
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Propose and coordinate the next small change for a stream
3
+ argument-hint: "<stream-directory> <direction-file>"
4
+ ---
5
+
6
+ You are the planner for the stream at `$1`. The current working directory is the stream's repository worktree.
7
+
8
+ Read the repository guidance, `.codeless/config.json`, `$1/planner.md`, every numbered Markdown file in `$1/changes/`, `$2`, its related contracts, and the current implementation relevant to the stream. Resolve the integration branch from that configuration.
9
+
10
+ Before proposing, inspect the branch, recent commits, and worktree. If changes are not explained as an active approved change in `planner.md`, stop and show the operator the evidence. Never discard work automatically.
11
+
12
+ If the latest numbered change is approved but uncommitted, resume it. If it is committed but unlanded on the configured integration branch, resume review or landing. Otherwise require a clean worktree with no commits outside the configured integration branch, fast-forward to that branch, and reread the direction and affected contracts. Stop on divergence. Once planning begins, keep that stream commit as the proposal's base; do not resynchronize merely because integration advances while the proposal awaits approval. Locked landing owns the later rebase.
13
+
14
+ Propose exactly one small, complete change and write it to `$1/change.md`:
15
+
16
+ ```markdown
17
+ # <short outcome>
18
+
19
+ ## Why
20
+
21
+ ## Change
22
+
23
+ ## Acceptance
24
+
25
+ ## Decisions
26
+ ```
27
+
28
+ Under `Decisions`, record only choices that matter now; write `None` when there are none.
29
+
30
+ Present the proposal and wait for the operator:
31
+
32
+ - `go`: call `approve_stream_change` exactly once. It validates and promotes the proposal, records one approval, and returns its number, title, and absolute numbered change path. After it succeeds, call `dispatch_stream_implementer` exactly once with that returned change path.
33
+ - `no`: append the rejected proposal and reason to `planner.md`; do not allocate a numbered change.
34
+ - other feedback: revise `change.md`, recheck the relevant state, and present it again.
35
+
36
+ Approval is separate from dispatch. Do not edit approval state by hand or reproduce the dispatch tool's worktree, pane, or agent operations. If dispatch returns an error, stop and show the operator the exact error; do not retry automatically.
37
+
38
+ After the implementer settles, follow the queued `/review <absolute-numbered-change-path>` instruction. Do not finish after merely reporting that implementation settled. Keep this implementer alive for every remediation round belonging to the same numbered change.
39
+
40
+ The implementation updates code, focused tests, owning contracts, and the direction; remove satisfied todo intentions. If no worthwhile work remains, say so.
@@ -0,0 +1,37 @@
1
+ ---
2
+ description: Commit one reviewed stream change
3
+ argument-hint: "<numbered-change-file>"
4
+ ---
5
+
6
+ Commit the reviewed implementation of `$1` on the current stream branch.
7
+
8
+ Before committing or landing:
9
+
10
+ - confirm `planner.md` records review approval for this change;
11
+ - inspect the complete diff for unrelated edits;
12
+ - confirm every acceptance criterion is met;
13
+ - run the relevant checks if their passing result is not current;
14
+ - determine whether this reviewed change already has its one commit because a previous landing attempt stopped.
15
+
16
+ If the change is not committed, create exactly one commit. Use an imperative subject that describes the resulting behavior. Add a short body only for a non-obvious design decision. If the reviewed change already has its one unlanded commit, do not create another.
17
+
18
+ Read `.codeless/config.json` to resolve the configured integration branch. Derive `<slug>` from the numbered change path and land it with:
19
+
20
+ ```sh
21
+ codeless land <slug>
22
+ ```
23
+
24
+ Replace the placeholder with the literal stream slug and do not include angle brackets. This command owns the shared integration lock, rebases the one stream commit when the configured integration branch advanced, runs the configured checks, and fast-forwards the dedicated integration checkout. It does not modify other branches or checkouts.
25
+
26
+ If landing succeeds, append the full commit hash and subject plus a `landed on <configured-integration-branch>` note to the stream's `planner.md`. Include any decisions the next planner needs before resetting the session.
27
+
28
+ Then call `next_stream_change` exactly once with `changePath` set to the absolute path of `$1` and `landedCommit` set to that full hash. Finish this turn after requesting the handoff. The tool validates the completed landing, fast-forwards the stream to the current configured integration branch, and starts a fresh planner session with the project's `/change` prompt. Do not propose the next change in this session or send `/new` through shell keystrokes.
29
+
30
+ The next planner proposes one change and waits for the operator's `go`; this handoff does not approve further implementation. If no worthwhile work remains, it reports that and stops. If handoff validation fails or the reset is cancelled, stop and report the state; do not retry automatically. Landing remains complete even if the handoff fails.
31
+
32
+ There are two expected landing stops:
33
+
34
+ - If another stream owns the integration slot, leave this stream committed where it is, report the owner, and wait. Do not poll, queue, or retry automatically.
35
+ - If this stream owns the slot and the rebase conflicts, keep the slot. Resolve the conflicts in this worktree so both the current configured integration branch and the approved change are preserved, stage the resolutions, and continue the rebase with `GIT_EDITOR=true git rebase --continue`. Repeat until the rebase completes, run the relevant checks, then run `codeless land <slug>` again to finish. The slot prevents another automated landing from moving the integration branch while you resolve it.
36
+
37
+ For any other failure while this stream owns the slot, report the exact state and wait for the operator. The slot remains held for deliberate recovery; never remove the workspace's landing lock automatically or on guesswork.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: Implement one approved stream change
3
+ argument-hint: "<numbered-change-file>"
4
+ ---
5
+
6
+ You are the implementer. Implement only the approved change in `$1` in the current worktree.
7
+
8
+ Read the numbered change, repository guidance, and the smallest relevant part of the codebase. Preserve unrelated work.
9
+
10
+ - Do not edit the planner journal, current proposal, or numbered changes beside `$1`.
11
+ - Do not commit, rebase, merge, switch branches, or reset files.
12
+ - Update the owning contracts and todo with the implementation.
13
+ - Add or update focused tests for changed behavior.
14
+ - Run the smallest useful checks.
15
+ - If the change cannot be completed safely, stop and explain the blocker with evidence.
16
+
17
+ When finished, report what changed, checks run and their results, and assumptions or remaining concerns.
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Review the worktree against one approved stream change
3
+ argument-hint: "<numbered-change-file>"
4
+ ---
5
+
6
+ You are the planner reviewing the implementation of `$1`.
7
+
8
+ Read the approved change and repository guidance. Inspect the complete worktree diff and relevant surrounding code. Review for correctness and regressions, unmet acceptance criteria, missing or weak tests, accidental scope growth, and consistency with the repository's domain model and style.
9
+
10
+ Run focused checks when the implementation report is insufficient evidence.
11
+
12
+ If remediation is required, call `rework_stream_implementer` once with `$1` as `changePath` and a concise actionable feedback list. The tool reuses the verified idle implementer, waits for its one feedback turn, returns its rework attempt, and queues review again. Do not reproduce agent operations or retry automatically.
13
+
14
+ If operator judgment is required, stop and ask one concrete question. If the change is ready:
15
+
16
+ 1. append a compact approval and verification summary to the stream's `planner.md`;
17
+ 2. call `finish_stream_implementer` once with `$1` as `changePath`; it gracefully exits the verified implementer and confirms its pane returned to the stream shell;
18
+ 3. only after that tool succeeds, read `.codeless/config.json`, resolve its `prompts` directory, and immediately follow that directory's `commit.md` instructions for `$1`.
19
+
20
+ Do not finish the implementer before approval because remediation for this numbered change retains its context. If the finish tool fails, stop and report its error.
package/spec/workflow.md CHANGED
@@ -1,16 +1,19 @@
1
1
  # Codeless workflow
2
2
 
3
- Codeless is an attended workflow for delivering independent capability changes
4
- through a project's integration branch. This contract owns the implemented
5
- workflow; the [project guide](../README.md) explains installation, operation,
6
- and reusable execution mechanics. Missing workflow behavior belongs in
7
- [the workflow todo](../todo/workflow.md).
3
+ Codeless is an attended workflow for delivering independent capability changes through a
4
+ project's integration branch. This contract owns the implemented workflow; the [project
5
+ guide](../README.md) explains installation, operation, and reusable execution mechanics.
6
+ Missing workflow behavior belongs in [the workflow todo](../todo/workflow.md).
8
7
 
9
- ## Stream and change lifecycle
8
+ ## Ownership and lifecycle
10
9
 
11
- A stream has one lowercase kebab-case slug, one `stream/<slug>` branch and
12
- worktree, one planner, one implementer pane, and at most one approved change in
13
- progress. Work inside a stream is sequential:
10
+ Codeless owns reusable execution mechanics and shared local state. Projects own
11
+ directions, prompt contents, model choices, and approval/review policy. Pi and Herdr own
12
+ their runtime APIs and process behavior.
13
+
14
+ A stream has one lowercase kebab-case slug, one `stream/<slug>` branch and worktree, one
15
+ planner, one implementer pane, and at most one approved change in progress. Work inside
16
+ a stream is sequential:
14
17
 
15
18
  1. create or reopen the stream from the configured integration branch;
16
19
  2. propose one small change and wait for operator approval;
@@ -21,22 +24,42 @@ progress. Work inside a stream is sequential:
21
24
  slot; and
22
25
  6. replace the planner session before proposing another change.
23
26
 
24
- Automated landing updates only the configured integration branch's dedicated
25
- clean checkout. Other branches and checkouts are neither stream sources nor
26
- landing targets. Codeless does not push.
27
+ Automated landing updates only the configured integration branch's dedicated clean
28
+ checkout. Other branches and checkouts are neither stream sources nor landing targets.
29
+ Codeless does not push.
30
+
31
+ Each project supplies `.codeless/config.json`, a direction at `<directions>/<slug>.md`, and
32
+ `change`, `implement`, `review`, and `commit` prompt templates. Creation refuses a
33
+ missing direction or prompt and an existing stream. Opening requires the existing
34
+ branch, worktree, journal, proposal file, direction, and prompts. Both install
35
+ dependencies before starting a new planner. Reopening a running managed planner
36
+ only focuses its workspace and preserves its conversation.
37
+
38
+ ## Command and tool boundaries
27
39
 
28
- Each project supplies `.codeless/config.json`, a direction at
29
- `todo/<slug>.md`, and `change`, `implement`, `review`, and `commit` prompt
30
- templates. Creation refuses a missing direction or prompt and an existing
31
- stream. Opening requires the existing branch, worktree, journal, proposal file,
32
- direction, and prompts. Both install dependencies before starting the planner.
40
+ The executable currently exposes both operator commands and subprocess entrypoints used
41
+ by planner tools:
42
+
43
+ | Surface | Responsibility |
44
+ | ------------------------------------------------- | ----------------------------------------------------------------------------- |
45
+ | `init`, `create`, `open`, `metrics` | Operator setup, session management, and observation |
46
+ | `approve`, `dispatch`, `rework`, `finish`, `next` | Backing commands for the corresponding planner tools |
47
+ | `land` | Commit integration, invoked by the project prompt or operator during recovery |
48
+ | `/change`, `/implement`, `/review`, `/commit` | Project-owned prompt templates |
49
+ | `/streams-activate`, `/streams-next` | Package-owned planner activation and session replacement |
50
+ | `/codeless-rework`, `/codeless-finish` | Package-owned commands delivered to the existing implementer |
51
+
52
+ The Pi commands bridge session/process operations. In particular, `next_stream_change`
53
+ queues a command so session replacement occurs after the current turn settles. Surface
54
+ reductions and additional tools belong in the workflow todo; all backing CLI commands
55
+ remain callable today.
33
56
 
34
57
  ## Shared local state
35
58
 
36
- Codeless keeps workflow state outside tracked project documents in a shared
37
- workspace selected by the local Git `codeless.workspaceRoot` setting or, by
38
- default, at `.codeless/state/` in the primary checkout. The default directory is
39
- Git-ignored, and every linked worktree resolves the same primary-checkout state:
59
+ Codeless keeps workflow state outside tracked project documents in a shared workspace
60
+ selected by the local Git `codeless.workspaceRoot` setting or, by default, at
61
+ `.codeless/state/` in the primary checkout. The default directory is Git-ignored, and
62
+ every linked worktree resolves the same primary-checkout state:
40
63
 
41
64
  ```text
42
65
  <primary-checkout>/.codeless/state/
@@ -49,183 +72,212 @@ Git-ignored, and every linked worktree resolves the same primary-checkout state:
49
72
  metrics/<slug>/NNN.json
50
73
  ```
51
74
 
52
- `codeless init` is the explicit, idempotent bootstrap for a configured project.
53
- It requires the configured integration branch to exist and creates the shared
54
- state directories plus that branch's worktree only at
75
+ `codeless init` is the explicit, idempotent bootstrap for a configured project. It
76
+ requires the configured integration branch and creates missing copies of the four
77
+ package-owned generic prompt starters in the invoking checkout's configured in-project
78
+ prompt directory. It validates every template and destination before mutation, reports
79
+ each absolute path as created or preserved, and never replaces an existing prompt file.
80
+ A non-directory ancestor or non-file prompt collision stops without prompt, state, ignore,
81
+ or worktree mutation. Missing prompts are not generated when init is invoked from the
82
+ dedicated integration checkout; the operator must initialize from an editable checkout.
83
+
84
+ Init creates the shared state directories plus that branch's worktree only at
55
85
  `<workspace>/worktree/<integration-branch>`. It reuses only the exact registered
56
- canonical checkout. An occupied target, a branch registered elsewhere, invalid
57
- checkout, or ambiguous Git registration stops unchanged; init never switches
58
- branches, moves worktrees, or repairs conflicts. With the default workspace,
59
- it accepts a repository ignore rule only when it ignores the state path without
60
- covering configuration or configured prompts, otherwise appending the narrow
61
- `/.codeless/state/` rule. An absolute workspace never changes repository
62
- ignores. No other command bootstraps this layout.
63
-
64
- `planner.md` owns decisions, approvals, review outcomes, landing history, and
65
- the context needed by a fresh planner. `change.md` is the editable current
66
- proposal. `changes/NNN.md` is the immutable-by-policy approved input to one
67
- implementation loop. These documents are local workflow state, not product
68
- contracts, and are never copied into `spec/`.
86
+ canonical checkout. An occupied target, a branch registered elsewhere, invalid checkout,
87
+ or ambiguous Git registration stops unchanged; init never switches branches, moves
88
+ worktrees, or repairs conflicts. With the default workspace, it accepts a repository
89
+ ignore rule only when it ignores the state path without covering configuration or
90
+ configured prompts, otherwise appending the narrow `/.codeless/state/` rule. An absolute
91
+ workspace never changes repository ignores. No other command bootstraps this layout.
92
+
93
+ `planner.md` owns decisions, approvals, review outcomes, landing history, and the
94
+ context needed by a fresh planner. `change.md` is the editable current proposal.
95
+ `changes/NNN.md` is the immutable-by-policy approved input to one implementation loop.
96
+ These documents are local workflow state, not product contracts, and are never copied
97
+ into `spec/`.
69
98
 
70
99
  After operator `go`, the argument-free planner-only `approve_stream_change` tool
71
- promotes the current proposal before any dispatch. It derives the active planner
72
- session and passes it to the backing CLI, which requires its `<slug>-planner`
73
- identity to match the clean `stream/<slug>` worktree and branch exactly at the
74
- current integration branch. The proposal needs one usable H1 title plus the `Why`, `Change`,
75
- `Acceptance`, and `Decisions` headings; titles must be representable by the
76
- canonical record. The CLI writes `changes/NNN.md` exclusively, where `NNN` is
77
- the successor of the greatest existing three-digit number (and stops after
78
- `999`), then appends a canonical journal approval containing the file, title,
79
- and proposal hash. Repeated calls reconcile that exact file and entry, completing
80
- one missing step without another number; conflicting or ambiguous partial state
81
- stops unchanged. Rejection and ordinary feedback allocate nothing. Dispatch
82
- remains a separate explicit tool call using the returned absolute path.
100
+ promotes the current proposal before any dispatch. It derives the active planner session
101
+ and passes it to the backing CLI, which requires its `<slug>-planner` identity to match
102
+ the clean `stream/<slug>` worktree and branch. The stream commit established for
103
+ planning is the proposal's base; approval does not compare it with the moving
104
+ integration branch. Landing alone acquires the integration lock and rebases that
105
+ stream change when integration has advanced. The proposal needs one usable H1 title plus
106
+ the `Why`, `Change`, `Acceptance`, and
107
+ `Decisions` headings in that order; titles must be representable by the canonical
108
+ record. The CLI writes `changes/NNN.md` exclusively, where `NNN` is the successor of the
109
+ greatest existing three-digit number (and stops after `999`), then appends a canonical
110
+ journal approval containing the file, title, and proposal hash. Repeated calls reconcile
111
+ that exact file and entry, completing one missing step without another number;
112
+ conflicting or ambiguous partial state stops unchanged. Rejection and ordinary feedback
113
+ allocate nothing. Dispatch remains a separate explicit tool call using the returned
114
+ absolute path.
83
115
 
84
116
  ## Role sessions and configuration
85
117
 
86
118
  Project configuration selects an exact Pi provider, model, and thinking level
87
119
  independently for planner and implementer roles.
88
120
 
89
- Before starting either role, Codeless uses Pi's machine-readable APIs to require
90
- the configured model, authentication, supported thinking level, and effective
91
- selection. It fails before agent work rather than accepting a fallback model or
92
- clamped thinking level. The validated selection is displayed and passed to the
93
- role process.
94
-
95
- Configuration changes take effect only at a new role-session boundary. An
96
- active review or remediation keeps its implementer setting. A successful
97
- post-landing handoff rereads and validates planner configuration from the
98
- fast-forwarded stream worktree before the replacement session receives its
99
- first project prompt.
100
-
101
- Every planner launch—creation, reopening, direct `planner` restart, and
102
- post-landing replacement—uses the package-owned extension as its activation
103
- boundary. Before its first project prompt, activation requires the exact
104
- `<slug>-planner` Pi session name, establishes and verifies Herdr reports
105
- `<slug-with-hyphens-replaced>_planner`, and verifies
106
- `approve_stream_change`, `dispatch_stream_implementer`, `rework_stream_implementer`,
107
- `finish_stream_implementer`, and `next_stream_change` are active. Missing or incompatible activation, identity
108
- mismatch, or an incomplete tool set stops visibly before `/change`. A direct
109
- restart may begin with Herdr's `pi` fallback identity; activation renames and
110
- rereads only that fallback. Any other identity mismatch stops. Implementers use
111
- the corresponding `_impl` and `-impl` forms. The package loads its planner
112
- extension explicitly; global Pi extension installation is not required.
121
+ Before starting either role, Codeless uses Pi's machine-readable APIs to require the
122
+ configured model, authentication, supported thinking level, and effective selection. It
123
+ fails before agent work rather than accepting a fallback model or clamped thinking
124
+ level. The validated selection is displayed and passed to the role process.
125
+
126
+ Configuration changes take effect only at a new role-session boundary. An active review
127
+ or remediation keeps its implementer setting. A successful post-landing handoff rereads
128
+ and validates planner configuration from the fast-forwarded stream worktree before the
129
+ replacement session receives its first project prompt.
130
+
131
+ Every new planner process uses `herdr agent start`, which owns its managed name
132
+ and waits for interactive readiness before Codeless sends activation. `open`
133
+ reuses the stream workspace and its root planner pane, adding a right-hand shell
134
+ only when absent. It accepts only a lone planner pane or a planner with one
135
+ right-hand pane. Starting a planner requires both existing panes to be shells in
136
+ the stream worktree; occupied, mismatched, or ambiguous layouts stop unchanged.
137
+ The operator must invoke opening from outside those target panes. Reopening an
138
+ existing managed planner focuses it without installation or another prompt.
139
+
140
+ The package-owned extension activates creation, reopening, and post-landing
141
+ replacement. Before the first project prompt it requires the exact
142
+ `<slug>-planner` Pi name, `<slug-with-hyphens-replaced>_planner` Herdr name,
143
+ managed interactive readiness, matching foreground worktree, and the current
144
+ native Pi session ID/file reported by Herdr's official Pi lifecycle integration.
145
+ It verifies `approve_stream_change`, `dispatch_stream_implementer`,
146
+ `rework_stream_implementer`, `finish_stream_implementer`, and `next_stream_change`
147
+ are active. During replacement, an otherwise-valid previous native session
148
+ reference receives a brief bounded synchronization wait; a wrong name, process,
149
+ lifecycle source, or worktree fails immediately. Any binding that remains missing
150
+ or incompatible stops visibly before `/change`. Activation never repairs names.
151
+ The direct `planner` command is removed;
152
+ recovery exits Pi deliberately and reopens from another Herdr shell.
153
+
154
+ Pi session replacement keeps the managed process and Herdr name while changing
155
+ its native conversation reference. Codeless revalidates that new binding before
156
+ prompting the replacement. Implementers use the corresponding `_impl` and
157
+ `-impl` names. Codeless loads its own extension explicitly; Herdr's official Pi
158
+ integration supplies lifecycle and native-session reporting. This boundary was
159
+ verified against Herdr 0.8.2 and Pi 0.85.1.
113
160
 
114
161
  ## Dispatch and review
115
162
 
116
163
  The planner-only `dispatch_stream_implementer` tool accepts an absolute approved
117
- `changes/NNN.md` path. Dispatch verifies the stream branch, clean worktree,
118
- planner pane, prompts, and implementer selection. It creates or reuses the
119
- right-hand Herdr pane only when that pane is an available shell or the expected
120
- idle implementer, starts a fresh ephemeral Pi implementer in the stream
121
- worktree, submits `/implement`, and waits for at most one hour.
164
+ `changes/NNN.md` path. Dispatch verifies the stream branch, clean worktree, planner
165
+ pane, prompts, and implementer selection. It creates or reuses the right-hand Herdr pane
166
+ only when that pane is an available shell or the expected idle implementer, starts a
167
+ fresh ephemeral Pi implementer in the stream worktree, submits `/implement`, and waits
168
+ for at most one hour.
122
169
 
123
170
  Successful dispatch loads the package-owned reporting extension while retaining
124
- `--no-session` and passes its report configuration through that extension's
125
- explicit Pi string flag, then returns one normalized attempt to the planner tool
126
- before it queues the expanded `/review` prompt. Attempts have a stable ID and
127
- capture only stream/change/role, start and settlement timestamps, Pi's actual
128
- settled provider/model/thinking selection, terminal outcome and final text,
129
- full-session Pi input/output/cache usage (including tool results, compaction,
130
- and branch summaries), available Pi model cost estimate with USD currency and
131
- source, and tool/error counts. Cost is omitted when Pi did not supply valid
132
- cost totals. They do not retain prompts, source, credentials, thinking, or a
133
- transcript. The extension writes its narrow report atomically once, then
134
- remains disarmed for remediation; Codeless atomically deduplicates it inside the
135
- per-change metric record, rejecting a
136
- conflicting duplicate ID. Missing, malformed, or unwritable collection warns
137
- and yields an explicitly incomplete attempt when possible without failing or
138
- repeating a settled implementation.
139
-
140
- The planner inspects the full diff and relevant code, checks the approved
141
- acceptance criteria, and runs focused checks when the implementation output is
142
- insufficient. The planner-only `rework_stream_implementer` tool accepts that
143
- approved path and concise feedback, verifies the expected idle implementer, its
144
- right-hand pane and worktree, then invokes one package-owned Pi command. That command
145
- verifies the immutable startup stream/change scope, arms package reporting in the existing
146
- conversation, and submits one bounded feedback turn. It records
147
- and returns one `rework` attempt before queueing review again; missing or malformed
148
- reports warn and yield an incomplete attempt after settlement. Prompt rejection,
149
- timeout, blocked state, identity/worktree/change mismatch, or ambiguous pane stops
150
- without a completed attempt or queued review. The separate planner-only
151
- `finish_stream_implementer` tool first verifies that immutable stream/change scope in the
152
- same idle implementer, then gracefully exits it and waits for its pane to become the
153
- stream-worktree shell. Its failure
154
- stops before commit or landing instructions continue.
155
-
156
- Dispatch, remediation, and shutdown do not retry automatically or replace an
157
- implementer session or its selected model.
171
+ `--no-session` and passes its report configuration through that extension's explicit Pi
172
+ string flag, then returns one normalized attempt to the planner tool before it queues
173
+ the expanded `/review` prompt. Attempts have a stable ID and capture only
174
+ stream/change/role, start and settlement timestamps, Pi's actual settled
175
+ provider/model/thinking selection, terminal outcome and final text, Pi
176
+ input/output/cache usage over the collected attempt (including tool results, compaction,
177
+ and branch summaries), available Pi model cost estimate with USD currency and source,
178
+ and tool/error counts. Cost is omitted when Pi did not supply valid cost totals.
179
+ Collection stores final assistant text and aggregate measurements, not a transcript or
180
+ separate prompt, source, credential, or thinking fields. Final assistant text is not
181
+ redacted. The extension writes its report atomically once, then remains disarmed for
182
+ remediation; Codeless atomically deduplicates it inside the per-change metric record,
183
+ rejecting a conflicting duplicate ID. Missing, malformed, or unwritable collection warns
184
+ and yields an explicitly incomplete attempt when possible without failing or repeating a
185
+ settled implementation.
186
+
187
+ The planner inspects the full diff and relevant code, checks the approved acceptance
188
+ criteria, and runs focused checks when the implementation output is insufficient. The
189
+ planner-only `rework_stream_implementer` tool accepts that approved path and concise
190
+ feedback, verifies the expected idle implementer, its right-hand pane and worktree, then
191
+ invokes one package-owned Pi command. That command verifies the immutable startup
192
+ stream/change scope, arms package reporting in the existing conversation, and submits
193
+ one bounded feedback turn. It records usage from that remediation turn, excluding
194
+ earlier conversation entries, and returns one `rework` attempt before queueing review
195
+ again; missing or malformed reports warn and yield an incomplete attempt after
196
+ settlement. Prompt rejection, timeout, blocked state, identity/worktree/change mismatch,
197
+ or ambiguous pane stops without a completed attempt or queued review. The separate
198
+ planner-only `finish_stream_implementer` tool first verifies that immutable
199
+ stream/change scope in the same idle implementer, then gracefully exits it and waits for
200
+ its pane to become the stream-worktree shell. Its failure stops before commit or landing
201
+ instructions continue.
202
+
203
+ Dispatch, remediation, and shutdown do not retry automatically or replace an implementer
204
+ session or its selected model.
158
205
 
159
206
  ## Commit and landing
160
207
 
161
- A reviewed change produces exactly one commit outside the merge base with the
162
- configured integration branch. `codeless land <slug>` requires clean stream and integration worktrees,
163
- then atomically acquires the shared `.land-lock` with its owner and captured
208
+ A reviewed change produces exactly one commit outside the merge base with the configured
209
+ integration branch. `codeless land <slug>` requires clean stream and integration
210
+ worktrees, then atomically acquires the shared `.land-lock` with its owner and captured
164
211
  integration commit.
165
212
 
166
- If the integration branch advanced, landing rebases the single stream commit. It then rereads and
167
- runs the configured project check in the stream worktree, requires checks to
168
- leave the worktree clean, and fast-forwards the dedicated `main` checkout. Only
213
+ If the integration branch advanced, landing rebases the single stream commit. It then
214
+ rereads and runs the configured project check in the stream worktree, requires checks to
215
+ leave the worktree clean, and fast-forwards the dedicated integration checkout. Only
169
216
  successful completion releases the lock.
170
217
 
171
- A lock owned by another stream stops landing without polling. A rebase conflict,
172
- failed check, or other error after acquisition retains this stream's lock for
173
- deliberate recovery. Rerunning landing for the same owner is allowed only while
174
- the recorded integration commit still matches. Codeless never removes a stale or
175
- ambiguous lock automatically.
218
+ A lock owned by another stream stops landing without polling. A rebase conflict, failed
219
+ check, or other error after acquisition retains this stream's lock for deliberate
220
+ recovery. Rerunning landing for the same owner is allowed only while the recorded
221
+ integration commit still matches. Codeless never removes a stale or ambiguous lock
222
+ automatically.
176
223
 
177
224
  ## Fresh planner handoff
178
225
 
179
226
  After landing, the planner records the full landed commit hash and calls the
180
- planner-only `next_stream_change` tool exactly once. The handoff requires the
181
- latest numbered change, its full hash in the journal and stream history, no
182
- unlanded stream commit, a clean worktree, and no unresolved lock owned by this
183
- stream or by an unknown owner.
184
-
185
- Codeless captures the current integration branch, fast-forwards the stream worktree, validates
186
- the updated direction, prompts, and planner selection, and returns the next
187
- session name and `/change` prompt. The extension replaces the Pi session in the
188
- same pane, preserves its name, activates the validated selection and planner
189
- identity, and only then sends the project prompt. Conversation history is not copied; the journal and
227
+ planner-only `next_stream_change` tool exactly once. The handoff requires the latest
228
+ numbered change, the full landed commit hash in the journal and stream history, no unlanded stream
229
+ commit, a clean worktree, and no unresolved lock owned by this stream or by an unknown
230
+ owner.
231
+
232
+ Codeless captures the current integration branch, fast-forwards the stream worktree,
233
+ validates the updated direction, prompts, and planner selection, and returns the next
234
+ session name and `/change` prompt. The extension replaces the Pi session in the same
235
+ pane, preserves its name, activates the validated selection and planner identity, and
236
+ only then sends the project prompt. Conversation history is not copied; the journal and
190
237
  project files carry durable context.
191
238
 
192
239
  A cancelled or failed replacement stops for operator attention. Landing remains
193
- complete, and any successful preparation fast-forward remains applied. There is
194
- no background retry. The replacement planner still needs a new operator `go`
195
- before another implementation.
240
+ complete, and any successful preparation fast-forward remains applied. There is no
241
+ background retry. The replacement planner still needs a new operator `go` before another
242
+ implementation.
196
243
 
197
244
  ## Local workflow metrics
198
245
 
199
- The first dispatch for a stream and numbered change creates one atomic local
200
- metric record. Every accepted dispatch creates a new attempt ID; re-ingesting an
201
- attempt ID is atomic and idempotent, while the original dispatch timestamp stays
202
- unchanged. Successful landing adds its timestamp and commit, or creates a landed
203
- record with unavailable elapsed time when dispatch collection was unavailable.
204
- Collection warnings do not change dispatch or landing outcomes.
205
-
206
- `codeless metrics` reports every recorded stream and a project total in two
207
- tables. The elapsed table reports landed and dispatched-but-unlanded change
208
- counts, measured versus unavailable elapsed coverage, and total and average
209
- dispatch-to-land wall-clock time. The attempt table aggregates only validated
210
- canonical attempt records and reports distinct changes with rework, initial and
211
- rework turns, incomplete collection, exact stored terminal-outcome labels, and
212
- summed tool errors.
213
-
214
- Usage coverage is measured versus unavailable attempts; input, output,
215
- cache-read, and cache-write totals include only attempts with recorded usage.
216
- Cost coverage follows the same rule, and totals are grouped by recorded currency
217
- without conversion. Missing usage or cost is unavailable, never zero. These
218
- measurements do not establish implementation quality or review success. They
219
- are prospective local observations, not journal state, an approval source, or a
220
- recovery mechanism.
246
+ The first dispatch for a stream and numbered change creates one atomic local metric
247
+ record. Every accepted dispatch creates a new attempt ID; re-ingesting an attempt ID is
248
+ atomic and idempotent, while the original dispatch timestamp stays unchanged. Successful
249
+ landing adds its timestamp and commit, or creates a landed record with unavailable
250
+ elapsed time when dispatch collection was unavailable. Collection warnings do not change
251
+ dispatch or landing outcomes.
252
+
253
+ `codeless metrics` reports every recorded stream and a project total in two tables. The
254
+ elapsed table reports landed and dispatched-but-unlanded change counts, measured versus
255
+ unavailable elapsed coverage, and total and average dispatch-to-land wall-clock time.
256
+ The attempt table aggregates only validated canonical attempt records and reports
257
+ distinct changes with rework, initial and rework turns, incomplete collection, exact
258
+ stored terminal-outcome labels, and summed tool errors.
259
+
260
+ Usage coverage is measured versus unavailable attempts; input, output, cache-read, and
261
+ cache-write totals include only attempts with recorded usage. Cost coverage follows the
262
+ same rule, and totals are grouped by recorded currency without conversion. Missing usage
263
+ or cost is unavailable, never zero. These measurements do not establish implementation
264
+ quality or review success. They are prospective local observations, not journal state,
265
+ an approval source, or a recovery mechanism.
221
266
 
222
267
  ## Limits
223
268
 
224
- Codeless is attended and intentionally has no supervisor, project registry,
225
- queue, automatic landing retry, stale-lock recovery, or unattended approval.
226
- Planner startup reads every numbered change, and conflict recovery currently
227
- causes the configured landing check to run twice. Because the default state is
228
- ignored, `git clean -fdx` can delete it.
269
+ Codeless is attended and intentionally has no supervisor, project registry, queue,
270
+ automatic landing retry, stale-lock recovery, or unattended approval. Planner startup
271
+ reads every numbered change, and conflict recovery currently causes the configured
272
+ landing check to run twice. Because the default state is ignored, `git clean -fdx` can
273
+ delete it.
274
+
275
+ The single-active-change rule and the requirement to dispatch only approved input still
276
+ partly depend on planner instructions. Approval reconciles records and hashes but does
277
+ not require the previous approved change to be complete before allocating a different
278
+ proposal. Dispatch requires an existing numbered file, but does not validate its
279
+ approval record/hash or require it to be the latest change. These are implementation
280
+ gaps, not additional permissions.
229
281
 
230
282
  ## References
231
283
 
package/src/cli.ts CHANGED
@@ -25,7 +25,6 @@ const usage = `Usage:
25
25
  codeless init
26
26
  codeless create <slug>
27
27
  codeless open <slug>
28
- codeless planner <slug>
29
28
  codeless approve <planner-session>
30
29
  codeless dispatch <numbered-change-file>
31
30
  codeless rework <numbered-change-file> <feedback>
@@ -167,6 +166,37 @@ export async function runCodeless(args: string[]): Promise<void> {
167
166
  run("git", ["show-ref", "--verify", `refs/heads/${integrationBranch}`], repository);
168
167
  const primary = primaryWorktree();
169
168
  const target = canonicalPath(join(workspaceRoot, "worktree", integrationBranch));
169
+ const promptNames = ["change", "implement", "review", "commit"];
170
+ const promptDirectory = join(repository, project.prompts);
171
+ const promptTemplates = join(import.meta.dir, "../prompts");
172
+ const promptPlan = promptNames.map((name) => {
173
+ const template = join(promptTemplates, `${name}.md`);
174
+ const destination = join(promptDirectory, `${name}.md`);
175
+ if (!existsSync(template) || !statSync(template).isFile()) {
176
+ throw new Error(`Missing packaged prompt template: ${template}`);
177
+ }
178
+ if (existsSync(destination) && !statSync(destination).isFile()) {
179
+ throw new Error(`Project prompt is not a file: ${destination}`);
180
+ }
181
+ return { template, destination, exists: existsSync(destination) };
182
+ });
183
+ let promptParent = promptDirectory;
184
+ while (!existsSync(promptParent)) promptParent = dirname(promptParent);
185
+ if (!statSync(promptParent).isDirectory()) {
186
+ throw new Error(`Prompt directory ancestor is not a directory: ${promptParent}`);
187
+ }
188
+ const resolvedPromptDirectory = canonicalPath(promptDirectory);
189
+ const promptLocal = relative(repository, resolvedPromptDirectory);
190
+ if (promptLocal === ".." || promptLocal.startsWith("../") || isAbsolute(promptLocal)) {
191
+ throw new Error(
192
+ `Prompt directory must remain inside the invoking checkout: ${promptDirectory}`,
193
+ );
194
+ }
195
+ if (promptPlan.some((entry) => !entry.exists) && repository === target) {
196
+ throw new Error(
197
+ "Initialize missing prompts from an editable checkout, not the dedicated integration checkout",
198
+ );
199
+ }
170
200
  const matchingBranch = registeredWorktrees().filter(
171
201
  (worktree) => worktree.branch === `refs/heads/${integrationBranch}`,
172
202
  );
@@ -255,6 +285,15 @@ export async function runCodeless(args: string[]): Promise<void> {
255
285
  throw new Error(`Integration worktree target is occupied: ${target}`);
256
286
  }
257
287
 
288
+ for (const { template, destination, exists } of promptPlan) {
289
+ if (exists) {
290
+ console.log(`Preserved prompt: ${destination}`);
291
+ } else {
292
+ mkdirSync(dirname(destination), { recursive: true });
293
+ writeFileSync(destination, readFileSync(template), { flag: "wx" });
294
+ console.log(`Created prompt: ${destination}`);
295
+ }
296
+ }
258
297
  if (defaultWorkspace && !stateIgnored) {
259
298
  const currentIgnore = existsSync(ignoreFile) ? readFileSync(ignoreFile, "utf8") : "";
260
299
  appendFileSync(
@@ -599,11 +638,6 @@ export async function runCodeless(args: string[]): Promise<void> {
599
638
  if (run("git", ["branch", "--show-current"], worktree).trim() !== branch)
600
639
  throw new Error(`${worktree} is not on ${branch}`);
601
640
  requireClean(worktree, branch);
602
- if (
603
- run("git", ["rev-parse", "HEAD"], worktree).trim() !==
604
- run("git", ["rev-parse", integrationBranch], worktree).trim()
605
- )
606
- throw new Error(`${branch} is not at the current ${integrationBranch} baseline`);
607
641
 
608
642
  const plannerPane = string(process.env["HERDR_PANE_ID"], "HERDR_PANE_ID");
609
643
  const agent = object(result(herdr(["agent", "get", plannerPane]))["agent"], "result.agent");
@@ -930,43 +964,6 @@ export async function runCodeless(args: string[]): Promise<void> {
930
964
  console.log(JSON.stringify({ pane, worktree }));
931
965
  }
932
966
 
933
- async function launchPlanner(slug: string): Promise<void> {
934
- if (process.env["HERDR_ENV"] !== "1") {
935
- throw new Error("Run codeless planner from the stream's Herdr-managed shell");
936
- }
937
- const documents = join(workspaceRoot, "stream", slug);
938
- const worktree = join(workspaceRoot, "worktree", slug);
939
- const branch = `stream/${slug}`;
940
- for (const file of ["planner.md", "change.md"]) {
941
- if (!existsSync(join(documents, file))) throw new Error(`Missing ${join(documents, file)}`);
942
- }
943
- if (canonicalPath(process.cwd()) !== worktree) {
944
- throw new Error(`Planner shell is in ${process.cwd()}, expected ${worktree}`);
945
- }
946
- if (run("git", ["branch", "--show-current"], worktree).trim() !== branch) {
947
- throw new Error(`${worktree} is not on ${branch}`);
948
- }
949
- const prompt = changePrompt(slug, documents, worktree);
950
- const selection = readProject(worktree).planner;
951
- await validateRoleSelection("planner", selection, worktree, plannerExtension);
952
- console.log(roleSelectionSummary("planner", selection));
953
- runVisible(
954
- "pi",
955
- [
956
- "--name",
957
- `${slug}-planner`,
958
- ...roleSelectionArguments(selection),
959
- "--extension",
960
- plannerExtension,
961
- "--prompt-template",
962
- promptDirectory(worktree),
963
- "--approve",
964
- activationPrompt(prompt),
965
- ],
966
- worktree,
967
- );
968
- }
969
-
970
967
  function acquireLandSlot(lock: string, slug: string, base: string): "acquired" | "resumed" {
971
968
  let acquired = false;
972
969
  try {
@@ -1122,7 +1119,7 @@ export async function runCodeless(args: string[]): Promise<void> {
1122
1119
  }
1123
1120
  const slug = target;
1124
1121
  if (
1125
- (action !== "create" && action !== "open" && action !== "planner" && action !== "land") ||
1122
+ (action !== "create" && action !== "open" && action !== "land") ||
1126
1123
  slug === undefined ||
1127
1124
  !/^[a-z][a-z0-9-]{0,23}$/.test(slug) ||
1128
1125
  details.length > 0
@@ -1133,10 +1130,6 @@ export async function runCodeless(args: string[]): Promise<void> {
1133
1130
  land(slug);
1134
1131
  return;
1135
1132
  }
1136
- if (action === "planner") {
1137
- await launchPlanner(slug);
1138
- return;
1139
- }
1140
1133
  if (process.env["HERDR_ENV"] !== "1") {
1141
1134
  throw new Error("Run codeless from a Herdr-managed shell pane");
1142
1135
  }
@@ -1184,33 +1177,104 @@ export async function runCodeless(args: string[]): Promise<void> {
1184
1177
  opened = herdr(["worktree", "open", "--path", worktree, "--label", slug, "--no-focus"]);
1185
1178
  }
1186
1179
 
1180
+ const workspace = id(opened, "workspace", "workspace_id");
1181
+ const plannerPane = id(opened, "root_pane", "pane_id");
1182
+ if (plannerPane === process.env["HERDR_PANE_ID"]) {
1183
+ throw new Error(
1184
+ "Run codeless open from another Herdr shell; the planner pane must be available",
1185
+ );
1186
+ }
1187
+ const planner = `${slug.replaceAll("-", "_")}_planner`;
1188
+ const layout = object(
1189
+ result(herdr(["pane", "layout", "--pane", plannerPane]))["layout"],
1190
+ "result.layout",
1191
+ );
1192
+ const implementerPane = rightPane(layout, plannerPane);
1193
+ const panes = layout["panes"] as JsonObject[];
1194
+ if (panes.length !== (implementerPane === undefined ? 1 : 2)) {
1195
+ throw new Error(
1196
+ "Stream layout must contain only the planner and an optional right-hand implementer pane",
1197
+ );
1198
+ }
1199
+
1200
+ function requireLaunchShell(pane: string): void {
1201
+ const processes = foregroundProcesses(paneProcessInfo(pane));
1202
+ if (processes.length !== 1 || !isShell(processes[0]!)) {
1203
+ throw new Error(`Pane ${pane} must be an available shell before opening the stream`);
1204
+ }
1205
+ if (canonicalPath(string(processes[0]!["cwd"], "shell cwd")) !== worktree) {
1206
+ throw new Error(`Pane ${pane} shell is not in ${worktree}`);
1207
+ }
1208
+ }
1209
+
1210
+ function requireManagedPlanner(agent: JsonObject): void {
1211
+ if (
1212
+ agent["name"] !== planner ||
1213
+ agent["agent"] !== "pi" ||
1214
+ agent["interactive_ready"] !== true
1215
+ ) {
1216
+ throw new Error(
1217
+ `Planner pane ${plannerPane} must contain the Herdr-managed ${planner}; exit an unmanaged agent before reopening`,
1218
+ );
1219
+ }
1220
+ if (canonicalPath(string(agent["foreground_cwd"], "planner foreground cwd")) !== worktree) {
1221
+ throw new Error(`Planner ${planner} is not in ${worktree}`);
1222
+ }
1223
+ const session = object(agent["agent_session"], "planner agent_session");
1224
+ if (
1225
+ agent["screen_detection_skipped"] !== true ||
1226
+ session["source"] !== "herdr:pi" ||
1227
+ session["agent"] !== "pi"
1228
+ ) {
1229
+ throw new Error(
1230
+ "Planner requires Herdr's official Pi lifecycle integration; run herdr integration install pi before reopening",
1231
+ );
1232
+ }
1233
+ }
1234
+
1235
+ if (action === "create" || result(opened)["already_open"] === false) {
1236
+ requirePaneShell(plannerPane, worktree);
1237
+ }
1238
+ const plannerProcesses = foregroundProcesses(paneProcessInfo(plannerPane));
1239
+ if (!(plannerProcesses.length === 1 && isShell(plannerProcesses[0]!))) {
1240
+ requireManagedPlanner(
1241
+ object(result(herdr(["agent", "get", plannerPane]))["agent"], "result.agent"),
1242
+ );
1243
+ run("herdr", ["workspace", "focus", workspace]);
1244
+ console.log(`Focused existing planner ${planner}; its session and work remain unchanged.`);
1245
+ return;
1246
+ }
1247
+ requireLaunchShell(plannerPane);
1248
+ if (implementerPane !== undefined) requireLaunchShell(implementerPane);
1249
+
1187
1250
  console.log("Installing project dependencies...");
1188
1251
  const [install, ...installArgs] = readProject(worktree).install;
1189
1252
  run(install, installArgs, worktree);
1190
-
1191
1253
  const selection = readProject(worktree).planner;
1192
1254
  await validateRoleSelection("planner", selection, worktree, plannerExtension);
1193
1255
  console.log(roleSelectionSummary("planner", selection));
1194
1256
 
1195
- const workspace = id(opened, "workspace", "workspace_id");
1196
- const plannerPane = id(opened, "root_pane", "pane_id");
1197
- const split = herdr([
1198
- "pane",
1199
- "split",
1200
- "--pane",
1201
- plannerPane,
1202
- "--direction",
1203
- "right",
1204
- "--ratio",
1205
- "0.5",
1206
- "--cwd",
1207
- worktree,
1208
- "--no-focus",
1209
- ]);
1210
- id(split, "pane", "pane_id");
1211
-
1212
- const planner = `${slug.replaceAll("-", "_")}_planner`;
1213
- run("herdr", [
1257
+ // Recheck after installation/preflight; Herdr owns final interactive readiness.
1258
+ requireLaunchShell(plannerPane);
1259
+ if (implementerPane === undefined) {
1260
+ const split = herdr([
1261
+ "pane",
1262
+ "split",
1263
+ "--pane",
1264
+ plannerPane,
1265
+ "--direction",
1266
+ "right",
1267
+ "--ratio",
1268
+ "0.5",
1269
+ "--cwd",
1270
+ worktree,
1271
+ "--no-focus",
1272
+ ]);
1273
+ requirePaneShell(id(split, "pane", "pane_id"), worktree);
1274
+ } else {
1275
+ requireLaunchShell(implementerPane);
1276
+ }
1277
+ const started = herdr([
1214
1278
  "agent",
1215
1279
  "start",
1216
1280
  planner,
@@ -1228,10 +1292,11 @@ export async function runCodeless(args: string[]): Promise<void> {
1228
1292
  promptDirectory(worktree),
1229
1293
  "--approve",
1230
1294
  ]);
1295
+ requireManagedPlanner(object(result(started)["agent"], "result.agent"));
1231
1296
  run("herdr", [
1232
1297
  "agent",
1233
1298
  "prompt",
1234
- planner,
1299
+ plannerPane,
1235
1300
  activationPrompt(changePrompt(slug, documents, worktree)),
1236
1301
  ]);
1237
1302
  run("herdr", ["workspace", "focus", workspace]);