@goodtek/vibeops 2.2.0 → 2.2.1
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/CHANGELOG.md +7 -0
- package/dist/cli.js +1 -1
- package/dist/commands/task-reship.js +22 -11
- package/dist/lib/task-push-pr.js +34 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to VibeOps are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.2.1 - 2026-06-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`vibeops task reship --allow-open-mr`**: push then reuse the open MR/PR instead of failing on duplicate `gh pr create`. Skips reship metadata archive when updating the same PR.
|
|
12
|
+
- **`finishTaskWithPullRequest`**: push before checking for an open MR so follow-up commits reach the remote.
|
|
13
|
+
|
|
7
14
|
## 2.2.0 - 2026-06-09
|
|
8
15
|
|
|
9
16
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -95,7 +95,7 @@ task
|
|
|
95
95
|
.option("--no-integrate", "Skip merging integration branch into task branch")
|
|
96
96
|
.option("--recreate-branch", "Create task branch from integration instead of reusing local/remote ref")
|
|
97
97
|
.option("--skip-llm", "Do not run LLM for Result / Test Result")
|
|
98
|
-
.option("--allow-open-mr", "
|
|
98
|
+
.option("--allow-open-mr", "Update the open MR/PR with new commits (merge before reship for a new PR)")
|
|
99
99
|
.option("--cwd <path>", "Target directory")
|
|
100
100
|
.action(async (taskRef, opts) => {
|
|
101
101
|
await taskReshipCommand(taskRef, {
|
|
@@ -44,7 +44,8 @@ export async function taskReshipCommand(taskRef, options = {}) {
|
|
|
44
44
|
}
|
|
45
45
|
const integrationBranch = gitCfg?.integrationBranch ?? "develop";
|
|
46
46
|
const remote = gitCfg?.remote ?? "origin";
|
|
47
|
-
|
|
47
|
+
const updateOpenMr = options.allowOpenMr === true;
|
|
48
|
+
if (!dryRun && !(await assertMergeRequestNotOpen(cwd, taskFile, updateOpenMr))) {
|
|
48
49
|
process.exitCode = 1;
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
@@ -113,7 +114,12 @@ export async function taskReshipCommand(taskRef, options = {}) {
|
|
|
113
114
|
log.blank();
|
|
114
115
|
if (dryRun) {
|
|
115
116
|
log.info(bold("dry-run — would also:"));
|
|
116
|
-
|
|
117
|
+
if (updateOpenMr) {
|
|
118
|
+
log.info(" · feat commit, push, update open MR/PR (unless --no-pr)");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
log.info(" · feat commit + reship metadata, then push once and open MR/PR (unless --no-pr)");
|
|
122
|
+
}
|
|
117
123
|
log.blank();
|
|
118
124
|
log.info(`Next: ${cyan("vibeops task merge")}, then optional ${cyan("task sync")}`);
|
|
119
125
|
return;
|
|
@@ -137,19 +143,24 @@ export async function taskReshipCommand(taskRef, options = {}) {
|
|
|
137
143
|
process.exitCode = 1;
|
|
138
144
|
return;
|
|
139
145
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
taskFile,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
if (updateOpenMr) {
|
|
147
|
+
log.info(dim("Updating open MR/PR (--allow-open-mr)."));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
gitCtx = await refreshGitContextBaseCommit(taskFile, gitCtx, cwd);
|
|
151
|
+
gitCtx = await prepareGitContextForReship(cwd, taskFile, gitCtx);
|
|
152
|
+
await commitReshipMetadata({
|
|
153
|
+
cwd,
|
|
154
|
+
taskFile,
|
|
155
|
+
taskId: parts.id,
|
|
156
|
+
dryRun: false,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
148
159
|
const prResult = await finishTaskWithPullRequest({
|
|
149
160
|
cwd,
|
|
150
161
|
taskFile,
|
|
151
162
|
skipPr: options.noPr === true,
|
|
152
|
-
forceNewMergeRequest:
|
|
163
|
+
forceNewMergeRequest: !updateOpenMr,
|
|
153
164
|
});
|
|
154
165
|
if (!prResult.ok) {
|
|
155
166
|
log.error("Reship failed — fix push/MR, then rerun task reship.");
|
package/dist/lib/task-push-pr.js
CHANGED
|
@@ -7,6 +7,12 @@ import { dim, log } from "./logger.js";
|
|
|
7
7
|
import { createMergeRequest, findMergeRequestByBranches, probeMergeRequestCli, } from "./pr-create.js";
|
|
8
8
|
import { fallbackTaskPr, generateTaskPrWithLlm } from "./task-pr-llm.js";
|
|
9
9
|
import { readGitContext, readTaskFile } from "./task.js";
|
|
10
|
+
function mergeRequestAlreadyExistsMessage(msg) {
|
|
11
|
+
const lower = msg.toLowerCase();
|
|
12
|
+
return (lower.includes("already exists") ||
|
|
13
|
+
lower.includes("merge request already exists") ||
|
|
14
|
+
lower.includes("pull request already exists"));
|
|
15
|
+
}
|
|
10
16
|
async function buildDiffSummary(cwd, gitCtx) {
|
|
11
17
|
const files = await gitDiffNameOnly(cwd, `${gitCtx.baseCommit}..HEAD`);
|
|
12
18
|
const commits = await gitLogOneline(cwd, `${gitCtx.baseCommit}..HEAD`);
|
|
@@ -55,19 +61,6 @@ export async function finishTaskWithPullRequest(opts) {
|
|
|
55
61
|
if (git.branch !== gitCtx.taskBranch) {
|
|
56
62
|
log.warn(`Current branch is ${git.branch ?? "(detached)"}, not ${gitCtx.taskBranch}. Push may fail.`);
|
|
57
63
|
}
|
|
58
|
-
if (opts.skipPr !== true && opts.forceNewMergeRequest !== true) {
|
|
59
|
-
const existing = await findMergeRequestByBranches({
|
|
60
|
-
cwd: opts.cwd,
|
|
61
|
-
host: gitCfg.host,
|
|
62
|
-
headBranch: gitCtx.taskBranch,
|
|
63
|
-
baseBranch: gitCtx.baseBranch,
|
|
64
|
-
state: "open",
|
|
65
|
-
});
|
|
66
|
-
if (existing !== null) {
|
|
67
|
-
log.info(dim(`Open ${label} already exists: ${existing.url}`));
|
|
68
|
-
return { ok: true, mergeRequestUrl: existing.url, pushed: true };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
64
|
const ahead = await gitCommitsAhead(opts.cwd, gitCtx.baseCommit, "HEAD");
|
|
72
65
|
if (ahead === 0) {
|
|
73
66
|
log.warn("No commits ahead of base — pushing branch anyway.");
|
|
@@ -85,6 +78,20 @@ export async function finishTaskWithPullRequest(opts) {
|
|
|
85
78
|
log.info(dim("Merge request creation skipped (--no-pr)."));
|
|
86
79
|
return { ok: true, pushed: true };
|
|
87
80
|
}
|
|
81
|
+
if (opts.forceNewMergeRequest !== true) {
|
|
82
|
+
const existing = await findMergeRequestByBranches({
|
|
83
|
+
cwd: opts.cwd,
|
|
84
|
+
host: gitCfg.host,
|
|
85
|
+
headBranch: gitCtx.taskBranch,
|
|
86
|
+
baseBranch: gitCtx.baseBranch,
|
|
87
|
+
state: "open",
|
|
88
|
+
});
|
|
89
|
+
if (existing !== null) {
|
|
90
|
+
log.ok(`Open ${label} updated: ${existing.url}`);
|
|
91
|
+
log.info(dim("MR/PR URL is on the host — not written to TASK md (use task merge)."));
|
|
92
|
+
return { ok: true, mergeRequestUrl: existing.url, pushed: true };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
88
95
|
const cliOk = await probeMergeRequestCli(gitCfg.host);
|
|
89
96
|
if (!cliOk) {
|
|
90
97
|
const tool = gitCfg.host === "gitlab" ? "glab" : "gh";
|
|
@@ -121,6 +128,20 @@ export async function finishTaskWithPullRequest(opts) {
|
|
|
121
128
|
}
|
|
122
129
|
catch (e) {
|
|
123
130
|
const msg = e instanceof Error ? e.message : String(e);
|
|
131
|
+
if (mergeRequestAlreadyExistsMessage(msg)) {
|
|
132
|
+
const existing = await findMergeRequestByBranches({
|
|
133
|
+
cwd: opts.cwd,
|
|
134
|
+
host: gitCfg.host,
|
|
135
|
+
headBranch: gitCtx.taskBranch,
|
|
136
|
+
baseBranch: gitCtx.baseBranch,
|
|
137
|
+
state: "open",
|
|
138
|
+
});
|
|
139
|
+
if (existing !== null) {
|
|
140
|
+
log.ok(`Open ${label} updated: ${existing.url}`);
|
|
141
|
+
log.info(dim("MR/PR URL is on the host — not written to TASK md (use task merge)."));
|
|
142
|
+
return { ok: true, mergeRequestUrl: existing.url, pushed: true };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
124
145
|
log.error(`Could not create merge request: ${msg}`);
|
|
125
146
|
log.info(dim(`Branch was pushed. Create MR manually: ${gitCtx.taskBranch} → ${gitCtx.baseBranch}`));
|
|
126
147
|
return { ok: false, pushed: true };
|