rl-workspaces 0.0.0-stage → 0.5.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/LICENSE +21 -0
- package/README.md +703 -2
- package/bin/rl +54 -0
- package/config.example.zsh +9 -0
- package/dist/access.js +40 -0
- package/dist/activity.js +90 -0
- package/dist/agent-claude.js +16 -0
- package/dist/agent-codex.js +22 -0
- package/dist/agent-launch.js +74 -0
- package/dist/agent-presets.js +45 -0
- package/dist/agent-registry.js +8 -0
- package/dist/agent-resume.js +96 -0
- package/dist/agent-runner.js +366 -0
- package/dist/agent-workflows.js +231 -0
- package/dist/agents.js +73 -0
- package/dist/automation-config.js +242 -0
- package/dist/base.js +66 -0
- package/dist/cleanup-path.js +33 -0
- package/dist/cli.js +57 -0
- package/dist/codex-sessions.js +188 -0
- package/dist/command-context.js +67 -0
- package/dist/common.js +262 -0
- package/dist/delete.js +113 -0
- package/dist/display.js +134 -0
- package/dist/doctor.js +145 -0
- package/dist/git-safety.js +24 -0
- package/dist/git-workflow.js +183 -0
- package/dist/help.js +726 -0
- package/dist/hooks.js +159 -0
- package/dist/json.js +34 -0
- package/dist/mcp.js +336 -0
- package/dist/picker.js +182 -0
- package/dist/pr-discovery.js +223 -0
- package/dist/pr-update.js +419 -0
- package/dist/pr-workflow.js +161 -0
- package/dist/repos.js +537 -0
- package/dist/review-policy.js +150 -0
- package/dist/session-workflows.js +616 -0
- package/dist/session.js +12 -0
- package/dist/state.js +250 -0
- package/dist/storage.js +366 -0
- package/dist/store.js +379 -0
- package/dist/summary.js +164 -0
- package/dist/task-state.js +65 -0
- package/dist/task.js +192 -0
- package/dist/templates.js +4 -0
- package/dist/workflow-config.js +177 -0
- package/dist/workflow.js +211 -0
- package/lib/rl.zsh +330 -0
- package/node_modules/graceful-fs/LICENSE +15 -0
- package/node_modules/graceful-fs/README.md +143 -0
- package/node_modules/graceful-fs/clone.js +23 -0
- package/node_modules/graceful-fs/graceful-fs.js +448 -0
- package/node_modules/graceful-fs/legacy-streams.js +118 -0
- package/node_modules/graceful-fs/package.json +53 -0
- package/node_modules/graceful-fs/polyfills.js +355 -0
- package/node_modules/proper-lockfile/LICENSE +21 -0
- package/node_modules/proper-lockfile/README.md +183 -0
- package/node_modules/proper-lockfile/index.js +40 -0
- package/node_modules/proper-lockfile/lib/adapter.js +85 -0
- package/node_modules/proper-lockfile/lib/lockfile.js +342 -0
- package/node_modules/proper-lockfile/lib/mtime-precision.js +55 -0
- package/node_modules/proper-lockfile/package.json +71 -0
- package/node_modules/retry/.npmignore +3 -0
- package/node_modules/retry/.travis.yml +15 -0
- package/node_modules/retry/License +21 -0
- package/node_modules/retry/Makefile +18 -0
- package/node_modules/retry/README.md +227 -0
- package/node_modules/retry/equation.gif +0 -0
- package/node_modules/retry/example/dns.js +31 -0
- package/node_modules/retry/example/stop.js +40 -0
- package/node_modules/retry/index.js +1 -0
- package/node_modules/retry/lib/retry.js +100 -0
- package/node_modules/retry/lib/retry_operation.js +158 -0
- package/node_modules/retry/package.json +32 -0
- package/node_modules/retry/test/common.js +10 -0
- package/node_modules/retry/test/integration/test-forever.js +24 -0
- package/node_modules/retry/test/integration/test-retry-operation.js +258 -0
- package/node_modules/retry/test/integration/test-retry-wrap.js +101 -0
- package/node_modules/retry/test/integration/test-timeouts.js +69 -0
- package/node_modules/signal-exit/LICENSE.txt +16 -0
- package/node_modules/signal-exit/README.md +39 -0
- package/node_modules/signal-exit/index.js +202 -0
- package/node_modules/signal-exit/package.json +38 -0
- package/node_modules/signal-exit/signals.js +53 -0
- package/package.json +57 -4
- package/shell/rl.zsh +11 -0
|
@@ -0,0 +1,183 @@
|
|
|
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";
|
|
4
|
+
import { commandContext, contextOptions, resolveWorkspace, } from "./command-context.js";
|
|
5
|
+
import { worktrees } from "./repos.js";
|
|
6
|
+
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
|
+
function revision(root, ref) {
|
|
20
|
+
return git(root, "rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`);
|
|
21
|
+
}
|
|
22
|
+
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);
|
|
27
|
+
const [command, ...rest] = positionals;
|
|
28
|
+
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
|
+
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
|
+
const selected = rest[0] ?? context.defaultBase.replace(/^origin\//, "");
|
|
115
|
+
const targetRecord = worktrees(context.repo).find((record) => record.branch === `refs/heads/${selected}`);
|
|
116
|
+
if (!targetRecord)
|
|
117
|
+
throw new Error(`Target must be a checked-out local branch: ${selected}`);
|
|
118
|
+
const target = canonical(targetRecord.worktree);
|
|
119
|
+
// Main and managed worktrees are the only mutation targets.
|
|
120
|
+
resolveWorkspace(context, target === context.repo ? "@" : target);
|
|
121
|
+
if (target === source.path)
|
|
122
|
+
throw new Error("Source and target are the same worktree");
|
|
123
|
+
if ("locked" in targetRecord)
|
|
124
|
+
throw new Error("Target worktree is locked");
|
|
125
|
+
assertReady(source.path, true, source.branch);
|
|
126
|
+
assertReady(target, true, selected);
|
|
127
|
+
const sourceHead = revision(source.path, "HEAD");
|
|
128
|
+
const targetHead = revision(target, "HEAD");
|
|
129
|
+
const mergeBase = git(target, "merge-base", targetHead, sourceHead);
|
|
130
|
+
if (!values.squash &&
|
|
131
|
+
mergeBase !== targetHead &&
|
|
132
|
+
mergeBase !== sourceHead)
|
|
133
|
+
throw new Error("Fast-forward merge is not possible. Rebase the feature explicitly first");
|
|
134
|
+
if (values.squash)
|
|
135
|
+
required(values, "message");
|
|
136
|
+
if (values["dry-run"]) {
|
|
137
|
+
console.log(`Would ${values.squash ? "squash merge" : "fast-forward merge"} ${source.branch} into ${selected}${values.cleanup ? " and remove the clean source worktree" : ""}`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
const result = await execute([
|
|
141
|
+
"git",
|
|
142
|
+
"-C",
|
|
143
|
+
target,
|
|
144
|
+
"merge",
|
|
145
|
+
...(values.squash ? ["--squash"] : ["--ff-only"]),
|
|
146
|
+
sourceHead,
|
|
147
|
+
], target);
|
|
148
|
+
process.stdout.write(result.stdout);
|
|
149
|
+
if (result.code)
|
|
150
|
+
throw new Error(`${result.stderr.trim()}\nMerge did not complete in ${target}; source workspace was retained. Inspect git status before continuing`);
|
|
151
|
+
if (values.squash) {
|
|
152
|
+
const integratedTree = git(target, "write-tree");
|
|
153
|
+
const staged = await execute(["git", "-C", target, "diff", "--cached", "--quiet"], target);
|
|
154
|
+
if (staged.code > 1)
|
|
155
|
+
throw new Error(staged.stderr || "Cannot inspect squash result");
|
|
156
|
+
if (staged.code === 1)
|
|
157
|
+
console.log(git(target, "commit", "-m", required(values, "message")));
|
|
158
|
+
if (git(target, "rev-parse", "HEAD^{tree}") !== integratedTree)
|
|
159
|
+
throw new Error("Squash result changed during commit; source workspace retained");
|
|
160
|
+
}
|
|
161
|
+
assertReady(target, true, selected);
|
|
162
|
+
if (!values.squash &&
|
|
163
|
+
git(target, "merge-base", "HEAD", sourceHead) !== sourceHead)
|
|
164
|
+
throw new Error("Target no longer contains the source; cleanup skipped");
|
|
165
|
+
if (!values.cleanup)
|
|
166
|
+
return 0;
|
|
167
|
+
// Recheck source immediately before removal; never force worktree/branch deletion.
|
|
168
|
+
assertReady(source.path, true, source.branch);
|
|
169
|
+
if (revision(source.path, "HEAD") !== sourceHead)
|
|
170
|
+
throw new Error("Source changed during merge; cleanup skipped");
|
|
171
|
+
if (source.path === context.repo)
|
|
172
|
+
throw new Error("Integration succeeded; main checkout cannot be removed");
|
|
173
|
+
process.chdir(target);
|
|
174
|
+
git(context.repo, "worktree", "remove", source.path);
|
|
175
|
+
await context.store.locked(() => context.store.archiveWorktrees([source.path]));
|
|
176
|
+
const deleted = await execute(["git", "-C", target, "branch", "-d", "--", source.branch], target);
|
|
177
|
+
if (deleted.code)
|
|
178
|
+
console.log(`Worktree removed; branch ${source.branch} retained: ${deleted.stderr.trim()}`);
|
|
179
|
+
else
|
|
180
|
+
process.stdout.write(deleted.stdout);
|
|
181
|
+
return 0;
|
|
182
|
+
});
|
|
183
|
+
}
|