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