@goodtek/vibeops 0.4.0 → 0.7.22
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.
- package/CHANGELOG.md +177 -0
- package/README.md +34 -54
- package/dist/cli.js +156 -214
- package/dist/commands/done.js +160 -0
- package/dist/commands/next.js +283 -0
- package/dist/commands/plan.js +53 -250
- package/dist/commands/rollback.js +126 -0
- package/dist/commands/start.js +129 -0
- package/dist/commands/task-add.js +200 -0
- package/dist/commands/task-status.js +122 -0
- package/dist/lib/brief.js +1 -1
- package/dist/lib/cursor-task-prompt.js +60 -0
- package/dist/lib/done-follow-up.js +308 -0
- package/dist/lib/git.js +189 -7
- package/dist/lib/guide-state.js +69 -0
- package/dist/lib/llm-complete.js +62 -0
- package/dist/lib/mvp-artifacts.js +204 -0
- package/dist/lib/mvp-constants.js +15 -0
- package/dist/lib/mvp-diff-summary.js +43 -0
- package/dist/lib/mvp-done-summary.js +335 -0
- package/dist/lib/resolve-command-task.js +40 -0
- package/dist/lib/slug.js +11 -0
- package/dist/lib/task-add-build-prompt.js +39 -0
- package/dist/lib/task-add-cursor.js +81 -0
- package/dist/lib/task-add-llm.js +268 -0
- package/dist/lib/task-merge.js +220 -0
- package/dist/lib/task-scaffold.js +56 -74
- package/dist/lib/task-status-brief.js +132 -0
- package/dist/lib/task.js +89 -37
- package/dist/lib/workflow-guide.js +485 -0
- package/dist/status/collector.js +2 -2
- package/dist/status/format.js +6 -2
- package/package.json +2 -1
- package/templates/docs/tasks/TASK-000-template.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,265 +1,207 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import {
|
|
4
|
-
import { agentPromptCommand } from "./commands/agent-prompt.js";
|
|
5
|
-
import { agentShowCommand } from "./commands/agent-show.js";
|
|
3
|
+
import { doneCommand } from "./commands/done.js";
|
|
6
4
|
import { githubInitCommand } from "./commands/github-init.js";
|
|
7
5
|
import { githubStatusCommand } from "./commands/github-status.js";
|
|
8
6
|
import { initCommand } from "./commands/init.js";
|
|
7
|
+
import { nextCommand } from "./commands/next.js";
|
|
9
8
|
import { notionInitCommand } from "./commands/notion-init.js";
|
|
10
9
|
import { notionSyncCommand } from "./commands/notion-sync.js";
|
|
11
10
|
import { notionTestCommand } from "./commands/notion-test.js";
|
|
12
11
|
import { planCommand } from "./commands/plan.js";
|
|
12
|
+
import { rollbackCommand } from "./commands/rollback.js";
|
|
13
|
+
import { startCommand } from "./commands/start.js";
|
|
13
14
|
import { statusCommand } from "./commands/status.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { taskGenerateCommand } from "./commands/task-generate.js";
|
|
17
|
-
import { taskPullCommand } from "./commands/task-pull.js";
|
|
18
|
-
import { taskRollbackCommand } from "./commands/task-rollback.js";
|
|
19
|
-
import { taskStartCommand } from "./commands/task-start.js";
|
|
15
|
+
import { taskAddCommand } from "./commands/task-add.js";
|
|
16
|
+
import { taskStatusCommand } from "./commands/task-status.js";
|
|
20
17
|
import { VERSION } from "./version.js";
|
|
21
18
|
const program = new Command();
|
|
22
19
|
program
|
|
23
20
|
.name("vibeops")
|
|
24
|
-
.description("VibeOps —
|
|
25
|
-
"
|
|
26
|
-
" and Git/Notion workflows into a project, then drives work one TASK at a time.")
|
|
21
|
+
.description("VibeOps — plan an MVP, build in Cursor (drag mvp-build.md), finish with Git rails.\n" +
|
|
22
|
+
" Commands: init · plan · start · done · next · status · task add · task status (+ optional notion · github · rollback)")
|
|
27
23
|
.version(VERSION, "-v, --version", "Print the VibeOps version");
|
|
28
24
|
program
|
|
29
25
|
.command("init")
|
|
30
|
-
.description("Install the VibeOps workflow
|
|
31
|
-
.option("--dry-run", "Show what would be created without writing
|
|
32
|
-
.option("--force", "Overwrite existing files
|
|
33
|
-
.option("--cwd <path>", "
|
|
34
|
-
.option("--name <projectName>", "Project name
|
|
35
|
-
.option("--git", "Initialize
|
|
36
|
-
.option("--no-git", "Skip Git
|
|
37
|
-
.option("--initial-commit", "
|
|
38
|
-
.option("--no-initial-commit", "
|
|
39
|
-
.option("--default-branch <name>", "Default
|
|
40
|
-
.option("--commit-message <message>", "Initial commit message
|
|
41
|
-
.action(async (
|
|
42
|
-
await initCommand(
|
|
43
|
-
});
|
|
44
|
-
program
|
|
45
|
-
.command("status")
|
|
46
|
-
.description("Show VibeOps installation, TASK counts, and integration state")
|
|
47
|
-
.option("--json", "Print machine-readable JSON")
|
|
48
|
-
.option("--cwd <path>", "Inspect a different directory")
|
|
49
|
-
.action(async (options) => {
|
|
50
|
-
await statusCommand(options);
|
|
26
|
+
.description("Install the VibeOps workflow into the current directory")
|
|
27
|
+
.option("--dry-run", "Show what would be created without writing files")
|
|
28
|
+
.option("--force", "Overwrite existing files")
|
|
29
|
+
.option("--cwd <path>", "Target directory")
|
|
30
|
+
.option("--name <projectName>", "Project name in .vibeops.json")
|
|
31
|
+
.option("--git", "Initialize Git without prompting")
|
|
32
|
+
.option("--no-git", "Skip Git")
|
|
33
|
+
.option("--initial-commit", "Create initial commit")
|
|
34
|
+
.option("--no-initial-commit", "Skip initial commit")
|
|
35
|
+
.option("--default-branch <name>", "Default branch (default main)")
|
|
36
|
+
.option("--commit-message <message>", "Initial commit message")
|
|
37
|
+
.action(async (opts) => {
|
|
38
|
+
await initCommand(opts);
|
|
51
39
|
});
|
|
52
40
|
program
|
|
53
41
|
.command("plan")
|
|
54
|
-
.description("
|
|
55
|
-
.option("--idea <text>", "One-line idea
|
|
56
|
-
.option("--from <path>", "
|
|
57
|
-
.option("--
|
|
58
|
-
.option("--
|
|
59
|
-
.option("--
|
|
60
|
-
.option("--
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.option("--push", "With --apply-planner: run git push after commit (TTY: optional `vibeops github init` before push; skips the push Yes/No prompt)")
|
|
67
|
-
.option("--no-notion-sync", "With --apply-planner: skip Notion sync even when the brief enables it")
|
|
68
|
-
.action(async (options) => {
|
|
69
|
-
const provider = options.provider === "openai" ||
|
|
70
|
-
options.provider === "codex-oauth" ||
|
|
71
|
-
options.provider === "cursor-agent"
|
|
72
|
-
? options.provider
|
|
42
|
+
.description("LLM planning → brief + TASK-mvp + .vibeops/generated/mvp-build.md")
|
|
43
|
+
.option("--idea <text>", "One-line idea (optional Name: idea prefix)")
|
|
44
|
+
.option("--from <path>", "Regenerate MVP artifacts from an existing brief")
|
|
45
|
+
.option("--non-interactive", "Placeholders only (CI); no LLM")
|
|
46
|
+
.option("--provider <id>", "openai | codex-oauth | cursor-agent")
|
|
47
|
+
.option("--model <id>", "LLM model id")
|
|
48
|
+
.option("--cwd <path>", "Target directory")
|
|
49
|
+
.action(async (opts) => {
|
|
50
|
+
const provider = opts.provider === "openai" ||
|
|
51
|
+
opts.provider === "codex-oauth" ||
|
|
52
|
+
opts.provider === "cursor-agent"
|
|
53
|
+
? opts.provider
|
|
73
54
|
: undefined;
|
|
74
55
|
await planCommand({
|
|
75
|
-
idea:
|
|
76
|
-
from:
|
|
77
|
-
|
|
78
|
-
nonInteractive: options.nonInteractive,
|
|
79
|
-
legacyWizard: options.legacyWizard === true,
|
|
56
|
+
idea: opts.idea,
|
|
57
|
+
from: opts.from,
|
|
58
|
+
nonInteractive: opts.nonInteractive,
|
|
80
59
|
provider,
|
|
81
|
-
llmModel:
|
|
82
|
-
cwd:
|
|
83
|
-
applyPlanner: options.applyPlanner === true,
|
|
84
|
-
applyPlannerDryRun: options.applyDryRun === true,
|
|
85
|
-
noGitCommit: options.noGitCommit === true,
|
|
86
|
-
gitPush: options.push === true,
|
|
87
|
-
noNotionSync: options.noNotionSync === true,
|
|
60
|
+
llmModel: opts.model,
|
|
61
|
+
cwd: opts.cwd,
|
|
88
62
|
});
|
|
89
63
|
});
|
|
90
|
-
|
|
91
|
-
.command("
|
|
92
|
-
.description("
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
.
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
.action(async (options) => {
|
|
99
|
-
await agentListCommand(options);
|
|
100
|
-
});
|
|
101
|
-
agent
|
|
102
|
-
.command("show <name>")
|
|
103
|
-
.description("Print an agent definition body")
|
|
104
|
-
.option("--raw", "Include frontmatter in the output")
|
|
105
|
-
.option("--cwd <path>", "Inspect a different directory")
|
|
106
|
-
.action(async (name, options) => {
|
|
107
|
-
await agentShowCommand(name, options);
|
|
64
|
+
program
|
|
65
|
+
.command("start [taskRef]")
|
|
66
|
+
.description("Create task branch (default: TASK-mvp, or active backlog TASK if no MVP file)")
|
|
67
|
+
.option("--dry-run", "Show plan only")
|
|
68
|
+
.option("--allow-dirty", "Allow dirty working tree")
|
|
69
|
+
.option("--cwd <path>", "Target directory")
|
|
70
|
+
.action(async (taskRef, opts) => {
|
|
71
|
+
await startCommand(taskRef, opts);
|
|
108
72
|
});
|
|
109
|
-
|
|
110
|
-
.command("
|
|
111
|
-
.description("
|
|
112
|
-
.option("--
|
|
113
|
-
.option("--
|
|
114
|
-
.
|
|
115
|
-
|
|
73
|
+
program
|
|
74
|
+
.command("done [taskRef]")
|
|
75
|
+
.description("Finish TASK: auto-fill Result/Test Result + summary md, merge to main, Notion sync (default: TASK-mvp or active backlog)")
|
|
76
|
+
.option("--dry-run", "Show plan only")
|
|
77
|
+
.option("--no-merge", "Skip push / merge / branch delete")
|
|
78
|
+
.option("--skip-summary", "Skip last-done-summary.md and TASK auto-fill")
|
|
79
|
+
.option("--refresh-task-sections", "Overwrite existing Result / Test Result")
|
|
80
|
+
.option("--merge-via-pr", "Merge via gh PR")
|
|
81
|
+
.option("--allow-dirty", "Allow dirty tree during merge")
|
|
82
|
+
.option("--no-notion-sync", "Skip automatic Notion sync after done")
|
|
83
|
+
.option("--cwd <path>", "Target directory")
|
|
84
|
+
.action(async (taskRef, opts) => {
|
|
85
|
+
await doneCommand(taskRef, opts);
|
|
116
86
|
});
|
|
117
|
-
|
|
118
|
-
.command("
|
|
119
|
-
.description("
|
|
120
|
-
|
|
121
|
-
.
|
|
122
|
-
.
|
|
123
|
-
.option("--
|
|
124
|
-
.option("--
|
|
125
|
-
.option("--
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
.option("--dry-run", "Print the plan without writing or modifying files")
|
|
129
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
130
|
-
.action(async (options) => {
|
|
131
|
-
await taskGenerateCommand(options);
|
|
87
|
+
program
|
|
88
|
+
.command("next")
|
|
89
|
+
.description("Interactive guide for the MVP workflow (↑/↓ · Yes/No)")
|
|
90
|
+
.option("--dry-run", "On runnable steps, print only")
|
|
91
|
+
.option("--non-interactive", "Print panel once")
|
|
92
|
+
.option("--execute", "With --non-interactive, run the runnable step once")
|
|
93
|
+
.option("--merge-via-pr", "On finish: merge via GitHub PR")
|
|
94
|
+
.option("--allow-dirty", "Allow dirty tree on merge")
|
|
95
|
+
.option("--cwd <path>", "Target directory")
|
|
96
|
+
.action(async (opts) => {
|
|
97
|
+
await nextCommand(opts);
|
|
132
98
|
});
|
|
133
|
-
|
|
134
|
-
.command("
|
|
135
|
-
.description("
|
|
136
|
-
.option("--
|
|
137
|
-
.option("--
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
.action(async (taskId, options) => {
|
|
141
|
-
await taskStartCommand(taskId, options);
|
|
99
|
+
program
|
|
100
|
+
.command("status")
|
|
101
|
+
.description("Installation, TASK-mvp state, Git, Notion, GitHub")
|
|
102
|
+
.option("--json", "JSON output")
|
|
103
|
+
.option("--cwd <path>", "Target directory")
|
|
104
|
+
.action(async (opts) => {
|
|
105
|
+
await statusCommand(opts);
|
|
142
106
|
});
|
|
107
|
+
const task = program.command("task").description("Backlog TASK files");
|
|
143
108
|
task
|
|
144
|
-
.command("
|
|
145
|
-
.description("
|
|
146
|
-
.option("--
|
|
147
|
-
.option("--
|
|
148
|
-
.option("--
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
109
|
+
.command("add")
|
|
110
|
+
.description("Interactive: close open TASK (optional), create next TASK-NNN, branch checkout, In Progress")
|
|
111
|
+
.option("--dry-run", "Show plan only (no writes, no LLM planning session)")
|
|
112
|
+
.option("--non-interactive", "CI: minimal scaffold without prompts (optional --idea)")
|
|
113
|
+
.option("--idea <text>", "Short description (CI / smoke only)")
|
|
114
|
+
.option("--cwd <path>", "Target directory")
|
|
115
|
+
.action(async (opts) => {
|
|
116
|
+
await taskAddCommand({
|
|
117
|
+
dryRun: Boolean(opts.dryRun),
|
|
118
|
+
nonInteractive: Boolean(opts.nonInteractive),
|
|
119
|
+
idea: opts.idea,
|
|
120
|
+
cwd: opts.cwd,
|
|
153
121
|
});
|
|
154
122
|
});
|
|
155
123
|
task
|
|
156
|
-
.command("
|
|
157
|
-
.description("
|
|
158
|
-
.option("--
|
|
159
|
-
.option("--
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.command("done <taskId>")
|
|
166
|
-
.description("Validate Result/Test Result, move Status to Review, and print a commit message (no auto-commit)")
|
|
167
|
-
.option("--dry-run", "Print the plan without touching files")
|
|
168
|
-
.option("--finalize", "Move Status to Done instead of Review (use after human review)")
|
|
169
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
170
|
-
.action(async (taskId, options) => {
|
|
171
|
-
await taskDoneCommand(taskId, options);
|
|
172
|
-
});
|
|
173
|
-
task
|
|
174
|
-
.command("rollback <taskId>")
|
|
175
|
-
.description("Default: advisory only. --confirm: non-destructive rollback. --confirm-destructive: hard reset.")
|
|
176
|
-
.option("--confirm", "Allow non-destructive rollback execution (branch-delete etc.)")
|
|
177
|
-
.option("--confirm-destructive", "Allow destructive rollback execution (reset --hard etc.)")
|
|
178
|
-
.option("--strategy <name>", "branch-delete | reset-base | revert-merge (default branch-delete)")
|
|
179
|
-
.option("--keep-branch", "Keep the task branch even when using branch-delete")
|
|
180
|
-
.option("--dry-run", "Print the plan without running any git commands, even with --confirm")
|
|
181
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
182
|
-
.action(async (taskId, options) => {
|
|
183
|
-
await taskRollbackCommand(taskId, options);
|
|
184
|
-
});
|
|
185
|
-
task
|
|
186
|
-
.command("pull")
|
|
187
|
-
.description("Generate `docs/tasks/*.md` skeletons from Notion Tasks DB rows (defaults to Status = Planned)")
|
|
188
|
-
.option("--dry-run", "Print the plan without touching files or Notion")
|
|
189
|
-
.option("--json", "Print machine-readable JSON")
|
|
190
|
-
.option("--status <name>", "Notion Status values to pull (comma-separated, e.g. 'Planned,Ready'). Default `Planned`")
|
|
191
|
-
.option("--limit <number>", "Maximum rows to pull from Notion (default 20, max 100)")
|
|
192
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
193
|
-
.option("--verbose", "Print a decision trace per considered row (taskId / pageId / docsPath / reason)")
|
|
194
|
-
.action(async (options) => {
|
|
195
|
-
await taskPullCommand(options);
|
|
124
|
+
.command("status")
|
|
125
|
+
.description("Briefing on current TASK progress and recommended next step")
|
|
126
|
+
.option("--json", "JSON output")
|
|
127
|
+
.option("--cwd <path>", "Target directory")
|
|
128
|
+
.action(async (opts) => {
|
|
129
|
+
await taskStatusCommand({
|
|
130
|
+
json: Boolean(opts.json),
|
|
131
|
+
cwd: opts.cwd,
|
|
132
|
+
});
|
|
196
133
|
});
|
|
197
|
-
|
|
198
|
-
.command("
|
|
199
|
-
.description("
|
|
134
|
+
program
|
|
135
|
+
.command("rollback [taskRef]")
|
|
136
|
+
.description("Advisory rollback for TASK-mvp (--confirm / --confirm-destructive to execute)")
|
|
137
|
+
.option("--confirm", "Run non-destructive rollback")
|
|
138
|
+
.option("--confirm-destructive", "Allow destructive rollback (reset --hard)")
|
|
139
|
+
.option("--strategy <name>", "branch-delete | reset-base | revert-merge")
|
|
140
|
+
.option("--keep-branch", "Keep task branch when deleting")
|
|
141
|
+
.option("--dry-run", "Print plan only")
|
|
142
|
+
.option("--cwd <path>", "Target directory")
|
|
143
|
+
.action(async (taskRef, opts) => {
|
|
144
|
+
await rollbackCommand(taskRef, opts);
|
|
145
|
+
});
|
|
146
|
+
const notion = program.command("notion").description("Optional Notion dashboard sync");
|
|
200
147
|
notion
|
|
201
148
|
.command("init")
|
|
202
|
-
.description("
|
|
203
|
-
.option("--dry-run", "
|
|
204
|
-
.option("--enable", "
|
|
205
|
-
.option("--projects-db <id>", "
|
|
206
|
-
.option("--tasks-db <id>", "
|
|
207
|
-
.option("--non-interactive", "
|
|
208
|
-
.option("--cwd <path>", "
|
|
209
|
-
.action(async (
|
|
210
|
-
await notionInitCommand(
|
|
149
|
+
.description("Configure Notion in .vibeops.json and .vibeops.env")
|
|
150
|
+
.option("--dry-run", "Plan only")
|
|
151
|
+
.option("--enable", "Enable Notion")
|
|
152
|
+
.option("--projects-db <id>", "Projects database id")
|
|
153
|
+
.option("--tasks-db <id>", "Tasks database id")
|
|
154
|
+
.option("--non-interactive", "No prompts")
|
|
155
|
+
.option("--cwd <path>", "Target directory")
|
|
156
|
+
.action(async (opts) => {
|
|
157
|
+
await notionInitCommand(opts);
|
|
211
158
|
});
|
|
212
159
|
notion
|
|
213
160
|
.command("test")
|
|
214
|
-
.description("
|
|
215
|
-
.option("--json", "
|
|
216
|
-
.option("--debug-shape", "
|
|
217
|
-
.option("--cwd <path>", "
|
|
218
|
-
.action(async (
|
|
219
|
-
await notionTestCommand(
|
|
161
|
+
.description("Verify Notion token and database schema")
|
|
162
|
+
.option("--json", "JSON output")
|
|
163
|
+
.option("--debug-shape", "Print API shape diagnostics")
|
|
164
|
+
.option("--cwd <path>", "Target directory")
|
|
165
|
+
.action(async (opts) => {
|
|
166
|
+
await notionTestCommand(opts);
|
|
220
167
|
});
|
|
221
168
|
notion
|
|
222
169
|
.command("sync")
|
|
223
|
-
.description("Push
|
|
224
|
-
.option("--dry-run", "
|
|
225
|
-
.option("--json", "
|
|
226
|
-
.option("--only-tasks", "
|
|
227
|
-
.option("--only-project", "
|
|
228
|
-
.option("--cwd <path>", "
|
|
229
|
-
.action(async (
|
|
230
|
-
await notionSyncCommand(
|
|
231
|
-
});
|
|
232
|
-
const github = program
|
|
233
|
-
.command("github")
|
|
234
|
-
.description("GitHub repository integration");
|
|
170
|
+
.description("Push docs/project + TASK metadata to Notion")
|
|
171
|
+
.option("--dry-run", "Query only")
|
|
172
|
+
.option("--json", "JSON output")
|
|
173
|
+
.option("--only-tasks", "Tasks DB only")
|
|
174
|
+
.option("--only-project", "Project DB only")
|
|
175
|
+
.option("--cwd <path>", "Target directory")
|
|
176
|
+
.action(async (opts) => {
|
|
177
|
+
await notionSyncCommand(opts);
|
|
178
|
+
});
|
|
179
|
+
const github = program.command("github").description("Optional GitHub remote setup");
|
|
235
180
|
github
|
|
236
181
|
.command("status")
|
|
237
|
-
.description("
|
|
238
|
-
.option("--json", "
|
|
239
|
-
.option("--cwd <path>", "
|
|
240
|
-
.action(async (
|
|
241
|
-
await githubStatusCommand(
|
|
182
|
+
.description("gh auth, remotes, .vibeops.json github section")
|
|
183
|
+
.option("--json", "JSON output")
|
|
184
|
+
.option("--cwd <path>", "Target directory")
|
|
185
|
+
.action(async (opts) => {
|
|
186
|
+
await githubStatusCommand(opts);
|
|
242
187
|
});
|
|
243
188
|
github
|
|
244
189
|
.command("init")
|
|
245
|
-
.description("
|
|
246
|
-
.option("--dry-run", "
|
|
247
|
-
.option("--yes", "
|
|
248
|
-
.option("--owner <owner>", "GitHub owner
|
|
249
|
-
.option("--repo <repo>", "
|
|
250
|
-
.option("--public", "
|
|
251
|
-
.option("--private", "
|
|
252
|
-
.option("--remote <name>", "
|
|
253
|
-
.option("--connect <ownerOrUrl>", "
|
|
254
|
-
.option("--no-package-update", "
|
|
255
|
-
.option("--cwd <path>", "
|
|
256
|
-
.action(async (
|
|
190
|
+
.description("Connect or create a GitHub repo and update config")
|
|
191
|
+
.option("--dry-run", "Plan only")
|
|
192
|
+
.option("--yes", "Non-interactive defaults")
|
|
193
|
+
.option("--owner <owner>", "GitHub owner")
|
|
194
|
+
.option("--repo <repo>", "Repo name")
|
|
195
|
+
.option("--public", "Public repo")
|
|
196
|
+
.option("--private", "Private repo")
|
|
197
|
+
.option("--remote <name>", "Remote name (default origin)")
|
|
198
|
+
.option("--connect <ownerOrUrl>", "Existing repo")
|
|
199
|
+
.option("--no-package-update", "Skip package.json repo fields")
|
|
200
|
+
.option("--cwd <path>", "Target directory")
|
|
201
|
+
.action(async (opts) => {
|
|
257
202
|
await githubInitCommand({
|
|
258
|
-
...
|
|
259
|
-
|
|
260
|
-
// Our command type still accepts the legacy `noPackageUpdate`
|
|
261
|
-
// shape for tests / direct invocation.
|
|
262
|
-
noPackageUpdate: options.packageUpdate === false,
|
|
203
|
+
...opts,
|
|
204
|
+
noPackageUpdate: opts.packageUpdate === false,
|
|
263
205
|
});
|
|
264
206
|
});
|
|
265
207
|
program.parseAsync(process.argv).catch((err) => {
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
}
|