@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -382
  3. package/dist/bootstrap/installer.js +4 -16
  4. package/dist/cli.js +26 -160
  5. package/dist/commands/init.js +3 -3
  6. package/dist/commands/status.js +145 -8
  7. package/dist/commands/task-add.js +64 -164
  8. package/dist/commands/task-done.js +117 -74
  9. package/dist/lib/config.js +2 -118
  10. package/dist/lib/git.js +1 -1
  11. package/dist/lib/paths.js +7 -3
  12. package/dist/lib/plan-llm-openai.js +0 -63
  13. package/dist/lib/resolve-task.js +34 -0
  14. package/dist/lib/task-add-llm.js +46 -233
  15. package/dist/lib/task-context.js +68 -0
  16. package/dist/lib/task-done-llm.js +148 -0
  17. package/dist/lib/task-merge.js +54 -159
  18. package/dist/lib/task-scaffold.js +39 -43
  19. package/dist/lib/task-start.js +72 -0
  20. package/dist/lib/task-summary.js +12 -239
  21. package/dist/lib/task.js +15 -24
  22. package/dist/types/config.js +0 -16
  23. package/package.json +3 -5
  24. package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  25. package/templates/.cursor/rules/02-git-safety.mdc +11 -0
  26. package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
  27. package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
  28. package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
  29. package/templates/AGENTS.md +26 -87
  30. package/templates/docs/logs/README.md +4 -35
  31. package/templates/docs/project/03-architecture.md +10 -29
  32. package/templates/docs/project/05-current-state.md +7 -27
  33. package/templates/docs/project/06-decisions.md +6 -17
  34. package/templates/docs/tasks/TASK-000-template.md +9 -44
  35. package/dist/agent/loader.js +0 -71
  36. package/dist/agent/prompt.js +0 -66
  37. package/dist/commands/agent-list.js +0 -32
  38. package/dist/commands/agent-prompt.js +0 -59
  39. package/dist/commands/agent-show.js +0 -26
  40. package/dist/commands/done.js +0 -160
  41. package/dist/commands/github-init.js +0 -554
  42. package/dist/commands/github-status.js +0 -164
  43. package/dist/commands/next.js +0 -283
  44. package/dist/commands/notion-init.js +0 -764
  45. package/dist/commands/notion-sync.js +0 -405
  46. package/dist/commands/notion-test.js +0 -595
  47. package/dist/commands/plan.js +0 -146
  48. package/dist/commands/rollback.js +0 -126
  49. package/dist/commands/start.js +0 -129
  50. package/dist/commands/task-check.js +0 -155
  51. package/dist/commands/task-generate.js +0 -206
  52. package/dist/commands/task-pull.js +0 -277
  53. package/dist/commands/task-rollback.js +0 -174
  54. package/dist/commands/task-start.js +0 -102
  55. package/dist/commands/task-status.js +0 -122
  56. package/dist/lib/brief.js +0 -356
  57. package/dist/lib/cursor-task-prompt.js +0 -60
  58. package/dist/lib/done-follow-up.js +0 -308
  59. package/dist/lib/github-cli.js +0 -247
  60. package/dist/lib/guide-state.js +0 -69
  61. package/dist/lib/mvp-artifacts.js +0 -204
  62. package/dist/lib/mvp-constants.js +0 -15
  63. package/dist/lib/mvp-diff-summary.js +0 -43
  64. package/dist/lib/mvp-done-summary.js +0 -335
  65. package/dist/lib/notion-client.js +0 -459
  66. package/dist/lib/notion-discovery.js +0 -671
  67. package/dist/lib/notion-env.js +0 -140
  68. package/dist/lib/notion-mappers.js +0 -148
  69. package/dist/lib/notion-schema.js +0 -272
  70. package/dist/lib/notion-sync.js +0 -337
  71. package/dist/lib/notion-target.js +0 -247
  72. package/dist/lib/package-json.js +0 -133
  73. package/dist/lib/plan-apply-planner.js +0 -131
  74. package/dist/lib/plan-codex-pkce.js +0 -234
  75. package/dist/lib/plan-llm-models.js +0 -237
  76. package/dist/lib/plan-llm-normalize.js +0 -146
  77. package/dist/lib/plan-llm-session.js +0 -682
  78. package/dist/lib/plan-post-apply-setup.js +0 -116
  79. package/dist/lib/project-docs.js +0 -95
  80. package/dist/lib/prompt-builder.js +0 -135
  81. package/dist/lib/resolve-command-task.js +0 -40
  82. package/dist/lib/task-add-build-prompt.js +0 -39
  83. package/dist/lib/task-add-cursor.js +0 -81
  84. package/dist/lib/task-generator.js +0 -183
  85. package/dist/lib/task-prompt.js +0 -23
  86. package/dist/lib/task-pull.js +0 -354
  87. package/dist/lib/task-status-brief.js +0 -132
  88. package/dist/lib/workflow-guide.js +0 -485
  89. package/dist/status/collector.js +0 -103
  90. package/dist/status/format.js +0 -181
  91. package/dist/types/brief.js +0 -126
  92. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  93. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  94. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  95. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  96. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  97. package/templates/.vibeops/agents/architect.md +0 -47
  98. package/templates/.vibeops/agents/builder.md +0 -38
  99. package/templates/.vibeops/agents/docs.md +0 -54
  100. package/templates/.vibeops/agents/orchestrator.md +0 -40
  101. package/templates/.vibeops/agents/planner.md +0 -60
  102. package/templates/.vibeops/agents/recovery.md +0 -49
  103. package/templates/.vibeops/agents/reviewer.md +0 -47
  104. package/templates/.vibeops/agents/tester.md +0 -43
  105. package/templates/.vibeops/prompts/create-plan.md +0 -33
  106. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  107. package/templates/.vibeops/prompts/implement-task.md +0 -39
  108. package/templates/.vibeops/prompts/review-task.md +0 -34
  109. package/templates/.vibeops/prompts/rollback.md +0 -32
  110. package/templates/.vibeops/prompts/start-project.md +0 -39
  111. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  112. package/templates/.vibeops/workflows/project-start.md +0 -73
  113. package/templates/.vibeops/workflows/rollback.md +0 -45
  114. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  115. package/templates/docs/project/00-overview.md +0 -27
  116. package/templates/docs/project/01-requirements.md +0 -30
  117. package/templates/docs/project/02-mvp-scope.md +0 -36
  118. package/templates/docs/project/04-tech-stack.md +0 -29
  119. package/templates/docs/project/07-backlog.md +0 -23
  120. package/templates/docs/project/08-env.md +0 -29
  121. package/templates/docs/project/09-deployment.md +0 -28
@@ -1,132 +0,0 @@
1
- import { join, relative } from "node:path";
2
- import { readConfig } from "./config.js";
3
- import { pathExists, readText } from "./filesystem.js";
4
- import { gitBranchExists, readGitInfo } from "./git.js";
5
- import { MVP_BUILD_PROMPT_REL } from "./mvp-constants.js";
6
- import { taskBuildPromptRel } from "./task-add-build-prompt.js";
7
- import { projectPaths } from "./paths.js";
8
- import { cursorImplementPromptRel } from "./cursor-task-prompt.js";
9
- import { summarizeGoal } from "./task-summary.js";
10
- import { branchNameForTaskFile, countTasks, findAcceptanceCriteria, hasNonEmptySection, isMvpTaskId, isOnTaskBranch, loadActionableTasks, readGitContext, statusDisplay, } from "./task.js";
11
- import { inferGuideStep, } from "./workflow-guide.js";
12
- function relTaskPath(cwd, filePath) {
13
- const r = relative(cwd, filePath).replace(/\\/g, "/");
14
- return r.startsWith("..") ? filePath : r;
15
- }
16
- function toBriefTask(cwd, meta) {
17
- return {
18
- id: meta.id,
19
- title: meta.title,
20
- status: statusDisplay(meta.status),
21
- mvpPhase: meta.mvpPhase,
22
- file: relTaskPath(cwd, meta.filePath),
23
- };
24
- }
25
- async function collectArtifacts(cwd, task) {
26
- const out = [];
27
- if (task === null)
28
- return out;
29
- if (isMvpTaskId(task.id) && (await pathExists(join(cwd, MVP_BUILD_PROMPT_REL)))) {
30
- out.push(MVP_BUILD_PROMPT_REL);
31
- }
32
- const buildRel = taskBuildPromptRel(task.id);
33
- if (await pathExists(join(cwd, buildRel)))
34
- out.push(buildRel);
35
- const implRel = cursorImplementPromptRel(task.id);
36
- if (await pathExists(join(cwd, implRel)))
37
- out.push(implRel);
38
- return out;
39
- }
40
- export async function collectTaskStatusBrief(cwd) {
41
- const paths = projectPaths(cwd);
42
- const config = await readConfig(cwd);
43
- const isVibeopsProject = config !== null;
44
- const emptyWorkflow = {
45
- stepId: "no-mvp",
46
- stepTitle: "No TASK yet",
47
- suggestedCommand: "vibeops plan",
48
- youDo: ["Run planning to create TASK-mvp and the build prompt."],
49
- };
50
- if (!isVibeopsProject) {
51
- const git = await readGitInfo(cwd);
52
- return {
53
- isVibeopsProject: false,
54
- projectName: null,
55
- mode: "greenfield",
56
- activeTask: null,
57
- referenceTask: null,
58
- inProgressTasks: [],
59
- counts: null,
60
- workflow: emptyWorkflow,
61
- git: {
62
- isRepo: git.isRepo,
63
- branch: git.branch,
64
- dirty: git.dirty,
65
- onTaskBranch: null,
66
- expectedTaskBranch: null,
67
- taskBranchExists: null,
68
- baseBranch: null,
69
- startedAt: null,
70
- },
71
- sections: null,
72
- artifacts: [],
73
- };
74
- }
75
- const guideCtx = await inferGuideStep({ cwd, paths, config });
76
- const actionable = await loadActionableTasks(paths.docsTasks);
77
- const counts = countTasks(actionable);
78
- const inProgress = actionable
79
- .filter((t) => t.status === "in_progress")
80
- .map((t) => toBriefTask(cwd, t));
81
- const active = guideCtx.task;
82
- const reference = guideCtx.referenceTask !== null ? toBriefTask(cwd, guideCtx.referenceTask) : null;
83
- let sections = null;
84
- let expectedBranch = null;
85
- let branchExists = null;
86
- let gitCtx = null;
87
- if (active !== null) {
88
- const body = await readText(active.filePath);
89
- gitCtx = await readGitContext(active.filePath);
90
- expectedBranch = branchNameForTaskFile(active.filePath);
91
- branchExists = await gitBranchExists(cwd, expectedBranch);
92
- sections = {
93
- goalExcerpt: summarizeGoal(body, 280),
94
- resultFilled: hasNonEmptySection(body, "Result"),
95
- testResultFilled: hasNonEmptySection(body, "Test Result"),
96
- acceptanceCriteriaCount: findAcceptanceCriteria(body).length,
97
- };
98
- }
99
- const git = await readGitInfo(cwd);
100
- const onTaskBranch = active !== null && gitCtx !== null ? isOnTaskBranch(git.branch, gitCtx) : null;
101
- const artifacts = await collectArtifacts(cwd, active);
102
- return {
103
- isVibeopsProject: true,
104
- projectName: config.name ?? null,
105
- mode: guideCtx.mode,
106
- activeTask: active !== null ? toBriefTask(cwd, active) : null,
107
- referenceTask: reference,
108
- inProgressTasks: inProgress,
109
- counts,
110
- workflow: {
111
- stepId: guideCtx.step.id,
112
- stepTitle: guideCtx.step.title,
113
- suggestedCommand: guideCtx.step.vibeopsCommand,
114
- youDo: guideCtx.step.youDo,
115
- },
116
- git: {
117
- isRepo: git.isRepo,
118
- branch: git.branch,
119
- dirty: git.dirty,
120
- onTaskBranch,
121
- expectedTaskBranch: expectedBranch ?? gitCtx?.taskBranch ?? null,
122
- taskBranchExists: branchExists,
123
- baseBranch: gitCtx?.baseBranch ?? null,
124
- startedAt: gitCtx?.startedAt ?? null,
125
- },
126
- sections,
127
- artifacts,
128
- };
129
- }
130
- export function taskStatusBriefToJson(brief) {
131
- return JSON.stringify(brief, null, 2);
132
- }
@@ -1,485 +0,0 @@
1
- import { basename, join } from "node:path";
2
- import { pathExists } from "./filesystem.js";
3
- import { readText } from "./filesystem.js";
4
- import { gitBranchExists, gitGovernanceOnlyDirty, readGitInfo } from "./git.js";
5
- import { findMvpTaskFile } from "./mvp-artifacts.js";
6
- import { MVP_BUILD_PROMPT_REL, NEXT_TASK_SUGGESTION_REL, } from "./mvp-constants.js";
7
- import { LAST_DONE_SUMMARY_REL } from "./mvp-done-summary.js";
8
- import { cursorImplementPromptRel } from "./cursor-task-prompt.js";
9
- import { branchNameForTaskFile, hasNonEmptySection, isMvpTaskId, isOnTaskBranch, loadActionableTasks, pickActiveTask, pickLatestDoneTask, readGitContext, readTaskFile, } from "./task.js";
10
- export async function resolveMvpTask(paths) {
11
- const file = await findMvpTaskFile(paths.docsTasks);
12
- if (file === null)
13
- return null;
14
- return readTaskFile(file);
15
- }
16
- export async function resolveGuideTask(paths, cwd) {
17
- const mvpFile = await findMvpTaskFile(paths.docsTasks);
18
- if (mvpFile !== null) {
19
- return {
20
- mode: "mvp",
21
- task: await readTaskFile(mvpFile),
22
- referenceTask: null,
23
- };
24
- }
25
- const actionable = await loadActionableTasks(paths.docsTasks);
26
- const next = await pickActiveTask(paths.docsTasks, cwd);
27
- if (next !== null) {
28
- return { mode: "backlog", task: next, referenceTask: null };
29
- }
30
- const referenceTask = pickLatestDoneTask(actionable);
31
- if (actionable.length > 0 && actionable.every((t) => t.status === "done")) {
32
- return { mode: "post-mvp", task: null, referenceTask };
33
- }
34
- if (referenceTask !== null || (await hasPostMvpArtifacts(cwd))) {
35
- return { mode: "post-mvp", task: null, referenceTask };
36
- }
37
- return { mode: "greenfield", task: null, referenceTask: null };
38
- }
39
- async function hasPostMvpArtifacts(cwd) {
40
- return ((await pathExists(join(cwd, LAST_DONE_SUMMARY_REL))) ||
41
- (await pathExists(join(cwd, NEXT_TASK_SUGGESTION_REL))));
42
- }
43
- export async function inferGuideStep(inputs) {
44
- const { cwd, paths, config } = inputs;
45
- const git = await readGitInfo(cwd);
46
- const resolved = await resolveGuideTask(paths, cwd);
47
- if (resolved.mode === "greenfield") {
48
- return {
49
- cwd,
50
- paths,
51
- config,
52
- mode: "greenfield",
53
- task: null,
54
- referenceTask: null,
55
- gitBranch: git.branch,
56
- gitDirty: git.dirty,
57
- step: buildStep("no-mvp", {
58
- mode: "greenfield",
59
- task: null,
60
- referenceTask: null,
61
- body: "",
62
- gitCtx: null,
63
- git,
64
- hasSuggestion: false,
65
- }),
66
- };
67
- }
68
- if (resolved.mode === "post-mvp") {
69
- const hasSuggestion = await pathExists(join(cwd, NEXT_TASK_SUGGESTION_REL));
70
- return {
71
- cwd,
72
- paths,
73
- config,
74
- mode: "post-mvp",
75
- task: null,
76
- referenceTask: resolved.referenceTask,
77
- gitBranch: git.branch,
78
- gitDirty: git.dirty,
79
- step: inputs.forceStepId !== undefined
80
- ? buildStep(inputs.forceStepId, {
81
- mode: "post-mvp",
82
- task: null,
83
- referenceTask: resolved.referenceTask,
84
- body: "",
85
- gitCtx: null,
86
- git,
87
- hasSuggestion,
88
- })
89
- : buildStep("continue-iteration", {
90
- mode: "post-mvp",
91
- task: null,
92
- referenceTask: resolved.referenceTask,
93
- body: "",
94
- gitCtx: null,
95
- git,
96
- hasSuggestion,
97
- }),
98
- };
99
- }
100
- const task = resolved.task;
101
- const body = await readText(task.filePath);
102
- const gitCtx = await readGitContext(task.filePath);
103
- const taskBranch = branchNameForTaskFile(task.filePath);
104
- const branchExists = await gitBranchExists(cwd, taskBranch);
105
- const hasSuggestion = await pathExists(join(cwd, NEXT_TASK_SUGGESTION_REL));
106
- const step = inputs.forceStepId !== undefined
107
- ? buildStep(inputs.forceStepId, {
108
- mode: resolved.mode,
109
- task,
110
- referenceTask: null,
111
- body,
112
- gitCtx,
113
- git,
114
- hasSuggestion,
115
- taskBranch,
116
- branchExists,
117
- })
118
- : await inferStep({
119
- cwd,
120
- mode: resolved.mode,
121
- task,
122
- body,
123
- gitCtx,
124
- git,
125
- hasSuggestion,
126
- taskBranch,
127
- branchExists,
128
- });
129
- return {
130
- cwd,
131
- paths,
132
- config,
133
- mode: resolved.mode,
134
- task,
135
- referenceTask: null,
136
- gitBranch: git.branch,
137
- gitDirty: git.dirty,
138
- step,
139
- };
140
- }
141
- /** Next guide step when the current one is manual (user chose Next). */
142
- export function successorStepId(current, ctx) {
143
- const resultOk = hasNonEmptySection(ctx.body, "Result");
144
- const testOk = hasNonEmptySection(ctx.body, "Test Result");
145
- switch (current) {
146
- case "implement":
147
- if (resultOk && testOk)
148
- return "finish";
149
- if (ctx.gitDirty === true)
150
- return "commit";
151
- return null;
152
- case "commit":
153
- return "finish";
154
- default:
155
- return null;
156
- }
157
- }
158
- async function inferStep(ctx) {
159
- const { mode, task, body, gitCtx, git, hasSuggestion, taskBranch, branchExists } = ctx;
160
- const resultOk = hasNonEmptySection(body, "Result");
161
- const testOk = hasNonEmptySection(body, "Test Result");
162
- const onTaskBranch = git.branch === taskBranch;
163
- if (branchExists && !onTaskBranch) {
164
- return buildStep("checkout-task", {
165
- mode,
166
- task,
167
- referenceTask: null,
168
- body,
169
- gitCtx,
170
- git,
171
- hasSuggestion,
172
- taskBranch,
173
- branchExists,
174
- });
175
- }
176
- if (task.status === "done") {
177
- if (mode === "mvp") {
178
- return buildStep("all-done", { mode, task, referenceTask: null, body, gitCtx, git, hasSuggestion });
179
- }
180
- if (isOnTaskBranch(git.branch, gitCtx)) {
181
- return buildStep("finish", { mode, task, referenceTask: null, body, gitCtx, git, hasSuggestion });
182
- }
183
- return buildStep("continue-iteration", {
184
- mode: "post-mvp",
185
- task,
186
- referenceTask: task,
187
- body,
188
- gitCtx,
189
- git,
190
- hasSuggestion,
191
- });
192
- }
193
- if ((!gitCtx || task.status === "planned") && !branchExists) {
194
- if (git.dirty === true) {
195
- const gov = await gitGovernanceOnlyDirty(ctx.cwd);
196
- if (!gov.onlyGovernance) {
197
- return buildStep("prepare-start", {
198
- mode,
199
- task,
200
- referenceTask: null,
201
- body,
202
- gitCtx,
203
- git,
204
- hasSuggestion,
205
- taskBranch,
206
- branchExists,
207
- });
208
- }
209
- }
210
- return buildStep("start", {
211
- mode,
212
- task,
213
- referenceTask: null,
214
- body,
215
- gitCtx,
216
- git,
217
- hasSuggestion,
218
- taskBranch,
219
- branchExists,
220
- });
221
- }
222
- if (!resultOk || !testOk) {
223
- return buildStep("implement", { mode, task, referenceTask: null, body, gitCtx, git, hasSuggestion });
224
- }
225
- if (git.dirty === true) {
226
- const gov = await gitGovernanceOnlyDirty(ctx.cwd);
227
- if (!gov.onlyGovernance) {
228
- return buildStep("commit", {
229
- mode,
230
- task,
231
- referenceTask: null,
232
- body,
233
- gitCtx,
234
- git,
235
- hasSuggestion,
236
- taskBranch,
237
- branchExists,
238
- });
239
- }
240
- }
241
- return buildStep("finish", { mode, task, referenceTask: null, body, gitCtx, git, hasSuggestion });
242
- }
243
- function buildStep(stepId, ctx) {
244
- const { task, referenceTask, gitCtx, git, hasSuggestion } = ctx;
245
- const id = task?.id ?? referenceTask?.id ?? null;
246
- const isMvp = id !== null && isMvpTaskId(id);
247
- switch (stepId) {
248
- case "no-mvp":
249
- return {
250
- id: "no-mvp",
251
- taskId: null,
252
- title: "No TASK yet",
253
- youDo: ["Run planning to create TASK-mvp and the build prompt."],
254
- vibeopsCommand: "vibeops plan",
255
- shellHints: ['vibeops plan --idea "Your product idea"'],
256
- runnable: false,
257
- };
258
- case "continue-iteration": {
259
- const ref = referenceTask ?? task;
260
- const lines = [];
261
- if (ref) {
262
- lines.push(`Last finished: ${ref.id}${ref.title ? ` — ${ref.title}` : ""}.`);
263
- }
264
- else {
265
- lines.push("MVP / backlog work is complete on this project.");
266
- }
267
- lines.push(`Read ${LAST_DONE_SUMMARY_REL} for what shipped (if present).`);
268
- if (hasSuggestion) {
269
- lines.push(`Review ${NEXT_TASK_SUGGESTION_REL} from the last vibeops done.`);
270
- }
271
- lines.push("Plan the next slice — prior summary is injected into TASK-mvp + mvp-build.md.");
272
- const shellHints = ['vibeops plan --idea "Next feature or slice"'];
273
- if (hasSuggestion) {
274
- shellHints.push(`cat ${NEXT_TASK_SUGGESTION_REL}`);
275
- }
276
- return {
277
- id: "continue-iteration",
278
- taskId: ref?.id ?? null,
279
- title: "Continue after MVP",
280
- youDo: lines,
281
- vibeopsCommand: "vibeops plan",
282
- shellHints,
283
- runnable: false,
284
- };
285
- }
286
- case "prepare-start":
287
- return {
288
- id: "prepare-start",
289
- taskId: id,
290
- title: `Prepare git before ${id}`,
291
- youDo: [
292
- git.branch
293
- ? `On ${git.branch} with uncommitted project files (not just .vibeops/).`
294
- : "Uncommitted project files on the working tree.",
295
- "Commit or stash them before `vibeops start` creates a task branch.",
296
- "If this is TASK-009 (Next.js scaffold), finish with `vibeops done TASK-009` or commit that work first.",
297
- `Optional: vibeops start ${id} --allow-dirty (carries current changes into the new task branch).`,
298
- ].filter((l) => l.length > 0),
299
- vibeopsCommand: null,
300
- shellHints: [
301
- "git status",
302
- 'git stash push -u -m "wip before task branch"',
303
- `vibeops start ${id} --allow-dirty`,
304
- ],
305
- runnable: false,
306
- };
307
- case "start":
308
- return {
309
- id: "start",
310
- taskId: id,
311
- title: isMvp ? "Start MVP branch" : `Start ${id}`,
312
- youDo: isMvp
313
- ? ["Creates task/mvp-* branch and Git Context."]
314
- : [`Creates task branch and Git Context for ${id}.`],
315
- vibeopsCommand: isMvp ? "vibeops start" : `vibeops start ${id}`,
316
- shellHints: [],
317
- runnable: true,
318
- };
319
- case "checkout-task": {
320
- const branch = ctx.taskBranch ?? gitCtx?.taskBranch ?? "task/…";
321
- return {
322
- id: "checkout-task",
323
- taskId: id,
324
- title: `Resume ${id} branch`,
325
- youDo: [
326
- git.branch ? `Current branch: ${git.branch}` : "",
327
- `Switch: git switch ${branch} (or: vibeops start ${id})`,
328
- "Does not recreate the branch if it already exists.",
329
- ].filter((l) => l.length > 0),
330
- vibeopsCommand: isMvp ? "vibeops start" : `vibeops start ${id}`,
331
- shellHints: [`git switch ${branch}`],
332
- runnable: true,
333
- };
334
- }
335
- case "implement": {
336
- const branchNote = gitCtx && git.branch && git.branch !== gitCtx.taskBranch
337
- ? `On ${git.branch} — switch: git switch ${gitCtx.taskBranch}`
338
- : gitCtx
339
- ? `Branch: ${gitCtx.taskBranch}`
340
- : "";
341
- const taskFile = task ? basename(task.filePath) : "TASK file";
342
- const fillResult = !hasNonEmptySection(ctx.body, "Result");
343
- const fillTestResult = !hasNonEmptySection(ctx.body, "Test Result");
344
- const needsCursorPrompt = fillResult || fillTestResult;
345
- const promptRel = task && needsCursorPrompt ? cursorImplementPromptRel(task.id) : null;
346
- const implementLines = isMvp
347
- ? [
348
- branchNote,
349
- `Drag ${MVP_BUILD_PROMPT_REL} into a new chat.`,
350
- ...(needsCursorPrompt && promptRel
351
- ? [
352
- `Result / Test Result empty — in Cursor: @${promptRel} (see ${promptRel}).`,
353
- ]
354
- : ["Meet Acceptance Criteria; fill Result and Test Result."]),
355
- ]
356
- : [
357
- branchNote,
358
- `Open docs/tasks/${taskFile} in Cursor.`,
359
- ...(needsCursorPrompt && promptRel
360
- ? [
361
- `Result / Test Result empty — in Cursor: @${promptRel} or paste the prompt from that file.`,
362
- ]
363
- : ["Meet Acceptance Criteria; fill Result and Test Result."]),
364
- ];
365
- return {
366
- id: "implement",
367
- taskId: id,
368
- title: isMvp ? "Build MVP in Cursor" : `Implement ${id}`,
369
- youDo: implementLines.filter((l) => l.length > 0),
370
- vibeopsCommand: null,
371
- shellHints: promptRel ? [`cat ${promptRel}`] : [],
372
- runnable: false,
373
- };
374
- }
375
- case "commit": {
376
- const doneCmd = isMvp ? "vibeops done" : `vibeops done ${id}`;
377
- return {
378
- id: "commit",
379
- taskId: id,
380
- title: "Commit work on task branch",
381
- youDo: [
382
- "Commit task files (docs/, src/) — not node_modules or .next.",
383
- `Or run ${doneCmd} to commit safe paths and merge.`,
384
- ],
385
- vibeopsCommand: null,
386
- shellHints: [
387
- "git status",
388
- "git add docs/ src/ …",
389
- `git commit -m "feat(${id?.toLowerCase() ?? "task"}): <summary>"`,
390
- doneCmd,
391
- ],
392
- runnable: false,
393
- };
394
- }
395
- case "finish": {
396
- const markedDone = task?.status === "done";
397
- return {
398
- id: "finish",
399
- taskId: id,
400
- title: isMvp
401
- ? "Finish MVP (merge)"
402
- : markedDone
403
- ? `Close out ${id} (merge & sync)`
404
- : `Finish ${id}`,
405
- youDo: markedDone
406
- ? [
407
- "TASK file already says Done — still on the task branch.",
408
- "Run done to commit if needed, merge to base branch, delete task branch.",
409
- "Runs Notion sync when enabled.",
410
- ]
411
- : [
412
- "Requires Result and Test Result.",
413
- isMvp
414
- ? "Marks Done, commits if needed, merges to main, deletes task branch."
415
- : "Marks Done, commits if needed, merges to main when on the task branch.",
416
- "Runs Notion sync when enabled; offers post-MVP cleanup if the tree is still dirty.",
417
- ],
418
- vibeopsCommand: isMvp ? "vibeops done" : `vibeops done ${id}`,
419
- shellHints: [],
420
- runnable: true,
421
- };
422
- }
423
- case "notion-sync":
424
- return {
425
- id: "notion-sync",
426
- taskId: id,
427
- title: "Sync Notion metadata",
428
- youDo: ["Optional — pushes project + TASK metadata to Notion."],
429
- vibeopsCommand: "vibeops notion sync",
430
- shellHints: ["vibeops notion sync --dry-run"],
431
- runnable: true,
432
- };
433
- case "all-done":
434
- return {
435
- id: "all-done",
436
- taskId: id,
437
- title: "MVP complete",
438
- youDo: [
439
- `Read ${LAST_DONE_SUMMARY_REL} for what shipped.`,
440
- "Update docs/project/05-current-state.md if the project stage changed.",
441
- "Next iteration: vibeops plan (prior summary is injected into TASK + mvp-build.md).",
442
- ],
443
- vibeopsCommand: "vibeops plan",
444
- shellHints: hasSuggestion ? [`cat ${NEXT_TASK_SUGGESTION_REL}`] : [],
445
- runnable: false,
446
- };
447
- }
448
- }
449
- /** Higher = later in the TASK workflow (used to keep manual Next advances). */
450
- export function guideStepRank(stepId) {
451
- switch (stepId) {
452
- case "no-mvp":
453
- case "continue-iteration":
454
- return 0;
455
- case "prepare-start":
456
- return 5;
457
- case "start":
458
- case "checkout-task":
459
- return 10;
460
- case "implement":
461
- return 20;
462
- case "commit":
463
- return 30;
464
- case "finish":
465
- case "notion-sync":
466
- return 40;
467
- case "all-done":
468
- return 50;
469
- default:
470
- return 0;
471
- }
472
- }
473
- export function followUpStepId(completed) {
474
- switch (completed) {
475
- case "start":
476
- case "checkout-task":
477
- return "implement";
478
- case "commit":
479
- return "implement";
480
- case "finish":
481
- return "all-done";
482
- default:
483
- return null;
484
- }
485
- }