rl-workspaces 0.6.0 → 0.7.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 +62 -53
- package/bin/rl +2 -54
- package/dist/agent-launch.js +1 -1
- package/dist/agent-resume.js +2 -2
- package/dist/bootstrap.js +74 -0
- package/dist/cli.js +2 -0
- package/dist/command-context.js +1 -1
- package/dist/common.js +3 -2
- package/dist/dependencies.js +197 -0
- package/dist/doctor.js +1 -1
- package/dist/help.js +81 -84
- package/dist/hooks.js +1 -1
- package/dist/install.js +143 -0
- package/dist/mcp-setup.js +171 -0
- package/dist/mcp.js +13 -16
- package/dist/picker.js +24 -7
- package/dist/pr-workflow.js +7 -28
- package/dist/repos.js +5 -1
- package/dist/session-workflows.js +71 -1
- package/dist/session.js +1 -1
- package/dist/state.js +4 -14
- package/dist/storage.js +2 -2
- package/dist/store.js +2 -2
- package/dist/summary.js +1 -1
- package/dist/workflow.js +24 -38
- package/lib/entry.zsh +54 -0
- package/lib/rl.zsh +35 -6
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +605 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +156 -0
- package/node_modules/smol-toml/dist/error.d.ts +37 -0
- package/node_modules/smol-toml/dist/error.js +67 -0
- package/node_modules/smol-toml/dist/extract.js +293 -0
- package/node_modules/smol-toml/dist/index.cjs +771 -0
- package/node_modules/smol-toml/dist/index.d.ts +44 -0
- package/node_modules/smol-toml/dist/index.js +34 -0
- package/node_modules/smol-toml/dist/parse.d.ts +73 -0
- package/node_modules/smol-toml/dist/parse.js +164 -0
- package/node_modules/smol-toml/dist/primitive.js +151 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
- package/node_modules/smol-toml/dist/stringify.js +219 -0
- package/node_modules/smol-toml/dist/struct.js +156 -0
- package/node_modules/smol-toml/dist/util.d.ts +43 -0
- package/node_modules/smol-toml/dist/util.js +57 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +6 -3
- package/shell/rl.zsh +1 -1
package/dist/picker.js
CHANGED
|
@@ -15,22 +15,37 @@ const stateColors = {
|
|
|
15
15
|
none: 90,
|
|
16
16
|
unknown: 90,
|
|
17
17
|
};
|
|
18
|
-
export function candidates(repo, base) {
|
|
19
|
-
|
|
18
|
+
export function candidates(repo, base, store) {
|
|
19
|
+
const adopted = new Map(store
|
|
20
|
+
?.records()
|
|
21
|
+
.filter(([, data]) => data.status === "active")
|
|
22
|
+
.map(([, data]) => [data.worktree, data.branch]));
|
|
23
|
+
const records = worktrees(repo)
|
|
20
24
|
.slice(1)
|
|
21
25
|
.flatMap((record) => {
|
|
22
26
|
const root = canonical(record.worktree);
|
|
23
|
-
|
|
27
|
+
const managed = inside(root, base);
|
|
28
|
+
const branch = (record.branch ?? "").replace(/^refs\/heads\//, "");
|
|
29
|
+
if (root === repo || (!managed && adopted.get(root) !== branch))
|
|
24
30
|
return [];
|
|
25
31
|
return [
|
|
26
32
|
{
|
|
27
|
-
name: path.relative(base, root),
|
|
33
|
+
name: managed ? path.relative(base, root) : path.basename(root),
|
|
28
34
|
path: root,
|
|
29
|
-
branch
|
|
35
|
+
branch,
|
|
30
36
|
locked: "locked" in record,
|
|
31
37
|
},
|
|
32
38
|
];
|
|
33
39
|
});
|
|
40
|
+
// Preserve existing managed names. Outside checkouts use their directory
|
|
41
|
+
// name unless it collides, in which case their absolute path is unambiguous.
|
|
42
|
+
const used = new Set(records.filter((item) => inside(item.path, base)).map((item) => item.name));
|
|
43
|
+
for (const item of records.filter((item) => !inside(item.path, base))) {
|
|
44
|
+
if (used.has(item.name))
|
|
45
|
+
item.name = item.path;
|
|
46
|
+
used.add(item.name);
|
|
47
|
+
}
|
|
48
|
+
return records;
|
|
34
49
|
}
|
|
35
50
|
export function prState(data) {
|
|
36
51
|
if (!data?.pr)
|
|
@@ -120,7 +135,9 @@ export function pickerRows(store, items, defaultBase, prs = new Map()) {
|
|
|
120
135
|
}
|
|
121
136
|
export async function select(store, items, defaultBase, multiple) {
|
|
122
137
|
if (!items.length) {
|
|
123
|
-
console.error(
|
|
138
|
+
console.error(multiple
|
|
139
|
+
? "No managed worktrees to delete."
|
|
140
|
+
: "No RL workspaces yet. Create one with rl switch <name> --create.");
|
|
124
141
|
return [];
|
|
125
142
|
}
|
|
126
143
|
const prs = await discoverPrs(store, items.map((item) => item.branch));
|
|
@@ -175,7 +192,7 @@ export async function main(argv) {
|
|
|
175
192
|
]);
|
|
176
193
|
arity(positionals, 0);
|
|
177
194
|
const store = new Store(required(values, "repo"), required(values, "state-dir"));
|
|
178
|
-
const selected = await select(store, candidates(store.repo, canonical(required(values, "base-dir"))), required(values, "default-base"), false);
|
|
195
|
+
const selected = await select(store, candidates(store.repo, canonical(required(values, "base-dir")), store), required(values, "default-base"), false);
|
|
179
196
|
if (selected.length)
|
|
180
197
|
process.stdout.write(selected[0].path + "\n");
|
|
181
198
|
return 0;
|
package/dist/pr-workflow.js
CHANGED
|
@@ -2,12 +2,11 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { args, arity, canonical, execute, git, inside, locked, } from "./common.js";
|
|
5
|
-
import { commandContext, contextOptions
|
|
5
|
+
import { commandContext, contextOptions } from "./command-context.js";
|
|
6
6
|
import { GIT_WORKFLOW_DIRECTORY, GIT_WORKFLOW_LOCK } from "./git-safety.js";
|
|
7
7
|
import { isJsonObject, parseJsonObject } from "./json.js";
|
|
8
8
|
const GH_TIMEOUT_MS = 20_000;
|
|
9
9
|
const PR_FIELDS = "number,url,headRefName,headRefOid,baseRefName,isCrossRepository,headRepository,headRepositoryOwner";
|
|
10
|
-
const CHECK_FIELDS = "name,state,bucket,link,workflow";
|
|
11
10
|
const FETCH_PREFIX = "refs/rl/pr-fetch/";
|
|
12
11
|
/** Bound requests and ignore a caller's unrelated GH_REPO override. */
|
|
13
12
|
export async function github(repo, argv) {
|
|
@@ -53,33 +52,13 @@ export async function repositoryIdentity(repo) {
|
|
|
53
52
|
return { name: raw.nameWithOwner, url: raw.url };
|
|
54
53
|
}
|
|
55
54
|
export async function main(argv) {
|
|
56
|
-
const { values, positionals } = args(argv, [...contextOptions, "name"], [
|
|
57
|
-
const context = commandContext(values);
|
|
55
|
+
const { values, positionals } = args(argv, [...contextOptions, "name"], []);
|
|
58
56
|
const [command, action, ...rest] = positionals;
|
|
59
|
-
if (command !== "pr" ||
|
|
60
|
-
throw new Error("Usage: rl pr
|
|
61
|
-
const identity = await repositoryIdentity(context.repo);
|
|
62
|
-
if (action === "checks") {
|
|
63
|
-
arity(rest, 0);
|
|
64
|
-
if (values.name)
|
|
65
|
-
throw new Error("checks does not accept --name");
|
|
66
|
-
const workspace = resolveWorkspace(context);
|
|
67
|
-
const result = await github(workspace.path, [
|
|
68
|
-
"pr",
|
|
69
|
-
"checks",
|
|
70
|
-
workspace.branch,
|
|
71
|
-
"--repo",
|
|
72
|
-
identity.url,
|
|
73
|
-
...(values.json ? ["--json", CHECK_FIELDS] : []),
|
|
74
|
-
]);
|
|
75
|
-
process.stdout.write(result.stdout);
|
|
76
|
-
if (result.stderr)
|
|
77
|
-
process.stderr.write(result.stderr);
|
|
78
|
-
return result.code;
|
|
79
|
-
}
|
|
57
|
+
if (command !== "pr" || action !== "checkout")
|
|
58
|
+
throw new Error("Usage: rl pr checkout <number> [--name <branch>]");
|
|
80
59
|
arity(rest, 1);
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
const context = commandContext(values);
|
|
61
|
+
const identity = await repositoryIdentity(context.repo);
|
|
83
62
|
const [number] = rest;
|
|
84
63
|
if (!/^[1-9]\d*$/.test(number))
|
|
85
64
|
throw new Error("Expected a positive PR number");
|
|
@@ -149,7 +128,7 @@ export async function main(argv) {
|
|
|
149
128
|
}
|
|
150
129
|
catch (error) {
|
|
151
130
|
// Preserve the valid checkout on adoption failure so hooks/files can be inspected.
|
|
152
|
-
throw new Error(`Created ${destination}, but RL adoption failed: ${error instanceof Error ? error.message : "unknown error"}. Run rl adopt inside it`);
|
|
131
|
+
throw new Error(`Created ${destination}, but RL adoption failed: ${error instanceof Error ? error.message : "unknown error"}. Run rl setup adopt inside it`);
|
|
153
132
|
}
|
|
154
133
|
console.log(destination);
|
|
155
134
|
return 0;
|
package/dist/repos.js
CHANGED
|
@@ -183,7 +183,11 @@ class Registry {
|
|
|
183
183
|
if (rest.length === 3 && values.alias !== undefined)
|
|
184
184
|
throw new Error("Choose either --alias <name> or the legacy <alias> <path> form");
|
|
185
185
|
const target = rest.length === 3 ? rest[2] : (rest[1] ?? ".");
|
|
186
|
-
const
|
|
186
|
+
const directoryPath = path.resolve(expandHome(target));
|
|
187
|
+
if (!fs.existsSync(directoryPath) ||
|
|
188
|
+
!fs.statSync(directoryPath).isDirectory())
|
|
189
|
+
throw new Error(`Repository directory does not exist: ${directoryPath}. Use 'rl repo add .' for the current repository, with --alias <name> to name it.`);
|
|
190
|
+
const root = canonical(worktrees(directoryPath)[0].worktree);
|
|
187
191
|
name = String(values.alias ?? (rest.length === 3 ? name : repositoryName(root)));
|
|
188
192
|
if (!validName(name))
|
|
189
193
|
throw new Error("Repository names must contain letters, numbers, dots, underscores, or hyphens; supply --alias <name>");
|
|
@@ -223,7 +223,7 @@ export function automationContext(root) {
|
|
|
223
223
|
.digest("hex");
|
|
224
224
|
return [
|
|
225
225
|
`RL native agent configuration detected (${fingerprint}; source: ${hasAutomation(root) ? "workspace configuration" : "built-in defaults"}; default workflow: ${defaultWorkflow(config)}). You are the coordinator of this session's own native subagents.`,
|
|
226
|
-
"
|
|
226
|
+
"Use RL from this existing session; no special agent launcher or user-pasted workflow prompt is required. Run 'rl workflow context --json' to discover the active profiles, workflows, policies and the complete protocol.tools CLI commands and input schemas. Use the CLI through your normal shell tools; an MCP connection is optional. If MCP tools are connected, the same operation names and schemas apply. Follow the active configuration automatically for applicable tasks. At planning milestones report the configured event with rl_workflow_event. Tool and before-finish hooks also queue matching requests. Read pending requests with rl_workflow_pending.",
|
|
227
227
|
"For coding tasks, report task_planned before implementation. A collaborative workflow starts all writer, reviewer and DRY native peers in parallel: claim all three roles, spawn them using your native tools, bind their handles with rl_workflow_bind, then relay messages or let peers use their own inbox. The writer submits checkpoints, the reviewer returns policy-based ratings, and DRY answers both peers. Keep all three peers available; do not replace them with sequential one-shot passes. Use collaboration tools for handoffs before final stage reports. Semantic new-function and duplication obligations require explicit declarations and consultation responses; RL cannot read private reasoning.",
|
|
228
228
|
"For each request: rl_workflow_start creates a run tied to YOUR parent session; rl_workflow_claim atomically returns a ready step and its instructions. Spawn that step with YOUR native subagent tool, then submit its evidence with rl_workflow_report. Run command steps yourself and report actual output. RL never starts Codex, Claude, or check processes for a workflow. Do not substitute a separate CLI or API agent. Respect current permissions, workspace boundaries, explicit user instructions and native delegation availability. If delegation is unavailable, dismiss the request with the concrete reason and tell the user; do not claim it ran.",
|
|
229
229
|
"Supply agent (codex or claude) and this parent conversation's full sessionId to workflow tools. UUIDs and observed Codex thr_ IDs are supported for workflow coordination; only verified UUIDs enter resumable history. CLI equivalents: 'rl workflow pending', 'rl workflow run --request <requestId>', 'rl workflow claim <run> --step <step>', 'rl workflow report <run> --file <report.json>', with --agent and --session. Profiles describe roles; their provider field is a legacy preference, never permission to start another provider. Unsupported native model/reasoning preferences must be reported, not silently substituted. Policies own scoring; return evidence without assigning yourself a score.",
|
|
@@ -320,6 +320,75 @@ export const WORKFLOW_TOOLS = [
|
|
|
320
320
|
},
|
|
321
321
|
...COLLABORATION_TOOLS,
|
|
322
322
|
];
|
|
323
|
+
export function workflowToolSchema(tool) {
|
|
324
|
+
return {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
agent: { type: "string", enum: ["codex", "claude"] },
|
|
328
|
+
sessionId: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Current parent conversation ID. Claude requires a UUID; Codex also accepts observed thr_ IDs and may inherit it.",
|
|
331
|
+
},
|
|
332
|
+
...tool.properties,
|
|
333
|
+
},
|
|
334
|
+
required: tool.required,
|
|
335
|
+
additionalProperties: false,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function workflowProtocol() {
|
|
339
|
+
const identity = { agent: "--agent", sessionId: "--session" };
|
|
340
|
+
return {
|
|
341
|
+
version: 1,
|
|
342
|
+
coordinator: "current-session",
|
|
343
|
+
requiresMcp: false,
|
|
344
|
+
requiresRlLauncher: false,
|
|
345
|
+
requiresNativeSubagents: true,
|
|
346
|
+
discovery: "rl workflow context --json",
|
|
347
|
+
instruction: "Use your current session's native subagent and messaging tools. RL supplies roles and coordination state. CLI operations below use the same inputSchema as MCP; tools with stdin: json accept the entire input object via --file -. These are coordination commands, never instructions to start a second provider process.",
|
|
348
|
+
tools: WORKFLOW_TOOLS.map((tool) => {
|
|
349
|
+
const action = tool.name.slice("rl_workflow_".length);
|
|
350
|
+
const file = !["context", "pending", "start", "claim", "inbox"].includes(action);
|
|
351
|
+
const flags = action === "start"
|
|
352
|
+
? {
|
|
353
|
+
...identity,
|
|
354
|
+
base: "--base",
|
|
355
|
+
prompt: "--prompt",
|
|
356
|
+
requestId: "--request",
|
|
357
|
+
}
|
|
358
|
+
: {
|
|
359
|
+
...identity,
|
|
360
|
+
...(action === "claim" || action === "inbox"
|
|
361
|
+
? { step: "--step" }
|
|
362
|
+
: {}),
|
|
363
|
+
...(action === "inbox" ? { claim: "--claim" } : {}),
|
|
364
|
+
};
|
|
365
|
+
return {
|
|
366
|
+
name: tool.name,
|
|
367
|
+
description: tool.description,
|
|
368
|
+
inputSchema: workflowToolSchema(tool),
|
|
369
|
+
cli: {
|
|
370
|
+
command: [
|
|
371
|
+
"rl",
|
|
372
|
+
"workflow",
|
|
373
|
+
action === "start" ? "run" : action,
|
|
374
|
+
...(file ? ["--file", "-"] : []),
|
|
375
|
+
"--json",
|
|
376
|
+
],
|
|
377
|
+
stdin: file ? "json" : "none",
|
|
378
|
+
positionals: file
|
|
379
|
+
? []
|
|
380
|
+
: action === "start"
|
|
381
|
+
? ["workflow"]
|
|
382
|
+
: action === "claim" || action === "inbox"
|
|
383
|
+
? ["run"]
|
|
384
|
+
: [],
|
|
385
|
+
flags: file ? {} : flags,
|
|
386
|
+
output: "json",
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
323
392
|
export async function callWorkflowTool(store, name, input) {
|
|
324
393
|
const definition = WORKFLOW_TOOLS.find((tool) => tool.name === name);
|
|
325
394
|
if (!definition)
|
|
@@ -337,6 +406,7 @@ export async function callWorkflowTool(store, name, input) {
|
|
|
337
406
|
source: hasAutomation(data.worktree) ? "workspace" : "built-in",
|
|
338
407
|
...readAutomation(data.worktree),
|
|
339
408
|
instructions: automationContext(data.worktree),
|
|
409
|
+
protocol: workflowProtocol(),
|
|
340
410
|
};
|
|
341
411
|
}
|
|
342
412
|
const parent = owner(input);
|
package/dist/session.js
CHANGED
|
@@ -7,6 +7,6 @@ export function currentSessionId(agent, explicit) {
|
|
|
7
7
|
// Resume the current conversation, including a fork, rather than its tree root.
|
|
8
8
|
const id = process.env.CODEX_THREAD_ID ?? process.env.CODEX_SESSION_ID;
|
|
9
9
|
if (!id)
|
|
10
|
-
throw new Error("No current Codex session ID. Run 'rl
|
|
10
|
+
throw new Error("No current Codex session ID. Run 'rl agent save codex' inside Codex, or supply the full UUID: rl agent save codex <UUID>");
|
|
11
11
|
return sessionId(id);
|
|
12
12
|
}
|
package/dist/state.js
CHANGED
|
@@ -145,7 +145,7 @@ export async function main(argv) {
|
|
|
145
145
|
arity(rest, 0, rest[0] === "open" ? 1 : 0);
|
|
146
146
|
return openPr(store, !!values.json);
|
|
147
147
|
}
|
|
148
|
-
if (command === "status"
|
|
148
|
+
if (command === "status")
|
|
149
149
|
arity(rest, 0);
|
|
150
150
|
if (command === "session" && rest[0] === "list") {
|
|
151
151
|
arity(rest, 1, 2);
|
|
@@ -154,10 +154,8 @@ export async function main(argv) {
|
|
|
154
154
|
}
|
|
155
155
|
if (command === "status" && values.fetch)
|
|
156
156
|
git(store.repo, "fetch", "--all", "--prune");
|
|
157
|
-
if (command === "status" ||
|
|
158
|
-
|
|
159
|
-
(command === "session" && rest[0] === "list"))
|
|
160
|
-
await syncCodexSessions(store, command === "instances" ? undefined : process.cwd());
|
|
157
|
+
if (command === "status" || (command === "session" && rest[0] === "list"))
|
|
158
|
+
await syncCodexSessions(store, process.cwd());
|
|
161
159
|
let replacement = "";
|
|
162
160
|
if (["context", "progress"].includes(command) &&
|
|
163
161
|
["set", "append"].includes(rest[0])) {
|
|
@@ -165,14 +163,6 @@ export async function main(argv) {
|
|
|
165
163
|
replacement = fs.readFileSync(rest[1] === "-" ? 0 : rest[1], "utf8");
|
|
166
164
|
}
|
|
167
165
|
return store.locked(() => {
|
|
168
|
-
if (command === "instances") {
|
|
169
|
-
arity(rest, 0);
|
|
170
|
-
json(store.records().map(([, data]) => ({
|
|
171
|
-
...data,
|
|
172
|
-
gitStatus: branchStatus(store.repo, data),
|
|
173
|
-
})));
|
|
174
|
-
return 0;
|
|
175
|
-
}
|
|
176
166
|
if (command === "archive") {
|
|
177
167
|
arity(rest, 0);
|
|
178
168
|
const root = canonical(required(values, "worktree"));
|
|
@@ -219,7 +209,7 @@ export async function main(argv) {
|
|
|
219
209
|
json(rest[1] ? data.sessions[agent(rest[1])] : data.sessions);
|
|
220
210
|
}
|
|
221
211
|
else
|
|
222
|
-
throw new Error("Usage: rl
|
|
212
|
+
throw new Error("Usage: rl agent sessions [agent] | save [agent] [UUID]");
|
|
223
213
|
}
|
|
224
214
|
else if (command === "context" || command === "progress") {
|
|
225
215
|
const target = path.join(path.dirname(file), command + ".md"), action = rest[0];
|
package/dist/storage.js
CHANGED
|
@@ -239,7 +239,7 @@ function remove(workspace, targets, recheck) {
|
|
|
239
239
|
function suggestions(data, usage, estimatedBytes, days) {
|
|
240
240
|
const result = [];
|
|
241
241
|
if (estimatedBytes)
|
|
242
|
-
result.push(`Run rl clean --dry-run to preview ${size(estimatedBytes)} of configured generated files.`);
|
|
242
|
+
result.push(`Run rl storage clean --dry-run to preview ${size(estimatedBytes)} of configured generated files.`);
|
|
243
243
|
else if (usage.dependenciesBytes || usage.buildsBytes)
|
|
244
244
|
result.push("Add generated directory paths to the clean list in this workspace's .rl/workflows.json.");
|
|
245
245
|
const accessed = lastAccessed(data);
|
|
@@ -256,7 +256,7 @@ export async function main(argv) {
|
|
|
256
256
|
if (!["storage", "clean"].includes(command) ||
|
|
257
257
|
(command === "storage" && values["dry-run"]) ||
|
|
258
258
|
(command === "clean" && values["older-than"] !== undefined))
|
|
259
|
-
throw new Error("Usage: rl storage [--json] [--older-than 14d] | rl clean [--dry-run] [--json] (current workspace only)");
|
|
259
|
+
throw new Error("Usage: rl storage [--json] [--older-than 14d] | rl storage clean [--dry-run] [--json] (current workspace only)");
|
|
260
260
|
const context = commandContext(values);
|
|
261
261
|
// Resolve from the caller's cwd before any scan, lock creation, or mutation.
|
|
262
262
|
const [file, data] = context.store.current();
|
package/dist/store.js
CHANGED
|
@@ -183,7 +183,7 @@ export class Store {
|
|
|
183
183
|
throw new Error(`Instance owns branch ${data.branch}; current branch is ${branch}`);
|
|
184
184
|
return [file, data];
|
|
185
185
|
}
|
|
186
|
-
throw new Error("No RL instance for this worktree. Run 'rl adopt' first");
|
|
186
|
+
throw new Error("No RL instance for this worktree. Run 'rl setup adopt' first");
|
|
187
187
|
}
|
|
188
188
|
save(file, data) {
|
|
189
189
|
data.updatedAt = now();
|
|
@@ -359,7 +359,7 @@ function installHooks(store, file, data) {
|
|
|
359
359
|
export function contextText(file, data) {
|
|
360
360
|
const sections = [
|
|
361
361
|
`RL feature workspace: ${data.id}\nBranch: ${data.branch}`,
|
|
362
|
-
"The following RL-owned documents are shared living feature documentation, not just conversation memory. Read them before starting work, and read the full files when excerpts are truncated. At meaningful milestones and before finishing your task, document what changed, what remains, implementation details (file/symbol references, flows, APIs, data structures), decisions and rationale, checks actually run, and blockers. Keep completed and planned work distinct; do not mark unverified work as tested or done. Respect your assigned scope and permissions; read-only agents should return proposed documentation updates to their parent. Use 'rl
|
|
362
|
+
"The following RL-owned documents are shared living feature documentation, not just conversation memory. Read them before starting work, and read the full files when excerpts are truncated. At meaningful milestones and before finishing your task, document what changed, what remains, implementation details (file/symbol references, flows, APIs, data structures), decisions and rationale, checks actually run, and blockers. Keep completed and planned work distinct; do not mark unverified work as tested or done. Respect your assigned scope and permissions; read-only agents should return proposed documentation updates to their parent. Use 'rl notes append context <file>' for durable domain or design notes and 'rl notes append progress <file>' for milestone/handoff updates ('-' reads stdin). Append is locked so concurrent agents do not overwrite one another. Use 'rl notes set context <file>' or 'rl notes set progress <file>' only for a deliberate replacement after reading the latest document and coordinating with other writers. Re-read the documents before planning subsequent work.",
|
|
363
363
|
];
|
|
364
364
|
sections.push("When the user asks to pause or save this task for later, save a task handoff with rl_task_pause through MCP, or 'rl pause --summary <text> --changes <text> --validation <text> --next <text> [--blockers <text>]'. Include the current session UUID (Codex shell commands can infer it), concrete changes, checks actually run and their results, remaining work, and blockers. Use 'Not run' for unperformed checks. A pause saves task state; it does not terminate the agent. The user can return with 'rl continue'.");
|
|
365
365
|
const automation = automationContext(data.worktree);
|
package/dist/summary.js
CHANGED
|
@@ -86,7 +86,7 @@ function render(store, data, context, progress, defaultBase, requested) {
|
|
|
86
86
|
parts.push("### Committed file changes", changes ? fence(changes) : "No net committed file changes.");
|
|
87
87
|
}
|
|
88
88
|
else
|
|
89
|
-
parts.push(`The configured comparison base ${inline(comparison)} could not be resolved. Committed work was omitted; run \`rl summary --base <branch-or-commit>\` to include it.`);
|
|
89
|
+
parts.push(`The configured comparison base ${inline(comparison)} could not be resolved. Committed work was omitted; run \`rl notes summary --base <branch-or-commit>\` to include it.`);
|
|
90
90
|
parts.push("## Uncommitted work", `Git status entries: ${status.staged} staged, ${status.unstaged} unstaged, ${status.untracked} untracked, ${status.conflicts} conflicts. An untracked directory counts as one status entry.`);
|
|
91
91
|
for (const [title, flags] of [
|
|
92
92
|
["Staged changes", ["--cached"]],
|
package/dist/workflow.js
CHANGED
|
@@ -16,8 +16,6 @@ const COMMAND_OPTIONS = {
|
|
|
16
16
|
exec: ["workspace"],
|
|
17
17
|
setup: ["workspace", "dry-run"],
|
|
18
18
|
switch: ["create", "base", "no-fetch", "setup", "agent", "output-path"],
|
|
19
|
-
new: ["base", "no-fetch", "setup", "output-path"],
|
|
20
|
-
open: ["branch", "base", "no-fetch", "setup", "output-path"],
|
|
21
19
|
};
|
|
22
20
|
async function createWorkspace(context, name, base, noFetch, options = {}) {
|
|
23
21
|
if (name.startsWith("-") ||
|
|
@@ -29,8 +27,7 @@ async function createWorkspace(context, name, base, noFetch, options = {}) {
|
|
|
29
27
|
throw new Error(`Invalid workspace name: ${name}`);
|
|
30
28
|
git(context.repo, "check-ref-format", "--branch", name);
|
|
31
29
|
return locked(path.join(context.store.root, CREATE_LOCK), LOCK_FILE, async () => {
|
|
32
|
-
const
|
|
33
|
-
const destination = path.resolve(context.managedDir, directoryName);
|
|
30
|
+
const destination = path.resolve(context.managedDir, name);
|
|
34
31
|
if (!inside(destination, context.managedDir) ||
|
|
35
32
|
canonical(destination) !== destination)
|
|
36
33
|
throw new Error("Workspace path must stay inside the managed directory without symlinks");
|
|
@@ -41,7 +38,7 @@ async function createWorkspace(context, name, base, noFetch, options = {}) {
|
|
|
41
38
|
const [root, branch] = context.store.worktree(registered.worktree);
|
|
42
39
|
if (branch !== name)
|
|
43
40
|
throw new Error("Workspace branch changed during selection; retry");
|
|
44
|
-
return { path: root, name
|
|
41
|
+
return { path: root, name, branch, created: false };
|
|
45
42
|
}
|
|
46
43
|
if (fs.existsSync(destination))
|
|
47
44
|
throw new Error(`Workspace path already exists: ${destination}`);
|
|
@@ -83,7 +80,7 @@ async function createWorkspace(context, name, base, noFetch, options = {}) {
|
|
|
83
80
|
git(context.repo, "worktree", "add", ...creationArgs);
|
|
84
81
|
return {
|
|
85
82
|
path: destination,
|
|
86
|
-
name
|
|
83
|
+
name,
|
|
87
84
|
branch: name,
|
|
88
85
|
created: true,
|
|
89
86
|
};
|
|
@@ -92,7 +89,7 @@ async function createWorkspace(context, name, base, noFetch, options = {}) {
|
|
|
92
89
|
export async function main(argv) {
|
|
93
90
|
const separator = argv.indexOf("--");
|
|
94
91
|
const forwarded = separator < 0 ? [] : argv.slice(separator + 1);
|
|
95
|
-
const { values, positionals } = args(separator < 0 ? argv : argv.slice(0, separator), [...contextOptions, "base", "
|
|
92
|
+
const { values, positionals } = args(separator < 0 ? argv : argv.slice(0, separator), [...contextOptions, "base", "workspace", "agent", "output-path"], ["create", "no-fetch", "setup", "dry-run"], { create: "c", base: "b" });
|
|
96
93
|
const context = commandContext(values);
|
|
97
94
|
const [command, ...names] = positionals;
|
|
98
95
|
const allowedOptions = COMMAND_OPTIONS[command];
|
|
@@ -101,10 +98,8 @@ export async function main(argv) {
|
|
|
101
98
|
for (const option of Object.keys(values))
|
|
102
99
|
if (!contextOptions.includes(option) && !allowedOptions.includes(option))
|
|
103
100
|
throw new Error(`--${option} is not supported by rl ${command}`);
|
|
104
|
-
if (command === "switch" &&
|
|
105
|
-
|
|
106
|
-
(values.base || values["no-fetch"]))
|
|
107
|
-
throw new Error("--base and --no-fetch require --create with rl switch");
|
|
101
|
+
if (command === "switch" && !values.create && values.base)
|
|
102
|
+
throw new Error("--base requires --create with rl switch");
|
|
108
103
|
const selector = typeof values.workspace === "string" ? values.workspace : undefined;
|
|
109
104
|
if (forwarded.length &&
|
|
110
105
|
command !== "exec" &&
|
|
@@ -132,7 +127,7 @@ export async function main(argv) {
|
|
|
132
127
|
console.log(file);
|
|
133
128
|
}
|
|
134
129
|
else
|
|
135
|
-
throw new Error("Usage: rl
|
|
130
|
+
throw new Error("Usage: rl setup show|init");
|
|
136
131
|
return 0;
|
|
137
132
|
}
|
|
138
133
|
if (command === "exec") {
|
|
@@ -159,35 +154,26 @@ export async function main(argv) {
|
|
|
159
154
|
});
|
|
160
155
|
});
|
|
161
156
|
}
|
|
162
|
-
|
|
163
|
-
throw new Error("Unknown workspace workflow command");
|
|
164
|
-
arity(names, command === "open" ? 0 : 1);
|
|
165
|
-
const selectedBranch = values.branch ?? (command === "open" ? values.base : undefined);
|
|
166
|
-
if (command === "open" && typeof selectedBranch !== "string")
|
|
167
|
-
throw new Error("Usage: rl open --branch <branch>");
|
|
157
|
+
arity(names, 1);
|
|
168
158
|
const agent = typeof values.agent === "string" ? values.agent : undefined;
|
|
169
159
|
if (agent && !SUPPORTED_AGENTS.has(agent))
|
|
170
160
|
throw new Error("Agent must be codex or claude");
|
|
171
|
-
const base =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
: {}),
|
|
189
|
-
})
|
|
190
|
-
: resolveWorkspace(context, names[0]);
|
|
161
|
+
const base = typeof values.base === "string" ? values.base : context.defaultBase;
|
|
162
|
+
let target;
|
|
163
|
+
if (values.create) {
|
|
164
|
+
target = await createWorkspace(context, names[0], base, !!values["no-fetch"]);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
try {
|
|
168
|
+
target = resolveWorkspace(context, names[0]);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!(error instanceof Error) ||
|
|
172
|
+
!error.message.startsWith("Workspace not found:"))
|
|
173
|
+
throw error;
|
|
174
|
+
target = await createWorkspace(context, names[0].replace(/^origin\//, ""), base, !!values["no-fetch"], { reuseExisting: true, requireBranch: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
191
177
|
await context.store.adopt(target.path, base);
|
|
192
178
|
if (values.setup) {
|
|
193
179
|
const code = await locked(path.join(context.store.root, SETUP_LOCK), LOCK_FILE, () => {
|
package/lib/entry.zsh
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/zsh -f
|
|
2
|
+
emulate -L zsh
|
|
3
|
+
readonly rl_home="${${(%):-%x}:A:h:h}"
|
|
4
|
+
if [[ "$1" == __hook ]]; then
|
|
5
|
+
# Hooks resolve the installed CLI on PATH, not a version-specific package path.
|
|
6
|
+
# Their explicit repository/state arguments must not be replaced by user config.
|
|
7
|
+
shift
|
|
8
|
+
exec node "$rl_home/dist/cli.js" state "$@"
|
|
9
|
+
fi
|
|
10
|
+
if [[ "$1" == --version || "$1" == -V ]]; then
|
|
11
|
+
exec node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")).version' "$rl_home/package.json"
|
|
12
|
+
fi
|
|
13
|
+
if [[ "$1" == setup && "$2" == shell ]]; then
|
|
14
|
+
if (( $# == 2 )); then
|
|
15
|
+
exec node "$rl_home/dist/help.js" setup shell
|
|
16
|
+
fi
|
|
17
|
+
for argument in "$@"; do
|
|
18
|
+
[[ "$argument" == -- ]] && break
|
|
19
|
+
if [[ "$argument" == --help || "$argument" == -h ]]; then
|
|
20
|
+
exec node "$rl_home/dist/help.js" "$@"
|
|
21
|
+
fi
|
|
22
|
+
done
|
|
23
|
+
if [[ "$3" != zsh && "$3" != bash || $# != 3 ]]; then
|
|
24
|
+
print -u2 'Usage: rl setup shell <zsh|bash>'
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
if [[ "$3" == zsh ]]; then
|
|
28
|
+
print -r -- "source ${(q)rl_home}/shell/rl.zsh"
|
|
29
|
+
else
|
|
30
|
+
print -r -- "_rl_executable=${(qq)rl_home}/bin/rl"
|
|
31
|
+
cat <<'BASH'
|
|
32
|
+
rl() {
|
|
33
|
+
local destination_file destination result previous="$PWD"
|
|
34
|
+
local -a arguments=("$@")
|
|
35
|
+
if [[ "$1" == switch && "${2:-}" == - ]]; then
|
|
36
|
+
[[ -n "${RL_PREVIOUS_WORKSPACE:-}" ]] || { printf '%s\n' 'No previous workspace in this shell' >&2; return 1; }
|
|
37
|
+
arguments[1]="$RL_PREVIOUS_WORKSPACE"
|
|
38
|
+
fi
|
|
39
|
+
destination_file=$(mktemp "${TMPDIR:-/tmp}/rl-shell.XXXXXXXX") || return 1
|
|
40
|
+
if RL_SHELL_OUTPUT="$destination_file" command "$_rl_executable" "${arguments[@]}"; then result=0; else result=$?; fi
|
|
41
|
+
destination=$(cat -- "$destination_file")
|
|
42
|
+
rm -f -- "$destination_file"
|
|
43
|
+
if [[ -n "$destination" ]]; then
|
|
44
|
+
builtin cd -- "$destination" || return 1
|
|
45
|
+
RL_PREVIOUS_WORKSPACE="$previous"
|
|
46
|
+
fi
|
|
47
|
+
return "$result"
|
|
48
|
+
}
|
|
49
|
+
BASH
|
|
50
|
+
fi
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
source "$rl_home/lib/rl.zsh" || exit 1
|
|
54
|
+
_rl_main "$@"
|
package/lib/rl.zsh
CHANGED
|
@@ -24,6 +24,12 @@ _rl_change_directory() {
|
|
|
24
24
|
print -rn -- "$PWD" > "$RL_SHELL_OUTPUT"
|
|
25
25
|
else
|
|
26
26
|
print -r -- "$PWD"
|
|
27
|
+
if [[ -t 1 && -t 2 ]]; then
|
|
28
|
+
case "${SHELL:t}" in
|
|
29
|
+
zsh|bash) print -u2 -r -- "Enable directory switching: eval \"\$(rl setup shell ${SHELL:t})\"" ;;
|
|
30
|
+
*) print -u2 'Enable directory switching with shell integration: rl help setup shell' ;;
|
|
31
|
+
esac
|
|
32
|
+
fi
|
|
27
33
|
fi
|
|
28
34
|
}
|
|
29
35
|
|
|
@@ -57,12 +63,12 @@ _rl_main() {
|
|
|
57
63
|
return
|
|
58
64
|
fi
|
|
59
65
|
done
|
|
60
|
-
if (( ${#help_args} == 1 )) && [[ "$help_args[1]" == (agent|workflow|policy|repo|git|notes
|
|
66
|
+
if (( ${#help_args} == 1 )) && [[ "$help_args[1]" == (agent|workflow|policy|repo|git|notes) ]]; then
|
|
61
67
|
_rl_help "${help_args[@]}"
|
|
62
68
|
return
|
|
63
69
|
fi
|
|
64
70
|
|
|
65
|
-
#
|
|
71
|
+
# Translate public command families to their internal handlers.
|
|
66
72
|
# Preserve a leading repository selector while normalizing the public command.
|
|
67
73
|
local -a routing_prefix=()
|
|
68
74
|
if [[ "$1" == --repo ]]; then
|
|
@@ -72,7 +78,23 @@ _rl_main() {
|
|
|
72
78
|
routing_prefix=("$1")
|
|
73
79
|
shift
|
|
74
80
|
fi
|
|
81
|
+
case "$1" in
|
|
82
|
+
new|open|instances|merge|clean|summary|handoff|mcp|session|context|progress|config|adopt|init)
|
|
83
|
+
print -u2 -r -- "Unknown command: $1"
|
|
84
|
+
print -u2 'Run rl help for available commands.'
|
|
85
|
+
return 1 ;;
|
|
86
|
+
status)
|
|
87
|
+
if (( ${argv[(Ie)--all]} || ${argv[(Ie)--list]} )); then
|
|
88
|
+
print -u2 'Use rl list for workspace overviews.'
|
|
89
|
+
return 1
|
|
90
|
+
fi ;;
|
|
91
|
+
esac
|
|
75
92
|
case "$1:$2" in
|
|
93
|
+
setup:dependencies|setup:install)
|
|
94
|
+
local setup_module=dependencies
|
|
95
|
+
[[ "$2" == install ]] && setup_module=install
|
|
96
|
+
command node "${${functions_source[_rl_main]}:A:h:h}/dist/$setup_module.js" "${@:3}"
|
|
97
|
+
return ;;
|
|
76
98
|
git:merge) argv=("$2" "${@:3}") ;;
|
|
77
99
|
agent:sessions) argv=(session list "${@:3}") ;;
|
|
78
100
|
agent:save) argv=(session save "${@:3}") ;;
|
|
@@ -81,9 +103,10 @@ _rl_main() {
|
|
|
81
103
|
storage:clean) argv=(clean "${@:3}") ;;
|
|
82
104
|
setup:init|setup:show) argv=(config "$2" "${@:3}") ;;
|
|
83
105
|
setup:adopt) argv=(adopt "${@:3}") ;;
|
|
106
|
+
setup:mcp) argv=(mcp-setup "${@:3}") ;;
|
|
84
107
|
setup:run) argv=(setup "${@:3}") ;;
|
|
85
108
|
setup:shell)
|
|
86
|
-
command "${${functions_source[_rl_main]}:A:h:h}/bin/rl"
|
|
109
|
+
command "${${functions_source[_rl_main]}:A:h:h}/bin/rl" setup shell "${@:3}"
|
|
87
110
|
return ;;
|
|
88
111
|
notes:summary) argv=(summary "${@:3}") ;;
|
|
89
112
|
notes:show|notes:path|notes:set|notes:append)
|
|
@@ -209,13 +232,19 @@ _rl_main() {
|
|
|
209
232
|
--default-base "$default_base" "$@"
|
|
210
233
|
return
|
|
211
234
|
fi
|
|
212
|
-
if [[ "$command" == pr &&
|
|
235
|
+
if [[ "$command" == pr && "$2" == checkout ]]; then
|
|
213
236
|
command node "${${functions_source[_rl_main]}:A:h:h}/dist/cli.js" pr \
|
|
214
237
|
--repo "$repo_root" --state-dir "$state_dir" --base-dir "$base_dir/$repo_name" \
|
|
215
238
|
--default-base "$default_base" "$@"
|
|
216
239
|
return
|
|
217
240
|
fi
|
|
218
241
|
case "$command" in
|
|
242
|
+
mcp-setup)
|
|
243
|
+
shift
|
|
244
|
+
command node "${${functions_source[_rl_main]}:A:h:h}/dist/cli.js" mcp-setup \
|
|
245
|
+
--repo "$repo_root" --state-dir "$state_dir" --base-dir "$base_dir/$repo_name" \
|
|
246
|
+
--default-base "$default_base" "$@"
|
|
247
|
+
return ;;
|
|
219
248
|
storage|clean)
|
|
220
249
|
command node "${${functions_source[_rl_main]}:A:h:h}/dist/cli.js" storage \
|
|
221
250
|
--repo "$repo_root" --state-dir "$state_dir" --base-dir "$base_dir/$repo_name" \
|
|
@@ -260,13 +289,13 @@ _rl_main() {
|
|
|
260
289
|
command node "${${functions_source[_rl_main]}:A:h:h}/dist/cli.js" summary \
|
|
261
290
|
--repo "$repo_root" --state-dir "$state_dir" --default-base "$default_base" "$@"
|
|
262
291
|
return ;;
|
|
263
|
-
adopt|doctor|status|
|
|
292
|
+
adopt|doctor|status|resume|session|context|progress|pr)
|
|
264
293
|
_rl_state "$@"
|
|
265
294
|
return ;;
|
|
266
295
|
esac
|
|
267
296
|
|
|
268
297
|
case "$command" in
|
|
269
|
-
switch|
|
|
298
|
+
switch|continue)
|
|
270
299
|
if [[ "$command" == switch && "$2" == - ]]; then
|
|
271
300
|
[[ -n "${RL_PREVIOUS_WORKSPACE:-}" ]] || { print -u2 'No previous workspace in this shell'; return 1; }
|
|
272
301
|
argv[2]="$RL_PREVIOUS_WORKSPACE"
|