@goodtek/vibeops 2.1.1 → 2.1.2

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 CHANGED
@@ -4,14 +4,13 @@ All notable changes to VibeOps are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
- ## 2.1.1 - 2026-06-08
7
+ ## 2.1.2 - 2026-06-08
8
8
 
9
9
  ### Changed
10
10
 
11
- - **Docs:** living markdown, templates, and Cursor rules aligned with 2.1 lifecycle (`task reship`, two-status model, sync does not edit TASK md).
12
- - **`task sync` CLI help:** description no longer says "mark Done".
11
+ - **`task ship`** / **`task reship`**: commit ship/reship metadata **before** push and MR/PR creation so the branch is pushed once (avoids duplicate `pull_request` CI runs on `opened` + `synchronize`).
13
12
 
14
- ## 2.1.0 - 2026-06-03
13
+ ## 2.1.1 - 2026-06-08
15
14
 
16
15
  ### Added
17
16
 
@@ -19,6 +18,13 @@ All notable changes to VibeOps are documented here.
19
18
 
20
19
  ### Changed
21
20
 
21
+ - **Docs:** living markdown, templates, and Cursor rules aligned with 2.1 lifecycle (`task reship`, two-status model, sync does not edit TASK md).
22
+ - **`task sync` CLI help:** description no longer says "mark Done".
23
+
24
+ ## 2.1.0 - 2026-06-03
25
+
26
+ ### Changed
27
+
22
28
  - **TASK md status:** only **In Progress** → **Shipped** (`task ship`). Merge (CLI/UI) and **`task sync`** do not edit the TASK file.
23
29
  - **`task sync`**: Git cleanup only (integration pull, delete `task/*` branches).
24
30
  - **`task add`**: blocks only another **In Progress** TASK (not **Shipped**).
package/README.md CHANGED
@@ -106,7 +106,7 @@ pnpm install && pnpm build && pnpm smoke
106
106
 
107
107
  - **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`).
108
108
  - **`task add`**: `task/<slug>` from **integration** (e.g. `develop`).
109
- - **`task ship`**: commit → push → MR/PR → Status **Shipped** on task branch.
109
+ - **`task ship`**: commit implementation + ship metadata (Status **Shipped**) → push once → MR/PR.
110
110
  - **`task reship`**: Shipped TASK only; integrates `develop`; opens **new** PR (archives previous MR in Git Context).
111
111
  - **`task merge`**: merge MR/PR into integration (CLI or host UI; TASK md unchanged).
112
112
  - **`task sync`**: integration ff-only pull → delete `task/*` branches (TASK md unchanged).
package/dist/cli.js CHANGED
File without changes
@@ -3,36 +3,21 @@ import { readText } from "../lib/filesystem.js";
3
3
  import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
4
4
  import { projectPaths } from "../lib/paths.js";
5
5
  import { relPath } from "../lib/task-context.js";
6
- import { commitDirtyWorkingTree, docsCommitMessageFor, featCommitMessageFor, pushBranch, } from "../lib/task-git-commit.js";
6
+ import { commitDirtyWorkingTree, docsCommitMessageFor, featCommitMessageFor, } from "../lib/task-git-commit.js";
7
7
  import { finishTaskWithPullRequest } from "../lib/task-push-pr.js";
8
8
  import { applyTaskShipMemory, llmCompleteTaskReship, writeTaskResultSections, } from "../lib/task-ship-llm.js";
9
9
  import { assertMergeRequestNotOpen, ensureTaskBranchForReship, integrateIntegrationIntoTaskBranch, loadGitConfigForReship, prepareGitContextForReship, refreshGitContextBaseCommit, resolveReshipTarget, } from "../lib/task-reship.js";
10
10
  import { hasNonEmptySection, parseTaskFilename, readGitContext, upsertGitContext, } from "../lib/task.js";
11
11
  import { listWorkingTreeRelPaths } from "../lib/git.js";
12
- async function commitReshipMetadataAndPush(opts) {
12
+ async function commitReshipMetadata(opts) {
13
13
  if (opts.dryRun) {
14
14
  log.info(dim(" would commit reship metadata (Status stays Shipped)"));
15
- if (opts.push !== null) {
16
- log.info(dim(` would git push ${opts.push.remote} ${opts.push.branch} (reship metadata)`));
17
- }
18
- return true;
15
+ return;
19
16
  }
20
17
  const committed = await commitDirtyWorkingTree(opts.cwd, docsCommitMessageFor(opts.taskId, "reship metadata"), false);
21
18
  if (!committed) {
22
19
  log.info(dim("No reship metadata commit (Git Context already on disk)."));
23
20
  }
24
- if (opts.push === null)
25
- return true;
26
- try {
27
- await pushBranch(opts.cwd, opts.push.remote, opts.push.branch, false);
28
- log.ok(`Pushed reship metadata → ${opts.push.remote}/${opts.push.branch}`);
29
- return true;
30
- }
31
- catch (e) {
32
- const msg = e instanceof Error ? e.message : String(e);
33
- log.error(`Reship metadata push failed: ${msg}`);
34
- return false;
35
- }
36
21
  }
37
22
  export async function taskReshipCommand(taskRef, options = {}) {
38
23
  const cwd = resolve(options.cwd ?? process.cwd());
@@ -129,8 +114,7 @@ export async function taskReshipCommand(taskRef, options = {}) {
129
114
  log.blank();
130
115
  if (dryRun) {
131
116
  log.info(bold("dry-run — would also:"));
132
- log.info(" · archive prior MR URL; open new MR/PR (unless --no-pr)");
133
- log.info(" · feat commit + reship metadata push");
117
+ log.info(" · feat commit + reship metadata, then push once and open MR/PR (unless --no-pr)");
134
118
  log.blank();
135
119
  log.info(`Next: ${cyan("vibeops task merge")}, then optional ${cyan("task sync")}`);
136
120
  return;
@@ -157,6 +141,12 @@ export async function taskReshipCommand(taskRef, options = {}) {
157
141
  gitCtx = await refreshGitContextBaseCommit(taskFile, gitCtx, cwd);
158
142
  gitCtx = await prepareGitContextForReship(taskFile, gitCtx);
159
143
  await upsertGitContext(taskFile, gitCtx);
144
+ await commitReshipMetadata({
145
+ cwd,
146
+ taskFile,
147
+ taskId: parts.id,
148
+ dryRun: false,
149
+ });
160
150
  const prResult = await finishTaskWithPullRequest({
161
151
  cwd,
162
152
  taskFile,
@@ -171,22 +161,6 @@ export async function taskReshipCommand(taskRef, options = {}) {
171
161
  if (prResult.mergeRequestUrl) {
172
162
  log.info(` ${dim("MR/PR")} ${prResult.mergeRequestUrl}`);
173
163
  }
174
- const gitCtxAfter = await readGitContext(taskFile);
175
- let pushTarget = null;
176
- if (gitCtxAfter !== null && gitCfg !== null) {
177
- pushTarget = { remote: gitCfg.remote, branch: gitCtxAfter.taskBranch };
178
- }
179
- const pushed = await commitReshipMetadataAndPush({
180
- cwd,
181
- taskFile,
182
- taskId: parts.id,
183
- dryRun: false,
184
- push: pushTarget,
185
- });
186
- if (!pushed) {
187
- process.exitCode = 1;
188
- return;
189
- }
190
164
  log.blank();
191
165
  log.info(`Next: ${cyan("vibeops task merge")} (or merge in the host UI), then ${cyan("vibeops task sync")}`);
192
166
  }
@@ -1,42 +1,24 @@
1
1
  import { resolve } from "node:path";
2
2
  import { readText } from "../lib/filesystem.js";
3
- import { requireGitConfig } from "../lib/git-config.js";
4
3
  import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
5
4
  import { projectPaths } from "../lib/paths.js";
6
5
  import { resolveTask, taskNotFoundMessage } from "../lib/resolve-task.js";
7
6
  import { relPath } from "../lib/task-context.js";
8
- import { commitDirtyWorkingTree, docsCommitMessageFor, featCommitMessageFor, pushBranch, } from "../lib/task-git-commit.js";
7
+ import { commitDirtyWorkingTree, docsCommitMessageFor, featCommitMessageFor, } from "../lib/task-git-commit.js";
9
8
  import { applyTaskShipMemory, fallbackResultSections, llmCompleteTaskShip, writeTaskResultSections, } from "../lib/task-ship-llm.js";
10
9
  import { finishTaskWithPullRequest } from "../lib/task-push-pr.js";
11
10
  import { hasNonEmptySection, parseTaskFilename, readGitContext, readTaskFile, updateInlineStatus, } from "../lib/task.js";
12
- async function commitShipMetadataAndPush(opts) {
11
+ async function commitShipMetadata(opts) {
13
12
  if (opts.dryRun) {
14
13
  log.info(dim(" would set Status → Shipped"));
15
14
  log.info(dim(" would commit ship metadata"));
16
- if (opts.push !== null) {
17
- log.info(dim(` would git push ${opts.push.remote} ${opts.push.branch} (ship metadata)`));
18
- }
19
- return true;
15
+ return;
20
16
  }
21
17
  await updateInlineStatus(opts.taskFile, "shipped");
22
18
  log.ok("Status → Shipped");
23
19
  const committed = await commitDirtyWorkingTree(opts.cwd, docsCommitMessageFor(opts.taskId, "mark shipped"), false);
24
20
  if (!committed) {
25
21
  log.warn("No file changes for ship metadata commit — check TASK Status on the remote branch.");
26
- return true;
27
- }
28
- if (opts.push === null)
29
- return true;
30
- try {
31
- await pushBranch(opts.cwd, opts.push.remote, opts.push.branch, false);
32
- log.ok(`Pushed ship metadata → ${opts.push.remote}/${opts.push.branch}`);
33
- return true;
34
- }
35
- catch (e) {
36
- const msg = e instanceof Error ? e.message : String(e);
37
- log.error(`Ship metadata push failed: ${msg}`);
38
- log.info(dim(`Fix auth/remote, then: git push ${opts.push.remote} ${opts.push.branch}`));
39
- return false;
40
22
  }
41
23
  }
42
24
  export async function taskShipCommand(taskRef, options = {}) {
@@ -104,24 +86,20 @@ export async function taskShipCommand(taskRef, options = {}) {
104
86
  if (!resultOk || !testOk) {
105
87
  log.info(" · LLM fill Result / Test Result + patch docs/project/*");
106
88
  }
107
- log.info(" · commit implementation, push task branch, open MR/PR (unless --no-pr)");
108
- log.info(" · Status → Shipped; commit metadata; push again");
109
- await finishTaskWithPullRequest({
89
+ log.info(" · commit implementation + ship metadata (Status → Shipped)");
90
+ log.info(" · push task branch once, open MR/PR (unless --no-pr)");
91
+ const partsDry = parseTaskFilename(taskFile);
92
+ await commitShipMetadata({
110
93
  cwd,
111
94
  taskFile,
95
+ taskId: partsDry.id,
112
96
  dryRun: true,
113
- skipPr: options.noPr === true,
114
97
  });
115
- const gitCtxDry = await readGitContext(taskFile);
116
- const gitCfgDry = gitCtxDry !== null ? await requireGitConfig(cwd).catch(() => null) : null;
117
- await commitShipMetadataAndPush({
98
+ await finishTaskWithPullRequest({
118
99
  cwd,
119
100
  taskFile,
120
- taskId,
121
101
  dryRun: true,
122
- push: gitCtxDry !== null && gitCfgDry !== null
123
- ? { remote: gitCfgDry.remote, branch: gitCtxDry.taskBranch }
124
- : null,
102
+ skipPr: options.noPr === true,
125
103
  });
126
104
  return;
127
105
  }
@@ -129,6 +107,12 @@ export async function taskShipCommand(taskRef, options = {}) {
129
107
  await commitDirtyWorkingTree(cwd, featCommitMessageFor(parts.id, meta.title), false);
130
108
  const gitCtx = await readGitContext(taskFile);
131
109
  if (gitCtx !== null) {
110
+ await commitShipMetadata({
111
+ cwd,
112
+ taskFile,
113
+ taskId: parts.id,
114
+ dryRun: false,
115
+ });
132
116
  const prResult = await finishTaskWithPullRequest({
133
117
  cwd,
134
118
  taskFile,
@@ -146,30 +130,6 @@ export async function taskShipCommand(taskRef, options = {}) {
146
130
  else {
147
131
  log.info(dim("No Git Context — push/MR skipped."));
148
132
  }
149
- const gitCtxAfter = await readGitContext(taskFile);
150
- let pushTarget = null;
151
- if (gitCtxAfter !== null) {
152
- try {
153
- const gitCfg = await requireGitConfig(cwd);
154
- pushTarget = { remote: gitCfg.remote, branch: gitCtxAfter.taskBranch };
155
- }
156
- catch (e) {
157
- if (e instanceof Error)
158
- log.warn(e.message);
159
- }
160
- }
161
- const shipped = await commitShipMetadataAndPush({
162
- cwd,
163
- taskFile,
164
- taskId: parts.id,
165
- dryRun: false,
166
- push: pushTarget,
167
- });
168
- if (!shipped) {
169
- log.error("Ship metadata push failed — fix manually, then merge the MR.");
170
- process.exitCode = 1;
171
- return;
172
- }
173
133
  log.blank();
174
134
  log.info(`Next: ${cyan("vibeops task merge")} (or merge in the host UI), then ${cyan("vibeops task sync")}`);
175
135
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@goodtek/vibeops",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
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,16 @@
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
+ "smoke": "pnpm typecheck && pnpm build && node scripts/smoke.mjs",
39
+ "clean": "rm -rf dist",
40
+ "prepack": "pnpm clean && pnpm build",
41
+ "publish:dry": "pnpm publish --dry-run --no-git-checks"
42
+ },
32
43
  "keywords": [
33
44
  "cursor",
34
45
  "ai",
@@ -47,14 +58,5 @@
47
58
  "@types/node": "^20.14.0",
48
59
  "tsx": "^4.19.0",
49
60
  "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
- "smoke": "pnpm typecheck && pnpm build && node scripts/smoke.mjs",
57
- "clean": "rm -rf dist",
58
- "publish:dry": "pnpm publish --dry-run --no-git-checks"
59
61
  }
60
- }
62
+ }