@goodtek/vibeops 0.4.0 → 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.
- package/CHANGELOG.md +198 -1
- package/README.md +62 -402
- package/dist/bootstrap/installer.js +4 -16
- package/dist/cli.js +52 -244
- package/dist/commands/init.js +3 -3
- package/dist/commands/status.js +145 -8
- package/dist/commands/task-add.js +100 -0
- package/dist/commands/task-done.js +117 -74
- package/dist/lib/config.js +2 -118
- package/dist/lib/git.js +190 -8
- package/dist/lib/llm-complete.js +62 -0
- package/dist/lib/paths.js +7 -3
- package/dist/lib/plan-llm-openai.js +0 -63
- package/dist/lib/resolve-task.js +34 -0
- package/dist/lib/slug.js +11 -0
- package/dist/lib/task-add-llm.js +81 -0
- package/dist/lib/task-context.js +68 -0
- package/dist/lib/task-done-llm.js +148 -0
- package/dist/lib/task-merge.js +115 -0
- package/dist/lib/task-scaffold.js +70 -92
- package/dist/lib/task-start.js +72 -0
- package/dist/lib/task-summary.js +12 -239
- package/dist/lib/task.js +80 -37
- package/dist/types/config.js +0 -16
- package/package.json +3 -4
- package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
- package/templates/.cursor/rules/02-git-safety.mdc +11 -0
- package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
- package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
- package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
- package/templates/AGENTS.md +26 -87
- package/templates/docs/logs/README.md +4 -35
- package/templates/docs/project/03-architecture.md +10 -29
- package/templates/docs/project/05-current-state.md +7 -27
- package/templates/docs/project/06-decisions.md +6 -17
- package/templates/docs/tasks/TASK-000-template.md +9 -44
- package/dist/agent/loader.js +0 -71
- package/dist/agent/prompt.js +0 -66
- package/dist/commands/agent-list.js +0 -32
- package/dist/commands/agent-prompt.js +0 -59
- package/dist/commands/agent-show.js +0 -26
- package/dist/commands/github-init.js +0 -554
- package/dist/commands/github-status.js +0 -164
- package/dist/commands/notion-init.js +0 -764
- package/dist/commands/notion-sync.js +0 -405
- package/dist/commands/notion-test.js +0 -595
- package/dist/commands/plan.js +0 -343
- package/dist/commands/task-check.js +0 -155
- package/dist/commands/task-generate.js +0 -206
- package/dist/commands/task-pull.js +0 -277
- package/dist/commands/task-rollback.js +0 -174
- package/dist/commands/task-start.js +0 -102
- package/dist/lib/brief.js +0 -356
- package/dist/lib/github-cli.js +0 -247
- package/dist/lib/notion-client.js +0 -459
- package/dist/lib/notion-discovery.js +0 -671
- package/dist/lib/notion-env.js +0 -140
- package/dist/lib/notion-mappers.js +0 -148
- package/dist/lib/notion-schema.js +0 -272
- package/dist/lib/notion-sync.js +0 -337
- package/dist/lib/notion-target.js +0 -247
- package/dist/lib/package-json.js +0 -133
- package/dist/lib/plan-apply-planner.js +0 -131
- package/dist/lib/plan-codex-pkce.js +0 -234
- package/dist/lib/plan-llm-models.js +0 -237
- package/dist/lib/plan-llm-normalize.js +0 -146
- package/dist/lib/plan-llm-session.js +0 -682
- package/dist/lib/plan-post-apply-setup.js +0 -116
- package/dist/lib/project-docs.js +0 -95
- package/dist/lib/prompt-builder.js +0 -135
- package/dist/lib/task-generator.js +0 -183
- package/dist/lib/task-prompt.js +0 -23
- package/dist/lib/task-pull.js +0 -354
- package/dist/status/collector.js +0 -103
- package/dist/status/format.js +0 -177
- package/dist/types/brief.js +0 -126
- package/templates/.cursor/rules/00-project-governance.mdc +0 -28
- package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
- package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
- package/templates/.cursor/rules/03-git-safety.mdc +0 -30
- package/templates/.cursor/rules/04-docs-update.mdc +0 -22
- package/templates/.vibeops/agents/architect.md +0 -47
- package/templates/.vibeops/agents/builder.md +0 -38
- package/templates/.vibeops/agents/docs.md +0 -54
- package/templates/.vibeops/agents/orchestrator.md +0 -40
- package/templates/.vibeops/agents/planner.md +0 -60
- package/templates/.vibeops/agents/recovery.md +0 -49
- package/templates/.vibeops/agents/reviewer.md +0 -47
- package/templates/.vibeops/agents/tester.md +0 -43
- package/templates/.vibeops/prompts/create-plan.md +0 -33
- package/templates/.vibeops/prompts/generate-tasks.md +0 -41
- package/templates/.vibeops/prompts/implement-task.md +0 -39
- package/templates/.vibeops/prompts/review-task.md +0 -34
- package/templates/.vibeops/prompts/rollback.md +0 -32
- package/templates/.vibeops/prompts/start-project.md +0 -39
- package/templates/.vibeops/workflows/notion-sync.md +0 -53
- package/templates/.vibeops/workflows/project-start.md +0 -73
- package/templates/.vibeops/workflows/rollback.md +0 -45
- package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
- package/templates/docs/project/00-overview.md +0 -27
- package/templates/docs/project/01-requirements.md +0 -30
- package/templates/docs/project/02-mvp-scope.md +0 -36
- package/templates/docs/project/04-tech-stack.md +0 -29
- package/templates/docs/project/07-backlog.md +0 -23
- package/templates/docs/project/08-env.md +0 -29
- package/templates/docs/project/09-deployment.md +0 -28
package/dist/cli.js
CHANGED
|
@@ -1,265 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { agentListCommand } from "./commands/agent-list.js";
|
|
4
|
-
import { agentPromptCommand } from "./commands/agent-prompt.js";
|
|
5
|
-
import { agentShowCommand } from "./commands/agent-show.js";
|
|
6
|
-
import { githubInitCommand } from "./commands/github-init.js";
|
|
7
|
-
import { githubStatusCommand } from "./commands/github-status.js";
|
|
8
3
|
import { initCommand } from "./commands/init.js";
|
|
9
|
-
import { notionInitCommand } from "./commands/notion-init.js";
|
|
10
|
-
import { notionSyncCommand } from "./commands/notion-sync.js";
|
|
11
|
-
import { notionTestCommand } from "./commands/notion-test.js";
|
|
12
|
-
import { planCommand } from "./commands/plan.js";
|
|
13
4
|
import { statusCommand } from "./commands/status.js";
|
|
14
|
-
import {
|
|
5
|
+
import { taskAddCommand } from "./commands/task-add.js";
|
|
15
6
|
import { taskDoneCommand } from "./commands/task-done.js";
|
|
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";
|
|
20
7
|
import { VERSION } from "./version.js";
|
|
21
8
|
const program = new Command();
|
|
22
9
|
program
|
|
23
10
|
.name("vibeops")
|
|
24
|
-
.description("VibeOps —
|
|
25
|
-
" It installs docs, Cursor rules, AGENTS.md, agents, TASK templates,\n" +
|
|
26
|
-
" and Git/Notion workflows into a project, then drives work one TASK at a time.")
|
|
11
|
+
.description("VibeOps v3 — Cursor-friendly TASK workflow (init · task add · task done · status)")
|
|
27
12
|
.version(VERSION, "-v, --version", "Print the VibeOps version");
|
|
28
13
|
program
|
|
29
14
|
.command("init")
|
|
30
|
-
.description("Install the VibeOps
|
|
31
|
-
.option("--dry-run", "Show what would be created
|
|
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);
|
|
51
|
-
});
|
|
52
|
-
program
|
|
53
|
-
.command("plan")
|
|
54
|
-
.description("Requires `vibeops init`: LLM planning → ProjectBrief + planning prompt; optional --apply-planner runs the planner via the same LLM (docs + TASKs, commit, push, Notion). Use --legacy-wizard for the fixed 20-question flow")
|
|
55
|
-
.option("--idea <text>", "One-line idea default (use `Name: idea` to extract the project name)")
|
|
56
|
-
.option("--from <path>", "Read an existing brief markdown and regenerate the prompt")
|
|
57
|
-
.option("--output <path>", "Output path for the Cursor planning prompt (default `.vibeops/generated/plan-prompt.md`)")
|
|
58
|
-
.option("--non-interactive", "Skip prompts and use the supplied values plus safe placeholders (no LLM)")
|
|
59
|
-
.option("--legacy-wizard", "Use the original 20-question wizard instead of an LLM planning session")
|
|
60
|
-
.option("--provider <id>", "LLM provider when several are available: openai | codex-oauth | cursor-agent")
|
|
61
|
-
.option("--model <id>", "LLM model id for the selected provider (skips interactive model list; same as setting VIBEOPS_*_MODEL for that path)")
|
|
62
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
63
|
-
.option("--apply-planner", "After writing the brief + prompt: call the same LLM provider to emit docs/project + docs/tasks; then (TTY) ask commit → push → Notion in order, or (--non-interactive) commit unless --no-git-commit, push only with --push, Notion unless --no-notion-sync")
|
|
64
|
-
.option("--apply-dry-run", "With --apply-planner (or alone): call the LLM and list files that would be written; no writes, git, or Notion")
|
|
65
|
-
.option("--no-git-commit", "With --apply-planner: skip git commit after writes")
|
|
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
|
|
73
|
-
: undefined;
|
|
74
|
-
await planCommand({
|
|
75
|
-
idea: options.idea,
|
|
76
|
-
from: options.from,
|
|
77
|
-
output: options.output,
|
|
78
|
-
nonInteractive: options.nonInteractive,
|
|
79
|
-
legacyWizard: options.legacyWizard === true,
|
|
80
|
-
provider,
|
|
81
|
-
llmModel: typeof options.model === "string" ? options.model : undefined,
|
|
82
|
-
cwd: options.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,
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
const agent = program
|
|
91
|
-
.command("agent")
|
|
92
|
-
.description("Inspect the `.vibeops/agents/*` agent definitions");
|
|
93
|
-
agent
|
|
94
|
-
.command("list")
|
|
95
|
-
.description("List available agents")
|
|
96
|
-
.option("--json", "Print machine-readable JSON")
|
|
97
|
-
.option("--cwd <path>", "Inspect a different directory")
|
|
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);
|
|
108
|
-
});
|
|
109
|
-
agent
|
|
110
|
-
.command("prompt <name> <taskId>")
|
|
111
|
-
.description("Print a Cursor-ready prompt built from the agent + TASK context")
|
|
112
|
-
.option("--context <path...>", "Additional context file paths")
|
|
113
|
-
.option("--cwd <path>", "Inspect a different directory")
|
|
114
|
-
.action(async (name, taskId, options) => {
|
|
115
|
-
await agentPromptCommand(name, taskId, options);
|
|
116
|
-
});
|
|
117
|
-
const task = program
|
|
118
|
-
.command("task")
|
|
119
|
-
.description("TASK lifecycle commands");
|
|
120
|
-
task
|
|
121
|
-
.command("generate")
|
|
122
|
-
.description("Build a Cursor prompt for generating TASK files, or with --scaffold write skeleton TASK markdown")
|
|
123
|
-
.option("--from <path>", "Primary backlog/brief markdown to feed into the prompt")
|
|
124
|
-
.option("--output <path>", "Output path for the generated prompt (default `.vibeops/generated/task-generate-prompt.md`)")
|
|
125
|
-
.option("--count <number>", "Suggested TASK count for Cursor (default 8, warns above 20)")
|
|
126
|
-
.option("--phase <name>", "Generate TASKs for a specific phase label only (e.g. 'MVP 4')")
|
|
127
|
-
.option("--scaffold", "Write skeleton TASK markdown files directly, without an LLM")
|
|
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);
|
|
132
|
-
});
|
|
133
|
-
task
|
|
134
|
-
.command("start <taskId>")
|
|
135
|
-
.description("Confirm a clean working tree (or docs-only governance changes), create the task branch, record Status/Git context, and print a Builder prompt")
|
|
136
|
-
.option("--dry-run", "Print the plan without touching files or Git")
|
|
137
|
-
.option("--allow-dirty", "Proceed even if the Git working tree is dirty (including non-doc changes)")
|
|
138
|
-
.option("--agent <name>", "Agent to build the prompt with (default `builder`)")
|
|
139
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
140
|
-
.action(async (taskId, options) => {
|
|
141
|
-
await taskStartCommand(taskId, options);
|
|
142
|
-
});
|
|
15
|
+
.description("Install the VibeOps v3 project tree")
|
|
16
|
+
.option("--dry-run", "Show what would be created")
|
|
17
|
+
.option("--force", "Overwrite existing files")
|
|
18
|
+
.option("--cwd <path>", "Target directory")
|
|
19
|
+
.option("--name <projectName>", "Project name in .vibeops.json")
|
|
20
|
+
.option("--git", "Initialize Git")
|
|
21
|
+
.option("--no-git", "Skip Git")
|
|
22
|
+
.option("--initial-commit", "Create initial commit")
|
|
23
|
+
.option("--no-initial-commit", "Skip initial commit")
|
|
24
|
+
.option("--default-branch <name>", "Default branch (default main)")
|
|
25
|
+
.option("--commit-message <message>", "Initial commit message")
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
await initCommand(opts);
|
|
28
|
+
});
|
|
29
|
+
const task = program.command("task").description("TASK lifecycle");
|
|
143
30
|
task
|
|
144
|
-
.command("
|
|
145
|
-
.description("
|
|
146
|
-
.option("--
|
|
147
|
-
.option("--
|
|
148
|
-
.option("--
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
31
|
+
.command("add")
|
|
32
|
+
.description("Create next TASK-NNN, task branch, In Progress (guide-only if one is open)")
|
|
33
|
+
.option("--dry-run", "Plan only")
|
|
34
|
+
.option("--non-interactive", "CI: minimal scaffold (optional --idea)")
|
|
35
|
+
.option("--idea <text>", "Short description (CI)")
|
|
36
|
+
.option("--cwd <path>", "Target directory")
|
|
37
|
+
.action(async (opts) => {
|
|
38
|
+
await taskAddCommand({
|
|
39
|
+
dryRun: Boolean(opts.dryRun),
|
|
40
|
+
nonInteractive: Boolean(opts.nonInteractive),
|
|
41
|
+
idea: opts.idea,
|
|
42
|
+
cwd: opts.cwd,
|
|
153
43
|
});
|
|
154
44
|
});
|
|
155
45
|
task
|
|
156
|
-
.command("
|
|
157
|
-
.description("
|
|
158
|
-
.option("--
|
|
159
|
-
.option("--
|
|
160
|
-
.option("--
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
196
|
-
});
|
|
197
|
-
const notion = program
|
|
198
|
-
.command("notion")
|
|
199
|
-
.description("Notion dashboard sync");
|
|
200
|
-
notion
|
|
201
|
-
.command("init")
|
|
202
|
-
.description("Interactive setup: use arrow keys + Enter to pick Yes/No, then write `.vibeops.json` `notion` section and `.vibeops.env(.example)`")
|
|
203
|
-
.option("--dry-run", "Print the plan without changing files (no interactive prompts)")
|
|
204
|
-
.option("--enable", "Set `notion.enabled = true` and skip the first prompt")
|
|
205
|
-
.option("--projects-db <id>", "Set `notion.projectsDatabaseId` (skip the prompt)")
|
|
206
|
-
.option("--tasks-db <id>", "Set `notion.tasksDatabaseId` (skip the prompt)")
|
|
207
|
-
.option("--non-interactive", "Force non-interactive mode in a TTY (use flag values + safe defaults only)")
|
|
208
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
209
|
-
.action(async (options) => {
|
|
210
|
-
await notionInitCommand(options);
|
|
211
|
-
});
|
|
212
|
-
notion
|
|
213
|
-
.command("test")
|
|
214
|
-
.description("Read-only check: Notion API auth + access to Projects/Tasks DBs + required-property schema validation")
|
|
215
|
-
.option("--json", "Print machine-readable JSON")
|
|
216
|
-
.option("--debug-shape", "Also print a token-safe diagnostic of the Projects/Tasks retrieve responses (top-level keys, data_sources, etc.)")
|
|
217
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
218
|
-
.action(async (options) => {
|
|
219
|
-
await notionTestCommand(options);
|
|
220
|
-
});
|
|
221
|
-
notion
|
|
222
|
-
.command("sync")
|
|
223
|
-
.description("Push `docs/project` + `docs/tasks` metadata into Notion Projects/Tasks DBs (read-only on local files)")
|
|
224
|
-
.option("--dry-run", "Print the plan without any Notion mutation (queries only)")
|
|
225
|
-
.option("--json", "Print machine-readable JSON")
|
|
226
|
-
.option("--only-tasks", "Sync the Tasks DB only (leave the Project row alone)")
|
|
227
|
-
.option("--only-project", "Sync the Project DB only (leave Task rows alone)")
|
|
228
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
229
|
-
.action(async (options) => {
|
|
230
|
-
await notionSyncCommand(options);
|
|
46
|
+
.command("done [taskRef]")
|
|
47
|
+
.description("Close TASK: LLM summary, project memory, merge")
|
|
48
|
+
.option("--dry-run", "Plan only")
|
|
49
|
+
.option("--no-merge", "Skip merge")
|
|
50
|
+
.option("--allow-dirty", "Allow dirty tree during merge")
|
|
51
|
+
.option("--yes", "Merge without prompting")
|
|
52
|
+
.option("--cwd <path>", "Target directory")
|
|
53
|
+
.action(async (taskRef, opts) => {
|
|
54
|
+
await taskDoneCommand(taskRef, {
|
|
55
|
+
dryRun: Boolean(opts.dryRun),
|
|
56
|
+
noMerge: Boolean(opts.noMerge),
|
|
57
|
+
allowDirty: Boolean(opts.allowDirty),
|
|
58
|
+
yes: Boolean(opts.yes),
|
|
59
|
+
cwd: opts.cwd,
|
|
60
|
+
});
|
|
231
61
|
});
|
|
232
|
-
|
|
233
|
-
.command("github")
|
|
234
|
-
.description("GitHub repository integration");
|
|
235
|
-
github
|
|
62
|
+
program
|
|
236
63
|
.command("status")
|
|
237
|
-
.description("
|
|
238
|
-
.option("--json", "
|
|
239
|
-
.option("--cwd <path>", "
|
|
240
|
-
.action(async (
|
|
241
|
-
await
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.command("init")
|
|
245
|
-
.description("Interactive setup: check gh auth, manage the git remote, optionally `gh repo create`, then update `.vibeops.json` and `package.json`")
|
|
246
|
-
.option("--dry-run", "Print the plan without running gh / git or writing files")
|
|
247
|
-
.option("--yes", "Skip interactive prompts and use safe defaults")
|
|
248
|
-
.option("--owner <owner>", "GitHub owner (user or org)")
|
|
249
|
-
.option("--repo <repo>", "GitHub repo name")
|
|
250
|
-
.option("--public", "Force visibility = public (skip the prompt)")
|
|
251
|
-
.option("--private", "Force visibility = private (skip the prompt)")
|
|
252
|
-
.option("--remote <name>", "Git remote name (default `origin`)")
|
|
253
|
-
.option("--connect <ownerOrUrl>", "Connect to an existing repo instead of creating one (owner/repo or https/ssh URL)")
|
|
254
|
-
.option("--no-package-update", "Do not modify `package.json` repository/homepage/bugs fields")
|
|
255
|
-
.option("--cwd <path>", "Run against a different directory")
|
|
256
|
-
.action(async (options) => {
|
|
257
|
-
await githubInitCommand({
|
|
258
|
-
...options,
|
|
259
|
-
// Commander turns `--no-package-update` into `packageUpdate: false`.
|
|
260
|
-
// Our command type still accepts the legacy `noPackageUpdate`
|
|
261
|
-
// shape for tests / direct invocation.
|
|
262
|
-
noPackageUpdate: options.packageUpdate === false,
|
|
64
|
+
.description("Briefing: active TASK, Git, next hint")
|
|
65
|
+
.option("--json", "JSON output")
|
|
66
|
+
.option("--cwd <path>", "Target directory")
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
await statusCommand({
|
|
69
|
+
json: Boolean(opts.json),
|
|
70
|
+
cwd: opts.cwd,
|
|
263
71
|
});
|
|
264
72
|
});
|
|
265
73
|
program.parseAsync(process.argv).catch((err) => {
|
package/dist/commands/init.js
CHANGED
|
@@ -34,9 +34,9 @@ export async function initCommand(options = {}) {
|
|
|
34
34
|
if (!dryRun) {
|
|
35
35
|
log.blank();
|
|
36
36
|
log.info("Next steps:");
|
|
37
|
-
log.info(" 1.
|
|
38
|
-
log.info(" 2. Run `vibeops
|
|
39
|
-
log.info(" 3.
|
|
37
|
+
log.info(" 1. Read AGENTS.md and `.cursor/rules/`.");
|
|
38
|
+
log.info(" 2. Run `vibeops task add` to create the first TASK.");
|
|
39
|
+
log.info(" 3. Plan in Cursor Ask (@docs/tasks/TASK-001-….md), then implement.");
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
async function resolveGitPlan(options, interactive) {
|
package/dist/commands/status.js
CHANGED
|
@@ -1,12 +1,149 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import { readConfig } from "../lib/config.js";
|
|
3
|
+
import { readText } from "../lib/filesystem.js";
|
|
4
|
+
import { readGitInfo } from "../lib/git.js";
|
|
5
|
+
import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
|
|
6
|
+
import { projectPaths } from "../lib/paths.js";
|
|
7
|
+
import { VERSION } from "../version.js";
|
|
8
|
+
import { computeNextHint, hintToText, listInProgressTasks, pickFocusTask, relPath, taskBranchExistsFor, } from "../lib/task-context.js";
|
|
9
|
+
import { countTasks, hasNonEmptySection, isOnTaskBranch, loadActionableTasks, readGitContext, statusDisplay } from "../lib/task.js";
|
|
10
|
+
import { summarizeGoal } from "../lib/task-summary.js";
|
|
11
|
+
async function buildReport(cwd) {
|
|
12
|
+
const paths = projectPaths(cwd);
|
|
13
|
+
const config = await readConfig(cwd);
|
|
14
|
+
const git = await readGitInfo(cwd);
|
|
15
|
+
const isVibeopsProject = config !== null;
|
|
16
|
+
let counts = null;
|
|
17
|
+
let inProgress = [];
|
|
18
|
+
let focus = null;
|
|
19
|
+
if (isVibeopsProject) {
|
|
20
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
21
|
+
counts = countTasks(tasks);
|
|
22
|
+
inProgress = await listInProgressTasks(paths);
|
|
23
|
+
focus = await pickFocusTask(paths, cwd);
|
|
24
|
+
}
|
|
25
|
+
let focusBlock = null;
|
|
26
|
+
let resultFilled = false;
|
|
27
|
+
let testFilled = false;
|
|
28
|
+
let onTaskBranch = null;
|
|
29
|
+
let taskBranch = null;
|
|
30
|
+
let branchExists = null;
|
|
31
|
+
let goalExcerpt = "";
|
|
32
|
+
if (focus !== null) {
|
|
33
|
+
const body = await readText(focus.filePath);
|
|
34
|
+
goalExcerpt = summarizeGoal(body);
|
|
35
|
+
resultFilled = hasNonEmptySection(body, "Result");
|
|
36
|
+
testFilled = hasNonEmptySection(body, "Test Result");
|
|
37
|
+
const ctx = await readGitContext(focus.filePath);
|
|
38
|
+
taskBranch = ctx?.taskBranch ?? null;
|
|
39
|
+
onTaskBranch = isOnTaskBranch(git.branch, ctx);
|
|
40
|
+
branchExists = await taskBranchExistsFor(cwd, focus);
|
|
41
|
+
focusBlock = {
|
|
42
|
+
id: focus.id,
|
|
43
|
+
title: focus.title,
|
|
44
|
+
status: statusDisplay(focus.status),
|
|
45
|
+
file: relPath(cwd, focus.filePath),
|
|
46
|
+
goalExcerpt,
|
|
47
|
+
resultFilled,
|
|
48
|
+
testResultFilled: testFilled,
|
|
49
|
+
onTaskBranch,
|
|
50
|
+
taskBranch,
|
|
51
|
+
taskBranchExists: branchExists,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const hint = computeNextHint({
|
|
55
|
+
isVibeopsProject,
|
|
56
|
+
focus,
|
|
57
|
+
resultFilled,
|
|
58
|
+
testFilled,
|
|
59
|
+
onTaskBranch,
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
vibeopsVersion: VERSION,
|
|
63
|
+
isVibeopsProject,
|
|
64
|
+
projectName: config?.name ?? null,
|
|
65
|
+
git: {
|
|
66
|
+
isRepo: git.isRepo,
|
|
67
|
+
branch: git.branch,
|
|
68
|
+
dirty: git.dirty,
|
|
69
|
+
},
|
|
70
|
+
counts,
|
|
71
|
+
inProgress: inProgress.map((t) => ({
|
|
72
|
+
id: t.id,
|
|
73
|
+
title: t.title,
|
|
74
|
+
file: relPath(cwd, t.filePath),
|
|
75
|
+
})),
|
|
76
|
+
focus: focusBlock,
|
|
77
|
+
nextHint: hintToText(hint, cwd, focus),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function printHuman(report) {
|
|
81
|
+
log.blank();
|
|
82
|
+
log.info(bold("─".repeat(56)));
|
|
83
|
+
log.info(bold(" VibeOps · status"));
|
|
84
|
+
log.info(bold("─".repeat(56)));
|
|
85
|
+
log.info(` ${dim("CLI")} v${report.vibeopsVersion}`);
|
|
86
|
+
if (!report.isVibeopsProject) {
|
|
87
|
+
log.info(` ${dim("Project")} ${yellow("not initialized")} — run vibeops init`);
|
|
88
|
+
log.info(bold("─".repeat(56)));
|
|
89
|
+
log.blank();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
log.info(` ${dim("Project")} ${report.projectName ?? "(unnamed)"}`);
|
|
93
|
+
if (report.counts) {
|
|
94
|
+
const c = report.counts;
|
|
95
|
+
log.info(` ${dim("Tasks")} ${c.total} total · ${cyan(String(c.in_progress))} in progress · ${c.done} done`);
|
|
96
|
+
}
|
|
97
|
+
if (report.inProgress.length > 1) {
|
|
98
|
+
log.blank();
|
|
99
|
+
log.info(bold(" In progress"));
|
|
100
|
+
for (const t of report.inProgress) {
|
|
101
|
+
log.info(` ${cyan(t.id)} — ${t.title}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
log.blank();
|
|
105
|
+
if (report.focus) {
|
|
106
|
+
const f = report.focus;
|
|
107
|
+
log.info(` ${dim("Focus")} ${cyan(f.id)} — ${f.title || dim("(no title)")}`);
|
|
108
|
+
log.info(` ${dim("Status")} ${f.status}`);
|
|
109
|
+
log.info(` ${dim("File")} ${f.file}`);
|
|
110
|
+
if (f.goalExcerpt)
|
|
111
|
+
log.info(` ${dim("Goal")} ${f.goalExcerpt}`);
|
|
112
|
+
log.info(` ${dim("Result")} ${f.resultFilled ? green("filled") : yellow("empty")} · Test Result ${f.testResultFilled ? green("filled") : yellow("empty")}`);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
log.info(` ${dim("Focus")} ${dim("(none)")}`);
|
|
116
|
+
}
|
|
117
|
+
log.blank();
|
|
118
|
+
log.info(bold(" Git"));
|
|
119
|
+
if (!report.git.isRepo) {
|
|
120
|
+
log.info(` ${yellow("not a git repository")}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
log.info(` ${dim("HEAD")} ${report.git.branch ?? "(detached)"}`);
|
|
124
|
+
if (report.git.dirty === true)
|
|
125
|
+
log.info(` ${dim("Tree")} ${yellow("dirty")}`);
|
|
126
|
+
else if (report.git.dirty === false)
|
|
127
|
+
log.info(` ${dim("Tree")} ${green("clean")}`);
|
|
128
|
+
if (report.focus?.taskBranch) {
|
|
129
|
+
const on = report.focus.onTaskBranch === true
|
|
130
|
+
? green("yes")
|
|
131
|
+
: report.focus.onTaskBranch === false
|
|
132
|
+
? yellow("no")
|
|
133
|
+
: dim("—");
|
|
134
|
+
log.info(` ${dim("Task br.")} ${report.focus.taskBranch} · on branch: ${on}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
log.blank();
|
|
138
|
+
log.info(`${dim("Next")} ${report.nextHint}`);
|
|
139
|
+
log.info(bold("─".repeat(56)));
|
|
140
|
+
log.blank();
|
|
141
|
+
}
|
|
142
|
+
export async function statusCommand(opts = {}) {
|
|
143
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
144
|
+
const report = await buildReport(cwd);
|
|
145
|
+
if (opts.json) {
|
|
146
|
+
log.raw(JSON.stringify(report, null, 2));
|
|
10
147
|
}
|
|
11
148
|
else {
|
|
12
149
|
printHuman(report);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { pathExists, writeText } from "../lib/filesystem.js";
|
|
3
|
+
import { askInput } from "../lib/inquirer-helpers.js";
|
|
4
|
+
import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
|
|
5
|
+
import { projectPaths } from "../lib/paths.js";
|
|
6
|
+
import { readConfig } from "../lib/config.js";
|
|
7
|
+
import { slugify } from "../lib/slug.js";
|
|
8
|
+
import { fallbackTaskDraft, llmScaffoldTask } from "../lib/task-add-llm.js";
|
|
9
|
+
import { allocateNextTaskNumber, formatTaskId, uniqueTaskPath, } from "../lib/task-scaffold.js";
|
|
10
|
+
import { findBlockingTask, relPath } from "../lib/task-context.js";
|
|
11
|
+
import { startTaskBranch } from "../lib/task-start.js";
|
|
12
|
+
import { loadActionableTasks, readGitContext } from "../lib/task.js";
|
|
13
|
+
export async function taskAddCommand(opts = {}) {
|
|
14
|
+
const root = resolve(opts.cwd ?? process.cwd());
|
|
15
|
+
const dryRun = opts.dryRun === true;
|
|
16
|
+
const nonInteractive = opts.nonInteractive === true || dryRun || process.stdin.isTTY !== true;
|
|
17
|
+
log.info(bold("vibeops task add"));
|
|
18
|
+
log.blank();
|
|
19
|
+
const paths = projectPaths(root);
|
|
20
|
+
const blocking = await findBlockingTask(paths, root);
|
|
21
|
+
if (blocking !== null) {
|
|
22
|
+
const ctx = await readGitContext(blocking.filePath);
|
|
23
|
+
log.warn(`${yellow("Blocked")} — ${bold(blocking.id)} is still open (${blocking.title || "no title"}).`);
|
|
24
|
+
if (ctx) {
|
|
25
|
+
log.info(` ${dim("branch")} ${ctx.taskBranch}`);
|
|
26
|
+
}
|
|
27
|
+
log.info(` ${dim("file")} ${relPath(root, blocking.filePath)}`);
|
|
28
|
+
log.blank();
|
|
29
|
+
log.info(`Finish it first: ${cyan(`vibeops task done ${blocking.id}`)}`);
|
|
30
|
+
log.info(`Then run ${cyan("vibeops task add")} again.`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const ideaDefault = "New work slice";
|
|
35
|
+
const idea = await askInput({
|
|
36
|
+
message: "What are you doing now? (short)",
|
|
37
|
+
nonInteractive,
|
|
38
|
+
default: opts.idea?.trim() || ideaDefault,
|
|
39
|
+
required: true,
|
|
40
|
+
});
|
|
41
|
+
const tasks = await loadActionableTasks(paths.docsTasks);
|
|
42
|
+
const taskId = formatTaskId(allocateNextTaskNumber(tasks));
|
|
43
|
+
let title;
|
|
44
|
+
let slug;
|
|
45
|
+
let markdown;
|
|
46
|
+
if (dryRun || nonInteractive) {
|
|
47
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
48
|
+
title = fb.title;
|
|
49
|
+
slug = fb.slug;
|
|
50
|
+
markdown = fb.markdown;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const llm = await llmScaffoldTask({ cwd: root, taskId, idea });
|
|
54
|
+
if (llm !== null) {
|
|
55
|
+
title = llm.title;
|
|
56
|
+
slug = llm.slug;
|
|
57
|
+
markdown = llm.markdown;
|
|
58
|
+
log.skip(`Scaffold via ${llm.provider}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
log.warn("LLM unavailable — using minimal TASK template.");
|
|
62
|
+
const fb = fallbackTaskDraft(taskId, idea);
|
|
63
|
+
title = fb.title;
|
|
64
|
+
slug = fb.slug;
|
|
65
|
+
markdown = fb.markdown;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slugify(slug || title), tasks.map((t) => t.filePath));
|
|
69
|
+
const relFile = relPath(root, filePath);
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
log.info(`[dry-run] Would create ${bold(taskId)} → ${cyan(relFile)}`);
|
|
72
|
+
log.info(dim(` branch task/${slugify(slug || title).replace(/^(\d+)-/, "$1-")}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (await pathExists(filePath)) {
|
|
76
|
+
throw new Error(`TASK file already exists: ${relFile}`);
|
|
77
|
+
}
|
|
78
|
+
await writeText(filePath, markdown);
|
|
79
|
+
log.ok(`Created ${bold(taskId)} → ${cyan(relFile)}`);
|
|
80
|
+
const config = await readConfig(root);
|
|
81
|
+
if (config === null) {
|
|
82
|
+
log.warn("No .vibeops.json — run vibeops init for full template.");
|
|
83
|
+
}
|
|
84
|
+
log.blank();
|
|
85
|
+
log.step("Starting task branch…");
|
|
86
|
+
const started = await startTaskBranch({
|
|
87
|
+
cwd: root,
|
|
88
|
+
taskFile: filePath,
|
|
89
|
+
allowDirty: true,
|
|
90
|
+
});
|
|
91
|
+
if (!started) {
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
log.blank();
|
|
96
|
+
log.info(bold("Next in Cursor"));
|
|
97
|
+
log.info(` Ask: @${relFile} — plan Scope / Acceptance Criteria`);
|
|
98
|
+
log.info(` Agent: same file — implement`);
|
|
99
|
+
log.info(` Done: ${cyan(`vibeops task done ${taskId}`)}`);
|
|
100
|
+
}
|