@unimatrix27/ralph-harness 1.0.0

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.
Files changed (103) hide show
  1. package/CONTRIBUTING.md +89 -0
  2. package/README.md +401 -0
  3. package/dist/bin/ralph-bootstrap-aws.d.ts +3 -0
  4. package/dist/bin/ralph-bootstrap-aws.d.ts.map +1 -0
  5. package/dist/bin/ralph-bootstrap-aws.js +43 -0
  6. package/dist/bin/ralph-bootstrap-aws.js.map +1 -0
  7. package/dist/bin/ralph-fire.d.ts +3 -0
  8. package/dist/bin/ralph-fire.d.ts.map +1 -0
  9. package/dist/bin/ralph-fire.js +59 -0
  10. package/dist/bin/ralph-fire.js.map +1 -0
  11. package/dist/bin/ralph-gsm.d.ts +3 -0
  12. package/dist/bin/ralph-gsm.d.ts.map +1 -0
  13. package/dist/bin/ralph-gsm.js +93 -0
  14. package/dist/bin/ralph-gsm.js.map +1 -0
  15. package/dist/bin/ralph-orchestrate.d.ts +3 -0
  16. package/dist/bin/ralph-orchestrate.d.ts.map +1 -0
  17. package/dist/bin/ralph-orchestrate.js +20 -0
  18. package/dist/bin/ralph-orchestrate.js.map +1 -0
  19. package/dist/bin/ralph-sync-credential.d.ts +3 -0
  20. package/dist/bin/ralph-sync-credential.d.ts.map +1 -0
  21. package/dist/bin/ralph-sync-credential.js +44 -0
  22. package/dist/bin/ralph-sync-credential.js.map +1 -0
  23. package/dist/bin/ralph-sync-github-pat.d.ts +3 -0
  24. package/dist/bin/ralph-sync-github-pat.d.ts.map +1 -0
  25. package/dist/bin/ralph-sync-github-pat.js +93 -0
  26. package/dist/bin/ralph-sync-github-pat.js.map +1 -0
  27. package/dist/bin/ralph-tail-logs.d.ts +3 -0
  28. package/dist/bin/ralph-tail-logs.d.ts.map +1 -0
  29. package/dist/bin/ralph-tail-logs.js +72 -0
  30. package/dist/bin/ralph-tail-logs.js.map +1 -0
  31. package/dist/bin/ralph-validate-config.d.ts +3 -0
  32. package/dist/bin/ralph-validate-config.d.ts.map +1 -0
  33. package/dist/bin/ralph-validate-config.js +41 -0
  34. package/dist/bin/ralph-validate-config.js.map +1 -0
  35. package/dist/lib/aws-bootstrap.d.ts +53 -0
  36. package/dist/lib/aws-bootstrap.d.ts.map +1 -0
  37. package/dist/lib/aws-bootstrap.js +438 -0
  38. package/dist/lib/aws-bootstrap.js.map +1 -0
  39. package/dist/lib/aws-clients.d.ts +17 -0
  40. package/dist/lib/aws-clients.d.ts.map +1 -0
  41. package/dist/lib/aws-clients.js +25 -0
  42. package/dist/lib/aws-clients.js.map +1 -0
  43. package/dist/lib/claude-runner.d.ts +21 -0
  44. package/dist/lib/claude-runner.d.ts.map +1 -0
  45. package/dist/lib/claude-runner.js +101 -0
  46. package/dist/lib/claude-runner.js.map +1 -0
  47. package/dist/lib/credential-syncer.d.ts +27 -0
  48. package/dist/lib/credential-syncer.d.ts.map +1 -0
  49. package/dist/lib/credential-syncer.js +116 -0
  50. package/dist/lib/credential-syncer.js.map +1 -0
  51. package/dist/lib/ec2-orchestrator.d.ts +38 -0
  52. package/dist/lib/ec2-orchestrator.d.ts.map +1 -0
  53. package/dist/lib/ec2-orchestrator.js +469 -0
  54. package/dist/lib/ec2-orchestrator.js.map +1 -0
  55. package/dist/lib/env-loader.d.ts +18 -0
  56. package/dist/lib/env-loader.d.ts.map +1 -0
  57. package/dist/lib/env-loader.js +120 -0
  58. package/dist/lib/env-loader.js.map +1 -0
  59. package/dist/lib/fire-launcher.d.ts +59 -0
  60. package/dist/lib/fire-launcher.d.ts.map +1 -0
  61. package/dist/lib/fire-launcher.js +320 -0
  62. package/dist/lib/fire-launcher.js.map +1 -0
  63. package/dist/lib/gh-runner.d.ts +13 -0
  64. package/dist/lib/gh-runner.d.ts.map +1 -0
  65. package/dist/lib/gh-runner.js +50 -0
  66. package/dist/lib/gh-runner.js.map +1 -0
  67. package/dist/lib/github-state-mutator.d.ts +11 -0
  68. package/dist/lib/github-state-mutator.d.ts.map +1 -0
  69. package/dist/lib/github-state-mutator.js +179 -0
  70. package/dist/lib/github-state-mutator.js.map +1 -0
  71. package/dist/lib/phase-result-schemas.d.ts +88 -0
  72. package/dist/lib/phase-result-schemas.d.ts.map +1 -0
  73. package/dist/lib/phase-result-schemas.js +180 -0
  74. package/dist/lib/phase-result-schemas.js.map +1 -0
  75. package/dist/lib/post-hoc-agent-stuck-checker.d.ts +26 -0
  76. package/dist/lib/post-hoc-agent-stuck-checker.d.ts.map +1 -0
  77. package/dist/lib/post-hoc-agent-stuck-checker.js +142 -0
  78. package/dist/lib/post-hoc-agent-stuck-checker.js.map +1 -0
  79. package/dist/lib/prompt-renderer.d.ts +4 -0
  80. package/dist/lib/prompt-renderer.d.ts.map +1 -0
  81. package/dist/lib/prompt-renderer.js +30 -0
  82. package/dist/lib/prompt-renderer.js.map +1 -0
  83. package/dist/lib/security-runner.d.ts +7 -0
  84. package/dist/lib/security-runner.d.ts.map +1 -0
  85. package/dist/lib/security-runner.js +53 -0
  86. package/dist/lib/security-runner.js.map +1 -0
  87. package/dist/lib/structured-log-emitter.d.ts +53 -0
  88. package/dist/lib/structured-log-emitter.d.ts.map +1 -0
  89. package/dist/lib/structured-log-emitter.js +122 -0
  90. package/dist/lib/structured-log-emitter.js.map +1 -0
  91. package/dist/lib/target-config-schema.d.ts +28 -0
  92. package/dist/lib/target-config-schema.d.ts.map +1 -0
  93. package/dist/lib/target-config-schema.js +157 -0
  94. package/dist/lib/target-config-schema.js.map +1 -0
  95. package/dist/lib/user-data-renderer.d.ts +20 -0
  96. package/dist/lib/user-data-renderer.d.ts.map +1 -0
  97. package/dist/lib/user-data-renderer.js +75 -0
  98. package/dist/lib/user-data-renderer.js.map +1 -0
  99. package/lib/cloud-init/system-setup.sh +338 -0
  100. package/package.json +55 -0
  101. package/prompts/discovery.md +182 -0
  102. package/prompts/implementation.md +161 -0
  103. package/prompts/review.md +135 -0
@@ -0,0 +1,89 @@
1
+ # Contributing to ralph-harness
2
+
3
+ ## Public-safe contract (read this first)
4
+
5
+ **No target-specific identifiers in this repository, ever.** This includes:
6
+
7
+ - organization or company names
8
+ - repository names of any specific target
9
+ - product, service, brand, or domain-specific terms tied to a particular target
10
+ - usernames or email addresses tied to a specific target's people or bots
11
+ - specific URLs, hostnames, or AWS account IDs
12
+ - copy-pasted code, prompts, or fixtures that name a specific target
13
+
14
+ The harness is generic by design: every target-specific knob is supplied at
15
+ runtime through the target repository's `.ralph/config.yaml` (see
16
+ [`docs/config-schema.md`](docs/config-schema.md)) and through environment
17
+ variables / SSM parameters. If you find yourself needing to commit something
18
+ target-specific to make a change land — stop and reshape the change so the
19
+ target detail moves into config instead.
20
+
21
+ PR reviewers must reject any change that introduces a target-specific
22
+ identifier. There is no automated check yet; this is enforced by review and by
23
+ the rule above. If you spot an existing leak, open an issue or PR to remove it.
24
+
25
+ ## Repository layout
26
+
27
+ ```
28
+ bin/ user-facing CLI entry points
29
+ lib/ sourceable bash modules
30
+ docs/ schema and design docs
31
+ tests/ bats-core tests
32
+ tests/fixtures/ yaml fixtures for the tests
33
+ .ralph/ (target repos only — not used by the harness itself)
34
+ ```
35
+
36
+ ## Development dependencies
37
+
38
+ - `bash` 4+ (or recent macOS bash; the modules assume `[[`-style conditionals)
39
+ - [`yq`](https://github.com/mikefarah/yq) v4 (the Go binary by mikefarah)
40
+ - `jq` (used by `gh --jq` in the github-state-mutator module)
41
+ - `gh` (GitHub CLI, authenticated for any target repo you mutate manually)
42
+ - `aws` CLI v2 (only needed for `ralph-tail-logs`; tests use a stub. Note: `ralph-bootstrap-aws` and `ralph-sync-credential` use the AWS SDK directly and do not require the `aws` CLI to be installed.)
43
+ - [`bats-core`](https://github.com/bats-core/bats-core) for tests
44
+
45
+ On macOS:
46
+
47
+ ```sh
48
+ brew install yq jq gh bats-core awscli
49
+ ```
50
+
51
+ On Debian/Ubuntu:
52
+
53
+ ```sh
54
+ sudo apt-get install bats jq gh
55
+ # yq via the Go release tarball or `snap install yq`
56
+ # awscli v2 via the official installer at https://aws.amazon.com/cli/
57
+ ```
58
+
59
+ ## Running the tests
60
+
61
+ ```sh
62
+ bats tests/
63
+ ```
64
+
65
+ All tests must pass before opening a PR.
66
+
67
+ ## Style
68
+
69
+ - Bash modules: `set -euo pipefail` only in scripts under `bin/`, never in
70
+ files under `lib/` (a sourced library must not change the caller's shell
71
+ options).
72
+ - Module-private helpers are named `tcs::__name`; public functions are
73
+ `tcs::name` (replace `tcs` with the module's prefix).
74
+ - Errors go to stderr via the module's `__err` helper and are prefixed with the
75
+ module name so users can tell where a failure came from.
76
+ - Exit codes are documented at the top of each module so callers can branch on
77
+ them.
78
+
79
+ ## Adding a new module
80
+
81
+ 1. Add `lib/<module>.sh` with a header comment that documents:
82
+ - what the module does in one paragraph
83
+ - the public function signatures
84
+ - the exit-code contract
85
+ - external dependencies
86
+ 2. Add a `bin/` CLI wrapper if the module is useful from the command line.
87
+ 3. Add bats tests under `tests/` and fixtures under `tests/fixtures/`.
88
+ 4. Update `README.md` and the relevant doc under `docs/` if the module adds
89
+ user-visible surface area.
package/README.md ADDED
@@ -0,0 +1,401 @@
1
+ # ralph-harness
2
+
3
+ Throwaway-EC2 loop that picks one `ready-for-agent` GitHub issue, implements it
4
+ on a fresh branch, opens a PR, and incorporates one auto-review pass — then
5
+ terminates.
6
+
7
+ Generic. Target repo + target-specific config supplied at runtime.
8
+
9
+ Status: iteration 1, in progress (single-fire from laptop). See issue #1 for
10
+ the PRD and the open issues for the slice plan.
11
+
12
+ ## Out of scope (iteration 1)
13
+
14
+ Iteration 1 is single-fire-from-laptop and intentionally minimal. The
15
+ following are deferred to later iterations:
16
+
17
+ - **Iteration 2 — management EC2.** A long-lived management box that
18
+ schedules `fire.sh` itself on a cron so the laptop stops being in the
19
+ loop. Reuses the slice 5 launcher and the slice 6 cloud-init module
20
+ unchanged.
21
+ - **Iteration 2 — Lambda janitor.** Out-of-band sweeper that
22
+ force-terminates any `Project=ralph` EC2 still running past
23
+ `MaxLifetimeMin`, defending against a launcher process killed before its
24
+ ceiling fires.
25
+ - **Iteration 3 — custom AMI.** Bake the dependency install (Node, .NET,
26
+ `gh`, Docker, `uv`, `claude`, `jq`, `yq`) so cold-start drops from minutes
27
+ to seconds. Iteration 1 installs at boot via `dotnet-install.sh` etc.
28
+ - **Iteration 3 — spot + ARM (`t4g`).** Iteration 1 fires
29
+ `m7a.xlarge` on-demand for predictability under the wall-clock backstop.
30
+ - **Iteration 3 — multi-target dispatch.** One run, one target. Routing
31
+ across multiple target repos is deferred.
32
+ - **Iteration 3 — observability dashboard.** Iteration 1 ships
33
+ per-instance CloudWatch streams plus the milestone-log GitHub issue
34
+ (caveman-format). A grafana/CW-dashboard view is later.
35
+
36
+ ## Architecture: iteration-2-extensible
37
+
38
+ The slice 5 launcher (`lib/fire-launcher.sh`) and slice 6 cloud-init module
39
+ (`lib/cloud-init/bootstrap.sh`) are written so the iteration-2 management
40
+ EC2 can call `fire::run` exactly the way the laptop does today. No core
41
+ script needs a rewrite to move the trigger off the laptop — the management
42
+ box just needs the same env (`RALPH_TARGET_REPO`, IAM permissions for the
43
+ launcher) and a cron entry. See [`docs/smoke-test.md`](docs/smoke-test.md)
44
+ for the manual end-to-end runbook iteration 2 will automate.
45
+
46
+ ## OAuth-vs-API-key migration footnote
47
+
48
+ Iteration 1 ships **OAuth-via-Keychain**. The EC2 worker authenticates to
49
+ Claude using the OAuth credential synced from the operator's macOS Keychain
50
+ into SSM (slice 4). Switching to a long-lived **API key** is a one-file
51
+ change in `lib/cloud-init/bootstrap.sh` (the `boot__fetch_secrets` step):
52
+ swap the SSM key, drop the credential file at the API-key consumer's
53
+ expected location instead. No other module changes. Tracked as iteration-2
54
+ work in issue #7.
55
+
56
+ OAuth caveats that apply today (and will go away if you migrate to an API
57
+ key):
58
+
59
+ - **Plan limits.** The EC2 worker burns the engineer's Claude plan limits
60
+ on every run.
61
+ - **Rotation.** Each desktop `claude /login` may invalidate the prior
62
+ refresh token. Re-run `ralph-sync-credential` immediately after every
63
+ login, or the worker will fail at the first `claude --print` call.
64
+ - **Concurrent use unverified.** Simultaneous use of the same OAuth
65
+ credential by the desktop app and an EC2 worker is not validated.
66
+ Assume one active consumer at a time.
67
+
68
+ ## Public-safe
69
+
70
+ This repository contains zero target-specific identifiers. Every target knob is
71
+ supplied at runtime via the target repo's `.ralph/config.yaml` and via
72
+ environment variables / SSM. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the
73
+ contract.
74
+
75
+ ## What's here today
76
+
77
+ Slice 10 — end-to-end smoke-test runbook (HITL):
78
+
79
+ - [`docs/smoke-test.md`](docs/smoke-test.md) — operator-driven runbook
80
+ that exercises the full chain on a fresh AWS account: `bootstrap-aws` →
81
+ `sync-credential` → seed PAT → `fire` → tail CloudWatch → verify PR →
82
+ verify cleanup. Acceptance signal is "every phase reached `PHASE_END`
83
+ and the instance is `terminated`."
84
+
85
+ Slice 1 — target-config-schema:
86
+
87
+ - [`docs/config-schema.md`](docs/config-schema.md) — full schema for the
88
+ `.ralph/config.yaml` file every target repo must commit.
89
+ - [`lib/target-config-schema.sh`](lib/target-config-schema.sh) — sourceable
90
+ bash module that loads and validates a target repo's `.ralph/config.yaml`.
91
+ Fails loud with a useful message on any error.
92
+ - [`bin/load-config`](bin/load-config) — thin CLI over the validator. Exits 0
93
+ on valid input, non-zero on bad input.
94
+
95
+ Slice 2 — github-state-mutator:
96
+
97
+ - [`lib/github-state-mutator.sh`](lib/github-state-mutator.sh) — idempotent
98
+ shell wrappers around `gh` for the four state mutations the orchestrator
99
+ needs: `swap_label`, `comment_issue`,
100
+ `find_or_create_milestone_log_issue`, `append_caveman_log`.
101
+ - [`bin/gsm`](bin/gsm) — CLI for manual verification against a sandbox repo.
102
+
103
+ Slice 3 — aws-bootstrap (TS, slice-4 port):
104
+
105
+ - [`src/lib/aws-bootstrap.ts`](src/lib/aws-bootstrap.ts) — idempotent
106
+ `ensure*` functions for every AWS-side resource the harness needs (KMS
107
+ alias `alias/ralph`, SSM SecureString placeholders, EC2 IAM role +
108
+ instance profile with minimum-scope inline policy, security group in the
109
+ default VPC, CloudWatch log group) plus the target-side `agent-stuck`
110
+ label. Implemented over the AWS SDK v3 clients (`@aws-sdk/client-{kms,ssm,iam,ec2,cloudwatch-logs,sts}`).
111
+ - `ralph-bootstrap-aws` (`src/bin/ralph-bootstrap-aws.ts`) — single-shot
112
+ CLI: reads config from env, ensures every resource, second run is a
113
+ clean no-op. Region is forced to `eu-central-1`.
114
+
115
+ Slice 5 — fire-launcher (single-fire EC2 + CloudWatch streaming):
116
+
117
+ - [`lib/fire-launcher.sh`](lib/fire-launcher.sh) — fires one throwaway EC2
118
+ in `eu-central-1` (`m7a.xlarge`, 30 GB gp3, AL2023 from public SSM AMI
119
+ parameter, default-VPC public subnet, auto-assigned public IP, IMDSv2
120
+ required) using the bootstrapped IAM instance profile and security group.
121
+ Tags every instance + volume `Project=ralph` plus a UTC `LaunchedAt`
122
+ timestamp and `MaxLifetimeMin`. Launches with
123
+ `--instance-initiated-shutdown-behavior terminate`, then polls
124
+ `describe-instances` until `terminated`. On the 75-minute ceiling the
125
+ launcher force-`terminate-instances` and exits non-zero.
126
+ - [`bin/fire.sh`](bin/fire.sh) — single-shot launcher CLI.
127
+
128
+ Slice 9 — review call (post-PR) + post-hoc agent-stuck detection:
129
+
130
+ - [`prompts/review.md`](prompts/review.md) — generic review prompt
131
+ template. Target context (repo, default branch, work dir, build/test
132
+ commands, picked issue number, PR number, PR branch, configured
133
+ `review_bot.username` and `.source`, optional
134
+ `prompt_extensions.review`) is injected via `{{...}}` placeholders;
135
+ the template itself contains zero target-specific identifiers.
136
+ - `lib/ec2-orchestrator.sh` — `orch::run` now chains a review call
137
+ onto the `PR_OPENED` branch from slice 8: bash `sleep
138
+ RALPH_REVIEW_WAIT_SEC` (default `600`s — the configured external
139
+ review bot's window), render `prompts/review.md`, invoke
140
+ `claude --print`, wrap the call in
141
+ `PHASE_START phase=review issue=<n> pr=<m>` /
142
+ `PHASE_END phase=review duration_s=… status=…` markers (status
143
+ comes from the result file), verify `/tmp/ralph/review-result.json`,
144
+ branch on its `status`. Right after the discovery `PICKED` branch
145
+ the orchestrator emits a stable `PICKED_ISSUE=<n>` marker so the
146
+ launcher's post-hoc check can correlate hard-killed instances back
147
+ to a source issue.
148
+ - The review call writes `/tmp/ralph/review-result.json` with one of
149
+ two shapes:
150
+ - `{"status":"NO_REVIEW","reason":"…"}` — no comments / reviews
151
+ from the configured `review_bot.username` (with matching
152
+ `review_bot.source`) within the 10-minute window. No revision,
153
+ no caveman log entry, no state mutation.
154
+ - `{"status":"REVISION_APPLIED","issue":<n>,"pr_number":<m>,"summary":"…","gotcha":"…"}`
155
+ — exactly ONE revision pass: the call addresses the configured
156
+ bot's verdict, runs build + test until green, commits with a
157
+ `review: address …` message, and pushes to the same PR branch.
158
+ No follow-up rounds, no agent-vs-agent ping-pong.
159
+ - Bash branches on `review-result.json.status`: `NO_REVIEW` →
160
+ `OUTCOME=pr_opened issue=<n> pr=<m> review=none`; `REVISION_APPLIED`
161
+ → `OUTCOME=pr_opened issue=<n> pr=<m> review=revised` and one
162
+ caveman-format comment is appended to the milestone-log issue via
163
+ `gsm::append_caveman_log` (from `lib/github-state-mutator.sh`,
164
+ bundled into the EC2 user-data alongside the orchestrator).
165
+ - Multi-reviewer case: only the configured `review_bot` is consulted;
166
+ Copilot, humans, and other bots on the same PR are ignored.
167
+ - `lib/fire-launcher.sh` — embeds `prompts/review.md` alongside
168
+ discovery + implementation, exports
169
+ `RALPH_REVIEW_PROMPT=/opt/ralph/prompts/review.md`, and bundles
170
+ `lib/github-state-mutator.sh` into the user-data so the orchestrator
171
+ can call `gsm::append_caveman_log` on the worker. After
172
+ `wait_for_terminated` (regardless of clean exit or wall-clock breach)
173
+ the launcher runs a post-hoc agent-stuck check from the laptop:
174
+ - List target-repo PRs whose body contains
175
+ `<!-- ralph-launch: <RALPH_LAUNCH_TAG> -->` (the marker the impl
176
+ call embeds). If at least one is found, the run is treated as a
177
+ clean termination.
178
+ - Otherwise, fetch the per-instance CloudWatch stream
179
+ (`aws logs filter-log-events`) for the orchestrator's
180
+ `PICKED_ISSUE=<n>` marker. If found, apply the configured
181
+ `agent_stuck_label` (default `agent-stuck`) to that source issue
182
+ via `gh issue edit`. Covers both wall-clock-killed and
183
+ orchestrator-crashed cases where the impl call could not
184
+ self-label.
185
+ - If neither a tagged PR nor a `PICKED_ISSUE` marker is recoverable,
186
+ the post-hoc check is a no-op (clean exit).
187
+
188
+ Slice 8 — implementation call:
189
+
190
+ - [`prompts/implementation.md`](prompts/implementation.md) — generic
191
+ implementation prompt template. Target context (repo, default branch,
192
+ work dir, build/test commands, branch prefix, agent-stuck label,
193
+ per-launch tag, optional `prompt_extensions.implementation`) is
194
+ injected via `{{...}}` placeholders at render time; the template
195
+ itself contains zero target-specific identifiers.
196
+ - `lib/ec2-orchestrator.sh` — `orch::run` now chains the implementation
197
+ call onto the `PICKED` branch from discovery: renders
198
+ `prompts/implementation.md`, appends the crafted context discovery
199
+ wrote to `/tmp/ralph/crafted-prompt.md`, invokes `claude --print`,
200
+ wraps the call in `PHASE_START phase=implementation` /
201
+ `PHASE_END phase=implementation duration_s=... issue=<n> status=...`
202
+ markers (status comes from the result file so a CloudWatch grep
203
+ yields a one-line per-iteration summary), and verifies the result
204
+ file before branching.
205
+ - The implementation call writes `/tmp/ralph/impl-result.json` with one
206
+ of two shapes:
207
+ - `{"status":"PR_OPENED","issue":<n>,"pr_number":<m>,"pr_url":...,"branch":...}`
208
+ - `{"status":"AGENT_STUCK","issue":<n>,"reason":"..."}`
209
+ - Bash branches on `impl-result.json.status`: `PR_OPENED` →
210
+ `OUTCOME=pr_opened issue=<n> pr=<m>`; `AGENT_STUCK` →
211
+ `OUTCOME=agent_stuck issue=<n>`. Any other status, missing output,
212
+ or invalid JSON aborts with exit 3 — the EC2 instance still
213
+ terminates via the cloud-init EXIT trap.
214
+ - The PR body carries an HTML-comment marker
215
+ `<!-- ralph-launch: <RALPH_LAUNCH_TAG> -->` (defaults to the EC2
216
+ instance id, set in `lib/cloud-init/bootstrap.sh`). Slice 9's
217
+ launcher post-hoc check uses this to correlate when the EC2 was
218
+ hard-killed before recording state.
219
+ - `agent-stuck` escape: the prompt instructs the impl call to
220
+ self-stop when ANY of (>3 build/test fix iterations on the same
221
+ failure surface) OR (>15 file edits without a green build) OR
222
+ (self-judged futility) hits. On stuck: the source issue gets the
223
+ `agent_stuck_label` label (default `agent-stuck`), no PR is opened,
224
+ and `impl-result.json` records `status=AGENT_STUCK`.
225
+ - The launcher embeds `prompts/implementation.md` into the rendered
226
+ user-data alongside the discovery prompt and exports
227
+ `RALPH_IMPLEMENTATION_PROMPT=/opt/ralph/prompts/implementation.md`.
228
+
229
+ Slice 7 — discovery call:
230
+
231
+ - [`prompts/discovery.md`](prompts/discovery.md) — generic discovery
232
+ prompt template. Target context (repo, default branch, work dir,
233
+ build/test commands, branch prefix, optional `prompt_extensions.discovery`)
234
+ is injected via `{{...}}` placeholders at render time; the template
235
+ itself contains zero target-specific identifiers.
236
+ - [`lib/ec2-orchestrator.sh`](lib/ec2-orchestrator.sh) — `orch::run`
237
+ now fires the real discovery call: renders the template, invokes
238
+ `claude --print` (with `--permission-mode bypassPermissions` by
239
+ default; override via `RALPH_CLAUDE_FLAGS`), wraps the call in
240
+ `PHASE_START phase=discovery` / `PHASE_END phase=discovery duration_s=...`
241
+ markers, and verifies the four output files exist before branching.
242
+ Each invocation is a fresh claude session (`memory` MCP excluded by
243
+ slice 6's MCP set).
244
+ - The discovery call writes four files under `$RALPH_OUT_DIR`
245
+ (default `/tmp/ralph/`):
246
+ - `decision.json` — `status` (`PICKED` | `NONE` | `ALL_BLOCKED`),
247
+ picked issue number, reasoning.
248
+ - `issue.json` — full `gh issue view --json ...` payload of the
249
+ picked issue (or `{}` for non-PICKED).
250
+ - `crafted-prompt.md` — implementation prompt for slice 8, with
251
+ target conventions surfaced from the target's `CLAUDE.md`,
252
+ `AGENTS.md`, `CONTEXT.md`, and any `docs/adr/*` files.
253
+ - `milestone-log.json` — `{milestone, log_issue}` pointing at the
254
+ `[log] <milestone>` issue (find-or-create) for cross-iteration
255
+ learnings.
256
+ - Bash branches on `decision.json.status`: `NONE` → `OUTCOME=no_work`,
257
+ `ALL_BLOCKED` → `OUTCOME=all_blocked`, `PICKED` → `OUTCOME=picked issue=<n>`
258
+ (slices 8/9 plug in here). Any other status, missing output file,
259
+ or invalid JSON aborts with exit 3 — the EC2 instance still
260
+ terminates via the cloud-init EXIT trap.
261
+ - The launcher embeds `prompts/discovery.md` into the rendered
262
+ user-data via a quoted heredoc and exports
263
+ `RALPH_DISCOVERY_PROMPT=/opt/ralph/prompts/discovery.md`, so the
264
+ prompt template ships alongside the lib bundle without a network
265
+ fetch.
266
+
267
+ Slice 6 — ec2-bootstrap (deps, secrets, fresh clone, hello orchestrator):
268
+
269
+ - [`lib/cloud-init/bootstrap.sh`](lib/cloud-init/bootstrap.sh) — slice 6's
270
+ cloud-init payload. Runs once on first boot. Installs OS dependencies
271
+ (Node 20, .NET 10 SDK via `dotnet-install.sh`, `gh`, Docker, `uv`,
272
+ `claude` CLI, plus `git`, `jq`, and `yq`). Configures the five MCPs the
273
+ harness uses (`serena`, `morph-mcp`, `context7`, `github`,
274
+ `sequential-thinking`); the `memory` MCP is intentionally NOT added so
275
+ every iteration starts with fresh context. Fetches the GitHub PAT and
276
+ the Claude OAuth credential from SSM SecureString
277
+ (`/ralph/github-pat`, `/ralph/claude-oauth-credential`) into mode-0600
278
+ files at their consumer's expected on-disk locations; never echoes the
279
+ values. Clones the target repo fresh on its resolved default branch
280
+ (`gh repo view --json defaultBranchRef`, no `main`/`master` assumption).
281
+ Runs safety guards (on default branch, clean working tree, origin
282
+ matches `RALPH_TARGET_REPO`). Validates `.ralph/config.yaml` via
283
+ `tcs::validate`. Hands off to `orch::run`. Ships stdout to CloudWatch
284
+ (`/ralph/main`, stream-per-instance) and runs `shutdown -h now` via a
285
+ bash `trap EXIT` so the box terminates on any exit (success or failure).
286
+ - `lib/ec2-orchestrator.sh` — sourceable shell module defining
287
+ `orch::run`. Slice 6 shipped a stub; slice 7 replaces it with the
288
+ real discovery call (see above). Slices 8/9 plug in implementation
289
+ and review after the `PICKED` branch.
290
+ - The launcher renders one self-contained user-data script by
291
+ concatenating `lib/target-config-schema.sh`, `lib/ec2-orchestrator.sh`,
292
+ and `lib/cloud-init/bootstrap.sh` after a small env shim that exports
293
+ the five required runtime knobs (`RALPH_TARGET_REPO`,
294
+ `RALPH_AWS_REGION`, `RALPH_GITHUB_TOKEN_SSM_KEY`,
295
+ `RALPH_CLAUDE_OAUTH_SSM_KEY`, `RALPH_LOG_GROUP`). No SSH at any layer;
296
+ SSM Session Manager is the only debug entry point.
297
+
298
+ ```sh
299
+ # Fire one EC2 (slice 6 ec2-bootstrap, stub orchestrator):
300
+ RALPH_TARGET_REPO=owner/target ralph-fire
301
+
302
+ # Tail the per-instance CloudWatch stream:
303
+ aws --region eu-central-1 logs tail /ralph/main \
304
+ --log-stream-names <i-...> --follow
305
+ ```
306
+
307
+ Slice 4 — operator helper CLIs (TS, macOS-only Keychain syncer):
308
+
309
+ - [`src/lib/credential-syncer.ts`](src/lib/credential-syncer.ts) — reads
310
+ the `Claude Code-credentials` entry from the macOS Keychain and uploads
311
+ it to the SSM SecureString at `/ralph/claude-oauth-credential`
312
+ (overridable via `RALPH_CLAUDE_OAUTH_SSM_KEY`), encrypted under
313
+ `alias/ralph`.
314
+ - `ralph-sync-credential` (`src/bin/ralph-sync-credential.ts`) — thin CLI
315
+ wrapper. Region is forced to `eu-central-1`. The credential is passed
316
+ to `@aws-sdk/client-ssm` as the request body of `PutParameter`, so it
317
+ is never visible on any process's argv and is never echoed in info or
318
+ error output.
319
+ - `ralph-sync-github-pat` — net-new operator CLI: reads a GitHub PAT
320
+ from stdin and uploads it to the SSM SecureString at `/ralph/github-pat`
321
+ (overridable via `RALPH_GITHUB_TOKEN_SSM_KEY`). Pass the token on stdin
322
+ only — argv is rejected.
323
+ - `ralph-tail-logs` — thin wrapper around `aws logs tail` that defaults
324
+ to `/ralph/main` and the worker's per-instance stream.
325
+
326
+ Run after every desktop `claude /login`:
327
+
328
+ ```sh
329
+ ralph-sync-credential
330
+ # or with a custom key:
331
+ RALPH_CLAUDE_OAUTH_SSM_KEY=/ralph/claude-oauth-credential ralph-sync-credential
332
+ ```
333
+
334
+ Caveats:
335
+
336
+ - **Rotation:** every desktop `claude /login` may invalidate the prior
337
+ refresh token. Re-run `ralph-sync-credential` immediately after each
338
+ login so the EC2 worker picks up the fresh credential.
339
+ - **Concurrent use unverified:** simultaneous use of the same credential by
340
+ the desktop app and an EC2 worker has not been validated; assume one
341
+ active consumer at a time.
342
+ - **Plan limits:** the EC2 worker burns the engineer's Claude plan limits
343
+ while running.
344
+ - **OAuth-vs-API-key:** iteration 1 ships OAuth-via-Keychain. Switching to a
345
+ long-lived API key is a one-file change in the future `ec2-bootstrap`
346
+ module (see issue #7).
347
+
348
+ [`src/`](src/) holds the TS modules and bin entries with co-located vitest
349
+ tests (`*.test.ts`). Run with `npm test`.
350
+
351
+ ```sh
352
+ # Validate a config file by hand:
353
+ ralph-validate-config path/to/.ralph/config.yaml
354
+
355
+ # Manually swap a label on a sandbox repo:
356
+ ralph-gsm swap-label owner/sandbox 1 ready-for-agent ready-for-human
357
+
358
+ # Bootstrap AWS resources for a target repo (idempotent):
359
+ RALPH_TARGET_REPO=owner/target ralph-bootstrap-aws
360
+
361
+ # Sync the macOS Keychain credential into SSM (re-run after every claude /login):
362
+ ralph-sync-credential
363
+
364
+ # Seed the GitHub PAT into SSM (one-time):
365
+ echo "$GITHUB_PAT" | ralph-sync-github-pat
366
+
367
+ # Fire one throwaway EC2 instance (full discovery → impl → review chain):
368
+ RALPH_TARGET_REPO=owner/target ralph-fire
369
+
370
+ # Run the test suite:
371
+ npm test
372
+ ```
373
+
374
+ Dependencies (operator side): Node ≥ 24 and the harness installed globally
375
+ (`npm install -g @unimatrix27/ralph-harness@1.0.0`), plus `jq`, `gh`, and
376
+ `aws` CLI v2. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for install hints.
377
+
378
+ ## Schema at a glance
379
+
380
+ A target repo's `.ralph/config.yaml` looks like:
381
+
382
+ ```yaml
383
+ build_cmd: "make build"
384
+ test_cmd: "make test"
385
+ branch_prefix: "ralph"
386
+ review_bot:
387
+ username: "claude"
388
+ source: "comment"
389
+ # optional:
390
+ agent_stuck_label: "agent-stuck"
391
+ prompt_extensions:
392
+ discovery: |
393
+ ...extra discovery-prompt instructions...
394
+ implementation: |
395
+ ...extra impl-prompt instructions...
396
+ review: |
397
+ ...extra review-prompt instructions...
398
+ ```
399
+
400
+ See [`docs/config-schema.md`](docs/config-schema.md) for the full spec, types,
401
+ and validation rules.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=ralph-bootstrap-aws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph-bootstrap-aws.d.ts","sourceRoot":"","sources":["../../src/bin/ralph-bootstrap-aws.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // ralph-bootstrap-aws — idempotent first-run bootstrap of the AWS-side and
4
+ // target-side resources the ralph-harness needs. Drop-in replacement for
5
+ // the (deleted) bin/bootstrap-aws.sh.
6
+ //
7
+ // Usage:
8
+ // ralph-bootstrap-aws
9
+ //
10
+ // Reads from env:
11
+ // RALPH_TARGET_REPO required (owner/repo)
12
+ // RALPH_GITHUB_TOKEN_SSM_KEY defaults to /ralph/github-pat
13
+ // RALPH_CLAUDE_OAUTH_SSM_KEY defaults to /ralph/claude-oauth-credential
14
+ // RALPH_LOG_GROUP defaults to /ralph/main
15
+ //
16
+ // Region is forced to eu-central-1 (matches lib/aws-clients.ts AWS_REGION).
17
+ //
18
+ // Re-running on an already-bootstrapped account is a clean no-op.
19
+ //
20
+ // Exit codes (matching bin/bootstrap-aws.sh):
21
+ // 0 success
22
+ // 2 usage / missing required env var
23
+ // non-zero propagated from underlying SDK / gh failure
24
+ import { DEFAULTS, moduleErr, runAll } from "../lib/aws-bootstrap.js";
25
+ import { defaultAwsClients } from "../lib/aws-clients.js";
26
+ async function main() {
27
+ const repo = process.env.RALPH_TARGET_REPO;
28
+ if (!repo || repo.length === 0) {
29
+ process.stderr.write(moduleErr("RALPH_TARGET_REPO is required (e.g. owner/repo)") + "\n");
30
+ process.exit(2);
31
+ }
32
+ const githubKey = process.env.RALPH_GITHUB_TOKEN_SSM_KEY || DEFAULTS.githubKey;
33
+ const oauthKey = process.env.RALPH_CLAUDE_OAUTH_SSM_KEY || DEFAULTS.oauthKey;
34
+ const logGroup = process.env.RALPH_LOG_GROUP || DEFAULTS.logGroup;
35
+ const clients = defaultAwsClients();
36
+ await runAll({ clients, repo, githubKey, oauthKey, logGroup });
37
+ }
38
+ main().catch((err) => {
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ process.stderr.write(`${msg}\n`);
41
+ process.exit(1);
42
+ });
43
+ //# sourceMappingURL=ralph-bootstrap-aws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph-bootstrap-aws.js","sourceRoot":"","sources":["../../src/bin/ralph-bootstrap-aws.ts"],"names":[],"mappings":";AACA,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,sCAAsC;AACtC,EAAE;AACF,SAAS;AACT,wBAAwB;AACxB,EAAE;AACF,kBAAkB;AAClB,yDAAyD;AACzD,iEAAiE;AACjE,8EAA8E;AAC9E,2DAA2D;AAC3D,EAAE;AACF,4EAA4E;AAC5E,EAAE;AACF,kEAAkE;AAClE,EAAE;AACF,8CAA8C;AAC9C,gBAAgB;AAChB,yCAAyC;AACzC,2DAA2D;AAE3D,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC3C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,SAAS,CAAC,iDAAiD,CAAC,GAAG,IAAI,CACpE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,QAAQ,CAAC,SAAS,CAAC;IAC/D,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAC9D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAElE,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;IACpC,MAAM,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=ralph-fire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph-fire.d.ts","sourceRoot":"","sources":["../../src/bin/ralph-fire.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // ralph-fire — slice 5 launcher (real). Replaces iteration-1's
4
+ // `bin/fire.sh` + `lib/fire-launcher.sh`. Loads `.env` from CWD then XDG,
5
+ // optionally subprocesses `ralph-bootstrap-aws` for first-run idempotent
6
+ // AWS-side resource ensure, then calls the launcher.
7
+ //
8
+ // Usage:
9
+ // ralph-fire — fire one EC2 worker
10
+ // ralph-fire --version — print the package version and exit
11
+ //
12
+ // Env knobs: see lib/fire-launcher.ts (resolveLauncherConfig). The set is
13
+ // byte-compatible with iteration-1's lib/fire-launcher.sh.
14
+ //
15
+ // Auto-bootstrap: defaults to subprocessing `ralph-bootstrap-aws` once
16
+ // before launching. Set RALPH_SKIP_BOOTSTRAP=1 to suppress.
17
+ import { spawnSync } from "node:child_process";
18
+ import { createRequire } from "node:module";
19
+ import { loadDotenv } from "../lib/env-loader.js";
20
+ import { LauncherError, run } from "../lib/fire-launcher.js";
21
+ const require = createRequire(import.meta.url);
22
+ const pkg = require("../../package.json");
23
+ async function main() {
24
+ const args = process.argv.slice(2);
25
+ if (args.length === 1 && args[0] === "--version") {
26
+ process.stdout.write(`${pkg.version}\n`);
27
+ process.exit(0);
28
+ }
29
+ if (args.length > 0) {
30
+ process.stderr.write(`ralph-fire: unexpected arguments: ${args.join(" ")}\n`);
31
+ process.exit(2);
32
+ }
33
+ loadDotenv();
34
+ if (process.env.RALPH_SKIP_BOOTSTRAP !== "1") {
35
+ const r = spawnSync("ralph-bootstrap-aws", [], {
36
+ stdio: "inherit",
37
+ env: process.env,
38
+ });
39
+ if (r.error) {
40
+ process.stderr.write(`ralph-fire: ralph-bootstrap-aws not found on PATH; install the package globally (npm install -g @unimatrix27/ralph-harness) or set RALPH_SKIP_BOOTSTRAP=1\n`);
41
+ process.exit(2);
42
+ }
43
+ if ((r.status ?? 1) !== 0) {
44
+ process.exit(r.status ?? 1);
45
+ }
46
+ }
47
+ const rc = await run({ defaultHarnessVersion: pkg.version });
48
+ process.exit(rc);
49
+ }
50
+ main().catch((err) => {
51
+ if (err instanceof LauncherError) {
52
+ process.stderr.write(`${err.message}\n`);
53
+ process.exit(err.exitCode);
54
+ }
55
+ const msg = err instanceof Error ? err.message : String(err);
56
+ process.stderr.write(`ralph-fire: ${msg}\n`);
57
+ process.exit(1);
58
+ });
59
+ //# sourceMappingURL=ralph-fire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph-fire.js","sourceRoot":"","sources":["../../src/bin/ralph-fire.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=ralph-gsm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph-gsm.d.ts","sourceRoot":"","sources":["../../src/bin/ralph-gsm.ts"],"names":[],"mappings":""}