@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.
- package/CHANGELOG.md +19 -0
- package/README.md +53 -21
- package/dist/cli.js +4 -26
- package/dist/commands/init.js +43 -21
- package/dist/commands/pull.js +8 -1
- package/dist/commands/status.js +91 -72
- package/dist/commands/task-add.js +86 -19
- package/dist/commands/task-merge.js +7 -3
- package/dist/commands/task-release.js +5 -2
- package/dist/lib/git-host-cli.js +115 -0
- package/dist/lib/git-integration-sync.js +214 -0
- package/dist/lib/git-remote.js +177 -59
- package/dist/lib/git.js +25 -0
- package/dist/lib/task-context.js +28 -16
- package/dist/lib/task-push-pr.js +6 -2
- package/dist/lib/task-reship.js +1 -1
- package/dist/lib/task-start.js +24 -15
- package/package.json +16 -12
- package/templates/core/AGENTS.md +1 -1
- package/templates/core/docs/project/06-decisions.md +1 -1
- package/dist/commands/task-reship.js +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to VibeOps are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.5.1 - 2026-07-20
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`task add`:** Preflight-syncs the integration branch **before** creating a TASK file. On ff-only failure, prints a classified diagnosis (dirty / ahead / diverged) with copy-paste fix commands. Incomplete adds (file created, branch missing) resume on the next `task add` instead of blocking with “finish ship first”.
|
|
12
|
+
- **`vibeops pull`:** Uses the same integration sync diagnosis when `--ff-only` fails.
|
|
13
|
+
|
|
14
|
+
## 2.5.0 - 2026-07-19
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
|
|
18
|
+
- **Removed `vibeops task reship`.** Use `vibeops task ship` (state-aware). After merge, confirm or pass `--new-cycle`. Hidden flag `ship --reship` remains as an alias for `--new-cycle`.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`vibeops init` remote setup:** ask GitHub / GitLab / Skip, then Create or Connect. Soft-gate when `gh`/`glab` is missing (install hints, optional `brew install` with consent, or manual URL).
|
|
23
|
+
- **`vibeops status`:** human output is a **Now / Next** card (focus, checklist, PR, next command); details (project, counts, LLM) move to a dim footer.
|
|
24
|
+
- Shared **host CLI install/login hints** for ship / merge / release when the tool is missing.
|
|
25
|
+
|
|
7
26
|
## 2.4.4 - 2026-06-25
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -7,30 +7,59 @@
|
|
|
7
7
|
|
|
8
8
|
VibeOps bootstraps an **agent-friendly repo**, starts numbered **TASK** files on Git branches, and runs a clear GitFlow lifecycle. You plan and implement in your chosen agent; the CLI handles files, Git, and short LLM assists.
|
|
9
9
|
|
|
10
|
+
**Current release:** [`@goodtek/vibeops@2.5.1`](https://www.npmjs.com/package/@goodtek/vibeops) — interactive init remote (GitHub/GitLab), state-aware `task ship` only (no `task reship`), `status` Now/Next card, clearer `task add` integration sync errors.
|
|
11
|
+
|
|
10
12
|
## Commands
|
|
11
13
|
|
|
12
14
|
| Command | Purpose |
|
|
13
15
|
|---------|---------|
|
|
14
|
-
| `vibeops init` | Core docs + agent packs
|
|
16
|
+
| `vibeops init` | Core docs + agent packs; interactive Git host + create/connect remote |
|
|
15
17
|
| `vibeops task add` | New `TASK-NNN` file + task branch |
|
|
16
18
|
| `vibeops task del` | Cancel TASK before merge (md + branch + close open MR) |
|
|
17
|
-
| `vibeops task ship` | State-aware submit:
|
|
18
|
-
| `vibeops task reship` | **Deprecated** alias for `task ship --new-cycle` |
|
|
19
|
+
| `vibeops task ship` | State-aware submit: new PR · update open PR · new PR cycle after merge (Status → **Shipped**) |
|
|
19
20
|
| `vibeops task merge` | Merge TASK MR/PR into integration branch (default: squash) |
|
|
20
21
|
| `vibeops task sync` | After merge: ff-only integration pull, delete task branches (no TASK md edits) |
|
|
21
22
|
| `vibeops task release` | Release PR: integration → production (GitFlow) |
|
|
22
23
|
| `vibeops pull` | Fetch remote + update integration branch (e.g. develop) |
|
|
23
|
-
| `vibeops status` |
|
|
24
|
+
| `vibeops status` | **Now / Next** card — focus TASK, checklist, what to run next |
|
|
24
25
|
| `vibeops llm` | Connect LLM providers (`connect` · `status` · `use`) |
|
|
25
26
|
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Node.js 20+.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g @goodtek/vibeops@2.5.1
|
|
33
|
+
# or latest: npm install -g @goodtek/vibeops
|
|
34
|
+
vibeops --version # expect 2.5.1
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If `vibeops --version` stays on an old release after install, check for a **shell alias** or **Volta** shim shadowing npm:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
type -a vibeops
|
|
41
|
+
# alias → unalias vibeops (and remove from ~/.zshrc)
|
|
42
|
+
# ~/.volta/bin/vibeops → volta install @goodtek/vibeops@2.5.1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Upgrading from before 2.5:** `task reship` was removed. Use `vibeops task ship` (re-run while the PR is open; after merge use confirm or `--new-cycle`).
|
|
46
|
+
|
|
47
|
+
Development:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm install && pnpm build && pnpm smoke
|
|
51
|
+
```
|
|
52
|
+
|
|
26
53
|
## Init
|
|
27
54
|
|
|
55
|
+
Interactive init asks which agents to install, Git branch policy, then **where the remote lives** (GitHub / GitLab / skip) and whether to **create** or **connect** a repository. If `gh`/`glab` is missing, init prints install hints (optional `brew install` with consent) or accepts a URL — it does not block the rest of VibeOps.
|
|
56
|
+
|
|
28
57
|
```bash
|
|
29
|
-
# Interactive: pick agents (≥1), then Git
|
|
58
|
+
# Interactive: pick agents (≥1), then Git + remote host
|
|
30
59
|
vibeops init
|
|
31
60
|
|
|
32
61
|
# Non-interactive (GitFlow: develop + main, origin required unless CI)
|
|
33
|
-
vibeops init --clients cursor,claude,codex --git --initial-commit --git-policy gitflow
|
|
62
|
+
vibeops init --clients cursor,claude,codex --git --initial-commit --git-policy gitflow --allow-no-remote
|
|
34
63
|
|
|
35
64
|
# Re-init templates on an existing project
|
|
36
65
|
vibeops init --clients cursor --yes
|
|
@@ -82,36 +111,39 @@ vibeops task release
|
|
|
82
111
|
|
|
83
112
|
Only one TASK **In Progress** at a time (`task add` blocks otherwise). **Shipped** slices do not block the next add; merge on the host or with `task merge`, then optional `task sync`.
|
|
84
113
|
|
|
85
|
-
|
|
114
|
+
`task add` fast-forward-pulls the integration branch first. If that fails (diverged / dirty / local-ahead), it prints the cause and fix commands and **does not** create a TASK file. If a previous run left a TASK file without a branch, rerun `task add` to resume.
|
|
86
115
|
|
|
87
|
-
|
|
116
|
+
## Status
|
|
88
117
|
|
|
89
118
|
```bash
|
|
90
|
-
vibeops
|
|
91
|
-
vibeops llm use auto # auto | codex-oauth | cursor-agent | openai
|
|
119
|
+
vibeops status
|
|
92
120
|
```
|
|
93
121
|
|
|
94
|
-
|
|
122
|
+
Prints a short card:
|
|
95
123
|
|
|
96
|
-
|
|
124
|
+
- **NOW** — focus TASK, stage, Result/Test checklist, branch, PR
|
|
125
|
+
- **NEXT** — one primary action (copy-pasteable command)
|
|
126
|
+
- footer — project, task counts, LLM (dim)
|
|
97
127
|
|
|
98
128
|
```bash
|
|
99
|
-
|
|
129
|
+
vibeops status --json # machine-readable
|
|
100
130
|
```
|
|
101
131
|
|
|
102
|
-
|
|
132
|
+
## LLM (optional)
|
|
133
|
+
|
|
134
|
+
For **`task add`** / **`task ship`** only (not for coding in the IDE). When `-m` is omitted, `ship` uses a connected provider to draft the commit subject:
|
|
103
135
|
|
|
104
136
|
```bash
|
|
105
|
-
|
|
137
|
+
vibeops llm connect
|
|
138
|
+
vibeops llm use auto # auto | codex-oauth | cursor-agent | openai
|
|
106
139
|
```
|
|
107
140
|
|
|
108
141
|
## Flags (common)
|
|
109
142
|
|
|
110
|
-
- **`init`**: `--clients`, `--yes`, `--dry-run`, `--force`, `--git`, `--initial-commit`, `--git-policy gitflow|trunk`, `--integration-branch`, `--production-branch`, `--allow-no-remote`, `--cwd`
|
|
143
|
+
- **`init`**: `--clients`, `--yes`, `--dry-run`, `--force`, `--git`, `--initial-commit`, `--git-policy gitflow|trunk`, `--integration-branch`, `--production-branch`, `--git-host github|gitlab`, `--allow-no-remote`, `--cwd`
|
|
111
144
|
- **`task add`**: `--dry-run`, `--non-interactive --idea "…"`
|
|
112
145
|
- **`task del`**: `--dry-run`, `--force`, `--no-remote-delete`, `--no-close-mr`
|
|
113
|
-
- **`task ship`**: `-m/--message`, `--new-cycle
|
|
114
|
-
- **`task reship`** (deprecated): `--dry-run`, `--no-pr`, `--no-integrate`, `--recreate-branch`, `--skip-llm`, `--allow-open-mr` → delegates to `task ship --new-cycle`
|
|
146
|
+
- **`task ship`**: `-m/--message`, `--new-cycle`, `--no-commit`, `--dry-run`, `--no-pr`, `--non-interactive`, `--allow-open-mr`, `--no-integrate`, `--recreate-branch`, `--skip-llm`
|
|
115
147
|
- **`task merge`**: `--dry-run`, `--merge`, `--rebase`
|
|
116
148
|
- **`task sync`**: `--dry-run`, `--no-remote-delete`, `--force`
|
|
117
149
|
- **`task release`**: `--dry-run`, `--no-merge`, `--merge`, `--rebase`
|
|
@@ -120,14 +152,14 @@ pnpm install && pnpm build && pnpm smoke
|
|
|
120
152
|
|
|
121
153
|
## Git
|
|
122
154
|
|
|
123
|
-
- **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`).
|
|
155
|
+
- **Init** records branch policy in `.vibeops.json` (`integrationBranch`, `productionBranch`, `host`). Interactive remote setup: ask GitHub/GitLab, create or connect; soft-gate when `gh`/`glab` is missing.
|
|
124
156
|
- **`task add`**: `task/<slug>` from **integration** (e.g. `develop`).
|
|
125
|
-
- **`task ship`** (state-aware): **no PR** → commit + ship metadata (Status **Shipped**) → push → open MR/PR; **open PR** → commit + push the same branch, CI re-runs, no new PR; **merged PR** → new PR cycle (carries uncommitted work onto the task branch, integrates `develop`, opens a **new** PR). Commit messages are TASK-id-scoped (`feat(task-001): …`). Refuses when HEAD is not the task branch.
|
|
126
|
-
- **`task reship`** *(deprecated)*: alias for `task ship --new-cycle`.
|
|
157
|
+
- **`task ship`** (state-aware): **no PR** → commit + ship metadata (Status **Shipped**) → push → open MR/PR; **open PR** → commit + push the same branch, CI re-runs, no new PR; **merged PR** → new PR cycle (carries uncommitted work onto the task branch, integrates `develop`, opens a **new** PR). Use `--new-cycle` in non-interactive mode. Commit messages are TASK-id-scoped (`feat(task-001): …`). Refuses when HEAD is not the task branch.
|
|
127
158
|
- **`task merge`**: merge MR/PR into integration (CLI or host UI; TASK md unchanged).
|
|
128
159
|
- **`task sync`**: integration ff-only pull → delete `task/*` branches (TASK md unchanged).
|
|
129
160
|
- **`pull`**: fetch + switch to integration branch + `git pull --ff-only` (one command).
|
|
130
161
|
- **`task release`**: integration → production PR + merge (skipped on trunk policy).
|
|
162
|
+
- **`status`**: Now / Next card — focus stage, checklist, PR, and the next command to run.
|
|
131
163
|
- No force-push to shared branches.
|
|
132
164
|
|
|
133
165
|
## License
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
3
|
import { pullCommand } from "./commands/pull.js";
|
|
4
4
|
import { initCommand } from "./commands/init.js";
|
|
5
5
|
import { llmConnectCommand, llmStatusCommand, llmUseCommand } from "./commands/llm.js";
|
|
@@ -9,7 +9,6 @@ import { taskDelCommand } from "./commands/task-del.js";
|
|
|
9
9
|
import { taskMergeCommand } from "./commands/task-merge.js";
|
|
10
10
|
import { taskReleaseCommand } from "./commands/task-release.js";
|
|
11
11
|
import { taskShipCommand } from "./commands/task-ship.js";
|
|
12
|
-
import { taskReshipCommand } from "./commands/task-reship.js";
|
|
13
12
|
import { taskSyncCommand } from "./commands/task-sync.js";
|
|
14
13
|
import { loadVibeopsEnv } from "./lib/env.js";
|
|
15
14
|
import { VERSION } from "./version.js";
|
|
@@ -61,7 +60,7 @@ program
|
|
|
61
60
|
});
|
|
62
61
|
const task = program
|
|
63
62
|
.command("task")
|
|
64
|
-
.description("TASK lifecycle (add · del · ship ·
|
|
63
|
+
.description("TASK lifecycle (add · del · ship · merge · sync · release)");
|
|
65
64
|
task
|
|
66
65
|
.command("add")
|
|
67
66
|
.description("Create next TASK-NNN, task branch, In Progress")
|
|
@@ -99,8 +98,8 @@ task
|
|
|
99
98
|
.description("State-aware submit: new PR · update open PR · new PR cycle after merge (Status → Shipped)")
|
|
100
99
|
.option("--dry-run", "Plan only")
|
|
101
100
|
.option("-m, --message <msg>", "Commit message (TASK id auto-prefixed; LLM/prompt when omitted)")
|
|
102
|
-
.option("--new-cycle", "Allow a new PR cycle after merge without prompting
|
|
103
|
-
.
|
|
101
|
+
.option("--new-cycle", "Allow a new PR cycle after merge without prompting")
|
|
102
|
+
.addOption(new Option("--reship", "Alias for --new-cycle").hideHelp())
|
|
104
103
|
.option("--no-commit", "Push already-committed changes only (skip staging/commit)")
|
|
105
104
|
.option("--no-pr", "Push only; skip creating MR/PR")
|
|
106
105
|
.option("--non-interactive", "CI: never prompt")
|
|
@@ -124,27 +123,6 @@ task
|
|
|
124
123
|
cwd: opts.cwd,
|
|
125
124
|
});
|
|
126
125
|
});
|
|
127
|
-
task
|
|
128
|
-
.command("reship [taskRef]")
|
|
129
|
-
.description("Deprecated alias for `task ship --new-cycle` (start a new PR cycle after merge)")
|
|
130
|
-
.option("--dry-run", "Plan only")
|
|
131
|
-
.option("--no-pr", "Push only; skip creating MR/PR")
|
|
132
|
-
.option("--no-integrate", "Skip merging integration branch into task branch")
|
|
133
|
-
.option("--recreate-branch", "Create task branch from integration instead of reusing local/remote ref")
|
|
134
|
-
.option("--skip-llm", "Do not run LLM for Result / Test Result")
|
|
135
|
-
.option("--allow-open-mr", "Update the open MR/PR with new commits (merge before reship for a new PR)")
|
|
136
|
-
.option("--cwd <path>", "Target directory")
|
|
137
|
-
.action(async (taskRef, opts) => {
|
|
138
|
-
await taskReshipCommand(taskRef, {
|
|
139
|
-
dryRun: Boolean(opts.dryRun),
|
|
140
|
-
noPr: opts.pr === false,
|
|
141
|
-
noIntegrate: opts.integrate === false,
|
|
142
|
-
recreateBranch: Boolean(opts.recreateBranch),
|
|
143
|
-
skipLlm: Boolean(opts.skipLlm),
|
|
144
|
-
allowOpenMr: Boolean(opts.allowOpenMr),
|
|
145
|
-
cwd: opts.cwd,
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
126
|
task
|
|
149
127
|
.command("merge [taskRef]")
|
|
150
128
|
.description("Merge TASK MR/PR into integration branch (default: squash)")
|
package/dist/commands/init.js
CHANGED
|
@@ -108,30 +108,41 @@ async function resolveBranchPolicy(options, interactive) {
|
|
|
108
108
|
}
|
|
109
109
|
return { integrationBranch: "develop", productionBranch: "main" };
|
|
110
110
|
}
|
|
111
|
-
function printNextSteps(clients, git) {
|
|
111
|
+
function printNextSteps(clients, git, opts) {
|
|
112
112
|
log.blank();
|
|
113
113
|
log.info("Next steps:");
|
|
114
|
-
|
|
114
|
+
let n = 1;
|
|
115
|
+
log.info(` ${n++}. Read AGENTS.md`);
|
|
115
116
|
if (clients.includes("cursor")) {
|
|
116
|
-
log.info(
|
|
117
|
+
log.info(` ${n++}. Cursor: @docs/tasks/TASK-001-….md in Ask, then Agent (+ /plan-task, /implement-task)`);
|
|
117
118
|
}
|
|
118
|
-
if (clients.includes("claude")) {
|
|
119
|
-
log.info(
|
|
119
|
+
else if (clients.includes("claude")) {
|
|
120
|
+
log.info(` ${n++}. Claude Code: open TASK file; use /plan-task then /implement-task`);
|
|
120
121
|
}
|
|
121
|
-
if (clients.includes("codex")) {
|
|
122
|
-
log.info(
|
|
122
|
+
else if (clients.includes("codex")) {
|
|
123
|
+
log.info(` ${n++}. Codex: open TASK file; use $plan-task / $implement-task when needed`);
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
-
log.info(
|
|
125
|
+
else {
|
|
126
|
+
log.info(` ${n++}. Open the current TASK file in your agent before coding`);
|
|
127
|
+
}
|
|
128
|
+
log.info(` ${n++}. ${cyan("vibeops llm connect")} — LLM for task add / task ship`);
|
|
129
|
+
if (!opts.hasRemote) {
|
|
130
|
+
log.info(` ${n++}. Add a remote later: ${dim(`git remote add ${git.remote} <url>`)} (or re-run init)`);
|
|
131
|
+
}
|
|
132
|
+
else if (!opts.pushedBaseline) {
|
|
133
|
+
log.info(` ${n++}. Push branches to origin (first time only):`);
|
|
134
|
+
log.info(` ${dim(`git push -u ${git.remote} ${git.productionBranch}`)}`);
|
|
135
|
+
if (git.integrationBranch !== git.productionBranch) {
|
|
136
|
+
log.info(` ${dim(`git push -u ${git.remote} ${git.integrationBranch}`)}`);
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
|
-
log.info(`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
log.info(` ${n++}. ${cyan("vibeops task add")} — branches from ${git.integrationBranch} (pulls latest first)`);
|
|
140
|
+
if (opts.hasRemote) {
|
|
141
|
+
log.info(` ${n++}. ${cyan("vibeops task ship")} → merge → sync — TASK lifecycle on ${git.host}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
log.info(` ${n++}. ${cyan("vibeops task ship")} — after origin exists`);
|
|
132
145
|
}
|
|
133
|
-
log.info(` 5. ${cyan("vibeops task add")} — branches from ${git.integrationBranch} (pulls latest first)`);
|
|
134
|
-
log.info(` 6. ${cyan("vibeops task ship")} → merge → sync — TASK lifecycle on ${git.host}`);
|
|
135
146
|
}
|
|
136
147
|
export async function initCommand(options = {}) {
|
|
137
148
|
const dryRun = options.dryRun === true;
|
|
@@ -223,6 +234,7 @@ export async function initCommand(options = {}) {
|
|
|
223
234
|
dryRun,
|
|
224
235
|
nonInteractive: !interactive,
|
|
225
236
|
allowMissing: options.allowNoRemote === true,
|
|
237
|
+
preferredHost: hostFromFlag ?? existingConfig?.git?.host,
|
|
226
238
|
defaultBranch: gitConfig.productionBranch,
|
|
227
239
|
});
|
|
228
240
|
if (remote !== null) {
|
|
@@ -232,14 +244,17 @@ export async function initCommand(options = {}) {
|
|
|
232
244
|
host: remote.host,
|
|
233
245
|
};
|
|
234
246
|
}
|
|
247
|
+
let pushedBaseline = hadRemoteBefore;
|
|
235
248
|
// If we created/configured a new remote during init, push baseline branches so
|
|
236
249
|
// `task add` can always pull latest integration before branching.
|
|
237
|
-
if (!dryRun &&
|
|
250
|
+
if (!dryRun &&
|
|
251
|
+
options.allowNoRemote !== true &&
|
|
252
|
+
!hadRemoteBefore &&
|
|
253
|
+
remote !== null) {
|
|
238
254
|
const remoteName = gitConfig.remote;
|
|
239
255
|
log.blank();
|
|
240
256
|
log.info("Remote bootstrap:");
|
|
241
257
|
try {
|
|
242
|
-
// Ensure local branches are up-to-date with origin if they already exist remotely.
|
|
243
258
|
const prodRemoteExists = await gitRemoteBranchExists(projectRoot, remoteName, gitConfig.productionBranch);
|
|
244
259
|
if (prodRemoteExists) {
|
|
245
260
|
await gitCheckout(projectRoot, gitConfig.productionBranch);
|
|
@@ -258,6 +273,7 @@ export async function initCommand(options = {}) {
|
|
|
258
273
|
await gitPush(projectRoot, remoteName, gitConfig.integrationBranch, true);
|
|
259
274
|
log.ok(`pushed ${gitConfig.integrationBranch} → ${remoteName}`);
|
|
260
275
|
}
|
|
276
|
+
pushedBaseline = true;
|
|
261
277
|
}
|
|
262
278
|
catch (e) {
|
|
263
279
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -267,14 +283,20 @@ export async function initCommand(options = {}) {
|
|
|
267
283
|
if (gitConfig.integrationBranch !== gitConfig.productionBranch) {
|
|
268
284
|
log.info(dim(` git push -u ${remoteName} ${gitConfig.integrationBranch}`));
|
|
269
285
|
}
|
|
286
|
+
pushedBaseline = false;
|
|
270
287
|
}
|
|
271
288
|
}
|
|
272
289
|
if (!dryRun) {
|
|
273
290
|
await writeConfig(projectRoot, buildConfig(name, clients, gitConfig, existingConfig));
|
|
274
|
-
if (
|
|
275
|
-
log.warn(
|
|
291
|
+
if (remote === null) {
|
|
292
|
+
log.warn(options.allowNoRemote === true
|
|
293
|
+
? "No git remote configured (--allow-no-remote). task ship push/MR will fail until origin exists."
|
|
294
|
+
: "No git remote configured. task ship push/MR will fail until origin exists.");
|
|
276
295
|
}
|
|
277
|
-
printNextSteps(clients, gitConfig
|
|
296
|
+
printNextSteps(clients, gitConfig, {
|
|
297
|
+
hasRemote: remote !== null,
|
|
298
|
+
pushedBaseline,
|
|
299
|
+
});
|
|
278
300
|
}
|
|
279
301
|
}
|
|
280
302
|
async function runGitSetup(projectRoot, plan, dryRun) {
|
package/dist/commands/pull.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { GitConfigError, requireGitConfig } from "../lib/git-config.js";
|
|
3
|
+
import { diagnoseIntegrationSync, printIntegrationSyncDiagnosis, } from "../lib/git-integration-sync.js";
|
|
3
4
|
import { gitFetchRemote, gitPullFastForwardOnly, gitRemoteBranchExists, gitRemoteUrl, gitSwitchToBranch, readGitInfo, } from "../lib/git.js";
|
|
4
5
|
import { bold, cyan, dim, log } from "../lib/logger.js";
|
|
5
6
|
/**
|
|
@@ -73,7 +74,13 @@ export async function pullCommand(options = {}) {
|
|
|
73
74
|
}
|
|
74
75
|
catch (e) {
|
|
75
76
|
const msg = e instanceof Error ? e.message : String(e);
|
|
76
|
-
|
|
77
|
+
const diagnosis = await diagnoseIntegrationSync(cwd, remote, integrationBranch);
|
|
78
|
+
if (!diagnosis.ok) {
|
|
79
|
+
printIntegrationSyncDiagnosis(diagnosis);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
log.error(`Could not fast-forward ${integrationBranch}: ${msg}. Commit or stash local changes, then rerun \`vibeops pull\`.`);
|
|
83
|
+
}
|
|
77
84
|
process.exitCode = 1;
|
|
78
85
|
}
|
|
79
86
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -7,7 +7,7 @@ import { buildLlmStatusReport } from "../lib/llm-status.js";
|
|
|
7
7
|
import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
|
|
8
8
|
import { projectPaths } from "../lib/paths.js";
|
|
9
9
|
import { VERSION } from "../version.js";
|
|
10
|
-
import { computeNextHint, hintToText, listInProgressTasks, pickFocusTask, relPath, taskBranchExistsFor, } from "../lib/task-context.js";
|
|
10
|
+
import { computeNextHint, hintToLines, hintToText, listInProgressTasks, pickFocusTask, relPath, taskBranchExistsFor, } from "../lib/task-context.js";
|
|
11
11
|
import { hasOpenTaskMergeRequest, isMergeRequestMerged, resolveTaskMergeRequestUrl, taskNeedsSync, } from "../lib/task-effective-status.js";
|
|
12
12
|
import { countTasks, hasNonEmptySection, isOnTaskBranch, loadActionableTasks, readGitContext, statusDisplay, } from "../lib/task.js";
|
|
13
13
|
import { summarizeGoal } from "../lib/task-summary.js";
|
|
@@ -59,6 +59,8 @@ async function buildReport(cwd) {
|
|
|
59
59
|
taskBranch,
|
|
60
60
|
taskBranchExists: branchExists,
|
|
61
61
|
mergeRequestUrl,
|
|
62
|
+
mergeRequestMerged,
|
|
63
|
+
needsSync,
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
const hint = computeNextHint({
|
|
@@ -85,6 +87,8 @@ async function buildReport(cwd) {
|
|
|
85
87
|
branch: git.branch,
|
|
86
88
|
dirty: git.dirty,
|
|
87
89
|
policy: config?.git ? formatGitPolicySummary(config.git) : null,
|
|
90
|
+
host: config?.git?.host ?? null,
|
|
91
|
+
remote: config?.git?.remote ?? null,
|
|
88
92
|
},
|
|
89
93
|
counts,
|
|
90
94
|
inProgress: inProgress.map((t) => ({
|
|
@@ -94,96 +98,111 @@ async function buildReport(cwd) {
|
|
|
94
98
|
})),
|
|
95
99
|
focus: focusBlock,
|
|
96
100
|
nextHint: hintToText(hint, cwd, focus),
|
|
101
|
+
nextLines: [...hintToLines(hint, cwd, focus)],
|
|
97
102
|
llm,
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
log.info(bold("
|
|
105
|
-
log.info(` ${dim("CLI")} v${report.vibeopsVersion}`);
|
|
105
|
+
function filledLabel(ok) {
|
|
106
|
+
return ok ? green("ok") : yellow("empty");
|
|
107
|
+
}
|
|
108
|
+
function printNow(report) {
|
|
109
|
+
log.info(bold(" NOW"));
|
|
106
110
|
if (!report.isVibeopsProject) {
|
|
107
|
-
log.info(`
|
|
108
|
-
log.info(bold("─".repeat(56)));
|
|
109
|
-
log.blank();
|
|
111
|
+
log.info(` ${dim("Project")} ${yellow("not a VibeOps project")}`);
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
112
|
-
log.info(` ${dim("Project")} ${report.projectName ?? "(unnamed)"}`);
|
|
113
|
-
if (report.clients.length > 0) {
|
|
114
|
-
log.info(` ${dim("Clients")} ${report.clients.join(", ")}`);
|
|
115
|
-
}
|
|
116
|
-
if (report.counts) {
|
|
117
|
-
const c = report.counts;
|
|
118
|
-
log.info(` ${dim("Tasks")} ${c.total} total · ${cyan(String(c.in_progress))} in progress · ${c.shipped} shipped`);
|
|
119
|
-
}
|
|
120
|
-
if (report.inProgress.length > 1) {
|
|
121
|
-
log.blank();
|
|
122
|
-
log.info(bold(" In progress"));
|
|
123
|
-
for (const t of report.inProgress) {
|
|
124
|
-
log.info(` ${cyan(t.id)} — ${t.title}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
log.blank();
|
|
128
114
|
if (report.focus) {
|
|
129
115
|
const f = report.focus;
|
|
130
|
-
log.info(`
|
|
131
|
-
log.info(`
|
|
132
|
-
log.info(`
|
|
133
|
-
if (f.
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
log.info(` ${dim("Focus")} ${cyan(f.id)} — ${f.title || dim("(no title)")}`);
|
|
117
|
+
log.info(` ${dim("Stage")} ${f.status}`);
|
|
118
|
+
log.info(` ${dim("Checklist")} Result ${filledLabel(f.resultFilled)} · Test Result ${filledLabel(f.testResultFilled)}`);
|
|
119
|
+
if (f.taskBranch) {
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (f.onTaskBranch === true)
|
|
122
|
+
parts.push("on it");
|
|
123
|
+
else if (f.onTaskBranch === false)
|
|
124
|
+
parts.push("not checked out");
|
|
125
|
+
if (report.git.dirty === true)
|
|
126
|
+
parts.push("dirty");
|
|
127
|
+
else if (report.git.dirty === false)
|
|
128
|
+
parts.push("clean");
|
|
129
|
+
const suffix = parts.length > 0 ? ` (${parts.join(" · ")})` : "";
|
|
130
|
+
log.info(` ${dim("Branch")} ${f.taskBranch}${suffix}`);
|
|
131
|
+
}
|
|
132
|
+
else if (report.git.isRepo) {
|
|
133
|
+
log.info(` ${dim("Branch")} ${report.git.branch ?? "(detached)"}${report.git.dirty === true ? yellow(" · dirty") : ""}`);
|
|
134
|
+
}
|
|
135
|
+
if (f.needsSync) {
|
|
136
|
+
log.info(` ${dim("PR")} ${dim("(merged — sync pending)")}`);
|
|
137
|
+
}
|
|
138
|
+
else if (f.mergeRequestUrl) {
|
|
139
|
+
const tag = f.mergeRequestMerged ? dim(" · merged") : "";
|
|
140
|
+
log.info(` ${dim("PR")} ${f.mergeRequestUrl}${tag}`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
log.info(` ${dim("PR")} ${dim("(none)")}`);
|
|
144
|
+
}
|
|
136
145
|
}
|
|
137
146
|
else {
|
|
138
|
-
log.info(`
|
|
147
|
+
log.info(` ${dim("Focus")} ${dim("(none)")}`);
|
|
148
|
+
if (report.git.isRepo) {
|
|
149
|
+
log.info(` ${dim("Branch")} ${report.git.branch ?? "(detached)"}${report.git.dirty === true ? yellow(" · dirty") : ""}`);
|
|
150
|
+
}
|
|
139
151
|
}
|
|
152
|
+
}
|
|
153
|
+
function printNext(report) {
|
|
140
154
|
log.blank();
|
|
141
|
-
log.info(bold("
|
|
142
|
-
|
|
143
|
-
log.info(` ${
|
|
155
|
+
log.info(bold(" NEXT"));
|
|
156
|
+
for (const line of report.nextLines) {
|
|
157
|
+
log.info(` ${cyan("→")} ${line}`);
|
|
144
158
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
}
|
|
160
|
+
function printDetails(report) {
|
|
161
|
+
log.blank();
|
|
162
|
+
log.info(dim(" ──"));
|
|
163
|
+
if (!report.isVibeopsProject) {
|
|
164
|
+
log.info(` ${dim("CLI")} v${report.vibeopsVersion}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const clients = report.clients.length > 0 ? report.clients.join(", ") : dim("(none)");
|
|
168
|
+
log.info(` ${dim("Project")} ${report.projectName ?? "(unnamed)"} · clients ${clients}`);
|
|
169
|
+
if (report.counts) {
|
|
170
|
+
const c = report.counts;
|
|
171
|
+
log.info(` ${dim("Tasks")} ${c.total} total · ${cyan(String(c.in_progress))} in progress · ${c.shipped} shipped`);
|
|
172
|
+
}
|
|
173
|
+
if (report.inProgress.length > 1) {
|
|
174
|
+
log.info(` ${dim("Active")} ${report.inProgress.map((t) => t.id).join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
const hostBits = [];
|
|
177
|
+
if (report.git.host)
|
|
178
|
+
hostBits.push(report.git.host);
|
|
179
|
+
if (report.git.remote)
|
|
180
|
+
hostBits.push(report.git.remote);
|
|
181
|
+
if (report.git.policy)
|
|
182
|
+
hostBits.push(report.git.policy);
|
|
183
|
+
if (hostBits.length > 0) {
|
|
184
|
+
log.info(` ${dim("Git")} ${hostBits.join(" · ")}`);
|
|
165
185
|
}
|
|
166
186
|
if (report.llm) {
|
|
167
|
-
log.blank();
|
|
168
|
-
log.info(bold(" LLM"));
|
|
169
|
-
log.info(` ${dim("Preferred")} ${report.llm.preference}`);
|
|
170
187
|
const active = report.llm.active !== null
|
|
171
|
-
?
|
|
188
|
+
? `${report.llm.active} (ok)`
|
|
172
189
|
: report.llm.anyAvailable
|
|
173
|
-
?
|
|
174
|
-
:
|
|
175
|
-
log.info(`
|
|
176
|
-
for (const p of report.llm.providers) {
|
|
177
|
-
const summary = p.summary.length > 56 ? `${p.summary.slice(0, 53)}…` : p.summary;
|
|
178
|
-
log.info(` ${dim(p.id.padEnd(14))} ${p.ok ? green("ok") : dim("—")} ${dim(summary)}`);
|
|
179
|
-
}
|
|
180
|
-
if (!report.llm.anyAvailable) {
|
|
181
|
-
log.info(` ${dim("Setup")} ${cyan("vibeops llm connect")}`);
|
|
182
|
-
}
|
|
190
|
+
? "(preference not available)"
|
|
191
|
+
: "none";
|
|
192
|
+
log.info(` ${dim("LLM")} ${active} · prefer ${report.llm.preference}${report.llm.anyAvailable ? "" : ` · ${cyan("vibeops llm connect")}`}`);
|
|
183
193
|
}
|
|
194
|
+
}
|
|
195
|
+
function printHuman(report) {
|
|
196
|
+
log.blank();
|
|
197
|
+
log.info(bold("─".repeat(44)));
|
|
198
|
+
log.info(bold(" VibeOps · status"));
|
|
199
|
+
log.info(bold("─".repeat(44)));
|
|
200
|
+
log.blank();
|
|
201
|
+
printNow(report);
|
|
202
|
+
printNext(report);
|
|
203
|
+
printDetails(report);
|
|
184
204
|
log.blank();
|
|
185
|
-
log.info(
|
|
186
|
-
log.info(bold("─".repeat(56)));
|
|
205
|
+
log.info(bold("─".repeat(44)));
|
|
187
206
|
log.blank();
|
|
188
207
|
}
|
|
189
208
|
export async function statusCommand(opts = {}) {
|