theslopmachine 0.7.5 → 0.7.6
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 +2 -0
- package/assets/agents/slopmachine-claude.md +3 -0
- package/assets/skills/claude-worker-management/SKILL.md +38 -1
- package/assets/skills/developer-session-lifecycle/SKILL.md +2 -0
- package/assets/slopmachine/utils/claude_live_common.mjs +45 -6
- package/assets/slopmachine/utils/claude_live_launch.mjs +2 -2
- package/assets/slopmachine/workflow-init.js +20 -0
- package/package.json +1 -1
- package/src/init.js +1 -0
package/README.md
CHANGED
|
@@ -170,6 +170,8 @@ Important details:
|
|
|
170
170
|
- `--adopt` moves the current project files into `repo/`, preserves root workflow state in the parent workspace, and skips the automatic bootstrap commit
|
|
171
171
|
- `--continue-from <PX>` is a smoother alias for existing-project bootstrap; it implies adoption mode and seeds the requested start phase in one step
|
|
172
172
|
- if `--continue-from <PX>` is run while your current working directory is already the real project `repo/`, SlopMachine automatically treats `..` as the workspace root and writes the workflow state there instead of creating `repo/repo`
|
|
173
|
+
- when a later start phase is seeded for adoption or recovery, the Beads workflow phases before that requested phase are created and immediately marked completed so tracker state matches the seeded entry point
|
|
174
|
+
- in the `slopmachine-claude` path, if adopted or resumed later-phase work has no recoverable tracked Claude developer session yet, the owner must launch and orient the needed Claude lane first and only then continue the substantive work in that same session
|
|
173
175
|
- `--phase <PX>` seeds the initial `current_phase` for adoption/recovery bootstrap; the owner should still fall back if the real repo evidence does not support that later phase
|
|
174
176
|
|
|
175
177
|
### `slopmachine set-token`
|
|
@@ -209,6 +209,7 @@ Maintain exactly one active developer session at a time.
|
|
|
209
209
|
- the live Claude lane must run the installed Claude `developer` agent for normal work, and implementation-capable helper branches should stay developer-scoped when the environment supports explicit agent selection
|
|
210
210
|
- launch Claude lanes with an explicit model choice rather than relying on the CLI default: use `opus` with `medium` effort for normal work, raise to `opus` with `xhigh` effort only when the planning/debugging/security difficulty genuinely justifies it, use `sonnet` with `medium` effort for documentation-heavy or otherwise simpler work, and keep helper subagents on `sonnet` by default unless there is a concrete reason to raise them too
|
|
211
211
|
- do not create a fresh `develop-N` Claude session unless controlled replacement or explicit user direction actually requires it
|
|
212
|
+
- if adopted or resumed work needs Claude developer execution but no recoverable tracked Claude session exists yet, determine the correct lane for the current boundary, launch and orient that lane through `claude-worker-management`, persist the returned session id, and only then continue the substantive work
|
|
212
213
|
- when `P7` begins, do not automatically switch away from `develop-N`
|
|
213
214
|
- each fresh evaluation result decides the remediation lane:
|
|
214
215
|
- `fail` -> route the issue list back to the latest `develop-N` Claude session and discard the working audit report file after triage
|
|
@@ -231,6 +232,8 @@ Maintain exactly one active developer session at a time.
|
|
|
231
232
|
|
|
232
233
|
Do not launch the developer before clarification is complete and the workflow is ready to enter `P2`.
|
|
233
234
|
|
|
235
|
+
If later-phase adopted or repaired work reaches scaffold, development, verification, hardening, or evaluator remediation with no recoverable Claude session yet, do not stall there or treat the absence itself as a blocker. Launch the required live Claude lane first, complete its first orientation exchange, persist the session id and lane metadata, and then continue the bounded work in that same session.
|
|
236
|
+
|
|
234
237
|
When the first develop developer session begins in `P2`, start it in this exact order through the live bridge:
|
|
235
238
|
|
|
236
239
|
1. launch the live `develop-1` Claude `developer` lane
|
|
@@ -37,6 +37,23 @@ Use this skill whenever `slopmachine-claude` needs to launch, inspect, or messag
|
|
|
37
37
|
- each substantive message should state the current engineering boundary, exact expected outcomes for that turn, the evidence required back, the important shortcuts that are not acceptable, and the stopping point
|
|
38
38
|
- default to one bounded engineering objective per owner turn; if a request would naturally cross planning, scaffold, development, or gate-review boundaries, split it into separate turns
|
|
39
39
|
|
|
40
|
+
## Session-presence rule
|
|
41
|
+
|
|
42
|
+
Before any Claude-backed developer work continues:
|
|
43
|
+
|
|
44
|
+
- determine whether the intended developer lane already has a recoverable live Claude session
|
|
45
|
+
- if the intended lane exists and its bridge state is recoverable, recover that same session and continue there
|
|
46
|
+
- if the current workflow boundary requires Claude developer work and no recoverable session is present yet, launch the necessary Claude lane first instead of treating the missing session as a handoff blocker
|
|
47
|
+
- do not send substantive work until the live lane exists, bridge registration has captured the Claude `session_id`, owner metadata and Beads comments have been synced, and the first session-start message for that lane has completed
|
|
48
|
+
- a missing Claude session during adoption, conservative phase repair, or other first-entry work is not itself an error; it means the owner must establish the correct live lane before continuing
|
|
49
|
+
|
|
50
|
+
Choose the first-launch action by boundary:
|
|
51
|
+
|
|
52
|
+
- `P2` planning entry with no Claude session yet -> launch `develop-1` and perform the planning handshake
|
|
53
|
+
- `P3` through `P6` entry with no recoverable `develop-N` session yet -> launch the appropriate `develop-N` lane, orient it to the current repo state, then continue with the bounded scaffold, development, verification, or hardening turn
|
|
54
|
+
- `P7` remediation routed to `develop-N` after a `fail` audit with no recoverable develop session yet -> launch the intended `develop-N` lane, orient it to the current delivered repo state and upcoming evaluator-driven remediation, then continue with the issue list
|
|
55
|
+
- `P7` remediation routed to `bugfix-N` after a `partial pass` audit -> launch the fresh `bugfix-N` lane and use the bugfix orientation handshake below
|
|
56
|
+
|
|
40
57
|
## Lane launch rule
|
|
41
58
|
|
|
42
59
|
For a new bounded developer session slot:
|
|
@@ -80,11 +97,12 @@ The default pattern is to let the live lane start normally and then persist the
|
|
|
80
97
|
For all later turns in the same bounded developer slot:
|
|
81
98
|
|
|
82
99
|
```bash
|
|
83
|
-
printf '%s' "$PROMPT" | node ~/slopmachine/utils/claude_live_turn.mjs --runtime-dir <dir> --timeout-ms <turn-timeout>
|
|
100
|
+
printf '%s' "$PROMPT" | node ~/slopmachine/utils/claude_live_turn.mjs --runtime-dir <dir> --prompt-stdin 1 --timeout-ms <turn-timeout>
|
|
84
101
|
```
|
|
85
102
|
|
|
86
103
|
- inject exactly one message at a time into the idle live lane
|
|
87
104
|
- pass the prompt directly to the wrapper through stdin as the primary input path instead of requiring an owner-side prompt file
|
|
105
|
+
- when invoking the turn wrapper from the OpenCode Bash tool, pass `--prompt-stdin 1` explicitly so prompt detection does not depend on host stdin/TTY quirks
|
|
88
106
|
- wait for `Stop` or `StopFailure` before sending the next message
|
|
89
107
|
- do not bypass the bridge by calling the channel HTTP endpoint directly from owner logic
|
|
90
108
|
- if turn execution fails, stop and recover explicitly instead of silently creating a new worker
|
|
@@ -286,6 +304,25 @@ Do not tell the developer worker to read files outside `repo/`.
|
|
|
286
304
|
If project-lead artifacts outside `repo/` matter, restate their content directly in the message instead of passing file paths.
|
|
287
305
|
Do not mention session names, slot labels, or workflow phase labels to the developer worker.
|
|
288
306
|
|
|
307
|
+
### Adopted or repaired `develop-N` orientation handshake
|
|
308
|
+
|
|
309
|
+
When work enters scaffold, development, integrated verification, hardening, or `fail`-routed remediation without a recoverable `develop-N` Claude session yet:
|
|
310
|
+
|
|
311
|
+
1. launch the live `develop-N` lane needed for that boundary
|
|
312
|
+
2. use the first message only to orient that session to the current repo and delivered state
|
|
313
|
+
3. make clear in plain engineering language that the codebase already exists and work is continuing from the current state rather than starting from zero
|
|
314
|
+
4. say what kind of bounded follow-up work will come next, such as scaffold completion, a development slice, verification corrections, hardening, or evaluator-driven remediation
|
|
315
|
+
5. wait for the first response and store the Claude session id from bridge `state.json`
|
|
316
|
+
6. only after that orientation exchange, continue the same live lane with the first bounded work request
|
|
317
|
+
|
|
318
|
+
The orientation message should:
|
|
319
|
+
|
|
320
|
+
- identify the project and current repo as the working context
|
|
321
|
+
- summarize the current delivered state and the accepted constraints that matter immediately
|
|
322
|
+
- restate only the current engineering boundary and the next work type that will be requested after orientation
|
|
323
|
+
- avoid broad replanning unless the owner has intentionally reopened planning
|
|
324
|
+
- avoid mentioning workflow internals, phase labels, or session-lane labels
|
|
325
|
+
|
|
289
326
|
### `bugfix-N` orientation handshake
|
|
290
327
|
|
|
291
328
|
When a fresh `partial pass` evaluation result opens the next remediation lane:
|
|
@@ -206,6 +206,8 @@ Keep `../metadata.json` focused on project facts and exported project metadata w
|
|
|
206
206
|
|
|
207
207
|
- if session or phase records disagree, stop and repair the inconsistency before proceeding
|
|
208
208
|
- if the current phase already has an active developer session, recover it instead of silently creating a replacement
|
|
209
|
+
- if an adopted, resumed, or conservatively repaired run reaches a developer-work boundary with no recoverable Claude session yet, treat that as a controlled first-launch case rather than a fatal inconsistency; choose the correct lane for the current boundary and hand off to `claude-worker-management` to launch and orient it before work continues
|
|
210
|
+
- if the current boundary requires Claude developer work but `active_developer_session_id` is still empty, do not continue substantive phase work until the missing-session case has been repaired by recovering or launching the intended lane
|
|
209
211
|
- if an evaluator session is marked active, recover it before continuing the current `P7` cycle
|
|
210
212
|
- treat live-lane recovery as deterministic recovery, not guesswork
|
|
211
213
|
- if the active Claude developer session is marked `rate_limited`, do not replace it with owner-side coding; preserve it, record the blocked state, and auto-wait for reset or resume from the same session when the wait helper completes
|
|
@@ -8,9 +8,9 @@ import crypto from 'node:crypto'
|
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
import { spawn } from 'node:child_process'
|
|
10
10
|
|
|
11
|
-
import { emitFailure, emitSuccess, extractRateLimitMetadata, parseArgs, readJsonFile, readPrompt, sleep, waitForRateLimitReset, writeFileIfNeeded, writeJsonIfNeeded } from './claude_worker_common.mjs'
|
|
11
|
+
import { emitFailure, emitSuccess, extractRateLimitMetadata, parseArgs, readJsonFile, readPrompt, readPromptInput, sleep, waitForRateLimitReset, writeFileIfNeeded, writeJsonIfNeeded } from './claude_worker_common.mjs'
|
|
12
12
|
|
|
13
|
-
export { emitFailure, emitSuccess, parseArgs, readPrompt, sleep, waitForRateLimitReset, writeJsonIfNeeded }
|
|
13
|
+
export { emitFailure, emitSuccess, parseArgs, readPrompt, readPromptInput, sleep, waitForRateLimitReset, writeJsonIfNeeded }
|
|
14
14
|
|
|
15
15
|
export const DEFAULT_LAUNCH_TIMEOUT_MS = 3600000
|
|
16
16
|
export const DEFAULT_TURN_TIMEOUT_MS = 3600000
|
|
@@ -170,17 +170,56 @@ export async function waitFor(predicate, { timeoutMs, intervalMs = DEFAULT_POLL_
|
|
|
170
170
|
throw new Error(errorMessage)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
function detectClaudeStartupPrompt(pane) {
|
|
174
|
+
if (!pane) {
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
pane.includes('Quick safety check:')
|
|
180
|
+
|| pane.includes('Yes, I trust this folder')
|
|
181
|
+
|| pane.includes('Accessing workspace:')
|
|
182
|
+
) {
|
|
183
|
+
return 'workspace-trust'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
pane.includes('WARNING: Loading development channels')
|
|
188
|
+
|| pane.includes('I am using this for local development')
|
|
189
|
+
) {
|
|
190
|
+
return 'development-channels'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function maybeAcceptClaudeStartupPrompts(sessionName, timeoutMs = 30000) {
|
|
174
197
|
const deadline = Date.now() + timeoutMs
|
|
198
|
+
let lastAcceptedPrompt = null
|
|
199
|
+
let lastAcceptedAt = 0
|
|
200
|
+
|
|
175
201
|
while (Date.now() < deadline) {
|
|
176
202
|
const pane = await tmuxCapturePane(sessionName)
|
|
177
203
|
if (pane.includes('Listening for channel messages from:')) {
|
|
178
204
|
return false
|
|
179
205
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
206
|
+
|
|
207
|
+
const promptKind = detectClaudeStartupPrompt(pane)
|
|
208
|
+
if (promptKind) {
|
|
209
|
+
const now = Date.now()
|
|
210
|
+
if (promptKind !== lastAcceptedPrompt || now - lastAcceptedAt >= 1500) {
|
|
211
|
+
lastAcceptedPrompt = promptKind
|
|
212
|
+
lastAcceptedAt = now
|
|
213
|
+
await tmuxSendEnter(sessionName)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await sleep(DEFAULT_POLL_INTERVAL_MS)
|
|
217
|
+
continue
|
|
183
218
|
}
|
|
219
|
+
|
|
220
|
+
lastAcceptedPrompt = null
|
|
221
|
+
lastAcceptedAt = 0
|
|
222
|
+
|
|
184
223
|
await sleep(DEFAULT_POLL_INTERVAL_MS)
|
|
185
224
|
}
|
|
186
225
|
return false
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
ensureRuntimeDirs,
|
|
16
16
|
makeSuffix,
|
|
17
17
|
makeToken,
|
|
18
|
-
|
|
18
|
+
maybeAcceptClaudeStartupPrompts,
|
|
19
19
|
parseArgs,
|
|
20
20
|
readJsonIfExists,
|
|
21
21
|
resolveUtilsDir,
|
|
@@ -147,7 +147,7 @@ try {
|
|
|
147
147
|
process.exit(1)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
await
|
|
150
|
+
await maybeAcceptClaudeStartupPrompts(tmuxSession, launchTimeoutMs)
|
|
151
151
|
|
|
152
152
|
const [{ event }, channelState] = await Promise.all([
|
|
153
153
|
waitForHookEvent(paths, 0, new Set(['SessionStart']), launchTimeoutMs),
|
|
@@ -9,6 +9,7 @@ const target = path.resolve(process.cwd(), targetInput)
|
|
|
9
9
|
const beadsCommand = process.env.BR_COMMAND || 'br'
|
|
10
10
|
|
|
11
11
|
const ROOT_TITLE = 'SlopMachine Workflow'
|
|
12
|
+
const PHASE_KEYS = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10']
|
|
12
13
|
const PHASE_TITLES = [
|
|
13
14
|
'P1 Clarification',
|
|
14
15
|
'P2 Planning',
|
|
@@ -21,6 +22,7 @@ const PHASE_TITLES = [
|
|
|
21
22
|
'P9 Submission Packaging',
|
|
22
23
|
'P10 Retrospective',
|
|
23
24
|
]
|
|
25
|
+
const requestedStartPhase = process.env.SLOPMACHINE_REQUESTED_START_PHASE || null
|
|
24
26
|
|
|
25
27
|
function log(message) {
|
|
26
28
|
console.log(`[workflow-init] ${message}`)
|
|
@@ -115,10 +117,17 @@ async function ensureLifecycleSeeded() {
|
|
|
115
117
|
|
|
116
118
|
log('Seeding workflow root and P1-P10 phases')
|
|
117
119
|
const rootId = await createIssue(ROOT_TITLE)
|
|
120
|
+
const requestedStartIndex = requestedStartPhase ? PHASE_KEYS.indexOf(requestedStartPhase) : -1
|
|
121
|
+
|
|
122
|
+
if (requestedStartPhase && requestedStartIndex === -1) {
|
|
123
|
+
die(`Unsupported requested start phase '${requestedStartPhase}'. Use one of: ${PHASE_KEYS.join(', ')}`)
|
|
124
|
+
}
|
|
118
125
|
|
|
119
126
|
let previousPhaseId = null
|
|
127
|
+
const phaseIds = []
|
|
120
128
|
for (const title of PHASE_TITLES) {
|
|
121
129
|
const phaseId = await createIssue(title)
|
|
130
|
+
phaseIds.push(phaseId)
|
|
122
131
|
|
|
123
132
|
const parentResult = await runBeads(['update', phaseId, '--parent', rootId], { env: { ...process.env, CI: '1' } })
|
|
124
133
|
if (parentResult.code !== 0) {
|
|
@@ -136,6 +145,17 @@ async function ensureLifecycleSeeded() {
|
|
|
136
145
|
|
|
137
146
|
previousPhaseId = phaseId
|
|
138
147
|
}
|
|
148
|
+
|
|
149
|
+
if (requestedStartIndex > 0) {
|
|
150
|
+
log(`Closing phases before requested start phase ${requestedStartPhase}`)
|
|
151
|
+
for (let index = 0; index < requestedStartIndex; index += 1) {
|
|
152
|
+
const closeResult = await runBeads(['close', phaseIds[index]], { env: { ...process.env, CI: '1' } })
|
|
153
|
+
if (closeResult.code !== 0) {
|
|
154
|
+
console.error(`${closeResult.stdout}${closeResult.stderr}`.trim())
|
|
155
|
+
die(`Failed to close seeded phase '${PHASE_TITLES[index]}' before '${requestedStartPhase}'.`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
async function main() {
|
package/package.json
CHANGED
package/src/init.js
CHANGED