@beremaran/ralphie 0.2.1 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to Ralphie are documented here. The project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.2.3] - 2026-10-08
9
+
10
+ ### Changed
11
+
12
+ - Agent sessions now inherit Ralphie's GitHub and git authority and may use
13
+ `gh` to read issues, pull requests, comments and CI results. See
14
+ `docs/safety.md#session-authority`.
15
+ - `bun run smoke:live` now exits with status 2 unless an explicit opt-in
16
+ environment variable is set, because live runs spend model quota.
17
+
18
+ ## [0.2.2] - 2026-10-07
19
+
20
+ ### Changed
21
+
22
+ - The README quick start now shows a global install, `bun add -g
23
+ @beremaran/ralphie`, next to the scoped `bunx` command. There are no changes
24
+ to the CLI.
25
+
8
26
  ## [0.2.1] - 2026-10-07
9
27
 
10
28
  ### Fixed
@@ -838,6 +856,7 @@ describe history, not the current tool.
838
856
  - Prevent no-change agent runs from being silently skipped without proving
839
857
  whether the issue is already resolved.
840
858
 
841
- [Unreleased]: https://github.com/beremaran/ralphie/compare/v0.2.1...HEAD
859
+ [Unreleased]: https://github.com/beremaran/ralphie/compare/v0.2.2...HEAD
860
+ [0.2.2]: https://github.com/beremaran/ralphie/compare/v0.2.1...v0.2.2
842
861
  [0.2.1]: https://github.com/beremaran/ralphie/compare/v0.2.0...v0.2.1
843
862
  [0.2.0]: https://github.com/beremaran/ralphie/compare/v0.1.2...v0.2.0
package/README.md CHANGED
@@ -24,6 +24,13 @@ Run the latest release without installing globally:
24
24
  bunx @beremaran/ralphie --version
25
25
  ```
26
26
 
27
+ Or install it globally; the command it provides is `ralphie`:
28
+
29
+ ```bash
30
+ bun add -g @beremaran/ralphie
31
+ ralphie --version
32
+ ```
33
+
27
34
  Write a starter config with `bunx @beremaran/ralphie init` (it detects the
28
35
  harnesses on PATH; see [Configuration](./docs/configuration.md)), then run the
29
36
  issue queue:
package/dist/ralphie.js CHANGED
@@ -26490,10 +26490,6 @@ ${clone2.stderr}` : "";
26490
26490
  };
26491
26491
  var runGit = (runner, repositoryPath, args, signal) => runner.run("git", ["-C", repositoryPath, ...args], signal === undefined ? undefined : { signal });
26492
26492
  var requireGit = async (runner, repositoryPath, args, failureMessage, signal) => await requireSuccess(runner, "git", ["-C", repositoryPath, ...args], failureMessage, signal === undefined ? undefined : { signal });
26493
- var DISABLED_PUSH_URL = "disabled://ralphie-sessions-cannot-push";
26494
- var disablePush = async (runner, repositoryPath, signal) => {
26495
- await requireGit(runner, repositoryPath, ["remote", "set-url", "--push", "origin", DISABLED_PUSH_URL], "Failed to disable pushing from the workspace.", signal);
26496
- };
26497
26493
  var selectBranch = async (runner, repositoryPath, branch, signal) => {
26498
26494
  if (branch !== undefined)
26499
26495
  return branch;
@@ -26520,7 +26516,6 @@ var prepareRepositoryState = async (runner, parsed, repositoryPath, branch, exis
26520
26516
  if (originSlug.toLowerCase() !== parsed.slug.toLowerCase()) {
26521
26517
  throw new Error(`${repositoryPath} contains ${originSlug}, not ${parsed.slug}.`);
26522
26518
  }
26523
- await disablePush(runner, repositoryPath, signal);
26524
26519
  if (exists)
26525
26520
  await requireGit(runner, repositoryPath, ["fetch", "--prune", "origin"], `Failed to fetch ${parsed.slug}.`, signal);
26526
26521
  const status = await requireGit(runner, repositoryPath, ["status", "--porcelain"], `Failed to inspect ${parsed.slug}.`, signal);
@@ -27922,9 +27917,10 @@ var TRACKER_DOC = `# Issue tracker
27922
27917
 
27923
27918
  Ralphie supplies the issue content in the prompt. Work from that text.
27924
27919
 
27925
- Do not use the \`gh\` CLI or any other tool to read, create, comment on, label
27926
- or close issues, and do not call the GitHub API. Ralphie performs every
27927
- change to the tracker itself.
27920
+ You may use the \`gh\` CLI to read issues, pull requests, comments and CI results.
27921
+ Do not create, edit, comment on, label, close or reopen anything.
27922
+ Ralphie performs every change to the tracker itself.
27923
+ For raw GitHub API calls, pass \`--method GET\`; never use the API to write.
27928
27924
  `;
27929
27925
  var labelsDoc = (labels) => [
27930
27926
  "# Triage labels",
@@ -31444,48 +31440,7 @@ var nodeSkillFileSystem = {
31444
31440
  }
31445
31441
  };
31446
31442
 
31447
- // src/harness/app/session-isolation.ts
31448
- var GITHUB_CREDENTIAL_VARIABLES = [
31449
- "GH_TOKEN",
31450
- "GITHUB_TOKEN",
31451
- "GH_ENTERPRISE_TOKEN",
31452
- "GITHUB_ENTERPRISE_TOKEN"
31453
- ];
31454
- var SSH_AGENT_VARIABLES = [
31455
- "SSH_AUTH_SOCK",
31456
- "SSH_ASKPASS",
31457
- "GIT_ASKPASS"
31458
- ];
31459
- var isolatedEnvironment = (env, configDirectory) => ({
31460
- ...env,
31461
- ...Object.fromEntries([...GITHUB_CREDENTIAL_VARIABLES, ...SSH_AGENT_VARIABLES].map((name) => [
31462
- name,
31463
- undefined
31464
- ])),
31465
- GH_CONFIG_DIR: configDirectory,
31466
- GIT_CONFIG_GLOBAL: "/dev/null",
31467
- GIT_CONFIG_SYSTEM: "/dev/null",
31468
- GIT_CONFIG_NOSYSTEM: "1",
31469
- GIT_CONFIG_COUNT: "1",
31470
- GIT_CONFIG_KEY_0: "credential.helper",
31471
- GIT_CONFIG_VALUE_0: "",
31472
- GIT_TERMINAL_PROMPT: "0",
31473
- GIT_SSH_COMMAND: "false"
31474
- });
31475
- var isolateSessions = (inner, scratch) => {
31476
- const run = async (request2) => {
31477
- const directory = await scratch.create();
31478
- try {
31479
- return await inner.run({
31480
- ...request2,
31481
- env: isolatedEnvironment(request2.env, directory.path)
31482
- });
31483
- } finally {
31484
- await directory.remove().catch(() => {});
31485
- }
31486
- };
31487
- return { run };
31488
- };
31443
+ // src/harness/app/read-only-session-guard.ts
31489
31444
  var guardReadOnlySessions = (inner, fingerprint) => {
31490
31445
  const run = async (request2) => {
31491
31446
  if (request2.access !== "read-only")
@@ -31509,20 +31464,6 @@ var guardReadOnlySessions = (inner, fingerprint) => {
31509
31464
  return { run };
31510
31465
  };
31511
31466
 
31512
- // src/harness/adapters/scratch-directory.ts
31513
- import { mkdtemp as mkdtemp2, rm as rm6 } from "fs/promises";
31514
- import { tmpdir as tmpdir2 } from "os";
31515
- import { join as join7 } from "path";
31516
- var makeTemporaryScratchDirectories = () => ({
31517
- create: async () => {
31518
- const path = await mkdtemp2(join7(tmpdir2(), "ralphie-session-"));
31519
- return {
31520
- path,
31521
- remove: () => rm6(path, { recursive: true, force: true })
31522
- };
31523
- }
31524
- });
31525
-
31526
31467
  // src/git/adapters/working-tree.ts
31527
31468
  import { createHash as createHash2 } from "crypto";
31528
31469
  var makeGitWorkingTreeService = (runner) => ({
@@ -31616,7 +31557,7 @@ var provideRepositoryFacts = (inner, read) => {
31616
31557
  };
31617
31558
 
31618
31559
  // src/run/adapters/state.ts
31619
- import { mkdir as mkdir7, rename as rename4, rm as rm7, writeFile as writeFile7 } from "fs/promises";
31560
+ import { mkdir as mkdir7, rename as rename4, rm as rm6, writeFile as writeFile7 } from "fs/promises";
31620
31561
  import { dirname as dirname4 } from "path";
31621
31562
 
31622
31563
  // src/run/state.ts
@@ -31765,7 +31706,7 @@ var persistRunStateAtomically = async (path, state) => {
31765
31706
  });
31766
31707
  await rename4(temporaryPath, path);
31767
31708
  } catch (cause) {
31768
- await rm7(temporaryPath, { force: true }).catch(() => {
31709
+ await rm6(temporaryPath, { force: true }).catch(() => {
31769
31710
  return;
31770
31711
  });
31771
31712
  throw new RalphieError({
@@ -31798,7 +31739,7 @@ var makeIdGenerator = () => ({
31798
31739
  });
31799
31740
 
31800
31741
  // src/workspace/adapters/workspace.ts
31801
- import { mkdir as mkdir8, rm as rm8 } from "fs/promises";
31742
+ import { mkdir as mkdir8, rm as rm7 } from "fs/promises";
31802
31743
  import { homedir as homedir2 } from "os";
31803
31744
  import { parse as parse7, resolve as resolve2, sep as sep2 } from "path";
31804
31745
  var assertSafeCleanupTarget = (workspace) => {
@@ -31830,7 +31771,7 @@ var WorkspaceLive = {
31830
31771
  },
31831
31772
  remove: async (workspace) => {
31832
31773
  try {
31833
- await rm8(assertSafeCleanupTarget(workspace), {
31774
+ await rm7(assertSafeCleanupTarget(workspace), {
31834
31775
  recursive: true,
31835
31776
  force: true
31836
31777
  });
@@ -31890,7 +31831,7 @@ var makeLiveRuntime = ({
31890
31831
  })
31891
31832
  }
31892
31833
  });
31893
- const harness = provideRepositoryFacts(guardReadOnlySessions(isolateSessions(bareHarness, makeTemporaryScratchDirectories()), makeGitWorkingTreeService(commandRunner).fingerprint), makeGitRepositoryFactsService(commandRunner).read);
31834
+ const harness = provideRepositoryFacts(guardReadOnlySessions(bareHarness, makeGitWorkingTreeService(commandRunner).fingerprint), makeGitRepositoryFactsService(commandRunner).read);
31894
31835
  const gitRepository = makeGitRepositoryService(commandRunner);
31895
31836
  const gitRepositoryInvariant = makeGitRepositoryInvariantService(commandRunner);
31896
31837
  const gitIssueCheckpoint = makeGitIssueCheckpointService(commandRunner);
@@ -33223,7 +33164,7 @@ var issueWorkflow = { run: workflow };
33223
33164
  // package.json
33224
33165
  var package_default = {
33225
33166
  name: "@beremaran/ralphie",
33226
- version: "0.2.1",
33167
+ version: "0.2.3",
33227
33168
  description: "Turn a GitHub issue queue into reviewed commits with coding-agent harnesses.",
33228
33169
  module: "./dist/ralphie.js",
33229
33170
  main: "./dist/ralphie.js",
@@ -33310,7 +33251,7 @@ var package_default = {
33310
33251
 
33311
33252
  // src/build-info.ts
33312
33253
  var LOCAL_BUILD_COMMIT_SHA = "local";
33313
- var injectedBuildInfo = { version: "0.2.1", commitSha: "local" };
33254
+ var injectedBuildInfo = { version: "0.2.3", commitSha: "local" };
33314
33255
  var BUILD_INFO = Object.freeze(injectedBuildInfo ?? {
33315
33256
  version: package_default.version,
33316
33257
  commitSha: LOCAL_BUILD_COMMIT_SHA
@@ -33336,21 +33277,21 @@ var makeRunEventLog = (input) => {
33336
33277
  };
33337
33278
 
33338
33279
  // src/run/adapters/layout.ts
33339
- import { join as join8 } from "path";
33280
+ import { join as join7 } from "path";
33340
33281
  var safeRunId = (runId) => runId.replace(/[^a-zA-Z0-9_-]/g, "_") || "run";
33341
33282
  var makeRunLayout = (workspace, runId) => {
33342
33283
  const workspaceRoot = resolveWorkspacePath(workspace);
33343
- const runRoot = join8(workspaceRoot, ".ralphie", "runs", safeRunId(runId));
33344
- const issueRoot = join8(runRoot, "issues");
33284
+ const runRoot = join7(workspaceRoot, ".ralphie", "runs", safeRunId(runId));
33285
+ const issueRoot = join7(runRoot, "issues");
33345
33286
  return {
33346
33287
  workspaceRoot,
33347
33288
  runRoot,
33348
- statePath: join8(runRoot, "state.json"),
33349
- eventLogPath: join8(runRoot, "events.jsonl"),
33350
- issueArtifactsDirectory: (issueNumber) => join8(issueRoot, String(issueNumber)),
33351
- issueArtifactsPath: (issueNumber) => join8(issueRoot, String(issueNumber), "artifacts.json"),
33352
- diagnosticsDirectory: (issueNumber) => join8(issueRoot, String(issueNumber)),
33353
- diagnosticsPath: (issueNumber, name) => join8(issueRoot, String(issueNumber), name)
33289
+ statePath: join7(runRoot, "state.json"),
33290
+ eventLogPath: join7(runRoot, "events.jsonl"),
33291
+ issueArtifactsDirectory: (issueNumber) => join7(issueRoot, String(issueNumber)),
33292
+ issueArtifactsPath: (issueNumber) => join7(issueRoot, String(issueNumber), "artifacts.json"),
33293
+ diagnosticsDirectory: (issueNumber) => join7(issueRoot, String(issueNumber)),
33294
+ diagnosticsPath: (issueNumber, name) => join7(issueRoot, String(issueNumber), name)
33354
33295
  };
33355
33296
  };
33356
33297
 
@@ -33,7 +33,7 @@ that binds concrete adapters into the runtime bundle.
33
33
  | --- | --- | --- |
34
34
  | `agent` | `src/agent/` | Prompts, structured-output and text-task helpers, and the role-to-session mapping (access mode, timeouts) over the harness port. Kept apart from `harness` on purpose: `agent` holds Ralphie's own prompts and role policy, while `harness` stays provider-neutral session plumbing that knows nothing about issues. |
35
35
  | `config` | `src/config/` | YAML configuration: the zod schema (`settings.ts`), layering and `--set` overrides (`load.ts`, `overrides.ts`), and the file-reader port with its Bun YAML adapter. |
36
- | `harness` | `src/harness/` | Provider-neutral harness port (session request, events, typed failures, structured results), the service that runs sessions and repairs invalid results, and one CLI adapter per harness (Claude Code, Codex, pi and OpenCode). Every workflow session runs through it, wrapped by session isolation, the read-only fingerprint guard and skill injection; `app/roles.ts` resolves the configured role assignments. The vendored skills live in `vendor/mattpocock-skills/` (see [Development](development.md#vendored-skills)). |
36
+ | `harness` | `src/harness/` | Provider-neutral harness port (session request, events, typed failures, structured results), the service that runs sessions and repairs invalid results, and one CLI adapter per harness (Claude Code, Codex, pi and OpenCode). Every workflow session runs through it with the read-only fingerprint guard and skill injection; the session environment inherits Ralphie's GitHub and git authority (see [Safety](safety.md#session-authority)). `app/roles.ts` resolves the configured role assignments. The vendored skills live in `vendor/mattpocock-skills/` (see [Development](development.md#vendored-skills)). |
37
37
  | `github` | `src/github/` | Issue value objects, repository slug parsing, and the Octokit/`gh` adapters. |
38
38
  | `git` | `src/git/` | Checkout preparation, checkpoints, issue operations, invariants, and remote-safety adapters. |
39
39
  | `issues` | `src/issues/` | Domain (`domain/`), executors and artifact/recovery logic (`app/`), filesystem adapters (`adapters/`). |
@@ -105,11 +105,13 @@ Spike against OpenCode v2.0.22 (the published docs mostly describe v1). The reco
105
105
 
106
106
  ## Dependency and side-effect rules
107
107
 
108
- Agents own reasoning and edits within their permitted tool boundary. They do not
109
- own commits, pushes, issue mutations, or delivery sequencing. Deterministic
110
- services under `src/git/adapters/` and `src/github/adapters/` perform those side
111
- effects and verify their invariants. The explicit runtime object makes these
112
- boundaries testable without a framework-specific execution model.
108
+ Sessions handle reasoning and code edits; Ralphie's deterministic services own
109
+ the intended commit, push, tracker mutation, and delivery sequence. Prompts
110
+ instruct sessions not to commit, push, or mutate GitHub (see
111
+ [Session authority](safety.md#session-authority)). The adapters under `src/git/adapters/` and
112
+ `src/github/adapters/` perform Ralphie's side effects and verify their
113
+ invariants. The explicit runtime object makes this intended workflow
114
+ testable without a framework-specific execution model.
113
115
 
114
116
  The `GitRepositoryFactsService` port in `src/git/ports.ts` reads HEAD, branch, status, recent log, and tracked files so read-only prompts, whose sessions have no shell, receive them as a `<repository-facts>` block.
115
117
 
@@ -223,7 +223,8 @@ the first prepares the checkout, and the last to finish restores it. If the
223
223
  repository has no `docs/agents/issue-tracker.md` or
224
224
  `docs/agents/triage-labels.md`, Ralphie generates them for the session: the
225
225
  label table comes from `labels`, and the tracker doc says issue content is in
226
- the prompt and sessions must not use `gh`. Committed versions always win.
226
+ the prompt, permits `gh` reads, and instructs sessions not to mutate the
227
+ tracker. Committed versions always win.
227
228
 
228
229
  ### `limits`
229
230
 
@@ -81,8 +81,12 @@ It is never run by `bun run test`, `bun run check`, or CI: it needs live
81
81
  harness logins, model spend, and GitHub credentials. Run it by hand before a
82
82
  release or after changing an adapter, prompt, or hand-off path:
83
83
 
84
+ Live runs are banned by default because they burn model quota: the script
85
+ exits `2` unless `RALPHIE_ALLOW_LIVE_SMOKE=1` is set. Only a human sets it;
86
+ agents must never do so.
87
+
84
88
  ```bash
85
- RALPHIE_SMOKE_SCRATCH_REPO=you/ralphie-scratch \
89
+ RALPHIE_ALLOW_LIVE_SMOKE=1 RALPHIE_SMOKE_SCRATCH_REPO=you/ralphie-scratch \
86
90
  bun run smoke:live -- --scratch-repo you/ralphie-scratch --harness claude,codex
87
91
  ```
88
92
 
@@ -203,10 +207,9 @@ Reviewing a sync pull request:
203
207
 
204
208
  1. Read the upstream diff in the pull request (or its compare link) for each
205
209
  driven skill, not only the vendored paths.
206
- 2. Check whether the new skill text still fits Ralphie's contract: schema-validated
207
- tool results, no agent commits or pushes, direct delivery. Where it does not,
208
- change the Ralphie skill overlay in the same pull request, never the vendored
209
- files.
210
+ 2. Check whether the new skill text fits Ralphie's intended flow: schema-validated
211
+ tool results and direct delivery by deterministic services. Where skill text conflicts, change the Ralphie skill overlay in the same
212
+ pull request, never the vendored files.
210
213
  3. Pull requests opened with the workflow's token do not trigger CI. Close and
211
214
  reopen the pull request, or run `bun run check` on its branch, before merging.
212
215
 
@@ -220,7 +223,8 @@ Before submitting a change:
220
223
 
221
224
  1. Add or update tests for the behavior.
222
225
  2. Run `bun run check`.
223
- 3. Keep Git and GitHub mutations inside their deterministic domain services.
226
+ 3. Keep Ralphie's intended Git and GitHub mutation workflow inside deterministic
227
+ domain services. See [Session authority](safety.md#session-authority).
224
228
  4. Update the authoritative page under [`docs/`](README.md) when documentation
225
229
  changes. Update [`CHANGELOG.md`](../CHANGELOG.md) when the command surface, the
226
230
  output shape, the run-state version, or the recovery contract changes.
@@ -27,9 +27,8 @@ checkout. It brings its own login, so Ralphie asks for no model credentials
27
27
  and stores none. Pick the harness, model and effort per role with the
28
28
  `harnesses` and `roles` keys in the
29
29
  [configuration file](configuration.md#harnesses-and-roles); without them every
30
- role uses Claude Code with its own defaults. Sessions never commit, push, or
31
- mutate GitHub; Ralphie's deterministic services do (see the
32
- [safety model](safety.md#agent-and-mutation-boundaries)).
30
+ role uses Claude Code with its own defaults. Sessions inherit Ralphie's GitHub
31
+ and git authority; see the [safety model](safety.md#session-authority).
33
32
 
34
33
  For interactive GitHub authentication, run `gh auth login` and verify the
35
34
  selected account with `gh auth status`. For unattended runs, supply a token
@@ -131,4 +130,4 @@ issue. See [Workflows](workflows.md) for what the selected route means and
131
130
  [Operations and recovery](operations-and-recovery.md)
132
131
  for the artifacts it leaves behind.
133
132
 
134
- For all available options, continue to the [CLI reference](cli-reference.md).
133
+ For all available options, continue to the [CLI reference](cli-reference.md).
package/docs/safety.md CHANGED
@@ -35,10 +35,12 @@ force-pushed over. Cancellation is checked at every mutation boundary, the push
35
35
  is attempted at most once, and failures and cancellations leave a clean,
36
36
  recoverable checkout.
37
37
 
38
- Implementation agents may use whatever shell their harness grants. Ralphie does
39
- not filter their commands: the guardrails are the session environment (see
40
- [Session isolation](#session-isolation)) and the deterministic repository
41
- invariants and delivery services, which remain authoritative.
38
+ Implementation sessions may use whatever shell their harness grants. Ralphie
39
+ does not filter their commands: sessions inherit the GitHub and git authority
40
+ available to Ralphie, while prompts state the allowed behavior. The remaining
41
+ automated checks are the read-only fingerprint guard, checkpoint restore, and
42
+ delivery's remote verification and non-force push (see
43
+ [Session authority](#session-authority)).
42
44
 
43
45
  ## Workspace risk
44
46
 
@@ -132,43 +134,37 @@ Each session also has a wall-clock limit
132
134
  ([`limits.sessionTimeoutMinutes`](configuration.md#limits)); exceeding it kills
133
135
  the session's whole process group, so tools the harness started die with it.
134
136
 
135
- ## Session isolation
136
-
137
- Sessions never hold GitHub or push authority (ADR-0003); the session
138
- environment enforces it instead of the prompts.
139
-
140
- - **No credentials.** Every session starts without `GH_TOKEN`, `GITHUB_TOKEN`,
141
- `GH_ENTERPRISE_TOKEN` and `GITHUB_ENTERPRISE_TOKEN`, and with `GH_CONFIG_DIR`
142
- pointing at a fresh, empty temporary directory that is removed when the
143
- session ends, so a stored `gh` login is not visible either. These entries
144
- override anything a request sets.
145
- - **No SSH or git credentials.** Every session also starts without
146
- `SSH_AUTH_SOCK`, `SSH_ASKPASS` and `GIT_ASKPASS`, with `GIT_CONFIG_GLOBAL`
147
- and `GIT_CONFIG_SYSTEM` set to `/dev/null` and the repository's
148
- `credential.helper` list reset (so no credential helper applies),
149
- `GIT_TERMINAL_PROMPT=0`, and `GIT_SSH_COMMAND=false`, so a git remote cannot
150
- authenticate through an ssh agent, a helper or a prompt. Ralphie's own
151
- delivery push runs outside sessions and is unaffected.
152
- - **Residual limitation.** The environment cannot hide credentials that a
153
- process running as the same OS user can read directly: a private key under
154
- `~/.ssh` used through an explicit `ssh -i`, a `gh` token read straight out of
155
- the operating system keyring (for example with `security find-generic-password`
156
- on macOS), or a credential file read by path. `gh` itself is logged out in a
157
- session even when its token lives in the keyring, because it finds keyring
158
- entries through the hosts file in its now-empty config directory. Ralphie does not claim to block these, and a determined
159
- yolo session could still use them. What it guarantees is that it never hands
160
- a token to a session, that the checkout's push URL is disabled, and that its
161
- own delivery push is verified against the remote. To close the gap, run
162
- Ralphie as a dedicated OS user whose home, keyring and ssh keys hold no
163
- credentials for the repository.
164
- - **No push from the workspace.** After preparing the checkout Ralphie sets
165
- origin's push URL to a disabled value, so `git push` inside the workspace
166
- fails. Ralphie's own delivery push names the fetch URL explicitly, never
167
- uses force, and is verified against the remote afterwards.
168
- - **Read-only means unchanged.** Before and after every read-only session
137
+ ## Session authority
138
+
139
+ Every session inherits the GitHub and git authority available to Ralphie,
140
+ including its token or `gh auth login` profile and its git credentials. Sessions
141
+ may use `gh` to read issues, pull requests, comments and CI results. Their
142
+ prompts instruct them not to create, edit, comment on, label, close or reopen
143
+ GitHub issues, and not to commit or push. These are behavioral instructions,
144
+ not technical restrictions: a session that ignores them can mutate GitHub or
145
+ commit and push when the inherited credentials allow it. The `origin` push URL
146
+ is not disabled inside the workspace.
147
+
148
+ Ralphie retains three checks around session work:
149
+
150
+ - **Read-only fingerprint guard.** Before and after every read-only session,
169
151
  Ralphie fingerprints HEAD, the index, tracked changes and untracked file
170
- contents. Any difference fails the session (kind `access`), which fails the
171
- issue closed.
152
+ contents. Any difference fails the session (kind `access`) and the issue.
153
+ - **Checkpoint restore.** On a failed issue outcome, Ralphie restores and
154
+ verifies the local checkout against its saved clean issue checkpoint. This
155
+ does not undo a GitHub mutation or a push a session already made.
156
+ - **Verified delivery.** Ralphie's delivery push is non-force, rechecks the
157
+ expected remote branch immediately before pushing, and verifies the remote
158
+ afterwards. Unexpected remote movement, including an early session push,
159
+ stops delivery instead of being adopted or overwritten. This detects the
160
+ movement; it does not reverse the session's push.
161
+
162
+ For a hard boundary on the credentials and repositories available to sessions,
163
+ run Ralphie under a dedicated OS user with only the intended credentials, or
164
+ use a fine-grained GitHub token scoped to the target repository with only the
165
+ permissions Ralphie needs. Sessions inherit those permissions. A repository
166
+ scope limits which repository the token can reach; it does not make access
167
+ read-only within that repository.
172
168
 
173
169
  ## Bounded command execution
174
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beremaran/ralphie",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Turn a GitHub issue queue into reviewed commits with coding-agent harnesses.",
5
5
  "module": "./dist/ralphie.js",
6
6
  "main": "./dist/ralphie.js",