@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,102 +0,0 @@
1
- import { relative, resolve } from "node:path";
2
- import { branchNameForTaskFile, findTaskFile, upsertGitContext, updateInlineStatus, } from "../lib/task.js";
3
- import { detectDefaultBranch, gitBranchExists, gitCheckoutNewBranch, gitGovernanceOnlyDirty, gitHeadCommit, readGitInfo, } from "../lib/git.js";
4
- import { bold, cyan, dim, log } from "../lib/logger.js";
5
- import { buildTaskPromptString } from "../lib/task-prompt.js";
6
- import { projectPaths } from "../lib/paths.js";
7
- function relOrAbs(root, p) {
8
- const r = relative(root, p);
9
- return r === "" ? "." : r.startsWith("..") ? p : r;
10
- }
11
- export async function taskStartCommand(taskId, options = {}) {
12
- const cwd = resolve(options.cwd ?? process.cwd());
13
- const paths = projectPaths(cwd);
14
- const agentName = options.agent ?? "builder";
15
- const taskFile = await findTaskFile(paths.docsTasks, taskId);
16
- if (!taskFile) {
17
- log.error(`TASK not found: ${taskId} (looked in ${relOrAbs(cwd, paths.docsTasks)})`);
18
- process.exitCode = 1;
19
- return;
20
- }
21
- const git = await readGitInfo(cwd);
22
- if (!git.isRepo) {
23
- log.error(`Not a git repository: ${cwd}`);
24
- process.exitCode = 1;
25
- return;
26
- }
27
- if (git.dirty === true && options.allowDirty !== true) {
28
- const gov = await gitGovernanceOnlyDirty(cwd);
29
- if (!gov.onlyGovernance) {
30
- log.error("Git working tree is dirty. Commit or stash first, or rerun with --allow-dirty.");
31
- if (gov.nonGovernancePaths.length > 0) {
32
- log.info(dim(" Non-documentation changes:"));
33
- for (const p of gov.nonGovernancePaths)
34
- log.info(` · ${p}`);
35
- }
36
- process.exitCode = 1;
37
- return;
38
- }
39
- log.warn("Uncommitted governance docs only (tasks / project / logs / .vibeops/state). Proceeding — commit when convenient.");
40
- for (const p of gov.allPaths)
41
- log.info(` ${dim("·")} ${p}`);
42
- log.blank();
43
- }
44
- const baseBranch = git.branch ?? (await detectDefaultBranch(cwd)) ?? "main";
45
- const baseCommit = (await gitHeadCommit(cwd)) ?? "";
46
- if (baseCommit.length === 0) {
47
- log.error("Failed to read HEAD commit. Repo has no commits yet?");
48
- process.exitCode = 1;
49
- return;
50
- }
51
- const taskBranch = branchNameForTaskFile(taskFile);
52
- const startedAt = new Date().toISOString();
53
- const ctx = { baseBranch, baseCommit, taskBranch, startedAt };
54
- log.info(bold(`vibeops task start ${taskId}`));
55
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
56
- log.info(` ${dim("base branch")} ${baseBranch}`);
57
- log.info(` ${dim("base commit")} ${baseCommit}`);
58
- log.info(` ${dim("task branch")} ${cyan(taskBranch)}`);
59
- log.info(` ${dim("started at")} ${startedAt}`);
60
- log.blank();
61
- if (options.dryRun === true) {
62
- log.info(bold("dry-run — would perform:"));
63
- log.info(` · git checkout -b ${taskBranch} ${baseBranch}`);
64
- log.info(` · update Status → In Progress in ${relOrAbs(cwd, taskFile)}`);
65
- log.info(` · upsert "## Git Context" section in ${relOrAbs(cwd, taskFile)}`);
66
- log.info(` · build Cursor prompt for agent "${agentName}"`);
67
- log.blank();
68
- log.info(dim("no files were written and no git command was executed."));
69
- return;
70
- }
71
- if (await gitBranchExists(cwd, taskBranch)) {
72
- log.error(`Task branch already exists: ${taskBranch}`);
73
- log.info(` Use a different TASK or delete the existing branch first: \`git branch -D ${taskBranch}\`.`);
74
- process.exitCode = 1;
75
- return;
76
- }
77
- await gitCheckoutNewBranch(cwd, taskBranch, baseBranch);
78
- log.ok(`checked out new branch: ${taskBranch}`);
79
- await updateInlineStatus(taskFile, "in_progress");
80
- await upsertGitContext(taskFile, ctx);
81
- log.ok(`updated ${relOrAbs(cwd, taskFile)} (Status + Git Context)`);
82
- log.blank();
83
- const promptResult = await buildTaskPromptString({
84
- projectRoot: paths.root,
85
- agentsDir: paths.vibeopsAgents,
86
- agentName,
87
- taskFilePath: taskFile,
88
- });
89
- if (!promptResult.ok) {
90
- log.warn(`agent "${agentName}" not found in ${relOrAbs(cwd, paths.vibeopsAgents)} — skipping prompt.`);
91
- if (promptResult.available.length > 0) {
92
- log.info(`Available agents: ${promptResult.available.join(", ")}`);
93
- }
94
- log.info(`Run \`vibeops task prompt ${taskId} --agent <name>\` later to generate the Cursor prompt.`);
95
- return;
96
- }
97
- log.info(bold(`Cursor prompt (agent: ${agentName}):`));
98
- log.info(dim("─".repeat(60)));
99
- log.raw(promptResult.prompt.endsWith("\n") ? promptResult.prompt : `${promptResult.prompt}\n`);
100
- log.info(dim("─".repeat(60)));
101
- log.info(`Copy the block above into Cursor. When done, run \`vibeops task check ${taskId}\`.`);
102
- }
@@ -1,122 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { collectTaskStatusBrief, taskStatusBriefToJson, } from "../lib/task-status-brief.js";
3
- import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
4
- function sectionFilled(ok) {
5
- return ok ? green("filled") : yellow("empty");
6
- }
7
- function printHuman(brief) {
8
- log.blank();
9
- log.info(bold("─".repeat(56)));
10
- log.info(bold(" VibeOps · TASK briefing"));
11
- log.info(bold("─".repeat(56)));
12
- if (!brief.isVibeopsProject) {
13
- log.info(` ${dim("Project")} ${yellow("not a VibeOps project")} (.vibeops.json missing)`);
14
- if (brief.git.isRepo && brief.git.branch) {
15
- log.info(` ${dim("Git")} ${brief.git.branch}`);
16
- }
17
- log.blank();
18
- log.info(` ${dim("Next")} ${cyan("vibeops init")}`);
19
- log.info(bold("─".repeat(56)));
20
- log.blank();
21
- return;
22
- }
23
- if (brief.projectName) {
24
- log.info(` ${dim("Project")} ${brief.projectName}`);
25
- }
26
- if (brief.counts) {
27
- const c = brief.counts;
28
- log.info(` ${dim("Backlog")} ${c.total} tasks · ${cyan(String(c.in_progress))} in progress · ${c.planned} planned · ${c.done} done`);
29
- }
30
- if (brief.inProgressTasks.length > 1) {
31
- log.blank();
32
- log.info(bold(" In progress (multiple)"));
33
- for (const t of brief.inProgressTasks) {
34
- log.info(` ${cyan(t.id)} — ${t.title || dim("(no title)")}`);
35
- }
36
- }
37
- log.blank();
38
- if (brief.activeTask) {
39
- const t = brief.activeTask;
40
- log.info(` ${dim("Focus")} ${cyan(t.id)} — ${t.title || dim("(no title)")}`);
41
- log.info(` ${dim("Status")} ${t.status}`);
42
- if (t.mvpPhase)
43
- log.info(` ${dim("Phase")} ${t.mvpPhase}`);
44
- log.info(` ${dim("File")} ${t.file}`);
45
- }
46
- else if (brief.mode === "post-mvp" && brief.referenceTask) {
47
- log.info(` ${dim("Focus")} ${dim("MVP/backlog complete — last:")} ${cyan(brief.referenceTask.id)}`);
48
- }
49
- else {
50
- log.info(` ${dim("Focus")} ${dim("(none — run vibeops plan or task add)")}`);
51
- }
52
- if (brief.sections) {
53
- log.blank();
54
- log.info(bold(" Progress"));
55
- if (brief.sections.goalExcerpt.length > 0) {
56
- log.info(` ${dim("Goal")} ${brief.sections.goalExcerpt}`);
57
- }
58
- log.info(` ${dim("Result")} ${sectionFilled(brief.sections.resultFilled)} · ${dim("Test Result")} ${sectionFilled(brief.sections.testResultFilled)}`);
59
- if (brief.sections.acceptanceCriteriaCount > 0) {
60
- log.info(` ${dim("AC")} ${brief.sections.acceptanceCriteriaCount} acceptance criteria`);
61
- }
62
- }
63
- log.blank();
64
- log.info(bold(" Git"));
65
- if (!brief.git.isRepo) {
66
- log.info(` ${yellow("not a git repository")}`);
67
- }
68
- else {
69
- const branch = brief.git.branch ?? "(detached)";
70
- log.info(` ${dim("HEAD")} ${branch}`);
71
- if (brief.git.dirty === true)
72
- log.info(` ${dim("Tree")} ${yellow("dirty")}`);
73
- else if (brief.git.dirty === false)
74
- log.info(` ${dim("Tree")} ${green("clean")}`);
75
- if (brief.git.expectedTaskBranch) {
76
- const on = brief.git.onTaskBranch === true
77
- ? green("yes")
78
- : brief.git.onTaskBranch === false
79
- ? yellow("no")
80
- : dim("—");
81
- const exists = brief.git.taskBranchExists === true
82
- ? dim("exists")
83
- : brief.git.taskBranchExists === false
84
- ? dim("not created")
85
- : "";
86
- log.info(` ${dim("Task br.")} ${brief.git.expectedTaskBranch} · on branch: ${on}${exists ? ` · ${exists}` : ""}`);
87
- }
88
- if (brief.git.baseBranch) {
89
- log.info(` ${dim("Base")} ${brief.git.baseBranch}`);
90
- }
91
- }
92
- if (brief.artifacts.length > 0) {
93
- log.blank();
94
- log.info(bold(" Cursor artifacts"));
95
- for (const a of brief.artifacts)
96
- log.info(` ${cyan(a)}`);
97
- }
98
- log.blank();
99
- log.info(bold(` Step: ${brief.workflow.stepTitle}`));
100
- for (const line of brief.workflow.youDo) {
101
- log.info(` ${dim("·")} ${line}`);
102
- }
103
- if (brief.workflow.suggestedCommand) {
104
- log.blank();
105
- log.info(` ${dim("Suggested")} ${cyan(brief.workflow.suggestedCommand)}`);
106
- }
107
- log.info(bold("─".repeat(56)));
108
- log.blank();
109
- }
110
- export async function taskStatusCommand(opts = {}) {
111
- const cwd = resolve(opts.cwd ?? process.cwd());
112
- const brief = await collectTaskStatusBrief(cwd);
113
- if (opts.json === true) {
114
- log.raw(taskStatusBriefToJson(brief));
115
- }
116
- else {
117
- printHuman(brief);
118
- }
119
- if (!brief.isVibeopsProject) {
120
- process.exitCode = 1;
121
- }
122
- }
package/dist/lib/brief.js DELETED
@@ -1,356 +0,0 @@
1
- import { basename } from "node:path";
2
- import { askCheckbox, askConfirm, askInput, askSelect, } from "./inquirer-helpers.js";
3
- import { VERSION } from "../version.js";
4
- import { AGENT_WORKFLOW_CHOICES, AUTH_REQUIREMENT_CHOICES, BACKEND_CHOICES, DATABASE_CHOICES, DB_LAYER_CHOICES, DEPLOYMENT_TARGET_CHOICES, FRONTEND_CHOICES, INTEGRATION_CHOICES, MVP_FEATURE_CHOICES, OUT_OF_SCOPE_CHOICES, PACKAGE_MANAGER_CHOICES, PROJECT_BRIEF_SCHEMA_VERSION, PROJECT_TYPE_CHOICES, RISK_AREA_CHOICES, TARGET_USER_CHOICES, } from "../types/brief.js";
5
- const PLACEHOLDER_PROJECT_NAME = "Unnamed Project";
6
- const PLACEHOLDER_IDEA = "(no idea provided — Planner Agent must fill this in)";
7
- const PLACEHOLDER_PROBLEM = "(no core problem provided — Planner Agent must fill this in)";
8
- const PLACEHOLDER_SUCCESS = "(no success criteria provided — Planner Agent must fill this in)";
9
- export function parseIdea(idea) {
10
- if (typeof idea !== "string")
11
- return {};
12
- const trimmed = idea.trim();
13
- if (trimmed.length === 0)
14
- return {};
15
- const colonIdx = trimmed.indexOf(":");
16
- if (colonIdx > 0 && colonIdx < 40) {
17
- const head = trimmed.slice(0, colonIdx).trim();
18
- const tail = trimmed.slice(colonIdx + 1).trim();
19
- if (head.length > 0 && !head.includes(" ") && tail.length > 0) {
20
- return { projectName: head, oneLineIdea: tail };
21
- }
22
- }
23
- return { oneLineIdea: trimmed };
24
- }
25
- function deriveProjectTypeDefault(idea) {
26
- if (typeof idea === "string" && /browser/i.test(idea)) {
27
- return "Browser Automation";
28
- }
29
- return "SaaS";
30
- }
31
- export async function gatherBrief(inputs) {
32
- const assumptions = [];
33
- const seed = inputs.seed ?? {};
34
- const ideaParsed = parseIdea(inputs.idea);
35
- const defaultName = seed.projectName ?? ideaParsed.projectName ?? basename(inputs.cwd) ?? PLACEHOLDER_PROJECT_NAME;
36
- const projectName = await askInput({
37
- message: "1/20 · Project name",
38
- nonInteractive: inputs.nonInteractive,
39
- default: defaultName,
40
- required: !inputs.nonInteractive,
41
- fallback: PLACEHOLDER_PROJECT_NAME,
42
- });
43
- const finalProjectName = projectName.length > 0 ? projectName : PLACEHOLDER_PROJECT_NAME;
44
- if (finalProjectName === PLACEHOLDER_PROJECT_NAME) {
45
- assumptions.push('projectName: directory name was empty too — defaulted to "Unnamed Project"');
46
- }
47
- const oneLineIdea = await askInput({
48
- message: "2/20 · One-line idea",
49
- nonInteractive: inputs.nonInteractive,
50
- default: seed.oneLineIdea ?? ideaParsed.oneLineIdea,
51
- required: !inputs.nonInteractive,
52
- fallback: PLACEHOLDER_IDEA,
53
- });
54
- const finalIdea = oneLineIdea.length > 0 ? oneLineIdea : PLACEHOLDER_IDEA;
55
- if (finalIdea === PLACEHOLDER_IDEA) {
56
- assumptions.push("oneLineIdea: not provided — placeholder for Planner Agent to fill");
57
- }
58
- const projectType = await askSelect({
59
- message: "3/20 · Project type",
60
- nonInteractive: inputs.nonInteractive,
61
- choices: PROJECT_TYPE_CHOICES,
62
- default: seed.projectType ?? deriveProjectTypeDefault(inputs.idea ?? seed.oneLineIdea),
63
- });
64
- const targetUsers = await askCheckbox({
65
- message: "4/20 · Target users (Space to toggle, Enter to confirm)",
66
- nonInteractive: inputs.nonInteractive,
67
- choices: TARGET_USER_CHOICES,
68
- default: seed.targetUsers,
69
- });
70
- const coreProblem = await askInput({
71
- message: "5/20 · Core problem",
72
- nonInteractive: inputs.nonInteractive,
73
- default: seed.coreProblem,
74
- fallback: PLACEHOLDER_PROBLEM,
75
- });
76
- const finalCoreProblem = coreProblem.length > 0 ? coreProblem : PLACEHOLDER_PROBLEM;
77
- if (finalCoreProblem === PLACEHOLDER_PROBLEM) {
78
- assumptions.push("coreProblem: not provided — placeholder for Planner Agent to fill");
79
- }
80
- const mvpFeatures = await askCheckbox({
81
- message: "6/20 · MVP must-have features",
82
- nonInteractive: inputs.nonInteractive,
83
- choices: MVP_FEATURE_CHOICES,
84
- default: seed.mvpFeatures,
85
- });
86
- const outOfScope = await askCheckbox({
87
- message: "7/20 · Out of scope for MVP",
88
- nonInteractive: inputs.nonInteractive,
89
- choices: OUT_OF_SCOPE_CHOICES,
90
- default: seed.outOfScope,
91
- });
92
- const frontend = await askSelect({
93
- message: "8/20 · Preferred frontend",
94
- nonInteractive: inputs.nonInteractive,
95
- choices: FRONTEND_CHOICES,
96
- default: seed.frontend ?? "Next.js",
97
- });
98
- const backend = await askSelect({
99
- message: "9/20 · Preferred backend",
100
- nonInteractive: inputs.nonInteractive,
101
- choices: BACKEND_CHOICES,
102
- default: seed.backend ?? "NestJS",
103
- });
104
- const database = await askSelect({
105
- message: "10/20 · Database",
106
- nonInteractive: inputs.nonInteractive,
107
- choices: DATABASE_CHOICES,
108
- default: seed.database ?? "PostgreSQL",
109
- });
110
- const dbLayer = await askSelect({
111
- message: "11/20 · ORM / DB layer",
112
- nonInteractive: inputs.nonInteractive,
113
- choices: DB_LAYER_CHOICES,
114
- default: seed.dbLayer ?? "Drizzle",
115
- });
116
- const packageManager = await askSelect({
117
- message: "12/20 · Package manager",
118
- nonInteractive: inputs.nonInteractive,
119
- choices: PACKAGE_MANAGER_CHOICES,
120
- default: seed.packageManager ?? "pnpm",
121
- });
122
- const deploymentTargets = await askCheckbox({
123
- message: "13/20 · Deployment target",
124
- nonInteractive: inputs.nonInteractive,
125
- choices: DEPLOYMENT_TARGET_CHOICES,
126
- default: seed.deploymentTargets,
127
- });
128
- const authRequirements = await askCheckbox({
129
- message: "14/20 · Auth requirement",
130
- nonInteractive: inputs.nonInteractive,
131
- choices: AUTH_REQUIREMENT_CHOICES,
132
- default: seed.authRequirements,
133
- });
134
- const integrations = await askCheckbox({
135
- message: "15/20 · External integrations",
136
- nonInteractive: inputs.nonInteractive,
137
- choices: INTEGRATION_CHOICES,
138
- default: seed.integrations,
139
- });
140
- const useNotion = await askConfirm({
141
- message: "16/20 · Use Notion dashboard sync?",
142
- nonInteractive: inputs.nonInteractive,
143
- default: seed.useNotion ?? true,
144
- });
145
- const useGitWorkflow = await askConfirm({
146
- message: "17/20 · Use Git task branch workflow?",
147
- nonInteractive: inputs.nonInteractive,
148
- default: seed.useGitWorkflow ?? true,
149
- });
150
- const agentWorkflowLevel = await askSelect({
151
- message: "18/20 · Agent workflow level",
152
- nonInteractive: inputs.nonInteractive,
153
- choices: AGENT_WORKFLOW_CHOICES,
154
- default: seed.agentWorkflowLevel ??
155
- "Advanced: Orchestrator + Planner + Architect + Builder + Tester + Reviewer + Docs + Recovery",
156
- });
157
- const risks = await askCheckbox({
158
- message: "19/20 · Risk areas",
159
- nonInteractive: inputs.nonInteractive,
160
- choices: RISK_AREA_CHOICES,
161
- default: seed.risks,
162
- });
163
- const successCriteria = await askInput({
164
- message: "20/20 · Success criteria",
165
- nonInteractive: inputs.nonInteractive,
166
- default: seed.successCriteria,
167
- fallback: PLACEHOLDER_SUCCESS,
168
- });
169
- const finalSuccess = successCriteria.length > 0 ? successCriteria : PLACEHOLDER_SUCCESS;
170
- if (finalSuccess === PLACEHOLDER_SUCCESS) {
171
- assumptions.push("successCriteria: not provided — placeholder for Planner Agent to fill");
172
- }
173
- const brief = {
174
- projectName: finalProjectName,
175
- oneLineIdea: finalIdea,
176
- projectType,
177
- targetUsers,
178
- coreProblem: finalCoreProblem,
179
- mvpFeatures,
180
- outOfScope,
181
- frontend,
182
- backend,
183
- database,
184
- dbLayer,
185
- packageManager,
186
- deploymentTargets,
187
- authRequirements,
188
- integrations,
189
- useNotion,
190
- useGitWorkflow,
191
- agentWorkflowLevel,
192
- risks,
193
- successCriteria: finalSuccess,
194
- };
195
- const meta = {
196
- vibeopsVersion: VERSION,
197
- generatedAt: new Date().toISOString(),
198
- source: inputs.nonInteractive ? "non-interactive" : "interactive",
199
- schemaVersion: PROJECT_BRIEF_SCHEMA_VERSION,
200
- assumptions,
201
- };
202
- return { brief, meta };
203
- }
204
- const SECTIONS = [
205
- { num: 1, title: "Project name", field: "projectName" },
206
- { num: 2, title: "One-line idea", field: "oneLineIdea" },
207
- { num: 3, title: "Project type", field: "projectType" },
208
- { num: 4, title: "Target users", field: "targetUsers" },
209
- { num: 5, title: "Core problem", field: "coreProblem" },
210
- { num: 6, title: "MVP must-have features", field: "mvpFeatures" },
211
- { num: 7, title: "Out of scope for MVP", field: "outOfScope" },
212
- { num: 8, title: "Preferred frontend", field: "frontend" },
213
- { num: 9, title: "Preferred backend", field: "backend" },
214
- { num: 10, title: "Database", field: "database" },
215
- { num: 11, title: "ORM / DB layer", field: "dbLayer" },
216
- { num: 12, title: "Package manager", field: "packageManager" },
217
- { num: 13, title: "Deployment target", field: "deploymentTargets" },
218
- { num: 14, title: "Auth requirement", field: "authRequirements" },
219
- { num: 15, title: "External integrations", field: "integrations" },
220
- { num: 16, title: "Use Notion dashboard sync?", field: "useNotion" },
221
- { num: 17, title: "Use Git task branch workflow?", field: "useGitWorkflow" },
222
- { num: 18, title: "Agent workflow level", field: "agentWorkflowLevel" },
223
- { num: 19, title: "Risk areas", field: "risks" },
224
- { num: 20, title: "Success criteria", field: "successCriteria" },
225
- ];
226
- function renderList(values) {
227
- if (values.length === 0)
228
- return "_(none)_";
229
- return values.map((v) => `- ${v}`).join("\n");
230
- }
231
- function renderScalar(value) {
232
- return value.length > 0 ? value : "_(empty)_";
233
- }
234
- function renderBool(value) {
235
- return value ? "yes" : "no";
236
- }
237
- export function briefToMarkdown(brief, meta) {
238
- const lines = [];
239
- lines.push(`# Project Brief — ${brief.projectName}`);
240
- lines.push("");
241
- lines.push(`> Generated: ${meta.generatedAt} · VibeOps ${meta.vibeopsVersion} · Source: ${meta.source} · schemaVersion: ${meta.schemaVersion}`);
242
- lines.push("");
243
- lines.push(meta.source === "llm-openai" ||
244
- meta.source === "llm-codex-oauth" ||
245
- meta.source === "llm-cursor-agent"
246
- ? meta.source === "llm-codex-oauth"
247
- ? "This brief was produced by an **LLM planning session** using **Codex (ChatGPT OAuth)** (`codex login`, read from ~/.codex/auth.json). Review and edit it, then let the Planner Agent fill `docs/project/*`."
248
- : meta.source === "llm-openai"
249
- ? "This brief was produced by an **LLM planning session** using an **OpenAI platform API key**. Review and edit it, then let the Planner Agent fill `docs/project/*`."
250
- : "This brief was produced by an **LLM planning session** using the **Cursor Agent CLI**. Review and edit it, then let the Planner Agent fill `docs/project/*`."
251
- : "This brief is the input for the Cursor **Planner Agent**. VibeOps collected these answers locally (or from `--from`) — the Planner Agent reads this brief, fills in `docs/project/*`, and creates the initial backlog.");
252
- lines.push("");
253
- for (const sec of SECTIONS) {
254
- lines.push(`## ${sec.num}. ${sec.title}`);
255
- lines.push("");
256
- const value = brief[sec.field];
257
- if (typeof value === "boolean") {
258
- lines.push(renderBool(value));
259
- }
260
- else if (Array.isArray(value)) {
261
- lines.push(renderList(value));
262
- }
263
- else {
264
- lines.push(renderScalar(value));
265
- }
266
- lines.push("");
267
- }
268
- lines.push("## Assumptions");
269
- lines.push("");
270
- if (meta.assumptions.length === 0) {
271
- lines.push("_(none)_");
272
- }
273
- else {
274
- for (const a of meta.assumptions)
275
- lines.push(`- ${a}`);
276
- }
277
- lines.push("");
278
- return lines.join("\n");
279
- }
280
- function extractSection(md, num) {
281
- const re = new RegExp(String.raw `^##\s+${num}\.\s+[^\n]*\n([\s\S]*?)(?=^##\s+|\Z)`, "m");
282
- const match = md.match(re);
283
- return match ? (match[1] ?? "").trim() : "";
284
- }
285
- function parseList(text) {
286
- if (text.length === 0 || text === "_(none)_")
287
- return [];
288
- const items = [];
289
- for (const raw of text.split("\n")) {
290
- const line = raw.trim();
291
- if (line.startsWith("- ")) {
292
- const v = line.slice(2).trim();
293
- if (v.length > 0)
294
- items.push(v);
295
- }
296
- }
297
- return items;
298
- }
299
- function parseScalar(text) {
300
- if (text === "_(empty)_")
301
- return "";
302
- return text.trim();
303
- }
304
- function parseBool(text) {
305
- const t = text.trim().toLowerCase();
306
- return t === "yes" || t === "true" || t === "y";
307
- }
308
- export function parseBriefFromMarkdown(md) {
309
- const brief = {
310
- projectName: parseScalar(extractSection(md, 1)),
311
- oneLineIdea: parseScalar(extractSection(md, 2)),
312
- projectType: parseScalar(extractSection(md, 3)),
313
- targetUsers: parseList(extractSection(md, 4)),
314
- coreProblem: parseScalar(extractSection(md, 5)),
315
- mvpFeatures: parseList(extractSection(md, 6)),
316
- outOfScope: parseList(extractSection(md, 7)),
317
- frontend: parseScalar(extractSection(md, 8)),
318
- backend: parseScalar(extractSection(md, 9)),
319
- database: parseScalar(extractSection(md, 10)),
320
- dbLayer: parseScalar(extractSection(md, 11)),
321
- packageManager: parseScalar(extractSection(md, 12)),
322
- deploymentTargets: parseList(extractSection(md, 13)),
323
- authRequirements: parseList(extractSection(md, 14)),
324
- integrations: parseList(extractSection(md, 15)),
325
- useNotion: parseBool(extractSection(md, 16)),
326
- useGitWorkflow: parseBool(extractSection(md, 17)),
327
- agentWorkflowLevel: parseScalar(extractSection(md, 18)),
328
- risks: parseList(extractSection(md, 19)),
329
- successCriteria: parseScalar(extractSection(md, 20)),
330
- };
331
- const assumptionMatch = md.match(/^##\s+Assumptions\s*\n([\s\S]*?)(?=^##\s+|\Z)/m);
332
- const assumptions = assumptionMatch && assumptionMatch[1] ? parseList(assumptionMatch[1].trim()) : [];
333
- const headerMatch = md.match(/^>\s+Generated:\s+([^\s]+)\s+·\s+VibeOps\s+([^\s]+)\s+·\s+Source:\s+([^\s·]+)/m);
334
- const generatedAt = headerMatch?.[1] ?? new Date().toISOString();
335
- const recordedVersion = headerMatch?.[2] ?? VERSION;
336
- const recordedSource = headerMatch?.[3] ?? "from-file";
337
- const meta = {
338
- vibeopsVersion: recordedVersion,
339
- generatedAt,
340
- source: recordedSource,
341
- schemaVersion: PROJECT_BRIEF_SCHEMA_VERSION,
342
- assumptions,
343
- };
344
- return { brief, meta };
345
- }
346
- export function findMissingRequired(brief) {
347
- const missing = [];
348
- if (brief.projectName.length === 0 || brief.projectName === PLACEHOLDER_PROJECT_NAME) {
349
- missing.push("projectName");
350
- }
351
- if (brief.oneLineIdea.length === 0 ||
352
- brief.oneLineIdea === PLACEHOLDER_IDEA) {
353
- missing.push("oneLineIdea");
354
- }
355
- return missing;
356
- }
@@ -1,60 +0,0 @@
1
- import { join, relative } from "node:path";
2
- import { mkdir } from "node:fs/promises";
3
- import { pathExists, writeText } from "./filesystem.js";
4
- import { hasNonEmptySection } from "./task.js";
5
- import { VERSION } from "../version.js";
6
- export function cursorImplementPromptRel(taskId) {
7
- const slug = taskId.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
8
- return `.vibeops/generated/cursor-implement-${slug}.md`;
9
- }
10
- export function buildCursorImplementPrompt(opts) {
11
- const fill = [];
12
- if (opts.fillResult)
13
- fill.push("**Result**");
14
- if (opts.fillTestResult)
15
- fill.push("**Test Result**");
16
- const fillLine = fill.length > 0
17
- ? `4. Update ${fill.join(" and ")} in \`${opts.taskPath}\` (replace Pending / (not yet) placeholders).`
18
- : "";
19
- return `# Implement ${opts.taskId}
20
-
21
- Project: \`${opts.projectName}\` (VibeOps ${opts.vibeopsVersion})
22
-
23
- Read and follow the **entire** TASK file:
24
-
25
- \`${opts.taskPath}\`
26
-
27
- Procedure:
28
-
29
- 1. Work only inside **Scope** and **Acceptance Criteria** in that file.
30
- 2. **Search** the repo for existing patterns before adding code or docs.
31
- 3. Do the work required by this TASK (implementation, spec, or docs — per Scope / Out of Scope).
32
- ${fillLine ? `${fillLine}\n` : ""}5. Do not touch other TASKs. No git push or merge.
33
-
34
- When finished, run \`vibeops next\` and choose Refresh, then \`vibeops done ${opts.taskId}\`.
35
- `;
36
- }
37
- /** Write \`.vibeops/generated/cursor-implement-*.md\` when Result/Test need filling. */
38
- export async function ensureCursorImplementPrompt(cwd, config, task, body) {
39
- const fillResult = !hasNonEmptySection(body, "Result");
40
- const fillTestResult = !hasNonEmptySection(body, "Test Result");
41
- if (!fillResult && !fillTestResult)
42
- return null;
43
- const rel = cursorImplementPromptRel(task.id);
44
- const taskPath = relative(cwd, task.filePath).replace(/\\/g, "/");
45
- const projectName = config?.name ?? "project";
46
- const text = buildCursorImplementPrompt({
47
- projectName,
48
- taskId: task.id,
49
- taskPath,
50
- vibeopsVersion: VERSION,
51
- fillResult,
52
- fillTestResult,
53
- });
54
- const outDir = join(cwd, ".vibeops", "generated");
55
- if (!(await pathExists(outDir))) {
56
- await mkdir(outDir, { recursive: true });
57
- }
58
- await writeText(join(cwd, rel), text);
59
- return { rel, fillResult, fillTestResult };
60
- }