@goodtek/vibeops 1.1.1 → 1.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,6 +4,14 @@ All notable changes to VibeOps are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.1.2 - 2026-06-03
8
+
9
+ ### Fixed
10
+
11
+ - **`task done` two-phase close:** after push/MR, writes Status Done and Git Context (`doneAt`, MR URL from the prior step), commits closure metadata (`docs(task-nnn): close task metadata`), and pushes again so the remote task branch matches the TASK file instead of leaving governance changes uncommitted locally.
12
+
13
+ ## 1.1.1 - 2026-05-27
14
+
7
15
  ### Fixed
8
16
 
9
17
  - Removed dead `task-merge.ts` (local merge no longer used).
package/README.md CHANGED
@@ -87,7 +87,7 @@ pnpm install && pnpm build && pnpm smoke
87
87
 
88
88
  - **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`).
89
89
  - **`task add`**: branch `task/<slug>` from the **integration** branch (e.g. `develop`).
90
- - **`task done`**: commit if needed → `git push` → `gh pr create` / `glab mr create` (LLM title/body). **You merge on GitHub/GitLab**; CI deploys.
90
+ - **`task done`**: commit implementation → `git push` → `gh pr create` / `glab mr create` → commit TASK closure (Status Done, Git Context) → push again. **You merge on GitHub/GitLab**; CI deploys.
91
91
  - Commit messages: `feat(task-nnn): …`
92
92
  - No force-push to shared branches
93
93
 
@@ -23,13 +23,3 @@ export async function loadInstallManifest(clients) {
23
23
  export function resolveDestination(entry, projectRoot) {
24
24
  return join(projectRoot, entry.relativePath);
25
25
  }
26
- /** @deprecated Use loadInstallManifest — walks entire templates/ (legacy). */
27
- export async function loadManifest() {
28
- const files = await walk(TEMPLATES_ROOT);
29
- return files
30
- .map((abs) => ({
31
- relativePath: relative(TEMPLATES_ROOT, abs),
32
- sourceAbs: abs,
33
- }))
34
- .sort((a, b) => a.relativePath.localeCompare(b.relativePath));
35
- }
package/dist/cli.js CHANGED
File without changes
@@ -5,13 +5,17 @@ import { projectPaths } from "../lib/paths.js";
5
5
  import { resolveTask, taskNotFoundMessage } from "../lib/resolve-task.js";
6
6
  import { applyTaskDoneMemory, fallbackResultSections, llmCompleteTaskDone, writeTaskResultSections, } from "../lib/task-done-llm.js";
7
7
  import { relPath } from "../lib/task-context.js";
8
- import { gitAddPaths, listWorkingTreeRelPaths, partitionPathsForAutoCommit, readGitInfo, runGit, } from "../lib/git.js";
8
+ import { requireGitConfig } from "../lib/git-config.js";
9
+ import { gitAddPaths, gitPush, listWorkingTreeRelPaths, partitionPathsForAutoCommit, readGitInfo, runGit, } from "../lib/git.js";
9
10
  import { hasNonEmptySection, markGitContextDone, parseTaskFilename, readGitContext, readTaskFile, updateInlineStatus, } from "../lib/task.js";
10
11
  import { finishTaskWithPullRequest } from "../lib/task-push-pr.js";
11
12
  function commitMessageFor(taskId, title) {
12
13
  const slug = title.replace(/^TASK-\d+\s*[:\-]\s*/i, "").trim() || taskId;
13
14
  return `feat(${taskId.toLowerCase()}): ${slug}`;
14
15
  }
16
+ function closureCommitMessageFor(taskId) {
17
+ return `docs(${taskId.toLowerCase()}): close task metadata`;
18
+ }
15
19
  async function tryCommitDirty(cwd, message, dryRun) {
16
20
  const git = await readGitInfo(cwd);
17
21
  if (!git.isRepo || git.dirty !== true)
@@ -31,6 +35,41 @@ async function tryCommitDirty(cwd, message, dryRun) {
31
35
  log.ok(`Committed: ${message}`);
32
36
  return true;
33
37
  }
38
+ /**
39
+ * After push/MR: write Status Done + Git Context closure, commit, push so the remote
40
+ * branch matches local TASK metadata (canonical two-phase close).
41
+ */
42
+ async function commitClosureMetadataAndPush(opts) {
43
+ if (opts.dryRun) {
44
+ log.info(dim(" would set Status → Done and update Git Context (doneAt)"));
45
+ log.info(dim(" would commit closure metadata"));
46
+ if (opts.push !== null) {
47
+ log.info(dim(` would git push ${opts.push.remote} ${opts.push.branch} (closure commit)`));
48
+ }
49
+ return true;
50
+ }
51
+ await updateInlineStatus(opts.taskFile, "done");
52
+ await markGitContextDone(opts.taskFile);
53
+ log.ok("Status → Done");
54
+ const committed = await tryCommitDirty(opts.cwd, closureCommitMessageFor(opts.taskId), false);
55
+ if (!committed) {
56
+ log.warn("No file changes for closure commit — verify TASK Status and Git Context on the remote branch.");
57
+ return true;
58
+ }
59
+ if (opts.push === null)
60
+ return true;
61
+ try {
62
+ await gitPush(opts.cwd, opts.push.remote, opts.push.branch, false);
63
+ log.ok(`Pushed closure commit → ${opts.push.remote}/${opts.push.branch}`);
64
+ return true;
65
+ }
66
+ catch (e) {
67
+ const msg = e instanceof Error ? e.message : String(e);
68
+ log.error(`Closure commit push failed: ${msg}`);
69
+ log.info(dim(`Fix auth/remote, then: git push ${opts.push.remote} ${opts.push.branch}`));
70
+ return false;
71
+ }
72
+ }
34
73
  export async function taskDoneCommand(taskRef, options = {}) {
35
74
  const cwd = resolve(options.cwd ?? process.cwd());
36
75
  const paths = projectPaths(cwd);
@@ -96,14 +135,27 @@ export async function taskDoneCommand(taskRef, options = {}) {
96
135
  if (!resultOk || !testOk) {
97
136
  log.info(" · LLM fill Result / Test Result + patch docs/project/*");
98
137
  }
99
- log.info(" · commit if dirty, push task branch, open MR/PR (unless --no-pr)");
100
- log.info(" · Status → Done after push/MR succeed");
138
+ log.info(" · commit implementation, push task branch, open MR/PR (unless --no-pr)");
139
+ log.info(" · Status → Done; commit closure metadata; push again");
101
140
  await finishTaskWithPullRequest({
102
141
  cwd,
103
142
  taskFile,
104
143
  dryRun: true,
105
144
  skipPr: options.noPr === true,
106
145
  });
146
+ const gitCtxDry = await readGitContext(taskFile);
147
+ const gitCfgDry = gitCtxDry !== null
148
+ ? await requireGitConfig(cwd).catch(() => null)
149
+ : null;
150
+ await commitClosureMetadataAndPush({
151
+ cwd,
152
+ taskFile,
153
+ taskId,
154
+ dryRun: true,
155
+ push: gitCtxDry !== null && gitCfgDry !== null
156
+ ? { remote: gitCfgDry.remote, branch: gitCtxDry.taskBranch }
157
+ : null,
158
+ });
107
159
  return;
108
160
  }
109
161
  const parts = parseTaskFilename(taskFile);
@@ -129,9 +181,30 @@ export async function taskDoneCommand(taskRef, options = {}) {
129
181
  else {
130
182
  log.info(dim("No Git Context — push/MR skipped."));
131
183
  }
132
- await updateInlineStatus(taskFile, "done");
133
- await markGitContextDone(taskFile);
134
- log.ok("Status → Done");
184
+ const gitCtxAfter = await readGitContext(taskFile);
185
+ let pushTarget = null;
186
+ if (gitCtxAfter !== null) {
187
+ try {
188
+ const gitCfg = await requireGitConfig(cwd);
189
+ pushTarget = { remote: gitCfg.remote, branch: gitCtxAfter.taskBranch };
190
+ }
191
+ catch (e) {
192
+ if (e instanceof Error)
193
+ log.warn(e.message);
194
+ }
195
+ }
196
+ const closed = await commitClosureMetadataAndPush({
197
+ cwd,
198
+ taskFile,
199
+ taskId: parts.id,
200
+ dryRun: false,
201
+ push: pushTarget,
202
+ });
203
+ if (!closed) {
204
+ log.error("TASK metadata is Done locally but closure push failed — push manually, then continue.");
205
+ process.exitCode = 1;
206
+ return;
207
+ }
135
208
  log.blank();
136
209
  log.info(`Next: ${cyan("vibeops task add")} or ${cyan("vibeops status")}`);
137
210
  }
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@goodtek/vibeops",
3
- "version": "1.1.1",
3
+ "version": "1.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",
7
6
  "author": "VibeOps contributors",
8
7
  "license": "MIT",
9
8
  "repository": {
@@ -30,16 +29,6 @@
30
29
  "engines": {
31
30
  "node": ">=20"
32
31
  },
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
- },
43
32
  "keywords": [
44
33
  "cursor",
45
34
  "ai",
@@ -58,5 +47,14 @@
58
47
  "@types/node": "^20.14.0",
59
48
  "tsx": "^4.19.0",
60
49
  "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"
61
59
  }
62
- }
60
+ }
@@ -1,12 +0,0 @@
1
- ---
2
- description: Git safety for task branches and merge requests
3
- alwaysApply: true
4
- ---
5
-
6
- # Git safety
7
-
8
- - Branch policy lives in `.vibeops.json` (`integrationBranch`, `productionBranch`).
9
- - `vibeops task add` creates `task/*` from the **integration** branch (e.g. `develop`).
10
- - Include the TASK id in commit messages, e.g. `feat(task-015): short title`.
11
- - **`vibeops task done`** pushes the branch and opens an MR/PR — it does **not** merge locally.
12
- - Merge on GitHub/GitLab; CI deploys after merge. No force-push to shared branches.
@@ -1,38 +0,0 @@
1
- # {{PROJECT_NAME}} — AI agent guide
2
-
3
- > Installed by **VibeOps**. Cursor reads this repo, not chat history.
4
-
5
- ## Source of truth
6
-
7
- | What | Where |
8
- |------|--------|
9
- | Current work | `docs/tasks/TASK-NNN-*.md` |
10
- | Project memory | `docs/project/05-current-state.md`, `03-architecture.md`, `06-decisions.md` |
11
- | Change history | Git commits on task branches |
12
- | **Not** trusted | Cursor chat, Slack |
13
-
14
- ## Before you code
15
-
16
- 1. `docs/project/05-current-state.md`
17
- 2. `docs/project/03-architecture.md` and `06-decisions.md` when relevant
18
- 3. **The full current TASK file** under `docs/tasks/`
19
-
20
- ## Workflow (human + CLI)
21
-
22
- ```bash
23
- vibeops init # once
24
- vibeops task add # new TASK + branch
25
- # Plan in Cursor Ask: @docs/tasks/TASK-NNN-….md
26
- # Build in Cursor Agent: same file + Scope / Acceptance Criteria
27
- vibeops task done # close TASK, update project docs, push + open MR/PR
28
- vibeops status # briefing
29
- vibeops llm connect # set up LLM providers and pick default
30
- ```
31
-
32
- ## Rules
33
-
34
- - **One TASK at a time** — stay inside Scope and Acceptance Criteria.
35
- - **Search** before adding files; reuse existing patterns.
36
- - When the TASK is finished, **Result** and **Test Result** must be filled before `vibeops task done`.
37
-
38
- Details: `.cursor/rules/`.