@goodtek/vibeops 2.3.0 → 2.4.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.
- package/CHANGELOG.md +20 -0
- package/README.md +20 -9
- package/dist/cli.js +22 -5
- package/dist/commands/task-reship.js +17 -171
- package/dist/commands/task-ship.js +260 -46
- package/dist/lib/inquirer-helpers.js +4 -0
- package/dist/lib/pr-create.js +10 -2
- package/dist/lib/resolve-task.js +31 -1
- package/dist/lib/task-commit-msg.js +80 -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-pr-llm.js +15 -3
- package/dist/lib/task-ship-state.js +39 -0
- package/package.json +12 -13
- 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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { readText } from "./filesystem.js";
|
|
3
|
+
import { listWorkingTreeRelPaths } from "./git.js";
|
|
4
|
+
import { bold, cyan, dim, green, log, yellow } from "./logger.js";
|
|
5
|
+
import { projectPaths } from "./paths.js";
|
|
6
|
+
import { prNumberFromUrl } from "./pr-create.js";
|
|
7
|
+
import { relPath } from "./task-context.js";
|
|
8
|
+
import { commitDirtyWorkingTree, docsCommitMessageFor, featCommitMessageFor, } from "./task-git-commit.js";
|
|
9
|
+
import { finishTaskWithPullRequest } from "./task-push-pr.js";
|
|
10
|
+
import { applyTaskShipMemory, llmCompleteTaskReship, writeTaskResultSections, } from "./task-ship-llm.js";
|
|
11
|
+
import { assertMergeRequestNotOpen, ensureTaskBranchForReship, integrateIntegrationIntoTaskBranch, loadGitConfigForReship, prepareGitContextForReship, refreshGitContextBaseCommit, resolveReshipTarget, } from "./task-reship.js";
|
|
12
|
+
import { hasNonEmptySection, parseTaskFilename, readGitContext } from "./task.js";
|
|
13
|
+
import { prRefLabel } from "./task-ship-state.js";
|
|
14
|
+
async function commitReshipMetadata(opts) {
|
|
15
|
+
if (opts.dryRun) {
|
|
16
|
+
log.info(dim(" would commit new-cycle metadata (Status stays Shipped)"));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const committed = await commitDirtyWorkingTree(opts.cwd, docsCommitMessageFor(opts.taskId, "new PR cycle metadata"), false);
|
|
20
|
+
if (!committed) {
|
|
21
|
+
log.info(dim("No new-cycle metadata commit (Git Context already on disk)."));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start a NEW PR cycle for an already-Shipped TASK whose previous PR was merged
|
|
26
|
+
* (the former `task reship` behavior). Carries uncommitted work onto the task
|
|
27
|
+
* branch, integrates the integration branch, and opens a fresh MR/PR.
|
|
28
|
+
*/
|
|
29
|
+
export async function runPostMergeNewCycle(taskRef, options = {}) {
|
|
30
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
31
|
+
const paths = projectPaths(cwd);
|
|
32
|
+
const dryRun = options.dryRun === true;
|
|
33
|
+
const target = await resolveReshipTarget(paths, cwd, taskRef);
|
|
34
|
+
if (target === null) {
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const { taskId, taskFile, meta, gitCtx: initialCtx } = target;
|
|
39
|
+
const relFile = relPath(cwd, taskFile);
|
|
40
|
+
log.info(bold(`vibeops task ship ${taskId} — new PR cycle`));
|
|
41
|
+
log.info(` ${dim("file")} ${relFile}`);
|
|
42
|
+
log.info(` ${dim("branch")} ${initialCtx.taskBranch}`);
|
|
43
|
+
log.info(` ${dim("status")} Shipped (unchanged)`);
|
|
44
|
+
log.blank();
|
|
45
|
+
const gitCfg = dryRun
|
|
46
|
+
? await loadGitConfigForReship(cwd).catch(() => null)
|
|
47
|
+
: await loadGitConfigForReship(cwd);
|
|
48
|
+
if (!dryRun && gitCfg === null) {
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const integrationBranch = gitCfg?.integrationBranch ?? "develop";
|
|
53
|
+
const remote = gitCfg?.remote ?? "origin";
|
|
54
|
+
const updateOpenMr = options.allowOpenMr === true;
|
|
55
|
+
if (!dryRun && !(await assertMergeRequestNotOpen(cwd, taskFile, updateOpenMr))) {
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const branchOk = await ensureTaskBranchForReship({
|
|
60
|
+
cwd,
|
|
61
|
+
taskFile,
|
|
62
|
+
gitCtx: initialCtx,
|
|
63
|
+
integrationBranch,
|
|
64
|
+
remote,
|
|
65
|
+
recreateBranch: options.recreateBranch === true,
|
|
66
|
+
dryRun,
|
|
67
|
+
});
|
|
68
|
+
if (!branchOk) {
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (options.noIntegrate !== true) {
|
|
73
|
+
const integrated = await integrateIntegrationIntoTaskBranch({
|
|
74
|
+
cwd,
|
|
75
|
+
integrationBranch,
|
|
76
|
+
remote,
|
|
77
|
+
dryRun,
|
|
78
|
+
});
|
|
79
|
+
if (!integrated) {
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
log.info(dim("Skipped integration merge (--no-integrate)."));
|
|
86
|
+
}
|
|
87
|
+
const body = await readText(taskFile);
|
|
88
|
+
let resultOk = hasNonEmptySection(body, "Result");
|
|
89
|
+
let testOk = hasNonEmptySection(body, "Test Result");
|
|
90
|
+
if (!options.skipLlm && !dryRun) {
|
|
91
|
+
log.step("LLM — follow-up Result / Test Result");
|
|
92
|
+
const patch = await llmCompleteTaskReship({
|
|
93
|
+
cwd,
|
|
94
|
+
taskId,
|
|
95
|
+
taskTitle: meta.title,
|
|
96
|
+
taskBody: body,
|
|
97
|
+
taskFileRel: relFile,
|
|
98
|
+
});
|
|
99
|
+
if (patch !== null) {
|
|
100
|
+
await writeTaskResultSections(taskFile, patch.result, patch.testResult);
|
|
101
|
+
log.ok(`Result / Test Result updated (${patch.provider})`);
|
|
102
|
+
const memory = await applyTaskShipMemory(cwd, patch);
|
|
103
|
+
for (const p of memory.updated)
|
|
104
|
+
log.ok(`Updated ${p}`);
|
|
105
|
+
for (const p of memory.skipped)
|
|
106
|
+
log.skip(`Skipped ${p}`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
log.warn("LLM unavailable — ensure Result / Test Result describe this follow-up.");
|
|
110
|
+
}
|
|
111
|
+
const body2 = await readText(taskFile);
|
|
112
|
+
resultOk = hasNonEmptySection(body2, "Result");
|
|
113
|
+
testOk = hasNonEmptySection(body2, "Test Result");
|
|
114
|
+
}
|
|
115
|
+
else if (options.skipLlm) {
|
|
116
|
+
log.info(dim("LLM skipped (--skip-llm)."));
|
|
117
|
+
}
|
|
118
|
+
log.info(bold("Sections"));
|
|
119
|
+
log.info(` ${resultOk ? green("✓") : yellow("·")} Result`);
|
|
120
|
+
log.info(` ${testOk ? green("✓") : yellow("·")} Test Result`);
|
|
121
|
+
log.blank();
|
|
122
|
+
if (dryRun) {
|
|
123
|
+
log.info(bold("dry-run — would also:"));
|
|
124
|
+
if (updateOpenMr) {
|
|
125
|
+
log.info(" · feat commit, push, update open MR/PR (unless --no-pr)");
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
log.info(" · feat commit + metadata, then push once and open a NEW MR/PR (unless --no-pr)");
|
|
129
|
+
}
|
|
130
|
+
log.blank();
|
|
131
|
+
log.info(`Next: ${cyan("vibeops task merge")}, then optional ${cyan("task sync")}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!resultOk || !testOk) {
|
|
135
|
+
log.error("Fill Result and Test Result for this follow-up, then rerun.");
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const dirtyBefore = await listWorkingTreeRelPaths(cwd);
|
|
140
|
+
if (dirtyBefore.length === 0) {
|
|
141
|
+
log.info(`Nothing to do — ${taskId} is merged with no new changes. Create a new TASK for new work (${cyan("vibeops task add")}).`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const parts = parseTaskFilename(taskFile);
|
|
145
|
+
await commitDirtyWorkingTree(cwd, featCommitMessageFor(parts.id, meta.title), false);
|
|
146
|
+
let gitCtx = await readGitContext(taskFile);
|
|
147
|
+
if (gitCtx === null) {
|
|
148
|
+
log.error("Git Context missing after commit.");
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (updateOpenMr) {
|
|
153
|
+
log.info(dim("Updating open MR/PR (--allow-open-mr)."));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
gitCtx = await refreshGitContextBaseCommit(taskFile, gitCtx, cwd);
|
|
157
|
+
gitCtx = await prepareGitContextForReship(cwd, taskFile, gitCtx);
|
|
158
|
+
await commitReshipMetadata({
|
|
159
|
+
cwd,
|
|
160
|
+
taskFile,
|
|
161
|
+
taskId: parts.id,
|
|
162
|
+
dryRun: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const prResult = await finishTaskWithPullRequest({
|
|
166
|
+
cwd,
|
|
167
|
+
taskFile,
|
|
168
|
+
skipPr: options.noPr === true,
|
|
169
|
+
forceNewMergeRequest: !updateOpenMr,
|
|
170
|
+
});
|
|
171
|
+
if (!prResult.ok) {
|
|
172
|
+
log.error("New PR cycle failed — fix push/MR, then rerun `vibeops task ship --new-cycle`.");
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
log.blank();
|
|
177
|
+
const ref = prRefLabel(prNumberFromUrl(prResult.mergeRequestUrl));
|
|
178
|
+
if (prResult.mergeRequestUrl) {
|
|
179
|
+
log.ok(`Started new PR cycle → PR ${ref}`);
|
|
180
|
+
log.info(` ${dim("MR/PR")} ${prResult.mergeRequestUrl}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
log.ok("Started new PR cycle (branch pushed; create the MR/PR manually).");
|
|
184
|
+
}
|
|
185
|
+
log.info(`Next: ${cyan("vibeops task merge")} (or merge in the host UI), then ${cyan("vibeops task sync")}`);
|
|
186
|
+
}
|
package/dist/lib/task-pr-llm.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { llmCompleteJson } from "./llm-complete.js";
|
|
2
|
+
import { taskScopedCommitMessage } from "./task-git-commit.js";
|
|
2
3
|
const SYSTEM = `You write pull/merge request titles and bodies for a software task.
|
|
3
4
|
Output valid JSON only: { "title": string, "body": string }.
|
|
4
|
-
Title: under 72 chars
|
|
5
|
+
Title: Conventional Commits format — type(task-nnn): lowercase subject (under 72 chars total).
|
|
6
|
+
Use task id as scope in lowercase (e.g. feat(task-013): ship public status page).
|
|
7
|
+
Allowed types: feat, fix, docs, chore, perf, ci, refactor, test, build, style, revert.
|
|
5
8
|
Body: markdown with Summary, Changes, Test plan sections. Facts only from the input.`;
|
|
9
|
+
/** Normalize PR titles to Conventional Commits with the TASK id as scope. */
|
|
10
|
+
export function normalizeTaskPrTitle(taskId, rawTitle) {
|
|
11
|
+
const trimmed = rawTitle.trim().replace(/\s+/g, " ");
|
|
12
|
+
const withoutTaskPrefix = trimmed.replace(/^TASK-\d+\s*[:\-]\s*/i, "").trim();
|
|
13
|
+
return taskScopedCommitMessage(taskId, withoutTaskPrefix || trimmed).slice(0, 72);
|
|
14
|
+
}
|
|
6
15
|
export async function generateTaskPrWithLlm(input, cwd, preference) {
|
|
7
16
|
const user = [
|
|
8
17
|
`Task: ${input.taskId}`,
|
|
@@ -27,14 +36,17 @@ export async function generateTaskPrWithLlm(input, cwd, preference) {
|
|
|
27
36
|
const prBody = typeof parsed.body === "string" ? parsed.body.trim() : "";
|
|
28
37
|
if (!prTitle || !prBody)
|
|
29
38
|
return null;
|
|
30
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
prTitle: normalizeTaskPrTitle(input.taskId, prTitle),
|
|
41
|
+
prBody,
|
|
42
|
+
};
|
|
31
43
|
}
|
|
32
44
|
catch {
|
|
33
45
|
return null;
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
export function fallbackTaskPr(input) {
|
|
37
|
-
const prTitle =
|
|
49
|
+
const prTitle = normalizeTaskPrTitle(input.taskId, input.title);
|
|
38
50
|
const prBody = [
|
|
39
51
|
"## Summary",
|
|
40
52
|
input.title,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { prNumberFromUrl } from "./pr-create.js";
|
|
2
|
+
import { resolveTaskMergeRequestLifecycle } from "./task-effective-status.js";
|
|
3
|
+
/**
|
|
4
|
+
* Single entry point for `ship` to learn the current PR/MR state of a TASK.
|
|
5
|
+
* Wraps {@link resolveTaskMergeRequestLifecycle} and extracts the PR number.
|
|
6
|
+
*/
|
|
7
|
+
export async function detectShipPrState(cwd, taskFile) {
|
|
8
|
+
const lifecycle = await resolveTaskMergeRequestLifecycle(cwd, taskFile);
|
|
9
|
+
const state = lifecycle.state === "open"
|
|
10
|
+
? "open"
|
|
11
|
+
: lifecycle.state === "merged"
|
|
12
|
+
? "merged"
|
|
13
|
+
: lifecycle.state === "closed"
|
|
14
|
+
? "closed"
|
|
15
|
+
: "none";
|
|
16
|
+
return {
|
|
17
|
+
state,
|
|
18
|
+
url: lifecycle.url,
|
|
19
|
+
number: prNumberFromUrl(lifecycle.url),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Map (PR state, TASK status) → the `ship` action. Pure so it is unit-testable.
|
|
24
|
+
* - open PR → update the existing PR (state 2)
|
|
25
|
+
* - merged PR → start a new PR cycle (state 3, guarded)
|
|
26
|
+
* - no/closed PR + In Progress → first submit (state 1)
|
|
27
|
+
* - no/closed PR + Shipped → status/PR mismatch (resync hint)
|
|
28
|
+
*/
|
|
29
|
+
export function resolveShipAction(prState, taskStatus) {
|
|
30
|
+
if (prState === "open")
|
|
31
|
+
return "update-open";
|
|
32
|
+
if (prState === "merged")
|
|
33
|
+
return "new-cycle";
|
|
34
|
+
return taskStatus === "shipped" ? "mismatch" : "first";
|
|
35
|
+
}
|
|
36
|
+
/** Format the PR reference for output (`#42` or `the PR`). */
|
|
37
|
+
export function prRefLabel(prNumber) {
|
|
38
|
+
return prNumber !== null ? `#${prNumber}` : "the PR";
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodtek/vibeops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
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,15 @@
|
|
|
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
|
+
"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"
|
|
61
60
|
}
|
|
62
|
-
}
|
|
61
|
+
}
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
|
|
3
3
|
- Work from `docs/tasks/TASK-NNN-*.md` only.
|
|
4
4
|
- One TASK at a time.
|
|
5
|
-
- Fill **Result** and **Test Result** before telling the human to run `vibeops task ship TASK-NNN`
|
|
5
|
+
- Fill **Result** and **Test Result** before telling the human to run `vibeops task ship TASK-NNN`. `ship` is state-aware: re-running before merge updates the open PR; after merge use `vibeops task ship --new-cycle`.
|
|
6
6
|
- Do not merge to integration or main unless the human runs `vibeops task merge` / `task sync` / `task release`.
|
|
@@ -8,4 +8,4 @@ alwaysApply: true
|
|
|
8
8
|
- Use branch pattern `task/*` from `vibeops task add`.
|
|
9
9
|
- Include TASK id in commits: `feat(task-nnn): …`.
|
|
10
10
|
- No force-push to shared branches.
|
|
11
|
-
- Run `vibeops task ship`, `task
|
|
11
|
+
- Run `vibeops task ship`, `task merge`, `task sync` when the human asks — do not merge locally in the IDE unless requested. (`ship` is state-aware: it updates an open PR or, with `--new-cycle`, starts a new PR cycle after merge.)
|
|
@@ -5,7 +5,7 @@ alwaysApply: true
|
|
|
5
5
|
|
|
6
6
|
# Docs before task ship
|
|
7
7
|
|
|
8
|
-
Before `vibeops task ship
|
|
8
|
+
Before `vibeops task ship`:
|
|
9
9
|
|
|
10
10
|
1. Fill **Result** and **Test Result** in the TASK file (paths, commands, outcomes — facts only).
|
|
11
11
|
2. If architecture or decisions changed, note them in the TASK; `task ship` can update `docs/project/*` via LLM.
|
package/templates/core/AGENTS.md
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
vibeops task add
|
|
9
9
|
# Plan / build in your agent (@docs/tasks/TASK-NNN-*.md)
|
|
10
10
|
|
|
11
|
-
vibeops task ship
|
|
11
|
+
vibeops task ship # state-aware: new PR / update open PR / new cycle
|
|
12
12
|
vibeops task merge # CLI or host UI
|
|
13
13
|
vibeops task sync # optional — branch cleanup only
|
|
14
14
|
|
|
15
|
-
# Same TASK
|
|
16
|
-
vibeops task
|
|
15
|
+
# Same TASK, before merge — just re-run ship to update the open PR:
|
|
16
|
+
vibeops task ship -m "address review"
|
|
17
|
+
# Same TASK, after merge — start a new PR cycle:
|
|
18
|
+
vibeops task ship --new-cycle
|
|
17
19
|
vibeops task merge
|
|
18
20
|
```
|
|
19
21
|
|
|
@@ -26,16 +28,17 @@ Only two values in `## Status`:
|
|
|
26
28
|
| Status | Meaning |
|
|
27
29
|
|--------|---------|
|
|
28
30
|
| **In Progress** | Active slice (`task add`) |
|
|
29
|
-
| **Shipped** | Submitted (`task ship`
|
|
31
|
+
| **Shipped** | Submitted (`task ship`) |
|
|
30
32
|
|
|
31
33
|
Merge and sync do **not** change the TASK file.
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
|
-
vibeops llm connect # optional — task add / ship
|
|
36
|
+
vibeops llm connect # optional — task add / ship
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
## Rules
|
|
38
40
|
|
|
39
41
|
- One **In Progress** TASK at a time; **Shipped** does not block `task add`.
|
|
40
|
-
- Fill **Result** and **Test Result** before `vibeops task ship` (
|
|
41
|
-
-
|
|
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`.
|
|
44
|
+
- Agents do not run `task merge` or `task sync` unless the human asks.
|
|
@@ -8,6 +8,6 @@ description: Implement the current VibeOps TASK file in the repo.
|
|
|
8
8
|
1. Read the full `docs/tasks/TASK-NNN-*.md` (Scope, Acceptance Criteria, Test Plan).
|
|
9
9
|
2. Search the codebase before adding new patterns.
|
|
10
10
|
3. Implement only within the TASK scope.
|
|
11
|
-
4. Do not run `vibeops task ship`, `
|
|
11
|
+
4. Do not run `vibeops task ship`, `merge`, or `sync` — the human runs the CLI lifecycle.
|
|
12
12
|
|
|
13
|
-
When implementation is ready, tell the human to run `vibeops task ship TASK-NNN`.
|
|
13
|
+
When implementation is ready, tell the human to run `vibeops task ship TASK-NNN`. `ship` is state-aware: re-running it before merge updates the open PR; after merge it starts a new PR cycle with `vibeops task ship --new-cycle`.
|