@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,146 +0,0 @@
1
- import { isAbsolute, join, relative, resolve } from "node:path";
2
- import { briefToMarkdown, findMissingRequired, gatherBrief, parseBriefFromMarkdown, parseIdea, } from "../lib/brief.js";
3
- import { pathExists, readText, writeText } from "../lib/filesystem.js";
4
- import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
5
- import { gatherBriefViaLlm, pickPlanDialogueLocale, pickPlanLlmProvider } from "../lib/plan-llm-session.js";
6
- import { pickPlanExecutionModel } from "../lib/plan-llm-models.js";
7
- import { writeMvpArtifacts } from "../lib/mvp-artifacts.js";
8
- import { LAST_DONE_SUMMARY_REL, readLastDoneSummary } from "../lib/mvp-done-summary.js";
9
- import { MVP_BUILD_PROMPT_REL } from "../lib/mvp-constants.js";
10
- import { projectPaths } from "../lib/paths.js";
11
- const DEFAULT_BRIEF_REL = ".vibeops/brief/project-brief.md";
12
- function toAbsolute(root, candidate) {
13
- return isAbsolute(candidate) ? candidate : resolve(root, candidate);
14
- }
15
- function relDisplay(root, abs) {
16
- const r = relative(root, abs);
17
- return r.length === 0 ? "." : r;
18
- }
19
- export async function planCommand(options = {}) {
20
- const cwd = resolve(options.cwd ?? process.cwd());
21
- const nonInteractive = options.nonInteractive === true;
22
- const paths = projectPaths(cwd);
23
- if (!(await pathExists(paths.config))) {
24
- log.error(`Not a VibeOps project: ${cyan(".vibeops.json")} was not found under this directory.`);
25
- log.info(` ${dim("Run")} ${cyan("vibeops init")} ${dim("here first, then run")} ${cyan("vibeops plan")} ${dim("again.")}`);
26
- process.exitCode = 1;
27
- return;
28
- }
29
- const briefAbs = join(cwd, DEFAULT_BRIEF_REL);
30
- log.info(bold(`vibeops plan`));
31
- log.info(dim(` cwd: ${cwd}`));
32
- if (options.from)
33
- log.info(dim(` from: ${options.from}`));
34
- if (options.idea)
35
- log.info(dim(` idea: ${options.idea}`));
36
- if (typeof options.llmModel === "string" && options.llmModel.trim().length > 0) {
37
- log.info(dim(` model: ${options.llmModel.trim()}`));
38
- }
39
- if (nonInteractive)
40
- log.info(dim(` mode: non-interactive`));
41
- log.blank();
42
- let bundle;
43
- if (typeof options.from === "string" && options.from.length > 0) {
44
- bundle = await loadFromFile({
45
- cwd,
46
- fromPath: toAbsolute(cwd, options.from),
47
- nonInteractive,
48
- idea: options.idea,
49
- });
50
- }
51
- else if (nonInteractive) {
52
- log.step("non-interactive: build ProjectBrief from flags + placeholders");
53
- log.blank();
54
- bundle = await gatherBrief({ cwd, idea: options.idea, nonInteractive: true });
55
- }
56
- else {
57
- if (process.stdin.isTTY !== true) {
58
- log.error("vibeops plan requires a TTY. In CI, pass --non-interactive or --from <brief.md>.");
59
- process.exitCode = 1;
60
- return;
61
- }
62
- log.step("LLM planning: interactive discovery → brief + MVP artifacts");
63
- log.blank();
64
- const interactiveLogin = true;
65
- const provider = await pickPlanLlmProvider(cwd, options.provider, { interactiveLogin });
66
- if (provider === null)
67
- return;
68
- const forcedModel = typeof options.llmModel === "string" && options.llmModel.trim().length > 0
69
- ? options.llmModel.trim()
70
- : undefined;
71
- const pickedModel = forcedModel ??
72
- (await pickPlanExecutionModel({ provider, cwd, interactive: interactiveLogin }));
73
- log.blank();
74
- const planningLocale = await pickPlanDialogueLocale();
75
- try {
76
- bundle = await gatherBriefViaLlm({
77
- cwd,
78
- idea: options.idea,
79
- provider,
80
- allowCodexBrowserLogin: interactiveLogin,
81
- llmModelOverride: pickedModel,
82
- planningLocale,
83
- });
84
- }
85
- catch (e) {
86
- const msg = e instanceof Error ? e.message : String(e);
87
- log.error(`LLM planning failed: ${msg}`);
88
- process.exitCode = 1;
89
- return;
90
- }
91
- }
92
- await writeText(briefAbs, briefToMarkdown(bundle.brief, bundle.meta));
93
- log.ok(`Wrote brief: ${relDisplay(cwd, briefAbs)}`);
94
- const prior = await readLastDoneSummary(cwd);
95
- if (prior !== null) {
96
- log.info(dim(` prior iteration: ${LAST_DONE_SUMMARY_REL} → included in TASK + mvp-build.md`));
97
- }
98
- const mvp = await writeMvpArtifacts(cwd, bundle);
99
- log.ok(`MVP TASK: ${relDisplay(cwd, mvp.taskFile)}`);
100
- log.ok(`MVP build prompt: ${relDisplay(cwd, mvp.buildPromptFile)}`);
101
- if (bundle.meta.assumptions.length > 0) {
102
- log.blank();
103
- log.info(`${yellow("!")} ${bold("Assumptions")} (reconfirm in Cursor if needed):`);
104
- for (const a of bundle.meta.assumptions)
105
- log.info(` · ${a}`);
106
- }
107
- log.blank();
108
- log.info(bold("Next steps:"));
109
- log.info(` 1) ${cyan("vibeops start")}`);
110
- log.info(` 2) Drag ${cyan(MVP_BUILD_PROMPT_REL)} into a new Cursor chat`);
111
- log.info(` 3) ${cyan("vibeops done")} when Result / Test Result are filled`);
112
- log.info(` 4) ${cyan("vibeops next")} for step-by-step guidance`);
113
- log.info(dim(`Re-plan from brief: vibeops plan --from ${DEFAULT_BRIEF_REL}`));
114
- }
115
- async function loadFromFile(inputs) {
116
- const { cwd, fromPath, nonInteractive, idea } = inputs;
117
- if (!(await pathExists(fromPath))) {
118
- log.error(`--from path does not exist: ${fromPath}`);
119
- process.exit(1);
120
- }
121
- const md = await readText(fromPath);
122
- log.step(`Loading brief: ${relDisplay(cwd, fromPath)}`);
123
- const parsed = parseBriefFromMarkdown(md);
124
- parsed.meta.source = "from-file";
125
- if (typeof idea === "string" && idea.length > 0) {
126
- const ideaParsed = parseIdea(idea);
127
- if (ideaParsed.projectName && parsed.brief.projectName.length === 0) {
128
- parsed.brief.projectName = ideaParsed.projectName;
129
- }
130
- if (ideaParsed.oneLineIdea && parsed.brief.oneLineIdea.length === 0) {
131
- parsed.brief.oneLineIdea = ideaParsed.oneLineIdea;
132
- }
133
- }
134
- const missing = findMissingRequired(parsed.brief);
135
- if (missing.length === 0)
136
- return parsed;
137
- if (nonInteractive) {
138
- log.warn(`Missing fields: ${missing.join(", ")} — placeholders recorded in Assumptions.`);
139
- return parsed;
140
- }
141
- log.warn(`Missing fields: ${missing.join(", ")} — asking only those questions.`);
142
- log.blank();
143
- const filled = await gatherBrief({ cwd, idea, nonInteractive: false, seed: parsed.brief });
144
- filled.meta.source = "from-file";
145
- return filled;
146
- }
@@ -1,126 +0,0 @@
1
- import { relative, resolve } from "node:path";
2
- import { gitBranchExists, gitCheckout, gitDeleteBranch, gitResetHard, readGitInfo, } from "../lib/git.js";
3
- import { commandTaskNotFoundMessage, resolveCommandTask, } from "../lib/resolve-command-task.js";
4
- import { bold, cyan, dim, log, red, yellow } from "../lib/logger.js";
5
- import { projectPaths } from "../lib/paths.js";
6
- import { readGitContext, readTaskFile, statusDisplay } from "../lib/task.js";
7
- function relOrAbs(root, p) {
8
- const r = relative(root, p);
9
- return r === "" ? "." : r.startsWith("..") ? p : r;
10
- }
11
- function shellQuote(s) {
12
- return /^[A-Za-z0-9._/\-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
13
- }
14
- function strategyCommands(strategy, ctx) {
15
- switch (strategy) {
16
- case "branch-delete":
17
- return [
18
- `git switch ${shellQuote(ctx.baseBranch)}`,
19
- `git branch -D ${shellQuote(ctx.taskBranch)}`,
20
- ];
21
- case "reset-base":
22
- return [
23
- `git switch ${shellQuote(ctx.taskBranch)}`,
24
- `git reset --hard ${shellQuote(ctx.baseCommit)}`,
25
- ];
26
- case "revert-merge":
27
- return [
28
- `git switch ${shellQuote(ctx.baseBranch)}`,
29
- `git log --oneline --merges ${shellQuote(ctx.baseBranch)} | head -5`,
30
- `git revert -m 1 <merge-sha>`,
31
- ];
32
- }
33
- }
34
- function strategyRisk(strategy) {
35
- switch (strategy) {
36
- case "branch-delete":
37
- return "Deletes the task branch and any unmerged work on it.";
38
- case "reset-base":
39
- return "Hard-resets the task branch to the base commit.";
40
- case "revert-merge":
41
- return "Manual revert on main after a merge was pushed.";
42
- }
43
- }
44
- const STRATEGIES = ["branch-delete", "reset-base", "revert-merge"];
45
- export async function rollbackCommand(taskRef, options = {}) {
46
- const cwd = resolve(options.cwd ?? process.cwd());
47
- const paths = projectPaths(cwd);
48
- const resolved = await resolveCommandTask(paths, cwd, taskRef);
49
- if (resolved === null) {
50
- log.error(commandTaskNotFoundMessage(taskRef));
51
- process.exitCode = 1;
52
- return;
53
- }
54
- const { taskFile } = resolved;
55
- const meta = await readTaskFile(taskFile);
56
- const ctx = await readGitContext(taskFile);
57
- const git = await readGitInfo(cwd);
58
- log.info(bold(`vibeops rollback ${meta.id}`));
59
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
60
- log.info(` ${dim("status")} ${statusDisplay(meta.status)}`);
61
- log.blank();
62
- if (ctx === null) {
63
- log.warn(`No Git Context in TASK file. Run ${cyan("vibeops start")} first.`);
64
- if (options.dryRun !== true) {
65
- process.exitCode = 1;
66
- }
67
- return;
68
- }
69
- log.info(bold("Git Context"));
70
- log.info(` base ${ctx.baseBranch} @ ${ctx.baseCommit}`);
71
- log.info(` task ${cyan(ctx.taskBranch)}`);
72
- log.blank();
73
- for (const s of STRATEGIES) {
74
- const destructive = s === "reset-base";
75
- log.info(` ${bold(s)} ${destructive ? red("destructive") : yellow("safe")}`);
76
- log.info(` ${dim(strategyRisk(s))}`);
77
- for (const cmd of strategyCommands(s, ctx)) {
78
- log.info(` ${dim("$")} ${cmd}`);
79
- }
80
- log.blank();
81
- }
82
- if (options.confirm !== true && options.confirmDestructive !== true) {
83
- log.info(`Guidance only. Use ${cyan("--confirm")} or ${cyan("--confirm-destructive")} to run.`);
84
- return;
85
- }
86
- const strategy = options.strategy ?? "branch-delete";
87
- if (!STRATEGIES.includes(strategy)) {
88
- log.error(`Unknown strategy: ${strategy}`);
89
- process.exitCode = 1;
90
- return;
91
- }
92
- if (strategy === "revert-merge") {
93
- log.info(`${yellow("!")} revert-merge is manual only — run the commands above.`);
94
- return;
95
- }
96
- if (strategy === "reset-base" && options.confirmDestructive !== true) {
97
- log.error("reset-base requires --confirm-destructive.");
98
- process.exitCode = 1;
99
- return;
100
- }
101
- if (!git.isRepo) {
102
- log.error("Not a git repository.");
103
- process.exitCode = 1;
104
- return;
105
- }
106
- if (options.dryRun === true) {
107
- log.info(bold(`dry-run (${strategy}):`));
108
- for (const cmd of strategyCommands(strategy, ctx)) {
109
- log.info(` ${dim("$")} ${cmd}`);
110
- }
111
- return;
112
- }
113
- if (strategy === "branch-delete") {
114
- if (git.branch === ctx.taskBranch)
115
- await gitCheckout(cwd, ctx.baseBranch);
116
- if (!options.keepBranch && (await gitBranchExists(cwd, ctx.taskBranch))) {
117
- await gitDeleteBranch(cwd, ctx.taskBranch, { force: true });
118
- log.ok(`Deleted ${ctx.taskBranch}`);
119
- }
120
- return;
121
- }
122
- if (git.branch !== ctx.taskBranch)
123
- await gitCheckout(cwd, ctx.taskBranch);
124
- await gitResetHard(cwd, ctx.baseCommit);
125
- log.ok(`Reset ${ctx.taskBranch} → ${ctx.baseCommit}`);
126
- }
@@ -1,129 +0,0 @@
1
- import { join, relative, resolve } from "node:path";
2
- import { branchNameForTaskFile, isMvpTaskId, readGitContext, readTaskFile, upsertGitContext, updateInlineStatus, } from "../lib/task.js";
3
- import { detectDefaultBranch, gitBranchExists, gitCheckout, gitCheckoutNewBranch, gitGovernanceOnlyDirty, gitHeadCommit, readGitInfo, } from "../lib/git.js";
4
- import { MVP_BUILD_PROMPT_REL } from "../lib/mvp-constants.js";
5
- import { taskBuildPromptRel } from "../lib/task-add-build-prompt.js";
6
- import { pathExists } from "../lib/filesystem.js";
7
- import { bold, cyan, dim, log } from "../lib/logger.js";
8
- import { projectPaths } from "../lib/paths.js";
9
- import { commandTaskNotFoundMessage, resolveCommandTask, } from "../lib/resolve-command-task.js";
10
- function relOrAbs(root, p) {
11
- const r = relative(root, p);
12
- return r === "" ? "." : r.startsWith("..") ? p : r;
13
- }
14
- export async function startCommand(taskRef, options = {}) {
15
- const cwd = resolve(options.cwd ?? process.cwd());
16
- const paths = projectPaths(cwd);
17
- const resolved = await resolveCommandTask(paths, cwd, taskRef);
18
- if (resolved === null) {
19
- log.error(commandTaskNotFoundMessage(taskRef));
20
- process.exitCode = 1;
21
- return;
22
- }
23
- const { taskId, taskFile, source } = resolved;
24
- if (source === "backlog-active") {
25
- log.info(dim(`No TASK-mvp — continuing backlog with ${cyan(taskId)}.`));
26
- }
27
- const git = await readGitInfo(cwd);
28
- if (!git.isRepo) {
29
- if (options.dryRun === true) {
30
- log.info(bold("dry-run — would:"));
31
- log.info(` · require a git repo under ${cwd}`);
32
- log.info(` · git checkout -b task/mvp-*`);
33
- log.info(` · Status → In Progress + Git Context`);
34
- return;
35
- }
36
- log.error(`Not a git repository: ${cwd}`);
37
- process.exitCode = 1;
38
- return;
39
- }
40
- if (git.dirty === true && options.allowDirty !== true) {
41
- const gov = await gitGovernanceOnlyDirty(cwd);
42
- if (!gov.onlyGovernance) {
43
- log.error("Git working tree is dirty. Commit or stash, or use --allow-dirty.");
44
- if (gov.nonGovernancePaths.length > 0) {
45
- for (const p of gov.nonGovernancePaths.slice(0, 16)) {
46
- log.info(` ${dim("·")} ${p}`);
47
- }
48
- if (gov.nonGovernancePaths.length > 16) {
49
- log.info(` ${dim("·")} … and ${gov.nonGovernancePaths.length - 16} more`);
50
- }
51
- }
52
- log.blank();
53
- log.info(dim("Options:"));
54
- log.info(` ${dim("1.")} Commit on ${git.branch ?? "HEAD"}: git add … && git commit`);
55
- log.info(` ${dim("2.")} Stash: git stash push -u -m "wip"`);
56
- log.info(` ${dim("3.")} Carry WIP into task branch: ${cyan(`vibeops start ${taskId} --allow-dirty`)}`);
57
- log.info(dim(" If this is TASK-009 scaffold on main, run vibeops done TASK-009 or commit it before TASK-010."));
58
- process.exitCode = 1;
59
- return;
60
- }
61
- log.warn("Only governance / VibeOps paths are dirty — proceeding.");
62
- }
63
- const baseBranch = git.branch ?? (await detectDefaultBranch(cwd)) ?? "main";
64
- const baseCommit = (await gitHeadCommit(cwd)) ?? "";
65
- if (baseCommit.length === 0) {
66
- log.error("No commits on HEAD yet.");
67
- process.exitCode = 1;
68
- return;
69
- }
70
- const taskBranch = branchNameForTaskFile(taskFile);
71
- const startedAt = new Date().toISOString();
72
- const ctx = { baseBranch, baseCommit, taskBranch, startedAt };
73
- const meta = await readTaskFile(taskFile);
74
- const existingCtx = await readGitContext(taskFile);
75
- const branchExists = await gitBranchExists(cwd, taskBranch);
76
- log.info(bold(`vibeops start`));
77
- log.info(` ${dim("task")} ${taskId}`);
78
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
79
- if (branchExists && existingCtx) {
80
- log.info(` ${dim("base branch")} ${existingCtx.baseBranch} @ ${existingCtx.baseCommit.slice(0, 7)}`);
81
- }
82
- else {
83
- log.info(` ${dim("base branch")} ${baseBranch} @ ${baseCommit.slice(0, 7)}`);
84
- }
85
- log.info(` ${dim("task branch")} ${cyan(taskBranch)}`);
86
- log.blank();
87
- if (options.dryRun === true) {
88
- log.info(bold("dry-run — would:"));
89
- log.info(branchExists
90
- ? ` · git switch ${taskBranch} (branch exists)`
91
- : ` · git checkout -b ${taskBranch}`);
92
- log.info(` · Status → In Progress + Git Context in TASK file`);
93
- return;
94
- }
95
- if (branchExists) {
96
- await gitCheckout(cwd, taskBranch);
97
- if (meta.status === "planned") {
98
- await updateInlineStatus(taskFile, "in_progress");
99
- }
100
- if (existingCtx === null) {
101
- await upsertGitContext(taskFile, ctx);
102
- }
103
- if (existingCtx?.baseBranch.startsWith("chore/vibeops-post-")) {
104
- log.warn(`Git Context base is ${existingCtx.baseBranch} — merge or delete that chore branch, then fix base in the TASK file if needed.`);
105
- }
106
- log.ok(`Resumed existing branch ${taskBranch}.`);
107
- }
108
- else {
109
- await gitCheckoutNewBranch(cwd, taskBranch, baseBranch);
110
- await updateInlineStatus(taskFile, "in_progress");
111
- await upsertGitContext(taskFile, ctx);
112
- log.ok(`Branch ${taskBranch} ready.`);
113
- }
114
- log.blank();
115
- log.info(bold("Build in Cursor"));
116
- if (isMvpTaskId(taskId)) {
117
- log.info(` Drag ${cyan(relOrAbs(cwd, MVP_BUILD_PROMPT_REL))} into a new chat.`);
118
- }
119
- else {
120
- const taskBuildRel = taskBuildPromptRel(taskId);
121
- if (await pathExists(join(cwd, taskBuildRel))) {
122
- log.info(` Drag ${cyan(relOrAbs(cwd, taskBuildRel))} into a new chat.`);
123
- }
124
- else {
125
- log.info(` Open ${cyan(relOrAbs(cwd, taskFile))} and implement per Acceptance Criteria.`);
126
- }
127
- }
128
- log.info(` When done: fill Result / Test Result, then ${cyan(`vibeops done ${taskId}`)}.`);
129
- }
@@ -1,155 +0,0 @@
1
- import { join, relative, resolve } from "node:path";
2
- import { pathExists, readText } from "../lib/filesystem.js";
3
- import { gitAllChangedFilesSinceTaskStart, gitCommitsAhead, gitLogOneline, readGitInfo, } from "../lib/git.js";
4
- import { bold, cyan, dim, green, log, red, yellow } from "../lib/logger.js";
5
- import { projectPaths } from "../lib/paths.js";
6
- import { findAcceptanceCriteria, findExpectedFiles, findTaskFile, hasNonEmptySection, readGitContext, readSection, readTaskFile, statusDisplay, } from "../lib/task.js";
7
- import { buildTaskPromptString } from "../lib/task-prompt.js";
8
- function relOrAbs(root, p) {
9
- const r = relative(root, p);
10
- return r === "" ? "." : r.startsWith("..") ? p : r;
11
- }
12
- function todayLogFileName() {
13
- const d = new Date();
14
- const y = d.getFullYear();
15
- const m = String(d.getMonth() + 1).padStart(2, "0");
16
- const day = String(d.getDate()).padStart(2, "0");
17
- return `${y}-${m}-${day}.md`;
18
- }
19
- export async function taskCheckCommand(taskId, options = {}) {
20
- const cwd = resolve(options.cwd ?? process.cwd());
21
- const paths = projectPaths(cwd);
22
- const agentName = options.agent ?? "reviewer";
23
- let missing = 0;
24
- const taskFile = await findTaskFile(paths.docsTasks, taskId);
25
- if (!taskFile) {
26
- log.error(`TASK not found: ${taskId} (looked in ${relOrAbs(cwd, paths.docsTasks)})`);
27
- process.exitCode = 1;
28
- return;
29
- }
30
- const meta = await readTaskFile(taskFile);
31
- const body = await readText(taskFile);
32
- log.info(bold(`vibeops task check ${meta.id}`));
33
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
34
- log.info(` ${dim("title")} ${meta.title || dim("(no title)")}`);
35
- log.info(` ${dim("status")} ${statusDisplay(meta.status)}`);
36
- log.blank();
37
- log.info(bold("Git"));
38
- const git = await readGitInfo(cwd);
39
- if (!git.isRepo) {
40
- log.info(` ${red("✗")} not a git repository`);
41
- missing++;
42
- }
43
- else {
44
- log.info(` ${dim("branch")} ${git.branch ?? dim("(detached)")}`);
45
- log.info(` ${dim("status")} ${git.dirty ? yellow("dirty") : green("clean")}`);
46
- }
47
- const ctx = await readGitContext(taskFile);
48
- if (ctx === null) {
49
- log.info(` ${yellow("·")} no Git Context section yet — run \`vibeops task start ${meta.id}\` first.`);
50
- missing++;
51
- }
52
- else {
53
- log.info(` ${dim("base")} ${ctx.baseBranch} @ ${ctx.baseCommit}`);
54
- log.info(` ${dim("task branch")} ${cyan(ctx.taskBranch)}`);
55
- log.info(` ${dim("started at")} ${ctx.startedAt}`);
56
- if (git.isRepo) {
57
- const range = `${ctx.baseCommit}..HEAD`;
58
- const summary = await gitAllChangedFilesSinceTaskStart(ctx.baseCommit, cwd);
59
- const ahead = await gitCommitsAhead(cwd, ctx.baseCommit);
60
- log.info(` ${dim("commits ahead")} ${ahead}`);
61
- log.info(` ${dim("working tree changed files")} ${summary.workingTree.length}`);
62
- log.info(` ${dim("committed changed files")} ${summary.committed.length}`);
63
- log.info(` ${dim("total changed files")} ${summary.all.length}`);
64
- const recent = await gitLogOneline(cwd, `${range}`);
65
- const head = recent.slice(0, 5);
66
- if (head.length > 0) {
67
- log.info(` ${dim("recent commits:")}`);
68
- for (const c of head)
69
- log.info(` · ${c.sha} ${c.message}`);
70
- }
71
- const expected = findExpectedFiles(body);
72
- if (expected.length > 0) {
73
- log.blank();
74
- log.info(bold("Expected Files to Change vs current diff"));
75
- const lower = new Set(summary.all.map((c) => c.toLowerCase()));
76
- let hit = 0;
77
- for (const e of expected) {
78
- const present = lower.has(e.toLowerCase());
79
- if (present)
80
- hit++;
81
- log.info(` ${present ? green("✓") : yellow("·")} ${e}${present ? "" : dim(" (not yet in diff)")}`);
82
- }
83
- log.info(dim(` match ${hit}/${expected.length} (${Math.round((hit / expected.length) * 100)}%)`));
84
- log.info(dim(` basis: working tree(${summary.workingTree.length}) ∪ committed(${summary.committed.length}) = total(${summary.all.length}) files`));
85
- }
86
- }
87
- }
88
- log.blank();
89
- log.info(bold("Acceptance Criteria"));
90
- const ac = findAcceptanceCriteria(body);
91
- if (ac.length === 0) {
92
- log.info(` ${dim("(none parsed — TASK file may not use \"1. …\" list format)")}`);
93
- }
94
- else {
95
- for (let i = 0; i < ac.length; i++) {
96
- log.info(` ${dim(`${i + 1}.`)} ${ac[i]}`);
97
- }
98
- log.info(dim(` → confirm each item manually with the diff before \`vibeops task done\`.`));
99
- }
100
- log.blank();
101
- log.info(bold("Docs touched in this round"));
102
- const docsChecks = [
103
- { label: "current-state (03)", path: join(paths.docsProject, "03-current-state.md"), required: false },
104
- { label: "current-state (05)", path: join(paths.docsProject, "05-current-state.md"), required: false },
105
- { label: `log (${todayLogFileName()})`, path: join(paths.docsLogs, todayLogFileName()), required: false },
106
- ];
107
- let anyDoc = false;
108
- for (const c of docsChecks) {
109
- const present = await pathExists(c.path);
110
- if (present)
111
- anyDoc = true;
112
- log.info(` ${present ? green("✓") : dim("·")} ${c.label} ${dim(relOrAbs(cwd, c.path))}`);
113
- }
114
- if (!anyDoc) {
115
- log.info(` ${yellow("!")} no current-state or today's log file found — Docs Agent must update them before \`task done\`.`);
116
- missing++;
117
- }
118
- log.blank();
119
- log.info(bold("TASK Result / Test Result"));
120
- const resultOk = hasNonEmptySection(body, "Result");
121
- const testResultOk = hasNonEmptySection(body, "Test Result");
122
- log.info(` ${resultOk ? green("✓") : yellow("·")} Result ${dim(resultOk ? readSection(body, "Result").split("\n")[0]?.slice(0, 80) ?? "" : "(empty / placeholder)")}`);
123
- log.info(` ${testResultOk ? green("✓") : yellow("·")} Test Result ${dim(testResultOk ? readSection(body, "Test Result").split("\n")[0]?.slice(0, 80) ?? "" : "(empty / placeholder)")}`);
124
- if (!resultOk)
125
- missing++;
126
- if (!testResultOk)
127
- missing++;
128
- log.blank();
129
- log.info(bold("Summary"));
130
- if (missing === 0) {
131
- log.ok("All checks present. Ready for `vibeops task done`.");
132
- }
133
- else {
134
- log.info(` ${yellow("!")} ${missing} item(s) need attention before \`vibeops task done\`.`);
135
- }
136
- log.blank();
137
- const promptResult = await buildTaskPromptString({
138
- projectRoot: paths.root,
139
- agentsDir: paths.vibeopsAgents,
140
- agentName,
141
- taskFilePath: taskFile,
142
- });
143
- if (promptResult.ok) {
144
- log.info(bold(`Cursor prompt (agent: ${agentName}):`));
145
- log.info(dim("─".repeat(60)));
146
- log.raw(promptResult.prompt.endsWith("\n") ? promptResult.prompt : `${promptResult.prompt}\n`);
147
- log.info(dim("─".repeat(60)));
148
- }
149
- else {
150
- log.info(dim(`(agent "${agentName}" not found — install \`.vibeops/agents/${agentName}.md\` or pass --agent <name>)`));
151
- }
152
- if (options.strict === true && missing > 0) {
153
- process.exitCode = 1;
154
- }
155
- }