patchrelay 0.1.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -0
  3. package/config/patchrelay.example.json +5 -0
  4. package/dist/build-info.js +29 -0
  5. package/dist/build-info.json +6 -0
  6. package/dist/cli/data.js +461 -0
  7. package/dist/cli/formatters/json.js +3 -0
  8. package/dist/cli/formatters/text.js +119 -0
  9. package/dist/cli/index.js +761 -0
  10. package/dist/codex-app-server.js +353 -0
  11. package/dist/codex-types.js +1 -0
  12. package/dist/config-types.js +1 -0
  13. package/dist/config.js +494 -0
  14. package/dist/db/authoritative-ledger-store.js +437 -0
  15. package/dist/db/issue-workflow-store.js +690 -0
  16. package/dist/db/linear-installation-store.js +184 -0
  17. package/dist/db/migrations.js +183 -0
  18. package/dist/db/shared.js +101 -0
  19. package/dist/db/stage-event-store.js +33 -0
  20. package/dist/db/webhook-event-store.js +46 -0
  21. package/dist/db-ports.js +5 -0
  22. package/dist/db-types.js +1 -0
  23. package/dist/db.js +40 -0
  24. package/dist/file-permissions.js +40 -0
  25. package/dist/http.js +321 -0
  26. package/dist/index.js +69 -0
  27. package/dist/install.js +302 -0
  28. package/dist/installation-ports.js +1 -0
  29. package/dist/issue-query-service.js +68 -0
  30. package/dist/ledger-ports.js +1 -0
  31. package/dist/linear-client.js +338 -0
  32. package/dist/linear-oauth-service.js +131 -0
  33. package/dist/linear-oauth.js +154 -0
  34. package/dist/linear-types.js +1 -0
  35. package/dist/linear-workflow.js +78 -0
  36. package/dist/logging.js +62 -0
  37. package/dist/preflight.js +227 -0
  38. package/dist/project-resolution.js +51 -0
  39. package/dist/reconciliation-action-applier.js +55 -0
  40. package/dist/reconciliation-actions.js +1 -0
  41. package/dist/reconciliation-engine.js +312 -0
  42. package/dist/reconciliation-snapshot-builder.js +96 -0
  43. package/dist/reconciliation-types.js +1 -0
  44. package/dist/runtime-paths.js +89 -0
  45. package/dist/service-queue.js +49 -0
  46. package/dist/service-runtime.js +96 -0
  47. package/dist/service-stage-finalizer.js +348 -0
  48. package/dist/service-stage-runner.js +233 -0
  49. package/dist/service-webhook-processor.js +181 -0
  50. package/dist/service-webhooks.js +148 -0
  51. package/dist/service.js +139 -0
  52. package/dist/stage-agent-activity-publisher.js +33 -0
  53. package/dist/stage-event-ports.js +1 -0
  54. package/dist/stage-failure.js +92 -0
  55. package/dist/stage-launch.js +54 -0
  56. package/dist/stage-lifecycle-publisher.js +213 -0
  57. package/dist/stage-reporting.js +153 -0
  58. package/dist/stage-turn-input-dispatcher.js +102 -0
  59. package/dist/token-crypto.js +21 -0
  60. package/dist/types.js +5 -0
  61. package/dist/utils.js +163 -0
  62. package/dist/webhook-agent-session-handler.js +157 -0
  63. package/dist/webhook-archive.js +24 -0
  64. package/dist/webhook-comment-handler.js +89 -0
  65. package/dist/webhook-desired-stage-recorder.js +150 -0
  66. package/dist/webhook-event-ports.js +1 -0
  67. package/dist/webhook-installation-handler.js +57 -0
  68. package/dist/webhooks.js +301 -0
  69. package/dist/workflow-policy.js +42 -0
  70. package/dist/workflow-ports.js +1 -0
  71. package/dist/workflow-types.js +1 -0
  72. package/dist/worktree-manager.js +66 -0
  73. package/infra/patchrelay-reload.service +6 -0
  74. package/infra/patchrelay.path +11 -0
  75. package/infra/patchrelay.service +28 -0
  76. package/package.json +55 -0
  77. package/runtime.env.example +8 -0
  78. package/service.env.example +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PatchRelay contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # PatchRelay
2
+
3
+ PatchRelay is a self-hosted harness for Linear-driven Codex work on your own machine.
4
+
5
+ It receives Linear webhooks, routes issues to the right local repository, prepares durable issue worktrees, runs staged Codex sessions through `codex app-server`, and keeps the whole run observable and resumable from the CLI.
6
+
7
+ PatchRelay is the system around the model:
8
+
9
+ - webhook intake and verification
10
+ - Linear OAuth and workspace installations
11
+ - issue-to-repo routing
12
+ - issue worktree and branch lifecycle
13
+ - stage orchestration and thread continuity
14
+ - comment forwarding into active runs
15
+ - read-only inspection and stage reporting
16
+
17
+ If you want Codex to work inside your real repos with your real tools, secrets, SSH access, and deployment surface, PatchRelay is the harness that makes that loop reliable.
18
+
19
+ ## Why PatchRelay
20
+
21
+ - Keep the agent in the real environment instead of rebuilding that environment in a hosted sandbox.
22
+ - Use your existing machine, repos, secrets, SSH config, shell tools, and deployment access.
23
+ - Keep deterministic workflow logic outside the model: routing, staging, worktree ownership, and reporting.
24
+ - Choose the Codex approval and sandbox settings that match your risk tolerance.
25
+ - Let Linear drive the loop through delegation, workflow stages, and comments.
26
+ - Drop into the exact issue worktree and resume control manually when needed.
27
+
28
+ ## What PatchRelay Owns
29
+
30
+ PatchRelay does the deterministic harness work that you do not want to re-implement around every model run:
31
+
32
+ - verifies and deduplicates Linear webhooks
33
+ - maps issue events to the correct local project and workflow policy
34
+ - creates and reuses one durable worktree and branch per issue lifecycle
35
+ - starts or forks Codex threads for the workflows you bind to Linear states
36
+ - persists enough state to correlate the Linear issue, local workspace, stage run, and Codex thread
37
+ - reports progress back to Linear and forwards follow-up comments into active runs
38
+ - exposes CLI and optional read-only inspection surfaces so operators can understand what happened
39
+
40
+ ## System Layers
41
+
42
+ PatchRelay works best when read as five layers with clear ownership:
43
+
44
+ - policy layer: repo workflow files and stage prompts
45
+ - coordination layer: issue claiming, stage selection, retries, and reconciliation
46
+ - execution layer: durable worktrees, Codex threads, and queued turn input delivery
47
+ - integration layer: Linear webhooks, OAuth, project routing, and deterministic state sync
48
+ - observability layer: CLI inspection, reports, event trails, and operator endpoints
49
+
50
+ That separation is intentional. PatchRelay is not the policy itself and it is not the coding agent. It is the harness that keeps those pieces coordinated in a real repository with real operational state.
51
+
52
+ ## Runtime Model
53
+
54
+ PatchRelay is designed for a local, operator-owned setup:
55
+
56
+ - PatchRelay service runs on your machine or server
57
+ - Codex runs through `codex app-server`
58
+ - Linear is the control surface
59
+ - `patchrelay` CLI is the operator interface
60
+ - a reverse proxy exposes the Linear-facing routes
61
+
62
+ Linux and Node.js `24+` are the intended runtime.
63
+
64
+ You will also need:
65
+
66
+ - `git`
67
+ - `codex`
68
+ - a Linear OAuth app for this PatchRelay deployment
69
+ - a Linear webhook secret
70
+ - a public HTTPS entrypoint such as Caddy, nginx, or a tunnel so Linear can reach your PatchRelay webhook
71
+
72
+ For the exact OAuth app settings and webhook categories, use the Linear onboarding guide.
73
+
74
+ ## How It Works
75
+
76
+ 1. A human delegates PatchRelay on an issue to start automation, or mentions it to start a conversational agent session.
77
+ 2. PatchRelay verifies the webhook and routes the issue to the right local project.
78
+ 3. Delegated issues create or reuse the issue worktree and launch the matching workflow through `codex app-server`.
79
+ 4. PatchRelay persists thread ids, run state, and observations so the work stays inspectable and resumable.
80
+ 5. Mentions stay conversational, while delegated sessions and issue comments can steer the active run. An operator can take over from the exact same worktree when needed.
81
+
82
+ ## Restart And Reconciliation
83
+
84
+ PatchRelay treats restart safety as part of the harness contract, not as a best-effort extra.
85
+
86
+ After a restart, the service should be able to answer:
87
+
88
+ - which issue owns each active worktree
89
+ - which stage was running or queued
90
+ - which Codex thread and turn belong to that work
91
+ - whether the issue is still eligible to continue
92
+ - whether the run should resume, hand off, or fail back to a human state
93
+
94
+ This is why PatchRelay keeps a small harness ledger alongside Codex thread history and Linear state. The goal is not to duplicate the model transcript. The goal is to make automation restartable, inspectable, and recoverable when the process or machine is interrupted.
95
+
96
+ ## Workflow Configuration
97
+
98
+ PatchRelay keeps workflow configuration simple:
99
+
100
+ - route issues to a project by team, issue prefix, or labels
101
+ - when an issue is delegated to PatchRelay, it looks at the current Linear state
102
+ - that Linear state selects the matching workflow to run
103
+ - that workflow selects the repo-local workflow file
104
+
105
+ Most teams only configure:
106
+
107
+ - which issues belong to which project
108
+ - which Linear states should wake each workflow
109
+ - which workflow file belongs to each workflow
110
+ - which active state PatchRelay should set while that workflow is running
111
+
112
+ Examples:
113
+
114
+ - a standard project can map `Start -> development`, `Review -> review`, and `Deploy -> deploy`
115
+ - a push-to-main project can automate implementation and review, then let GitHub Actions handle deployment while PatchRelay moves failures back to `Human Needed`
116
+ - a project with a QA gate can add a `qa` workflow bound to `Ready for QA`
117
+
118
+ ## Access Control
119
+
120
+ PatchRelay reacts only for issues that route to a configured project.
121
+
122
+ - use `linear_team_ids`, `issue_key_prefixes`, and optional labels to keep unrelated or public boards out of scope
123
+ - in the normal setup, anyone with access to the routed Linear project can delegate work to the PatchRelay app
124
+ - use `trusted_actors` only when a project needs a narrower allowlist inside Linear
125
+
126
+ That keeps the default model simple without forcing an extra allowlist for every team.
127
+
128
+ ## Quick Start
129
+
130
+ ### 1. Install
131
+
132
+ ```bash
133
+ npm install -g patchrelay
134
+ ```
135
+
136
+ ### 2. Bootstrap config
137
+
138
+ ```bash
139
+ patchrelay init https://patchrelay.example.com
140
+ ```
141
+
142
+ `patchrelay init` requires the public HTTPS origin up front because Linear needs a fixed webhook URL and OAuth callback URL for this PatchRelay instance.
143
+
144
+ It creates the local config, env file, and user service units:
145
+
146
+ - `~/.config/patchrelay/runtime.env`
147
+ - `~/.config/patchrelay/service.env`
148
+ - `~/.config/patchrelay/patchrelay.json`
149
+ - `~/.config/systemd/user/patchrelay.service`
150
+ - `~/.config/systemd/user/patchrelay-reload.service`
151
+ - `~/.config/systemd/user/patchrelay.path`
152
+
153
+ The generated `patchrelay.json` is intentionally minimal, and `patchrelay init` prints the webhook URL, OAuth callback URL, and the Linear app values you need next.
154
+
155
+ ### 3. Configure access
156
+
157
+ Edit `~/.config/patchrelay/service.env` and fill in only the Linear OAuth client values. Keep the generated webhook secret and token-encryption key:
158
+
159
+ ```bash
160
+ LINEAR_WEBHOOK_SECRET=generated-by-patchrelay-init
161
+ PATCHRELAY_TOKEN_ENCRYPTION_KEY=generated-by-patchrelay-init
162
+ LINEAR_OAUTH_CLIENT_ID=replace-with-linear-oauth-client-id
163
+ LINEAR_OAUTH_CLIENT_SECRET=replace-with-linear-oauth-client-secret
164
+ ```
165
+
166
+ Keep service secrets in `service.env`. `runtime.env` is for non-secret overrides such as `PATCHRELAY_DB_PATH` or `PATCHRELAY_LOG_FILE`. Everyday local inspection commands do not require exporting these values in your shell.
167
+
168
+ ### 4. Configure a project
169
+
170
+ Add repositories after `patchrelay init` with `patchrelay project apply <id> <repo-path>`.
171
+
172
+ For a single project, that is usually enough. For multiple projects, add routing with `--issue-prefix APP` or `--team-id <linear-team-id>`.
173
+
174
+ The generated `~/.config/patchrelay/patchrelay.json` is machine-level service config only. Project entries should be created with the CLI, not by hand-editing a placeholder template.
175
+
176
+ `patchrelay project apply` is idempotent:
177
+
178
+ - it creates or updates the local project entry
179
+ - it checks whether PatchRelay is ready
180
+ - it reloads the service when it can
181
+ - it reuses or starts the Linear connect flow when the local setup is ready
182
+ - if workflow files or secrets are still missing, it tells you exactly what to fix and can be rerun safely
183
+
184
+ ### 5. Add workflow docs to the repo
185
+
186
+ By default PatchRelay looks for:
187
+
188
+ ```text
189
+ IMPLEMENTATION_WORKFLOW.md
190
+ REVIEW_WORKFLOW.md
191
+ DEPLOY_WORKFLOW.md
192
+ CLEANUP_WORKFLOW.md
193
+ ```
194
+
195
+ These files define how the agent should work in that repo.
196
+
197
+ ### 6. Validate
198
+
199
+ ```bash
200
+ patchrelay doctor
201
+ ```
202
+
203
+ ### 7. Check the installation
204
+
205
+ ```bash
206
+ patchrelay installations
207
+ ```
208
+
209
+ In the normal happy path, the earlier `patchrelay project apply <id> <repo-path>` command already handles the connect step for you. `patchrelay connect --project <id>` still exists as the advanced/manual command when you want to retry or debug only the Linear authorization layer.
210
+
211
+ If you later add another local repo that should use the same Linear installation, run `patchrelay project apply <id> <repo-path>` for that repo too. PatchRelay now reuses the single saved installation automatically when there is no ambiguity, so you usually will not need another browser approval.
212
+
213
+ Important:
214
+
215
+ - Linear needs a public HTTPS URL to reach your webhook.
216
+ - `patchrelay init <public-base-url>` writes `server.public_base_url`, which PatchRelay uses when it prints webhook URLs.
217
+ - For ingress, OAuth app setup, and webhook details, use the self-hosting and Linear onboarding docs.
218
+
219
+ ## Daily Loop
220
+
221
+ 1. Delegate a Linear issue to the PatchRelay app.
222
+ 2. PatchRelay reads the current Linear state like `Start`, `Ready for QA`, or `Deploy` to choose the matching workflow.
223
+ 3. Linear sends the delegation and agent-session webhooks to PatchRelay, which creates or reuses the issue worktree and launches the matching workflow.
224
+ 4. Follow up in the issue comments to steer the active run or wake it with fresh input while it remains delegated.
225
+ 5. Watch progress from the terminal or open the same worktree and take over manually.
226
+
227
+ Useful commands:
228
+
229
+ - `patchrelay list --active`
230
+ - `patchrelay inspect APP-123`
231
+ - `patchrelay live APP-123 --watch`
232
+ - `patchrelay report APP-123`
233
+ - `patchrelay events APP-123 --follow`
234
+ - `patchrelay worktree APP-123 --cd`
235
+ - `patchrelay open APP-123`
236
+ - `patchrelay retry APP-123 --stage review`
237
+
238
+ `patchrelay open` is the handoff bridge: it opens Codex in the issue worktree and resumes the existing thread when PatchRelay has one.
239
+
240
+ Today that takeover path is intentionally YOLO mode: it launches Codex with `--dangerously-bypass-approvals-and-sandbox`.
241
+
242
+ ## Operator View
243
+
244
+ PatchRelay keeps enough durable state to answer the questions that matter during and after a run:
245
+
246
+ - which worktree and branch belong to an issue
247
+ - which stage is active or queued
248
+ - which Codex thread owns the current work
249
+ - what the agent said
250
+ - which commands it ran
251
+ - which files it changed
252
+ - whether the stage completed, failed, or needs handoff
253
+
254
+ ## Docs
255
+
256
+ Use the README for the product overview and quick start. Use the docs for operating details:
257
+
258
+ - [Self-hosting and deployment](https://github.com/krasnoperov/patchrelay/blob/main/docs/self-hosting.md)
259
+ - [Linear agent onboarding](https://github.com/krasnoperov/patchrelay/blob/main/docs/linear-agent-onboarding.md)
260
+ - [CLI reference](https://github.com/krasnoperov/patchrelay/blob/main/docs/cli-reference.md)
261
+ - [Architecture](https://github.com/krasnoperov/patchrelay/blob/main/docs/architecture.md)
262
+ - [Module map](https://github.com/krasnoperov/patchrelay/blob/main/docs/module-map.md)
263
+ - [Authoritative vs derived state](https://github.com/krasnoperov/patchrelay/blob/main/docs/state-authority.md)
264
+ - [Persistence audit](https://github.com/krasnoperov/patchrelay/blob/main/docs/persistence-audit.md)
265
+ - [Codex integration details](https://github.com/krasnoperov/patchrelay/blob/main/docs/codex-workflow.md)
266
+ - [Workflow file requirements](https://github.com/krasnoperov/patchrelay/blob/main/docs/IMPLEMENTATION_WORKFLOW_REQUIREMENTS.md)
267
+ - [Security policy](https://github.com/krasnoperov/patchrelay/blob/main/SECURITY.md)
268
+
269
+ ## Status
270
+
271
+ PatchRelay is usable now, but still early and opinionated. The focus is a strong self-hosted harness for Linear + Codex work, not a generalized SaaS control plane.
@@ -0,0 +1,5 @@
1
+ {
2
+ "server": {
3
+ "public_base_url": "https://patchrelay.example.com"
4
+ }
5
+ }
@@ -0,0 +1,29 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { getBundledAssetPath } from "./runtime-paths.js";
4
+ const fallbackBuildInfo = {
5
+ service: "patchrelay",
6
+ version: "0.1.0",
7
+ commit: "unknown",
8
+ builtAt: "unknown",
9
+ };
10
+ export function getBuildInfo() {
11
+ const buildInfoPath = path.resolve(process.cwd(), "dist/build-info.json");
12
+ const fallbackPath = getBundledAssetPath("dist/build-info.json");
13
+ const resolvedPath = existsSync(buildInfoPath) ? buildInfoPath : fallbackPath;
14
+ if (!existsSync(resolvedPath)) {
15
+ return fallbackBuildInfo;
16
+ }
17
+ try {
18
+ const parsed = JSON.parse(readFileSync(resolvedPath, "utf8"));
19
+ return {
20
+ service: typeof parsed.service === "string" ? parsed.service : fallbackBuildInfo.service,
21
+ version: typeof parsed.version === "string" ? parsed.version : fallbackBuildInfo.version,
22
+ commit: typeof parsed.commit === "string" ? parsed.commit : fallbackBuildInfo.commit,
23
+ builtAt: typeof parsed.builtAt === "string" ? parsed.builtAt : fallbackBuildInfo.builtAt,
24
+ };
25
+ }
26
+ catch {
27
+ return fallbackBuildInfo;
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "service": "patchrelay",
3
+ "version": "0.1.0",
4
+ "commit": "c0fb14e9ba7f",
5
+ "builtAt": "2026-03-12T18:50:58.770Z"
6
+ }