@goodtek/vibeops 2.4.4 → 2.5.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.
@@ -1,32 +1,14 @@
1
+ import { basename } from "node:path";
1
2
  import { execFile } from "node:child_process";
2
3
  import { promisify } from "node:util";
3
- import { askInput } from "./inquirer-helpers.js";
4
+ import { brewInstall, formatHostCliHint, formatHostCliMissingMessage, hostCliTool, parseOwnerRepo, probeBrew, probeHostCliAuth, remoteUrlForHost, repoExistsOnHost, runInteractiveCli, } from "./git-host-cli.js";
4
5
  import { detectGitHost } from "./git-host.js";
5
6
  import { gitRemoteUrl, runGit } from "./git.js";
6
- import { dim, log } from "./logger.js";
7
+ import { askInput, askSelect, askYesNo } from "./inquirer-helpers.js";
8
+ import { dim, log, yellow } from "./logger.js";
7
9
  const execFileAsync = promisify(execFile);
8
- async function probeGh() {
9
- try {
10
- await execFileAsync("gh", ["auth", "status"], { maxBuffer: 1024 * 1024 });
11
- return true;
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- async function probeGlab() {
18
- try {
19
- await execFileAsync("glab", ["auth", "status"], { maxBuffer: 1024 * 1024 });
20
- return true;
21
- }
22
- catch {
23
- return false;
24
- }
25
- }
26
10
  async function createGithubRepo(cwd, name) {
27
- const { stdout } = await execFileAsync("gh",
28
- // gh requires an explicit visibility flag when non-interactive.
29
- ["repo", "create", name, "--source=.", "--remote=origin", "--push", "--private"], { cwd, maxBuffer: 8 * 1024 * 1024 });
11
+ const { stdout } = await execFileAsync("gh", ["repo", "create", name, "--source=.", "--remote=origin", "--push", "--private"], { cwd, maxBuffer: 8 * 1024 * 1024 });
30
12
  const match = stdout.match(/https:\/\/github\.com\/[^\s]+/);
31
13
  if (match)
32
14
  return match[0].replace(/\.git$/, "") + ".git";
@@ -42,8 +24,135 @@ async function createGitlabRepo(cwd, name, defaultBranch) {
42
24
  return url;
43
25
  throw new Error("glab repo create finished but origin URL could not be read.");
44
26
  }
27
+ async function addRemoteUrl(cwd, remote, url) {
28
+ const host = detectGitHost(url);
29
+ if (!host) {
30
+ throw new Error("Only GitHub and GitLab HTTPS/SSH URLs are supported.");
31
+ }
32
+ await runGit(cwd, ["remote", "add", remote, url.trim()]);
33
+ return { remote, url: url.trim(), host };
34
+ }
35
+ async function resolveConnectUrl(host, input) {
36
+ const trimmed = input.trim();
37
+ const parsed = parseOwnerRepo(trimmed);
38
+ if (parsed) {
39
+ if (trimmed.startsWith("http") || trimmed.startsWith("git@") || trimmed.startsWith("ssh://")) {
40
+ return trimmed.endsWith(".git") ? trimmed : `${trimmed.replace(/\/$/, "")}.git`;
41
+ }
42
+ return remoteUrlForHost(host, parsed.owner, parsed.repo);
43
+ }
44
+ if (detectGitHost(trimmed)) {
45
+ return trimmed.endsWith(".git") ? trimmed : `${trimmed.replace(/\/$/, "")}.git`;
46
+ }
47
+ throw new Error(`Could not parse remote: ${trimmed}`);
48
+ }
49
+ async function repairMissingCli(host) {
50
+ const tool = hostCliTool(host);
51
+ const status = await probeHostCliAuth(host);
52
+ log.warn(status === "missing"
53
+ ? formatHostCliMissingMessage(host, "create a remote repository")
54
+ : `${tool} is installed but not logged in.`);
55
+ for (const line of formatHostCliHint(host)) {
56
+ log.info(dim(` ${line}`));
57
+ }
58
+ const brewOk = status === "missing" ? await probeBrew() : false;
59
+ const choices = [
60
+ "I installed / logged in — retry",
61
+ ...(brewOk ? [`Run brew install ${tool}`] : []),
62
+ "Enter remote URL manually",
63
+ "Skip remote for now",
64
+ ];
65
+ // Loop so brew install / retry can re-prompt.
66
+ for (;;) {
67
+ const pick = await askSelect({
68
+ message: "How do you want to continue?",
69
+ nonInteractive: false,
70
+ choices,
71
+ });
72
+ if (pick === "I installed / logged in — retry") {
73
+ const again = await probeHostCliAuth(host);
74
+ if (again === "ok")
75
+ return "retry";
76
+ log.warn(again === "missing"
77
+ ? `${tool} still not found on PATH.`
78
+ : `${tool} still not authenticated. Run \`${tool} auth login\`.`);
79
+ continue;
80
+ }
81
+ if (pick.startsWith("Run brew install")) {
82
+ log.info(dim(`Running: brew install ${tool}`));
83
+ try {
84
+ await brewInstall(tool);
85
+ log.ok(`Installed ${tool}`);
86
+ log.info(dim(`Next: ${tool} auth login`));
87
+ const loginNow = await askYesNo({
88
+ message: `Run \`${tool} auth login\` now?`,
89
+ nonInteractive: false,
90
+ defaultValue: true,
91
+ });
92
+ if (loginNow) {
93
+ try {
94
+ await runInteractiveCli(tool, ["auth", "login"]);
95
+ }
96
+ catch {
97
+ /* user may cancel */
98
+ }
99
+ }
100
+ }
101
+ catch (e) {
102
+ const msg = e instanceof Error ? e.message : String(e);
103
+ log.warn(`brew install failed: ${msg}`);
104
+ }
105
+ continue;
106
+ }
107
+ if (pick === "Enter remote URL manually")
108
+ return "manual";
109
+ return "skip";
110
+ }
111
+ }
112
+ async function connectExisting(cwd, remote, host) {
113
+ const input = await askInput({
114
+ message: "Repository URL or owner/repo",
115
+ nonInteractive: false,
116
+ required: true,
117
+ });
118
+ const url = await resolveConnectUrl(host, input);
119
+ const parsed = parseOwnerRepo(url) ?? parseOwnerRepo(input);
120
+ if (parsed && (await probeHostCliAuth(host)) === "ok") {
121
+ const exists = await repoExistsOnHost(host, parsed.owner, parsed.repo);
122
+ if (!exists) {
123
+ log.warn(yellow(`Could not verify ${parsed.owner}/${parsed.repo} on ${host} (missing or private). Continuing with remote add.`));
124
+ }
125
+ }
126
+ return addRemoteUrl(cwd, remote, url);
127
+ }
128
+ async function createOnHost(cwd, remote, host, defaultBranch) {
129
+ const folder = basename(cwd);
130
+ const label = host === "gitlab" ? "GitLab project path (group/project)" : "GitHub repository (owner/repo or name)";
131
+ const name = await askInput({
132
+ message: label,
133
+ nonInteractive: false,
134
+ required: true,
135
+ default: folder,
136
+ });
137
+ log.info(dim(`Creating ${host} repository via ${hostCliTool(host)}…`));
138
+ if (host === "gitlab") {
139
+ const url = await createGitlabRepo(cwd, name, defaultBranch);
140
+ // glab may name remote differently; ensure our remote name.
141
+ const current = await gitRemoteUrl(cwd, remote);
142
+ if (!current) {
143
+ const origin = await gitRemoteUrl(cwd, "origin");
144
+ if (origin && remote !== "origin") {
145
+ await runGit(cwd, ["remote", "rename", "origin", remote]);
146
+ }
147
+ }
148
+ return { remote, url, host };
149
+ }
150
+ const url = await createGithubRepo(cwd, name);
151
+ return { remote, url, host };
152
+ }
45
153
  /**
46
- * Ensures `origin` (or named remote) exists. Interactive path can create via gh/glab or manual URL.
154
+ * Ensures `origin` (or named remote) exists.
155
+ * Interactive: ask host → create/connect; soft-gate when CLI missing.
47
156
  */
48
157
  export async function ensureOriginRemote(opts) {
49
158
  const remote = opts.remoteName ?? "origin";
@@ -59,8 +168,9 @@ export async function ensureOriginRemote(opts) {
59
168
  return { remote, url: existing, host };
60
169
  }
61
170
  if (opts.dryRun) {
62
- log.info(dim(`would configure git remote ${remote}`));
63
- return { remote, url: "(dry-run)", host: "github" };
171
+ const host = opts.preferredHost ?? "github";
172
+ log.info(dim(`would configure git remote ${remote} (${host})`));
173
+ return { remote, url: "(dry-run)", host };
64
174
  }
65
175
  if (opts.allowMissing) {
66
176
  return null;
@@ -69,40 +179,48 @@ export async function ensureOriginRemote(opts) {
69
179
  throw new Error(`Git remote "${remote}" is not configured. Add it with: git remote add ${remote} <url>`);
70
180
  }
71
181
  log.blank();
72
- log.info("Remote origin is required for push and merge requests.");
73
- const ghOk = await probeGh();
74
- const glabOk = await probeGlab();
75
- if (ghOk) {
76
- const name = await askInput({
77
- message: "GitHub repository name (owner/repo or name for gh to create)",
78
- nonInteractive: false,
79
- required: true,
80
- });
81
- log.info(dim("Creating GitHub repository via gh…"));
82
- const url = await createGithubRepo(opts.cwd, name);
83
- return { remote, url, host: "github" };
84
- }
85
- if (glabOk) {
86
- const name = await askInput({
87
- message: "GitLab project path (group/project)",
88
- nonInteractive: false,
89
- required: true,
90
- });
91
- log.info(dim("Creating GitLab project via glab…"));
92
- const defaultBranch = typeof opts.defaultBranch === "string" && opts.defaultBranch.length > 0
93
- ? opts.defaultBranch
94
- : "main";
95
- const url = await createGitlabRepo(opts.cwd, name, defaultBranch);
96
- return { remote, url, host: "gitlab" };
182
+ log.info("Remote setup — used later for pull requests / merge requests.");
183
+ const hostPick = await askSelect({
184
+ message: "Where will this project's remote live?",
185
+ nonInteractive: false,
186
+ choices: ["GitHub", "GitLab", "Skip for now (local only)"],
187
+ default: opts.preferredHost === "gitlab"
188
+ ? "GitLab"
189
+ : opts.preferredHost === "github"
190
+ ? "GitHub"
191
+ : undefined,
192
+ });
193
+ if (hostPick.startsWith("Skip")) {
194
+ log.warn("No git remote configured. task ship push/MR will fail until origin exists.");
195
+ log.info(dim(` Later: git remote add ${remote} <url>`));
196
+ return null;
97
197
  }
98
- const url = await askInput({
99
- message: `Remote URL for ${remote} (https://github.com/… or https://gitlab.com/…)`,
198
+ const host = hostPick === "GitLab" ? "gitlab" : "github";
199
+ const mode = await askSelect({
200
+ message: "Set up the remote",
100
201
  nonInteractive: false,
101
- required: true,
202
+ choices: ["Create a new repository", "Connect an existing repository"],
102
203
  });
103
- await runGit(opts.cwd, ["remote", "add", remote, url.trim()]);
104
- const host = detectGitHost(url);
105
- if (!host)
106
- throw new Error("Only GitHub and GitLab HTTPS/SSH URLs are supported.");
107
- return { remote, url: url.trim(), host };
204
+ if (mode.startsWith("Connect")) {
205
+ return connectExisting(opts.cwd, remote, host);
206
+ }
207
+ // Create path — requires authenticated CLI (soft gate).
208
+ for (;;) {
209
+ const auth = await probeHostCliAuth(host);
210
+ if (auth === "ok") {
211
+ const defaultBranch = typeof opts.defaultBranch === "string" && opts.defaultBranch.length > 0
212
+ ? opts.defaultBranch
213
+ : "main";
214
+ return createOnHost(opts.cwd, remote, host, defaultBranch);
215
+ }
216
+ const action = await repairMissingCli(host);
217
+ if (action === "retry")
218
+ continue;
219
+ if (action === "manual") {
220
+ return connectExisting(opts.cwd, remote, host);
221
+ }
222
+ log.warn("No git remote configured. task ship push/MR will fail until origin exists.");
223
+ log.info(dim(` Later: git remote add ${remote} <url>`));
224
+ return null;
225
+ }
108
226
  }
package/dist/lib/git.js CHANGED
@@ -519,6 +519,31 @@ export async function gitDeleteRemoteBranch(cwd, remote, branch) {
519
519
  export async function gitPullFastForwardOnly(cwd, remote, branch) {
520
520
  await runGit(cwd, ["pull", "--ff-only", remote, branch]);
521
521
  }
522
+ /**
523
+ * Commits reachable only from `left` vs only from `right` (git left-right).
524
+ * Example: `develop...origin/develop` → { left: local-only, right: remote-only }.
525
+ */
526
+ export async function gitLeftRightCount(cwd, leftRef, rightRef) {
527
+ try {
528
+ const { stdout } = await runGit(cwd, [
529
+ "rev-list",
530
+ "--left-right",
531
+ "--count",
532
+ `${leftRef}...${rightRef}`,
533
+ ]);
534
+ const parts = stdout.trim().split(/\s+/);
535
+ if (parts.length < 2)
536
+ return null;
537
+ const left = Number.parseInt(parts[0], 10);
538
+ const right = Number.parseInt(parts[1], 10);
539
+ if (!Number.isFinite(left) || !Number.isFinite(right))
540
+ return null;
541
+ return { left, right };
542
+ }
543
+ catch {
544
+ return null;
545
+ }
546
+ }
522
547
  /** Merge `remote/branch` into the current HEAD (follow-up integration). */
523
548
  export async function gitMergeRemoteBranch(cwd, remote, branch) {
524
549
  await runGit(cwd, ["merge", `${remote}/${branch}`, "--no-edit"]);
@@ -49,7 +49,7 @@ export function computeNextHint(input) {
49
49
  if (input.hasMergeRequest && !input.mergeRequestMerged)
50
50
  return "task-merge";
51
51
  if (input.hasLocalChanges)
52
- return "task-reship";
52
+ return "task-ship-followup";
53
53
  return "task-add";
54
54
  }
55
55
  if (!input.resultFilled || !input.testFilled) {
@@ -57,36 +57,48 @@ export function computeNextHint(input) {
57
57
  }
58
58
  return input.hasMergeRequest ? "task-merge" : "task-ship";
59
59
  }
60
- export function hintToText(hint, cwd, task) {
60
+ /** Multi-line NEXT block lines (without the leading arrow prefix). */
61
+ export function hintToLines(hint, cwd, task) {
61
62
  switch (hint) {
62
63
  case "init":
63
- return "Run `vibeops init` in this directory.";
64
+ return ["vibeops init"];
64
65
  case "task-add":
65
- return "Run `vibeops task add` to start the next slice.";
66
+ return ["vibeops task add"];
66
67
  case "task-ship":
67
- return task ? `Run \`vibeops task ship ${task.id}\`.` : "Run `vibeops task ship`.";
68
- case "task-reship":
68
+ return [task ? `vibeops task ship ${task.id}` : "vibeops task ship"];
69
+ case "task-ship-followup":
69
70
  return task
70
- ? `Follow-up on ${task.id}? Edit files (develop is fine), then \`vibeops task reship ${task.id}\`.`
71
- : "Run `vibeops task reship TASK-NNN` for a Shipped follow-up.";
71
+ ? [
72
+ `Follow up on ${task.id} (after merge: confirm or --new-cycle)`,
73
+ `vibeops task ship ${task.id}`,
74
+ ]
75
+ : ["vibeops task ship --new-cycle"];
72
76
  case "task-merge":
73
77
  return task
74
- ? `Merge the MR (UI or \`vibeops task merge ${task.id}\`), then optional task sync.`
75
- : "Merge the MR on the host or with `vibeops task merge`.";
78
+ ? [`vibeops task merge ${task.id}`, "(or merge in the host UI)"]
79
+ : ["vibeops task merge"];
76
80
  case "task-sync":
77
81
  return task
78
- ? `Run \`vibeops task sync ${task.id}\` to delete task branches, then task add.`
79
- : "Run `vibeops task sync` to delete task branches after merge.";
82
+ ? [`vibeops task sync ${task.id}`, "then: vibeops task add"]
83
+ : ["vibeops task sync"];
80
84
  case "cursor-plan":
81
85
  return task
82
- ? `In Cursor Ask: @${relPath(cwd, task.filePath)} — refine Scope / AC.`
83
- : "Open the TASK file in Cursor Ask.";
86
+ ? [
87
+ `Cursor Ask: @${relPath(cwd, task.filePath)} — refine Scope / AC`,
88
+ ]
89
+ : ["Open the TASK file in Cursor Ask"];
84
90
  case "cursor-implement":
85
91
  return task
86
- ? `In Cursor Agent: @${relPath(cwd, task.filePath)} — implement, then task ship.`
87
- : "Implement per the TASK file in Cursor.";
92
+ ? [
93
+ `Cursor Agent: @${relPath(cwd, task.filePath)} — implement`,
94
+ `then: vibeops task ship ${task.id}`,
95
+ ]
96
+ : ["Implement per the TASK file, then vibeops task ship"];
88
97
  }
89
98
  }
99
+ export function hintToText(hint, cwd, task) {
100
+ return hintToLines(hint, cwd, task).join(" · ");
101
+ }
90
102
  export async function listInProgressTasks(paths) {
91
103
  return (await loadActionableTasks(paths.docsTasks)).filter((t) => t.status === "in_progress");
92
104
  }
@@ -1,3 +1,4 @@
1
+ import { formatHostCliHint, formatHostCliMissingMessage } from "./git-host-cli.js";
1
2
  import { readText } from "./filesystem.js";
2
3
  import { readConfig } from "./config.js";
3
4
  import { detectGitHost, mergeRequestLabel } from "./git-host.js";
@@ -94,8 +95,11 @@ export async function finishTaskWithPullRequest(opts) {
94
95
  }
95
96
  const cliOk = await probeMergeRequestCli(gitCfg.host);
96
97
  if (!cliOk) {
97
- const tool = gitCfg.host === "gitlab" ? "glab" : "gh";
98
- log.warn(`${tool} CLI not found — create the ${label} manually: ${gitCtx.taskBranch} → ${gitCtx.baseBranch}`);
98
+ log.warn(formatHostCliMissingMessage(gitCfg.host, `create the ${label}`));
99
+ for (const line of formatHostCliHint(gitCfg.host)) {
100
+ log.info(dim(` ${line}`));
101
+ }
102
+ log.info(dim(`Create the ${label} manually: ${gitCtx.taskBranch} → ${gitCtx.baseBranch}`));
99
103
  return { ok: true, pushed: true };
100
104
  }
101
105
  const config = await readConfig(opts.cwd);
@@ -43,7 +43,7 @@ export function reshipNotFoundMessage(taskRef) {
43
43
  if (taskRef?.trim()) {
44
44
  return `Shipped TASK not found: ${normalizeTaskRef(taskRef)}. Check docs/tasks/.`;
45
45
  }
46
- return "No Shipped TASK found. Pass `vibeops task reship TASK-NNN`.";
46
+ return "No Shipped TASK found. Pass `vibeops task ship TASK-NNN` (use --new-cycle after merge).";
47
47
  }
48
48
  async function switchToTaskBranch(cwd, taskBranch) {
49
49
  try {
@@ -1,11 +1,19 @@
1
1
  import { relative, resolve } from "node:path";
2
2
  import { branchNameForTaskFile, readGitContext, readTaskFile, updateInlineStatus, upsertGitContext, } from "./task.js";
3
- import { gitBranchExists, gitCheckout, gitCheckoutNewBranch, gitGovernanceOnlyDirty, gitHeadCommit, gitPullFastForwardOnly, gitRemoteBranchExists, gitSwitchToBranch, gitRemoteUrl, readGitInfo, } from "./git.js";
3
+ import { gitBranchExists, gitCheckout, gitCheckoutNewBranch, gitGovernanceOnlyDirty, gitHeadCommit, gitSwitchToBranch, readGitInfo, } from "./git.js";
4
+ import { ensureIntegrationSynced, printIntegrationSyncDiagnosis, } from "./git-integration-sync.js";
4
5
  import { cyan, dim, log } from "./logger.js";
5
6
  function relOrAbs(root, p) {
6
7
  const r = relative(root, p);
7
8
  return r === "" ? "." : r.startsWith("..") ? p : r;
8
9
  }
10
+ /** True when In Progress TASK has no task branch yet (failed mid-add). */
11
+ export async function isIncompleteTaskStart(cwd, taskFile) {
12
+ const ctx = await readGitContext(taskFile);
13
+ if (ctx === null)
14
+ return true;
15
+ return !(await gitBranchExists(cwd, ctx.taskBranch));
16
+ }
9
17
  /** Create or resume task branch from integration branch and mark TASK In Progress. */
10
18
  export async function startTaskBranch(opts) {
11
19
  const cwd = resolve(opts.cwd);
@@ -49,20 +57,21 @@ export async function startTaskBranch(opts) {
49
57
  }
50
58
  }
51
59
  // Ensure integration branch is up-to-date before creating a new task branch.
52
- if (!branchExists) {
53
- const remoteUrl = await gitRemoteUrl(cwd, remote);
54
- if (remoteUrl) {
55
- const hasRemoteBranch = await gitRemoteBranchExists(cwd, remote, integrationBranch);
56
- if (hasRemoteBranch) {
57
- try {
58
- await gitPullFastForwardOnly(cwd, remote, integrationBranch);
59
- log.info(dim(`Pulled latest ${remote}/${integrationBranch} (--ff-only).`));
60
- }
61
- catch {
62
- log.warn(`Could not fast-forward pull ${remote}/${integrationBranch}. Resolve manually, then rerun task add.`);
63
- return false;
64
- }
65
- }
60
+ if (!branchExists && opts.skipIntegrationPull !== true) {
61
+ const synced = await ensureIntegrationSynced({
62
+ cwd,
63
+ remote,
64
+ integrationBranch,
65
+ fetch: true,
66
+ });
67
+ if (!synced.ok) {
68
+ printIntegrationSyncDiagnosis(synced.diagnosis);
69
+ log.blank();
70
+ log.info(dim(`TASK file is already created — after fixing sync, rerun ${cyan("vibeops task add")} to resume the branch (does not create a second TASK).`));
71
+ return false;
72
+ }
73
+ if (synced.pulled) {
74
+ log.info(dim(synced.diagnosis.summary));
66
75
  }
67
76
  }
68
77
  const baseBranch = integrationBranch;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@goodtek/vibeops",
3
- "version": "2.4.4",
3
+ "version": "2.5.1",
4
4
  "description": "Minimal CLI rails for Cursor-based TASK-driven vibe coding.",
5
5
  "type": "module",
6
+ "packageManager": "pnpm@9.15.9",
6
7
  "author": "VibeOps contributors",
7
8
  "license": "MIT",
8
9
  "repository": {
@@ -29,6 +30,19 @@
29
30
  "engines": {
30
31
  "node": ">=20"
31
32
  },
33
+ "scripts": {
34
+ "dev": "tsx src/cli.ts",
35
+ "build": "tsc -p tsconfig.json",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "start": "node dist/cli.js",
38
+ "test": "pnpm build && node --test \"tests/*.test.mjs\"",
39
+ "smoke": "pnpm typecheck && pnpm build && node --test \"tests/*.test.mjs\" && node scripts/smoke.mjs",
40
+ "clean": "rm -rf dist",
41
+ "prepack": "pnpm clean && pnpm build",
42
+ "publish:dry": "pnpm publish --dry-run --no-git-checks",
43
+ "publish:npm": "scripts/npm-publish.sh",
44
+ "publish:npm:dry": "scripts/npm-publish.sh --dry-run"
45
+ },
32
46
  "keywords": [
33
47
  "cursor",
34
48
  "ai",
@@ -47,15 +61,5 @@
47
61
  "@types/node": "^20.14.0",
48
62
  "tsx": "^4.19.0",
49
63
  "typescript": "^5.5.4"
50
- },
51
- "scripts": {
52
- "dev": "tsx src/cli.ts",
53
- "build": "tsc -p tsconfig.json",
54
- "typecheck": "tsc -p tsconfig.json --noEmit",
55
- "start": "node dist/cli.js",
56
- "test": "pnpm build && node --test \"tests/*.test.mjs\"",
57
- "smoke": "pnpm typecheck && pnpm build && node --test \"tests/*.test.mjs\" && node scripts/smoke.mjs",
58
- "clean": "rm -rf dist",
59
- "publish:dry": "pnpm publish --dry-run --no-git-checks"
60
64
  }
61
- }
65
+ }
@@ -40,5 +40,5 @@ vibeops llm connect # optional — task add / ship
40
40
 
41
41
  - One **In Progress** TASK at a time; **Shipped** does not block `task add`.
42
42
  - Fill **Result** and **Test Result** before `vibeops task ship` (warned, not blocked, when updating an open PR).
43
- - `ship` is state-aware: re-run it to update an open PR; after merge it needs `--new-cycle` (or a confirm) to start a new PR cycle. `task reship` still works as a deprecated alias for `task ship --new-cycle`.
43
+ - `ship` is state-aware: re-run it to update an open PR; after merge it needs `--new-cycle` (or a confirm) to start a new PR cycle.
44
44
  - Agents do not run `task merge` or `task sync` unless the human asks.
@@ -5,5 +5,5 @@
5
5
  ## D-001 · VibeOps workflow
6
6
 
7
7
  - **Date:** {{CREATED_AT}}
8
- - **Decision:** TASK-driven development with `vibeops task add` / `task ship` / `task reship` / `merge` / `sync` and Cursor for plan/build. TASK status: **In Progress** → **Shipped** only.
8
+ - **Decision:** TASK-driven development with `vibeops task add` / `task ship` / `merge` / `sync` and Cursor for plan/build. TASK status: **In Progress** → **Shipped** only.
9
9
  - **Why:** Git `docs/tasks` beats chat as source of truth.
@@ -1,21 +0,0 @@
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
- */
7
- export async function taskReshipCommand(taskRef, options = {}) {
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."));
10
- log.blank();
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,
20
- });
21
- }