theslopmachine 0.7.3 → 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.
@@ -43,10 +43,8 @@ The installed runtime copies under `~/slopmachine/` are the ordinary evaluation
43
43
  - number every fresh evaluation audit sequentially across the whole run for routing and metadata purposes
44
44
  - persist `../.tmp/audit_report-<N>.md` only for `partial pass` audits that actually open bugfix sessions
45
45
  - if a fresh audit is `fail` or `pass`, extract what you need from the generated working report, record the verdict and routing in metadata, and then discard the report file instead of leaving it in `../.tmp/`
46
- - for a `partial pass` audit that opens a bugfix session, store each scoped fix-check under that audit number:
47
- - `../.tmp/audit_report-<N>-fix_check-1.md`
48
- - `../.tmp/audit_report-<N>-fix_check-2.md`
49
- - and so on
46
+ - for a `partial pass` audit that opens a bugfix session, keep one replace-in-place fix-check report under that audit number:
47
+ - `../.tmp/audit_report-<N>-fix_check.md`
50
48
 
51
49
  ## Evaluator-session model
52
50
 
@@ -72,18 +70,14 @@ The installed runtime copies under `~/slopmachine/` are the ordinary evaluation
72
70
 
73
71
  For each fresh audit:
74
72
 
75
- - compose the chosen evaluation prompt yourself; do not tell the evaluator to read prompt files on its own
76
- - use the original project prompt from metadata
73
+ - do not prefix, suffix, summarize, or otherwise rewrite the chosen evaluation prompt yourself
77
74
  - read the chosen evaluation prompt file contents yourself before launching evaluation
78
- - compose one large final prompt block
79
- - prefix the request with a clear instruction that the reviewer must work in the current project directory and evaluate the delivered project
80
- - make sure `README.md` and the code inside the current project directory are sufficient for evaluation; do not assume the evaluator will rely on parent-root docs or sibling workflow artifacts
81
- - inject the full original project prompt into the `{prompt}` placeholder for the chosen evaluation prompt content, but otherwise do not rewrite or replace the template body
82
- - send that fully composed text block directly to one fresh `General` evaluator session
83
- - require that session to produce a detailed file-backed audit report plus an issue summary
75
+ - inject only the full original project prompt into the `{prompt}` placeholder and leave the rest of the template body unchanged
76
+ - send that resulting evaluation prompt text verbatim to one fresh `General` evaluator session with zero additions or reductions beyond the `{prompt}` substitution
77
+ - let the prompt itself define the evaluator output contract; do not append extra response requirements outside the prompt body
84
78
  - assign the next audit number
85
79
  - if and only if the verdict is `partial pass`, keep the normalized report path as `../.tmp/audit_report-<N>.md`
86
- - if the verdict is `fail` or `pass`, discard the generated report file after extracting the issue summary or verdict you need
80
+ - if the verdict is `fail` or `pass`, discard the generated report file after extracting the verdict or issue list you need from the evaluator result and/or report contents
87
81
  - record the evaluator session id, prompt kind, audit number, verdict, kept-or-discarded report status, and routing decision in metadata
88
82
 
89
83
  ## Fresh-audit branching rule
@@ -123,17 +117,16 @@ Inside a `partial pass` audit's bugfix loop:
123
117
  - after the developer claims the fixes are done, run a rough targeted owner-side verification pass on the affected behavior before asking for evaluator confirmation
124
118
  - then return to the same evaluator session and send only the exact issue list or current unresolved subset for scoped fix confirmation
125
119
  - require a file-backed fix-check report for that scoped verification pass
126
- - store each fix-check report as `../.tmp/audit_report-<N>-fix_check-<M>.md`
127
- - if unresolved issues remain, take only that unresolved subset back to the same bugfix session and repeat the same-session fix-check loop
120
+ - store the scoped fix-check report as `../.tmp/audit_report-<N>-fix_check.md`
121
+ - if unresolved issues remain, take only that unresolved subset back to the same bugfix session and repeat the same-session fix-check loop, replacing the same fix-check report each time so it always covers the whole audit issue list in its latest state
128
122
  - once all issues from `audit_report-<N>.md` are resolved, mark that bugfix session completed in metadata
129
123
 
130
124
  ## Post-bugfix coverage and README audit
131
125
 
132
126
  - after 2 bugfix sessions have been completed, do not leave `P7` yet; this audit is the last subphase inside `P7`
133
- - read `~/slopmachine/test-coverage-prompt.md` yourself before launching the audit
134
127
  - launch a fresh `General` evaluator session for this audit
135
128
  - prepare the audit workspace with `node ~/slopmachine/utils/prepare_strict_audit_workspace.mjs --workspace-root .. --name test-coverage-readme-audit` and use the returned `run_dir` as the evaluator working directory so `repo/README.md` and `../.tmp/` both resolve correctly
136
- - compose the request yourself and make clear that the reviewer is working in the current project directory and must write the report to `../.tmp/test_coverage_and_readme_audit_report.md`
129
+ - send `~/slopmachine/test-coverage-prompt.md` verbatim with zero additions or reductions; do not prepend cwd notes, workflow notes, or custom audit instructions because that prompt already defines its own report path and audit workspace assumptions
137
130
  - before each rerun, remove or replace the previous `../.tmp/test_coverage_and_readme_audit_report.md`; do not keep numbered variants for this report
138
131
  - if the report finds any issue, treat that as blocking `P7` completion
139
132
  - route those issues to the currently active recoverable developer session; prefer the most recently used developer session, which will usually be `bugfix-2`
@@ -155,10 +148,10 @@ Inside a `partial pass` audit's bugfix loop:
155
148
  - `P7` is complete only after 2 bugfix sessions have been completed and the post-bugfix coverage/README audit has run as the last subphase of `P7`
156
149
  - the second bugfix session must be completed by resolving its scoped issue list through the same-audit fix-check loop
157
150
  - fresh `pass` audits before that point are discarded clean audits and do not replace the 2-bugfix-session requirement
158
- - after the second bugfix session completes, run the coverage/README audit; if it becomes clean within 3 remediation attempts, move to `P8 Final Human Decision` with a clean report, otherwise move to `P8 Final Human Decision` with the latest final report after the third attempt
151
+ - after the second bugfix session completes, run the coverage/README audit; if it becomes clean within 3 remediation attempts, move to `P8 Final Readiness Decision` with a clean report, otherwise move to `P8 Final Readiness Decision` with the latest final report after the third attempt
159
152
 
160
153
  ## Boundaries
161
154
 
162
- - this phase is owner-side evaluation orchestration, not the final human decision gate
155
+ - this phase is owner-side evaluation orchestration, not a user approval gate
163
156
  - keep audit numbering deterministic and monotonic across the whole run
164
157
  - do not reopen the old counted-cycle report-root model
@@ -17,7 +17,7 @@ Use this skill only during `P9 Submission Packaging`.
17
17
  - packaging is incomplete until every required final artifact path has been verified to exist
18
18
  - do not stop packaging for approval, status confirmation, or handoff once this phase has begun; continue until the package is complete
19
19
  - when a task or platform question id exists such as `TASK-123`, use that exact id as the final deliverable/archive name without adding an extra `ID-` prefix
20
- - normalize project-type metadata and packaging labels to the expected engineering categories such as `full_stack` or `fullstack`, `pure_backend`, `pure_frontend`, `cross_platform_app`, or `mobile_app`
20
+ - normalize `metadata.json.project_type` to the exact allowed values `fullstack`, `backend`, `android`, `ios`, `desktop`, or `web`, and keep any packaging labels aligned with that same delivered project reality
21
21
 
22
22
  ## Required final structure
23
23
 
@@ -32,16 +32,16 @@ The final delivery layout in the parent project root must be:
32
32
  - `sessions/`
33
33
  - `sessions/<label>.json` for every tracked developer session, including `develop-N.json` and `bugfix-N.json` when present
34
34
  - for Claude-backed developer sessions:
35
- - `claude-sessions.zip` in the parent root containing the whole Claude project session folder once
35
+ - `claude-sessions.zip` in the parent root containing only the tracked relevant Claude session artifacts once
36
36
  - no `sessions/` directory is required when all tracked developer sessions are Claude-backed
37
37
  - `metadata.json`
38
38
  - `.tmp/`
39
39
  - `audit_report-<N>.md` only for bugfix-triggering `partial pass` audits
40
- - `audit_report-<N>-fix_check-<M>.md` when present
40
+ - `audit_report-<N>-fix_check.md` when present
41
41
  - `test_coverage_and_readme_audit_report.md`
42
42
  - `repo/`
43
43
 
44
- In the clean two-bugfix path, `.tmp/` should end with at least 5 required markdown reports once the final coverage/README audit is included: 2 kept partial-pass audit reports, at least 2 corresponding fix-check reports, and the final coverage/README audit report. Extra fix checks may legitimately increase that count.
44
+ In the clean two-bugfix path, `.tmp/` should end with at least 5 required markdown reports once the final coverage/README audit is included: 2 kept partial-pass audit reports, 2 corresponding single fix-check reports, and the final coverage/README audit report.
45
45
 
46
46
  Inside the delivered `repo/`, the repository must remain self-sufficient:
47
47
 
@@ -56,7 +56,7 @@ No screenshots are required as packaging artifacts.
56
56
  ## Required packaging actions
57
57
 
58
58
  - verify the parent-root package structure matches the blueprint exactly
59
- - make sure parent-root `../metadata.json` is complete and reflects the delivered project truthfully
59
+ - make sure parent-root `../metadata.json` is complete, reflects the delivered project truthfully, and contains only these keys: `prompt`, `project_type`, `frontend_language`, `backend_language`, `database`, `frontend_framework`, `backend_framework`
60
60
  - verify parent-root `../docs/design.md` exists and reflects the final delivered design when applicable
61
61
  - verify parent-root `../docs/api-spec.md` exists and reflects the final delivered interfaces when applicable
62
62
  - verify parent-root `../docs/test-coverage.md` exists and reflects the final delivered verification coverage
@@ -74,7 +74,7 @@ No screenshots are required as packaging artifacts.
74
74
  - verify parent-root `../.tmp/` exists and contains the required audit and fix-check reports
75
75
  - verify parent-root `../.tmp/test_coverage_and_readme_audit_report.md` exists from the final post-bugfix coverage/README audit
76
76
  - export all tracked developer sessions before closing packaging
77
- - when packaging succeeds, update workflow metadata to mark `packaging_completed` as true
77
+ - when packaging succeeds and any tracked live Claude tmux lanes have been stopped, update workflow metadata to mark `packaging_completed` as true
78
78
 
79
79
  ## Session export sequence
80
80
 
@@ -85,18 +85,20 @@ Export tracked developer sessions from metadata using the tracked lane labels, f
85
85
 
86
86
  For session export:
87
87
 
88
- 1. if at least one tracked developer session backend is `claude` or `claude-live`, run `node ~/slopmachine/utils/package_claude_session.mjs --cwd "$PWD" --session-id <any-claude-session-id> --label claude-sessions --output ../claude-sessions.zip`
88
+ 1. if at least one tracked developer session backend is `claude` or `claude-live`, gather the tracked Claude `session_id` values from metadata and run `node ~/slopmachine/utils/package_claude_session.mjs --cwd "$PWD" --session-ids <tracked-claude-session-id-1,tracked-claude-session-id-2,...> --label claude-sessions --output ../claude-sessions.zip`
89
89
  2. if `<backend>` is neither `claude` nor `claude-live`, run `opencode export <session-id> > ../session-export-<label>.raw`
90
90
  3. if `<backend>` is neither `claude` nor `claude-live`, run `python3 ~/slopmachine/utils/strip_session_parent.py ../session-export-<label>.raw --output ../sessions/<label>.json`
91
91
 
92
92
  Where `<backend>` comes from the tracked developer session record in metadata.
93
93
  Use `opencode` when no explicit backend field exists or when the backend is not Claude-backed.
94
- For Claude-backed sessions, the package helper resolves the Claude project folder under `~/.claude/projects/` from a tracked `session_id` plus the current project `cwd`, normalizes the copied JSONL session files by flattening channel-originated user turns, and packages that folder once.
94
+ For Claude-backed sessions, the package helper resolves the tracked Claude session artifacts under `~/.claude/projects/` from the tracked `session_id` values plus the current project `cwd`, copies only those tracked `session_id.jsonl` files and matching `session_id/` companion directories when present, normalizes the copied JSONL session files by flattening channel-originated user turns, and packages only that tracked set once. Do not sweep unrelated random Claude sessions into the archive just because they share the same Claude project directory.
95
95
 
96
96
  After those steps:
97
97
 
98
98
  - verify every non-Claude developer session has been exported into `../sessions/<label>.json`
99
- - verify Claude-backed sessions have been packaged once into `../claude-sessions.zip`
99
+ - verify Claude-backed sessions have been packaged once into `../claude-sessions.zip` using the tracked relevant Claude session ids rather than the whole local Claude project directory
100
+ - after Claude-backed session packaging succeeds, stop each tracked live Claude runtime with `node ~/slopmachine/utils/claude_live_stop.mjs --runtime-dir <runtime_dir>` before marking packaging complete
101
+ - verify each stopped Claude runtime no longer has a live tmux session before closing packaging
100
102
  - treat only the raw `../session-export-<label>.raw` files as temporary packaging intermediates
101
103
  - remove the raw `../session-export-<label>.raw` files before closing packaging
102
104
  - if the required utilities, metadata session ids, or output files are missing, packaging is not ready to continue
@@ -127,13 +129,14 @@ After those steps:
127
129
  - confirm the cleanup helper has been run and that no known recursive cleanup targets remain in the delivered repo tree
128
130
  - confirm no environment-dependent dependency directories, editor-state folders, runtime caches, or workflow utility scripts are packaged into the delivered product
129
131
  - confirm parent-root `../.tmp/` exists and contains the required kept `audit_report-<N>.md` files for partial-pass audits only
130
- - confirm every bugfix-triggering audit number has its matching `audit_report-<N>-fix_check-<M>.md` files when fix checks were required
132
+ - confirm every bugfix-triggering audit number has its matching `audit_report-<N>-fix_check.md` file when fix checks were required
131
133
  - confirm parent-root `../.tmp/test_coverage_and_readme_audit_report.md` exists and is the final replaced copy rather than a numbered variant
132
134
  - confirm parent-root `../docs/test-coverage.md` explains the tested flows, mapped tests, and coverage boundaries
133
135
  - confirm every non-Claude developer session exists under parent-root `../sessions/` using the tracked `<label>.json` names
134
136
  - confirm Claude-backed developer sessions exist in the parent root as `claude-sessions.zip`
137
+ - confirm no tracked Claude live tmux session is still running after packaging finishes
135
138
  - confirm parent-root `../docs/` remains consistent as an external reference set when workflow policy still requires it, but the delivered repo does not depend on it
136
- - confirm parent-root metadata fields are populated correctly
139
+ - confirm parent-root metadata fields are populated correctly and no extra keys exist in `../metadata.json`
137
140
  - confirm workflow metadata marks `packaging_completed` as true
138
141
  - confirm no `submission/` directory or other obsolete packaging artifact structure remains
139
142
 
@@ -12,8 +12,8 @@ Use this skill after development begins whenever you are reviewing work, decidin
12
12
  - load this skill before review, acceptance, rejection, runtime gate interpretation, hardening readiness decisions, or broad-gate decisions
13
13
  - treat it as owner-side review and gate guidance, not developer-visible text
14
14
  - use this skill as the source of truth for owner-side verification, review pressure, and gate interpretation
15
- - outside the final human decision, do not pause execution for human approval while using this skill; continue reviewing, rejecting, fixing, and rerunning until the work qualifies
16
- - outside the clarification-complete gate and `P8 Final Human Decision`, do not pause execution just to summarize progress or ask the user whether to continue; keep driving the workflow until the current gate or phase objective is actually satisfied
15
+ - do not pause execution for human approval while using this skill; continue reviewing, rejecting, fixing, and rerunning until the work qualifies
16
+ - clarification completion and `P8 Final Readiness Decision` are internal workflow transitions, not user-stop gates; do not pause execution just to summarize progress or ask the user whether to continue
17
17
 
18
18
  ## Documentation and repo hygiene
19
19
 
@@ -163,7 +163,7 @@ Any earlier extra Docker run needs a concrete blocker-based justification.
163
163
 
164
164
  Use evidence such as internal metadata files, structured Beads comments, verification command results, and file/project-state checks.
165
165
 
166
- - clarification requires the `clarification-gate` conditions plus explicit approval record
166
+ - clarification requires the `clarification-gate` conditions plus an internally accepted clarification record that is ready to roll directly into planning
167
167
  - planning requires the `developer-session-lifecycle` and planning-gate conditions plus a fresh planning-oriented start and the required documentation and repo hygiene state when relevant
168
168
  - planning exit also requires explicit owner review that the accepted planning artifacts cover the section-addressable contract deeply enough for later implementation: in-scope and out-of-scope, actors and success paths, modules, business rules, state machines, permissions, validation, verification strategy, checkpoints, and definition of done when applicable
169
169
  - planning exit does not pass if those sections exist only nominally or remain too vague to drive implementation without broad reinvention
@@ -214,7 +214,7 @@ Use evidence such as internal metadata files, structured Beads comments, verific
214
214
  - before `P7`, for `fullstack` and `web` projects, require an explicit frontend unit-test verdict backed by direct file-level evidence; if frontend unit tests are missing or insufficient, treat that as a critical gap
215
215
  - before `P7`, require repo-local build/preview/config traceability plus disclosure in `README.md` of feature flags, debug/demo surfaces, and mock defaults when those surfaces exist
216
216
  - before `P7`, require logging and validation contracts to be statically traceable enough that the owner can review them from the repo plus external references when needed
217
- - final evaluation readiness requires the audit-numbered `P7` model under `../.tmp/`; only `partial pass` fresh evaluations leave persisted `audit_report-<N>.md` files, `fail` audits route back to the latest `develop-N` session and discard their working report after triage, `pass` audits discard their working report and rerun fresh evaluation, `partial pass` audits open scoped `bugfix-N` sessions whose fix checks are stored as `audit_report-<N>-fix_check-<M>.md`, and the last subphase of `P7` runs `test_coverage_and_readme_audit_report.md` with up to 3 remediation attempts before carrying the latest report forward
217
+ - final evaluation readiness requires the audit-numbered `P7` model under `../.tmp/`; only `partial pass` fresh evaluations leave persisted `audit_report-<N>.md` files, `fail` audits route back to the latest `develop-N` session and discard their working report after triage, `pass` audits discard their working report and rerun fresh evaluation, `partial pass` audits open scoped `bugfix-N` sessions whose fix checks are stored in a single replace-in-place `audit_report-<N>-fix_check.md`, and the last subphase of `P7` runs `test_coverage_and_readme_audit_report.md` with up to 3 remediation attempts before carrying the latest report forward
218
218
  - before leaving `P7`, if `README.md` documents `docker compose up --build` and/or `./run_tests.sh` as part of the delivered external contract, run those exact commands on the final state and require them to pass before moving to `P8`
219
219
  - if the `P7` issue-fix loop materially reopens the integrated verification boundary, route it back through integrated verification before continuing with follow-up fix verification
220
220
  - before leaving `P7`, require the parent-root `../.tmp/test_coverage_and_readme_audit_report.md` to exist from the last `P7` subphase; if it finds issues, route the fixes to the currently active recoverable developer session, replace the report, and rerun the audit, but stop after 3 remediation attempts and keep the latest report as the final carried-forward evidence
@@ -15,6 +15,7 @@ This file is the repo-local engineering rulebook for `slopmachine` projects.
15
15
  - Read the code before making assumptions.
16
16
  - Work in meaningful vertical slices.
17
17
  - Do not call work complete while it is still shaky.
18
+ - Once given a bounded objective, keep going autonomously until it is complete or genuinely blocked; do not stop for reassurance or permission when a prompt-faithful default lets you proceed.
18
19
  - Reuse and extend shared cross-cutting patterns instead of inventing incompatible local ones.
19
20
  - Before coding, identify the actors or personas touched by the change and the concrete path to success for each one.
20
21
  - Make important business rules explicit before coding: defaults, limits, allowed transitions, uniqueness, conflicts, reversals, retries, and ownership rules when they matter.
@@ -28,7 +29,7 @@ This file is the repo-local engineering rulebook for `slopmachine` projects.
28
29
 
29
30
  - Preserve the full prompt intent, including implied business constraints.
30
31
  - Do not weaken required actor models, operator flows, security controls, or lifecycle behavior for implementation convenience.
31
- - If a requirement is ambiguous, choose the safest prompt-faithful behavior or surface the ambiguity instead of guessing lazily.
32
+ - If a requirement is ambiguous, choose the safest prompt-faithful behavior and keep moving when a defensible default exists; surface the ambiguity only when it is genuinely blocking or materially changes the product contract.
32
33
  - If the feature depends on business rules, make those rules traceable in code, tests, and `README.md` rather than leaving them implicit.
33
34
 
34
35
  ## Architecture Rules
@@ -15,6 +15,7 @@ This file is the repo-local engineering rulebook for `slopmachine-claude` projec
15
15
  - Read the code before making assumptions.
16
16
  - Work in meaningful vertical slices.
17
17
  - Do not call work complete while it is still shaky.
18
+ - Once given a bounded objective, keep going autonomously until it is complete or genuinely blocked; do not stop for reassurance or permission when a prompt-faithful default lets you proceed.
18
19
  - Reuse and extend shared cross-cutting patterns instead of inventing incompatible local ones.
19
20
  - Before coding, identify the actors or personas touched by the change and the concrete path to success for each one.
20
21
  - Make important business rules explicit before coding: defaults, limits, allowed transitions, uniqueness, conflicts, reversals, retries, and ownership rules when they matter.
@@ -28,7 +29,7 @@ This file is the repo-local engineering rulebook for `slopmachine-claude` projec
28
29
 
29
30
  - Preserve the full prompt intent, including implied business constraints.
30
31
  - Do not weaken required actor models, operator flows, security controls, or lifecycle behavior for implementation convenience.
31
- - If a requirement is ambiguous, choose the safest prompt-faithful behavior or surface the ambiguity instead of guessing lazily.
32
+ - If a requirement is ambiguous, choose the safest prompt-faithful behavior and keep moving when a defensible default exists; surface the ambiguity only when it is genuinely blocking or materially changes the product contract.
32
33
  - If the feature depends on business rules, make those rules traceable in code, tests, and `README.md` rather than leaving them implicit.
33
34
 
34
35
  ## Architecture Rules
@@ -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
- export async function maybeAcceptDevelopmentChannelsPrompt(sessionName, timeoutMs = 30000) {
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
- if (pane.includes('I am using this for local development')) {
181
- await tmuxSendEnter(sessionName)
182
- return true
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
- maybeAcceptDevelopmentChannelsPrompt,
18
+ maybeAcceptClaudeStartupPrompts,
19
19
  parseArgs,
20
20
  readJsonIfExists,
21
21
  resolveUtilsDir,
@@ -36,8 +36,8 @@ const cwd = argv.cwd ? path.resolve(argv.cwd) : null
36
36
  const lane = argv.lane
37
37
  const agentName = argv.agent || 'developer'
38
38
  const claudeCommand = argv['claude-command'] || 'claude'
39
- const laneModel = argv.model || 'sonnet'
40
- const laneEffort = argv.effort || null
39
+ const laneModel = argv.model || 'opus'
40
+ const laneEffort = argv.effort || 'medium'
41
41
  const subagentModel = argv['subagent-model'] || 'sonnet'
42
42
  const launchTimeoutMs = Number.parseInt(argv['timeout-ms'] || String(DEFAULT_LAUNCH_TIMEOUT_MS), 10)
43
43
  const replace = argv.replace === '1'
@@ -147,7 +147,7 @@ try {
147
147
  process.exit(1)
148
148
  }
149
149
 
150
- await maybeAcceptDevelopmentChannelsPrompt(tmuxSession, Math.min(launchTimeoutMs, 30000))
150
+ await maybeAcceptClaudeStartupPrompts(tmuxSession, launchTimeoutMs)
151
151
 
152
152
  const [{ event }, channelState] = await Promise.all([
153
153
  waitForHookEvent(paths, 0, new Set(['SessionStart']), launchTimeoutMs),
@@ -7,7 +7,7 @@ import json
7
7
  import re
8
8
  import sys
9
9
  from pathlib import Path
10
- from typing import Any
10
+ from typing import Any, Iterable
11
11
 
12
12
 
13
13
  CHANNEL_MESSAGE_RE = re.compile(
@@ -15,6 +15,10 @@ CHANNEL_MESSAGE_RE = re.compile(
15
15
  re.DOTALL,
16
16
  )
17
17
 
18
+ CHANNEL_INSTRUCTION_RE = re.compile(r"slopmachine-[^\s\"]+", re.IGNORECASE)
19
+ CHANNEL_TAG_RE = re.compile(r"</?channel\b[^>]*>", re.IGNORECASE)
20
+ WEBHOOK_TERM_RE = re.compile(r"\b(?:webhook|UserPromptSubmit|StopFailure)\b", re.IGNORECASE)
21
+
18
22
 
19
23
  def parse_args() -> argparse.Namespace:
20
24
  parser = argparse.ArgumentParser(
@@ -37,6 +41,11 @@ def parse_args() -> argparse.Namespace:
37
41
  action="store_true",
38
42
  help="Keep channel origin metadata fields instead of stripping them",
39
43
  )
44
+ parser.add_argument(
45
+ "--recursive",
46
+ action="store_true",
47
+ help="If input is a directory, recursively normalize all .jsonl files into the output directory.",
48
+ )
40
49
  return parser.parse_args()
41
50
 
42
51
 
@@ -49,55 +58,137 @@ def maybe_flatten_channel_content(content: str) -> tuple[str, bool]:
49
58
  return body, True
50
59
 
51
60
 
61
+ def scrub_string(value: str) -> str:
62
+ flattened, _ = maybe_flatten_channel_content(value.strip())
63
+ normalized = flattened
64
+ normalized = CHANNEL_TAG_RE.sub("", normalized)
65
+ normalized = CHANNEL_INSTRUCTION_RE.sub("normalized-channel", normalized)
66
+ normalized = WEBHOOK_TERM_RE.sub("", normalized)
67
+ normalized = normalized.replace(
68
+ 'Messages arrive as ordinary inbound work requests. ...',
69
+ 'Messages arrive as ordinary inbound work requests.',
70
+ )
71
+ return normalized
72
+
73
+
74
+ def scrub_value(value: Any) -> Any:
75
+ if isinstance(value, str):
76
+ return scrub_string(value)
77
+ if isinstance(value, list):
78
+ return [scrub_value(item) for item in value]
79
+ if isinstance(value, dict):
80
+ return {key: scrub_value(item) for key, item in value.items()}
81
+ return value
82
+
83
+
84
+ def strip_channel_instruction_attachment(attachment: dict[str, Any]) -> tuple[dict[str, Any] | None, bool]:
85
+ if attachment.get("type") != "mcp_instructions_delta":
86
+ return attachment, False
87
+
88
+ changed = False
89
+ normalized = dict(attachment)
90
+
91
+ added_names = normalized.get("addedNames")
92
+ if isinstance(added_names, list):
93
+ filtered_names = [name for name in added_names if not (isinstance(name, str) and name.startswith("slopmachine-"))]
94
+ if filtered_names != added_names:
95
+ normalized["addedNames"] = filtered_names
96
+ changed = True
97
+
98
+ added_blocks = normalized.get("addedBlocks")
99
+ if isinstance(added_blocks, list):
100
+ filtered_blocks = []
101
+ for block in added_blocks:
102
+ if not isinstance(block, str):
103
+ filtered_blocks.append(block)
104
+ continue
105
+ if "<channel source=" in block or "Messages arrive as <channel source=" in block or CHANNEL_INSTRUCTION_RE.search(block):
106
+ changed = True
107
+ continue
108
+ filtered_blocks.append(block)
109
+ if filtered_blocks != added_blocks:
110
+ normalized["addedBlocks"] = filtered_blocks
111
+
112
+ removed_names = normalized.get("removedNames")
113
+ if isinstance(removed_names, list) and not normalized.get("addedNames") and not normalized.get("addedBlocks") and not removed_names:
114
+ return None, True
115
+
116
+ if not normalized.get("addedNames") and not normalized.get("addedBlocks") and not normalized.get("removedNames"):
117
+ return None, True
118
+
119
+ return scrub_value(normalized), changed
120
+
121
+
122
+ def strip_transport_metadata(record: dict[str, Any], *, keep_channel_origin: bool) -> dict[str, Any]:
123
+ normalized = dict(record)
124
+ normalized.pop("isMeta", None)
125
+ if not keep_channel_origin:
126
+ normalized.pop("origin", None)
127
+ return scrub_value(normalized)
128
+
129
+
52
130
  def normalize_record(record: dict[str, Any], *, keep_channel_origin: bool) -> dict[str, Any] | None:
53
131
  if record.get("type") == "queue-operation":
54
132
  return None
55
133
 
134
+ if record.get("type") == "attachment":
135
+ attachment = record.get("attachment")
136
+ if isinstance(attachment, dict):
137
+ cleaned_attachment, _ = strip_channel_instruction_attachment(attachment)
138
+ if cleaned_attachment is None:
139
+ return None
140
+ normalized = strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
141
+ normalized["attachment"] = cleaned_attachment
142
+ return normalized
143
+ return strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
144
+
56
145
  if record.get("type") != "user":
57
- return record
146
+ return strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
58
147
 
59
148
  message = record.get("message")
60
149
  if not isinstance(message, dict):
61
- return record
150
+ return strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
62
151
 
63
152
  if message.get("role") != "user":
64
- return record
153
+ return strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
65
154
 
66
155
  content = message.get("content")
67
156
  if not isinstance(content, str):
68
- return record
157
+ return strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
69
158
 
70
159
  flattened, changed = maybe_flatten_channel_content(content)
160
+ normalized = strip_transport_metadata(record, keep_channel_origin=keep_channel_origin)
71
161
  if not changed:
72
- return record
162
+ return normalized
73
163
 
74
- normalized = dict(record)
75
164
  normalized_message = dict(message)
76
165
  normalized_message["content"] = flattened
77
166
  normalized["message"] = normalized_message
78
- normalized.pop("isMeta", None)
79
-
80
- if not keep_channel_origin:
81
- normalized.pop("origin", None)
82
167
 
83
168
  return normalized
84
169
 
85
170
 
86
- def main() -> int:
87
- args = parse_args()
88
- input_path = Path(args.input)
89
- output_path = Path(args.output)
171
+ def iter_input_files(input_path: Path, recursive: bool) -> Iterable[Path]:
172
+ if input_path.is_file():
173
+ yield input_path
174
+ return
90
175
 
91
- if not input_path.exists():
92
- print(f"Input file not found: {input_path}", file=sys.stderr)
93
- return 1
176
+ if not input_path.is_dir():
177
+ raise ValueError(f"Input path must be a file or directory: {input_path}")
94
178
 
95
- output_path.parent.mkdir(parents=True, exist_ok=True)
179
+ pattern = "**/*.jsonl" if recursive else "*.jsonl"
180
+ for candidate in sorted(input_path.glob(pattern)):
181
+ if candidate.is_file():
182
+ yield candidate
96
183
 
184
+
185
+ def normalize_file(input_path: Path, output_path: Path, *, keep_channel_origin: bool, keep_queue_operations: bool) -> dict[str, Any]:
97
186
  total = 0
98
187
  queue_dropped = 0
99
188
  channel_flattened = 0
100
189
 
190
+ output_path.parent.mkdir(parents=True, exist_ok=True)
191
+
101
192
  with input_path.open("r", encoding="utf-8") as src, output_path.open("w", encoding="utf-8") as dst:
102
193
  for line_no, line in enumerate(src, start=1):
103
194
  stripped = line.strip()
@@ -108,17 +199,15 @@ def main() -> int:
108
199
  try:
109
200
  record = json.loads(stripped)
110
201
  except json.JSONDecodeError as exc:
111
- print(f"Invalid JSON at line {line_no}: {exc}", file=sys.stderr)
112
- return 1
202
+ raise ValueError(f"Invalid JSON at line {line_no} in {input_path}: {exc}") from exc
113
203
 
114
204
  if not isinstance(record, dict):
115
- print(f"Expected object at line {line_no}", file=sys.stderr)
116
- return 1
205
+ raise ValueError(f"Expected object at line {line_no} in {input_path}")
117
206
 
118
- normalized = normalize_record(record, keep_channel_origin=args.keep_channel_origin)
207
+ normalized = normalize_record(record, keep_channel_origin=keep_channel_origin)
119
208
 
120
209
  if normalized is None:
121
- if record.get("type") == "queue-operation" and not args.keep_queue_operations:
210
+ if record.get("type") == "queue-operation" and not keep_queue_operations:
122
211
  queue_dropped += 1
123
212
  continue
124
213
 
@@ -137,14 +226,60 @@ def main() -> int:
137
226
 
138
227
  dst.write(json.dumps(normalized, ensure_ascii=False) + "\n")
139
228
 
140
- summary = {
141
- "ok": True,
229
+ return {
142
230
  "input": str(input_path),
143
231
  "output": str(output_path),
144
232
  "records_seen": total,
145
233
  "queue_operations_dropped": queue_dropped,
146
234
  "channel_messages_flattened": channel_flattened,
147
235
  }
236
+
237
+
238
+ def main() -> int:
239
+ args = parse_args()
240
+ input_path = Path(args.input)
241
+ output_path = Path(args.output)
242
+
243
+ if not input_path.exists():
244
+ print(f"Input file not found: {input_path}", file=sys.stderr)
245
+ return 1
246
+
247
+ try:
248
+ files = list(iter_input_files(input_path, recursive=args.recursive))
249
+ except ValueError as exc:
250
+ print(str(exc), file=sys.stderr)
251
+ return 1
252
+
253
+ summaries = []
254
+ for source in files:
255
+ if input_path.is_dir():
256
+ rel = source.relative_to(input_path)
257
+ dest = output_path / rel
258
+ else:
259
+ dest = output_path
260
+ try:
261
+ summaries.append(
262
+ normalize_file(
263
+ source,
264
+ dest,
265
+ keep_channel_origin=args.keep_channel_origin,
266
+ keep_queue_operations=args.keep_queue_operations,
267
+ )
268
+ )
269
+ except ValueError as exc:
270
+ print(str(exc), file=sys.stderr)
271
+ return 1
272
+
273
+ summary = {
274
+ "ok": True,
275
+ "input": str(input_path),
276
+ "output": str(output_path),
277
+ "files_processed": len(summaries),
278
+ "records_seen": sum(item["records_seen"] for item in summaries),
279
+ "queue_operations_dropped": sum(item["queue_operations_dropped"] for item in summaries),
280
+ "channel_messages_flattened": sum(item["channel_messages_flattened"] for item in summaries),
281
+ "per_file": summaries,
282
+ }
148
283
  print(json.dumps(summary))
149
284
  return 0
150
285