patchrelay 0.50.1 → 0.50.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/README.md +2 -2
- package/config/patchrelay.example.json +2 -1
- package/dist/build-info.json +3 -3
- package/dist/completion-check.js +6 -0
- package/dist/config.js +23 -3
- package/dist/prompting/patchrelay.js +107 -164
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,12 +61,12 @@ patchrelay service status
|
|
|
61
61
|
patchrelay dashboard
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Each repo needs two workflow files
|
|
64
|
+
Each repo needs two workflow files for repo-specific run behavior:
|
|
65
65
|
|
|
66
66
|
- `IMPLEMENTATION_WORKFLOW.md` — implementation, CI repair, queue repair runs
|
|
67
67
|
- `REVIEW_WORKFLOW.md` — review fix runs
|
|
68
68
|
|
|
69
|
-
Keep them short, action-oriented, human-authored. See [prompting.md](./docs/prompting.md) for how the built-in scaffold composes them
|
|
69
|
+
Keep them short, action-oriented, human-authored. Durable machine-level policy belongs in Codex `developer_instructions`; workflow files are for repo-local behavior and validation. See [prompting.md](./docs/prompting.md) for how the built-in scaffold composes them.
|
|
70
70
|
|
|
71
71
|
Full install, ingress, and GitHub/Linear app setup: [self-hosting.md](./docs/self-hosting.md). Daily ops and CLI cheatsheet: [operator-guide.md](./docs/operator-guide.md).
|
|
72
72
|
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"git_bin": "git",
|
|
7
7
|
"codex": {
|
|
8
8
|
"model": "gpt-5.3-codex-spark",
|
|
9
|
-
"reasoning_effort": "high"
|
|
9
|
+
"reasoning_effort": "high",
|
|
10
|
+
"developer_instructions": "Keep the public API stable for this installation unless the task explicitly requires a breaking change."
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
}
|
package/dist/build-info.json
CHANGED
package/dist/completion-check.js
CHANGED
|
@@ -101,6 +101,12 @@ function buildCompletionCheckPrompt(params) {
|
|
|
101
101
|
'- "done" if the task was successfully completed without a PR.',
|
|
102
102
|
'- "failed" if the run stopped incorrectly and PatchRelay should not auto-continue.',
|
|
103
103
|
"",
|
|
104
|
+
"Bias rules:",
|
|
105
|
+
'- Prefer "continue" when the task looks like normal code-delivery work and the run simply ended before publishing a branch or PR.',
|
|
106
|
+
'- Use "done" only when the issue explicitly permits a no-PR outcome or the deliverable is clearly non-repo work such as planning, orchestration, or follow-up issue creation.',
|
|
107
|
+
'- Use "needs_input" only when one specific missing human decision blocks the very next concrete action. Do not use it just because the branch might need more work.',
|
|
108
|
+
'- If the run appears unfinished but still actionable by PatchRelay, return "continue", not "done".',
|
|
109
|
+
"",
|
|
104
110
|
"Facts:",
|
|
105
111
|
`- Issue: ${params.issue.issueKey ?? params.issue.linearIssueId}`,
|
|
106
112
|
...(params.issue.title ? [`- Title: ${params.issue.title}`] : []),
|
package/dist/config.js
CHANGED
|
@@ -9,6 +9,21 @@ import { ensureAbsolutePath } from "./utils.js";
|
|
|
9
9
|
const LINEAR_OAUTH_CALLBACK_PATH = "/oauth/linear/callback";
|
|
10
10
|
const REPO_SETTINGS_DIRNAME = ".patchrelay";
|
|
11
11
|
const REPO_SETTINGS_FILENAME = "project.json";
|
|
12
|
+
const DEFAULT_PATCHRELAY_DEVELOPER_INSTRUCTIONS = [
|
|
13
|
+
"You are PatchRelay's coding agent.",
|
|
14
|
+
"",
|
|
15
|
+
"Core rules:",
|
|
16
|
+
"- Complete the delegated task in the current repository and worktree.",
|
|
17
|
+
"- Stay inside the issue's scope. Do not widen into unrelated cleanup or polish.",
|
|
18
|
+
"- Use repository docs and workflow files as the source of truth for local conventions.",
|
|
19
|
+
"- Prefer concrete fixes over speculative defenses.",
|
|
20
|
+
"- For code-delivery work, publish before stopping.",
|
|
21
|
+
"- If you change files for an implementation run, commit, push the issue branch, and open or update the PR.",
|
|
22
|
+
"- For repair runs, work on the existing PR branch and do not open a new PR.",
|
|
23
|
+
"- A requested-changes repair is only complete after a newer PR head is pushed, unless a genuine external blocker prevents correct publication.",
|
|
24
|
+
"- If a broader inconsistency is not required to make this task correct, mention it briefly instead of expanding scope.",
|
|
25
|
+
"- Before publishing, do one brief reviewer-minded pass on the current head and fix likely in-scope blockers.",
|
|
26
|
+
].join("\n");
|
|
12
27
|
const trustedActorsSchema = z
|
|
13
28
|
.object({
|
|
14
29
|
ids: z.array(z.string().min(1)).default([]),
|
|
@@ -238,6 +253,13 @@ function loadPromptLayer(configDir, layer) {
|
|
|
238
253
|
replaceSections: Object.fromEntries(Object.entries(layer.replace_sections).map(([sectionId, fragmentPath]) => [sectionId, readPromptFile(configDir, fragmentPath)])),
|
|
239
254
|
};
|
|
240
255
|
}
|
|
256
|
+
function mergeDeveloperInstructions(custom) {
|
|
257
|
+
const normalized = custom?.trim();
|
|
258
|
+
if (!normalized) {
|
|
259
|
+
return DEFAULT_PATCHRELAY_DEVELOPER_INSTRUCTIONS;
|
|
260
|
+
}
|
|
261
|
+
return `${DEFAULT_PATCHRELAY_DEVELOPER_INSTRUCTIONS}\n\n## Local Developer Instructions\n\n${normalized}`;
|
|
262
|
+
}
|
|
241
263
|
function expandEnv(value, env) {
|
|
242
264
|
if (typeof value === "string") {
|
|
243
265
|
return value.replace(/\$\{([A-Z0-9_]+)(?::-(.*?))?\}/g, (_match, name, fallback) => {
|
|
@@ -548,9 +570,7 @@ export function loadConfig(configPath = process.env.PATCHRELAY_CONFIG ?? getDefa
|
|
|
548
570
|
...(parsed.runner.codex.reasoning_effort ? { reasoningEffort: parsed.runner.codex.reasoning_effort } : {}),
|
|
549
571
|
...(parsed.runner.codex.service_name ? { serviceName: parsed.runner.codex.service_name } : {}),
|
|
550
572
|
...(parsed.runner.codex.base_instructions ? { baseInstructions: parsed.runner.codex.base_instructions } : {}),
|
|
551
|
-
|
|
552
|
-
? { developerInstructions: parsed.runner.codex.developer_instructions }
|
|
553
|
-
: {}),
|
|
573
|
+
developerInstructions: mergeDeveloperInstructions(parsed.runner.codex.developer_instructions),
|
|
554
574
|
approvalPolicy: parsed.runner.codex.approval_policy,
|
|
555
575
|
sandboxMode: parsed.runner.codex.sandbox_mode,
|
|
556
576
|
persistExtendedHistory: parsed.runner.codex.persist_extended_history,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
const WORKFLOW_FILES = {
|
|
4
4
|
implementation: "IMPLEMENTATION_WORKFLOW.md",
|
|
@@ -22,12 +22,10 @@ export const PATCHRELAY_REPLACEABLE_SECTION_IDS = [
|
|
|
22
22
|
"workflow-guidance",
|
|
23
23
|
"publication-contract",
|
|
24
24
|
];
|
|
25
|
-
function
|
|
25
|
+
function hasWorkflowFile(repoPath, runType) {
|
|
26
26
|
const filename = WORKFLOW_FILES[runType];
|
|
27
27
|
const filePath = path.join(repoPath, filename);
|
|
28
|
-
|
|
29
|
-
return undefined;
|
|
30
|
-
return readFileSync(filePath, "utf8").trim();
|
|
28
|
+
return existsSync(filePath);
|
|
31
29
|
}
|
|
32
30
|
function buildPromptHeader(issue) {
|
|
33
31
|
return [
|
|
@@ -102,7 +100,7 @@ function summarizeRelationEntries(entries, options) {
|
|
|
102
100
|
}
|
|
103
101
|
return lines;
|
|
104
102
|
}
|
|
105
|
-
function
|
|
103
|
+
function buildIssueTopology(context) {
|
|
106
104
|
const unresolvedBlockers = Array.isArray(context?.unresolvedBlockers)
|
|
107
105
|
? context.unresolvedBlockers.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
108
106
|
: [];
|
|
@@ -111,18 +109,10 @@ function buildCoordinationGuidance(context) {
|
|
|
111
109
|
: Array.isArray(context?.trackedDependents)
|
|
112
110
|
? context.trackedDependents.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
113
111
|
: [];
|
|
114
|
-
const lines = [
|
|
115
|
-
"### Coordination / Issue Topology",
|
|
116
|
-
"",
|
|
117
|
-
"First decide whether this issue should publish code itself or mainly coordinate other issues.",
|
|
118
|
-
"If this issue is a parent tracker, umbrella, migration program, or convergence container and the concrete implementation belongs in child issues, do not create a duplicate umbrella PR.",
|
|
119
|
-
"When child issues already own the concrete code slices, use this issue to coordinate, create or refine follow-up issues, or verify convergence. Only ship code here if this issue still has unique implementation scope that is not already owned elsewhere.",
|
|
120
|
-
"Prefer one PR per concrete implementation issue over a broad parent branch that restates overlapping child work.",
|
|
121
|
-
];
|
|
122
112
|
if (unresolvedBlockers.length === 0 && childIssues.length === 0) {
|
|
123
|
-
return
|
|
113
|
+
return [];
|
|
124
114
|
}
|
|
125
|
-
lines
|
|
115
|
+
const lines = ["### Issue Topology", ""];
|
|
126
116
|
if (unresolvedBlockers.length > 0) {
|
|
127
117
|
lines.push("Unresolved blockers:");
|
|
128
118
|
lines.push(...summarizeRelationEntries(unresolvedBlockers));
|
|
@@ -136,35 +126,25 @@ function buildCoordinationGuidance(context) {
|
|
|
136
126
|
}
|
|
137
127
|
return lines;
|
|
138
128
|
}
|
|
139
|
-
function
|
|
129
|
+
function buildConstraints(issue, context) {
|
|
140
130
|
const description = issue.description?.trim();
|
|
141
131
|
const scope = extractIssueSection(description, "Scope");
|
|
142
132
|
const acceptance = extractIssueSection(description, "Acceptance criteria")
|
|
143
133
|
?? extractIssueSection(description, "Success criteria");
|
|
144
134
|
const relevantCode = extractIssueSection(description, "Relevant code");
|
|
135
|
+
const topology = buildIssueTopology(context);
|
|
145
136
|
return [
|
|
146
|
-
"##
|
|
137
|
+
"## Constraints",
|
|
147
138
|
"",
|
|
148
|
-
"Stay inside the delegated task.",
|
|
149
|
-
"Finish the issue completely enough to satisfy its stated scope and acceptance criteria, but do not widen it into unrelated product polish or follow-up cleanup.",
|
|
150
|
-
"Only broaden to adjacent routes, copy, or supporting surfaces when the issue text or repository guidance explicitly says they are the same user flow.",
|
|
151
|
-
"Your implementation goal is to leave the branch review-ready, not merely locally working: look for likely regressions or invariant breaks in the touched flow before you stop.",
|
|
152
|
-
"If a narrow patch fixes the immediate symptom but leaves the same underlying risk elsewhere in the changed flow, keep going until the branch is likely to survive strict review or clearly explain the blocker.",
|
|
153
|
-
"If you notice a worthwhile broader inconsistency that is not required to make this task correct, mention it in your summary as follow-up context instead of expanding the implementation.",
|
|
139
|
+
"Stay inside the delegated task. Do not widen scope into unrelated cleanup or optional polish.",
|
|
154
140
|
"",
|
|
155
141
|
...(scope ? ["### In Scope", "", scope, ""] : []),
|
|
156
142
|
...(acceptance ? ["### Acceptance / Done", "", acceptance, ""] : []),
|
|
157
143
|
...(relevantCode ? ["### Relevant Code", "", relevantCode, ""] : []),
|
|
158
|
-
...
|
|
159
|
-
"",
|
|
160
|
-
"### Likely Review Invariants",
|
|
161
|
-
"",
|
|
162
|
-
"- Check the surfaces explicitly named in the task before stopping.",
|
|
163
|
-
"- If repository guidance says certain changed surfaces are one flow, verify that shared flow, but do not treat unrelated surrounding cleanup as part of this task.",
|
|
164
|
-
"- A review repair should fix the concrete concern on the current head, not silently expand the Linear issue into a broader rewrite.",
|
|
144
|
+
...topology,
|
|
165
145
|
].join("\n");
|
|
166
146
|
}
|
|
167
|
-
function
|
|
147
|
+
function buildOrchestrationConstraints(context) {
|
|
168
148
|
const unresolvedBlockers = Array.isArray(context?.unresolvedBlockers)
|
|
169
149
|
? context.unresolvedBlockers.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
170
150
|
: [];
|
|
@@ -174,23 +154,14 @@ function buildOrchestrationScopeDiscipline(context) {
|
|
|
174
154
|
? context.trackedDependents.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
175
155
|
: [];
|
|
176
156
|
return [
|
|
177
|
-
"##
|
|
157
|
+
"## Constraints",
|
|
178
158
|
"",
|
|
179
|
-
"This issue is orchestration work.",
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"When you split required implementation work out of the parent, make it a child issue of this umbrella rather than making the parent block the child.",
|
|
186
|
-
"If sequencing matters, let the parent wait on the child; do not model the child as blocked by the parent umbrella it is supposed to satisfy.",
|
|
187
|
-
"If child work is still in motion, babysit the plan, record useful observations, and return to waiting.",
|
|
188
|
-
"If child work looks delivered, audit whether the original parent goal is actually satisfied.",
|
|
189
|
-
"Create blocking follow-up work only when it is necessary to satisfy the original parent goal.",
|
|
190
|
-
"Prefer non-blocking follow-up issues over keeping the umbrella open for optional polish or adjacent expansion.",
|
|
191
|
-
"New child issues should stay in Backlog and undelegated by default.",
|
|
192
|
-
"Only delegate or move a new child to Start when it is immediately actionable, unblocked, and you intend for PatchRelay to begin it right away.",
|
|
193
|
-
"If you create multiple new child issues, keep later-wave or dependency-bound children queued rather than waking them all at once.",
|
|
159
|
+
"This issue is orchestration work. Coordinate convergence instead of duplicating child implementation.",
|
|
160
|
+
"Inspect the current child set before acting. Reuse existing child issues when they already cover the needed slices instead of creating duplicates.",
|
|
161
|
+
"Babysit child progress and solve parent-owned integration or convergence issues when the delivered pieces do not yet fit together cleanly.",
|
|
162
|
+
"Do not open an overlapping umbrella PR unless this parent owns unique direct work.",
|
|
163
|
+
"Create new child issues only for genuinely missing required work needed to satisfy the parent goal.",
|
|
164
|
+
"Leave later-wave child issues queued unless they are immediately actionable.",
|
|
194
165
|
"",
|
|
195
166
|
"### Child Issue Summaries",
|
|
196
167
|
"",
|
|
@@ -204,29 +175,28 @@ function buildOrchestrationScopeDiscipline(context) {
|
|
|
204
175
|
"### Convergence Rule",
|
|
205
176
|
"",
|
|
206
177
|
"- Close the umbrella when the original parent goal is satisfied.",
|
|
207
|
-
"-
|
|
208
|
-
"- Do not invent optional expansion without explicit human approval.",
|
|
178
|
+
"- Create blocking follow-up work only when it is required to satisfy that goal.",
|
|
209
179
|
].join("\n");
|
|
210
180
|
}
|
|
211
|
-
function
|
|
181
|
+
function buildHumanContextLines(context) {
|
|
212
182
|
const promptContext = typeof context?.promptContext === "string" ? context.promptContext.trim() : "";
|
|
213
183
|
const latestPrompt = typeof context?.promptBody === "string" ? context.promptBody.trim() : "";
|
|
214
184
|
const operatorPrompt = typeof context?.operatorPrompt === "string" ? context.operatorPrompt.trim() : "";
|
|
215
185
|
const userComment = typeof context?.userComment === "string" ? context.userComment.trim() : "";
|
|
216
186
|
const lines = [];
|
|
217
187
|
if (promptContext) {
|
|
218
|
-
lines.push("
|
|
188
|
+
lines.push("Linear session context:", promptContext, "");
|
|
219
189
|
}
|
|
220
190
|
if (latestPrompt) {
|
|
221
|
-
lines.push("
|
|
191
|
+
lines.push("Latest human instruction:", latestPrompt, "");
|
|
222
192
|
}
|
|
223
193
|
if (operatorPrompt) {
|
|
224
|
-
lines.push("
|
|
194
|
+
lines.push("Operator prompt:", operatorPrompt, "");
|
|
225
195
|
}
|
|
226
196
|
if (userComment) {
|
|
227
|
-
lines.push("
|
|
197
|
+
lines.push("Human follow-up comment:", userComment, "");
|
|
228
198
|
}
|
|
229
|
-
return lines
|
|
199
|
+
return lines;
|
|
230
200
|
}
|
|
231
201
|
function resolveRequestedChangesMode(runType, context) {
|
|
232
202
|
if (runType === "branch_upkeep") {
|
|
@@ -303,12 +273,12 @@ function buildRequestedChangesContext(runType, context) {
|
|
|
303
273
|
const mode = resolveRequestedChangesMode(runType, context);
|
|
304
274
|
const lines = [];
|
|
305
275
|
if (mode === "branch_upkeep") {
|
|
306
|
-
lines.push("
|
|
276
|
+
lines.push("Branch upkeep is required on the existing PR branch.", "Goal: restore merge readiness on the current branch and push a newer head without regressing review or CI readiness.");
|
|
307
277
|
}
|
|
308
278
|
else {
|
|
309
279
|
const reviewer = typeof context?.reviewerName === "string" ? context.reviewerName : undefined;
|
|
310
280
|
const reviewBody = typeof context?.reviewBody === "string" ? context.reviewBody.trim() : "";
|
|
311
|
-
lines.push("
|
|
281
|
+
lines.push("Requested changes on the existing PR branch.", "Goal: restore review readiness and push a newer head on the current PR branch.", "Address the real concern behind the feedback and verify nearby invariants in the touched flow before you publish.", reviewer ? `Reviewer: ${reviewer}` : "", reviewBody ? `Review summary:\n${reviewBody}` : "");
|
|
312
282
|
appendStructuredReviewContext(lines, context);
|
|
313
283
|
}
|
|
314
284
|
return lines.join("\n").trim();
|
|
@@ -318,10 +288,8 @@ function buildCiRepairContext(context) {
|
|
|
318
288
|
? context.ciSnapshot
|
|
319
289
|
: undefined;
|
|
320
290
|
return [
|
|
321
|
-
"
|
|
322
|
-
"",
|
|
323
|
-
"Goal: restore CI readiness on the existing PR branch so the next full CI run is likely to pass.",
|
|
324
|
-
"A full CI iteration has settled failed on your PR. Start from the specific failing check/job/step below on the latest remote PR branch tip, but do not stop at a narrow patch if the same root cause is likely to fail other checks in the suite.",
|
|
291
|
+
"Settled CI failure on the existing PR branch.",
|
|
292
|
+
"Goal: restore CI readiness and push a branch that is likely to pass the next full CI run.",
|
|
325
293
|
snapshot?.gateCheckName ? `Gate check: ${String(snapshot.gateCheckName)}` : "",
|
|
326
294
|
snapshot?.gateCheckStatus ? `Gate status: ${String(snapshot.gateCheckStatus)}` : "",
|
|
327
295
|
snapshot?.settledAt ? `Settled at: ${String(snapshot.settledAt)}` : "",
|
|
@@ -337,14 +305,6 @@ function buildCiRepairContext(context) {
|
|
|
337
305
|
Array.isArray(context?.annotations) && context.annotations.length > 0
|
|
338
306
|
? `Annotations:\n${context.annotations.map((entry) => `- ${String(entry)}`).join("\n")}`
|
|
339
307
|
: "",
|
|
340
|
-
"",
|
|
341
|
-
"Fetch the latest remote branch state first. If the branch moved since this failure, restart from the new tip instead of pushing older work.",
|
|
342
|
-
"Read the latest logs for the named failing check, identify the root cause, and check whether that same cause is likely to affect other jobs or checks.",
|
|
343
|
-
"Fix the root cause, not just the first visible symptom.",
|
|
344
|
-
"Do not change workflows, dependency installation, or unrelated tests unless the failing logs clearly point there.",
|
|
345
|
-
"Run the narrowest local verification that gives real confidence for the suite, then commit and push.",
|
|
346
|
-
"Do not open a new PR. Keep working on the existing branch until the branch is likely to pass CI again or the situation is clearly stuck.",
|
|
347
|
-
"Do not change test expectations unless the test is genuinely wrong.",
|
|
348
308
|
].filter(Boolean).join("\n");
|
|
349
309
|
}
|
|
350
310
|
function appendQueueRepairContext(lines, context) {
|
|
@@ -388,67 +348,57 @@ function appendQueueRepairContext(lines, context) {
|
|
|
388
348
|
function buildQueueRepairContext(context) {
|
|
389
349
|
const lines = [];
|
|
390
350
|
appendQueueRepairContext(lines, context);
|
|
391
|
-
lines.push("
|
|
351
|
+
lines.push("Merge queue rejection on the existing PR branch.", "Goal: restore a mergeable branch, verify the queue-blocking fix, and push the existing PR branch.", context?.failureReason ? `Failure reason: ${String(context.failureReason)}` : "");
|
|
392
352
|
return lines.filter(Boolean).join("\n");
|
|
393
353
|
}
|
|
394
|
-
function
|
|
354
|
+
function buildFollowUpContextLines(issue, runType, context) {
|
|
395
355
|
const wakeReason = typeof context?.wakeReason === "string" ? context.wakeReason : undefined;
|
|
396
356
|
const followUps = Array.isArray(context?.followUps) ? context.followUps : [];
|
|
397
357
|
const followUpLines = followUps
|
|
398
358
|
.filter((entry) => Boolean(entry) && typeof entry === "object")
|
|
399
359
|
.map((entry) => `${String(entry.type ?? "follow_up")} from ${String(entry.author ?? "unknown")}: ${String(entry.text ?? "").trim()}`.trim())
|
|
400
360
|
.filter((line) => !line.endsWith(":"));
|
|
401
|
-
const lines = [
|
|
402
|
-
|
|
403
|
-
""
|
|
404
|
-
wakeReason === "
|
|
405
|
-
? "
|
|
406
|
-
: wakeReason === "
|
|
407
|
-
? "
|
|
408
|
-
: wakeReason === "
|
|
409
|
-
? "
|
|
410
|
-
: wakeReason === "
|
|
411
|
-
? "
|
|
412
|
-
: wakeReason === "
|
|
413
|
-
? "
|
|
414
|
-
: wakeReason === "
|
|
415
|
-
? "
|
|
416
|
-
: wakeReason === "
|
|
417
|
-
? "
|
|
418
|
-
: wakeReason === "
|
|
419
|
-
? "
|
|
420
|
-
:
|
|
421
|
-
|
|
422
|
-
: `Why this turn exists: Continue the existing ${runType} run from the latest issue state.`,
|
|
423
|
-
wakeReason === "direct_reply"
|
|
424
|
-
? "Required action now: Apply the latest human answer, continue from the current branch/session context, and publish the next concrete result."
|
|
425
|
-
: wakeReason === "initial_delegate"
|
|
426
|
-
? "Required action now: Inspect the umbrella goal, review the child set, and record the next orchestration step."
|
|
427
|
-
: wakeReason === "completion_check_continue"
|
|
428
|
-
? "Required action now: Continue from the current branch and thread context, finish the task, and publish the next concrete result."
|
|
429
|
-
: "Required action now: Continue from the latest branch state, refresh any stale assumptions, and publish the next concrete result.",
|
|
430
|
-
"",
|
|
431
|
-
];
|
|
361
|
+
const lines = [];
|
|
362
|
+
const turnReason = wakeReason === "direct_reply"
|
|
363
|
+
? "Human reply to the previous question."
|
|
364
|
+
: wakeReason === "initial_delegate"
|
|
365
|
+
? "Initial orchestration turn after delegation."
|
|
366
|
+
: wakeReason === "child_delivered"
|
|
367
|
+
? "A child issue was delivered."
|
|
368
|
+
: wakeReason === "child_changed"
|
|
369
|
+
? "A child issue changed state."
|
|
370
|
+
: wakeReason === "child_regressed"
|
|
371
|
+
? "A child issue regressed."
|
|
372
|
+
: wakeReason === "human_instruction"
|
|
373
|
+
? "A human added new orchestration guidance."
|
|
374
|
+
: wakeReason === "completion_check_continue"
|
|
375
|
+
? "The previous turn ended without a PR and PatchRelay chose to continue automatically."
|
|
376
|
+
: wakeReason === "branch_upkeep"
|
|
377
|
+
? "GitHub still shows the PR branch as needing upkeep."
|
|
378
|
+
: wakeReason === "followup_comment"
|
|
379
|
+
? "A human follow-up comment arrived after the previous turn."
|
|
380
|
+
: `Continue the existing ${runType} run from the latest issue state.`;
|
|
381
|
+
lines.push(`Turn reason: ${turnReason}`);
|
|
432
382
|
if (wakeReason === "completion_check_continue" && typeof context?.completionCheckSummary === "string" && context.completionCheckSummary.trim()) {
|
|
433
|
-
lines.push(`Completion check summary: ${context.completionCheckSummary.trim()}
|
|
383
|
+
lines.push(`Completion check summary: ${context.completionCheckSummary.trim()}`);
|
|
434
384
|
}
|
|
435
385
|
if (followUpLines.length > 0) {
|
|
436
|
-
lines.push("Recent updates:");
|
|
386
|
+
lines.push("", "Recent updates:");
|
|
437
387
|
followUpLines.forEach((line) => lines.push(`- ${line}`));
|
|
438
|
-
lines.push("");
|
|
439
388
|
}
|
|
440
389
|
if (issue.prNumber || issue.prHeadSha || issue.prReviewState || context?.mergeStateStatus) {
|
|
441
|
-
lines.push("
|
|
390
|
+
lines.push("", "Current PR facts:", `Fact freshness: ${context?.githubFactsFresh === true
|
|
442
391
|
? "refreshed immediately before this turn was created."
|
|
443
392
|
: "may now be stale; refresh before making irreversible decisions."}`, issue.prNumber ? `Current PR: #${issue.prNumber}` : "", issue.prHeadSha ? `Current relevant head SHA: ${issue.prHeadSha}` : "", issue.prReviewState ? `Current review state: ${issue.prReviewState}` : "", typeof context?.mergeStateStatus === "string" ? `Merge state against ${String(context?.baseBranch ?? "main")}: ${String(context.mergeStateStatus)}` : "");
|
|
444
393
|
}
|
|
445
|
-
return lines.filter(Boolean)
|
|
394
|
+
return lines.filter(Boolean);
|
|
446
395
|
}
|
|
447
|
-
function
|
|
396
|
+
function buildCurrentContext(runType, issue, context, followUp = false) {
|
|
448
397
|
const lines = [];
|
|
449
398
|
if (followUp) {
|
|
450
|
-
lines.push(
|
|
399
|
+
lines.push(...buildFollowUpContextLines(issue, runType, context), "");
|
|
451
400
|
}
|
|
401
|
+
lines.push(...buildHumanContextLines(context));
|
|
452
402
|
switch (runType) {
|
|
453
403
|
case "ci_repair":
|
|
454
404
|
lines.push(buildCiRepairContext(context));
|
|
@@ -464,74 +414,74 @@ function buildReactiveContext(runType, issue, context, followUp = false) {
|
|
|
464
414
|
break;
|
|
465
415
|
}
|
|
466
416
|
const content = lines.map((line) => line.trimEnd()).join("\n").trim();
|
|
467
|
-
|
|
417
|
+
if (!content.length)
|
|
418
|
+
return undefined;
|
|
419
|
+
return ["## Current Context", "", content].join("\n");
|
|
468
420
|
}
|
|
469
421
|
function buildWorkflowGuidance(repoPath, runType) {
|
|
470
|
-
const
|
|
471
|
-
if (
|
|
472
|
-
return
|
|
473
|
-
|
|
474
|
-
|
|
422
|
+
const filename = WORKFLOW_FILES[runType];
|
|
423
|
+
if (hasWorkflowFile(repoPath, runType)) {
|
|
424
|
+
return [
|
|
425
|
+
"## Workflow",
|
|
426
|
+
"",
|
|
427
|
+
`Read and follow \`${filename}\` in the repository for task-specific behavior before making irreversible changes.`,
|
|
428
|
+
].join("\n");
|
|
475
429
|
}
|
|
476
|
-
return
|
|
430
|
+
return [
|
|
431
|
+
"## Workflow",
|
|
432
|
+
"",
|
|
433
|
+
"Use repository docs and local guidance as the source of truth for task-specific behavior.",
|
|
434
|
+
].join("\n");
|
|
477
435
|
}
|
|
478
436
|
function buildOrchestrationWorkflowGuidance() {
|
|
479
437
|
return [
|
|
480
|
-
"## Workflow
|
|
438
|
+
"## Workflow",
|
|
481
439
|
"",
|
|
482
|
-
"Use the wake reason and
|
|
483
|
-
"
|
|
484
|
-
"
|
|
440
|
+
"Use the wake reason and child issue summaries to decide the next orchestration step.",
|
|
441
|
+
"Prefer supervising, auditing, and unblocking existing child work over creating more issues.",
|
|
442
|
+
"If the parent goal now depends on an integration fix between delivered child slices, own that convergence work here without restating already-owned child implementation.",
|
|
485
443
|
"Keep outputs concise and observable in Linear.",
|
|
486
444
|
].join("\n");
|
|
487
445
|
}
|
|
446
|
+
function buildPrePushSelfReviewSection(target) {
|
|
447
|
+
const publishTarget = target === "new_pr"
|
|
448
|
+
? "open or update the PR"
|
|
449
|
+
: "push the existing PR branch";
|
|
450
|
+
return [
|
|
451
|
+
"## Final Self-Review Before Push",
|
|
452
|
+
"",
|
|
453
|
+
`Before you ${publishTarget}, do one brief reviewer-minded pass on the current head.`,
|
|
454
|
+
"Fix any likely in-scope blocker you can see now: missing edge-case handling, broken adjacent invariant in the touched flow, mismatch between the PR explanation and the code, or an obviously unreviewable half-finished branch.",
|
|
455
|
+
"Do not widen scope for optional cleanup. If the issue explicitly allows a non-PR outcome, complete that outcome clearly; otherwise publish before stopping.",
|
|
456
|
+
];
|
|
457
|
+
}
|
|
488
458
|
function buildPublicationContract(runType, issueClass) {
|
|
489
459
|
if (issueClass === "orchestration") {
|
|
490
460
|
return [
|
|
491
|
-
"##
|
|
461
|
+
"## Publish",
|
|
492
462
|
"",
|
|
493
|
-
"
|
|
494
|
-
"
|
|
495
|
-
"Valid orchestration outcomes include: recording an observation, updating the rollout plan, creating follow-up issues, opening a small cleanup PR that the parent clearly owns, or closing the umbrella.",
|
|
496
|
-
"If you create new blocking follow-up work, justify it against the original parent goal rather than optional polish.",
|
|
497
|
-
"Blocking follow-up children must block the parent goal they satisfy, not the other way around.",
|
|
498
|
-
"If you create new child issues, leave them in Backlog unless they are truly ready to start now.",
|
|
463
|
+
"Publish the orchestration outcome clearly: observation, follow-up issues, rollout update, closeout, or a small parent-owned cleanup PR.",
|
|
464
|
+
"Do not open an overlapping umbrella PR unless this parent owns unique direct work.",
|
|
499
465
|
].join("\n");
|
|
500
466
|
}
|
|
501
467
|
if (runType === "implementation") {
|
|
502
468
|
return [
|
|
503
|
-
"##
|
|
469
|
+
"## Publish",
|
|
504
470
|
"",
|
|
505
|
-
"
|
|
506
|
-
"If the
|
|
507
|
-
"If the worktree already contains relevant changes for this issue, verify them and publish them.",
|
|
508
|
-
"If you changed files for this issue, commit them, push the issue branch, and open or update the PR before stopping.",
|
|
509
|
-
"Do not stop with only local commits or uncommitted changes.",
|
|
471
|
+
"If this is code-delivery work, publish before stopping: commit, push the issue branch, and open or update the PR.",
|
|
472
|
+
"If the issue explicitly allows a non-PR outcome, complete that outcome clearly instead of inventing a PR.",
|
|
510
473
|
"",
|
|
511
|
-
"
|
|
512
|
-
"",
|
|
513
|
-
"When you open or update a PR, shape the body so a strict reviewer can decide in one pass.",
|
|
514
|
-
"",
|
|
515
|
-
"Title: imperative, ≤72 chars. Do not prefix with the issue key — the branch carries it.",
|
|
516
|
-
"",
|
|
517
|
-
"Body sections, in this order. Omit any that do not apply but keep the order:",
|
|
518
|
-
"",
|
|
519
|
-
" ## Why — 1-3 sentences on the problem and motivation.",
|
|
520
|
-
" ## What — ≤5 bullets naming the files or surfaces that change.",
|
|
521
|
-
" ## Tradeoffs — one explicit tradeoff taken, or the single word \"None\".",
|
|
522
|
-
" ## Risks — 1-3 things a strict reviewer would ask about. For each, either fix it before committing or explain why it is acceptable. This section is load-bearing; a strict reviewer reads it first.",
|
|
523
|
-
"",
|
|
524
|
-
"Do not restate the diff in prose. Quote the ambiguous fragment directly if the reader needs to see it.",
|
|
525
|
-
"Do not add a \"Verification\" or \"I ran these commands\" section; CI owns pass/fail and posts check runs the reviewer already sees.",
|
|
474
|
+
...buildPrePushSelfReviewSection("new_pr"),
|
|
526
475
|
].join("\n");
|
|
527
476
|
}
|
|
528
477
|
return [
|
|
529
|
-
"##
|
|
478
|
+
"## Publish",
|
|
530
479
|
"",
|
|
531
|
-
"
|
|
532
|
-
"If you changed files for this repair, commit them and push the same branch before stopping.",
|
|
480
|
+
"Restore and publish on the existing PR branch: commit and push the same branch.",
|
|
533
481
|
"Do not open a new PR.",
|
|
534
|
-
"
|
|
482
|
+
"A PR-less stop is not a successful outcome for a repair run unless a genuine external blocker prevents any correct push.",
|
|
483
|
+
"",
|
|
484
|
+
...buildPrePushSelfReviewSection("existing_pr"),
|
|
535
485
|
].join("\n");
|
|
536
486
|
}
|
|
537
487
|
function buildSections(issue, runType, repoPath, context, followUp = false) {
|
|
@@ -539,20 +489,13 @@ function buildSections(issue, runType, repoPath, context, followUp = false) {
|
|
|
539
489
|
const sections = [
|
|
540
490
|
{ id: "header", content: buildPromptHeader(issue) },
|
|
541
491
|
];
|
|
542
|
-
const
|
|
543
|
-
if (followUp && reactiveContext) {
|
|
544
|
-
sections.push({ id: "follow-up-turn", content: reactiveContext });
|
|
545
|
-
}
|
|
492
|
+
const currentContext = buildCurrentContext(runType, issue, context, followUp);
|
|
546
493
|
sections.push({ id: "task-objective", content: buildTaskObjective(issue) }, {
|
|
547
494
|
id: "scope-discipline",
|
|
548
|
-
content: issueClass === "orchestration" ?
|
|
495
|
+
content: issueClass === "orchestration" ? buildOrchestrationConstraints(context) : buildConstraints(issue, context),
|
|
549
496
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
sections.push({ id: "human-context", content: humanContext });
|
|
553
|
-
}
|
|
554
|
-
if (!followUp && reactiveContext) {
|
|
555
|
-
sections.push({ id: "reactive-context", content: reactiveContext });
|
|
497
|
+
if (currentContext) {
|
|
498
|
+
sections.push({ id: "reactive-context", content: currentContext });
|
|
556
499
|
}
|
|
557
500
|
const workflow = issueClass === "orchestration"
|
|
558
501
|
? buildOrchestrationWorkflowGuidance()
|