@goodtek/vibeops 2.2.1 → 2.4.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/CHANGELOG.md +30 -0
- package/README.md +22 -9
- package/dist/cli.js +43 -6
- package/dist/commands/task-del.js +104 -0
- package/dist/commands/task-reship.js +17 -171
- package/dist/commands/task-ship.js +260 -46
- package/dist/lib/filesystem.js +4 -1
- package/dist/lib/inquirer-helpers.js +4 -0
- package/dist/lib/pr-create.js +29 -2
- package/dist/lib/resolve-task.js +31 -1
- package/dist/lib/task-commit-msg.js +80 -0
- package/dist/lib/task-del.js +193 -0
- package/dist/lib/task-effective-status.js +15 -7
- package/dist/lib/task-git-commit.js +24 -0
- package/dist/lib/task-new-cycle.js +186 -0
- package/dist/lib/task-ship-state.js +39 -0
- package/package.json +3 -2
- package/templates/clients/claude/CLAUDE.md +1 -1
- package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +1 -1
- package/templates/clients/cursor/.cursor/rules/03-docs-before-ship.mdc +1 -1
- package/templates/core/AGENTS.md +10 -7
- package/templates/skills-shared/implement-task/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,36 @@ All notable changes to VibeOps are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.4.0 - 2026-06-12
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`vibeops task ship` is now state-aware.** It detects the TASK's PR/MR state and branches:
|
|
12
|
+
1. **No PR** → push + open a new PR (first submit, Status → Shipped).
|
|
13
|
+
2. **Open PR (unmerged)** → commit + push the **same** branch to update the existing PR (no new PR); CI re-runs. This replaces the manual `git add/commit/push` loop.
|
|
14
|
+
3. **Merged PR** → start a **new** PR cycle (former `reship`), guarded by an interactive confirm (`This starts a NEW PR cycle after merge. Continue? (y/N)`) or `--new-cycle` in non-interactive mode.
|
|
15
|
+
- Every path prints a one-line summary: `Created PR #<n> → <url>`, `Updated existing PR #<n> (pushed <sha>) — CI re-running`, `Started new PR cycle → PR #<n>`, or `Nothing to ship (working tree clean, PR #<n> up to date)`.
|
|
16
|
+
- Commit messages are auto-scoped with the TASK id (`feat(task-001): …`). `ship` refuses when HEAD is not the TASK branch.
|
|
17
|
+
- PR/MR state resolution is unified in a single source (`resolveTaskMergeRequestLifecycle`).
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`vibeops task ship`** flags: `-m/--message <msg>` (TASK id auto-prefixed; LLM-drafted or prompted when omitted), `--new-cycle` (alias `--reship`), `--no-commit` (push committed history only), `--non-interactive`.
|
|
22
|
+
|
|
23
|
+
### Deprecated
|
|
24
|
+
|
|
25
|
+
- **`vibeops task reship`** is now a thin alias for `vibeops task ship --new-cycle` and prints a deprecation notice.
|
|
26
|
+
|
|
27
|
+
## 2.3.0 - 2026-06-09
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`vibeops task del [TASK-NNN]`** — cancel a TASK before merge: delete `docs/tasks/*.md`, close open MR/PR (unless `--no-close-mr`), delete local/remote `task/*` branch. Refuses when the MR is **merged** into integration.
|
|
32
|
+
|
|
33
|
+
### Flags
|
|
34
|
+
|
|
35
|
+
- `--dry-run`, `--force` (dirty working tree), `--no-remote-delete`, `--no-close-mr`, `--cwd`
|
|
36
|
+
|
|
7
37
|
## 2.2.1 - 2026-06-09
|
|
8
38
|
|
|
9
39
|
### Fixed
|
package/README.md
CHANGED
|
@@ -13,8 +13,9 @@ VibeOps bootstraps an **agent-friendly repo**, starts numbered **TASK** files on
|
|
|
13
13
|
|---------|---------|
|
|
14
14
|
| `vibeops init` | Core docs + agent packs (cursor / claude / codex) |
|
|
15
15
|
| `vibeops task add` | New `TASK-NNN` file + task branch |
|
|
16
|
-
| `vibeops task
|
|
17
|
-
| `vibeops task
|
|
16
|
+
| `vibeops task del` | Cancel TASK before merge (md + branch + close open MR) |
|
|
17
|
+
| `vibeops task ship` | State-aware submit: open a new PR · update the open PR · start a new PR cycle after merge (Status → **Shipped**) |
|
|
18
|
+
| `vibeops task reship` | **Deprecated** alias for `task ship --new-cycle` |
|
|
18
19
|
| `vibeops task merge` | Merge TASK MR/PR into integration branch (default: squash) |
|
|
19
20
|
| `vibeops task sync` | After merge: ff-only integration pull, delete task branches (no TASK md edits) |
|
|
20
21
|
| `vibeops task release` | Release PR: integration → production (GitFlow) |
|
|
@@ -55,13 +56,24 @@ vibeops task add
|
|
|
55
56
|
vibeops task ship
|
|
56
57
|
vibeops task merge
|
|
57
58
|
vibeops pull
|
|
58
|
-
#
|
|
59
|
-
vibeops task
|
|
59
|
+
# Same TASK, before merge — edit, then just re-run ship (updates the open PR):
|
|
60
|
+
vibeops task ship -m "address review"
|
|
61
|
+
# Same TASK, after merge — start a new PR cycle:
|
|
62
|
+
vibeops task ship --new-cycle
|
|
60
63
|
vibeops task merge
|
|
61
64
|
vibeops task sync
|
|
62
65
|
vibeops task add
|
|
63
66
|
```
|
|
64
67
|
|
|
68
|
+
`ship` detects the TASK's PR state and does the right thing:
|
|
69
|
+
|
|
70
|
+
| State | `ship` does | Output |
|
|
71
|
+
|-------|-------------|--------|
|
|
72
|
+
| No PR yet | push + open a new PR | `Created PR #<n> → <url>` |
|
|
73
|
+
| PR open (unmerged) | commit + push the **same** branch (no new PR), CI re-runs | `Updated existing PR #<n> (pushed <sha>) — CI re-running` |
|
|
74
|
+
| PR merged | start a **new** PR cycle (needs confirm or `--new-cycle`) | `Started new PR cycle → PR #<n>` |
|
|
75
|
+
| No change | no-op | `Nothing to ship (working tree clean, PR #<n> up to date)` |
|
|
76
|
+
|
|
65
77
|
Occasionally (GitFlow release to production):
|
|
66
78
|
|
|
67
79
|
```bash
|
|
@@ -72,7 +84,7 @@ Only one TASK **In Progress** at a time (`task add` blocks otherwise). **Shipped
|
|
|
72
84
|
|
|
73
85
|
## LLM (optional)
|
|
74
86
|
|
|
75
|
-
For **`task add`** / **`task ship`**
|
|
87
|
+
For **`task add`** / **`task ship`** only (not for coding in the IDE). When `-m` is omitted, `ship` uses a connected provider to draft the commit subject:
|
|
76
88
|
|
|
77
89
|
```bash
|
|
78
90
|
vibeops llm connect
|
|
@@ -97,8 +109,9 @@ pnpm install && pnpm build && pnpm smoke
|
|
|
97
109
|
|
|
98
110
|
- **`init`**: `--clients`, `--yes`, `--dry-run`, `--force`, `--git`, `--initial-commit`, `--git-policy gitflow|trunk`, `--integration-branch`, `--production-branch`, `--allow-no-remote`, `--cwd`
|
|
99
111
|
- **`task add`**: `--dry-run`, `--non-interactive --idea "…"`
|
|
100
|
-
- **`task
|
|
101
|
-
- **`task
|
|
112
|
+
- **`task del`**: `--dry-run`, `--force`, `--no-remote-delete`, `--no-close-mr`
|
|
113
|
+
- **`task ship`**: `-m/--message`, `--new-cycle` (alias `--reship`), `--no-commit`, `--dry-run`, `--no-pr`, `--non-interactive`, `--allow-open-mr`, `--no-integrate`, `--recreate-branch`, `--skip-llm`
|
|
114
|
+
- **`task reship`** (deprecated): `--dry-run`, `--no-pr`, `--no-integrate`, `--recreate-branch`, `--skip-llm`, `--allow-open-mr` → delegates to `task ship --new-cycle`
|
|
102
115
|
- **`task merge`**: `--dry-run`, `--merge`, `--rebase`
|
|
103
116
|
- **`task sync`**: `--dry-run`, `--no-remote-delete`, `--force`
|
|
104
117
|
- **`task release`**: `--dry-run`, `--no-merge`, `--merge`, `--rebase`
|
|
@@ -109,8 +122,8 @@ pnpm install && pnpm build && pnpm smoke
|
|
|
109
122
|
|
|
110
123
|
- **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`).
|
|
111
124
|
- **`task add`**: `task/<slug>` from **integration** (e.g. `develop`).
|
|
112
|
-
- **`task ship
|
|
113
|
-
- **`task reship
|
|
125
|
+
- **`task ship`** (state-aware): **no PR** → commit + ship metadata (Status **Shipped**) → push → open MR/PR; **open PR** → commit + push the same branch, CI re-runs, no new PR; **merged PR** → new PR cycle (carries uncommitted work onto the task branch, integrates `develop`, opens a **new** PR). Commit messages are TASK-id-scoped (`feat(task-001): …`). Refuses when HEAD is not the task branch.
|
|
126
|
+
- **`task reship`** *(deprecated)*: alias for `task ship --new-cycle`.
|
|
114
127
|
- **`task merge`**: merge MR/PR into integration (CLI or host UI; TASK md unchanged).
|
|
115
128
|
- **`task sync`**: integration ff-only pull → delete `task/*` branches (TASK md unchanged).
|
|
116
129
|
- **`pull`**: fetch + switch to integration branch + `git pull --ff-only` (one command).
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initCommand } from "./commands/init.js";
|
|
|
5
5
|
import { llmConnectCommand, llmStatusCommand, llmUseCommand } from "./commands/llm.js";
|
|
6
6
|
import { statusCommand } from "./commands/status.js";
|
|
7
7
|
import { taskAddCommand } from "./commands/task-add.js";
|
|
8
|
+
import { taskDelCommand } from "./commands/task-del.js";
|
|
8
9
|
import { taskMergeCommand } from "./commands/task-merge.js";
|
|
9
10
|
import { taskReleaseCommand } from "./commands/task-release.js";
|
|
10
11
|
import { taskShipCommand } from "./commands/task-ship.js";
|
|
@@ -58,7 +59,9 @@ program
|
|
|
58
59
|
gitHost: opts.gitHost,
|
|
59
60
|
});
|
|
60
61
|
});
|
|
61
|
-
const task = program
|
|
62
|
+
const task = program
|
|
63
|
+
.command("task")
|
|
64
|
+
.description("TASK lifecycle (add · del · ship · reship · merge · sync · release)");
|
|
62
65
|
task
|
|
63
66
|
.command("add")
|
|
64
67
|
.description("Create next TASK-NNN, task branch, In Progress")
|
|
@@ -74,22 +77,56 @@ task
|
|
|
74
77
|
cwd: opts.cwd,
|
|
75
78
|
});
|
|
76
79
|
});
|
|
80
|
+
task
|
|
81
|
+
.command("del [taskRef]")
|
|
82
|
+
.description("Delete TASK before merge: remove md, close open MR/PR, delete task branch")
|
|
83
|
+
.option("--dry-run", "Plan only")
|
|
84
|
+
.option("--force", "Proceed with a dirty working tree (changes may remain)")
|
|
85
|
+
.option("--no-remote-delete", "Keep the task branch on the remote")
|
|
86
|
+
.option("--no-close-mr", "Do not close an open MR/PR")
|
|
87
|
+
.option("--cwd <path>", "Target directory")
|
|
88
|
+
.action(async (taskRef, opts) => {
|
|
89
|
+
await taskDelCommand(taskRef, {
|
|
90
|
+
dryRun: Boolean(opts.dryRun),
|
|
91
|
+
force: Boolean(opts.force),
|
|
92
|
+
noRemoteDelete: Boolean(opts.noRemoteDelete),
|
|
93
|
+
noCloseMr: Boolean(opts.noCloseMr),
|
|
94
|
+
cwd: opts.cwd,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
77
97
|
task
|
|
78
98
|
.command("ship [taskRef]")
|
|
79
|
-
.description("
|
|
99
|
+
.description("State-aware submit: new PR · update open PR · new PR cycle after merge (Status → Shipped)")
|
|
80
100
|
.option("--dry-run", "Plan only")
|
|
101
|
+
.option("-m, --message <msg>", "Commit message (TASK id auto-prefixed; LLM/prompt when omitted)")
|
|
102
|
+
.option("--new-cycle", "Allow a new PR cycle after merge without prompting (alias: --reship)")
|
|
103
|
+
.option("--reship", "Alias for --new-cycle")
|
|
104
|
+
.option("--no-commit", "Push already-committed changes only (skip staging/commit)")
|
|
81
105
|
.option("--no-pr", "Push only; skip creating MR/PR")
|
|
106
|
+
.option("--non-interactive", "CI: never prompt")
|
|
107
|
+
.option("--no-integrate", "New PR cycle: skip merging integration into the task branch")
|
|
108
|
+
.option("--recreate-branch", "New PR cycle: recreate task branch from integration")
|
|
109
|
+
.option("--skip-llm", "New PR cycle: do not run LLM for Result / Test Result")
|
|
110
|
+
.option("--allow-open-mr", "Update the open MR/PR with new commits instead of a new cycle")
|
|
82
111
|
.option("--cwd <path>", "Target directory")
|
|
83
112
|
.action(async (taskRef, opts) => {
|
|
84
113
|
await taskShipCommand(taskRef, {
|
|
85
114
|
dryRun: Boolean(opts.dryRun),
|
|
86
|
-
|
|
115
|
+
message: opts.message,
|
|
116
|
+
newCycle: Boolean(opts.newCycle) || Boolean(opts.reship),
|
|
117
|
+
noCommit: opts.commit === false,
|
|
118
|
+
noPr: opts.pr === false,
|
|
119
|
+
nonInteractive: Boolean(opts.nonInteractive),
|
|
120
|
+
noIntegrate: opts.integrate === false,
|
|
121
|
+
recreateBranch: Boolean(opts.recreateBranch),
|
|
122
|
+
skipLlm: Boolean(opts.skipLlm),
|
|
123
|
+
allowOpenMr: Boolean(opts.allowOpenMr),
|
|
87
124
|
cwd: opts.cwd,
|
|
88
125
|
});
|
|
89
126
|
});
|
|
90
127
|
task
|
|
91
128
|
.command("reship [taskRef]")
|
|
92
|
-
.description("
|
|
129
|
+
.description("Deprecated alias for `task ship --new-cycle` (start a new PR cycle after merge)")
|
|
93
130
|
.option("--dry-run", "Plan only")
|
|
94
131
|
.option("--no-pr", "Push only; skip creating MR/PR")
|
|
95
132
|
.option("--no-integrate", "Skip merging integration branch into task branch")
|
|
@@ -100,8 +137,8 @@ task
|
|
|
100
137
|
.action(async (taskRef, opts) => {
|
|
101
138
|
await taskReshipCommand(taskRef, {
|
|
102
139
|
dryRun: Boolean(opts.dryRun),
|
|
103
|
-
noPr:
|
|
104
|
-
noIntegrate:
|
|
140
|
+
noPr: opts.pr === false,
|
|
141
|
+
noIntegrate: opts.integrate === false,
|
|
105
142
|
recreateBranch: Boolean(opts.recreateBranch),
|
|
106
143
|
skipLlm: Boolean(opts.skipLlm),
|
|
107
144
|
allowOpenMr: Boolean(opts.allowOpenMr),
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { bold, cyan, dim, log } from "../lib/logger.js";
|
|
3
|
+
import { projectPaths } from "../lib/paths.js";
|
|
4
|
+
import { relPath } from "../lib/task-context.js";
|
|
5
|
+
import { assertTaskDeletable, assertWorkingTreeForDel, closeOpenTaskMergeRequestIfNeeded, deleteTaskBranches, deleteTaskMarkdown, loadGitConfigForDel, resolveDelTarget, taskDelNotFoundMessage, validateTaskBranchForDel, } from "../lib/task-del.js";
|
|
6
|
+
import { readGitContext, statusDisplay } from "../lib/task.js";
|
|
7
|
+
export async function taskDelCommand(taskRef, options = {}) {
|
|
8
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
9
|
+
const dryRun = options.dryRun === true;
|
|
10
|
+
const paths = projectPaths(cwd);
|
|
11
|
+
const target = await resolveDelTarget(paths, cwd, taskRef);
|
|
12
|
+
if (target === null) {
|
|
13
|
+
log.error(taskDelNotFoundMessage(taskRef));
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { taskId, taskFile, meta, taskBranch } = target;
|
|
18
|
+
const relFile = relPath(cwd, taskFile);
|
|
19
|
+
log.info(bold(`vibeops task del ${taskId}`));
|
|
20
|
+
log.info(` ${dim("file")} ${relFile}`);
|
|
21
|
+
log.info(` ${dim("branch")} ${taskBranch}`);
|
|
22
|
+
log.info(` ${dim("status")} ${statusDisplay(meta.status)}`);
|
|
23
|
+
log.blank();
|
|
24
|
+
const deletable = await assertTaskDeletable(cwd, target);
|
|
25
|
+
if (!deletable.ok) {
|
|
26
|
+
log.error(deletable.reason);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const gitCfg = dryRun
|
|
31
|
+
? await loadGitConfigForDel(cwd).catch(() => null)
|
|
32
|
+
: await loadGitConfigForDel(cwd);
|
|
33
|
+
if (!dryRun && gitCfg === null) {
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const integrationBranch = gitCfg?.integrationBranch ?? "develop";
|
|
38
|
+
const productionBranch = gitCfg?.productionBranch ?? "main";
|
|
39
|
+
const remote = gitCfg?.remote ?? "origin";
|
|
40
|
+
if (!validateTaskBranchForDel(taskBranch, integrationBranch, productionBranch)) {
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (dryRun) {
|
|
45
|
+
log.info(bold("dry-run — would:"));
|
|
46
|
+
const ctx = await readGitContext(taskFile);
|
|
47
|
+
if (ctx !== null) {
|
|
48
|
+
await closeOpenTaskMergeRequestIfNeeded({
|
|
49
|
+
cwd,
|
|
50
|
+
taskFile,
|
|
51
|
+
taskBranch,
|
|
52
|
+
baseBranch: ctx.baseBranch,
|
|
53
|
+
dryRun: true,
|
|
54
|
+
skipClose: options.noCloseMr === true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
log.info(` · git switch ${integrationBranch} (if on ${taskBranch})`);
|
|
58
|
+
await deleteTaskBranches({
|
|
59
|
+
cwd,
|
|
60
|
+
taskBranch,
|
|
61
|
+
integrationBranch,
|
|
62
|
+
remote,
|
|
63
|
+
dryRun: true,
|
|
64
|
+
noRemoteDelete: options.noRemoteDelete === true,
|
|
65
|
+
});
|
|
66
|
+
await deleteTaskMarkdown(taskFile, true);
|
|
67
|
+
log.blank();
|
|
68
|
+
log.info(`Next: ${cyan("vibeops task add")}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!(await assertWorkingTreeForDel(cwd, options.force === true))) {
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const ctx = await readGitContext(taskFile);
|
|
76
|
+
if (ctx === null) {
|
|
77
|
+
log.error("Git Context missing.");
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await closeOpenTaskMergeRequestIfNeeded({
|
|
82
|
+
cwd,
|
|
83
|
+
taskFile,
|
|
84
|
+
taskBranch,
|
|
85
|
+
baseBranch: ctx.baseBranch,
|
|
86
|
+
dryRun: false,
|
|
87
|
+
skipClose: options.noCloseMr === true,
|
|
88
|
+
});
|
|
89
|
+
const branchesOk = await deleteTaskBranches({
|
|
90
|
+
cwd,
|
|
91
|
+
taskBranch,
|
|
92
|
+
integrationBranch,
|
|
93
|
+
remote,
|
|
94
|
+
dryRun: false,
|
|
95
|
+
noRemoteDelete: options.noRemoteDelete === true,
|
|
96
|
+
});
|
|
97
|
+
if (!branchesOk) {
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await deleteTaskMarkdown(taskFile, false);
|
|
102
|
+
log.blank();
|
|
103
|
+
log.info(`Next: ${cyan("vibeops task add")}`);
|
|
104
|
+
}
|
|
@@ -1,175 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { finishTaskWithPullRequest } from "../lib/task-push-pr.js";
|
|
8
|
-
import { applyTaskShipMemory, llmCompleteTaskReship, writeTaskResultSections, } from "../lib/task-ship-llm.js";
|
|
9
|
-
import { assertMergeRequestNotOpen, ensureTaskBranchForReship, integrateIntegrationIntoTaskBranch, loadGitConfigForReship, prepareGitContextForReship, refreshGitContextBaseCommit, resolveReshipTarget, } from "../lib/task-reship.js";
|
|
10
|
-
import { hasNonEmptySection, parseTaskFilename, readGitContext, } from "../lib/task.js";
|
|
11
|
-
import { listWorkingTreeRelPaths } from "../lib/git.js";
|
|
12
|
-
async function commitReshipMetadata(opts) {
|
|
13
|
-
if (opts.dryRun) {
|
|
14
|
-
log.info(dim(" would commit reship metadata (Status stays Shipped)"));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const committed = await commitDirtyWorkingTree(opts.cwd, docsCommitMessageFor(opts.taskId, "reship metadata"), false);
|
|
18
|
-
if (!committed) {
|
|
19
|
-
log.info(dim("No reship metadata commit (Git Context already on disk)."));
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
import { dim, log, yellow } from "../lib/logger.js";
|
|
2
|
+
import { taskShipCommand } from "./task-ship.js";
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated `reship` is now `vibeops task ship --new-cycle`.
|
|
5
|
+
* Kept as a thin alias for backward compatibility.
|
|
6
|
+
*/
|
|
22
7
|
export async function taskReshipCommand(taskRef, options = {}) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const dryRun = options.dryRun === true;
|
|
26
|
-
const target = await resolveReshipTarget(paths, cwd, taskRef);
|
|
27
|
-
if (target === null) {
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const { taskId, taskFile, meta, gitCtx: initialCtx } = target;
|
|
32
|
-
const relFile = relPath(cwd, taskFile);
|
|
33
|
-
log.info(bold(`vibeops task reship ${taskId}`));
|
|
34
|
-
log.info(` ${dim("file")} ${relFile}`);
|
|
35
|
-
log.info(` ${dim("branch")} ${initialCtx.taskBranch}`);
|
|
36
|
-
log.info(` ${dim("status")} Shipped (unchanged)`);
|
|
37
|
-
log.blank();
|
|
38
|
-
const gitCfg = dryRun
|
|
39
|
-
? await loadGitConfigForReship(cwd).catch(() => null)
|
|
40
|
-
: await loadGitConfigForReship(cwd);
|
|
41
|
-
if (!dryRun && gitCfg === null) {
|
|
42
|
-
process.exitCode = 1;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const integrationBranch = gitCfg?.integrationBranch ?? "develop";
|
|
46
|
-
const remote = gitCfg?.remote ?? "origin";
|
|
47
|
-
const updateOpenMr = options.allowOpenMr === true;
|
|
48
|
-
if (!dryRun && !(await assertMergeRequestNotOpen(cwd, taskFile, updateOpenMr))) {
|
|
49
|
-
process.exitCode = 1;
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const branchOk = await ensureTaskBranchForReship({
|
|
53
|
-
cwd,
|
|
54
|
-
taskFile,
|
|
55
|
-
gitCtx: initialCtx,
|
|
56
|
-
integrationBranch,
|
|
57
|
-
remote,
|
|
58
|
-
recreateBranch: options.recreateBranch === true,
|
|
59
|
-
dryRun,
|
|
60
|
-
});
|
|
61
|
-
if (!branchOk) {
|
|
62
|
-
process.exitCode = 1;
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (options.noIntegrate !== true) {
|
|
66
|
-
const integrated = await integrateIntegrationIntoTaskBranch({
|
|
67
|
-
cwd,
|
|
68
|
-
integrationBranch,
|
|
69
|
-
remote,
|
|
70
|
-
dryRun,
|
|
71
|
-
});
|
|
72
|
-
if (!integrated) {
|
|
73
|
-
process.exitCode = 1;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
log.info(dim("Skipped integration merge (--no-integrate)."));
|
|
79
|
-
}
|
|
80
|
-
const body = await readText(taskFile);
|
|
81
|
-
let resultOk = hasNonEmptySection(body, "Result");
|
|
82
|
-
let testOk = hasNonEmptySection(body, "Test Result");
|
|
83
|
-
if (!options.skipLlm && !dryRun) {
|
|
84
|
-
log.step("LLM — follow-up Result / Test Result");
|
|
85
|
-
const patch = await llmCompleteTaskReship({
|
|
86
|
-
cwd,
|
|
87
|
-
taskId,
|
|
88
|
-
taskTitle: meta.title,
|
|
89
|
-
taskBody: body,
|
|
90
|
-
taskFileRel: relFile,
|
|
91
|
-
});
|
|
92
|
-
if (patch !== null) {
|
|
93
|
-
await writeTaskResultSections(taskFile, patch.result, patch.testResult);
|
|
94
|
-
log.ok(`Result / Test Result updated (${patch.provider})`);
|
|
95
|
-
const memory = await applyTaskShipMemory(cwd, patch);
|
|
96
|
-
for (const p of memory.updated)
|
|
97
|
-
log.ok(`Updated ${p}`);
|
|
98
|
-
for (const p of memory.skipped)
|
|
99
|
-
log.skip(`Skipped ${p}`);
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
log.warn("LLM unavailable — ensure Result / Test Result describe this follow-up.");
|
|
103
|
-
}
|
|
104
|
-
const body2 = await readText(taskFile);
|
|
105
|
-
resultOk = hasNonEmptySection(body2, "Result");
|
|
106
|
-
testOk = hasNonEmptySection(body2, "Test Result");
|
|
107
|
-
}
|
|
108
|
-
else if (options.skipLlm) {
|
|
109
|
-
log.info(dim("LLM skipped (--skip-llm)."));
|
|
110
|
-
}
|
|
111
|
-
log.info(bold("Sections"));
|
|
112
|
-
log.info(` ${resultOk ? green("✓") : yellow("·")} Result`);
|
|
113
|
-
log.info(` ${testOk ? green("✓") : yellow("·")} Test Result`);
|
|
8
|
+
log.warn(`${yellow("reship is now `ship` (deprecated alias)")} — delegating to \`vibeops task ship --new-cycle\`.`);
|
|
9
|
+
log.info(dim("Run `vibeops task ship` directly; it detects first submit / open-PR update / new PR cycle."));
|
|
114
10
|
log.blank();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
log.info(`Next: ${cyan("vibeops task merge")}, then optional ${cyan("task sync")}`);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (!resultOk || !testOk) {
|
|
128
|
-
log.error("Fill Result and Test Result for this follow-up, then rerun.");
|
|
129
|
-
process.exitCode = 1;
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const dirtyBefore = await listWorkingTreeRelPaths(cwd);
|
|
133
|
-
if (dirtyBefore.length === 0) {
|
|
134
|
-
log.error("No changes to reship. Edit files (e.g. on develop), then rerun.");
|
|
135
|
-
process.exitCode = 1;
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const parts = parseTaskFilename(taskFile);
|
|
139
|
-
await commitDirtyWorkingTree(cwd, featCommitMessageFor(parts.id, meta.title), false);
|
|
140
|
-
let gitCtx = await readGitContext(taskFile);
|
|
141
|
-
if (gitCtx === null) {
|
|
142
|
-
log.error("Git Context missing after commit.");
|
|
143
|
-
process.exitCode = 1;
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
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
|
-
}
|
|
159
|
-
const prResult = await finishTaskWithPullRequest({
|
|
160
|
-
cwd,
|
|
161
|
-
taskFile,
|
|
162
|
-
skipPr: options.noPr === true,
|
|
163
|
-
forceNewMergeRequest: !updateOpenMr,
|
|
11
|
+
await taskShipCommand(taskRef, {
|
|
12
|
+
dryRun: options.dryRun,
|
|
13
|
+
cwd: options.cwd,
|
|
14
|
+
noPr: options.noPr,
|
|
15
|
+
newCycle: true,
|
|
16
|
+
noIntegrate: options.noIntegrate,
|
|
17
|
+
recreateBranch: options.recreateBranch,
|
|
18
|
+
skipLlm: options.skipLlm,
|
|
19
|
+
allowOpenMr: options.allowOpenMr,
|
|
164
20
|
});
|
|
165
|
-
if (!prResult.ok) {
|
|
166
|
-
log.error("Reship failed — fix push/MR, then rerun task reship.");
|
|
167
|
-
process.exitCode = 1;
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (prResult.mergeRequestUrl) {
|
|
171
|
-
log.info(` ${dim("MR/PR")} ${prResult.mergeRequestUrl}`);
|
|
172
|
-
}
|
|
173
|
-
log.blank();
|
|
174
|
-
log.info(`Next: ${cyan("vibeops task merge")} (or merge in the host UI), then ${cyan("vibeops task sync")}`);
|
|
175
21
|
}
|