@dpeek/codeless 0.1.3 → 0.1.5
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 +30 -17
- package/extension/planner.js +107 -74
- package/package.json +1 -1
- package/prompts/change.md +3 -3
- package/prompts/commit.md +1 -1
- package/spec/workflow.md +44 -26
package/README.md
CHANGED
|
@@ -100,16 +100,20 @@ Review and commit those local prompt edits in the invoking checkout, then bring
|
|
|
100
100
|
that commit onto the configured integration branch before creating streams.
|
|
101
101
|
Required prompts and directions must exist for stream creation and opening.
|
|
102
102
|
|
|
103
|
-
The package-owned planner extension
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
The package-owned planner extension admits each newly launched planner using
|
|
104
|
+
its exact `<slug>-planner` Pi name, `<slug>_planner` Herdr identity, managed
|
|
105
|
+
readiness, canonical worktree, and official Pi lifecycle integration. It checks
|
|
106
|
+
that all five planner tools are active before sending `/change` and binds the
|
|
107
|
+
admitted process, pane, worktree, name, and live session ID.
|
|
108
|
+
|
|
109
|
+
After landing, replacement activation uses a one-use ticket held in that Pi
|
|
110
|
+
process, bound to the new conversation's session ID. It verifies the local
|
|
111
|
+
identity, effective model/thinking selection, and tools without querying Herdr's
|
|
112
|
+
asynchronous status. Saved session entries cannot replay a handoff. Codeless
|
|
113
|
+
requires activation acknowledgement before submitting `/change` exactly once;
|
|
114
|
+
failed or cancelled handoffs never retry automatically. After a failed
|
|
115
|
+
replacement, exit Pi and reopen the stream from another Herdr shell. Herdr's
|
|
116
|
+
integration still supplies monitoring and external agent control. Global
|
|
113
117
|
installation of Codeless's extension is unnecessary.
|
|
114
118
|
The approval tool has no arguments. Its extension derives the active
|
|
115
119
|
`<slug>-planner` Pi session and passes it to the backing CLI, which requires it
|
|
@@ -131,7 +135,13 @@ replaces the Pi session in the same pane. It preserves the planner name, applies
|
|
|
131
135
|
the planner selection read and validated after the stream fast-forwards to the
|
|
132
136
|
captured integration commit, then activates and verifies the replacement before
|
|
133
137
|
sending `/change` after resources reload. The previous conversation is not copied; the
|
|
134
|
-
journal and project files carry context.
|
|
138
|
+
journal and project files carry context. The fresh planner reads repository guidance,
|
|
139
|
+
its journal, and current direction first. It stops on wholly gated work, uses the
|
|
140
|
+
latest numbered change only for active or ambiguous recovery, and reads older changes
|
|
141
|
+
only for journal-linked unresolved decisions. After selecting an ungated candidate it
|
|
142
|
+
reads relevant contracts and implementation; after a fast-forward it also rereads
|
|
143
|
+
current direction and files affected by incoming commits, without mining deleted or
|
|
144
|
+
historical documents for work.
|
|
135
145
|
|
|
136
146
|
This uses Pi's `newSession({ setup, withSession })` command API, verified with
|
|
137
147
|
Pi 0.85.1. Only the replacement context activates the selection and sends the
|
|
@@ -228,8 +238,10 @@ only when absent, and uses `herdr agent start` for named, readiness-checked Pi
|
|
|
228
238
|
startup. It verifies the result before sending activation. An occupied or
|
|
229
239
|
mismatched pane, unmanaged agent, ambiguous layout, or failed startup stops;
|
|
230
240
|
Codeless never takes over an existing agent. Pi's display name is separate from
|
|
231
|
-
Herdr's managed agent name.
|
|
232
|
-
|
|
241
|
+
Herdr's managed agent name. Launch admission verifies both names, lifecycle
|
|
242
|
+
authority, and worktree; subsequent conversation replacements use the local
|
|
243
|
+
handoff described above. Codeless never renames an unmanaged process. There is
|
|
244
|
+
no direct `planner` command.
|
|
233
245
|
|
|
234
246
|
Dispatch validates the implementer selection before touching the planner's
|
|
235
247
|
right-hand pane, starts a fresh ephemeral implementer with Codeless's reporting
|
|
@@ -276,10 +288,11 @@ the integration checkout. Success releases the lock. Other branches and
|
|
|
276
288
|
checkouts are untouched; no push is performed.
|
|
277
289
|
|
|
278
290
|
Another lock owner causes a stop, without queuing or polling. Rebase conflicts
|
|
279
|
-
or failed checks retain ownership.
|
|
280
|
-
`land
|
|
281
|
-
|
|
282
|
-
|
|
291
|
+
or failed checks retain ownership. During conflict resolution, run focused checks
|
|
292
|
+
when useful; rerun `land` for the configured full check, which it alone owns.
|
|
293
|
+
It verifies the recorded integration commit has not changed. To abandon a landing,
|
|
294
|
+
inspect the owner/base and Git state before manually removing the lock. There is
|
|
295
|
+
no automatic stale-lock removal or retry.
|
|
283
296
|
|
|
284
297
|
`next` is the session handoff's preparation command. It requires the stream's
|
|
285
298
|
own clean worktree and latest numbered change, a full commit hash present in its
|
package/extension/planner.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { validAttempt } from "../src/attempt.ts";
|
|
4
5
|
|
|
5
6
|
const codeless = fileURLToPath(new URL("../bin/codeless", import.meta.url));
|
|
6
7
|
const thinkingLevels = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
7
|
-
const nativeSessionAttempts = 20;
|
|
8
|
-
const nativeSessionRetryDelayMs = 50;
|
|
9
8
|
const requiredTools = [
|
|
10
9
|
"approve_stream_change",
|
|
11
10
|
"dispatch_stream_implementer",
|
|
@@ -30,32 +29,66 @@ function selection(value) {
|
|
|
30
29
|
return { provider: value.provider, model: value.model, thinking: value.thinking };
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
// Pi reloads extension modules when replacing a session. Keep only in-flight
|
|
33
|
+
// tickets on the process global; neither persisted entries nor a module cache
|
|
34
|
+
// can establish handoff provenance. Every ticket is removed on consume/finally.
|
|
35
|
+
const handoffKey = Symbol.for("@dpeek/codeless/planner-handoffs");
|
|
36
|
+
const handoffs = (globalThis[handoffKey] ??= new Map());
|
|
37
|
+
|
|
38
|
+
function worktree(cwd) {
|
|
39
|
+
try {
|
|
40
|
+
return realpathSync(cwd);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error("Codeless could not resolve the planner worktree");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
export default function plannerExtension(pi) {
|
|
34
47
|
let registered = false;
|
|
35
48
|
let pending;
|
|
49
|
+
let admitted;
|
|
36
50
|
|
|
37
51
|
pi.registerCommand("streams-activate", {
|
|
38
52
|
description: "Verify this planner session before starting its project prompt",
|
|
39
53
|
handler: async (args, ctx) => {
|
|
40
|
-
let
|
|
54
|
+
let input;
|
|
41
55
|
try {
|
|
42
|
-
|
|
56
|
+
input = JSON.parse(args);
|
|
43
57
|
} catch {
|
|
44
|
-
throw new Error("Codeless activation requires
|
|
58
|
+
throw new Error("Codeless activation requires a JSON prompt or handoff ticket");
|
|
45
59
|
}
|
|
46
|
-
|
|
60
|
+
const replacement = typeof input === "object" && input !== null;
|
|
61
|
+
const ticket = replacement ? handoffs.get(input.handoff) : undefined;
|
|
62
|
+
if (replacement) {
|
|
63
|
+
handoffs.delete(input.handoff);
|
|
64
|
+
if (!ticket) throw new Error("Codeless handoff is missing, expired, or already consumed");
|
|
65
|
+
} else if (typeof input !== "string" || !input.startsWith("/change ")) {
|
|
47
66
|
throw new Error("Codeless activation requires a /change project prompt");
|
|
48
67
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if (admitted) throw new Error("Codeless planner session is already activated");
|
|
69
|
+
const sessionName = pi.getSessionName();
|
|
70
|
+
const match = /^([a-z][a-z0-9-]{0,23})-planner$/.exec(sessionName ?? "");
|
|
71
|
+
if (!match)
|
|
72
|
+
throw new Error("Codeless activation requires an exact <slug>-planner Pi session name");
|
|
73
|
+
const expectedPlanner = `${match[1].replaceAll("-", "_")}_planner`;
|
|
74
|
+
const pane = process.env.HERDR_PANE_ID;
|
|
75
|
+
if (!pane) throw new Error("Codeless activation requires a Herdr-managed planner pane");
|
|
76
|
+
const cwd = worktree(ctx.cwd);
|
|
77
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
78
|
+
if (replacement) {
|
|
79
|
+
if (
|
|
80
|
+
ticket.binding.pid !== process.pid ||
|
|
81
|
+
ticket.binding.pane !== pane ||
|
|
82
|
+
ticket.binding.cwd !== cwd ||
|
|
83
|
+
ticket.binding.sessionName !== sessionName ||
|
|
84
|
+
ticket.sessionId !== sessionId ||
|
|
85
|
+
ticket.binding.sessionId === sessionId
|
|
86
|
+
) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Codeless handoff does not match this planner process, pane, worktree, or session",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const requested = ticket.selection;
|
|
59
92
|
const reference = `${requested.provider}/${requested.model}`;
|
|
60
93
|
const model = ctx.modelRegistry.find(requested.provider, requested.model);
|
|
61
94
|
if (!model) throw new Error(`Planner requested ${reference}, but Pi could not find it`);
|
|
@@ -68,36 +101,26 @@ export default function plannerExtension(pi) {
|
|
|
68
101
|
ctx.model.id !== requested.model ||
|
|
69
102
|
pi.getThinkingLevel() !== requested.thinking
|
|
70
103
|
) {
|
|
71
|
-
const effective = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "no model";
|
|
72
104
|
throw new Error(
|
|
73
|
-
`
|
|
105
|
+
`Codeless planner could not apply ${reference} at thinking level ${requested.thinking}`,
|
|
74
106
|
);
|
|
75
107
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
return JSON.parse(identity.stdout).result?.agent;
|
|
95
|
-
} catch {
|
|
96
|
-
throw new Error("Herdr returned an invalid planner identity response");
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const attempts = activation ? nativeSessionAttempts : 1;
|
|
100
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
108
|
+
} else {
|
|
109
|
+
const plannerIdentity = async () => {
|
|
110
|
+
const identity = await pi.exec("herdr", ["agent", "get", pane], { timeout: 30_000 });
|
|
111
|
+
if (identity.code !== 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
identity.stderr.trim() ||
|
|
114
|
+
identity.stdout.trim() ||
|
|
115
|
+
"Codeless could not verify Herdr planner identity",
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(identity.stdout).result?.agent;
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error("Herdr returned an invalid planner identity response");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
101
124
|
const agent = await plannerIdentity();
|
|
102
125
|
if (agent?.name !== expectedPlanner) {
|
|
103
126
|
throw new Error(
|
|
@@ -107,36 +130,17 @@ export default function plannerExtension(pi) {
|
|
|
107
130
|
if (agent.agent !== "pi" || agent.interactive_ready !== true) {
|
|
108
131
|
throw new Error("Codeless requires a Herdr-managed Pi planner started by codeless open");
|
|
109
132
|
}
|
|
110
|
-
if (
|
|
111
|
-
typeof agent.foreground_cwd !== "string" ||
|
|
112
|
-
resolve(agent.foreground_cwd) !== resolve(ctx.cwd)
|
|
113
|
-
) {
|
|
133
|
+
if (typeof agent.foreground_cwd !== "string" || worktree(agent.foreground_cwd) !== cwd) {
|
|
114
134
|
throw new Error("Codeless planner worktree does not match Herdr's foreground cwd");
|
|
115
135
|
}
|
|
116
136
|
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
137
|
if (
|
|
124
138
|
agent.screen_detection_skipped !== true ||
|
|
125
139
|
session?.source !== "herdr:pi" ||
|
|
126
|
-
session.agent !== "pi"
|
|
127
|
-
!expectedSession
|
|
140
|
+
session.agent !== "pi"
|
|
128
141
|
) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
"Codeless planner native session does not match Herdr's Pi lifecycle integration",
|
|
131
|
-
);
|
|
142
|
+
throw new Error("Codeless planner requires Herdr's Pi lifecycle integration");
|
|
132
143
|
}
|
|
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));
|
|
140
144
|
}
|
|
141
145
|
const activeTools = ctx.getSystemPromptOptions().selectedTools ?? [];
|
|
142
146
|
const missing = requiredTools.filter((tool) => !activeTools.includes(tool));
|
|
@@ -145,8 +149,10 @@ export default function plannerExtension(pi) {
|
|
|
145
149
|
`Codeless planner activation is missing required tools: ${missing.join(", ")}`,
|
|
146
150
|
);
|
|
147
151
|
}
|
|
152
|
+
admitted = { pid: process.pid, pane, cwd, sessionName, sessionId };
|
|
148
153
|
ctx.ui.notify(`Planner activated: ${sessionName} / ${expectedPlanner}`, "info");
|
|
149
|
-
|
|
154
|
+
if (ticket) ticket.activated = true;
|
|
155
|
+
else pi.sendUserMessage(input, { expandPromptTemplates: true });
|
|
150
156
|
},
|
|
151
157
|
});
|
|
152
158
|
|
|
@@ -156,8 +162,18 @@ export default function plannerExtension(pi) {
|
|
|
156
162
|
if (!pending || pending.running) throw new Error("No pending Codeless handoff");
|
|
157
163
|
const request = pending;
|
|
158
164
|
request.running = true;
|
|
165
|
+
let token;
|
|
159
166
|
try {
|
|
160
167
|
await ctx.waitForIdle();
|
|
168
|
+
if (
|
|
169
|
+
!admitted ||
|
|
170
|
+
admitted.pid !== process.pid ||
|
|
171
|
+
admitted.pane !== process.env.HERDR_PANE_ID ||
|
|
172
|
+
admitted.cwd !== worktree(ctx.cwd) ||
|
|
173
|
+
admitted.sessionId !== ctx.sessionManager.getSessionId() ||
|
|
174
|
+
admitted.sessionName !== pi.getSessionName()
|
|
175
|
+
)
|
|
176
|
+
throw new Error("Codeless handoff requires the activated planner session");
|
|
161
177
|
const execution = await pi.exec(
|
|
162
178
|
"bun",
|
|
163
179
|
[codeless, "next", request.changePath, request.landedCommit],
|
|
@@ -181,23 +197,39 @@ export default function plannerExtension(pi) {
|
|
|
181
197
|
throw new Error("Codeless returned an invalid planner handoff");
|
|
182
198
|
}
|
|
183
199
|
const requested = selection(requestedValue);
|
|
200
|
+
token = randomUUID();
|
|
201
|
+
const ticket = {
|
|
202
|
+
binding: admitted,
|
|
203
|
+
selection: requested,
|
|
204
|
+
sessionId: undefined,
|
|
205
|
+
activated: false,
|
|
206
|
+
};
|
|
207
|
+
handoffs.set(token, ticket);
|
|
184
208
|
const result = await ctx.newSession({
|
|
185
209
|
setup: async (sm) => {
|
|
186
210
|
sm.appendSessionInfo(sessionName);
|
|
187
|
-
sm.
|
|
188
|
-
role: "planner",
|
|
189
|
-
selection: requested,
|
|
190
|
-
});
|
|
211
|
+
ticket.sessionId = sm.getSessionId();
|
|
191
212
|
},
|
|
192
213
|
withSession: async (replacement) => {
|
|
193
|
-
await replacement.sendUserMessage(
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
await replacement.sendUserMessage(
|
|
215
|
+
`/streams-activate ${JSON.stringify({ handoff: token })}`,
|
|
216
|
+
{
|
|
217
|
+
expandPromptTemplates: true,
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
// Pi displays command errors instead of rejecting sendUserMessage.
|
|
221
|
+
// Require an explicit receipt before submitting the project prompt.
|
|
222
|
+
if (!ticket.activated)
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Codeless replacement activation failed; exit Pi and reopen the stream",
|
|
225
|
+
);
|
|
226
|
+
await replacement.sendUserMessage(prompt, { expandPromptTemplates: true });
|
|
196
227
|
},
|
|
197
228
|
});
|
|
198
229
|
if (result.cancelled)
|
|
199
230
|
throw new Error("Codeless handoff cancelled; the current session was retained");
|
|
200
231
|
} finally {
|
|
232
|
+
if (token) handoffs.delete(token);
|
|
201
233
|
pending = undefined;
|
|
202
234
|
}
|
|
203
235
|
},
|
|
@@ -233,6 +265,7 @@ export default function plannerExtension(pi) {
|
|
|
233
265
|
additionalProperties: false,
|
|
234
266
|
},
|
|
235
267
|
async execute(_toolCallId, params) {
|
|
268
|
+
if (!admitted) throw new Error("Codeless handoff requires an activated planner");
|
|
236
269
|
if (pending) throw new Error("A Codeless handoff is already pending");
|
|
237
270
|
pending = {
|
|
238
271
|
changePath: params.changePath.replace(/^@/, ""),
|
package/package.json
CHANGED
package/prompts/change.md
CHANGED
|
@@ -5,11 +5,11 @@ argument-hint: "<stream-directory> <direction-file>"
|
|
|
5
5
|
|
|
6
6
|
You are the planner for the stream at `$1`. The current working directory is the stream's repository worktree.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
First read the repository guidance, `$1/planner.md`, and `$2`. Read `.codeless/config.json` to resolve the integration branch. Current direction is authoritative for candidate discovery: do not mine deleted or historical documents for work when it is clear. If `planner.md` shows no active work and current direction has no ungated worthwhile candidate, stop rather than reading more context.
|
|
9
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.
|
|
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. Read the latest numbered change only when active or ambiguous work needs recovery. Read an older numbered change only when `planner.md` identifies its unresolved decision as still relevant.
|
|
11
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,
|
|
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 select an ungated candidate from current direction, require a clean worktree with no commits outside the configured integration branch, and fast-forward to that branch. After fast-forwarding, reread `$2`, every file affected by incoming commits, and the affected contracts and implementation; do not mine deleted or historical documents. 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
13
|
|
|
14
14
|
Propose exactly one small, complete change and write it to `$1/change.md`:
|
|
15
15
|
|
package/prompts/commit.md
CHANGED
|
@@ -32,6 +32,6 @@ The next planner proposes one change and waits for the operator's `go`; this han
|
|
|
32
32
|
There are two expected landing stops:
|
|
33
33
|
|
|
34
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
|
|
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 focused checks when useful to validate a resolution, then run `codeless land <slug>` again to finish; `land` alone runs the configured full check. The slot prevents another automated landing from moving the integration branch while you resolve it.
|
|
36
36
|
|
|
37
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.
|
package/spec/workflow.md
CHANGED
|
@@ -137,26 +137,39 @@ the stream worktree; occupied, mismatched, or ambiguous layouts stop unchanged.
|
|
|
137
137
|
The operator must invoke opening from outside those target panes. Reopening an
|
|
138
138
|
existing managed planner focuses it without installation or another prompt.
|
|
139
139
|
|
|
140
|
-
The package-owned extension
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
The package-owned extension admits a newly launched planner after checking its
|
|
141
|
+
exact `<slug>-planner` Pi name, `<slug-with-hyphens-replaced>_planner` Herdr
|
|
142
|
+
name, managed interactive readiness, canonical foreground worktree, official Pi
|
|
143
|
+
lifecycle authority and reporter, and active planner tools. Admission binds the
|
|
144
|
+
process ID, pane, canonical worktree, planner name, and native Pi session ID.
|
|
145
|
+
Activation never repairs names or submits the initial prompt twice. The direct
|
|
146
|
+
`planner` command is removed; recovery exits Pi deliberately and reopens from
|
|
147
|
+
another Herdr shell.
|
|
148
|
+
|
|
149
|
+
Post-landing conversation replacement uses that admission, without querying
|
|
150
|
+
Herdr. After idle and workflow validation, Codeless creates a one-use in-memory
|
|
151
|
+
handoff ticket for the admitted parent and requested model/thinking selection.
|
|
152
|
+
Pi's `newSession` setup binds it to the newly allocated native session ID;
|
|
153
|
+
`withSession` invokes activation in the fresh extension. Activation consumes the
|
|
154
|
+
ticket before checking the same process, pane, canonical worktree and planner
|
|
155
|
+
name, a distinct matching replacement session, effective selection, and active
|
|
156
|
+
tools. Saved session entries cannot authorize a handoff. The ticket registry
|
|
157
|
+
survives Pi's extension module reload within the process, but no ticket survives
|
|
158
|
+
consumption, cancellation, failure, or process exit.
|
|
159
|
+
|
|
160
|
+
Pi displays extension-command errors without rejecting the command submission.
|
|
161
|
+
Codeless therefore requires an explicit activation acknowledgement before
|
|
162
|
+
`withSession` submits the project prompt exactly once through the replacement
|
|
163
|
+
context. Missing acknowledgement stops visibly. Neither old Pi contexts nor
|
|
164
|
+
Herdr's asynchronous lifecycle or native-session snapshots participate in
|
|
165
|
+
replacement activation. Delayed, missing, or rejected lifecycle reports cannot
|
|
166
|
+
block this local handoff. Cancellation retains the old conversation; failed
|
|
167
|
+
replacement requires deliberate exit and reopen, with no automatic retry.
|
|
168
|
+
|
|
169
|
+
Herdr's official Pi integration remains responsible for lifecycle monitoring,
|
|
170
|
+
external agent control, and native-session restore reporting. Implementers use
|
|
171
|
+
the corresponding `_impl` and `-impl` names. Codeless loads its own extension
|
|
172
|
+
explicitly. This boundary was checked against Herdr 0.8.2 and Pi 0.85.1.
|
|
160
173
|
|
|
161
174
|
## Dispatch and review
|
|
162
175
|
|
|
@@ -213,7 +226,8 @@ integration commit.
|
|
|
213
226
|
If the integration branch advanced, landing rebases the single stream commit. It then
|
|
214
227
|
rereads and runs the configured project check in the stream worktree, requires checks to
|
|
215
228
|
leave the worktree clean, and fast-forwards the dedicated integration checkout. Only
|
|
216
|
-
successful completion releases the lock.
|
|
229
|
+
successful completion releases the lock. Conflict recovery may run focused checks to
|
|
230
|
+
validate resolutions, but only `land` runs the configured full check after rebase.
|
|
217
231
|
|
|
218
232
|
A lock owned by another stream stops landing without polling. A rebase conflict, failed
|
|
219
233
|
check, or other error after acquisition retains this stream's lock for deliberate
|
|
@@ -234,7 +248,13 @@ validates the updated direction, prompts, and planner selection, and returns the
|
|
|
234
248
|
session name and `/change` prompt. The extension replaces the Pi session in the same
|
|
235
249
|
pane, preserves its name, activates the validated selection and planner identity, and
|
|
236
250
|
only then sends the project prompt. Conversation history is not copied; the journal and
|
|
237
|
-
project files carry durable context.
|
|
251
|
+
project files carry durable context. The prompt first reads repository guidance, the
|
|
252
|
+
journal, and current direction; it stops when work is wholly gated. It reads the latest
|
|
253
|
+
numbered change only for active or ambiguous recovery and older changes only for
|
|
254
|
+
journal-identified unresolved decisions. Current direction selects candidates before
|
|
255
|
+
relevant contracts and implementation are inspected. After a baseline fast-forward, it
|
|
256
|
+
rereads current direction plus files affected by incoming commits, affected contracts,
|
|
257
|
+
and code, without mining deleted or historical documents for work.
|
|
238
258
|
|
|
239
259
|
A cancelled or failed replacement stops for operator attention. Landing remains
|
|
240
260
|
complete, and any successful preparation fast-forward remains applied. There is no
|
|
@@ -267,10 +287,8 @@ an approval source, or a recovery mechanism.
|
|
|
267
287
|
## Limits
|
|
268
288
|
|
|
269
289
|
Codeless is attended and intentionally has no supervisor, project registry, queue,
|
|
270
|
-
automatic landing retry, stale-lock recovery, or unattended approval.
|
|
271
|
-
|
|
272
|
-
landing check to run twice. Because the default state is ignored, `git clean -fdx` can
|
|
273
|
-
delete it.
|
|
290
|
+
automatic landing retry, stale-lock recovery, or unattended approval. Because the
|
|
291
|
+
default state is ignored, `git clean -fdx` can delete it.
|
|
274
292
|
|
|
275
293
|
The single-active-change rule and the requirement to dispatch only approved input still
|
|
276
294
|
partly depend on planner instructions. Approval reconciles records and hashes but does
|