@goodtek/vibeops 0.7.22 → 1.1.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 +51 -0
- package/README.md +51 -380
- package/dist/bootstrap/installer.js +42 -20
- package/dist/bootstrap/manifest.js +23 -3
- package/dist/cli.js +69 -154
- package/dist/commands/init.js +279 -114
- package/dist/commands/llm.js +177 -0
- package/dist/commands/status.js +181 -8
- package/dist/commands/task-add.js +89 -162
- package/dist/commands/task-done.js +112 -73
- package/dist/lib/config.js +77 -113
- package/dist/lib/env.js +32 -0
- package/dist/lib/git-config.js +21 -0
- package/dist/lib/git-host.js +12 -0
- package/dist/lib/git-policy.js +67 -0
- package/dist/lib/git-remote.js +108 -0
- package/dist/lib/git.js +44 -1
- package/dist/lib/init-clients.js +55 -0
- package/dist/lib/llm-complete.js +18 -19
- package/dist/lib/llm-connect.js +150 -0
- package/dist/lib/llm-preference.js +102 -0
- package/dist/lib/llm-status.js +19 -0
- package/dist/lib/paths.js +7 -3
- package/dist/lib/plan-llm-openai.js +0 -63
- package/dist/lib/pr-create.js +66 -0
- package/dist/lib/resolve-task.js +34 -0
- package/dist/lib/task-add-llm.js +53 -233
- package/dist/lib/task-context.js +68 -0
- package/dist/lib/task-done-llm.js +155 -0
- package/dist/lib/task-pr-llm.js +50 -0
- package/dist/lib/task-push-pr.js +131 -0
- package/dist/lib/task-scaffold.js +39 -43
- package/dist/lib/task-start.js +100 -0
- package/dist/lib/task-summary.js +12 -239
- package/dist/lib/task.js +30 -24
- package/dist/types/config.js +0 -16
- package/package.json +14 -14
- package/templates/.cursor/rules/02-git-safety.mdc +12 -0
- package/templates/AGENTS.md +27 -87
- package/templates/clients/claude/CLAUDE.md +28 -0
- package/templates/clients/cursor/.cursor/rules/01-task-source-of-truth.mdc +12 -0
- package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +11 -0
- package/templates/clients/cursor/.cursor/rules/03-docs-on-done.mdc +13 -0
- package/templates/core/AGENTS.md +44 -0
- package/templates/core/docs/logs/README.md +7 -0
- package/templates/core/docs/project/03-architecture.md +15 -0
- package/templates/core/docs/project/05-current-state.md +15 -0
- package/templates/core/docs/project/06-decisions.md +9 -0
- package/templates/core/docs/tasks/TASK-000-template.md +37 -0
- package/templates/skills-shared/implement-task/SKILL.md +22 -0
- package/templates/skills-shared/plan-task/SKILL.md +19 -0
- 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-merge.js +0 -220
- 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/logs/README.md +0 -38
- 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/03-architecture.md +0 -34
- package/templates/docs/project/04-tech-stack.md +0 -29
- package/templates/docs/project/05-current-state.md +0 -35
- package/templates/docs/project/06-decisions.md +0 -20
- 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/templates/docs/tasks/TASK-000-template.md +0 -72
package/dist/commands/status.js
CHANGED
|
@@ -1,12 +1,185 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import { readConfig, getClientsFromConfig } from "../lib/config.js";
|
|
3
|
+
import { formatGitPolicySummary } from "../lib/git-policy.js";
|
|
4
|
+
import { readText } from "../lib/filesystem.js";
|
|
5
|
+
import { readGitInfo } from "../lib/git.js";
|
|
6
|
+
import { buildLlmStatusReport } from "../lib/llm-status.js";
|
|
7
|
+
import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
|
|
8
|
+
import { projectPaths } from "../lib/paths.js";
|
|
9
|
+
import { VERSION } from "../version.js";
|
|
10
|
+
import { computeNextHint, hintToText, listInProgressTasks, pickFocusTask, relPath, taskBranchExistsFor, } from "../lib/task-context.js";
|
|
11
|
+
import { countTasks, hasNonEmptySection, isOnTaskBranch, loadActionableTasks, readGitContext, statusDisplay } from "../lib/task.js";
|
|
12
|
+
import { summarizeGoal } from "../lib/task-summary.js";
|
|
13
|
+
async function buildReport(cwd) {
|
|
14
|
+
const paths = projectPaths(cwd);
|
|
15
|
+
const config = await readConfig(cwd);
|
|
16
|
+
const git = await readGitInfo(cwd);
|
|
17
|
+
const isVibeopsProject = config !== null;
|
|
18
|
+
let counts = null;
|
|
19
|
+
let inProgress = [];
|
|
20
|
+
let focus = null;
|
|
21
|
+
if (isVibeopsProject) {
|
|
22
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
23
|
+
counts = countTasks(tasks);
|
|
24
|
+
inProgress = await listInProgressTasks(paths);
|
|
25
|
+
focus = await pickFocusTask(paths, cwd);
|
|
26
|
+
}
|
|
27
|
+
let focusBlock = null;
|
|
28
|
+
let resultFilled = false;
|
|
29
|
+
let testFilled = false;
|
|
30
|
+
let onTaskBranch = null;
|
|
31
|
+
let taskBranch = null;
|
|
32
|
+
let branchExists = null;
|
|
33
|
+
let goalExcerpt = "";
|
|
34
|
+
let mergeRequestUrl = null;
|
|
35
|
+
if (focus !== null) {
|
|
36
|
+
const body = await readText(focus.filePath);
|
|
37
|
+
goalExcerpt = summarizeGoal(body);
|
|
38
|
+
resultFilled = hasNonEmptySection(body, "Result");
|
|
39
|
+
testFilled = hasNonEmptySection(body, "Test Result");
|
|
40
|
+
const ctx = await readGitContext(focus.filePath);
|
|
41
|
+
taskBranch = ctx?.taskBranch ?? null;
|
|
42
|
+
mergeRequestUrl = ctx?.mergeRequestUrl ?? null;
|
|
43
|
+
onTaskBranch = isOnTaskBranch(git.branch, ctx);
|
|
44
|
+
branchExists = await taskBranchExistsFor(cwd, focus);
|
|
45
|
+
focusBlock = {
|
|
46
|
+
id: focus.id,
|
|
47
|
+
title: focus.title,
|
|
48
|
+
status: statusDisplay(focus.status),
|
|
49
|
+
file: relPath(cwd, focus.filePath),
|
|
50
|
+
goalExcerpt,
|
|
51
|
+
resultFilled,
|
|
52
|
+
testResultFilled: testFilled,
|
|
53
|
+
onTaskBranch,
|
|
54
|
+
taskBranch,
|
|
55
|
+
taskBranchExists: branchExists,
|
|
56
|
+
mergeRequestUrl,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const hint = computeNextHint({
|
|
60
|
+
isVibeopsProject,
|
|
61
|
+
focus,
|
|
62
|
+
resultFilled,
|
|
63
|
+
testFilled,
|
|
64
|
+
onTaskBranch,
|
|
65
|
+
});
|
|
66
|
+
const llm = isVibeopsProject ? await buildLlmStatusReport(cwd) : null;
|
|
67
|
+
return {
|
|
68
|
+
vibeopsVersion: VERSION,
|
|
69
|
+
isVibeopsProject,
|
|
70
|
+
projectName: config?.name ?? null,
|
|
71
|
+
clients: getClientsFromConfig(config),
|
|
72
|
+
git: {
|
|
73
|
+
isRepo: git.isRepo,
|
|
74
|
+
branch: git.branch,
|
|
75
|
+
dirty: git.dirty,
|
|
76
|
+
policy: config?.git ? formatGitPolicySummary(config.git) : null,
|
|
77
|
+
},
|
|
78
|
+
counts,
|
|
79
|
+
inProgress: inProgress.map((t) => ({
|
|
80
|
+
id: t.id,
|
|
81
|
+
title: t.title,
|
|
82
|
+
file: relPath(cwd, t.filePath),
|
|
83
|
+
})),
|
|
84
|
+
focus: focusBlock,
|
|
85
|
+
nextHint: hintToText(hint, cwd, focus),
|
|
86
|
+
llm,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function printHuman(report) {
|
|
90
|
+
log.blank();
|
|
91
|
+
log.info(bold("─".repeat(56)));
|
|
92
|
+
log.info(bold(" VibeOps · status"));
|
|
93
|
+
log.info(bold("─".repeat(56)));
|
|
94
|
+
log.info(` ${dim("CLI")} v${report.vibeopsVersion}`);
|
|
95
|
+
if (!report.isVibeopsProject) {
|
|
96
|
+
log.info(` ${dim("Project")} ${yellow("not initialized")} — run vibeops init`);
|
|
97
|
+
log.info(bold("─".repeat(56)));
|
|
98
|
+
log.blank();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
log.info(` ${dim("Project")} ${report.projectName ?? "(unnamed)"}`);
|
|
102
|
+
if (report.clients.length > 0) {
|
|
103
|
+
log.info(` ${dim("Clients")} ${report.clients.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
if (report.counts) {
|
|
106
|
+
const c = report.counts;
|
|
107
|
+
log.info(` ${dim("Tasks")} ${c.total} total · ${cyan(String(c.in_progress))} in progress · ${c.done} done`);
|
|
108
|
+
}
|
|
109
|
+
if (report.inProgress.length > 1) {
|
|
110
|
+
log.blank();
|
|
111
|
+
log.info(bold(" In progress"));
|
|
112
|
+
for (const t of report.inProgress) {
|
|
113
|
+
log.info(` ${cyan(t.id)} — ${t.title}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
log.blank();
|
|
117
|
+
if (report.focus) {
|
|
118
|
+
const f = report.focus;
|
|
119
|
+
log.info(` ${dim("Focus")} ${cyan(f.id)} — ${f.title || dim("(no title)")}`);
|
|
120
|
+
log.info(` ${dim("Status")} ${f.status}`);
|
|
121
|
+
log.info(` ${dim("File")} ${f.file}`);
|
|
122
|
+
if (f.goalExcerpt)
|
|
123
|
+
log.info(` ${dim("Goal")} ${f.goalExcerpt}`);
|
|
124
|
+
log.info(` ${dim("Result")} ${f.resultFilled ? green("filled") : yellow("empty")} · Test Result ${f.testResultFilled ? green("filled") : yellow("empty")}`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
log.info(` ${dim("Focus")} ${dim("(none)")}`);
|
|
128
|
+
}
|
|
129
|
+
log.blank();
|
|
130
|
+
log.info(bold(" Git"));
|
|
131
|
+
if (!report.git.isRepo) {
|
|
132
|
+
log.info(` ${yellow("not a git repository")}`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
log.info(` ${dim("HEAD")} ${report.git.branch ?? "(detached)"}`);
|
|
136
|
+
if (report.git.dirty === true)
|
|
137
|
+
log.info(` ${dim("Tree")} ${yellow("dirty")}`);
|
|
138
|
+
else if (report.git.dirty === false)
|
|
139
|
+
log.info(` ${dim("Tree")} ${green("clean")}`);
|
|
140
|
+
if (report.git.policy) {
|
|
141
|
+
log.info(` ${dim("Policy")} ${report.git.policy}`);
|
|
142
|
+
}
|
|
143
|
+
if (report.focus?.taskBranch) {
|
|
144
|
+
const on = report.focus.onTaskBranch === true
|
|
145
|
+
? green("yes")
|
|
146
|
+
: report.focus.onTaskBranch === false
|
|
147
|
+
? yellow("no")
|
|
148
|
+
: dim("—");
|
|
149
|
+
log.info(` ${dim("Task br.")} ${report.focus.taskBranch} · on branch: ${on}`);
|
|
150
|
+
}
|
|
151
|
+
if (report.focus?.mergeRequestUrl) {
|
|
152
|
+
log.info(` ${dim("MR/PR")} ${report.focus.mergeRequestUrl}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (report.llm) {
|
|
156
|
+
log.blank();
|
|
157
|
+
log.info(bold(" LLM"));
|
|
158
|
+
log.info(` ${dim("Preferred")} ${report.llm.preference}`);
|
|
159
|
+
const active = report.llm.active !== null
|
|
160
|
+
? green(`${report.llm.active} (ok)`)
|
|
161
|
+
: report.llm.anyAvailable
|
|
162
|
+
? yellow("(preference not available)")
|
|
163
|
+
: yellow("none");
|
|
164
|
+
log.info(` ${dim("Active")} ${active}`);
|
|
165
|
+
for (const p of report.llm.providers) {
|
|
166
|
+
const summary = p.summary.length > 56 ? `${p.summary.slice(0, 53)}…` : p.summary;
|
|
167
|
+
log.info(` ${dim(p.id.padEnd(14))} ${p.ok ? green("ok") : dim("—")} ${dim(summary)}`);
|
|
168
|
+
}
|
|
169
|
+
if (!report.llm.anyAvailable) {
|
|
170
|
+
log.info(` ${dim("Setup")} ${cyan("vibeops llm connect")}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
log.blank();
|
|
174
|
+
log.info(`${dim("Next")} ${report.nextHint}`);
|
|
175
|
+
log.info(bold("─".repeat(56)));
|
|
176
|
+
log.blank();
|
|
177
|
+
}
|
|
178
|
+
export async function statusCommand(opts = {}) {
|
|
179
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
180
|
+
const report = await buildReport(cwd);
|
|
181
|
+
if (opts.json) {
|
|
182
|
+
log.raw(JSON.stringify(report, null, 2));
|
|
10
183
|
}
|
|
11
184
|
else {
|
|
12
185
|
printHuman(report);
|
|
@@ -1,55 +1,29 @@
|
|
|
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 { askInput
|
|
3
|
+
import { GitConfigError, requireGitConfig } from "../lib/git-config.js";
|
|
4
|
+
import { askInput } from "../lib/inquirer-helpers.js";
|
|
5
|
+
import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
|
|
8
6
|
import { projectPaths } from "../lib/paths.js";
|
|
9
|
-
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
|
|
17
|
-
|
|
18
|
-
|
|
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;
|
|
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";
|
|
13
|
+
async function loadGitConfigOrNull(root, dryRun) {
|
|
14
|
+
try {
|
|
15
|
+
return await requireGitConfig(root);
|
|
32
16
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
catch (e) {
|
|
18
|
+
if (e instanceof GitConfigError) {
|
|
19
|
+
if (dryRun)
|
|
20
|
+
return null;
|
|
21
|
+
log.error(e.message);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw e;
|
|
39
26
|
}
|
|
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
27
|
}
|
|
54
28
|
export async function taskAddCommand(opts = {}) {
|
|
55
29
|
const root = resolve(opts.cwd ?? process.cwd());
|
|
@@ -57,144 +31,97 @@ export async function taskAddCommand(opts = {}) {
|
|
|
57
31
|
const nonInteractive = opts.nonInteractive === true || dryRun || process.stdin.isTTY !== true;
|
|
58
32
|
log.info(bold("vibeops task add"));
|
|
59
33
|
log.blank();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
const gitCfg = await loadGitConfigOrNull(root, dryRun);
|
|
35
|
+
if (!dryRun && gitCfg === null)
|
|
36
|
+
return;
|
|
37
|
+
const paths = projectPaths(root);
|
|
38
|
+
const blocking = await findBlockingTask(paths, root);
|
|
39
|
+
if (blocking !== null) {
|
|
40
|
+
const ctx = await readGitContext(blocking.filePath);
|
|
41
|
+
log.warn(`${yellow("Blocked")} — ${bold(blocking.id)} is still open (${blocking.title || "no title"}).`);
|
|
42
|
+
if (ctx) {
|
|
43
|
+
log.info(` ${dim("branch")} ${ctx.taskBranch}`);
|
|
44
|
+
}
|
|
45
|
+
log.info(` ${dim("file")} ${relPath(root, blocking.filePath)}`);
|
|
46
|
+
log.blank();
|
|
47
|
+
log.info(`Finish it first: ${cyan(`vibeops task done ${blocking.id}`)}`);
|
|
48
|
+
log.info(`Then run ${cyan("vibeops task add")} again.`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
67
51
|
}
|
|
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");
|
|
52
|
+
const ideaDefault = "New work slice";
|
|
74
53
|
const idea = await askInput({
|
|
75
|
-
message:
|
|
76
|
-
? "What do you want to build? (starting point for planning)"
|
|
77
|
-
: "What are you doing now? (short)",
|
|
54
|
+
message: "What are you doing now? (short)",
|
|
78
55
|
nonInteractive,
|
|
79
|
-
default: ideaDefault,
|
|
56
|
+
default: opts.idea?.trim() || ideaDefault,
|
|
80
57
|
required: true,
|
|
81
58
|
});
|
|
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);
|
|
59
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
60
|
+
const taskId = formatTaskId(allocateNextTaskNumber(tasks));
|
|
61
|
+
let title;
|
|
62
|
+
let slug;
|
|
63
|
+
let markdown;
|
|
64
|
+
if (dryRun || nonInteractive) {
|
|
65
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
66
|
+
title = fb.title;
|
|
67
|
+
slug = fb.slug;
|
|
68
|
+
markdown = fb.markdown;
|
|
120
69
|
}
|
|
121
70
|
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}`;
|
|
71
|
+
const llm = await llmScaffoldTask({ cwd: root, taskId, idea });
|
|
72
|
+
if (llm !== null) {
|
|
73
|
+
title = llm.title;
|
|
74
|
+
slug = llm.slug;
|
|
75
|
+
markdown = llm.markdown;
|
|
76
|
+
log.skip(`Scaffold via ${llm.provider}`);
|
|
136
77
|
}
|
|
137
78
|
else {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
spawnedFrom: parent?.id,
|
|
145
|
-
});
|
|
146
|
-
if (!nonInteractive) {
|
|
147
|
-
log.warn("LLM unavailable — using minimal TASK template.");
|
|
148
|
-
}
|
|
79
|
+
log.warn("LLM unavailable — using minimal TASK template.");
|
|
80
|
+
log.info(dim(` Run ${cyan("vibeops llm connect")} to set up Codex, Cursor Agent CLI, or OpenAI.`));
|
|
81
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
82
|
+
title = fb.title;
|
|
83
|
+
slug = fb.slug;
|
|
84
|
+
markdown = fb.markdown;
|
|
149
85
|
}
|
|
150
86
|
}
|
|
151
|
-
const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slug, tasks.map((t) => t.filePath));
|
|
152
|
-
const relFile =
|
|
153
|
-
const
|
|
154
|
-
const buildRel = taskBuildPromptRel(taskId);
|
|
155
|
-
const buildAbs = taskBuildPromptAbs(root, taskId);
|
|
156
|
-
if (buildPromptMarkdown === null) {
|
|
157
|
-
buildPromptMarkdown = defaultBuildPromptForTask(projectName, taskId, taskRelative);
|
|
158
|
-
}
|
|
87
|
+
const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slugify(slug || title), tasks.map((t) => t.filePath));
|
|
88
|
+
const relFile = relPath(root, filePath);
|
|
89
|
+
const integration = gitCfg?.integrationBranch ?? "integration";
|
|
159
90
|
if (dryRun) {
|
|
160
91
|
log.info(`[dry-run] Would create ${bold(taskId)} → ${cyan(relFile)}`);
|
|
161
|
-
|
|
162
|
-
|
|
92
|
+
log.info(dim(` branch task/${slugify(slug || title).replace(/^(\d+)-/, "$1-")} from ${integration}`));
|
|
93
|
+
if (gitCfg) {
|
|
94
|
+
await startTaskBranch({
|
|
95
|
+
cwd: root,
|
|
96
|
+
taskFile: filePath,
|
|
97
|
+
integrationBranch: gitCfg.integrationBranch,
|
|
98
|
+
remote: gitCfg.remote,
|
|
99
|
+
dryRun: true,
|
|
100
|
+
});
|
|
163
101
|
}
|
|
164
|
-
log.blank();
|
|
165
102
|
return;
|
|
166
103
|
}
|
|
167
104
|
if (await pathExists(filePath)) {
|
|
168
105
|
throw new Error(`TASK file already exists: ${relFile}`);
|
|
169
106
|
}
|
|
170
107
|
await writeText(filePath, markdown);
|
|
171
|
-
await mkdir(join(root, ".vibeops", "generated"), { recursive: true });
|
|
172
|
-
await writeText(buildAbs, buildPromptMarkdown);
|
|
173
108
|
log.ok(`Created ${bold(taskId)} → ${cyan(relFile)}`);
|
|
174
|
-
if (mode === "plan") {
|
|
175
|
-
log.ok(`Cursor build doc → ${cyan(buildRel)}`);
|
|
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;
|
|
183
|
-
}
|
|
184
109
|
log.blank();
|
|
185
|
-
log.step(
|
|
186
|
-
await
|
|
187
|
-
|
|
188
|
-
|
|
110
|
+
log.step("Starting task branch…");
|
|
111
|
+
const started = await startTaskBranch({
|
|
112
|
+
cwd: root,
|
|
113
|
+
taskFile: filePath,
|
|
114
|
+
integrationBranch: gitCfg.integrationBranch,
|
|
115
|
+
remote: gitCfg.remote,
|
|
116
|
+
allowDirty: true,
|
|
117
|
+
});
|
|
118
|
+
if (!started) {
|
|
119
|
+
process.exitCode = 1;
|
|
189
120
|
return;
|
|
190
121
|
}
|
|
191
122
|
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
|
-
}
|
|
123
|
+
log.info(bold("Next in Cursor"));
|
|
124
|
+
log.info(` Ask: @${relFile} — plan Scope / Acceptance Criteria`);
|
|
125
|
+
log.info(` Agent: same file — implement`);
|
|
126
|
+
log.info(` Done: ${cyan(`vibeops task done ${taskId}`)}`);
|
|
200
127
|
}
|