@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,34 +1,15 @@
1
- # 03 — Architecture
1
+ # Architecture
2
2
 
3
- > Filled in by the `architect` agent.
3
+ > Living overview. Update when structure or major paths change (often via `vibeops task done`).
4
4
 
5
- ## Big picture
5
+ ## System
6
6
 
7
- <!--
8
- Use an ASCII diagram to draw the "inside / outside the system" boundary.
9
- Flows like user → frontend → backend → DB / external services.
10
- -->
7
+ (describe stack and main folders as the project grows)
11
8
 
12
- ## Components
9
+ ## Key paths
13
10
 
14
- <!--
15
- | Name | Responsibility | Inputs | Outputs |
16
- | ----------- | ------------------------ | ------------- | ------------- |
17
- | component-a | ... | ... | ... |
18
- -->
19
-
20
- ## Data model
21
-
22
- <!-- The 3 to 7 core entities and how they relate. Field-level detail belongs to a separate TASK. -->
23
-
24
- ## Data flow
25
-
26
- <!-- The call sequence for the 1 to 2 most common user scenarios. -->
27
-
28
- ## External boundaries
29
-
30
- <!-- Which external services / APIs we talk to. Auth method, public or private. -->
31
-
32
- ## Non-deterministic / failure paths
33
-
34
- <!-- High-level behavior under network failures, concurrency, data loss. -->
11
+ | Area | Path |
12
+ |------|------|
13
+ | Application | (TBD) |
14
+ | TASK docs | `docs/tasks/` |
15
+ | Project memory | `docs/project/` |
@@ -1,35 +1,15 @@
1
- # 05 — Current State
1
+ # Current state
2
2
 
3
- > Record **facts only**. The `docs` agent refreshes this document after each implementation.
3
+ > Facts only. Updated by `vibeops task done` and humans.
4
4
 
5
5
  ## Stage
6
6
 
7
- - **Current stage**: bootstrap complete. Planning and implementation have not started yet.
8
- - No code (`src/`, `package.json`, …) exists yet.
7
+ - Bootstrapped with VibeOps. No TASK started yet.
9
8
 
10
- ## What is in place
9
+ ## Active TASK
11
10
 
12
- | Item | Location | Notes |
13
- | ----------------- | ------------------------------------- | --------------------------- |
14
- | Operating guide | `AGENTS.md`, `.cursor/rules/*` | Installed by VibeOps |
15
- | Agent definitions | `.vibeops/agents/*` | 8 agents |
16
- | Project docs | `docs/project/00 ~ 09` | Empty (waiting for plan) |
17
- | TASK folder | `docs/tasks/` | Empty (waiting for generate) |
18
- | Logs folder | `docs/logs/` | Empty |
11
+ (none)
19
12
 
20
- ## What is missing
13
+ ## Next step
21
14
 
22
- - The actual bodies of `docs/project/*` (planner / architect will fill these in).
23
- - `docs/tasks/TASK-001-*.md` (`task generate`).
24
- - Any code at all.
25
- - Notion connection (run `vibeops notion init` if desired).
26
-
27
- ## Next TASK
28
-
29
- **The backlog is empty.** Run `vibeops plan --idea "<your idea>"` to fill in `docs/project/{00,01,02,07}`; the first TASK candidates fall out of that.
30
-
31
- ## Working rules (short summary)
32
-
33
- - Implement **one TASK at a time**.
34
- - Every mutating command should support `--dry-run` when possible.
35
- - After implementation, update this document, the TASK file, and `docs/logs/YYYY-MM-DD.md` together.
15
+ Run `vibeops task add` to create the first TASK.
@@ -1,20 +1,9 @@
1
- # 06 — Decisions
1
+ # Decisions
2
2
 
3
- Decisions that have already been made. A conflicting proposal changes them only after a separate TASK reopens the discussion.
3
+ > Append-only log. Add bullets when a TASK locks a choice — do not rewrite history.
4
4
 
5
- ## D-001 · Source of truth is Git `docs/`
5
+ ## D-001 · VibeOps workflow
6
6
 
7
- Chat is not trusted. When docs and chat disagree, fix the docs first, then implement.
8
-
9
- ## D-002 · One TASK at a time
10
-
11
- Large refactors do not happen without a dedicated TASK.
12
-
13
- ## D-003 · Notion is the human dashboard (metadata only)
14
-
15
- No body sync. No realtime.
16
-
17
- <!--
18
- Add later decisions as `D-NNN · one-line summary`.
19
- Keep each entry short — one paragraph that covers "why" and "result".
20
- -->
7
+ - **Date:** {{CREATED_AT}}
8
+ - **Decision:** TASK-driven development with `vibeops task add` / `task done` and Cursor for plan/build.
9
+ - **Why:** Git `docs/tasks` beats chat as source of truth.
@@ -1,67 +1,32 @@
1
- # TASK-NNN · <one-line title>
2
-
3
- > This file is an example produced from VibeOps' `TASK-000-template.md`.
4
- > Real TASKs come from `vibeops plan` (TASK-mvp), `vibeops task add` (work-now slice), or your backlog process. Filenames follow `TASK-NNN-<slug>.md`.
1
+ # TASK-NNN · title
5
2
 
6
3
  ## Status
7
4
 
8
- planned
9
-
10
- ## MVP Phase
11
-
12
- MVP 1 · <phase name>
5
+ Planned
13
6
 
14
7
  ## Goal
15
8
 
16
- <2 to 4 sentences. What becomes possible when this TASK ships.>
17
-
18
- ## Background
19
-
20
- <Why this is needed now. Which prior decisions / TASKs this builds on.>
9
+ (what becomes possible when this TASK ships)
21
10
 
22
11
  ## Scope
23
12
 
24
- - <bullet 1>
25
- - <bullet 2>
13
+ -
26
14
 
27
15
  ## Out of Scope
28
16
 
29
- - <explicit exclusion 1>
30
- - <explicit exclusion 2>
17
+ -
31
18
 
32
19
  ## Acceptance Criteria
33
20
 
34
- 1. <verifiable statement>
35
- 2. <verifiable statement>
36
-
37
- ## Files to Inspect First
38
-
39
- - <path 1>
40
- - <path 2>
41
-
42
- ## Expected Files to Change
43
-
44
- - new: <path>
45
- - update: <path>
46
-
47
- ## Risks
48
-
49
- - <risk 1 and mitigation>
50
- - <risk 2 and mitigation>
21
+ 1.
51
22
 
52
23
  ## Test Plan
53
24
 
54
- - <command / case 1>
55
- - <command / case 2>
56
-
57
- ## Rollback Plan
58
-
59
- - <how to roll back>
25
+ -
60
26
 
61
- ## Implementation Plan
27
+ ## Git Context
62
28
 
63
- 1. <step 1>
64
- 2. <step 2>
29
+ (populated by `vibeops task add`)
65
30
 
66
31
  ## Result
67
32
 
@@ -1,71 +0,0 @@
1
- import { readdir } from "node:fs/promises";
2
- import { basename, extname, join } from "node:path";
3
- import matter from "gray-matter";
4
- import { isDirectory, readText } from "../lib/filesystem.js";
5
- function metaFromFile(filePath, data, body) {
6
- const fallbackName = basename(filePath, extname(filePath));
7
- const name = typeof data["name"] === "string" ? data["name"] : fallbackName;
8
- const role = typeof data["role"] === "string" ? data["role"] : extractFirstHeading(body);
9
- const description = typeof data["description"] === "string" ? data["description"] : undefined;
10
- return { name, role, description, filePath };
11
- }
12
- function extractFirstHeading(body) {
13
- for (const line of body.split("\n")) {
14
- const m = /^#\s+(.*)$/.exec(line.trim());
15
- if (m)
16
- return m[1].trim();
17
- }
18
- return "(no role)";
19
- }
20
- function frontmatterSlice(raw) {
21
- if (!raw.startsWith("---"))
22
- return "";
23
- const end = raw.indexOf("\n---", 3);
24
- if (end < 0)
25
- return "";
26
- return raw.slice(0, end + 4);
27
- }
28
- export async function loadAgent(filePath) {
29
- const raw = await readText(filePath);
30
- const parsed = matter(raw);
31
- const meta = metaFromFile(filePath, parsed.data, parsed.content);
32
- return {
33
- meta,
34
- body: parsed.content.trimStart(),
35
- rawFrontmatter: frontmatterSlice(raw),
36
- raw,
37
- };
38
- }
39
- export async function listAgents(agentsDir) {
40
- if (!(await isDirectory(agentsDir)))
41
- return [];
42
- const entries = await readdir(agentsDir, { withFileTypes: true });
43
- const files = entries
44
- .filter((e) => e.isFile() && e.name.endsWith(".md"))
45
- .map((e) => join(agentsDir, e.name))
46
- .sort();
47
- const records = [];
48
- for (const f of files) {
49
- try {
50
- records.push(await loadAgent(f));
51
- }
52
- catch {
53
- // skip malformed agent files
54
- }
55
- }
56
- return records;
57
- }
58
- export async function findAgent(agentsDir, name) {
59
- const all = await listAgents(agentsDir);
60
- const target = name.toLowerCase();
61
- for (const a of all) {
62
- if (a.meta.name.toLowerCase() === target)
63
- return a;
64
- }
65
- for (const a of all) {
66
- const filenameKey = basename(a.meta.filePath, extname(a.meta.filePath)).toLowerCase();
67
- if (filenameKey === target)
68
- return a;
69
- }
70
- return null;
71
- }
@@ -1,66 +0,0 @@
1
- import { relative } from "node:path";
2
- import { readText } from "../lib/filesystem.js";
3
- function relOrAbs(root, p) {
4
- const r = relative(root, p);
5
- return r === "" ? "." : r.startsWith("..") ? p : r;
6
- }
7
- function header(inputs) {
8
- const lines = [];
9
- lines.push(`# Cursor prompt — agent: ${inputs.agent.meta.name}`);
10
- lines.push("");
11
- if (inputs.config) {
12
- lines.push(`Project: \`${inputs.config.name}\``);
13
- lines.push(`VibeOps: \`${inputs.config.vibeopsVersion}\``);
14
- }
15
- else {
16
- lines.push("Project: (no .vibeops.json found — running outside a VibeOps project)");
17
- }
18
- if (inputs.task) {
19
- lines.push(`TASK: \`${inputs.task.meta.id}\``);
20
- lines.push(`TASK file: \`${relOrAbs(inputs.projectRoot, inputs.task.meta.filePath)}\``);
21
- }
22
- lines.push("");
23
- return lines.join("\n");
24
- }
25
- function agentSection(agent) {
26
- return `## Agent definition (${agent.meta.name})\n\n${agent.body.trim()}\n`;
27
- }
28
- function taskSection(task) {
29
- return `## TASK file content\n\n${task.body.trim()}\n`;
30
- }
31
- async function contextSection(paths, projectRoot) {
32
- if (paths.length === 0)
33
- return "";
34
- const parts = ["## Extra context\n"];
35
- for (const p of paths) {
36
- try {
37
- const text = await readText(p);
38
- parts.push(`### ${relOrAbs(projectRoot, p)}\n\n\`\`\`\n${text}\n\`\`\`\n`);
39
- }
40
- catch {
41
- parts.push(`### ${relOrAbs(projectRoot, p)}\n\n_(could not read file)_\n`);
42
- }
43
- }
44
- return parts.join("\n");
45
- }
46
- const FOOTER = `---
47
-
48
- Apply the **Role / Inputs / Output Format / Rules / Forbidden** sections of the agent definition above as-is.
49
- Your output must be a single markdown blob that pastes cleanly into the Cursor chat.
50
-
51
- When done, follow the "Completion report" format in \`AGENTS.md\`:
52
- TASK ID · summary · changed files · verification · doc updates (05-current-state / TASK / docs/logs).
53
- `;
54
- export async function buildPrompt(inputs) {
55
- const parts = [
56
- header(inputs),
57
- agentSection(inputs.agent),
58
- ];
59
- if (inputs.task)
60
- parts.push(taskSection(inputs.task));
61
- const extra = await contextSection(inputs.contextPaths ?? [], inputs.projectRoot);
62
- if (extra.length > 0)
63
- parts.push(extra);
64
- parts.push(FOOTER);
65
- return parts.join("\n");
66
- }
@@ -1,32 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { listAgents } from "../agent/loader.js";
3
- import { bold, dim, log } from "../lib/logger.js";
4
- import { projectPaths } from "../lib/paths.js";
5
- export async function agentListCommand(options = {}) {
6
- const cwd = resolve(options.cwd ?? process.cwd());
7
- const paths = projectPaths(cwd);
8
- const agents = await listAgents(paths.vibeopsAgents);
9
- if (options.json) {
10
- const payload = agents.map((a) => ({
11
- name: a.meta.name,
12
- role: a.meta.role,
13
- description: a.meta.description ?? null,
14
- filePath: a.meta.filePath,
15
- }));
16
- log.raw(`${JSON.stringify(payload, null, 2)}\n`);
17
- return;
18
- }
19
- if (agents.length === 0) {
20
- log.warn(`No agents found in ${paths.vibeopsAgents}`);
21
- log.info(dim("Run `vibeops init` first."));
22
- process.exitCode = 1;
23
- return;
24
- }
25
- log.info(bold("Agents"));
26
- const nameWidth = Math.max(...agents.map((a) => a.meta.name.length));
27
- for (const a of agents) {
28
- const padded = a.meta.name.padEnd(nameWidth);
29
- const summary = a.meta.description ?? a.meta.role;
30
- log.info(` ${padded} ${dim(summary)}`);
31
- }
32
- }
@@ -1,59 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { findAgent, listAgents } from "../agent/loader.js";
3
- import { buildPrompt } from "../agent/prompt.js";
4
- import { readConfig } from "../lib/config.js";
5
- import { readText } from "../lib/filesystem.js";
6
- import { log } from "../lib/logger.js";
7
- import { projectPaths } from "../lib/paths.js";
8
- import { readTaskFile, scanTasks } from "../lib/task.js";
9
- async function locateTaskFile(tasksDir, taskId) {
10
- const all = await scanTasks(tasksDir);
11
- const target = taskId.toUpperCase();
12
- for (const t of all) {
13
- if (t.id.toUpperCase() === target)
14
- return t.filePath;
15
- }
16
- return null;
17
- }
18
- export async function agentPromptCommand(name, taskId, options = {}) {
19
- const cwd = resolve(options.cwd ?? process.cwd());
20
- const paths = projectPaths(cwd);
21
- const agent = await findAgent(paths.vibeopsAgents, name);
22
- if (!agent) {
23
- const available = (await listAgents(paths.vibeopsAgents)).map((a) => a.meta.name);
24
- log.error(`Unknown agent: "${name}".`);
25
- if (available.length > 0) {
26
- log.info(`Available: ${available.join(", ")}`);
27
- }
28
- else {
29
- log.info(`No agents installed in ${paths.vibeopsAgents}. Run \`vibeops init\` first.`);
30
- }
31
- process.exitCode = 1;
32
- return;
33
- }
34
- const config = await readConfig(paths.root);
35
- let task;
36
- const looksLikeTaskId = /^TASK-\d+/i.test(taskId);
37
- if (looksLikeTaskId) {
38
- const filePath = await locateTaskFile(paths.docsTasks, taskId);
39
- if (!filePath) {
40
- log.error(`TASK not found: ${taskId} (looked in ${paths.docsTasks})`);
41
- process.exitCode = 1;
42
- return;
43
- }
44
- const meta = await readTaskFile(filePath);
45
- const body = await readText(filePath);
46
- task = { meta, body };
47
- }
48
- else if (taskId !== "(unspecified)" && taskId.length > 0) {
49
- log.warn(`"${taskId}" does not look like a TASK id (expected TASK-NNN). Continuing without TASK context.`);
50
- }
51
- const prompt = await buildPrompt({
52
- agent,
53
- config,
54
- task,
55
- projectRoot: paths.root,
56
- contextPaths: options.context ?? [],
57
- });
58
- log.raw(prompt.endsWith("\n") ? prompt : `${prompt}\n`);
59
- }
@@ -1,26 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { findAgent, listAgents } from "../agent/loader.js";
3
- import { log } from "../lib/logger.js";
4
- import { projectPaths } from "../lib/paths.js";
5
- export async function agentShowCommand(name, options = {}) {
6
- const cwd = resolve(options.cwd ?? process.cwd());
7
- const paths = projectPaths(cwd);
8
- const agent = await findAgent(paths.vibeopsAgents, name);
9
- if (!agent) {
10
- const available = (await listAgents(paths.vibeopsAgents)).map((a) => a.meta.name);
11
- log.error(`Unknown agent: "${name}".`);
12
- if (available.length > 0) {
13
- log.info(`Available: ${available.join(", ")}`);
14
- }
15
- else {
16
- log.info(`No agents installed in ${paths.vibeopsAgents}. Run \`vibeops init\` first.`);
17
- }
18
- process.exitCode = 1;
19
- return;
20
- }
21
- if (options.raw) {
22
- log.raw(agent.raw.endsWith("\n") ? agent.raw : `${agent.raw}\n`);
23
- return;
24
- }
25
- log.raw(agent.body.endsWith("\n") ? agent.body : `${agent.body}\n`);
26
- }
@@ -1,160 +0,0 @@
1
- import { relative, resolve } from "node:path";
2
- import { askYesNo } from "../lib/inquirer-helpers.js";
3
- import { readText } from "../lib/filesystem.js";
4
- import { bold, dim, green, log, yellow } from "../lib/logger.js";
5
- import { writeDoneSummary } from "../lib/mvp-done-summary.js";
6
- import { mergeTaskBranch } from "../lib/task-merge.js";
7
- import { projectPaths } from "../lib/paths.js";
8
- import { commandTaskNotFoundMessage, resolveCommandTask, } from "../lib/resolve-command-task.js";
9
- import { hasNonEmptySection, parseTaskFilename, readGitContext, readTaskFile, updateInlineStatus, } from "../lib/task.js";
10
- import { runDoneFollowUp } from "../lib/done-follow-up.js";
11
- import { gitAddPaths, listWorkingTreeRelPaths, partitionPathsForAutoCommit, readGitInfo, runGit, } from "../lib/git.js";
12
- function relOrAbs(root, p) {
13
- const r = relative(root, p);
14
- return r === "" ? "." : r.startsWith("..") ? p : r;
15
- }
16
- function commitMessageFor(taskId, title) {
17
- const trimmed = (title || taskId).trim();
18
- const slug = trimmed.replace(/^TASK-(?:\d+|mvp)\s*[·:\-]\s*/i, "").trim() || trimmed;
19
- return `feat(${taskId.toLowerCase()}): ${slug}`;
20
- }
21
- async function tryCommitDirty(cwd, message, dryRun) {
22
- const git = await readGitInfo(cwd);
23
- if (!git.isRepo || git.dirty !== true)
24
- return false;
25
- const { committable, excluded } = partitionPathsForAutoCommit(await listWorkingTreeRelPaths(cwd), { unmerged: [] });
26
- if (committable.length === 0) {
27
- if (excluded.length > 0) {
28
- log.warn("Dirty tree is only node_modules/.next-style paths — add them to .gitignore; commit skipped.");
29
- }
30
- return false;
31
- }
32
- if (excluded.length > 0) {
33
- log.warn(`Skipping ${excluded.length} build/install path(s) from auto-commit (e.g. ${excluded[0]}).`);
34
- }
35
- if (dryRun) {
36
- log.info(dim(` would: git add (${committable.length} paths) && git commit -m "${message}"`));
37
- return true;
38
- }
39
- try {
40
- await gitAddPaths(cwd, committable);
41
- await runGit(cwd, ["commit", "-q", "-m", message]);
42
- log.ok(`Committed: ${message}`);
43
- return true;
44
- }
45
- catch (e) {
46
- const msg = e instanceof Error ? e.message : String(e);
47
- log.warn(`Commit skipped (${msg}).`);
48
- return false;
49
- }
50
- }
51
- /** Finalize TASK-mvp: auto-fill Result/Test Result, summary md, merge, Notion. */
52
- export async function doneCommand(taskRef, options = {}) {
53
- const cwd = resolve(options.cwd ?? process.cwd());
54
- const paths = projectPaths(cwd);
55
- const dryRun = options.dryRun === true;
56
- const interactive = !dryRun && process.stdin.isTTY === true;
57
- const resolved = await resolveCommandTask(paths, cwd, taskRef);
58
- if (resolved === null) {
59
- log.error(commandTaskNotFoundMessage(taskRef));
60
- process.exitCode = 1;
61
- return;
62
- }
63
- const { taskFile, source } = resolved;
64
- if (source === "backlog-active") {
65
- log.info(dim(`No TASK-mvp — using ${resolved.taskId}.`));
66
- }
67
- const meta = await readTaskFile(taskFile);
68
- log.info(bold(`vibeops done ${meta.id}`));
69
- log.info(` ${dim("file")} ${relOrAbs(cwd, taskFile)}`);
70
- log.blank();
71
- if (options.skipSummary !== true) {
72
- log.step(bold("Summary + TASK sections"));
73
- await writeDoneSummary({
74
- cwd,
75
- taskId: meta.id,
76
- taskTitle: meta.title,
77
- taskFile,
78
- dryRun,
79
- refreshTaskSections: options.refreshTaskSections === true,
80
- });
81
- log.blank();
82
- }
83
- const body = await readText(taskFile);
84
- const resultOk = hasNonEmptySection(body, "Result");
85
- const testResultOk = hasNonEmptySection(body, "Test Result");
86
- log.info(bold("Required sections"));
87
- let missing = 0;
88
- for (const [label, ok] of [
89
- ["Result", resultOk],
90
- ["Test Result", testResultOk],
91
- ]) {
92
- if (!ok)
93
- missing++;
94
- log.info(` ${ok ? green("✓") : yellow("·")} ${label}`);
95
- }
96
- log.blank();
97
- if (missing > 0) {
98
- log.error(`Could not auto-fill Result / Test Result. Connect Codex OAuth, Cursor Agent CLI, or OPENAI_API_KEY — or fill sections manually.`);
99
- process.exitCode = 1;
100
- return;
101
- }
102
- if (dryRun) {
103
- log.info(bold("dry-run — would:"));
104
- log.info(` · Status → Done`);
105
- log.info(` · commit if dirty, then merge (unless --no-merge)`);
106
- log.info(` · Notion sync (if enabled)`);
107
- await runDoneFollowUp({
108
- cwd,
109
- taskFile,
110
- taskTitle: meta.title,
111
- dryRun: true,
112
- noNotionSync: options.noNotionSync,
113
- interactive,
114
- });
115
- return;
116
- }
117
- await updateInlineStatus(taskFile, "done");
118
- log.ok(`Status → Done`);
119
- const parts = parseTaskFilename(taskFile);
120
- const commitMsg = commitMessageFor(parts.id, meta.title);
121
- await tryCommitDirty(cwd, commitMsg, false);
122
- const gitCtx = await readGitContext(taskFile);
123
- if (options.noMerge !== true && gitCtx !== null) {
124
- let doMerge = !interactive;
125
- if (interactive) {
126
- doMerge = await askYesNo({
127
- message: `Push, merge into ${gitCtx.baseBranch}, and delete ${gitCtx.taskBranch}?`,
128
- nonInteractive: false,
129
- defaultValue: true,
130
- });
131
- }
132
- if (doMerge) {
133
- const mergeResult = await mergeTaskBranch({
134
- cwd,
135
- taskId: meta.id,
136
- taskTitle: meta.title,
137
- gitCtx,
138
- mode: options.mergeViaPr === true ? "pr" : "direct",
139
- allowDirty: options.allowDirty,
140
- });
141
- if (!mergeResult.ok) {
142
- process.exitCode = 1;
143
- }
144
- }
145
- else {
146
- log.info(dim("Merge skipped — run `vibeops next` later."));
147
- }
148
- }
149
- else if (gitCtx === null) {
150
- log.info(dim("No Git Context — merge skipped. Run `vibeops start` before the next MVP."));
151
- }
152
- await runDoneFollowUp({
153
- cwd,
154
- taskFile,
155
- taskTitle: meta.title,
156
- dryRun: false,
157
- noNotionSync: options.noNotionSync,
158
- interactive,
159
- });
160
- }