@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 +8 -0
- package/README.md +1 -1
- package/dist/bootstrap/manifest.js +0 -10
- package/dist/cli.js +0 -0
- package/dist/commands/task-done.js +79 -6
- package/package.json +11 -13
- package/templates/.cursor/rules/02-git-safety.mdc +0 -12
- package/templates/AGENTS.md +0 -38
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
|
|
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 {
|
|
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
|
|
100
|
-
log.info(" · Status → Done
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
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.
|
package/templates/AGENTS.md
DELETED
|
@@ -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/`.
|