rl-workspaces 0.5.0 → 0.6.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.
- package/README.md +173 -141
- package/dist/agent-presets.js +42 -17
- package/dist/agent-runner.js +20 -0
- package/dist/agent-workflows.js +60 -16
- package/dist/automation-config.js +144 -5
- package/dist/git-workflow.js +8 -102
- package/dist/help.js +32 -32
- package/dist/session-workflows.js +51 -17
- package/dist/workflow-collaboration.js +531 -0
- package/lib/rl.zsh +3 -4
- package/package.json +1 -1
package/dist/agent-workflows.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import { stripVTControlCharacters } from "node:util";
|
|
4
4
|
import { args, arity, json } from "./common.js";
|
|
5
5
|
import { commandContext, contextOptions } from "./command-context.js";
|
|
6
|
-
import { CONFIG_FILES, initAutomation, readAutomation, setAutomation, } from "./automation-config.js";
|
|
6
|
+
import { CONFIG_FILES, initAutomation, readAutomation, setAutomation, defaultWorkflow, } from "./automation-config.js";
|
|
7
7
|
import { controlRun, planRun, readRun, rescoreRun, runMetrics, } from "./agent-runner.js";
|
|
8
8
|
import { callWorkflowTool, resumeNativeRun } from "./session-workflows.js";
|
|
9
9
|
import { parseJsonObject } from "./json.js";
|
|
@@ -17,6 +17,12 @@ function report(run, asJson) {
|
|
|
17
17
|
console.log(`\n${visible(run.workflow)} · ${run.status}\nRun: ${run.id}\nWorkspace: ${visible(run.workspace)}\nScope: ${visible(run.scope.base)} → ${run.scope.head.slice(0, 12)} · ${run.scope.files.length} changed files\n`);
|
|
18
18
|
for (const stage of run.stages)
|
|
19
19
|
console.log(` ${stage.id.padEnd(22)} ${stage.status.padEnd(10)} ${stage.attempts} attempt(s) · ${(stage.durationMs / 1000).toFixed(1)}s${stage.error ? ` · ${visible(stage.error)}` : ""}`);
|
|
20
|
+
if (run.collaboration) {
|
|
21
|
+
const rating = run.config.policies[run.config.workflows[run.workflow].policy].rating;
|
|
22
|
+
for (const submission of run.collaboration.submissions.filter((item) => !item.supersededBy))
|
|
23
|
+
console.log(` Review ${submission.id.slice(0, 8)} · round ${submission.round} · ${submission.review ? `${submission.review.rating}/${rating.max} · ${submission.review.accepted ? "accepted" : "changes required"}` : "awaiting reviewer"}`);
|
|
24
|
+
console.log(` DRY: ${run.collaboration.consultations.filter((item) => !item.answer).length} unanswered consultation(s)`);
|
|
25
|
+
}
|
|
20
26
|
if (run.evaluation) {
|
|
21
27
|
const result = run.evaluation;
|
|
22
28
|
console.log(`\n${result.score === null ? "Not scored" : `Score: ${result.score}/100`} · ${result.verdict}${result.verdict === "incomplete" && result.score !== null ? " (provisional)" : ""}`);
|
|
@@ -44,7 +50,8 @@ export async function main(argv) {
|
|
|
44
50
|
"session",
|
|
45
51
|
"step",
|
|
46
52
|
"request",
|
|
47
|
-
|
|
53
|
+
"claim",
|
|
54
|
+
], ["json", "dry-run", "default"]);
|
|
48
55
|
const context = commandContext(values);
|
|
49
56
|
const [namespace, action, ...rest] = positionals;
|
|
50
57
|
arity(rest, 0, 1);
|
|
@@ -52,6 +59,9 @@ export async function main(argv) {
|
|
|
52
59
|
const asJson = !!values.json;
|
|
53
60
|
if (values.file && values.instructions)
|
|
54
61
|
throw new Error("Choose --file or --instructions, not both");
|
|
62
|
+
if (values.default &&
|
|
63
|
+
!(namespace === "workflow" && ["add", "set"].includes(action)))
|
|
64
|
+
throw new Error("--default requires workflow add/set");
|
|
55
65
|
if ((values.description || values.model || values.reasoning) &&
|
|
56
66
|
!(namespace === "agent" &&
|
|
57
67
|
["add", "set"].includes(action) &&
|
|
@@ -89,16 +99,27 @@ export async function main(argv) {
|
|
|
89
99
|
if ((namespace === "agent" || namespace === "workflow") &&
|
|
90
100
|
["add", "set"].includes(action)) {
|
|
91
101
|
arity(rest, 1);
|
|
92
|
-
const
|
|
93
|
-
?
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
const instructions = namespace === "agent" && values.instructions !== undefined
|
|
103
|
+
? fs.readFileSync(values.instructions === "-" ? 0 : String(values.instructions), "utf8")
|
|
104
|
+
: undefined;
|
|
105
|
+
const input = instructions === undefined ? loadInput() : undefined;
|
|
106
|
+
const value = await context.store.locked(() => {
|
|
107
|
+
const previous = namespace === "agent" && action === "set"
|
|
108
|
+
? readAutomation(data.worktree).agents[rest[0]]
|
|
109
|
+
: undefined;
|
|
110
|
+
const profile = instructions === undefined
|
|
111
|
+
? input
|
|
112
|
+
: {
|
|
113
|
+
provider: "inherit",
|
|
114
|
+
...previous,
|
|
115
|
+
description: String(values.description ?? previous?.description ?? rest[0]),
|
|
116
|
+
instructions,
|
|
117
|
+
...(values.model ? { model: values.model } : {}),
|
|
118
|
+
...(values.reasoning ? { reasoning: values.reasoning } : {}),
|
|
119
|
+
};
|
|
120
|
+
setAutomation(data.worktree, namespace === "agent" ? "agents" : "workflows", rest[0], profile, action === "add", !!values.default);
|
|
121
|
+
return profile;
|
|
122
|
+
});
|
|
102
123
|
await context.store.adopt(data.worktree);
|
|
103
124
|
if (asJson)
|
|
104
125
|
json(value);
|
|
@@ -111,8 +132,25 @@ export async function main(argv) {
|
|
|
111
132
|
...(values.session ? { sessionId: values.session } : {}),
|
|
112
133
|
};
|
|
113
134
|
if (namespace === "workflow" &&
|
|
114
|
-
[
|
|
115
|
-
|
|
135
|
+
[
|
|
136
|
+
"context",
|
|
137
|
+
"pending",
|
|
138
|
+
"event",
|
|
139
|
+
"claim",
|
|
140
|
+
"report",
|
|
141
|
+
"dismiss",
|
|
142
|
+
"bind",
|
|
143
|
+
"inbox",
|
|
144
|
+
"consult",
|
|
145
|
+
"answer",
|
|
146
|
+
"submit",
|
|
147
|
+
"review",
|
|
148
|
+
].includes(action)) {
|
|
149
|
+
const input = action === "report" ||
|
|
150
|
+
action === "dismiss" ||
|
|
151
|
+
action === "event" ||
|
|
152
|
+
["bind", "consult", "answer", "submit", "review"].includes(action) ||
|
|
153
|
+
values.file
|
|
116
154
|
? loadInput()
|
|
117
155
|
: {};
|
|
118
156
|
const result = await callWorkflowTool(context.store, `rl_workflow_${action}`, {
|
|
@@ -120,6 +158,7 @@ export async function main(argv) {
|
|
|
120
158
|
...parent,
|
|
121
159
|
...(rest[0] ? { run: rest[0] } : {}),
|
|
122
160
|
...(values.step ? { step: values.step } : {}),
|
|
161
|
+
...(values.claim ? { claim: values.claim } : {}),
|
|
123
162
|
});
|
|
124
163
|
if (asJson || action !== "context")
|
|
125
164
|
json(result);
|
|
@@ -166,7 +205,7 @@ export async function main(argv) {
|
|
|
166
205
|
const name = rest[0];
|
|
167
206
|
if (action === "run") {
|
|
168
207
|
const config = configuration();
|
|
169
|
-
const workflow = name ??
|
|
208
|
+
const workflow = name ?? defaultWorkflow(config), base = String(values.base ?? context.defaultBase);
|
|
170
209
|
if (values["dry-run"]) {
|
|
171
210
|
const plan = planRun(context.store, config, workflow, base);
|
|
172
211
|
if (asJson)
|
|
@@ -215,7 +254,12 @@ export async function main(argv) {
|
|
|
215
254
|
else {
|
|
216
255
|
console.log(`Metrics: ${run.id}`);
|
|
217
256
|
for (const [key, value] of Object.entries(metrics))
|
|
218
|
-
if (key
|
|
257
|
+
if (key === "collaboration" && metrics.collaboration) {
|
|
258
|
+
const team = metrics.collaboration;
|
|
259
|
+
console.log(`Reviews: ${team.accepted} accepted · ${team.submissions} submissions · latest rating ${team.latestRating ?? "not reported"}`);
|
|
260
|
+
console.log(`Consultations: ${team.consultations} total · ${team.awaitingAnswers} awaiting answers`);
|
|
261
|
+
}
|
|
262
|
+
else if (key !== "stages")
|
|
219
263
|
console.log(`${key}: ${value ?? "not reported"}`);
|
|
220
264
|
}
|
|
221
265
|
return 0;
|
|
@@ -35,7 +35,7 @@ function integer(value, min, max) {
|
|
|
35
35
|
throw new Error(`Expected an integer from ${min} to ${max}`);
|
|
36
36
|
}
|
|
37
37
|
export function validateAutomation(value) {
|
|
38
|
-
fields(value, ["agents", "policies", "workflows"]);
|
|
38
|
+
fields(value, ["agents", "policies", "workflows", "defaultWorkflow"], ["agents", "policies", "workflows"]);
|
|
39
39
|
for (const kind of ["agents", "policies", "workflows"]) {
|
|
40
40
|
const entries = value[kind];
|
|
41
41
|
if (!object(entries) ||
|
|
@@ -79,7 +79,8 @@ export function validateAutomation(value) {
|
|
|
79
79
|
"weights",
|
|
80
80
|
"minimumScore",
|
|
81
81
|
"blockRequired",
|
|
82
|
-
|
|
82
|
+
"rating",
|
|
83
|
+
], ["instructions", "weights", "minimumScore", "blockRequired"]);
|
|
83
84
|
nonempty(item.instructions);
|
|
84
85
|
fields(item.weights, [...SEVERITIES]);
|
|
85
86
|
for (const weight of Object.values(item.weights))
|
|
@@ -87,6 +88,16 @@ export function validateAutomation(value) {
|
|
|
87
88
|
integer(item.minimumScore, 0, 100);
|
|
88
89
|
if (typeof item.blockRequired !== "boolean")
|
|
89
90
|
throw new Error("blockRequired must be boolean");
|
|
91
|
+
if (item.rating !== undefined) {
|
|
92
|
+
fields(item.rating, ["min", "max", "minimum", "instructions"]);
|
|
93
|
+
for (const key of ["min", "max", "minimum"])
|
|
94
|
+
integer(item.rating[key], 0, 1000);
|
|
95
|
+
if (Number(item.rating.max) <= Number(item.rating.min) ||
|
|
96
|
+
Number(item.rating.minimum) < Number(item.rating.min) ||
|
|
97
|
+
Number(item.rating.minimum) > Number(item.rating.max))
|
|
98
|
+
throw new Error("Invalid reviewer rating range");
|
|
99
|
+
nonempty(item.rating.instructions);
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
else {
|
|
92
103
|
fields(item, [
|
|
@@ -97,6 +108,7 @@ export function validateAutomation(value) {
|
|
|
97
108
|
"timeoutSeconds",
|
|
98
109
|
"steps",
|
|
99
110
|
"triggers",
|
|
111
|
+
"collaboration",
|
|
100
112
|
], [
|
|
101
113
|
"description",
|
|
102
114
|
"policy",
|
|
@@ -176,9 +188,77 @@ export function validateAutomation(value) {
|
|
|
176
188
|
done.add(id);
|
|
177
189
|
}
|
|
178
190
|
}
|
|
191
|
+
if (item.collaboration !== undefined) {
|
|
192
|
+
const collaboration = item.collaboration;
|
|
193
|
+
fields(collaboration, [
|
|
194
|
+
"writer",
|
|
195
|
+
"reviewer",
|
|
196
|
+
"dry",
|
|
197
|
+
"maxRounds",
|
|
198
|
+
"rules",
|
|
199
|
+
]);
|
|
200
|
+
const roles = [
|
|
201
|
+
collaboration.writer,
|
|
202
|
+
collaboration.reviewer,
|
|
203
|
+
collaboration.dry,
|
|
204
|
+
];
|
|
205
|
+
if (new Set(roles).size !== 3 || Number(item.concurrency) < 3)
|
|
206
|
+
throw new Error("Collaborative roles require three distinct steps and concurrency of at least three");
|
|
207
|
+
for (const role of roles) {
|
|
208
|
+
identifier(role);
|
|
209
|
+
const step = item.steps.find((step) => step.id === role);
|
|
210
|
+
if (!step?.agent || step.after.length)
|
|
211
|
+
throw new Error("Collaborative roles must be agent steps ready together at startup");
|
|
212
|
+
const profile = object(value.agents)
|
|
213
|
+
? value.agents[step.agent]
|
|
214
|
+
: undefined;
|
|
215
|
+
if (role === collaboration.writer &&
|
|
216
|
+
object(profile) &&
|
|
217
|
+
profile.access !== "workspace-write")
|
|
218
|
+
throw new Error("Collaborative writer must have workspace-write access");
|
|
219
|
+
if (role !== collaboration.writer &&
|
|
220
|
+
object(profile) &&
|
|
221
|
+
profile.access === "workspace-write")
|
|
222
|
+
throw new Error("Reviewer and DRY roles must be read-only");
|
|
223
|
+
}
|
|
224
|
+
integer(collaboration.maxRounds, 1, 20);
|
|
225
|
+
const policy = object(value.policies)
|
|
226
|
+
? value.policies[item.policy]
|
|
227
|
+
: undefined;
|
|
228
|
+
if (!object(policy) || !object(policy.rating))
|
|
229
|
+
throw new Error("Collaborative workflows require a reviewer rating policy");
|
|
230
|
+
if (!Array.isArray(collaboration.rules) ||
|
|
231
|
+
collaboration.rules.length > 32)
|
|
232
|
+
throw new Error("Expected up to 32 collaboration rules");
|
|
233
|
+
const routes = new Set();
|
|
234
|
+
for (const rule of collaboration.rules) {
|
|
235
|
+
fields(rule, ["from", "event", "to", "required"]);
|
|
236
|
+
if (!roles.includes(rule.from) ||
|
|
237
|
+
!roles.includes(rule.to) ||
|
|
238
|
+
rule.from === rule.to ||
|
|
239
|
+
typeof rule.required !== "boolean")
|
|
240
|
+
throw new Error("Invalid collaboration route");
|
|
241
|
+
identifier(rule.event);
|
|
242
|
+
if (rule.required &&
|
|
243
|
+
!((rule.from === collaboration.writer &&
|
|
244
|
+
rule.event === "new_function") ||
|
|
245
|
+
(rule.from === collaboration.reviewer &&
|
|
246
|
+
rule.event === "suspected_duplicate")))
|
|
247
|
+
throw new Error("Required consultations support writer new_function and reviewer suspected_duplicate; use required: false for additional message routes");
|
|
248
|
+
const key = `${rule.from}:${rule.event}:${rule.to}`;
|
|
249
|
+
if (routes.has(key))
|
|
250
|
+
throw new Error("Duplicate collaboration rule");
|
|
251
|
+
routes.add(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
179
254
|
}
|
|
180
255
|
}
|
|
181
256
|
}
|
|
257
|
+
if (value.defaultWorkflow !== undefined &&
|
|
258
|
+
(!object(value.workflows) ||
|
|
259
|
+
typeof value.defaultWorkflow !== "string" ||
|
|
260
|
+
!Object.hasOwn(value.workflows, value.defaultWorkflow)))
|
|
261
|
+
throw new Error("Unknown default workflow");
|
|
182
262
|
}
|
|
183
263
|
function configPath(root, relative) {
|
|
184
264
|
const directory = path.join(root, ".rl"), file = path.join(root, relative);
|
|
@@ -191,16 +271,68 @@ export function readAutomation(root) {
|
|
|
191
271
|
const result = {};
|
|
192
272
|
for (const [kind, relative] of Object.entries(CONFIG_FILES)) {
|
|
193
273
|
const file = configPath(root, relative);
|
|
194
|
-
if (!fs.existsSync(file))
|
|
274
|
+
if (!fs.existsSync(file)) {
|
|
195
275
|
result[kind] = structuredClone(PRESET[kind]);
|
|
276
|
+
if (kind === "workflows")
|
|
277
|
+
result.defaultWorkflow = PRESET.defaultWorkflow;
|
|
278
|
+
}
|
|
196
279
|
else {
|
|
197
280
|
const data = readJsonObject(file);
|
|
198
|
-
fields(data, ["version", kind]);
|
|
281
|
+
fields(data, kind === "workflows" ? ["version", kind, "default"] : ["version", kind], ["version", kind]);
|
|
199
282
|
if (data.version !== 1)
|
|
200
283
|
throw new Error("Unsupported agent configuration version");
|
|
201
284
|
result[kind] = data[kind];
|
|
285
|
+
if (kind === "workflows")
|
|
286
|
+
result.defaultWorkflow =
|
|
287
|
+
data.default === undefined
|
|
288
|
+
? object(data.workflows)
|
|
289
|
+
? Object.keys(data.workflows)[0]
|
|
290
|
+
: undefined
|
|
291
|
+
: data.default;
|
|
202
292
|
}
|
|
203
293
|
}
|
|
294
|
+
// Older add/set commands could write just one configuration file. Supply
|
|
295
|
+
// the roles/rating needed by the inherited workflow without replacing any
|
|
296
|
+
// explicit profile or changing the user's files.
|
|
297
|
+
if (!fs.existsSync(configPath(root, CONFIG_FILES.workflows))) {
|
|
298
|
+
if (object(result.agents))
|
|
299
|
+
result.agents = { ...structuredClone(PRESET.agents), ...result.agents };
|
|
300
|
+
if (object(result.policies) &&
|
|
301
|
+
object(result.policies.review) &&
|
|
302
|
+
result.policies.review.rating === undefined)
|
|
303
|
+
result.policies.review = {
|
|
304
|
+
...result.policies.review,
|
|
305
|
+
rating: structuredClone(PRESET.policies.review.rating),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
else if (!fs.existsSync(configPath(root, CONFIG_FILES.agents)) &&
|
|
309
|
+
object(result.workflows) &&
|
|
310
|
+
object(result.agents)) {
|
|
311
|
+
const legacy = {
|
|
312
|
+
behavior: {
|
|
313
|
+
...PRESET.agents.reviewer,
|
|
314
|
+
description: "Review runtime correctness and security",
|
|
315
|
+
instructions: "Independently review the supplied scope for runtime correctness, authorization, persistence and concurrency defects. Stay read-only; report concrete evidence and coverage limitations using the supplied result schema.",
|
|
316
|
+
},
|
|
317
|
+
reuse: {
|
|
318
|
+
...PRESET.agents.dry,
|
|
319
|
+
description: "Review reuse and complexity",
|
|
320
|
+
instructions: "Independently search existing source and dependencies for concrete reuse opportunities in the supplied scope. Stay read-only; distinguish behavioral overlap from superficial similarity and report paths, symbols, findings and coverage limitations using the supplied result schema.",
|
|
321
|
+
},
|
|
322
|
+
quality: {
|
|
323
|
+
...PRESET.agents.reviewer,
|
|
324
|
+
description: "Review implementation quality and validation",
|
|
325
|
+
instructions: "Independently review the supplied scope for type contracts, maintainability, failure handling and meaningful validation. Stay read-only; report evidence-backed findings and coverage limitations using the supplied result schema.",
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
for (const workflow of Object.values(result.workflows))
|
|
329
|
+
if (object(workflow) && Array.isArray(workflow.steps))
|
|
330
|
+
for (const step of workflow.steps)
|
|
331
|
+
if (object(step) &&
|
|
332
|
+
typeof step.agent === "string" &&
|
|
333
|
+
Object.hasOwn(legacy, step.agent))
|
|
334
|
+
result.agents[step.agent] = structuredClone(legacy[step.agent]);
|
|
335
|
+
}
|
|
204
336
|
validateAutomation(result);
|
|
205
337
|
return result;
|
|
206
338
|
}
|
|
@@ -218,12 +350,13 @@ export function initAutomation(root, provider) {
|
|
|
218
350
|
writeJson(configPath(root, relative), {
|
|
219
351
|
version: 1,
|
|
220
352
|
[kind]: preset[kind],
|
|
353
|
+
...(kind === "workflows" ? { default: preset.defaultWorkflow } : {}),
|
|
221
354
|
});
|
|
222
355
|
}
|
|
223
356
|
export function hasAutomation(root) {
|
|
224
357
|
return Object.values(CONFIG_FILES).some((relative) => fs.existsSync(configPath(root, relative)));
|
|
225
358
|
}
|
|
226
|
-
export function setAutomation(root, kind, name, value, addOnly = false) {
|
|
359
|
+
export function setAutomation(root, kind, name, value, addOnly = false, makeDefault = false) {
|
|
227
360
|
identifier(name);
|
|
228
361
|
const config = readAutomation(root);
|
|
229
362
|
if (addOnly && Object.hasOwn(config[kind], name))
|
|
@@ -234,9 +367,15 @@ export function setAutomation(root, kind, name, value, addOnly = false) {
|
|
|
234
367
|
writable: true,
|
|
235
368
|
configurable: true,
|
|
236
369
|
});
|
|
370
|
+
if (makeDefault)
|
|
371
|
+
config.defaultWorkflow = name;
|
|
237
372
|
validateAutomation(config);
|
|
238
373
|
writeJson(configPath(root, CONFIG_FILES[kind]), {
|
|
239
374
|
version: 1,
|
|
240
375
|
[kind]: config[kind],
|
|
376
|
+
...(kind === "workflows" ? { default: config.defaultWorkflow } : {}),
|
|
241
377
|
});
|
|
242
378
|
}
|
|
379
|
+
export function defaultWorkflow(config) {
|
|
380
|
+
return config.defaultWorkflow ?? Object.keys(config.workflows)[0];
|
|
381
|
+
}
|
package/dist/git-workflow.js
CHANGED
|
@@ -1,116 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { args, arity, canonical, execute, git, gitRaw, locked, required, } from "./common.js";
|
|
2
|
+
import { args, arity, canonical, execute, git, locked, required, } from "./common.js";
|
|
4
3
|
import { commandContext, contextOptions, resolveWorkspace, } from "./command-context.js";
|
|
5
4
|
import { worktrees } from "./repos.js";
|
|
6
5
|
import { assertReady, GIT_WORKFLOW_DIRECTORY, GIT_WORKFLOW_LOCK, } from "./git-safety.js";
|
|
7
|
-
const BACKUP_PREFIX = "refs/rl/backups/";
|
|
8
|
-
const READ_OPTIONS = new Set([
|
|
9
|
-
"--stat",
|
|
10
|
-
"--name-only",
|
|
11
|
-
"--name-status",
|
|
12
|
-
"--numstat",
|
|
13
|
-
"--patch",
|
|
14
|
-
"--oneline",
|
|
15
|
-
"--graph",
|
|
16
|
-
"--decorate",
|
|
17
|
-
"--all",
|
|
18
|
-
]);
|
|
19
6
|
function revision(root, ref) {
|
|
20
7
|
return git(root, "rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`);
|
|
21
8
|
}
|
|
22
9
|
export async function main(argv) {
|
|
23
|
-
const
|
|
24
|
-
const trailing = separator < 0 ? [] : argv.slice(separator + 1);
|
|
25
|
-
const { values, positionals } = args(separator < 0 ? argv : argv.slice(0, separator), [...contextOptions, "base", "message"], ["all", "squash", "cleanup", "dry-run"], { message: "m" });
|
|
26
|
-
const context = commandContext(values);
|
|
10
|
+
const { values, positionals } = args(argv, [...contextOptions, "message"], ["squash", "cleanup", "dry-run"], { message: "m" });
|
|
27
11
|
const [command, ...rest] = positionals;
|
|
12
|
+
if (command !== "merge")
|
|
13
|
+
throw new Error("Unknown Git workflow command");
|
|
14
|
+
arity(rest, 0, 1);
|
|
15
|
+
if (values.message && !values.squash)
|
|
16
|
+
throw new Error("Invalid merge options");
|
|
17
|
+
const context = commandContext(values);
|
|
28
18
|
const source = resolveWorkspace(context);
|
|
29
|
-
if (command === "diff" || command === "log") {
|
|
30
|
-
arity(rest, 0);
|
|
31
|
-
if (values.all ||
|
|
32
|
-
values.squash ||
|
|
33
|
-
values.cleanup ||
|
|
34
|
-
values.message ||
|
|
35
|
-
values["dry-run"])
|
|
36
|
-
throw new Error("Invalid display options");
|
|
37
|
-
const pathSeparator = trailing.indexOf("--");
|
|
38
|
-
const flags = pathSeparator < 0 ? trailing : trailing.slice(0, pathSeparator);
|
|
39
|
-
const paths = pathSeparator < 0 ? [] : trailing.slice(pathSeparator + 1);
|
|
40
|
-
for (const flag of flags)
|
|
41
|
-
if (!READ_OPTIONS.has(flag) && !/^(-\d+|--max-count=\d+)$/.test(flag))
|
|
42
|
-
throw new Error(`Unsupported display option: ${flag}`);
|
|
43
|
-
const base = revision(source.path, String(values.base ?? context.defaultBase));
|
|
44
|
-
const range = command === "diff"
|
|
45
|
-
? git(source.path, "merge-base", base, "HEAD")
|
|
46
|
-
: `${base}..HEAD`;
|
|
47
|
-
process.stdout.write(gitRaw(source.path, "--no-pager", command, "--no-ext-diff", "--no-textconv", ...(command === "log" ? ["--oneline"] : []), ...flags, range, "--", ...paths));
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
if (trailing.length)
|
|
51
|
-
throw new Error("Extra arguments are only supported by diff and log");
|
|
52
19
|
return locked(path.join(context.store.root, GIT_WORKFLOW_DIRECTORY), GIT_WORKFLOW_LOCK, async () => {
|
|
53
|
-
if (command === "step") {
|
|
54
|
-
arity(rest, 1);
|
|
55
|
-
const [action] = rest;
|
|
56
|
-
if (!["commit", "rebase", "squash"].includes(action))
|
|
57
|
-
throw new Error("Usage: rl step commit|rebase|squash");
|
|
58
|
-
if (values.cleanup ||
|
|
59
|
-
values.squash ||
|
|
60
|
-
(values.all && action !== "commit"))
|
|
61
|
-
throw new Error("Invalid step options");
|
|
62
|
-
assertReady(source.path, action !== "commit", source.branch);
|
|
63
|
-
if (action === "commit") {
|
|
64
|
-
if (values.base)
|
|
65
|
-
throw new Error("commit does not accept --base");
|
|
66
|
-
const commitMessage = required(values, "message");
|
|
67
|
-
if (values["dry-run"]) {
|
|
68
|
-
const changes = git(source.path, "diff", "--name-only", ...(values.all ? ["HEAD"] : ["--cached"]));
|
|
69
|
-
if (!changes)
|
|
70
|
-
throw new Error("No changes to commit");
|
|
71
|
-
console.log(`Would commit ${values.all ? "tracked" : "staged"} changes in ${source.branch}: ${commitMessage}\n${changes}`);
|
|
72
|
-
return 0;
|
|
73
|
-
}
|
|
74
|
-
console.log(git(source.path, "commit", ...(values.all ? ["--all"] : []), "-m", required(values, "message")));
|
|
75
|
-
return 0;
|
|
76
|
-
}
|
|
77
|
-
if (action === "rebase" && values.message)
|
|
78
|
-
throw new Error("rebase does not accept --message");
|
|
79
|
-
const base = revision(source.path, String(values.base ?? context.defaultBase));
|
|
80
|
-
if (action === "rebase") {
|
|
81
|
-
if (values["dry-run"]) {
|
|
82
|
-
console.log(`Would rebase ${source.branch} onto ${base}`);
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
const result = await execute(["git", "-C", source.path, "rebase", base], source.path);
|
|
86
|
-
process.stdout.write(result.stdout);
|
|
87
|
-
if (result.code)
|
|
88
|
-
throw new Error(`${result.stderr.trim()}\nRebase did not complete. Resolve conflicts and run git rebase --continue, or git rebase --abort in ${source.path}`);
|
|
89
|
-
return 0;
|
|
90
|
-
}
|
|
91
|
-
const message = required(values, "message");
|
|
92
|
-
const head = revision(source.path, "HEAD");
|
|
93
|
-
const baseCommit = git(source.path, "merge-base", base, head);
|
|
94
|
-
if (baseCommit === head)
|
|
95
|
-
throw new Error("No feature commits to squash");
|
|
96
|
-
if (values["dry-run"]) {
|
|
97
|
-
console.log(`Would squash commits after ${baseCommit} in ${source.branch}: ${message}`);
|
|
98
|
-
return 0;
|
|
99
|
-
}
|
|
100
|
-
const tree = git(source.path, "rev-parse", `${head}^{tree}`);
|
|
101
|
-
const backup = BACKUP_PREFIX + randomUUID();
|
|
102
|
-
const squashed = git(source.path, "commit-tree", tree, "-p", baseCommit, "-m", message);
|
|
103
|
-
git(source.path, "update-ref", backup, head);
|
|
104
|
-
// CAS leaves concurrent branch changes untouched; the worktree and index never change.
|
|
105
|
-
git(source.path, "update-ref", "-m", "rl step squash", `refs/heads/${source.branch}`, squashed, head);
|
|
106
|
-
console.log(`Squashed feature commits. Original HEAD retained at ${backup}`);
|
|
107
|
-
return 0;
|
|
108
|
-
}
|
|
109
|
-
if (command !== "merge")
|
|
110
|
-
throw new Error("Unknown Git workflow command");
|
|
111
|
-
arity(rest, 0, 1);
|
|
112
|
-
if (values.all || values.base || (values.message && !values.squash))
|
|
113
|
-
throw new Error("Invalid merge options");
|
|
114
20
|
const selected = rest[0] ?? context.defaultBase.replace(/^origin\//, "");
|
|
115
21
|
const targetRecord = worktrees(context.repo).find((record) => record.branch === `refs/heads/${selected}`);
|
|
116
22
|
if (!targetRecord)
|
package/dist/help.js
CHANGED
|
@@ -149,6 +149,9 @@ const topics = {
|
|
|
149
149
|
"agent set": {
|
|
150
150
|
description: "Save an agent profile",
|
|
151
151
|
usage: "rl agent set <name> --file <profile.json|->",
|
|
152
|
+
notes: [
|
|
153
|
+
"Use --instructions <file|-> to change instructions while preserving the profile's access, model and other settings.",
|
|
154
|
+
],
|
|
152
155
|
},
|
|
153
156
|
"agent show": {
|
|
154
157
|
description: "Inspect a workflow agent profile",
|
|
@@ -196,7 +199,7 @@ const topics = {
|
|
|
196
199
|
notes: [
|
|
197
200
|
"Creates .rl/agents.json, .rl/policies.json and .rl/agent-workflows.json.",
|
|
198
201
|
"Existing configuration is never overwritten.",
|
|
199
|
-
"
|
|
202
|
+
"Adopted workspaces use built-in writer/reviewer/DRY roles immediately. Init writes editable copies of their profiles, policy and workflow.",
|
|
200
203
|
],
|
|
201
204
|
},
|
|
202
205
|
"workflow list": {
|
|
@@ -220,18 +223,18 @@ const topics = {
|
|
|
220
223
|
"--request <UUID> Start a queued workflow request instead of choosing a name",
|
|
221
224
|
],
|
|
222
225
|
notes: [
|
|
223
|
-
"Default workflow:
|
|
226
|
+
"Default workflow: writer-reviewer-dry. The parent spawns all three native peers in parallel and binds their handles; RL never launches agent processes.",
|
|
224
227
|
"The parent claims ready steps, spawns native subagents and reports evidence. RL applies the policy; provider usage is parent-reported.",
|
|
225
228
|
],
|
|
226
229
|
examples: ["rl workflow run --base origin/test --dry-run"],
|
|
227
230
|
},
|
|
228
231
|
"workflow add": {
|
|
229
232
|
description: "Add a workflow with dependencies and spawn triggers",
|
|
230
|
-
usage: "rl workflow add <name> --file <workflow.json|->",
|
|
233
|
+
usage: "rl workflow add <name> --file <workflow.json|-> [--default]",
|
|
231
234
|
},
|
|
232
235
|
"workflow set": {
|
|
233
236
|
description: "Save a workflow definition and trigger rules",
|
|
234
|
-
usage: "rl workflow set <name> --file <workflow.json|->",
|
|
237
|
+
usage: "rl workflow set <name> --file <workflow.json|-> [--default]",
|
|
235
238
|
},
|
|
236
239
|
"workflow context": {
|
|
237
240
|
description: "Discover profiles, policies, workflows and trigger rules",
|
|
@@ -257,6 +260,30 @@ const topics = {
|
|
|
257
260
|
description: "Dismiss an unavailable workflow with a reason",
|
|
258
261
|
usage: "rl workflow dismiss --file <dismissal.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
259
262
|
},
|
|
263
|
+
"workflow bind": {
|
|
264
|
+
description: "Bind a role to the native peer spawned by this session",
|
|
265
|
+
usage: "rl workflow bind <run> --file <binding.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
266
|
+
},
|
|
267
|
+
"workflow inbox": {
|
|
268
|
+
description: "Read a peer's requests, review checkpoints and ratings",
|
|
269
|
+
usage: "rl workflow inbox <run> --step <step> --claim <token> [--agent codex|claude] [--session <UUID>]",
|
|
270
|
+
},
|
|
271
|
+
"workflow consult": {
|
|
272
|
+
description: "Ask an existing peer through a configured workflow route",
|
|
273
|
+
usage: "rl workflow consult <run> --file <consultation.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
274
|
+
},
|
|
275
|
+
"workflow answer": {
|
|
276
|
+
description: "Return reuse or duplication evidence to a requesting peer",
|
|
277
|
+
usage: "rl workflow answer <run> --file <answer.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
278
|
+
},
|
|
279
|
+
"workflow submit": {
|
|
280
|
+
description: "Submit writer work to the existing reviewer",
|
|
281
|
+
usage: "rl workflow submit <run> --file <submission.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
282
|
+
},
|
|
283
|
+
"workflow review": {
|
|
284
|
+
description: "Return a rating and evidence for a stable writer checkpoint",
|
|
285
|
+
usage: "rl workflow review <run> --file <review.json|-> [--agent codex|claude] [--session <UUID>]",
|
|
286
|
+
},
|
|
260
287
|
"workflow status": {
|
|
261
288
|
description: "Inspect a saved run",
|
|
262
289
|
usage: "rl workflow status [UUID|latest] [--json]",
|
|
@@ -338,33 +365,9 @@ const topics = {
|
|
|
338
365
|
},
|
|
339
366
|
git: {
|
|
340
367
|
group: "Code and reviews",
|
|
341
|
-
description: "
|
|
368
|
+
description: "Integrate changes across workspaces",
|
|
342
369
|
usage: "rl git <command>",
|
|
343
370
|
},
|
|
344
|
-
"git diff": {
|
|
345
|
-
description: "Show workspace changes against the base",
|
|
346
|
-
usage: "rl git diff [--base <ref>] [-- <display options> [-- paths...]]",
|
|
347
|
-
examples: ["rl git diff -- --stat"],
|
|
348
|
-
},
|
|
349
|
-
"git log": {
|
|
350
|
-
description: "Show feature commits",
|
|
351
|
-
usage: "rl git log [--base <ref>] [-- <display options>]",
|
|
352
|
-
examples: ["rl git log -- --graph"],
|
|
353
|
-
},
|
|
354
|
-
"git commit": {
|
|
355
|
-
description: "Commit staged changes",
|
|
356
|
-
usage: "rl git commit -m <message> [--all] [--dry-run]",
|
|
357
|
-
notes: ["--all includes modified tracked files."],
|
|
358
|
-
},
|
|
359
|
-
"git rebase": {
|
|
360
|
-
description: "Rebase onto the base branch",
|
|
361
|
-
usage: "rl git rebase [--base <ref>] [--dry-run]",
|
|
362
|
-
},
|
|
363
|
-
"git squash": {
|
|
364
|
-
description: "Squash feature commits",
|
|
365
|
-
usage: "rl git squash -m <message> [--base <ref>] [--dry-run]",
|
|
366
|
-
notes: ["The original HEAD is retained under a printed backup ref."],
|
|
367
|
-
},
|
|
368
371
|
"git merge": {
|
|
369
372
|
description: "Integrate into a checked-out base branch",
|
|
370
373
|
usage: "rl git merge [target] [--squash -m <message>] [--cleanup] [--dry-run]",
|
|
@@ -551,9 +554,6 @@ const topics = {
|
|
|
551
554
|
},
|
|
552
555
|
};
|
|
553
556
|
const aliases = [
|
|
554
|
-
[["step"], ["git"]],
|
|
555
|
-
[["diff"], ["git", "diff"]],
|
|
556
|
-
[["log"], ["git", "log"]],
|
|
557
557
|
[["merge"], ["git", "merge"]],
|
|
558
558
|
[["clean"], ["storage", "clean"]],
|
|
559
559
|
[["summary"], ["notes", "summary"]],
|