@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/commands/plan.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
2
|
-
import { briefToMarkdown, findMissingRequired, gatherBrief, parseBriefFromMarkdown, parseIdea, } from "../lib/brief.js";
|
|
3
|
-
import { askYesNo } from "../lib/inquirer-helpers.js";
|
|
4
|
-
import { pathExists, readText, writeText } from "../lib/filesystem.js";
|
|
5
|
-
import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
|
|
6
|
-
import { applyPlannerMarkdownFlow, commitPlannerGit, pushPlannerGit } from "../lib/plan-apply-planner.js";
|
|
7
|
-
import { gatherBriefViaLlm, pickPlanDialogueLocale, pickPlanLlmProvider } from "../lib/plan-llm-session.js";
|
|
8
|
-
import { pickPlanExecutionModel } from "../lib/plan-llm-models.js";
|
|
9
|
-
import { ensureGitForPlannerCommit, offerGithubInitBeforePush, runNotionSyncWithOptionalInit } from "../lib/plan-post-apply-setup.js";
|
|
10
|
-
import { buildPlanPrompt } from "../lib/prompt-builder.js";
|
|
11
|
-
import { projectPaths } from "../lib/paths.js";
|
|
12
|
-
const DEFAULT_BRIEF_REL = ".vibeops/brief/project-brief.md";
|
|
13
|
-
const DEFAULT_PROMPT_REL = ".vibeops/generated/plan-prompt.md";
|
|
14
|
-
function toAbsolute(root, candidate) {
|
|
15
|
-
return isAbsolute(candidate) ? candidate : resolve(root, candidate);
|
|
16
|
-
}
|
|
17
|
-
function relDisplay(root, abs) {
|
|
18
|
-
const r = relative(root, abs);
|
|
19
|
-
return r.length === 0 ? "." : r;
|
|
20
|
-
}
|
|
21
|
-
export async function planCommand(options) {
|
|
22
|
-
const cwd = resolve(options.cwd ?? process.cwd());
|
|
23
|
-
const nonInteractive = options.nonInteractive === true;
|
|
24
|
-
const paths = projectPaths(cwd);
|
|
25
|
-
if (!(await pathExists(paths.config))) {
|
|
26
|
-
log.error(`Not a VibeOps project: ${cyan(".vibeops.json")} was not found under this directory.`);
|
|
27
|
-
log.info(` ${dim("Run")} ${cyan("vibeops init")} ${dim("here first, then run")} ${cyan("vibeops plan")} ${dim("again.")}`);
|
|
28
|
-
process.exitCode = 1;
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
const briefAbs = join(cwd, DEFAULT_BRIEF_REL);
|
|
32
|
-
const promptAbs = typeof options.output === "string" ? toAbsolute(cwd, options.output) : join(cwd, DEFAULT_PROMPT_REL);
|
|
33
|
-
log.info(bold(`vibeops plan`));
|
|
34
|
-
log.info(dim(` cwd: ${cwd}`));
|
|
35
|
-
if (options.from)
|
|
36
|
-
log.info(dim(` from: ${options.from}`));
|
|
37
|
-
if (options.idea)
|
|
38
|
-
log.info(dim(` idea: ${options.idea}`));
|
|
39
|
-
if (typeof options.llmModel === "string" && options.llmModel.trim().length > 0) {
|
|
40
|
-
log.info(dim(` model: ${options.llmModel.trim()}`));
|
|
41
|
-
}
|
|
42
|
-
if (nonInteractive)
|
|
43
|
-
log.info(dim(` mode: non-interactive`));
|
|
44
|
-
log.blank();
|
|
45
|
-
let bundle;
|
|
46
|
-
let llmProviderForApply;
|
|
47
|
-
let llmModelForApply;
|
|
48
|
-
if (typeof options.from === "string" && options.from.length > 0) {
|
|
49
|
-
bundle = await loadFromFile({
|
|
50
|
-
cwd,
|
|
51
|
-
fromPath: toAbsolute(cwd, options.from),
|
|
52
|
-
nonInteractive,
|
|
53
|
-
idea: options.idea,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
if (!nonInteractive && process.stdin.isTTY !== true) {
|
|
58
|
-
log.error("vibeops plan requires a TTY. In CI/piped environments, pass --non-interactive or supply --from <brief.md>.");
|
|
59
|
-
process.exitCode = 1;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (nonInteractive) {
|
|
63
|
-
log.step("non-interactive: build the ProjectBrief from flag values + safe placeholders");
|
|
64
|
-
log.blank();
|
|
65
|
-
bundle = await gatherBrief({
|
|
66
|
-
cwd,
|
|
67
|
-
idea: options.idea,
|
|
68
|
-
nonInteractive: true,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
else if (options.legacyWizard === true) {
|
|
72
|
-
log.step("legacy wizard: build the ProjectBrief from 20 fixed questions (arrow keys · Space · Enter)");
|
|
73
|
-
log.blank();
|
|
74
|
-
bundle = await gatherBrief({
|
|
75
|
-
cwd,
|
|
76
|
-
idea: options.idea,
|
|
77
|
-
nonInteractive: false,
|
|
78
|
-
legacyWizard: true,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
log.step("LLM planning: verify provider, then interactive discovery + structured brief");
|
|
83
|
-
log.blank();
|
|
84
|
-
const interactiveLogin = process.stdin.isTTY === true;
|
|
85
|
-
const provider = await pickPlanLlmProvider(cwd, options.provider, { interactiveLogin });
|
|
86
|
-
if (provider === null) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const forcedModel = typeof options.llmModel === "string" && options.llmModel.trim().length > 0
|
|
90
|
-
? options.llmModel.trim()
|
|
91
|
-
: undefined;
|
|
92
|
-
const pickedModel = forcedModel ??
|
|
93
|
-
(await pickPlanExecutionModel({
|
|
94
|
-
provider,
|
|
95
|
-
cwd,
|
|
96
|
-
interactive: interactiveLogin,
|
|
97
|
-
}));
|
|
98
|
-
log.blank();
|
|
99
|
-
const planningLocale = await pickPlanDialogueLocale();
|
|
100
|
-
try {
|
|
101
|
-
bundle = await gatherBriefViaLlm({
|
|
102
|
-
cwd,
|
|
103
|
-
idea: options.idea,
|
|
104
|
-
provider,
|
|
105
|
-
allowCodexBrowserLogin: interactiveLogin,
|
|
106
|
-
llmModelOverride: pickedModel,
|
|
107
|
-
planningLocale,
|
|
108
|
-
});
|
|
109
|
-
llmProviderForApply = provider;
|
|
110
|
-
llmModelForApply =
|
|
111
|
-
typeof pickedModel === "string" && pickedModel.trim().length > 0 ? pickedModel.trim() : undefined;
|
|
112
|
-
}
|
|
113
|
-
catch (e) {
|
|
114
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
115
|
-
log.error(`LLM planning failed: ${msg}`);
|
|
116
|
-
process.exitCode = 1;
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const briefMd = briefToMarkdown(bundle.brief, bundle.meta);
|
|
122
|
-
await writeText(briefAbs, briefMd);
|
|
123
|
-
log.ok(`Wrote brief: ${relDisplay(cwd, briefAbs)}`);
|
|
124
|
-
const promptMd = buildPlanPrompt({
|
|
125
|
-
brief: bundle.brief,
|
|
126
|
-
meta: bundle.meta,
|
|
127
|
-
briefRelativePath: relDisplay(cwd, briefAbs),
|
|
128
|
-
});
|
|
129
|
-
await writeText(promptAbs, promptMd);
|
|
130
|
-
log.ok(`Cursor planning prompt: ${relDisplay(cwd, promptAbs)}`);
|
|
131
|
-
if (bundle.meta.assumptions.length > 0) {
|
|
132
|
-
log.blank();
|
|
133
|
-
log.info(`${yellow("!")} ${bold("Assumptions")} (items the Planner Agent should reconfirm):`);
|
|
134
|
-
for (const a of bundle.meta.assumptions)
|
|
135
|
-
log.info(` · ${a}`);
|
|
136
|
-
}
|
|
137
|
-
const wantsApply = options.applyPlanner === true || options.applyPlannerDryRun === true;
|
|
138
|
-
if (wantsApply) {
|
|
139
|
-
const applyDryRun = options.applyPlannerDryRun === true;
|
|
140
|
-
const exec = await resolvePlannerApplyExecution({
|
|
141
|
-
cwd,
|
|
142
|
-
nonInteractive,
|
|
143
|
-
fromLlmProvider: llmProviderForApply,
|
|
144
|
-
fromLlmModel: llmModelForApply,
|
|
145
|
-
cliProvider: options.provider,
|
|
146
|
-
cliModel: options.llmModel,
|
|
147
|
-
});
|
|
148
|
-
if (exec === null) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
const applyResult = await applyPlannerMarkdownFlow({
|
|
153
|
-
cwd,
|
|
154
|
-
planPromptMarkdown: promptMd,
|
|
155
|
-
provider: exec.provider,
|
|
156
|
-
model: exec.model,
|
|
157
|
-
dryRun: applyDryRun,
|
|
158
|
-
});
|
|
159
|
-
const applyInteractive = !nonInteractive && process.stdin.isTTY === true && !applyDryRun;
|
|
160
|
-
const hasWrittenFiles = !applyDryRun && applyResult.writtenRelativePaths.length > 0;
|
|
161
|
-
let doCommit = hasWrittenFiles && options.noGitCommit !== true;
|
|
162
|
-
if (doCommit && applyInteractive) {
|
|
163
|
-
log.blank();
|
|
164
|
-
log.step("Next: commit, push, and Notion (confirm each step)");
|
|
165
|
-
doCommit = await askYesNo({
|
|
166
|
-
message: "[1] Create a git commit for the planner output (docs/project + docs/tasks)?",
|
|
167
|
-
nonInteractive: false,
|
|
168
|
-
defaultValue: true,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
let committedOk = false;
|
|
172
|
-
if (doCommit) {
|
|
173
|
-
const gitReady = await ensureGitForPlannerCommit({
|
|
174
|
-
cwd,
|
|
175
|
-
interactive: applyInteractive,
|
|
176
|
-
});
|
|
177
|
-
if (!gitReady) {
|
|
178
|
-
doCommit = false;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (doCommit) {
|
|
182
|
-
try {
|
|
183
|
-
await commitPlannerGit(cwd, applyResult.writtenRelativePaths, "docs: planner output from vibeops plan (--apply-planner)");
|
|
184
|
-
log.ok("Git commit created for planner docs/tasks.");
|
|
185
|
-
committedOk = true;
|
|
186
|
-
}
|
|
187
|
-
catch (e) {
|
|
188
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
189
|
-
log.warn(`Git commit did not run (${msg}). Review changes and commit manually.`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
let doPush = false;
|
|
193
|
-
if (committedOk) {
|
|
194
|
-
if (options.gitPush === true) {
|
|
195
|
-
doPush = true;
|
|
196
|
-
}
|
|
197
|
-
else if (applyInteractive) {
|
|
198
|
-
doPush = await askYesNo({
|
|
199
|
-
message: "[2] Push this commit to the remote (git push)? (If Yes, you may be offered `vibeops github init` to connect or create a GitHub repo / set `origin`.)",
|
|
200
|
-
nonInteractive: false,
|
|
201
|
-
defaultValue: false,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
if (doPush) {
|
|
205
|
-
await offerGithubInitBeforePush({ cwd, interactive: applyInteractive });
|
|
206
|
-
try {
|
|
207
|
-
await pushPlannerGit(cwd);
|
|
208
|
-
log.ok("git push complete.");
|
|
209
|
-
}
|
|
210
|
-
catch (e) {
|
|
211
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
212
|
-
log.error(`git push failed: ${msg}`);
|
|
213
|
-
process.exitCode = 1;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else if (options.gitPush === true && !committedOk) {
|
|
218
|
-
log.warn("`--push` skipped (no successful commit: dry-run, --no-git-commit, you declined the commit prompt, commit error, or no files written).");
|
|
219
|
-
}
|
|
220
|
-
let doNotion = bundle.brief.useNotion === true && options.noNotionSync !== true && !applyDryRun;
|
|
221
|
-
if (doNotion && applyInteractive) {
|
|
222
|
-
doNotion = await askYesNo({
|
|
223
|
-
message: "[3] Run Notion sync now (project + TASK metadata)?",
|
|
224
|
-
nonInteractive: false,
|
|
225
|
-
defaultValue: true,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (doNotion) {
|
|
229
|
-
const hadErrBeforeNotion = process.exitCode === 1;
|
|
230
|
-
await runNotionSyncWithOptionalInit({ cwd, interactive: applyInteractive });
|
|
231
|
-
if (hadErrBeforeNotion) {
|
|
232
|
-
process.exitCode = 1;
|
|
233
|
-
}
|
|
234
|
-
if (process.exitCode === 1 && !hadErrBeforeNotion) {
|
|
235
|
-
log.warn("Notion sync failed — fix integration and run `vibeops notion sync`.");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
else if (bundle.brief.useNotion === true && applyDryRun) {
|
|
239
|
-
log.info(dim("Notion sync skipped for --apply-dry-run."));
|
|
240
|
-
}
|
|
241
|
-
else if (bundle.brief.useNotion === true && options.noNotionSync === true) {
|
|
242
|
-
log.info(dim("Notion sync skipped (--no-notion-sync)."));
|
|
243
|
-
}
|
|
244
|
-
else if (bundle.brief.useNotion === true && applyInteractive && !doNotion) {
|
|
245
|
-
log.info(dim("Notion sync skipped (you chose No)."));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch (e) {
|
|
249
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
250
|
-
log.error(`Planner apply failed: ${msg}`);
|
|
251
|
-
process.exitCode = 1;
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
log.blank();
|
|
256
|
-
log.info(bold("Next steps:"));
|
|
257
|
-
if (wantsApply && options.applyPlannerDryRun !== true) {
|
|
258
|
-
log.info(` 1) Open this repo in Cursor and run ${cyan("vibeops task start TASK-NNN")} for each backlog item.`);
|
|
259
|
-
log.info(` 2) The planning prompt is still at ${cyan(relDisplay(cwd, promptAbs))} if you need to re-run the planner.`);
|
|
260
|
-
log.info(` 3) To revise the brief only, edit ${cyan(relDisplay(cwd, briefAbs))} then:`);
|
|
261
|
-
log.info(` ${dim("vibeops plan --from " + DEFAULT_BRIEF_REL + " [--apply-planner …]")}`);
|
|
262
|
-
}
|
|
263
|
-
else if (wantsApply && options.applyPlannerDryRun === true) {
|
|
264
|
-
log.info(` 1) Re-run without ${cyan("--apply-dry-run")} to write files, commit, and (if enabled) Notion sync.`);
|
|
265
|
-
log.info(` 2) Or paste ${cyan(relDisplay(cwd, promptAbs))} into Cursor for a manual planner pass.`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
log.info(` 1) Open a new Cursor chat and paste the full contents of ${cyan(relDisplay(cwd, promptAbs))}.`);
|
|
269
|
-
log.info(` 2) Or run again with ${cyan("--apply-planner")}: the CLI calls the same LLM and writes ${cyan("docs/project/*")} + ${cyan("docs/tasks/*")} from the prompt (brief alone does not create TASK files).`);
|
|
270
|
-
log.info(` 3) Regenerate only the prompt from an edited brief:`);
|
|
271
|
-
log.info(` ${dim("vibeops plan --from " + DEFAULT_BRIEF_REL + " [--apply-planner …]")}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
async function resolvePlannerApplyExecution(inputs) {
|
|
275
|
-
if (inputs.fromLlmProvider !== undefined) {
|
|
276
|
-
return { provider: inputs.fromLlmProvider, model: inputs.fromLlmModel };
|
|
277
|
-
}
|
|
278
|
-
const tty = process.stdin.isTTY === true;
|
|
279
|
-
if (inputs.cliProvider !== undefined) {
|
|
280
|
-
const interactiveLogin = tty && !inputs.nonInteractive;
|
|
281
|
-
const provider = await pickPlanLlmProvider(inputs.cwd, inputs.cliProvider, { interactiveLogin });
|
|
282
|
-
if (provider === null)
|
|
283
|
-
return null;
|
|
284
|
-
let model = inputs.cliModel?.trim();
|
|
285
|
-
if (!model && tty && !inputs.nonInteractive) {
|
|
286
|
-
const picked = await pickPlanExecutionModel({
|
|
287
|
-
provider,
|
|
288
|
-
cwd: inputs.cwd,
|
|
289
|
-
interactive: true,
|
|
290
|
-
});
|
|
291
|
-
model = picked?.trim();
|
|
292
|
-
}
|
|
293
|
-
return { provider, model: model && model.length > 0 ? model : undefined };
|
|
294
|
-
}
|
|
295
|
-
if (!tty || inputs.nonInteractive) {
|
|
296
|
-
log.error("When using --apply-planner without an interactive LLM briefing in the same run, pass --provider openai | codex-oauth | cursor-agent (and optional --model / env defaults).");
|
|
297
|
-
process.exitCode = 1;
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
const provider = await pickPlanLlmProvider(inputs.cwd, undefined, { interactiveLogin: true });
|
|
301
|
-
if (provider === null)
|
|
302
|
-
return null;
|
|
303
|
-
const model = await pickPlanExecutionModel({ provider, cwd: inputs.cwd, interactive: true });
|
|
304
|
-
return { provider, model };
|
|
305
|
-
}
|
|
306
|
-
async function loadFromFile(inputs) {
|
|
307
|
-
const { cwd, fromPath, nonInteractive, idea } = inputs;
|
|
308
|
-
if (!(await pathExists(fromPath))) {
|
|
309
|
-
log.error(`--from path does not exist: ${fromPath}`);
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
const md = await readText(fromPath);
|
|
313
|
-
log.step(`Loading brief: ${relDisplay(cwd, fromPath)}`);
|
|
314
|
-
const parsed = parseBriefFromMarkdown(md);
|
|
315
|
-
parsed.meta.source = "from-file";
|
|
316
|
-
if (typeof idea === "string" && idea.length > 0) {
|
|
317
|
-
const ideaParsed = parseIdea(idea);
|
|
318
|
-
if (ideaParsed.projectName && parsed.brief.projectName.length === 0) {
|
|
319
|
-
parsed.brief.projectName = ideaParsed.projectName;
|
|
320
|
-
}
|
|
321
|
-
if (ideaParsed.oneLineIdea && parsed.brief.oneLineIdea.length === 0) {
|
|
322
|
-
parsed.brief.oneLineIdea = ideaParsed.oneLineIdea;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const missing = findMissingRequired(parsed.brief);
|
|
326
|
-
if (missing.length === 0) {
|
|
327
|
-
return parsed;
|
|
328
|
-
}
|
|
329
|
-
if (nonInteractive) {
|
|
330
|
-
log.warn(`Missing required fields: ${missing.join(", ")} → filling with placeholders and recording them in Assumptions.`);
|
|
331
|
-
return parsed;
|
|
332
|
-
}
|
|
333
|
-
log.warn(`Missing required fields: ${missing.join(", ")} → asking only the missing questions.`);
|
|
334
|
-
log.blank();
|
|
335
|
-
const filled = await gatherBrief({
|
|
336
|
-
cwd,
|
|
337
|
-
idea,
|
|
338
|
-
nonInteractive: false,
|
|
339
|
-
seed: parsed.brief,
|
|
340
|
-
});
|
|
341
|
-
filled.meta.source = "from-file";
|
|
342
|
-
return filled;
|
|
343
|
-
}
|
|
@@ -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
|
-
}
|