@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 +57 -25
- package/extension/planner.js +41 -15
- package/package.json +2 -1
- package/prompts/change.md +40 -0
- package/prompts/commit.md +37 -0
- package/prompts/implement.md +17 -0
- package/prompts/review.md +20 -0
- package/spec/workflow.md +215 -163
- package/src/cli.ts +134 -69
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.
|
|
16
|
-
|
|
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.
|
|
95
|
-
|
|
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
|
|
99
|
-
|
|
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
|
|
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.
|
|
106
|
-
|
|
107
|
-
|
|
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.
|
|
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
|
|
161
|
-
|
|
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
|
|
176
|
-
|
|
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,
|
|
194
|
-
|
|
195
|
-
|
|
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`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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;
|
|
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
|
|
package/extension/planner.js
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
##
|
|
8
|
+
## Ownership and lifecycle
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
the
|
|
78
|
-
`
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
and branch summaries), available Pi model cost estimate with USD currency and
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
per-change metric record,
|
|
136
|
-
conflicting duplicate ID. Missing, malformed, or unwritable collection warns
|
|
137
|
-
and yields an explicitly incomplete attempt when possible without failing or
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
The planner inspects the full diff and relevant code, checks the approved
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
and returns one `rework` attempt before queueing review
|
|
148
|
-
reports warn and yield an incomplete attempt after
|
|
149
|
-
timeout, blocked state, identity/worktree/change mismatch,
|
|
150
|
-
without a completed attempt or queued review. The separate
|
|
151
|
-
`finish_stream_implementer` tool first verifies that immutable
|
|
152
|
-
same idle implementer, then gracefully exits it and waits for
|
|
153
|
-
stream-worktree shell. Its failure
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
Dispatch, remediation, and shutdown do not retry automatically or replace an
|
|
157
|
-
|
|
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
|
-
|
|
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
|
|
167
|
-
runs the configured project check in the stream worktree, requires checks to
|
|
168
|
-
leave the worktree clean, and fast-forwards the dedicated
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Codeless captures the current integration branch, fast-forwards the stream worktree,
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
`codeless metrics` reports every recorded stream and a project total in two
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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 !== "
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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
|
-
|
|
1299
|
+
plannerPane,
|
|
1235
1300
|
activationPrompt(changePrompt(slug, documents, worktree)),
|
|
1236
1301
|
]);
|
|
1237
1302
|
run("herdr", ["workspace", "focus", workspace]);
|