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.
@@ -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
- ], ["json", "dry-run"]);
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 value = namespace === "agent" && values.instructions !== undefined
93
- ? {
94
- provider: "inherit",
95
- description: String(values.description ?? rest[0]),
96
- instructions: fs.readFileSync(values.instructions === "-" ? 0 : String(values.instructions), "utf8"),
97
- ...(values.model ? { model: values.model } : {}),
98
- ...(values.reasoning ? { reasoning: values.reasoning } : {}),
99
- }
100
- : loadInput();
101
- await context.store.locked(() => setAutomation(data.worktree, namespace === "agent" ? "agents" : "workflows", rest[0], value, action === "add"));
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
- ["context", "pending", "event", "claim", "report", "dismiss"].includes(action)) {
115
- const input = action === "report" || action === "dismiss" || action === "event"
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 ?? "triple-review", base = String(values.base ?? context.defaultBase);
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 !== "stages")
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
+ }
@@ -1,116 +1,22 @@
1
1
  import path from "node:path";
2
- import { randomUUID } from "node:crypto";
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 separator = argv.indexOf("--");
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
- "Roles inherit the current session by default. The preset queues a review before finishing changed work.",
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: triple-review. RL never launches workflow agent or check processes.",
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: "Inspect and integrate branch changes",
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"]],