@goodtek/vibeops 0.7.22 → 1.0.0
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 +21 -1
- package/README.md +62 -382
- package/dist/bootstrap/installer.js +4 -16
- package/dist/cli.js +26 -160
- package/dist/commands/init.js +3 -3
- package/dist/commands/status.js +145 -8
- package/dist/commands/task-add.js +64 -164
- package/dist/commands/task-done.js +117 -74
- package/dist/lib/config.js +2 -118
- package/dist/lib/git.js +1 -1
- package/dist/lib/paths.js +7 -3
- package/dist/lib/plan-llm-openai.js +0 -63
- package/dist/lib/resolve-task.js +34 -0
- package/dist/lib/task-add-llm.js +46 -233
- package/dist/lib/task-context.js +68 -0
- package/dist/lib/task-done-llm.js +148 -0
- package/dist/lib/task-merge.js +54 -159
- package/dist/lib/task-scaffold.js +39 -43
- package/dist/lib/task-start.js +72 -0
- package/dist/lib/task-summary.js +12 -239
- package/dist/lib/task.js +15 -24
- package/dist/types/config.js +0 -16
- package/package.json +3 -5
- package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
- package/templates/.cursor/rules/02-git-safety.mdc +11 -0
- package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
- package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
- package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
- package/templates/AGENTS.md +26 -87
- package/templates/docs/logs/README.md +4 -35
- package/templates/docs/project/03-architecture.md +10 -29
- package/templates/docs/project/05-current-state.md +7 -27
- package/templates/docs/project/06-decisions.md +6 -17
- package/templates/docs/tasks/TASK-000-template.md +9 -44
- package/dist/agent/loader.js +0 -71
- package/dist/agent/prompt.js +0 -66
- package/dist/commands/agent-list.js +0 -32
- package/dist/commands/agent-prompt.js +0 -59
- package/dist/commands/agent-show.js +0 -26
- package/dist/commands/done.js +0 -160
- package/dist/commands/github-init.js +0 -554
- package/dist/commands/github-status.js +0 -164
- package/dist/commands/next.js +0 -283
- package/dist/commands/notion-init.js +0 -764
- package/dist/commands/notion-sync.js +0 -405
- package/dist/commands/notion-test.js +0 -595
- package/dist/commands/plan.js +0 -146
- package/dist/commands/rollback.js +0 -126
- package/dist/commands/start.js +0 -129
- package/dist/commands/task-check.js +0 -155
- package/dist/commands/task-generate.js +0 -206
- package/dist/commands/task-pull.js +0 -277
- package/dist/commands/task-rollback.js +0 -174
- package/dist/commands/task-start.js +0 -102
- package/dist/commands/task-status.js +0 -122
- package/dist/lib/brief.js +0 -356
- package/dist/lib/cursor-task-prompt.js +0 -60
- package/dist/lib/done-follow-up.js +0 -308
- package/dist/lib/github-cli.js +0 -247
- package/dist/lib/guide-state.js +0 -69
- package/dist/lib/mvp-artifacts.js +0 -204
- package/dist/lib/mvp-constants.js +0 -15
- package/dist/lib/mvp-diff-summary.js +0 -43
- package/dist/lib/mvp-done-summary.js +0 -335
- package/dist/lib/notion-client.js +0 -459
- package/dist/lib/notion-discovery.js +0 -671
- package/dist/lib/notion-env.js +0 -140
- package/dist/lib/notion-mappers.js +0 -148
- package/dist/lib/notion-schema.js +0 -272
- package/dist/lib/notion-sync.js +0 -337
- package/dist/lib/notion-target.js +0 -247
- package/dist/lib/package-json.js +0 -133
- package/dist/lib/plan-apply-planner.js +0 -131
- package/dist/lib/plan-codex-pkce.js +0 -234
- package/dist/lib/plan-llm-models.js +0 -237
- package/dist/lib/plan-llm-normalize.js +0 -146
- package/dist/lib/plan-llm-session.js +0 -682
- package/dist/lib/plan-post-apply-setup.js +0 -116
- package/dist/lib/project-docs.js +0 -95
- package/dist/lib/prompt-builder.js +0 -135
- package/dist/lib/resolve-command-task.js +0 -40
- package/dist/lib/task-add-build-prompt.js +0 -39
- package/dist/lib/task-add-cursor.js +0 -81
- package/dist/lib/task-generator.js +0 -183
- package/dist/lib/task-prompt.js +0 -23
- package/dist/lib/task-pull.js +0 -354
- package/dist/lib/task-status-brief.js +0 -132
- package/dist/lib/workflow-guide.js +0 -485
- package/dist/status/collector.js +0 -103
- package/dist/status/format.js +0 -181
- package/dist/types/brief.js +0 -126
- package/templates/.cursor/rules/00-project-governance.mdc +0 -28
- package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
- package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
- package/templates/.cursor/rules/03-git-safety.mdc +0 -30
- package/templates/.cursor/rules/04-docs-update.mdc +0 -22
- package/templates/.vibeops/agents/architect.md +0 -47
- package/templates/.vibeops/agents/builder.md +0 -38
- package/templates/.vibeops/agents/docs.md +0 -54
- package/templates/.vibeops/agents/orchestrator.md +0 -40
- package/templates/.vibeops/agents/planner.md +0 -60
- package/templates/.vibeops/agents/recovery.md +0 -49
- package/templates/.vibeops/agents/reviewer.md +0 -47
- package/templates/.vibeops/agents/tester.md +0 -43
- package/templates/.vibeops/prompts/create-plan.md +0 -33
- package/templates/.vibeops/prompts/generate-tasks.md +0 -41
- package/templates/.vibeops/prompts/implement-task.md +0 -39
- package/templates/.vibeops/prompts/review-task.md +0 -34
- package/templates/.vibeops/prompts/rollback.md +0 -32
- package/templates/.vibeops/prompts/start-project.md +0 -39
- package/templates/.vibeops/workflows/notion-sync.md +0 -53
- package/templates/.vibeops/workflows/project-start.md +0 -73
- package/templates/.vibeops/workflows/rollback.md +0 -45
- package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
- package/templates/docs/project/00-overview.md +0 -27
- package/templates/docs/project/01-requirements.md +0 -30
- package/templates/docs/project/02-mvp-scope.md +0 -36
- package/templates/docs/project/04-tech-stack.md +0 -29
- package/templates/docs/project/07-backlog.md +0 -23
- package/templates/docs/project/08-env.md +0 -29
- package/templates/docs/project/09-deployment.md +0 -28
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { doneCommand } from "./commands/done.js";
|
|
4
|
-
import { githubInitCommand } from "./commands/github-init.js";
|
|
5
|
-
import { githubStatusCommand } from "./commands/github-status.js";
|
|
6
3
|
import { initCommand } from "./commands/init.js";
|
|
7
|
-
import { nextCommand } from "./commands/next.js";
|
|
8
|
-
import { notionInitCommand } from "./commands/notion-init.js";
|
|
9
|
-
import { notionSyncCommand } from "./commands/notion-sync.js";
|
|
10
|
-
import { notionTestCommand } from "./commands/notion-test.js";
|
|
11
|
-
import { planCommand } from "./commands/plan.js";
|
|
12
|
-
import { rollbackCommand } from "./commands/rollback.js";
|
|
13
|
-
import { startCommand } from "./commands/start.js";
|
|
14
4
|
import { statusCommand } from "./commands/status.js";
|
|
15
5
|
import { taskAddCommand } from "./commands/task-add.js";
|
|
16
|
-
import {
|
|
6
|
+
import { taskDoneCommand } from "./commands/task-done.js";
|
|
17
7
|
import { VERSION } from "./version.js";
|
|
18
8
|
const program = new Command();
|
|
19
9
|
program
|
|
20
10
|
.name("vibeops")
|
|
21
|
-
.description("VibeOps —
|
|
22
|
-
" Commands: init · plan · start · done · next · status · task add · task status (+ optional notion · github · rollback)")
|
|
11
|
+
.description("VibeOps v3 — Cursor-friendly TASK workflow (init · task add · task done · status)")
|
|
23
12
|
.version(VERSION, "-v, --version", "Print the VibeOps version");
|
|
24
13
|
program
|
|
25
14
|
.command("init")
|
|
26
|
-
.description("Install the VibeOps
|
|
27
|
-
.option("--dry-run", "Show what would be created
|
|
15
|
+
.description("Install the VibeOps v3 project tree")
|
|
16
|
+
.option("--dry-run", "Show what would be created")
|
|
28
17
|
.option("--force", "Overwrite existing files")
|
|
29
18
|
.option("--cwd <path>", "Target directory")
|
|
30
19
|
.option("--name <projectName>", "Project name in .vibeops.json")
|
|
31
|
-
.option("--git", "Initialize Git
|
|
20
|
+
.option("--git", "Initialize Git")
|
|
32
21
|
.option("--no-git", "Skip Git")
|
|
33
22
|
.option("--initial-commit", "Create initial commit")
|
|
34
23
|
.option("--no-initial-commit", "Skip initial commit")
|
|
@@ -37,80 +26,13 @@ program
|
|
|
37
26
|
.action(async (opts) => {
|
|
38
27
|
await initCommand(opts);
|
|
39
28
|
});
|
|
40
|
-
program
|
|
41
|
-
.command("plan")
|
|
42
|
-
.description("LLM planning → brief + TASK-mvp + .vibeops/generated/mvp-build.md")
|
|
43
|
-
.option("--idea <text>", "One-line idea (optional Name: idea prefix)")
|
|
44
|
-
.option("--from <path>", "Regenerate MVP artifacts from an existing brief")
|
|
45
|
-
.option("--non-interactive", "Placeholders only (CI); no LLM")
|
|
46
|
-
.option("--provider <id>", "openai | codex-oauth | cursor-agent")
|
|
47
|
-
.option("--model <id>", "LLM model id")
|
|
48
|
-
.option("--cwd <path>", "Target directory")
|
|
49
|
-
.action(async (opts) => {
|
|
50
|
-
const provider = opts.provider === "openai" ||
|
|
51
|
-
opts.provider === "codex-oauth" ||
|
|
52
|
-
opts.provider === "cursor-agent"
|
|
53
|
-
? opts.provider
|
|
54
|
-
: undefined;
|
|
55
|
-
await planCommand({
|
|
56
|
-
idea: opts.idea,
|
|
57
|
-
from: opts.from,
|
|
58
|
-
nonInteractive: opts.nonInteractive,
|
|
59
|
-
provider,
|
|
60
|
-
llmModel: opts.model,
|
|
61
|
-
cwd: opts.cwd,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
program
|
|
65
|
-
.command("start [taskRef]")
|
|
66
|
-
.description("Create task branch (default: TASK-mvp, or active backlog TASK if no MVP file)")
|
|
67
|
-
.option("--dry-run", "Show plan only")
|
|
68
|
-
.option("--allow-dirty", "Allow dirty working tree")
|
|
69
|
-
.option("--cwd <path>", "Target directory")
|
|
70
|
-
.action(async (taskRef, opts) => {
|
|
71
|
-
await startCommand(taskRef, opts);
|
|
72
|
-
});
|
|
73
|
-
program
|
|
74
|
-
.command("done [taskRef]")
|
|
75
|
-
.description("Finish TASK: auto-fill Result/Test Result + summary md, merge to main, Notion sync (default: TASK-mvp or active backlog)")
|
|
76
|
-
.option("--dry-run", "Show plan only")
|
|
77
|
-
.option("--no-merge", "Skip push / merge / branch delete")
|
|
78
|
-
.option("--skip-summary", "Skip last-done-summary.md and TASK auto-fill")
|
|
79
|
-
.option("--refresh-task-sections", "Overwrite existing Result / Test Result")
|
|
80
|
-
.option("--merge-via-pr", "Merge via gh PR")
|
|
81
|
-
.option("--allow-dirty", "Allow dirty tree during merge")
|
|
82
|
-
.option("--no-notion-sync", "Skip automatic Notion sync after done")
|
|
83
|
-
.option("--cwd <path>", "Target directory")
|
|
84
|
-
.action(async (taskRef, opts) => {
|
|
85
|
-
await doneCommand(taskRef, opts);
|
|
86
|
-
});
|
|
87
|
-
program
|
|
88
|
-
.command("next")
|
|
89
|
-
.description("Interactive guide for the MVP workflow (↑/↓ · Yes/No)")
|
|
90
|
-
.option("--dry-run", "On runnable steps, print only")
|
|
91
|
-
.option("--non-interactive", "Print panel once")
|
|
92
|
-
.option("--execute", "With --non-interactive, run the runnable step once")
|
|
93
|
-
.option("--merge-via-pr", "On finish: merge via GitHub PR")
|
|
94
|
-
.option("--allow-dirty", "Allow dirty tree on merge")
|
|
95
|
-
.option("--cwd <path>", "Target directory")
|
|
96
|
-
.action(async (opts) => {
|
|
97
|
-
await nextCommand(opts);
|
|
98
|
-
});
|
|
99
|
-
program
|
|
100
|
-
.command("status")
|
|
101
|
-
.description("Installation, TASK-mvp state, Git, Notion, GitHub")
|
|
102
|
-
.option("--json", "JSON output")
|
|
103
|
-
.option("--cwd <path>", "Target directory")
|
|
104
|
-
.action(async (opts) => {
|
|
105
|
-
await statusCommand(opts);
|
|
106
|
-
});
|
|
107
|
-
const task = program.command("task").description("Backlog TASK files");
|
|
29
|
+
const task = program.command("task").description("TASK lifecycle");
|
|
108
30
|
task
|
|
109
31
|
.command("add")
|
|
110
|
-
.description("
|
|
111
|
-
.option("--dry-run", "
|
|
112
|
-
.option("--non-interactive", "CI: minimal scaffold
|
|
113
|
-
.option("--idea <text>", "Short description (CI
|
|
32
|
+
.description("Create next TASK-NNN, task branch, In Progress (guide-only if one is open)")
|
|
33
|
+
.option("--dry-run", "Plan only")
|
|
34
|
+
.option("--non-interactive", "CI: minimal scaffold (optional --idea)")
|
|
35
|
+
.option("--idea <text>", "Short description (CI)")
|
|
114
36
|
.option("--cwd <path>", "Target directory")
|
|
115
37
|
.action(async (opts) => {
|
|
116
38
|
await taskAddCommand({
|
|
@@ -121,87 +43,31 @@ task
|
|
|
121
43
|
});
|
|
122
44
|
});
|
|
123
45
|
task
|
|
124
|
-
.command("
|
|
125
|
-
.description("
|
|
126
|
-
.option("--
|
|
46
|
+
.command("done [taskRef]")
|
|
47
|
+
.description("Close TASK: LLM summary, project memory, merge")
|
|
48
|
+
.option("--dry-run", "Plan only")
|
|
49
|
+
.option("--no-merge", "Skip merge")
|
|
50
|
+
.option("--allow-dirty", "Allow dirty tree during merge")
|
|
51
|
+
.option("--yes", "Merge without prompting")
|
|
127
52
|
.option("--cwd <path>", "Target directory")
|
|
128
|
-
.action(async (opts) => {
|
|
129
|
-
await
|
|
130
|
-
|
|
53
|
+
.action(async (taskRef, opts) => {
|
|
54
|
+
await taskDoneCommand(taskRef, {
|
|
55
|
+
dryRun: Boolean(opts.dryRun),
|
|
56
|
+
noMerge: Boolean(opts.noMerge),
|
|
57
|
+
allowDirty: Boolean(opts.allowDirty),
|
|
58
|
+
yes: Boolean(opts.yes),
|
|
131
59
|
cwd: opts.cwd,
|
|
132
60
|
});
|
|
133
61
|
});
|
|
134
62
|
program
|
|
135
|
-
.command("rollback [taskRef]")
|
|
136
|
-
.description("Advisory rollback for TASK-mvp (--confirm / --confirm-destructive to execute)")
|
|
137
|
-
.option("--confirm", "Run non-destructive rollback")
|
|
138
|
-
.option("--confirm-destructive", "Allow destructive rollback (reset --hard)")
|
|
139
|
-
.option("--strategy <name>", "branch-delete | reset-base | revert-merge")
|
|
140
|
-
.option("--keep-branch", "Keep task branch when deleting")
|
|
141
|
-
.option("--dry-run", "Print plan only")
|
|
142
|
-
.option("--cwd <path>", "Target directory")
|
|
143
|
-
.action(async (taskRef, opts) => {
|
|
144
|
-
await rollbackCommand(taskRef, opts);
|
|
145
|
-
});
|
|
146
|
-
const notion = program.command("notion").description("Optional Notion dashboard sync");
|
|
147
|
-
notion
|
|
148
|
-
.command("init")
|
|
149
|
-
.description("Configure Notion in .vibeops.json and .vibeops.env")
|
|
150
|
-
.option("--dry-run", "Plan only")
|
|
151
|
-
.option("--enable", "Enable Notion")
|
|
152
|
-
.option("--projects-db <id>", "Projects database id")
|
|
153
|
-
.option("--tasks-db <id>", "Tasks database id")
|
|
154
|
-
.option("--non-interactive", "No prompts")
|
|
155
|
-
.option("--cwd <path>", "Target directory")
|
|
156
|
-
.action(async (opts) => {
|
|
157
|
-
await notionInitCommand(opts);
|
|
158
|
-
});
|
|
159
|
-
notion
|
|
160
|
-
.command("test")
|
|
161
|
-
.description("Verify Notion token and database schema")
|
|
162
|
-
.option("--json", "JSON output")
|
|
163
|
-
.option("--debug-shape", "Print API shape diagnostics")
|
|
164
|
-
.option("--cwd <path>", "Target directory")
|
|
165
|
-
.action(async (opts) => {
|
|
166
|
-
await notionTestCommand(opts);
|
|
167
|
-
});
|
|
168
|
-
notion
|
|
169
|
-
.command("sync")
|
|
170
|
-
.description("Push docs/project + TASK metadata to Notion")
|
|
171
|
-
.option("--dry-run", "Query only")
|
|
172
|
-
.option("--json", "JSON output")
|
|
173
|
-
.option("--only-tasks", "Tasks DB only")
|
|
174
|
-
.option("--only-project", "Project DB only")
|
|
175
|
-
.option("--cwd <path>", "Target directory")
|
|
176
|
-
.action(async (opts) => {
|
|
177
|
-
await notionSyncCommand(opts);
|
|
178
|
-
});
|
|
179
|
-
const github = program.command("github").description("Optional GitHub remote setup");
|
|
180
|
-
github
|
|
181
63
|
.command("status")
|
|
182
|
-
.description("
|
|
64
|
+
.description("Briefing: active TASK, Git, next hint")
|
|
183
65
|
.option("--json", "JSON output")
|
|
184
66
|
.option("--cwd <path>", "Target directory")
|
|
185
67
|
.action(async (opts) => {
|
|
186
|
-
await
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
.command("init")
|
|
190
|
-
.description("Connect or create a GitHub repo and update config")
|
|
191
|
-
.option("--dry-run", "Plan only")
|
|
192
|
-
.option("--yes", "Non-interactive defaults")
|
|
193
|
-
.option("--owner <owner>", "GitHub owner")
|
|
194
|
-
.option("--repo <repo>", "Repo name")
|
|
195
|
-
.option("--public", "Public repo")
|
|
196
|
-
.option("--private", "Private repo")
|
|
197
|
-
.option("--remote <name>", "Remote name (default origin)")
|
|
198
|
-
.option("--connect <ownerOrUrl>", "Existing repo")
|
|
199
|
-
.option("--no-package-update", "Skip package.json repo fields")
|
|
200
|
-
.option("--cwd <path>", "Target directory")
|
|
201
|
-
.action(async (opts) => {
|
|
202
|
-
await githubInitCommand({
|
|
203
|
-
...opts,
|
|
204
|
-
noPackageUpdate: opts.packageUpdate === false,
|
|
68
|
+
await statusCommand({
|
|
69
|
+
json: Boolean(opts.json),
|
|
70
|
+
cwd: opts.cwd,
|
|
205
71
|
});
|
|
206
72
|
});
|
|
207
73
|
program.parseAsync(process.argv).catch((err) => {
|
package/dist/commands/init.js
CHANGED
|
@@ -34,9 +34,9 @@ export async function initCommand(options = {}) {
|
|
|
34
34
|
if (!dryRun) {
|
|
35
35
|
log.blank();
|
|
36
36
|
log.info("Next steps:");
|
|
37
|
-
log.info(" 1.
|
|
38
|
-
log.info(" 2. Run `vibeops
|
|
39
|
-
log.info(" 3.
|
|
37
|
+
log.info(" 1. Read AGENTS.md and `.cursor/rules/`.");
|
|
38
|
+
log.info(" 2. Run `vibeops task add` to create the first TASK.");
|
|
39
|
+
log.info(" 3. Plan in Cursor Ask (@docs/tasks/TASK-001-….md), then implement.");
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
async function resolveGitPlan(options, interactive) {
|
package/dist/commands/status.js
CHANGED
|
@@ -1,12 +1,149 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import { readConfig } from "../lib/config.js";
|
|
3
|
+
import { readText } from "../lib/filesystem.js";
|
|
4
|
+
import { readGitInfo } from "../lib/git.js";
|
|
5
|
+
import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
|
|
6
|
+
import { projectPaths } from "../lib/paths.js";
|
|
7
|
+
import { VERSION } from "../version.js";
|
|
8
|
+
import { computeNextHint, hintToText, listInProgressTasks, pickFocusTask, relPath, taskBranchExistsFor, } from "../lib/task-context.js";
|
|
9
|
+
import { countTasks, hasNonEmptySection, isOnTaskBranch, loadActionableTasks, readGitContext, statusDisplay } from "../lib/task.js";
|
|
10
|
+
import { summarizeGoal } from "../lib/task-summary.js";
|
|
11
|
+
async function buildReport(cwd) {
|
|
12
|
+
const paths = projectPaths(cwd);
|
|
13
|
+
const config = await readConfig(cwd);
|
|
14
|
+
const git = await readGitInfo(cwd);
|
|
15
|
+
const isVibeopsProject = config !== null;
|
|
16
|
+
let counts = null;
|
|
17
|
+
let inProgress = [];
|
|
18
|
+
let focus = null;
|
|
19
|
+
if (isVibeopsProject) {
|
|
20
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
21
|
+
counts = countTasks(tasks);
|
|
22
|
+
inProgress = await listInProgressTasks(paths);
|
|
23
|
+
focus = await pickFocusTask(paths, cwd);
|
|
24
|
+
}
|
|
25
|
+
let focusBlock = null;
|
|
26
|
+
let resultFilled = false;
|
|
27
|
+
let testFilled = false;
|
|
28
|
+
let onTaskBranch = null;
|
|
29
|
+
let taskBranch = null;
|
|
30
|
+
let branchExists = null;
|
|
31
|
+
let goalExcerpt = "";
|
|
32
|
+
if (focus !== null) {
|
|
33
|
+
const body = await readText(focus.filePath);
|
|
34
|
+
goalExcerpt = summarizeGoal(body);
|
|
35
|
+
resultFilled = hasNonEmptySection(body, "Result");
|
|
36
|
+
testFilled = hasNonEmptySection(body, "Test Result");
|
|
37
|
+
const ctx = await readGitContext(focus.filePath);
|
|
38
|
+
taskBranch = ctx?.taskBranch ?? null;
|
|
39
|
+
onTaskBranch = isOnTaskBranch(git.branch, ctx);
|
|
40
|
+
branchExists = await taskBranchExistsFor(cwd, focus);
|
|
41
|
+
focusBlock = {
|
|
42
|
+
id: focus.id,
|
|
43
|
+
title: focus.title,
|
|
44
|
+
status: statusDisplay(focus.status),
|
|
45
|
+
file: relPath(cwd, focus.filePath),
|
|
46
|
+
goalExcerpt,
|
|
47
|
+
resultFilled,
|
|
48
|
+
testResultFilled: testFilled,
|
|
49
|
+
onTaskBranch,
|
|
50
|
+
taskBranch,
|
|
51
|
+
taskBranchExists: branchExists,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const hint = computeNextHint({
|
|
55
|
+
isVibeopsProject,
|
|
56
|
+
focus,
|
|
57
|
+
resultFilled,
|
|
58
|
+
testFilled,
|
|
59
|
+
onTaskBranch,
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
vibeopsVersion: VERSION,
|
|
63
|
+
isVibeopsProject,
|
|
64
|
+
projectName: config?.name ?? null,
|
|
65
|
+
git: {
|
|
66
|
+
isRepo: git.isRepo,
|
|
67
|
+
branch: git.branch,
|
|
68
|
+
dirty: git.dirty,
|
|
69
|
+
},
|
|
70
|
+
counts,
|
|
71
|
+
inProgress: inProgress.map((t) => ({
|
|
72
|
+
id: t.id,
|
|
73
|
+
title: t.title,
|
|
74
|
+
file: relPath(cwd, t.filePath),
|
|
75
|
+
})),
|
|
76
|
+
focus: focusBlock,
|
|
77
|
+
nextHint: hintToText(hint, cwd, focus),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function printHuman(report) {
|
|
81
|
+
log.blank();
|
|
82
|
+
log.info(bold("─".repeat(56)));
|
|
83
|
+
log.info(bold(" VibeOps · status"));
|
|
84
|
+
log.info(bold("─".repeat(56)));
|
|
85
|
+
log.info(` ${dim("CLI")} v${report.vibeopsVersion}`);
|
|
86
|
+
if (!report.isVibeopsProject) {
|
|
87
|
+
log.info(` ${dim("Project")} ${yellow("not initialized")} — run vibeops init`);
|
|
88
|
+
log.info(bold("─".repeat(56)));
|
|
89
|
+
log.blank();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
log.info(` ${dim("Project")} ${report.projectName ?? "(unnamed)"}`);
|
|
93
|
+
if (report.counts) {
|
|
94
|
+
const c = report.counts;
|
|
95
|
+
log.info(` ${dim("Tasks")} ${c.total} total · ${cyan(String(c.in_progress))} in progress · ${c.done} done`);
|
|
96
|
+
}
|
|
97
|
+
if (report.inProgress.length > 1) {
|
|
98
|
+
log.blank();
|
|
99
|
+
log.info(bold(" In progress"));
|
|
100
|
+
for (const t of report.inProgress) {
|
|
101
|
+
log.info(` ${cyan(t.id)} — ${t.title}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
log.blank();
|
|
105
|
+
if (report.focus) {
|
|
106
|
+
const f = report.focus;
|
|
107
|
+
log.info(` ${dim("Focus")} ${cyan(f.id)} — ${f.title || dim("(no title)")}`);
|
|
108
|
+
log.info(` ${dim("Status")} ${f.status}`);
|
|
109
|
+
log.info(` ${dim("File")} ${f.file}`);
|
|
110
|
+
if (f.goalExcerpt)
|
|
111
|
+
log.info(` ${dim("Goal")} ${f.goalExcerpt}`);
|
|
112
|
+
log.info(` ${dim("Result")} ${f.resultFilled ? green("filled") : yellow("empty")} · Test Result ${f.testResultFilled ? green("filled") : yellow("empty")}`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
log.info(` ${dim("Focus")} ${dim("(none)")}`);
|
|
116
|
+
}
|
|
117
|
+
log.blank();
|
|
118
|
+
log.info(bold(" Git"));
|
|
119
|
+
if (!report.git.isRepo) {
|
|
120
|
+
log.info(` ${yellow("not a git repository")}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
log.info(` ${dim("HEAD")} ${report.git.branch ?? "(detached)"}`);
|
|
124
|
+
if (report.git.dirty === true)
|
|
125
|
+
log.info(` ${dim("Tree")} ${yellow("dirty")}`);
|
|
126
|
+
else if (report.git.dirty === false)
|
|
127
|
+
log.info(` ${dim("Tree")} ${green("clean")}`);
|
|
128
|
+
if (report.focus?.taskBranch) {
|
|
129
|
+
const on = report.focus.onTaskBranch === true
|
|
130
|
+
? green("yes")
|
|
131
|
+
: report.focus.onTaskBranch === false
|
|
132
|
+
? yellow("no")
|
|
133
|
+
: dim("—");
|
|
134
|
+
log.info(` ${dim("Task br.")} ${report.focus.taskBranch} · on branch: ${on}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
log.blank();
|
|
138
|
+
log.info(`${dim("Next")} ${report.nextHint}`);
|
|
139
|
+
log.info(bold("─".repeat(56)));
|
|
140
|
+
log.blank();
|
|
141
|
+
}
|
|
142
|
+
export async function statusCommand(opts = {}) {
|
|
143
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
144
|
+
const report = await buildReport(cwd);
|
|
145
|
+
if (opts.json) {
|
|
146
|
+
log.raw(JSON.stringify(report, null, 2));
|
|
10
147
|
}
|
|
11
148
|
else {
|
|
12
149
|
printHuman(report);
|
|
@@ -1,200 +1,100 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, relative, resolve } from "node:path";
|
|
3
|
-
import { doneCommand } from "./done.js";
|
|
4
|
-
import { startCommand } from "./start.js";
|
|
1
|
+
import { resolve } from "node:path";
|
|
5
2
|
import { pathExists, writeText } from "../lib/filesystem.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { askInput } from "../lib/inquirer-helpers.js";
|
|
4
|
+
import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
|
|
8
5
|
import { projectPaths } from "../lib/paths.js";
|
|
9
6
|
import { readConfig } from "../lib/config.js";
|
|
10
7
|
import { slugify } from "../lib/slug.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { loadActionableTasks,
|
|
16
|
-
function relOrAbs(root, p) {
|
|
17
|
-
const r = relative(root, p);
|
|
18
|
-
return r === "" ? "." : r.startsWith("..") ? p : r;
|
|
19
|
-
}
|
|
20
|
-
async function maybeCloseInProgressTask(root, inProgress, nonInteractive) {
|
|
21
|
-
if (nonInteractive)
|
|
22
|
-
return true;
|
|
23
|
-
log.info(`Current TASK ${bold(inProgress.id)} is ${cyan("In Progress")}: ${dim(inProgress.title)}`);
|
|
24
|
-
log.blank();
|
|
25
|
-
const closeFirst = await yesNoSelect({
|
|
26
|
-
message: "Close this TASK first (commit, merge, clean tree) before starting a new one?",
|
|
27
|
-
defaultValue: true,
|
|
28
|
-
});
|
|
29
|
-
if (!closeFirst) {
|
|
30
|
-
log.warn(`Keeping ${inProgress.id} open — finish it or run ${cyan("vibeops done")} before switching branches.`);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
log.blank();
|
|
34
|
-
log.step(`Closing ${inProgress.id} via vibeops done…`);
|
|
35
|
-
await doneCommand(inProgress.id, { cwd: root, allowDirty: true });
|
|
36
|
-
if (process.exitCode !== undefined && process.exitCode !== 0) {
|
|
37
|
-
log.error("Could not close the current TASK — fix blockers above, then run task add again.");
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
log.blank();
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
async function pickAddMode(nonInteractive) {
|
|
44
|
-
if (nonInteractive)
|
|
45
|
-
return "just";
|
|
46
|
-
const choice = await askSelect({
|
|
47
|
-
message: "How do you want to create the new TASK?",
|
|
48
|
-
nonInteractive: false,
|
|
49
|
-
choices: ["Just create task", "Create plan using LLM"],
|
|
50
|
-
default: "Just create task",
|
|
51
|
-
});
|
|
52
|
-
return choice.startsWith("Create plan") ? "plan" : "just";
|
|
53
|
-
}
|
|
8
|
+
import { fallbackTaskDraft, llmScaffoldTask } from "../lib/task-add-llm.js";
|
|
9
|
+
import { allocateNextTaskNumber, formatTaskId, uniqueTaskPath, } from "../lib/task-scaffold.js";
|
|
10
|
+
import { findBlockingTask, relPath } from "../lib/task-context.js";
|
|
11
|
+
import { startTaskBranch } from "../lib/task-start.js";
|
|
12
|
+
import { loadActionableTasks, readGitContext } from "../lib/task.js";
|
|
54
13
|
export async function taskAddCommand(opts = {}) {
|
|
55
14
|
const root = resolve(opts.cwd ?? process.cwd());
|
|
56
15
|
const dryRun = opts.dryRun === true;
|
|
57
16
|
const nonInteractive = opts.nonInteractive === true || dryRun || process.stdin.isTTY !== true;
|
|
58
17
|
log.info(bold("vibeops task add"));
|
|
59
18
|
log.blank();
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
19
|
+
const paths = projectPaths(root);
|
|
20
|
+
const blocking = await findBlockingTask(paths, root);
|
|
21
|
+
if (blocking !== null) {
|
|
22
|
+
const ctx = await readGitContext(blocking.filePath);
|
|
23
|
+
log.warn(`${yellow("Blocked")} — ${bold(blocking.id)} is still open (${blocking.title || "no title"}).`);
|
|
24
|
+
if (ctx) {
|
|
25
|
+
log.info(` ${dim("branch")} ${ctx.taskBranch}`);
|
|
26
|
+
}
|
|
27
|
+
log.info(` ${dim("file")} ${relPath(root, blocking.filePath)}`);
|
|
28
|
+
log.blank();
|
|
29
|
+
log.info(`Finish it first: ${cyan(`vibeops task done ${blocking.id}`)}`);
|
|
30
|
+
log.info(`Then run ${cyan("vibeops task add")} again.`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
67
33
|
}
|
|
68
|
-
const
|
|
69
|
-
const latestDone = pickLatestDoneTask(tasks);
|
|
70
|
-
const nextNum = allocateNextTaskNumber(tasks);
|
|
71
|
-
const taskId = formatTaskId(nextNum);
|
|
72
|
-
const ideaDefault = opts.idea?.trim() ||
|
|
73
|
-
(latestDone ? `Follow-up after ${latestDone.id}` : "New work slice");
|
|
34
|
+
const ideaDefault = "New work slice";
|
|
74
35
|
const idea = await askInput({
|
|
75
|
-
message:
|
|
76
|
-
? "What do you want to build? (starting point for planning)"
|
|
77
|
-
: "What are you doing now? (short)",
|
|
36
|
+
message: "What are you doing now? (short)",
|
|
78
37
|
nonInteractive,
|
|
79
|
-
default: ideaDefault,
|
|
38
|
+
default: opts.idea?.trim() || ideaDefault,
|
|
80
39
|
required: true,
|
|
81
40
|
});
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
log.info(`[dry-run] Would run LLM planning for ${bold(taskId)}`);
|
|
93
|
-
log.info(dim(` idea: ${idea}`));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (nonInteractive) {
|
|
97
|
-
log.error("Create plan using LLM requires an interactive terminal (omit --non-interactive).");
|
|
98
|
-
process.exitCode = 1;
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
log.step("LLM task planning (interactive questions)…");
|
|
102
|
-
const planned = await gatherTaskPlanViaLlm({
|
|
103
|
-
cwd: root,
|
|
104
|
-
taskId,
|
|
105
|
-
projectName,
|
|
106
|
-
seedIdea: idea,
|
|
107
|
-
});
|
|
108
|
-
if (planned === null) {
|
|
109
|
-
log.error("No LLM provider available for planning.");
|
|
110
|
-
process.exitCode = 1;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
markdown = planned.markdown;
|
|
114
|
-
buildPromptMarkdown = planned.buildPromptMarkdown;
|
|
115
|
-
llmNote = `planned via ${planned.provider}`;
|
|
116
|
-
const header = /^#\s+TASK-\d+:\s*(.+)$/m.exec(markdown);
|
|
117
|
-
if (header?.[1])
|
|
118
|
-
title = header[1].trim();
|
|
119
|
-
slug = slugify(title);
|
|
41
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
42
|
+
const taskId = formatTaskId(allocateNextTaskNumber(tasks));
|
|
43
|
+
let title;
|
|
44
|
+
let slug;
|
|
45
|
+
let markdown;
|
|
46
|
+
if (dryRun || nonInteractive) {
|
|
47
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
48
|
+
title = fb.title;
|
|
49
|
+
slug = fb.slug;
|
|
50
|
+
markdown = fb.markdown;
|
|
120
51
|
}
|
|
121
52
|
else {
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
latestDone,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
if (quick !== null) {
|
|
132
|
-
title = quick.title;
|
|
133
|
-
slug = quick.slug;
|
|
134
|
-
markdown = quick.markdown;
|
|
135
|
-
llmNote = `quick scaffold via ${quick.provider}`;
|
|
53
|
+
const llm = await llmScaffoldTask({ cwd: root, taskId, idea });
|
|
54
|
+
if (llm !== null) {
|
|
55
|
+
title = llm.title;
|
|
56
|
+
slug = llm.slug;
|
|
57
|
+
markdown = llm.markdown;
|
|
58
|
+
log.skip(`Scaffold via ${llm.provider}`);
|
|
136
59
|
}
|
|
137
60
|
else {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
mvpPhase: parent?.mvpPhase?.trim() || "Work-now slice",
|
|
144
|
-
spawnedFrom: parent?.id,
|
|
145
|
-
});
|
|
146
|
-
if (!nonInteractive) {
|
|
147
|
-
log.warn("LLM unavailable — using minimal TASK template.");
|
|
148
|
-
}
|
|
61
|
+
log.warn("LLM unavailable — using minimal TASK template.");
|
|
62
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
63
|
+
title = fb.title;
|
|
64
|
+
slug = fb.slug;
|
|
65
|
+
markdown = fb.markdown;
|
|
149
66
|
}
|
|
150
67
|
}
|
|
151
|
-
const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slug, tasks.map((t) => t.filePath));
|
|
152
|
-
const relFile =
|
|
153
|
-
const taskRelative = relFile.replace(/\\/g, "/");
|
|
154
|
-
const buildRel = taskBuildPromptRel(taskId);
|
|
155
|
-
const buildAbs = taskBuildPromptAbs(root, taskId);
|
|
156
|
-
if (buildPromptMarkdown === null) {
|
|
157
|
-
buildPromptMarkdown = defaultBuildPromptForTask(projectName, taskId, taskRelative);
|
|
158
|
-
}
|
|
68
|
+
const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slugify(slug || title), tasks.map((t) => t.filePath));
|
|
69
|
+
const relFile = relPath(root, filePath);
|
|
159
70
|
if (dryRun) {
|
|
160
71
|
log.info(`[dry-run] Would create ${bold(taskId)} → ${cyan(relFile)}`);
|
|
161
|
-
|
|
162
|
-
log.info(`[dry-run] Would write ${cyan(buildRel)}`);
|
|
163
|
-
}
|
|
164
|
-
log.blank();
|
|
72
|
+
log.info(dim(` branch task/${slugify(slug || title).replace(/^(\d+)-/, "$1-")}`));
|
|
165
73
|
return;
|
|
166
74
|
}
|
|
167
75
|
if (await pathExists(filePath)) {
|
|
168
76
|
throw new Error(`TASK file already exists: ${relFile}`);
|
|
169
77
|
}
|
|
170
78
|
await writeText(filePath, markdown);
|
|
171
|
-
await mkdir(join(root, ".vibeops", "generated"), { recursive: true });
|
|
172
|
-
await writeText(buildAbs, buildPromptMarkdown);
|
|
173
79
|
log.ok(`Created ${bold(taskId)} → ${cyan(relFile)}`);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (llmNote)
|
|
178
|
-
log.skip(llmNote);
|
|
179
|
-
const git = await readGitInfo(root);
|
|
180
|
-
if (!git.isRepo) {
|
|
181
|
-
log.warn("Not a git repository — TASK file saved. Run vibeops init --git, then start.");
|
|
182
|
-
return;
|
|
80
|
+
const config = await readConfig(root);
|
|
81
|
+
if (config === null) {
|
|
82
|
+
log.warn("No .vibeops.json — run vibeops init for full template.");
|
|
183
83
|
}
|
|
184
84
|
log.blank();
|
|
185
|
-
log.step(
|
|
186
|
-
await
|
|
187
|
-
|
|
188
|
-
|
|
85
|
+
log.step("Starting task branch…");
|
|
86
|
+
const started = await startTaskBranch({
|
|
87
|
+
cwd: root,
|
|
88
|
+
taskFile: filePath,
|
|
89
|
+
allowDirty: true,
|
|
90
|
+
});
|
|
91
|
+
if (!started) {
|
|
92
|
+
process.exitCode = 1;
|
|
189
93
|
return;
|
|
190
94
|
}
|
|
191
95
|
log.blank();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
log.info(bold("Work in Cursor"));
|
|
198
|
-
log.info(` Edit ${cyan(relFile)} freely, then ${cyan(`vibeops done ${taskId}`)} when finished.`);
|
|
199
|
-
}
|
|
96
|
+
log.info(bold("Next in Cursor"));
|
|
97
|
+
log.info(` Ask: @${relFile} — plan Scope / Acceptance Criteria`);
|
|
98
|
+
log.info(` Agent: same file — implement`);
|
|
99
|
+
log.info(` Done: ${cyan(`vibeops task done ${taskId}`)}`);
|
|
200
100
|
}
|