theslopmachine 1.0.22 → 1.0.24
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 +13 -7
- package/README.md +3 -4
- package/RELEASE.md +1 -1
- package/assets/agents/slopmachine-claude.md +4 -2
- package/assets/agents/slopmachine.md +3 -3
- package/assets/skills/deep-retrospective/run.py +13 -1
- package/assets/skills/deep-retrospective/workflow-reference.md +1 -0
- package/assets/skills/developer-session-lifecycle/SKILL.md +3 -3
- package/assets/skills/final-evaluation-orchestration/SKILL.md +5 -1
- package/assets/skills/p8-readiness-reconciliation/SKILL.md +1 -1
- package/assets/skills/planning-guidance/SKILL.md +7 -4
- package/assets/skills/scaffold-guidance/SKILL.md +6 -0
- package/assets/skills/submission-packaging/SKILL.md +3 -3
- package/assets/slopmachine/phase-1-design-prompt.md +18 -2
- package/assets/slopmachine/utils/README.md +3 -3
- package/assets/slopmachine/utils/package_claude_session.mjs +4 -4
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/constants.js +0 -2
- package/src/init.js +83 -447
- package/src/install.js +1 -2
- package/src/send-data.js +10 -4
package/MANUAL.md
CHANGED
|
@@ -15,30 +15,36 @@ The installer copies OpenCode agents to `~/.config/opencode/agents`, Claude asse
|
|
|
15
15
|
## Initialize A Task
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
slopmachine init
|
|
18
|
+
slopmachine init <github-url>
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Run init from an empty workflow root. The GitHub repository name becomes the task root directory name. For example, `slopmachine init https://github.com/example/t178.git` clones into `./t178/`.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
The cloned task root must already contain the task-facing structure: product code in `repo/`, product-facing docs in `docs/`, final kept reports in `.tmp/`, and project facts in `metadata.json`. SlopMachine creates workflow-private state in sibling `./.ai` and `./.beads` directories.
|
|
24
|
+
|
|
25
|
+
Init relies on normal git authentication. If the repository is private and local git cannot access it, clone fails.
|
|
26
|
+
|
|
27
|
+
SlopMachine no longer seeds developer-facing docs, API spec placeholders, product README content, `AGENTS.md`, or `.claude/settings.json`. It only writes the allowed task-root `CLAUDE.md` rulebook.
|
|
28
|
+
|
|
29
|
+
Use `-o` to open OpenCode after bootstrap:
|
|
24
30
|
|
|
25
31
|
```sh
|
|
26
|
-
slopmachine init
|
|
32
|
+
slopmachine init https://github.com/example/t178.git -o
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
The active developer rulebook is recorded in `../.ai/metadata.json` as `developer_rulebook_file`.
|
|
35
|
+
The active developer rulebook is recorded in `../.ai/metadata.json` as `developer_rulebook_file`.
|
|
30
36
|
|
|
31
37
|
## Continue From A Phase Alias
|
|
32
38
|
|
|
33
39
|
```sh
|
|
34
|
-
slopmachine init --continue-from P5
|
|
40
|
+
slopmachine init <github-url> --continue-from P5
|
|
35
41
|
```
|
|
36
42
|
|
|
37
43
|
Legacy aliases remain accepted for CLI compatibility, but owner-facing language uses Phase 1 through Phase 8.
|
|
38
44
|
|
|
39
45
|
## Developer Rulebooks
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
Claude developer lanes read `CLAUDE.md`. SlopMachine seeds only this product engineering rulebook into the task root; it is not an owner workflow instruction file.
|
|
42
48
|
|
|
43
49
|
## Verification
|
|
44
50
|
|
package/README.md
CHANGED
|
@@ -27,12 +27,11 @@ slopmachine install
|
|
|
27
27
|
```sh
|
|
28
28
|
slopmachine --help
|
|
29
29
|
slopmachine install
|
|
30
|
-
slopmachine init <
|
|
31
|
-
slopmachine init --claude <target-dir>
|
|
30
|
+
slopmachine init <github-url>
|
|
32
31
|
slopmachine set-token
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
Use `slopmachine init
|
|
34
|
+
Use `slopmachine init <github-url>` from an empty workflow root. The CLI clones the GitHub repository into `./<repo-name>/`, uses that cloned folder as the task root, creates workflow-private state under `./.ai` and `./.beads`, and records the repo name as `task_root` and `run_id`. The cloned task root is expected to contain the task-facing `docs/`, `.tmp/`, `metadata.json`, and `repo/` structure. SlopMachine no longer seeds developer-facing docs or product README content; it only writes the allowed task-root `CLAUDE.md` rulebook.
|
|
36
35
|
|
|
37
36
|
## Phase Map
|
|
38
37
|
|
|
@@ -69,4 +68,4 @@ npm run check
|
|
|
69
68
|
|
|
70
69
|
## Developer-Facing Boundaries
|
|
71
70
|
|
|
72
|
-
Developer-facing prompts and
|
|
71
|
+
Developer-facing prompts and the task-root `CLAUDE.md` rulebook avoid owner workflow mechanics. They focus on good engineering practice: read the code, implement real behavior, keep README claims honest, test meaningful behavior, avoid secrets, do not run Docker or `run_tests.sh` unless asked, and provide proof for completed work.
|
package/RELEASE.md
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
- Preserves the reference CLI/package behavior.
|
|
6
6
|
- Rebuilds owner agents around Phase 1 through Phase 8 terminology.
|
|
7
7
|
- Adds generic developer prompts for OpenCode and Claude.
|
|
8
|
-
-
|
|
8
|
+
- Seeds only the task-root `CLAUDE.md` rulebook; developer-facing docs and product README content come from the cloned task repository and implementation lane work.
|
|
9
9
|
- Includes Claude-specific worker skills and all required slopmachine utility scripts.
|
|
10
10
|
- Keeps legacy `P*` phase aliases for CLI compatibility.
|
|
@@ -108,6 +108,8 @@ Good Claude-message style:
|
|
|
108
108
|
|
|
109
109
|
## Owner Direct Fixes And Developer Awareness
|
|
110
110
|
|
|
111
|
+
The owner may directly make small safe edits to existing docs, config, wrappers, cleanup, and light glue when the change does not require product-design judgment, broad debugging, new product behavior, or new tests. Inside `./repo`, owner-side edits are limited to existing configuration, Docker files, test wrappers, run scripts, verification scripts, cleanup scripts, and similarly narrow glue. The owner must never create a new file anywhere under `./repo`. New product files, meaningful implementation work, new tests, behavioral changes, and larger fixes must go to the active Claude lane.
|
|
112
|
+
|
|
111
113
|
When the owner makes direct edits to the task directory (README, config, scripts, docs, glue code, cleanup), the active Claude lane (develop-1, bugfix-1, or test-coverage-1) must always be informed of what changed. Batching is required: make a group of fixes, batch them together, then inform the lane once. Do not notify the lane turn by turn for every small edit.
|
|
112
114
|
|
|
113
115
|
This rule applies strictly to the persistent implementation lanes — develop-1, bugfix-1, and test-coverage-1. It does not apply to evaluator sessions, clarification workers, faithfulness reviewers, planning subagents, or other temporary owner-side sessions.
|
|
@@ -148,7 +150,7 @@ Example: `I made a few edits to the README for the startup docs and fixed a conf
|
|
|
148
150
|
- Never use `task` with `developer`, `implement`, `helper`, maintenance, or ad hoc coding subagents for product implementation, product bugfixes, product test authoring, product docs authored by the implementation lane, or implementation verification guidance. Those must go through live Claude lanes using the packaged Claude utilities.
|
|
149
151
|
- Do not use OpenCode subagents, local edits, raw `claude` commands, manual tmux typing, or untracked helper scripts as a substitute for Claude live-lane implementation. The only normal interaction path with Claude lanes is `claude_live_launch.mjs`, `claude_live_turn.mjs`, `claude_live_status.mjs`, and `claude_live_stop.mjs`.
|
|
150
152
|
- Use `question` only for material user decisions that cannot be resolved by a prompt-faithful default.
|
|
151
|
-
- Use `edit`/`write` only for owner-side workflow files, reports, and tiny safe owner fixes that do not substitute for Claude implementation work.
|
|
153
|
+
- Use `edit`/`write` only for owner-side workflow files, reports, and tiny safe owner fixes that do not substitute for Claude implementation work. Inside `./repo`, owner-side edits are limited to existing configuration, Docker files, test wrappers, run scripts, verification scripts, cleanup scripts, and similarly narrow glue. The owner must never create a new file anywhere under `./repo`; new product files, meaningful implementation work, new tests, behavioral changes, and larger fixes must go to the active Claude lane. Do not edit installed packaged prompt assets; those must always be read fresh and pasted verbatim under the non-negotiable verbatim prompt paste rule at the top of this file. If a tiny owner fix touches product code/docs, notify the active Claude lane and ask it to inspect/acknowledge before continuing.
|
|
152
154
|
- Use `todowrite` for substantial multi-step owner work when tracking improves reliability.
|
|
153
155
|
- Use Context7/Exa only when current documentation or external facts are needed.
|
|
154
156
|
|
|
@@ -228,7 +230,7 @@ Use these sequential names as the canonical workflow model. Legacy `P*` names ar
|
|
|
228
230
|
|
|
229
231
|
- Required skills: `beads-operations`, `developer-session-lifecycle`, `claude-worker-management`, `planning-guidance`, `planning-gate`, `owner-evidence-discipline`, and `report-output-discipline` when reports are long or reusable.
|
|
230
232
|
- Establish or resume the primary Claude lane and start design/planning.
|
|
231
|
-
- Follow the deterministic planning sequence in `planning-guidance` exactly: (1) send original prompt with only
|
|
233
|
+
- Follow the deterministic planning sequence in `planning-guidance` exactly: (1) send original prompt with only the required planning/placeholder sentences appended, (2) after acknowledgement send clarifications, (3) after acknowledgement send the design prompt verbatim.
|
|
232
234
|
- Delegate owner-private `../.ai/plan.md` creation to a general owner-side subagent. Read the installed `~/slopmachine/phase-2-execution-planning-prompt.md` and `~/slopmachine/phase-2-plan-template.md` fresh. Paste both bodies verbatim into the subagent message.
|
|
233
235
|
- Record lane/session and artifact decisions in metadata and Beads.
|
|
234
236
|
- Exit only when `planning-gate` is satisfied.
|
|
@@ -108,7 +108,7 @@ Good worker-message style:
|
|
|
108
108
|
|
|
109
109
|
## Owner Direct Fixes And Developer Awareness
|
|
110
110
|
|
|
111
|
-
The owner may directly make small safe edits to docs, config, wrappers, cleanup, and light glue when the change does not require product-design judgment, broad debugging, new product behavior, or new tests. The owner must
|
|
111
|
+
The owner may directly make small safe edits to existing docs, config, wrappers, cleanup, and light glue when the change does not require product-design judgment, broad debugging, new product behavior, or new tests. Inside `./repo`, owner-side edits are limited to existing configuration, Docker files, test wrappers, run scripts, verification scripts, cleanup scripts, and similarly narrow glue. The owner must never create a new file anywhere under `./repo`. New product files, meaningful implementation work, new tests, behavioral changes, and larger fixes must go to the active developer/bugfix/test-coverage lane.
|
|
112
112
|
|
|
113
113
|
When the owner makes direct edits to the task directory (README, config, scripts, docs, glue code, cleanup), the active developer/bugfix/test-coverage lane must always be informed of what changed. Batching is required: make a group of fixes, batch them together, then inform the lane once. Do not notify the lane turn by turn for every small edit.
|
|
114
114
|
|
|
@@ -149,7 +149,7 @@ Example: `I made a few edits to the README for the startup docs and fixed a conf
|
|
|
149
149
|
- Do not use `implement`, `helper`, maintenance, or extra ad hoc subagents for product implementation unless the user explicitly asks. Keep implementation in the tracked active developer session except for evaluator-isolated work or a recorded recovery/context reason.
|
|
150
150
|
- Use `question` only for material user decisions that cannot be resolved by a prompt-faithful default.
|
|
151
151
|
- Use `bash` for git, package managers, tests, Docker, CLIs, runtime checks, and artifact commands.
|
|
152
|
-
- Use `edit`/`write` for owner-side workflow files, tiny safe
|
|
152
|
+
- Use `edit`/`write` for owner-side workflow files, reports, and tiny safe edits to existing docs/config/wrappers/scripts/glue. Inside `./repo`, never use owner-side editing to create new files; new repo files must be created by the active developer/bugfix/test-coverage lane. Do not edit installed packaged prompt assets; those must always be read fresh and pasted verbatim under the non-negotiable verbatim prompt paste rule at the top of this file.
|
|
153
153
|
- Use `todowrite` for substantial multi-step owner work when tracking improves reliability.
|
|
154
154
|
- Use Context7/Exa only when current documentation or external facts are needed.
|
|
155
155
|
|
|
@@ -197,7 +197,7 @@ Use these sequential names as the canonical workflow model. Legacy `P*` names ar
|
|
|
197
197
|
|
|
198
198
|
- Required skills: `beads-operations`, `developer-session-lifecycle`, `planning-guidance`, `planning-gate`, `owner-evidence-discipline`, and `report-output-discipline` when reports are long or reusable.
|
|
199
199
|
- Establish or resume the primary developer session and start design/planning.
|
|
200
|
-
- Follow the deterministic planning sequence in `planning-guidance` exactly: (1) send original prompt with only
|
|
200
|
+
- Follow the deterministic planning sequence in `planning-guidance` exactly: (1) send original prompt with only the required planning/placeholder sentences appended, (2) after acknowledgement send clarifications, (3) after acknowledgement send the design prompt verbatim.
|
|
201
201
|
- Delegate owner-private `../.ai/plan.md` creation to a general owner-side subagent. Read the installed `~/slopmachine/phase-2-execution-planning-prompt.md` and `~/slopmachine/phase-2-plan-template.md` fresh. Paste both bodies verbatim into the subagent message.
|
|
202
202
|
- Record session and artifact decisions in metadata and Beads.
|
|
203
203
|
- Exit only when `planning-gate` is satisfied.
|
|
@@ -272,7 +272,19 @@ def main():
|
|
|
272
272
|
beads = read_beads(project_path)
|
|
273
273
|
ai_data = read_ai_metadata(project_path)
|
|
274
274
|
task_dir = project_path if os.path.isdir(os.path.join(project_path, "docs")) \
|
|
275
|
-
else
|
|
275
|
+
else None
|
|
276
|
+
if not task_dir:
|
|
277
|
+
ai_meta = os.path.join(project_path, ".ai", "metadata.json")
|
|
278
|
+
if os.path.isfile(ai_meta):
|
|
279
|
+
with open(ai_meta) as f:
|
|
280
|
+
ai_data = json.load(f)
|
|
281
|
+
task_root_name = ai_data.get("task_root") if isinstance(ai_data, dict) else None
|
|
282
|
+
candidate = os.path.join(project_path, task_root_name)
|
|
283
|
+
if os.path.isdir(os.path.join(candidate, "docs")):
|
|
284
|
+
task_dir = candidate
|
|
285
|
+
if not task_dir:
|
|
286
|
+
print(f" Warning: could not find task root at {project_path} (no docs/ and no .ai/metadata.json with task_root)")
|
|
287
|
+
task_dir = project_path
|
|
276
288
|
task_docs = read_task_docs(task_dir)
|
|
277
289
|
audit_reports = read_audit_reports(task_dir)
|
|
278
290
|
print(f" Beads: {len(beads)}, Audit reports: {len(audit_reports)}")
|
|
@@ -238,3 +238,4 @@ Implementation must:
|
|
|
238
238
|
10. **Module isolation:** Modules built in isolation with no cross-module integration tests proving data/behavior flow between them
|
|
239
239
|
11. **Skipped evaluator passes:** Fewer than 5 internal evaluator passes run in Phase 4
|
|
240
240
|
12. **No browser verification before P6:** Web/fullstack projects reach P6 without any browser verification, finding crashes that should have been caught in P3 or P4
|
|
241
|
+
13. **Owner-created repo files:** Owner creates new files under repo/ instead of routing file creation through the active developer lane
|
|
@@ -12,7 +12,7 @@ Use this skill for startup preflight, session policy, metadata consistency, lane
|
|
|
12
12
|
Sessions are the primary deliverable. An incomplete or corrupted session dataset invalidates the submission regardless of code quality. Every session must be preserved, continuous, and authentic.
|
|
13
13
|
|
|
14
14
|
- Do not edit, rename, restructure, rewrite, clean, delete, or fabricate session files or trajectory records. They are immutable evidence.
|
|
15
|
-
- Do not perform untracked implementation work. Developer/Claude implementation, debugging, and substantive product fixes must happen inside a tracked implementation session. Owner orchestration, verification commands, package checks, and tiny safe owner-side docs/config/
|
|
15
|
+
- Do not perform untracked implementation work. Developer/Claude implementation, debugging, and substantive product fixes must happen inside a tracked implementation session. Owner orchestration, verification commands, package checks, and tiny safe owner-side edits to existing docs/config/wrappers/scripts/glue are allowed when recorded in metadata/Beads and the active implementation lane is notified afterward. The owner must never create a new file anywhere under `./repo`; new repo files must be created by the active developer/Claude lane.
|
|
16
16
|
- Sessions must progress strictly forward. Never return to a closed session. The lifecycle is:
|
|
17
17
|
1. Development session → complete → stop/close
|
|
18
18
|
2. Bugfix session → complete both audit cycles → close
|
|
@@ -23,7 +23,7 @@ Sessions are the primary deliverable. An incomplete or corrupted session dataset
|
|
|
23
23
|
## Preflight
|
|
24
24
|
|
|
25
25
|
- Confirm cwd is task root `./`.
|
|
26
|
-
- Confirm the current working directory is the task root (the directory containing `repo/`, `docs/`, and `metadata.json`). If the root
|
|
26
|
+
- Confirm the current working directory is the task root (the directory containing `repo/`, `docs/`, and `metadata.json`). If the root does not contain these expected paths, stop and reject: sessions must be started from the task root, not from inside `repo/` or any subdirectory.
|
|
27
27
|
- Confirm product repo exists at `./repo`.
|
|
28
28
|
- Confirm workflow-private root exists at `../.ai`, workflow state exists at `../.ai/metadata.json`, and Beads root exists at `../.beads` when initialized.
|
|
29
29
|
- Confirm task docs are limited to `./docs/questions.md`, `./docs/design.md`, and `./docs/api-spec.md` when applicable.
|
|
@@ -48,7 +48,7 @@ Sessions are the primary deliverable. An incomplete or corrupted session dataset
|
|
|
48
48
|
- Phase 5 uses fresh evaluator sessions for full audits. Evaluator-session reuse occurs in three cases: (1) fail-regeneration — same session receives only the regeneration prompt after fixes; (2) Partial Pass fix-check — same session verifies all scoped issues are fixed; (3) Pass-with-items fix-check — same session verifies all scoped recommendations are closed.
|
|
49
49
|
- Audit Cycle 1 and Audit Cycle 2 fixes go through the dedicated bugfix lane. After both audit cycles complete, the bugfix lane is closed and a new test-coverage/final-reconciliation lane takes over for coverage/README remediation.
|
|
50
50
|
- Phase 6 reconciliation uses the currently active developer/Claude lane for Docker, runtime, browser, account, `run_tests.sh`, coverage, README, and final readiness fixes unless a new lane is explicitly justified by context limits or isolation risk.
|
|
51
|
-
- If the owner directly changes docs, wrappers, config, cleanup, or light glue, send the active lane a minimal acknowledgement request that names the changed surface and asks it to inspect/confirm before readiness continues.
|
|
51
|
+
- If the owner directly changes existing docs, wrappers, config, cleanup, scripts, or light glue, send the active lane a minimal acknowledgement request that names the changed surface and asks it to inspect/confirm before readiness continues.
|
|
52
52
|
|
|
53
53
|
## Metadata Discipline
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ No other full-audit or fail-regeneration prompt is allowed. In particular, the o
|
|
|
58
58
|
|
|
59
59
|
If any deviation is found from either allowed prompt send, the current audit cycle is invalid. Archive every report or candidate report generated during that invalid cycle unchanged under `../.ai/archive/`, record the restart reason in metadata and Beads, then restart that audit cycle from a fresh evaluator session using the installed asset and exact saved send packet. Do not patch, rename, or reuse invalid-cycle reports as kept reports.
|
|
60
60
|
|
|
61
|
-
Record the installed prompt asset path, prepared prompt path, exact send packet path, evaluator session id, and report path in metadata and Beads. Treat evaluator reports as immutable once written.
|
|
61
|
+
Record the installed prompt asset path, prepared prompt path, exact send packet path, evaluator session id, and report path in metadata and Beads. Treat evaluator reports as immutable once written except for the narrow minor wording cleanup allowed by cycle validation below.
|
|
62
62
|
|
|
63
63
|
## Report Paths And Names
|
|
64
64
|
|
|
@@ -152,6 +152,8 @@ Run this procedure twice: once for Audit Cycle 1 and once for Audit Cycle 2.
|
|
|
152
152
|
|
|
153
153
|
Before a cycle can close, validate the whole cycle as a hard gate. If any item fails, archive every candidate/kept/fix-check report produced in that cycle unchanged under `../.ai/archive/`, record the reason, and restart that audit cycle from a fresh evaluator session using the non-negotiable full-audit prompt block.
|
|
154
154
|
|
|
155
|
+
Exception: if the only issue is minor wording that does not change findings, verdict, severity, evidence, scoped issue identity, fix status, or report meaning, the owner may patch that wording directly in the kept report or fix-check report instead of restarting the cycle. Record the exact wording cleanup, file path, and reason in metadata and Beads. Do not use this exception to alter verdicts, remove real findings, add missing fix evidence, repair a mismatched issue list, or hide a materially invalid regeneration/fix-check.
|
|
156
|
+
|
|
155
157
|
Required for each cycle:
|
|
156
158
|
- the installed evaluation prompt asset path is recorded and matches the project type;
|
|
157
159
|
- the exact saved send packet path is recorded;
|
|
@@ -160,8 +162,10 @@ Required for each cycle:
|
|
|
160
162
|
- the kept audit report exists at `./.tmp/audit_report-<N>.md`;
|
|
161
163
|
- the kept audit report is rich and complete: at least 150 lines and not materially shallower than the installed prompt's required output structure;
|
|
162
164
|
- the kept audit report includes the required verdict, scope/boundary, prompt/repository mapping, section review or blocker/high panel as applicable, issues/suggestions or explicit no-issue statement, security/data-risk review where applicable, and test/logging/coverage sections required by the installed prompt;
|
|
165
|
+
- the kept audit report has no language that reveals or implies it is a regeneration, rerun, continuation, post-fix report, or comparison against an earlier audit. Forbidden examples include: `regenerated`, `regeneration`, `rerun`, `previous audit`, `previous report`, `prior audit`, `after fixes`, `after the fixes`, `fixed since`, `remaining`, `still`, `now fixed`, `resolved`, `no longer`, `left`, `updated verdict`, or equivalent wording that exposes audit history. If this language is only incidental wording and does not affect findings or meaning, patch it and record the cleanup. If the report is continuation-shaped, fix-oriented, or materially depends on prior audit history, archive and restart the cycle;
|
|
163
166
|
- the cycle fix-check report exists at `./.tmp/audit_report-<N>-fix_check.md`;
|
|
164
167
|
- the cycle fix-check report confirms every issue/recommendation/caveat/suggestion/action item/requested change from the kept audit report is fixed, or explicitly confirms the kept audit report had zero scoped items to close;
|
|
168
|
+
- the scoped issue list in the fix-check exactly matches the scoped issue list from the corresponding kept audit report. Every kept-audit issue/recommendation/caveat/suggestion/action item/requested change must appear in the fix-check with a fixed status and evidence, and the fix-check must not silently drop, merge beyond recognition, rename into a different issue, or add unrelated new audit issues. If the lists do not match, the cycle is invalid and must restart unless the mismatch is only a harmless wording/label discrepancy that can be patched without changing issue identity, fix status, evidence, or meaning;
|
|
165
169
|
- the fix-check report does not perform a broader new audit and does not use history-exposing comparison language.
|
|
166
170
|
|
|
167
171
|
No audit cycle is complete without both `./.tmp/audit_report-<N>.md` and `./.tmp/audit_report-<N>-fix_check.md` passing this validation gate.
|
|
@@ -74,7 +74,7 @@ If any final runtime, test, browser, account, or platform check cannot run, read
|
|
|
74
74
|
|
|
75
75
|
## Owner Direct Fixes
|
|
76
76
|
|
|
77
|
-
- The owner may directly fix only minor, safe docs, wrapper, config, cleanup, or light glue issues when the change does not require product-design judgment, new tests, behavioral changes, or non-trivial debugging.
|
|
77
|
+
- The owner may directly fix only minor, safe existing docs, wrapper, config, cleanup, run-script, verification-script, or light glue issues when the change does not require product-design judgment, new tests, behavioral changes, or non-trivial debugging. The owner must never create a new file anywhere under `./repo`; any new repo file must be created by the active developer/Claude lane.
|
|
78
78
|
- If the reconciliation issue is large enough to need real implementation work, meaningful test updates, runtime debugging, README/runtime restructuring, or product judgment, do not fix it owner-side. Send it to the currently active developer/Claude lane.
|
|
79
79
|
- Batch multiple direct fixes into a group, then inform the lane once. Do not notify turn by turn for every small edit. After all fixes in the batch are complete, send a single note describing all changed surfaces and ask the lane to inspect and acknowledge.
|
|
80
80
|
- The note should be concise and developer-facing, not a workflow report.
|
|
@@ -22,7 +22,7 @@ Phase 2 establishes the primary developer session and produces the accepted plan
|
|
|
22
22
|
- Accepted `./docs/questions.md`.
|
|
23
23
|
- Accepted `../.ai/requirements-breakdown.md`.
|
|
24
24
|
- Accepted `../.ai/clarification-faithfulness-review.md`.
|
|
25
|
-
- Packaged design prompt
|
|
25
|
+
- Packaged design prompt.
|
|
26
26
|
- Packaged execution planning prompt and plan template.
|
|
27
27
|
|
|
28
28
|
## Deterministic Planning Sequence (Must Follow Exactly)
|
|
@@ -31,10 +31,11 @@ This sequence is strict. Do not combine, reorder, skip, or batch these steps. Ea
|
|
|
31
31
|
|
|
32
32
|
### Step 1: Send Original Prompt
|
|
33
33
|
|
|
34
|
-
Send the original product prompt from `./metadata.json` exactly as-is. No prefix, no introduction, no context, no clarifications. Append only
|
|
34
|
+
Send the original product prompt from `./metadata.json` exactly as-is. No prefix, no introduction, no context, no clarifications. Append only these exact sentences at the end:
|
|
35
35
|
|
|
36
36
|
```
|
|
37
37
|
Don't write code yet — we'll plan this first.
|
|
38
|
+
This folder may contain placeholder docs or repo content from the starting skeleton; please inspect it and remove or replace unrelated placeholder material as we go.
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
That is the entire message. Wait for the developer to acknowledge before proceeding.
|
|
@@ -53,7 +54,7 @@ Wait for the developer to acknowledge before proceeding.
|
|
|
53
54
|
|
|
54
55
|
### Step 3: Send Design Prompt
|
|
55
56
|
|
|
56
|
-
After the developer acknowledges the clarifications, read the installed `~/slopmachine/phase-1-design-prompt.md` fresh from its asset path and paste its full body verbatim under the non-negotiable verbatim prompt paste rule. The design prompt references the context already provided in Steps 1 and 2 and the
|
|
57
|
+
After the developer acknowledges the clarifications, read the installed `~/slopmachine/phase-1-design-prompt.md` fresh from its asset path and paste its full body verbatim under the non-negotiable verbatim prompt paste rule. The design prompt references the context already provided in Steps 1 and 2 and tells the lane what design surfaces to cover because `./docs/design.md` may be missing or placeholder-only.
|
|
57
58
|
|
|
58
59
|
The design must:
|
|
59
60
|
- be a true product/system design, not an execution plan
|
|
@@ -72,7 +73,9 @@ Patch small wording/traceability issues directly when meaning is unchanged. Send
|
|
|
72
73
|
|
|
73
74
|
### Step 5: Create `./docs/api-spec.md` When Applicable
|
|
74
75
|
|
|
75
|
-
Use the accepted design as input. Require exact endpoint/interface contracts where APIs exist. If no meaningful API exists, mark `./docs/api-spec.md` as not applicable with a short reason. Record the API spec artifact path and applicability decision in metadata and Beads.
|
|
76
|
+
Use the accepted design as input. Require exact endpoint/interface contracts where APIs exist. If `./docs/api-spec.md` is missing or placeholder-only, the implementation lane must create or replace it. If no meaningful API exists, mark `./docs/api-spec.md` as not applicable with a short reason. Record the API spec artifact path and applicability decision in metadata and Beads.
|
|
77
|
+
|
|
78
|
+
The API spec prompt must ask for, where applicable: endpoint/interface name, purpose, actor/consumer, method/path or call signature, auth and permission model, request fields, response fields, validation rules, success status/result, important error cases, persistence/side effects, frontend or job consumers, and required API/integration tests. Do not provide endpoint conclusions that the design has not established; ask the lane to derive exact contracts from the accepted design and codebase context.
|
|
76
79
|
|
|
77
80
|
### Step 6: Owner Reviews Design and API Spec Together
|
|
78
81
|
|
|
@@ -39,7 +39,13 @@ Use casual, direct language. Example:
|
|
|
39
39
|
```text
|
|
40
40
|
Let's start with the scaffold. Set up the base project in ./repo for the stack described in docs/design.md. Keep this to the framework, runtime, tests, and README foundation with a minimal proof page or endpoint. Do not add product-specific business logic yet.
|
|
41
41
|
|
|
42
|
+
The cloned folder may contain placeholder docs, README text, scripts, or config. Remove or replace unrelated placeholder content instead of preserving it.
|
|
43
|
+
|
|
42
44
|
The scaffold needs a working docker-compose.yml with a profile-gated test service, a run_tests.sh that runs the full test suite through Docker, a separate stack-native local test harness for fast implementation-time checks, a unit_tests directory for unit tests, an API_tests directory for API and integration HTTP tests, and a README baseline with startup, access, verification, and test commands. Make sure the local harness is separate from the Docker test path. The local harness is for fast iteration. run_tests.sh is the broad dockerized verification wrapper for later.
|
|
45
|
+
|
|
46
|
+
The README needs to be enough for a reviewer to run and verify the project later: project type near the top, what the repo delivers, stack, repo layout, primary startup/access command, legacy docker-compose compatibility string when Docker Compose is used, verification method, auth/demo credentials or "No authentication required", seeded data or empty-state note, mock/local/debug disclosures, known limitations, and the Dockerized ./run_tests.sh contract. Do not leave template placeholders in README.
|
|
47
|
+
|
|
48
|
+
The run_tests.sh and Docker config must be real, not placeholders: run_tests.sh should execute the broad Docker-contained test path, docker-compose.yml should include the app services needed for runtime and a profile-gated test service, and the local test harness should remain separate for fast implementation-time checks.
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
Adjust the exact wording to the project. Do not over-format the message.
|
|
@@ -11,8 +11,8 @@ Packaging is a final-delivery contract, not a reporting exercise. Do not close P
|
|
|
11
11
|
|
|
12
12
|
## Package Boundary
|
|
13
13
|
|
|
14
|
-
- Package root is `
|
|
15
|
-
- Product repo is
|
|
14
|
+
- Package root is the task root directory (named by the task-id from `../.ai/metadata.json`'s `task_root` field).
|
|
15
|
+
- Product repo is `<task-root>/repo`.
|
|
16
16
|
- Workflow-private state remains outside package under `../.ai`, `../.beads`, and `../claude-sessions.zip`.
|
|
17
17
|
- Do not package workflow-private state as product files.
|
|
18
18
|
- The final task-root package allowlist is `docs/`, `repo/`, `.tmp/`, `metadata.json`, plus explicit VCS/validation exceptions.
|
|
@@ -81,7 +81,7 @@ Do not run broad Docker prune commands that can affect unrelated projects.
|
|
|
81
81
|
## Session Export
|
|
82
82
|
|
|
83
83
|
- Export tracked developer sessions before closing packaging.
|
|
84
|
-
- Claude-backed lanes produce `../claude-sessions.zip` as a sibling handoff artifact, not as a product file under
|
|
84
|
+
- Claude-backed lanes produce `../claude-sessions.zip` as a sibling handoff artifact, not as a product file under the task root.
|
|
85
85
|
- Use packaged session export utilities when available and preserve prompt-anchored session provenance.
|
|
86
86
|
- Verify exported session content is anchored to the original prompt from `./metadata.json` strongly enough to support package lineage.
|
|
87
87
|
- Do not manually edit, rewrite, fabricate, rename, clean, delete, or restructure raw session/trajectory files to make the package look cleaner. Only use packaged export/normalization utilities that preserve provenance.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
You are helping create the product/system design for a software project.
|
|
2
2
|
|
|
3
|
-
The original prompt, stack and context, accepted clarifications, and accepted requirements have already been provided in the previous steps. The
|
|
3
|
+
The original prompt, stack and context, accepted clarifications, and accepted requirements have already been provided in the previous steps. The task root may contain placeholder docs from the cloned starting repository; inspect them, keep only relevant true information, and replace unrelated placeholder content.
|
|
4
4
|
|
|
5
5
|
Your task is to write `./docs/design.md`.
|
|
6
6
|
|
|
@@ -10,6 +10,22 @@ This is planning/design work only. Do not write implementation code.
|
|
|
10
10
|
|
|
11
11
|
Produce a design document that is faithful to the original prompt and accepted clarifications. The design should be specific enough to guide implementation, but it must not become a step-by-step execution checklist.
|
|
12
12
|
|
|
13
|
+
## Expected Shape
|
|
14
|
+
|
|
15
|
+
Use clear sections that cover these surfaces where applicable:
|
|
16
|
+
- project overview: project type, delivery shape, business objective, success outcome, and stack summary
|
|
17
|
+
- prompt and clarification alignment: accepted requirements, accepted clarifications, safe assumptions, and explicit non-goals that do not narrow the prompt
|
|
18
|
+
- actors, roles, permissions, and the main user/system flows
|
|
19
|
+
- module design: purpose, owned behavior, UI/API/job surfaces, data ownership, failure/security cases, and required test surfaces
|
|
20
|
+
- data design: entities/objects, lifecycle rules, visibility, and access rules
|
|
21
|
+
- UI or interaction design, or a brief not-applicable reason
|
|
22
|
+
- API or interface design, including whether `./docs/api-spec.md` is required
|
|
23
|
+
- security and privacy design: auth, authorization, object isolation, sensitive data, logging/redaction, debug/admin protection, file/path safety, and abuse prevention where relevant
|
|
24
|
+
- runtime and configuration design: runtime model, configuration model, persistence, seed/demo data, external integrations, and README startup/access/auth expectations
|
|
25
|
+
- verification strategy: unit, API/integration, E2E/platform, frontend component, frontend-to-backend, README/runtime, Docker, and known proof boundaries
|
|
26
|
+
- API spec handoff: whether a separate API spec is required and which API/interface families it must cover
|
|
27
|
+
- remaining design risks that still need a decision
|
|
28
|
+
|
|
13
29
|
## Required Design Qualities
|
|
14
30
|
|
|
15
31
|
The design must:
|
|
@@ -39,6 +55,6 @@ The design should define what the system must be and how its major parts fit tog
|
|
|
39
55
|
|
|
40
56
|
## Output
|
|
41
57
|
|
|
42
|
-
Write the completed design to `./docs/design.md
|
|
58
|
+
Write the completed design to `./docs/design.md`. If a placeholder file already exists there, replace unrelated placeholder content rather than preserving it. If a section is not applicable, keep it brief and explain why.
|
|
43
59
|
|
|
44
60
|
If the project has meaningful APIs or interface contracts, say that `./docs/api-spec.md` should be completed next and summarize the API families that need specification.
|
|
@@ -13,7 +13,7 @@ The current type-check gate covers the Claude/session tooling family because the
|
|
|
13
13
|
|
|
14
14
|
## Path Model
|
|
15
15
|
|
|
16
|
-
- `--task-root` means the workflow task root
|
|
16
|
+
- `--task-root` means the workflow task root directory (named by the task-id from `../.ai/metadata.json`'s `task_root` field).
|
|
17
17
|
- Product code lives under `<task-root>/repo`.
|
|
18
18
|
- Workflow-private state lives outside task root under `<workflow-root>/.ai` and `<workflow-root>/.beads`.
|
|
19
19
|
- Claude project lookup uses the task root because Claude transcripts are keyed by the working directory used to launch Claude.
|
|
@@ -115,10 +115,10 @@ Required:
|
|
|
115
115
|
- `--task-root <task-root>`
|
|
116
116
|
|
|
117
117
|
Important options:
|
|
118
|
-
- `--output <zip-path>` writes the zip to a custom path; default is `<
|
|
118
|
+
- `--output <zip-path>` writes the zip to a custom path; default is `<workflow-root>/claude-sessions.zip`
|
|
119
119
|
- `--label <text>` adds reporting context
|
|
120
120
|
|
|
121
|
-
The helper copies the Claude project/session directory as-is, removes `.DS_Store` files and top-level `.jsonl` transcripts under 25KB from the staged copy, zips the
|
|
121
|
+
The helper copies the Claude project/session directory as-is into a temp directory, removes `.DS_Store` files and top-level `.jsonl` transcripts under 25KB from the staged copy, zips the whole copied folder, and writes a single zip. It does not normalize or rewrite transcript files.
|
|
122
122
|
|
|
123
123
|
### `analyze_claude_project_dir.mjs`
|
|
124
124
|
|
|
@@ -18,7 +18,7 @@ Required:
|
|
|
18
18
|
--task-root <task-root>
|
|
19
19
|
|
|
20
20
|
Options:
|
|
21
|
-
--output <zip-path> Zip output path (default: <
|
|
21
|
+
--output <zip-path> Zip output path (default: <workflow-root>/claude-sessions.zip)
|
|
22
22
|
--label <text> Optional package label for reporting
|
|
23
23
|
`)
|
|
24
24
|
}
|
|
@@ -168,7 +168,7 @@ try {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
const sourceProjectDir = await resolveClaudeProjectDir(taskRoot)
|
|
171
|
-
const outputPath = argv.output ? path.resolve(argv.output) : path.join(taskRoot, 'claude-sessions.zip')
|
|
171
|
+
const outputPath = argv.output ? path.resolve(argv.output) : path.join(path.dirname(taskRoot), 'claude-sessions.zip')
|
|
172
172
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-claude-project-'))
|
|
173
173
|
const packageRoot = path.join(tempRoot, path.basename(sourceProjectDir))
|
|
174
174
|
|
|
@@ -177,7 +177,7 @@ try {
|
|
|
177
177
|
await removeDsStoreFiles(packageRoot)
|
|
178
178
|
const tinyRootTranscriptsRemoved = await removeTinyRootTranscripts(packageRoot)
|
|
179
179
|
const included = (await listFilesRecursive(packageRoot)).sort((left, right) => left.localeCompare(right))
|
|
180
|
-
await createZipArchive(
|
|
180
|
+
await createZipArchive(tempRoot, outputPath)
|
|
181
181
|
|
|
182
182
|
emitSuccess(path.basename(sourceProjectDir), {
|
|
183
183
|
output: outputPath,
|
|
@@ -185,7 +185,7 @@ try {
|
|
|
185
185
|
label: argv.label || null,
|
|
186
186
|
included,
|
|
187
187
|
tiny_root_transcripts_removed: tinyRootTranscriptsRemoved,
|
|
188
|
-
packaging_mode: '
|
|
188
|
+
packaging_mode: 'raw_claude_project_dir_folder',
|
|
189
189
|
})
|
|
190
190
|
} finally {
|
|
191
191
|
await fs.rm(tempRoot, { recursive: true, force: true }).catch(() => {})
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ function printHelp() {
|
|
|
10
10
|
Commands:
|
|
11
11
|
setup Configure slopmachine in the local user environment
|
|
12
12
|
upgrade Install slopmachine@latest globally and rerun setup
|
|
13
|
-
init
|
|
13
|
+
init <github-url> Clone a task repository into the current workflow root and bootstrap workflow state (-o opens OpenCode)
|
|
14
14
|
cleanup Remove/archive obsolete installed agents, skills, and assets (--dry-run previews)
|
|
15
15
|
set-token Store the upload token in machine config
|
|
16
16
|
send-data Upload workflow artifacts to Supabase
|
package/src/constants.js
CHANGED
|
@@ -44,12 +44,10 @@ export const REQUIRED_SLOPMACHINE_FILES = [
|
|
|
44
44
|
"clarifier-agent-prompt.md",
|
|
45
45
|
"clarification-faithfulness-review-prompt.md",
|
|
46
46
|
"phase-1-design-prompt.md",
|
|
47
|
-
"phase-1-design-template.md",
|
|
48
47
|
"phase-2-execution-planning-prompt.md",
|
|
49
48
|
"phase-2-plan-template.md",
|
|
50
49
|
"test-coverage-prompt.md",
|
|
51
50
|
"owner-verification-checklist.md",
|
|
52
|
-
"exact-readme-template.md",
|
|
53
51
|
"workflow-init.js",
|
|
54
52
|
"templates/AGENTS.md",
|
|
55
53
|
"templates/CLAUDE.md",
|
package/src/init.js
CHANGED
|
@@ -1,111 +1,11 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import { randomUUID } from 'node:crypto'
|
|
3
1
|
import path from 'node:path'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
4
3
|
|
|
5
4
|
import { PACKAGE_ROOT, buildPaths } from './constants.js'
|
|
6
|
-
import { ensureDir, log, pathExists,
|
|
7
|
-
|
|
8
|
-
const GITIGNORE_ENTRIES = [
|
|
9
|
-
'# OS / editor noise',
|
|
10
|
-
'.DS_Store',
|
|
11
|
-
'**/.DS_Store',
|
|
12
|
-
'Thumbs.db',
|
|
13
|
-
'.idea/',
|
|
14
|
-
'.vscode/',
|
|
15
|
-
'*.swp',
|
|
16
|
-
'*.swo',
|
|
17
|
-
'*~',
|
|
18
|
-
|
|
19
|
-
'# Environment / secrets',
|
|
20
|
-
'.env',
|
|
21
|
-
'.env.*',
|
|
22
|
-
'!.env.example',
|
|
23
|
-
'*.pem',
|
|
24
|
-
'*.key',
|
|
25
|
-
'*.crt',
|
|
26
|
-
|
|
27
|
-
'# Logs',
|
|
28
|
-
'*.log',
|
|
29
|
-
'logs/',
|
|
30
|
-
'npm-debug.log*',
|
|
31
|
-
'yarn-debug.log*',
|
|
32
|
-
'yarn-error.log*',
|
|
33
|
-
'pnpm-debug.log*',
|
|
34
|
-
'gh-multi_logs/',
|
|
35
|
-
'antigravity-logs/',
|
|
36
|
-
|
|
37
|
-
'# Node / package managers',
|
|
38
|
-
'node_modules/',
|
|
39
|
-
'.npm/',
|
|
40
|
-
'.pnpm-store/',
|
|
41
|
-
'.yarn/cache/',
|
|
42
|
-
'.yarn/unplugged/',
|
|
43
|
-
'.yarn/build-state.yml',
|
|
44
|
-
'.yarn/install-state.gz',
|
|
45
|
-
|
|
46
|
-
'# Build / coverage outputs',
|
|
47
|
-
'dist/',
|
|
48
|
-
'build/',
|
|
49
|
-
'out/',
|
|
50
|
-
'coverage/',
|
|
51
|
-
'.coverage',
|
|
52
|
-
'.nyc_output/',
|
|
53
|
-
'.next/',
|
|
54
|
-
'.nuxt/',
|
|
55
|
-
'.output/',
|
|
56
|
-
'.vite/',
|
|
57
|
-
'storybook-static/',
|
|
58
|
-
|
|
59
|
-
'# Test / framework caches',
|
|
60
|
-
'.cache/',
|
|
61
|
-
'.parcel-cache/',
|
|
62
|
-
'.turbo/',
|
|
63
|
-
'.eslintcache',
|
|
64
|
-
'*.tsbuildinfo',
|
|
65
|
-
'playwright-report/',
|
|
66
|
-
'test-results/',
|
|
67
|
-
'blob-report/',
|
|
68
|
-
|
|
69
|
-
'# Python caches',
|
|
70
|
-
'.pytest_cache/',
|
|
71
|
-
'.mypy_cache/',
|
|
72
|
-
'.ruff_cache/',
|
|
73
|
-
'__pycache__/',
|
|
74
|
-
'**/__pycache__/',
|
|
75
|
-
'*.py[cod]',
|
|
76
|
-
'.venv/',
|
|
77
|
-
'venv/',
|
|
78
|
-
|
|
79
|
-
'# Archives / packaged outputs',
|
|
80
|
-
'*.tgz',
|
|
81
|
-
'*.tar.gz',
|
|
82
|
-
'*.zip',
|
|
83
|
-
'*.raw',
|
|
84
|
-
|
|
85
|
-
'# Local session/runtime artifacts',
|
|
86
|
-
'sessions/',
|
|
87
|
-
'claude-sessions.zip',
|
|
88
|
-
'session-export-*.raw',
|
|
89
|
-
'.tmp-*/',
|
|
90
|
-
'.tmp-home*/',
|
|
91
|
-
'.tmp-project*/',
|
|
92
|
-
'.tmp-fakebin/',
|
|
93
|
-
'sync-machine-assets.local.mjs',
|
|
94
|
-
]
|
|
5
|
+
import { ensureDir, log, pathExists, resolveCommand, runCommand, warn } from './utils.js'
|
|
95
6
|
|
|
96
7
|
const ALLOWED_EXISTING_ENTRIES = new Set(['.DS_Store', '.git'])
|
|
97
|
-
const INITIAL_COMMIT_PATHS = ['.gitignore', 'AGENTS.md', 'CLAUDE.md', '.claude', 'docs', 'metadata.json', 'repo']
|
|
98
|
-
const ADOPTION_ROOT_KEEP = new Set([
|
|
99
|
-
'.DS_Store',
|
|
100
|
-
'.git',
|
|
101
|
-
'.gitignore',
|
|
102
|
-
'.ai',
|
|
103
|
-
'.beads',
|
|
104
|
-
'task',
|
|
105
|
-
'metadata.json',
|
|
106
|
-
])
|
|
107
8
|
const VALID_START_PHASES = new Set(['P1', 'P2', 'P3', 'P4', 'P5', 'P7', 'P8', 'P9', 'P10'])
|
|
108
|
-
const VALID_PROJECT_TYPES = new Set(['backend', 'fullstack', 'web', 'android', 'ios', 'desktop'])
|
|
109
9
|
const PHASE_LABELS = {
|
|
110
10
|
P1: 'Phase 1 Clarification',
|
|
111
11
|
P2: 'Phase 2 Planning',
|
|
@@ -155,11 +55,8 @@ async function resolveBrCommand(paths) {
|
|
|
155
55
|
|
|
156
56
|
function parseInitArgs(args) {
|
|
157
57
|
let openAfterInit = false
|
|
158
|
-
let
|
|
159
|
-
let continueFromExisting = false
|
|
160
|
-
let targetInput = '.'
|
|
58
|
+
let githubUrl = null
|
|
161
59
|
let requestedStartPhase = null
|
|
162
|
-
let developerRulebookFile = null
|
|
163
60
|
|
|
164
61
|
function setRequestedStartPhase(optionName, phase) {
|
|
165
62
|
if (!VALID_START_PHASES.has(phase)) {
|
|
@@ -179,47 +76,22 @@ function parseInitArgs(args) {
|
|
|
179
76
|
continue
|
|
180
77
|
}
|
|
181
78
|
|
|
182
|
-
if (arg === '--adopt') {
|
|
183
|
-
adoptExisting = true
|
|
184
|
-
continue
|
|
185
|
-
}
|
|
186
|
-
|
|
187
79
|
if (arg === '--claude') {
|
|
188
|
-
|
|
189
|
-
throw new Error('Conflicting developer rulebook options. Use only one of --claude, --opencode, or --rulebook.')
|
|
190
|
-
}
|
|
191
|
-
developerRulebookFile = 'CLAUDE.md'
|
|
80
|
+
// Claude is the only seeded developer rulebook now; keep this option as a harmless explicit mode.
|
|
192
81
|
continue
|
|
193
82
|
}
|
|
194
83
|
|
|
195
84
|
if (arg === '--opencode') {
|
|
196
|
-
|
|
197
|
-
throw new Error('Conflicting developer rulebook options. Use only one of --claude, --opencode, or --rulebook.')
|
|
198
|
-
}
|
|
199
|
-
developerRulebookFile = 'AGENTS.md'
|
|
85
|
+
throw new Error('--opencode is no longer supported for init. The cloned task root keeps only CLAUDE.md as the seeded developer rulebook.')
|
|
200
86
|
continue
|
|
201
87
|
}
|
|
202
88
|
|
|
203
89
|
if (arg === '--rulebook') {
|
|
204
|
-
|
|
205
|
-
if (!nextArg) {
|
|
206
|
-
throw new Error('Missing value for --rulebook')
|
|
207
|
-
}
|
|
208
|
-
const normalizedRulebook = normalizeDeveloperRulebookOption(nextArg)
|
|
209
|
-
if (developerRulebookFile && developerRulebookFile !== normalizedRulebook) {
|
|
210
|
-
throw new Error('Conflicting developer rulebook options. Use only one of --claude, --opencode, or --rulebook.')
|
|
211
|
-
}
|
|
212
|
-
developerRulebookFile = normalizedRulebook
|
|
213
|
-
index += 1
|
|
214
|
-
continue
|
|
90
|
+
throw new Error('--rulebook is no longer supported for init. The cloned task root keeps only CLAUDE.md as the seeded developer rulebook.')
|
|
215
91
|
}
|
|
216
92
|
|
|
217
93
|
if (arg.startsWith('--rulebook=')) {
|
|
218
|
-
|
|
219
|
-
if (developerRulebookFile && developerRulebookFile !== normalizedRulebook) {
|
|
220
|
-
throw new Error('Conflicting developer rulebook options. Use only one of --claude, --opencode, or --rulebook.')
|
|
221
|
-
}
|
|
222
|
-
developerRulebookFile = normalizedRulebook
|
|
94
|
+
throw new Error('--rulebook is no longer supported for init. The cloned task root keeps only CLAUDE.md as the seeded developer rulebook.')
|
|
223
95
|
continue
|
|
224
96
|
}
|
|
225
97
|
|
|
@@ -244,16 +116,12 @@ function parseInitArgs(args) {
|
|
|
244
116
|
if (!nextArg) {
|
|
245
117
|
throw new Error('Missing value for --continue-from')
|
|
246
118
|
}
|
|
247
|
-
adoptExisting = true
|
|
248
|
-
continueFromExisting = true
|
|
249
119
|
setRequestedStartPhase('--continue-from', nextArg)
|
|
250
120
|
index += 1
|
|
251
121
|
continue
|
|
252
122
|
}
|
|
253
123
|
|
|
254
124
|
if (arg.startsWith('--continue-from=')) {
|
|
255
|
-
adoptExisting = true
|
|
256
|
-
continueFromExisting = true
|
|
257
125
|
setRequestedStartPhase('--continue-from', arg.slice('--continue-from='.length))
|
|
258
126
|
continue
|
|
259
127
|
}
|
|
@@ -262,44 +130,24 @@ function parseInitArgs(args) {
|
|
|
262
130
|
throw new Error(`Unknown init option: ${arg}`)
|
|
263
131
|
}
|
|
264
132
|
|
|
265
|
-
|
|
133
|
+
if (!githubUrl) {
|
|
134
|
+
githubUrl = arg
|
|
135
|
+
} else {
|
|
136
|
+
throw new Error('slopmachine init accepts exactly one GitHub URL positional argument')
|
|
137
|
+
}
|
|
266
138
|
}
|
|
267
139
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
function normalizeDeveloperRulebookOption(value) {
|
|
272
|
-
const normalized = String(value || '').trim().toLowerCase()
|
|
273
|
-
if (['agents', 'agent', 'opencode', 'agents.md'].includes(normalized)) {
|
|
274
|
-
return 'AGENTS.md'
|
|
140
|
+
if (!githubUrl) {
|
|
141
|
+
throw new Error('Usage: slopmachine init <github-url> [options]. The cloned repository name is used as the task root directory name.')
|
|
275
142
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
throw new Error("Unsupported --rulebook value. Use 'agents'/'opencode' or 'claude'.")
|
|
143
|
+
|
|
144
|
+
return { openAfterInit, githubUrl, requestedStartPhase, developerRulebookFile: 'CLAUDE.md' }
|
|
280
145
|
}
|
|
281
146
|
|
|
282
147
|
function normalizeRequestedStartPhase(phase) {
|
|
283
148
|
return phase === 'P4' ? 'P3' : phase
|
|
284
149
|
}
|
|
285
150
|
|
|
286
|
-
function resolveWorkflowRootForInit(targetPath, options) {
|
|
287
|
-
if (!options.continueFromExisting) {
|
|
288
|
-
return targetPath
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (path.basename(targetPath) === 'task') {
|
|
292
|
-
return path.dirname(targetPath)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (path.basename(targetPath) === 'repo') {
|
|
296
|
-
const parent = path.dirname(targetPath)
|
|
297
|
-
return path.basename(parent) === 'task' ? path.dirname(parent) : parent
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return targetPath
|
|
301
|
-
}
|
|
302
|
-
|
|
303
151
|
async function assertRequiredFiles(paths) {
|
|
304
152
|
const installedRoot = paths.slopmachineDir
|
|
305
153
|
const packagedRoot = path.join(PACKAGE_ROOT, 'assets', 'slopmachine')
|
|
@@ -307,18 +155,12 @@ async function assertRequiredFiles(paths) {
|
|
|
307
155
|
const installedFiles = {
|
|
308
156
|
runtimeAssetRoot: installedRoot,
|
|
309
157
|
trackerScript: path.join(installedRoot, 'workflow-init.js'),
|
|
310
|
-
agentsTemplate: path.join(installedRoot, 'templates', 'AGENTS.md'),
|
|
311
158
|
claudeTemplate: path.join(installedRoot, 'templates', 'CLAUDE.md'),
|
|
312
|
-
phase1DesignTemplate: path.join(installedRoot, 'phase-1-design-template.md'),
|
|
313
|
-
readmeTemplate: path.join(installedRoot, 'exact-readme-template.md'),
|
|
314
159
|
}
|
|
315
160
|
const packagedFiles = {
|
|
316
161
|
runtimeAssetRoot: packagedRoot,
|
|
317
162
|
trackerScript: path.join(packagedRoot, 'workflow-init.js'),
|
|
318
|
-
agentsTemplate: path.join(packagedRoot, 'templates', 'AGENTS.md'),
|
|
319
163
|
claudeTemplate: path.join(packagedRoot, 'templates', 'CLAUDE.md'),
|
|
320
|
-
phase1DesignTemplate: path.join(packagedRoot, 'phase-1-design-template.md'),
|
|
321
|
-
readmeTemplate: path.join(packagedRoot, 'exact-readme-template.md'),
|
|
322
164
|
}
|
|
323
165
|
|
|
324
166
|
const packagedReady = await Promise.all(Object.entries(packagedFiles)
|
|
@@ -341,14 +183,6 @@ async function assertRequiredFiles(paths) {
|
|
|
341
183
|
)
|
|
342
184
|
}
|
|
343
185
|
|
|
344
|
-
async function resolveTarget(targetInput) {
|
|
345
|
-
const targetPath = path.resolve(process.cwd(), targetInput)
|
|
346
|
-
if (!(await pathExists(targetPath))) {
|
|
347
|
-
throw new Error(`Target directory '${targetInput}' does not exist or is not accessible.`)
|
|
348
|
-
}
|
|
349
|
-
return targetPath
|
|
350
|
-
}
|
|
351
|
-
|
|
352
186
|
async function assertBootstrapTargetIsEmpty(targetPath) {
|
|
353
187
|
const entries = await fs.readdir(targetPath, { withFileTypes: true })
|
|
354
188
|
const unexpectedEntries = entries
|
|
@@ -359,103 +193,56 @@ async function assertBootstrapTargetIsEmpty(targetPath) {
|
|
|
359
193
|
return
|
|
360
194
|
}
|
|
361
195
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
async function moveEntry(sourcePath, targetPath) {
|
|
368
|
-
try {
|
|
369
|
-
await fs.rename(sourcePath, targetPath)
|
|
370
|
-
} catch (error) {
|
|
371
|
-
if (error && error.code === 'EXDEV') {
|
|
372
|
-
await fs.cp(sourcePath, targetPath, { recursive: true, force: true })
|
|
373
|
-
await fs.rm(sourcePath, { recursive: true, force: true })
|
|
374
|
-
return
|
|
375
|
-
}
|
|
376
|
-
throw error
|
|
377
|
-
}
|
|
196
|
+
throw new Error(
|
|
197
|
+
`slopmachine init expects a new or empty workflow root. Found existing entries: ${unexpectedEntries.join(', ')}`,
|
|
198
|
+
)
|
|
378
199
|
}
|
|
379
200
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const taskGitDir = path.join(taskRoot, '.git')
|
|
385
|
-
if (await pathExists(sourceGitDir) && !(await pathExists(taskGitDir))) {
|
|
386
|
-
await moveEntry(sourceGitDir, taskGitDir)
|
|
201
|
+
function deriveTaskIdFromGitHubUrl(githubUrl) {
|
|
202
|
+
const trimmed = String(githubUrl || '').trim()
|
|
203
|
+
if (!trimmed) {
|
|
204
|
+
throw new Error('Missing GitHub URL')
|
|
387
205
|
}
|
|
388
206
|
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
207
|
+
const withoutTrailingSlash = trimmed.replace(/\/+$/, '')
|
|
208
|
+
const lastPathPart = withoutTrailingSlash.split('/').pop()
|
|
209
|
+
const taskId = lastPathPart ? lastPathPart.replace(/\.git$/i, '') : ''
|
|
210
|
+
if (!taskId || taskId === '.' || taskId === '..') {
|
|
211
|
+
throw new Error(`Unable to derive task root directory name from GitHub URL: ${githubUrl}`)
|
|
393
212
|
}
|
|
394
213
|
|
|
395
|
-
|
|
396
|
-
const namesToMove = entries
|
|
397
|
-
.map((entry) => entry.name)
|
|
398
|
-
.filter((name) => !ADOPTION_ROOT_KEEP.has(name))
|
|
399
|
-
|
|
400
|
-
if (namesToMove.length === 0) {
|
|
401
|
-
return { movedEntries: [], adoptedGitignore: false }
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const repoPath = path.join(taskRoot, 'repo')
|
|
405
|
-
await ensureDir(repoPath)
|
|
406
|
-
|
|
407
|
-
let adoptedGitignore = false
|
|
408
|
-
const rootGitignorePath = path.join(workflowRoot, '.gitignore')
|
|
409
|
-
if (await pathExists(rootGitignorePath)) {
|
|
410
|
-
const repoGitignorePath = path.join(repoPath, '.gitignore')
|
|
411
|
-
if (!(await pathExists(repoGitignorePath))) {
|
|
412
|
-
await moveEntry(rootGitignorePath, repoGitignorePath)
|
|
413
|
-
adoptedGitignore = true
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const movedEntries = []
|
|
418
|
-
for (const name of namesToMove) {
|
|
419
|
-
const sourcePath = path.join(workflowRoot, name)
|
|
420
|
-
const destinationPath = path.join(repoPath, name)
|
|
421
|
-
await moveEntry(sourcePath, destinationPath)
|
|
422
|
-
movedEntries.push(name)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return { movedEntries, adoptedGitignore }
|
|
214
|
+
return taskId
|
|
426
215
|
}
|
|
427
216
|
|
|
428
|
-
async function
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
log('Git repository already initialized')
|
|
432
|
-
return
|
|
217
|
+
async function cloneTaskRepository(githubUrl, taskRoot) {
|
|
218
|
+
if (await pathExists(taskRoot)) {
|
|
219
|
+
throw new Error(`Task root already exists: ${taskRoot}`)
|
|
433
220
|
}
|
|
434
221
|
|
|
435
|
-
log(
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
throw new Error('Git
|
|
222
|
+
log(`Cloning task repository into ${path.basename(taskRoot)}/`)
|
|
223
|
+
const result = await runCommand('git', ['clone', githubUrl, taskRoot], { stdio: 'inherit' })
|
|
224
|
+
if (result.code !== 0) {
|
|
225
|
+
throw new Error('Git clone failed. Check the GitHub URL and local git authentication, then retry.')
|
|
439
226
|
}
|
|
440
227
|
}
|
|
441
228
|
|
|
442
|
-
async function
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
229
|
+
async function assertClonedTaskStructure(taskRoot) {
|
|
230
|
+
const requiredPaths = [
|
|
231
|
+
'metadata.json',
|
|
232
|
+
'docs',
|
|
233
|
+
'.tmp',
|
|
234
|
+
'repo',
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
const missing = []
|
|
238
|
+
for (const relativePath of requiredPaths) {
|
|
239
|
+
if (!(await pathExists(path.join(taskRoot, relativePath)))) {
|
|
240
|
+
missing.push(relativePath)
|
|
453
241
|
}
|
|
454
242
|
}
|
|
455
243
|
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
await fs.appendFile(gitignorePath, `${prefix}${linesToAppend.join('\n')}\n`, 'utf8')
|
|
244
|
+
if (missing.length > 0) {
|
|
245
|
+
throw new Error(`Cloned task repository is missing required task-root paths: ${missing.join(', ')}`)
|
|
459
246
|
}
|
|
460
247
|
}
|
|
461
248
|
|
|
@@ -491,147 +278,47 @@ async function writeFileIfMissing(filePath, content) {
|
|
|
491
278
|
return true
|
|
492
279
|
}
|
|
493
280
|
|
|
494
|
-
function
|
|
495
|
-
return typeof value === 'string' ? value : ''
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function buildProjectMetadata(existingMetadata) {
|
|
499
|
-
const metadata = existingMetadata && typeof existingMetadata === 'object' && !Array.isArray(existingMetadata)
|
|
500
|
-
? existingMetadata
|
|
501
|
-
: {}
|
|
502
|
-
const projectType = normalizeProjectMetadataString(metadata.project_type).trim().toLowerCase()
|
|
503
|
-
|
|
504
|
-
return {
|
|
505
|
-
prompt: normalizeProjectMetadataString(metadata.prompt),
|
|
506
|
-
project_type: VALID_PROJECT_TYPES.has(projectType) ? projectType : '',
|
|
507
|
-
frontend_language: normalizeProjectMetadataString(metadata.frontend_language),
|
|
508
|
-
backend_language: normalizeProjectMetadataString(metadata.backend_language),
|
|
509
|
-
database: normalizeProjectMetadataString(metadata.database),
|
|
510
|
-
frontend_framework: normalizeProjectMetadataString(metadata.frontend_framework),
|
|
511
|
-
backend_framework: normalizeProjectMetadataString(metadata.backend_framework),
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function projectMetadataMatchesSchema(existingMetadata, normalizedMetadata) {
|
|
516
|
-
if (!existingMetadata || typeof existingMetadata !== 'object' || Array.isArray(existingMetadata)) {
|
|
517
|
-
return false
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const expectedKeys = Object.keys(normalizedMetadata)
|
|
521
|
-
const existingKeys = Object.keys(existingMetadata)
|
|
522
|
-
|
|
523
|
-
return existingKeys.length === expectedKeys.length
|
|
524
|
-
&& expectedKeys.every((key) => existingMetadata[key] === normalizedMetadata[key])
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
async function ensureProjectMetadata(projectMetadataPath) {
|
|
528
|
-
const existed = await pathExists(projectMetadataPath)
|
|
529
|
-
const existingMetadata = await readJsonIfExists(projectMetadataPath)
|
|
530
|
-
const normalizedMetadata = buildProjectMetadata(existingMetadata)
|
|
531
|
-
|
|
532
|
-
if (projectMetadataMatchesSchema(existingMetadata, normalizedMetadata)) {
|
|
533
|
-
return
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
log(existed ? 'Normalizing task metadata.json' : 'Creating task metadata.json')
|
|
537
|
-
await writeJson(projectMetadataPath, normalizedMetadata)
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
async function createInitialPhaseArtifacts(taskRoot, workflowRoot, runtimeAssetRoot, options) {
|
|
541
|
-
const questionsContent = `# Questions\n\n` +
|
|
542
|
-
`This file records only genuine original-prompt ambiguities that needed interpretation because they were unclear, incomplete, contradictory, or materially ambiguous.\n\n` +
|
|
543
|
-
`Use this structure for each real clarification item:\n\n` +
|
|
544
|
-
`### 1. <short clarification title>\n` +
|
|
545
|
-
`- Question: <the exact ambiguity or missing detail that needed to be locked>\n` +
|
|
546
|
-
`- My Understanding: <how the prompt was interpreted, why this was ambiguous, and why it matters>\n` +
|
|
547
|
-
`- Solution: <the chosen prompt-faithful resolution or safe default, written decisively>\n`
|
|
548
|
-
const designContent = await fs.readFile(
|
|
549
|
-
path.join(runtimeAssetRoot, 'phase-1-design-template.md'),
|
|
550
|
-
'utf8',
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
const apiSpecContent = `# API Spec\n\n` +
|
|
554
|
-
`Use this owner-maintained external document when API or interface contracts are material to the project.\n\n` +
|
|
555
|
-
`If the project has no meaningful API surface, mark this file as not applicable rather than deleting it silently.\n`
|
|
556
|
-
|
|
557
|
-
const repoReadmeContent = await fs.readFile(
|
|
558
|
-
path.join(runtimeAssetRoot, 'exact-readme-template.md'),
|
|
559
|
-
'utf8',
|
|
560
|
-
)
|
|
561
|
-
|
|
281
|
+
async function createOwnerPrivateArtifacts(workflowRoot, options) {
|
|
562
282
|
const startupContextContent = `# Startup Context\n\n` +
|
|
563
|
-
`- Bootstrap mode:
|
|
283
|
+
`- Bootstrap mode: github-clone\n` +
|
|
564
284
|
`${options.requestedStartPhase ? `- Requested start phase: ${options.requestedStartPhase}\n` : ''}` +
|
|
565
|
-
|
|
285
|
+
`- The cloned task root may contain placeholder developer-facing docs or repo content. The implementation lane should inspect and clear unrelated placeholders before relying on them.\n` +
|
|
566
286
|
`- If the user sends a prompt block followed by appended stack/context lines, keep only the prompt block in ./metadata.json under prompt and record the appended stack/context lines separately here.\n`
|
|
567
287
|
|
|
568
|
-
await writeFileIfMissing(path.join(taskRoot, 'docs', 'questions.md'), questionsContent)
|
|
569
|
-
await writeFileIfMissing(path.join(taskRoot, 'docs', 'design.md'), designContent)
|
|
570
|
-
await writeFileIfMissing(path.join(taskRoot, 'docs', 'api-spec.md'), apiSpecContent)
|
|
571
288
|
await writeFileIfMissing(path.join(workflowRoot, '.ai', 'startup-context.md'), startupContextContent)
|
|
572
289
|
await writeFileIfMissing(path.join(workflowRoot, '.ai', 'plan.md'), '# Owner Private Plan\n\nThis owner-private plan is the internal execution checklist. Do not expose it to Claude as an instruction file; translate each slice into normal human prompts.\n')
|
|
573
290
|
await writeFileIfMissing(path.join(workflowRoot, '.ai', 'requirements-breakdown.md'), '# Requirements Breakdown\n\nOwner-private requirement extraction goes here before design, API spec, or planning.\n')
|
|
574
291
|
await writeFileIfMissing(path.join(workflowRoot, '.ai', 'test-coverage.md'), '# Test Coverage Plan\n\nOwner-private coverage planning and audit notes go here when applicable.\n')
|
|
575
|
-
await writeFileIfMissing(path.join(taskRoot, 'repo', 'README.md'), repoReadmeContent)
|
|
576
292
|
}
|
|
577
293
|
|
|
578
|
-
async function
|
|
294
|
+
async function createWorkflowStructure(workflowRoot, taskRoot, claudeTemplate, options) {
|
|
579
295
|
const initialPhase = options.requestedStartPhase ? PHASE_LABELS[options.requestedStartPhase] : 'Phase 1 Clarification'
|
|
580
|
-
const developerRulebookFile = options.developerRulebookFile || '
|
|
296
|
+
const developerRulebookFile = options.developerRulebookFile || 'CLAUDE.md'
|
|
297
|
+
const taskId = options.taskId
|
|
581
298
|
|
|
582
|
-
log('Creating
|
|
583
|
-
await ensureDir(path.join(taskRoot, '.tmp'))
|
|
584
|
-
await ensureDir(path.join(taskRoot, 'docs'))
|
|
299
|
+
log('Creating workflow directories')
|
|
585
300
|
await ensureDir(path.join(workflowRoot, '.ai', 'claude-live'))
|
|
586
301
|
await ensureDir(path.join(workflowRoot, '.ai', 'worktrees'))
|
|
587
302
|
|
|
588
|
-
log('Creating repo/ working directory')
|
|
589
|
-
await ensureDir(path.join(taskRoot, 'repo'))
|
|
590
|
-
const repoAgentsPath = path.join(taskRoot, 'AGENTS.md')
|
|
591
303
|
const repoClaudePath = path.join(taskRoot, 'CLAUDE.md')
|
|
592
304
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
await fs.copyFile(repoAgentsPath, path.join(workflowRoot, '.ai', 'original-task-AGENTS.md'))
|
|
597
|
-
}
|
|
598
|
-
await fs.copyFile(agentsTemplate, repoAgentsPath)
|
|
599
|
-
await fs.rm(repoClaudePath, { force: true })
|
|
600
|
-
await fs.rm(path.join(taskRoot, '.claude'), { recursive: true, force: true })
|
|
601
|
-
} else {
|
|
602
|
-
log('Copying CLAUDE template into task/')
|
|
603
|
-
if (await pathExists(repoClaudePath)) {
|
|
604
|
-
await fs.copyFile(repoClaudePath, path.join(workflowRoot, '.ai', 'original-task-CLAUDE.md'))
|
|
605
|
-
}
|
|
606
|
-
await fs.copyFile(claudeTemplate, repoClaudePath)
|
|
607
|
-
await fs.rm(repoAgentsPath, { force: true })
|
|
608
|
-
await ensureDir(path.join(taskRoot, '.claude'))
|
|
609
|
-
await writeFileIfMissing(
|
|
610
|
-
path.join(taskRoot, '.claude', 'settings.json'),
|
|
611
|
-
`${JSON.stringify({
|
|
612
|
-
$schema: 'https://json.schemastore.org/claude-code-settings.json',
|
|
613
|
-
agent: 'developer',
|
|
614
|
-
env: {
|
|
615
|
-
CLAUDE_CODE_SUBAGENT_MODEL: 'sonnet',
|
|
616
|
-
},
|
|
617
|
-
}, null, 2)}\n`,
|
|
618
|
-
)
|
|
305
|
+
log(`Copying CLAUDE template into ${taskId}/`)
|
|
306
|
+
if (await pathExists(repoClaudePath)) {
|
|
307
|
+
await fs.copyFile(repoClaudePath, path.join(workflowRoot, '.ai', 'original-task-CLAUDE.md'))
|
|
619
308
|
}
|
|
620
|
-
|
|
621
|
-
const projectMetadataPath = path.join(taskRoot, 'metadata.json')
|
|
622
|
-
await ensureProjectMetadata(projectMetadataPath)
|
|
309
|
+
await fs.copyFile(claudeTemplate, repoClaudePath)
|
|
623
310
|
|
|
624
311
|
const workflowMetadataPath = path.join(workflowRoot, '.ai', 'metadata.json')
|
|
625
312
|
if (!(await pathExists(workflowMetadataPath))) {
|
|
626
313
|
log('Creating .ai/metadata.json')
|
|
627
314
|
await fs.writeFile(workflowMetadataPath, `${JSON.stringify({
|
|
628
|
-
run_id:
|
|
315
|
+
run_id: taskId,
|
|
629
316
|
current_phase: initialPhase,
|
|
630
317
|
awaiting_human: false,
|
|
631
318
|
evaluation_prompt_kind: null,
|
|
632
319
|
active_evaluator_session_id: null,
|
|
633
|
-
task_root:
|
|
634
|
-
evaluation_reports_root:
|
|
320
|
+
task_root: taskId,
|
|
321
|
+
evaluation_reports_root: `${taskId}/.tmp`,
|
|
635
322
|
developer_sessions: [],
|
|
636
323
|
developer_rulebook_file: developerRulebookFile,
|
|
637
324
|
current_developer_lane: null,
|
|
@@ -642,40 +329,13 @@ async function createRepoStructure(workflowRoot, taskRoot, runtimeAssetRoot, age
|
|
|
642
329
|
next_bugfix_session_number: 1,
|
|
643
330
|
packaging_completed: false,
|
|
644
331
|
claude_live_root: '.ai/claude-live',
|
|
645
|
-
bootstrap_mode:
|
|
332
|
+
bootstrap_mode: 'github-clone',
|
|
333
|
+
github_url: options.githubUrl,
|
|
646
334
|
requested_start_phase: options.requestedStartPhase,
|
|
647
335
|
}, null, 2)}\n`, 'utf8')
|
|
648
336
|
}
|
|
649
337
|
|
|
650
|
-
await
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
async function createInitialCommit(targetPath) {
|
|
654
|
-
log('Creating initial git checkpoint')
|
|
655
|
-
const existingInitialCommitPaths = []
|
|
656
|
-
for (const relativePath of INITIAL_COMMIT_PATHS) {
|
|
657
|
-
if (await pathExists(path.join(targetPath, relativePath))) {
|
|
658
|
-
existingInitialCommitPaths.push(relativePath)
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
const addResult = await runCommand('git', ['-C', targetPath, 'add', '--', ...existingInitialCommitPaths], { stdio: 'inherit' })
|
|
662
|
-
if (addResult.code !== 0) {
|
|
663
|
-
throw new Error('Initial git add failed')
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const commitResult = await runCommand('git', ['-C', targetPath, 'commit', '-m', 'init'], { stdio: 'inherit' })
|
|
667
|
-
if (commitResult.code !== 0) {
|
|
668
|
-
throw new Error('Initial git commit failed. Configure git user.name/user.email or inspect repository state and retry.')
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const statusResult = await runCommand('git', ['-C', targetPath, 'status', '--short'])
|
|
672
|
-
if (statusResult.code !== 0) {
|
|
673
|
-
throw new Error('Unable to verify the git worktree after bootstrap commit.')
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (statusResult.stdout.trim()) {
|
|
677
|
-
throw new Error(`Bootstrap left the repository dirty after the initial commit:\n${statusResult.stdout.trim()}`)
|
|
678
|
-
}
|
|
338
|
+
await createOwnerPrivateArtifacts(workflowRoot, options)
|
|
679
339
|
}
|
|
680
340
|
|
|
681
341
|
async function maybeOpenOpencode(taskRoot, openAfterInit) {
|
|
@@ -683,7 +343,9 @@ async function maybeOpenOpencode(taskRoot, openAfterInit) {
|
|
|
683
343
|
return
|
|
684
344
|
}
|
|
685
345
|
|
|
686
|
-
|
|
346
|
+
const taskDirName = path.basename(taskRoot)
|
|
347
|
+
|
|
348
|
+
log(`Opening OpenCode in ${taskDirName}/`)
|
|
687
349
|
let result
|
|
688
350
|
|
|
689
351
|
if (process.platform === 'win32') {
|
|
@@ -697,7 +359,7 @@ async function maybeOpenOpencode(taskRoot, openAfterInit) {
|
|
|
697
359
|
: await resolveCommand('zsh') || await resolveCommand('bash') || await resolveCommand('sh')
|
|
698
360
|
|
|
699
361
|
if (!shellPath) {
|
|
700
|
-
warn(
|
|
362
|
+
warn(`No usable shell was found to launch OpenCode automatically. Launch OpenCode manually inside ${taskDirName}/.`)
|
|
701
363
|
return
|
|
702
364
|
}
|
|
703
365
|
|
|
@@ -708,7 +370,7 @@ async function maybeOpenOpencode(taskRoot, openAfterInit) {
|
|
|
708
370
|
}
|
|
709
371
|
|
|
710
372
|
if (result.code !== 0) {
|
|
711
|
-
warn(`Failed to launch OpenCode automatically (${result.stderr || `exit code ${result.code}`}). Launch it manually inside
|
|
373
|
+
warn(`Failed to launch OpenCode automatically (${result.stderr || `exit code ${result.code}`}). Launch it manually inside ${taskDirName}/.`)
|
|
712
374
|
}
|
|
713
375
|
}
|
|
714
376
|
|
|
@@ -716,53 +378,27 @@ export async function runInit(args = []) {
|
|
|
716
378
|
assertSupportedPlatform()
|
|
717
379
|
|
|
718
380
|
const paths = buildPaths()
|
|
719
|
-
const { openAfterInit,
|
|
381
|
+
const { openAfterInit, githubUrl, requestedStartPhase: rawRequestedStartPhase, developerRulebookFile } = parseInitArgs(args)
|
|
720
382
|
const requestedStartPhase = normalizeRequestedStartPhase(rawRequestedStartPhase)
|
|
721
|
-
if (requestedStartPhase && requestedStartPhase !== 'P1' && !adoptExisting && !continueFromExisting) {
|
|
722
|
-
throw new Error(`Later start phase '${requestedStartPhase}' requires --adopt or --continue-from because later-phase entry is only valid for existing-project recovery.`)
|
|
723
|
-
}
|
|
724
383
|
|
|
725
|
-
const {
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const taskRoot = path.join(workflowRoot,
|
|
384
|
+
const { trackerScript, claudeTemplate } = await assertRequiredFiles(paths)
|
|
385
|
+
const workflowRoot = process.cwd()
|
|
386
|
+
const taskId = deriveTaskIdFromGitHubUrl(githubUrl)
|
|
387
|
+
const taskRoot = path.join(workflowRoot, taskId)
|
|
729
388
|
|
|
389
|
+
log(`Task id: ${taskId}`)
|
|
390
|
+
log(`GitHub URL: ${githubUrl}`)
|
|
730
391
|
log(`Workflow root: ${workflowRoot}`)
|
|
731
392
|
log(`Task root: ${taskRoot}`)
|
|
732
393
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (adoptExisting) {
|
|
738
|
-
const adoptionSummary = await prepareExistingProjectForAdoption(workflowRoot, taskRoot)
|
|
739
|
-
if (adoptionSummary.movedEntries.length > 0) {
|
|
740
|
-
log(`Adopted existing project into repo/: ${adoptionSummary.movedEntries.join(', ')}`)
|
|
741
|
-
} else {
|
|
742
|
-
log('Adoption mode enabled: no project files needed to be moved into repo/')
|
|
743
|
-
}
|
|
744
|
-
if (adoptionSummary.adoptedGitignore) {
|
|
745
|
-
log('Moved the existing project .gitignore into repo/.gitignore')
|
|
746
|
-
}
|
|
747
|
-
} else {
|
|
748
|
-
await assertBootstrapTargetIsEmpty(workflowRoot)
|
|
749
|
-
}
|
|
394
|
+
await assertBootstrapTargetIsEmpty(workflowRoot)
|
|
395
|
+
await cloneTaskRepository(githubUrl, taskRoot)
|
|
396
|
+
await assertClonedTaskStructure(taskRoot)
|
|
750
397
|
|
|
751
398
|
log('Ensuring .ai/artifacts exists')
|
|
752
399
|
await ensureDir(path.join(workflowRoot, '.ai', 'artifacts'))
|
|
753
|
-
await ensureDir(taskRoot)
|
|
754
|
-
|
|
755
|
-
await ensureGitRepo(taskRoot)
|
|
756
|
-
log('Ensuring .gitignore defaults')
|
|
757
|
-
await ensureGitignore(taskRoot)
|
|
758
400
|
await runWorkflowBootstrap(paths, workflowRoot, trackerScript, { requestedStartPhase })
|
|
759
|
-
await
|
|
760
|
-
|
|
761
|
-
if (!adoptExisting) {
|
|
762
|
-
await createInitialCommit(taskRoot)
|
|
763
|
-
} else {
|
|
764
|
-
log('Skipping automatic bootstrap commit because this workspace adopted an existing project')
|
|
765
|
-
}
|
|
401
|
+
await createWorkflowStructure(workflowRoot, taskRoot, claudeTemplate, { githubUrl, requestedStartPhase, developerRulebookFile, taskId })
|
|
766
402
|
|
|
767
403
|
await maybeOpenOpencode(taskRoot, openAfterInit)
|
|
768
404
|
|
package/src/install.js
CHANGED
|
@@ -150,7 +150,6 @@ async function writeOpencodeConfig(filePath, value) {
|
|
|
150
150
|
const LEGACY_SUFFIX_PATTERN = /-(v2|v3)(\.md)?$/
|
|
151
151
|
const OBSOLETE_PACKAGE_ASSET_FILES = [
|
|
152
152
|
'01-phase-1-design-prompt.md',
|
|
153
|
-
'02-phase-1-design-template.md',
|
|
154
153
|
'03-phase-2-execution-planning-prompt.md',
|
|
155
154
|
'04-phase-2-plan-template.md',
|
|
156
155
|
'06-owner-verification-checklist.md',
|
|
@@ -1532,7 +1531,7 @@ export async function runInstall() {
|
|
|
1532
1531
|
console.log('\nNext steps')
|
|
1533
1532
|
console.log('- Review any warnings above for skipped files or missing external dependencies.')
|
|
1534
1533
|
console.log('- If Docker was reported as stopped, start Docker before using theslopmachine on a real project.')
|
|
1535
|
-
console.log('- Run `slopmachine init
|
|
1534
|
+
console.log('- Run `slopmachine init <github-url>` inside an empty workflow root to clone and bootstrap a task workspace.')
|
|
1536
1535
|
}
|
|
1537
1536
|
|
|
1538
1537
|
export async function runCleanup(args = []) {
|
package/src/send-data.js
CHANGED
|
@@ -119,12 +119,18 @@ async function resolveWorkflowPaths() {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const
|
|
123
|
-
if (await pathExists(
|
|
124
|
-
|
|
122
|
+
const cwdWorkflowMetadata = path.join(cwd, '.ai', 'metadata.json')
|
|
123
|
+
if (await pathExists(cwdWorkflowMetadata)) {
|
|
124
|
+
const workflowMetadata = await readJsonIfExists(cwdWorkflowMetadata)
|
|
125
|
+
if (workflowMetadata && typeof workflowMetadata.task_root === 'string') {
|
|
126
|
+
const taskDir = path.join(cwd, workflowMetadata.task_root)
|
|
127
|
+
if (await pathExists(path.join(taskDir, 'metadata.json'))) {
|
|
128
|
+
return { workflowRoot: cwd, taskRoot: taskDir, repoPath: path.join(taskDir, 'repo') }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
throw new Error('slopmachine send-data
|
|
133
|
+
throw new Error('slopmachine send-data could not find the task root. Run from a task root directory (containing metadata.json and repo/), or from a workflow root (containing .ai/metadata.json).')
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
async function loadWorkflowMetadata(workflowRoot) {
|