@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.
Files changed (144) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +51 -380
  3. package/dist/bootstrap/installer.js +42 -20
  4. package/dist/bootstrap/manifest.js +23 -3
  5. package/dist/cli.js +69 -154
  6. package/dist/commands/init.js +279 -114
  7. package/dist/commands/llm.js +177 -0
  8. package/dist/commands/status.js +181 -8
  9. package/dist/commands/task-add.js +89 -162
  10. package/dist/commands/task-done.js +112 -73
  11. package/dist/lib/config.js +77 -113
  12. package/dist/lib/env.js +32 -0
  13. package/dist/lib/git-config.js +21 -0
  14. package/dist/lib/git-host.js +12 -0
  15. package/dist/lib/git-policy.js +67 -0
  16. package/dist/lib/git-remote.js +108 -0
  17. package/dist/lib/git.js +44 -1
  18. package/dist/lib/init-clients.js +55 -0
  19. package/dist/lib/llm-complete.js +18 -19
  20. package/dist/lib/llm-connect.js +150 -0
  21. package/dist/lib/llm-preference.js +102 -0
  22. package/dist/lib/llm-status.js +19 -0
  23. package/dist/lib/paths.js +7 -3
  24. package/dist/lib/plan-llm-openai.js +0 -63
  25. package/dist/lib/pr-create.js +66 -0
  26. package/dist/lib/resolve-task.js +34 -0
  27. package/dist/lib/task-add-llm.js +53 -233
  28. package/dist/lib/task-context.js +68 -0
  29. package/dist/lib/task-done-llm.js +155 -0
  30. package/dist/lib/task-pr-llm.js +50 -0
  31. package/dist/lib/task-push-pr.js +131 -0
  32. package/dist/lib/task-scaffold.js +39 -43
  33. package/dist/lib/task-start.js +100 -0
  34. package/dist/lib/task-summary.js +12 -239
  35. package/dist/lib/task.js +30 -24
  36. package/dist/types/config.js +0 -16
  37. package/package.json +14 -14
  38. package/templates/.cursor/rules/02-git-safety.mdc +12 -0
  39. package/templates/AGENTS.md +27 -87
  40. package/templates/clients/claude/CLAUDE.md +28 -0
  41. package/templates/clients/cursor/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  42. package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +11 -0
  43. package/templates/clients/cursor/.cursor/rules/03-docs-on-done.mdc +13 -0
  44. package/templates/core/AGENTS.md +44 -0
  45. package/templates/core/docs/logs/README.md +7 -0
  46. package/templates/core/docs/project/03-architecture.md +15 -0
  47. package/templates/core/docs/project/05-current-state.md +15 -0
  48. package/templates/core/docs/project/06-decisions.md +9 -0
  49. package/templates/core/docs/tasks/TASK-000-template.md +37 -0
  50. package/templates/skills-shared/implement-task/SKILL.md +22 -0
  51. package/templates/skills-shared/plan-task/SKILL.md +19 -0
  52. package/dist/agent/loader.js +0 -71
  53. package/dist/agent/prompt.js +0 -66
  54. package/dist/commands/agent-list.js +0 -32
  55. package/dist/commands/agent-prompt.js +0 -59
  56. package/dist/commands/agent-show.js +0 -26
  57. package/dist/commands/done.js +0 -160
  58. package/dist/commands/github-init.js +0 -554
  59. package/dist/commands/github-status.js +0 -164
  60. package/dist/commands/next.js +0 -283
  61. package/dist/commands/notion-init.js +0 -764
  62. package/dist/commands/notion-sync.js +0 -405
  63. package/dist/commands/notion-test.js +0 -595
  64. package/dist/commands/plan.js +0 -146
  65. package/dist/commands/rollback.js +0 -126
  66. package/dist/commands/start.js +0 -129
  67. package/dist/commands/task-check.js +0 -155
  68. package/dist/commands/task-generate.js +0 -206
  69. package/dist/commands/task-pull.js +0 -277
  70. package/dist/commands/task-rollback.js +0 -174
  71. package/dist/commands/task-start.js +0 -102
  72. package/dist/commands/task-status.js +0 -122
  73. package/dist/lib/brief.js +0 -356
  74. package/dist/lib/cursor-task-prompt.js +0 -60
  75. package/dist/lib/done-follow-up.js +0 -308
  76. package/dist/lib/github-cli.js +0 -247
  77. package/dist/lib/guide-state.js +0 -69
  78. package/dist/lib/mvp-artifacts.js +0 -204
  79. package/dist/lib/mvp-constants.js +0 -15
  80. package/dist/lib/mvp-diff-summary.js +0 -43
  81. package/dist/lib/mvp-done-summary.js +0 -335
  82. package/dist/lib/notion-client.js +0 -459
  83. package/dist/lib/notion-discovery.js +0 -671
  84. package/dist/lib/notion-env.js +0 -140
  85. package/dist/lib/notion-mappers.js +0 -148
  86. package/dist/lib/notion-schema.js +0 -272
  87. package/dist/lib/notion-sync.js +0 -337
  88. package/dist/lib/notion-target.js +0 -247
  89. package/dist/lib/package-json.js +0 -133
  90. package/dist/lib/plan-apply-planner.js +0 -131
  91. package/dist/lib/plan-codex-pkce.js +0 -234
  92. package/dist/lib/plan-llm-models.js +0 -237
  93. package/dist/lib/plan-llm-normalize.js +0 -146
  94. package/dist/lib/plan-llm-session.js +0 -682
  95. package/dist/lib/plan-post-apply-setup.js +0 -116
  96. package/dist/lib/project-docs.js +0 -95
  97. package/dist/lib/prompt-builder.js +0 -135
  98. package/dist/lib/resolve-command-task.js +0 -40
  99. package/dist/lib/task-add-build-prompt.js +0 -39
  100. package/dist/lib/task-add-cursor.js +0 -81
  101. package/dist/lib/task-generator.js +0 -183
  102. package/dist/lib/task-merge.js +0 -220
  103. package/dist/lib/task-prompt.js +0 -23
  104. package/dist/lib/task-pull.js +0 -354
  105. package/dist/lib/task-status-brief.js +0 -132
  106. package/dist/lib/workflow-guide.js +0 -485
  107. package/dist/status/collector.js +0 -103
  108. package/dist/status/format.js +0 -181
  109. package/dist/types/brief.js +0 -126
  110. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  111. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  112. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  113. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  114. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  115. package/templates/.vibeops/agents/architect.md +0 -47
  116. package/templates/.vibeops/agents/builder.md +0 -38
  117. package/templates/.vibeops/agents/docs.md +0 -54
  118. package/templates/.vibeops/agents/orchestrator.md +0 -40
  119. package/templates/.vibeops/agents/planner.md +0 -60
  120. package/templates/.vibeops/agents/recovery.md +0 -49
  121. package/templates/.vibeops/agents/reviewer.md +0 -47
  122. package/templates/.vibeops/agents/tester.md +0 -43
  123. package/templates/.vibeops/prompts/create-plan.md +0 -33
  124. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  125. package/templates/.vibeops/prompts/implement-task.md +0 -39
  126. package/templates/.vibeops/prompts/review-task.md +0 -34
  127. package/templates/.vibeops/prompts/rollback.md +0 -32
  128. package/templates/.vibeops/prompts/start-project.md +0 -39
  129. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  130. package/templates/.vibeops/workflows/project-start.md +0 -73
  131. package/templates/.vibeops/workflows/rollback.md +0 -45
  132. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  133. package/templates/docs/logs/README.md +0 -38
  134. package/templates/docs/project/00-overview.md +0 -27
  135. package/templates/docs/project/01-requirements.md +0 -30
  136. package/templates/docs/project/02-mvp-scope.md +0 -36
  137. package/templates/docs/project/03-architecture.md +0 -34
  138. package/templates/docs/project/04-tech-stack.md +0 -29
  139. package/templates/docs/project/05-current-state.md +0 -35
  140. package/templates/docs/project/06-decisions.md +0 -20
  141. package/templates/docs/project/07-backlog.md +0 -23
  142. package/templates/docs/project/08-env.md +0 -29
  143. package/templates/docs/project/09-deployment.md +0 -28
  144. package/templates/docs/tasks/TASK-000-template.md +0 -72
@@ -1,12 +1,185 @@
1
1
  import { resolve } from "node:path";
2
- import { collectStatus } from "../status/collector.js";
3
- import { printHuman, toJson } from "../status/format.js";
4
- import { log } from "../lib/logger.js";
5
- export async function statusCommand(options = {}) {
6
- const cwd = resolve(options.cwd ?? process.cwd());
7
- const report = await collectStatus(cwd);
8
- if (options.json) {
9
- log.raw(toJson(report));
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 { mkdir } from "node:fs/promises";
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 { bold, cyan, dim, log } from "../lib/logger.js";
7
- import { askInput, askSelect, yesNoSelect } from "../lib/inquirer-helpers.js";
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 { defaultBuildPromptForTask, gatherTaskPlanViaLlm, llmGenerateQuickTask, } from "../lib/task-add-llm.js";
12
- import { taskBuildPromptAbs, taskBuildPromptRel, } from "../lib/task-add-build-prompt.js";
13
- import { allocateNextTaskNumber, buildWorkNowTaskMarkdown, formatTaskId, titleFromIdea, uniqueTaskPath, } from "../lib/task-scaffold.js";
14
- import { readGitInfo } from "../lib/git.js";
15
- import { loadActionableTasks, pickInProgressTask, pickLatestDoneTask, } from "../lib/task.js";
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;
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
- 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;
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
- let tasks = await loadActionableTasks(projectPaths(root).docsTasks);
61
- const inProgress = pickInProgressTask(tasks);
62
- if (inProgress !== null) {
63
- const ok = await maybeCloseInProgressTask(root, inProgress, nonInteractive);
64
- if (!ok)
65
- return;
66
- tasks = await loadActionableTasks(projectPaths(root).docsTasks);
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 mode = await pickAddMode(nonInteractive);
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: mode === "plan"
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 paths = projectPaths(root);
83
- const config = await readConfig(root);
84
- const projectName = config?.name ?? "project";
85
- let title = titleFromIdea(idea);
86
- let slug = slugify(title);
87
- let markdown = "";
88
- let buildPromptMarkdown = null;
89
- let llmNote = null;
90
- if (mode === "plan") {
91
- if (dryRun) {
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
- let quick = null;
123
- if (!nonInteractive || opts.idea) {
124
- quick = await llmGenerateQuickTask({
125
- cwd: root,
126
- taskId,
127
- idea,
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
- const parent = inProgress ?? latestDone;
139
- markdown = buildWorkNowTaskMarkdown({
140
- id: taskId,
141
- title,
142
- idea,
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
- }
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 = relOrAbs(root, filePath);
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
- }
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
- if (mode === "plan") {
162
- log.info(`[dry-run] Would write ${cyan(buildRel)}`);
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(`Starting ${taskId} (branch + In Progress)…`);
186
- await startCommand(taskId, { cwd: root, allowDirty: true });
187
- if (process.exitCode !== undefined && process.exitCode !== 0) {
188
- log.warn(`TASK file exists but start failed — run ${cyan(`vibeops start ${taskId}`)} when Git is ready.`);
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
- if (mode === "plan") {
193
- log.info(bold("Build in Cursor"));
194
- log.info(` Drag ${cyan(buildRel)} into a new chat and implement.`);
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
  }