theslopmachine 0.7.2 → 0.7.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/MANUAL.md +1 -1
- package/README.md +11 -1
- package/RELEASE.md +16 -0
- package/assets/agents/developer.md +2 -1
- package/assets/agents/slopmachine-claude.md +28 -20
- package/assets/agents/slopmachine.md +22 -18
- package/assets/claude/agents/developer.md +2 -1
- package/assets/skills/beads-operations/SKILL.md +1 -1
- package/assets/skills/clarification-gate/SKILL.md +6 -4
- package/assets/skills/claude-worker-management/SKILL.md +6 -6
- package/assets/skills/developer-session-lifecycle/SKILL.md +11 -9
- package/assets/skills/development-guidance/SKILL.md +1 -0
- package/assets/skills/evaluation-triage/SKILL.md +3 -2
- package/assets/skills/final-evaluation-orchestration/SKILL.md +12 -19
- package/assets/skills/hardening-gate/SKILL.md +1 -0
- package/assets/skills/planning-guidance/SKILL.md +1 -0
- package/assets/skills/scaffold-guidance/SKILL.md +1 -0
- package/assets/skills/submission-packaging/SKILL.md +14 -11
- package/assets/skills/verification-gates/SKILL.md +5 -4
- package/assets/slopmachine/scaffold-playbooks/docker-shared-contract.md +4 -0
- package/assets/slopmachine/templates/AGENTS.md +3 -1
- package/assets/slopmachine/templates/CLAUDE.md +3 -1
- package/assets/slopmachine/utils/__pycache__/normalize_claude_session.cpython-311.pyc +0 -0
- package/assets/slopmachine/utils/claude_live_launch.mjs +2 -2
- package/assets/slopmachine/utils/normalize_claude_session.py +162 -27
- package/assets/slopmachine/utils/package_claude_session.mjs +120 -23
- package/assets/slopmachine/utils/prepare_evaluation_prompt.mjs +41 -0
- package/assets/slopmachine/workflow-init.js +1 -1
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/constants.js +1 -0
- package/src/init.js +117 -28
- package/src/send-data.js +4 -4
|
@@ -60,6 +60,7 @@ Hardening should treat these as the main review buckets before final evaluation
|
|
|
60
60
|
- enforce env-file discipline during hardening
|
|
61
61
|
- run documentation verification against the real codebase and runtime behavior, not just document existence
|
|
62
62
|
- if `README.md` declares containerized runtime or broad test commands, verify that the final delivered output really supports those exact commands and that the docs do not overpromise beyond what the repo actually does
|
|
63
|
+
- verify that every dependency needed by the README-documented `docker compose up --build` and `./run_tests.sh` paths is declared in Dockerfiles or other repo-controlled container build definitions rather than relying on host-installed packages or runtimes
|
|
63
64
|
- audit README compliance against the strict post-bugfix README review shape:
|
|
64
65
|
- project type near the top
|
|
65
66
|
- startup instructions
|
|
@@ -163,6 +163,7 @@ Selected-stack defaults:
|
|
|
163
163
|
- `./run_tests.sh` must exist for every project as the platform-independent broad test wrapper
|
|
164
164
|
- `./run_tests.sh` must be able to run on a clean Linux VM that only has Docker and curl available by default
|
|
165
165
|
- do not require host package managers, host language runtimes, or host test tooling for the broad test path unless the stack absolutely forces it and the exception is explicitly justified
|
|
166
|
+
- define all runtime and broad-test dependencies in Dockerfiles, image build stages, or other repo-controlled container build definitions; do not rely on hidden host packages even when the developer machine happens to have them
|
|
166
167
|
- `./run_tests.sh` must prepare or install anything required inside its own controlled execution path when that setup is needed for a clean environment
|
|
167
168
|
- for web projects, `./run_tests.sh` must run the full test path through Docker rather than a purely local test invocation
|
|
168
169
|
- when host-level setup would otherwise be required, prefer a Dockerized `./run_tests.sh` path even outside traditional web stacks so the broad verification remains portable
|
|
@@ -90,6 +90,7 @@ At scaffold time, do not require:
|
|
|
90
90
|
- create `./run_tests.sh` during scaffold for every project
|
|
91
91
|
- create `./run_app.sh` during scaffold for non-web platforms when it helps expose the host-side or platform-specific local flow, but keep `docker compose up --build` and containerized `./run_tests.sh` as required baseline commands
|
|
92
92
|
- if the project has database dependencies, create `./init_db.sh` during scaffold as the only project-standard database initialization path
|
|
93
|
+
- define the runtime and broad-test dependency set in Dockerfiles or other repo-controlled container build definitions during scaffold; do not assume host package managers, host SDKs, or host language runtimes beyond Docker and documented baseline prerequisites
|
|
93
94
|
- make the scaffold handoff compact and checklist-driven rather than a long narrative dump
|
|
94
95
|
|
|
95
96
|
## Minimal real test floor
|
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
16
|
-
-
|
|
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
|
|
|
@@ -48,6 +48,7 @@ Use this skill after development begins whenever you are reviewing work, decidin
|
|
|
48
48
|
- for Android, mobile, desktop, and iOS-targeted projects, allow `./run_app.sh` as an additional platform helper but not as a replacement for the required Docker command
|
|
49
49
|
- require `./run_tests.sh` to be self-sufficient enough to run from a clean Linux VM that only has Docker and curl available by default
|
|
50
50
|
- do not accept a broad test path that depends on host package managers or preinstalled host language runtimes when Docker can provide the execution environment instead
|
|
51
|
+
- do not accept Docker runtime/test paths that rely on undeclared host packages, SDKs, compilers, CLIs, or language runtimes; all such dependencies must be defined in Dockerfiles or other repo-controlled container build definitions
|
|
51
52
|
- for web projects, require `./run_tests.sh` to be the Dockerized broad test path used only for the limited broad verification moments rather than as the ordinary development verification path
|
|
52
53
|
- when host-level setup would otherwise be required, prefer a Dockerized `./run_tests.sh` path even outside traditional web stacks so the broad verification remains portable
|
|
53
54
|
- for non-web projects, require `./run_tests.sh` to remain containerized and usable as the platform-equivalent broad test path used for final broad verification
|
|
@@ -162,7 +163,7 @@ Any earlier extra Docker run needs a concrete blocker-based justification.
|
|
|
162
163
|
|
|
163
164
|
Use evidence such as internal metadata files, structured Beads comments, verification command results, and file/project-state checks.
|
|
164
165
|
|
|
165
|
-
- clarification requires the `clarification-gate` conditions plus
|
|
166
|
+
- clarification requires the `clarification-gate` conditions plus an internally accepted clarification record that is ready to roll directly into planning
|
|
166
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
|
|
167
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
|
|
168
169
|
- planning exit does not pass if those sections exist only nominally or remain too vague to drive implementation without broad reinvention
|
|
@@ -213,7 +214,7 @@ Use evidence such as internal metadata files, structured Beads comments, verific
|
|
|
213
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
|
|
214
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
|
|
215
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
|
|
216
|
-
- 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
|
|
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
|
|
217
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`
|
|
218
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
|
|
219
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
|
|
@@ -49,6 +49,7 @@ That proof differs by family, but it must always be **meaningful**:
|
|
|
49
49
|
At minimum, `docker compose up --build` must prove all of the following:
|
|
50
50
|
|
|
51
51
|
- images build from source in the repo
|
|
52
|
+
- all system packages, language runtimes, CLIs, compilers, and SDKs required by the runtime path are declared in Dockerfiles or image build stages rather than assumed from the host
|
|
52
53
|
- declared runtime or support services start without hidden host setup
|
|
53
54
|
- readiness can be observed through healthchecks, predictable logs, or explicit successful artifact production
|
|
54
55
|
- the stack reaches a meaningful healthy state without manual container surgery
|
|
@@ -71,6 +72,9 @@ It must prove that the scaffold can be verified from a clean-enough developer or
|
|
|
71
72
|
- run smoke checks against the actual baseline contract
|
|
72
73
|
- clean up one-off test containers and temporary runtime state
|
|
73
74
|
|
|
75
|
+
`docker compose up --build` and `./run_tests.sh` must never depend on host-installed language runtimes, package managers, CLIs, compilers, SDKs, or other local system packages beyond Docker and the documented baseline host prerequisites.
|
|
76
|
+
If a dependency is needed for runtime or broad verification, define it in Dockerfiles, image build stages, or other repo-controlled container build definitions.
|
|
77
|
+
|
|
74
78
|
`./run_tests.sh` must not be a fake wrapper that only prints TODOs, only runs lint when runtime proof is expected, or silently skips the meaningful part of verification.
|
|
75
79
|
|
|
76
80
|
Minimum proof by family:
|
|
@@ -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
|
|
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
|
|
@@ -88,6 +89,7 @@ This file is the repo-local engineering rulebook for `slopmachine` projects.
|
|
|
88
89
|
- `./run_tests.sh` should use the same startup-value model as `docker compose up --build` rather than a separate pre-seeded test-secret path.
|
|
89
90
|
- If local-development runtime values must persist across restarts, keep them only in Docker-managed runtime state rather than committed repo files.
|
|
90
91
|
- If such a bootstrap script exists, document in the script and in `README.md` that it is for local development bootstrap only and is not the production secret-management path.
|
|
92
|
+
- Do not let `docker compose up --build` or `./run_tests.sh` depend on host-installed packages, SDKs, language runtimes, CLIs, or toolchains beyond Docker and the documented baseline host prerequisites; define those dependencies in Dockerfiles or other repo-controlled container build definitions.
|
|
91
93
|
|
|
92
94
|
## Product Integrity Rules
|
|
93
95
|
|
|
@@ -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
|
|
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
|
|
@@ -88,6 +89,7 @@ This file is the repo-local engineering rulebook for `slopmachine-claude` projec
|
|
|
88
89
|
- `./run_tests.sh` should use the same startup-value model as `docker compose up --build` rather than a separate pre-seeded test-secret path.
|
|
89
90
|
- If local-development runtime values must persist across restarts, keep them only in Docker-managed runtime state rather than committed repo files.
|
|
90
91
|
- If such a bootstrap script exists, document in the script and in `README.md` that it is for local development bootstrap only and is not the production secret-management path.
|
|
92
|
+
- Do not let `docker compose up --build` or `./run_tests.sh` depend on host-installed packages, SDKs, language runtimes, CLIs, or toolchains beyond Docker and the documented baseline host prerequisites; define those dependencies in Dockerfiles or other repo-controlled container build definitions.
|
|
91
93
|
|
|
92
94
|
## Product Integrity Rules
|
|
93
95
|
|
|
Binary file
|
|
@@ -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 || '
|
|
40
|
-
const laneEffort = argv.effort ||
|
|
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'
|
|
@@ -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
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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
|
|