@goodtek/vibeops 0.7.22 → 1.1.1
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 +51 -0
- package/README.md +51 -380
- package/dist/bootstrap/installer.js +42 -20
- package/dist/bootstrap/manifest.js +23 -3
- package/dist/cli.js +69 -154
- package/dist/commands/init.js +279 -114
- package/dist/commands/llm.js +177 -0
- package/dist/commands/status.js +181 -8
- package/dist/commands/task-add.js +89 -162
- package/dist/commands/task-done.js +112 -73
- package/dist/lib/config.js +77 -113
- package/dist/lib/env.js +32 -0
- package/dist/lib/git-config.js +21 -0
- package/dist/lib/git-host.js +12 -0
- package/dist/lib/git-policy.js +67 -0
- package/dist/lib/git-remote.js +108 -0
- package/dist/lib/git.js +44 -1
- package/dist/lib/init-clients.js +55 -0
- package/dist/lib/llm-complete.js +18 -19
- package/dist/lib/llm-connect.js +150 -0
- package/dist/lib/llm-preference.js +102 -0
- package/dist/lib/llm-status.js +19 -0
- package/dist/lib/paths.js +7 -3
- package/dist/lib/plan-llm-openai.js +0 -63
- package/dist/lib/pr-create.js +66 -0
- package/dist/lib/resolve-task.js +34 -0
- package/dist/lib/task-add-llm.js +53 -233
- package/dist/lib/task-context.js +68 -0
- package/dist/lib/task-done-llm.js +155 -0
- package/dist/lib/task-pr-llm.js +50 -0
- package/dist/lib/task-push-pr.js +131 -0
- package/dist/lib/task-scaffold.js +39 -43
- package/dist/lib/task-start.js +100 -0
- package/dist/lib/task-summary.js +12 -239
- package/dist/lib/task.js +30 -24
- package/dist/types/config.js +0 -16
- package/package.json +14 -14
- package/templates/.cursor/rules/02-git-safety.mdc +12 -0
- package/templates/AGENTS.md +27 -87
- package/templates/clients/claude/CLAUDE.md +28 -0
- package/templates/clients/cursor/.cursor/rules/01-task-source-of-truth.mdc +12 -0
- package/templates/clients/cursor/.cursor/rules/02-git-safety.mdc +11 -0
- package/templates/clients/cursor/.cursor/rules/03-docs-on-done.mdc +13 -0
- package/templates/core/AGENTS.md +44 -0
- package/templates/core/docs/logs/README.md +7 -0
- package/templates/core/docs/project/03-architecture.md +15 -0
- package/templates/core/docs/project/05-current-state.md +15 -0
- package/templates/core/docs/project/06-decisions.md +9 -0
- package/templates/core/docs/tasks/TASK-000-template.md +37 -0
- package/templates/skills-shared/implement-task/SKILL.md +22 -0
- package/templates/skills-shared/plan-task/SKILL.md +19 -0
- 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-merge.js +0 -220
- 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/logs/README.md +0 -38
- 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/03-architecture.md +0 -34
- package/templates/docs/project/04-tech-stack.md +0 -29
- package/templates/docs/project/05-current-state.md +0 -35
- package/templates/docs/project/06-decisions.md +0 -20
- 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/templates/docs/tasks/TASK-000-template.md +0 -72
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
export const TASK_GENERATE_SCHEMA_VERSION = 1;
|
|
2
|
-
export const REQUIRED_TASK_SECTIONS = [
|
|
3
|
-
"Status",
|
|
4
|
-
"MVP Phase",
|
|
5
|
-
"Goal",
|
|
6
|
-
"Background",
|
|
7
|
-
"Scope",
|
|
8
|
-
"Out of Scope",
|
|
9
|
-
"Acceptance Criteria",
|
|
10
|
-
"Files to Inspect First",
|
|
11
|
-
"Expected Files to Change",
|
|
12
|
-
"Risks",
|
|
13
|
-
"Test Plan",
|
|
14
|
-
"Rollback Plan",
|
|
15
|
-
"Git Context",
|
|
16
|
-
"Notion Page",
|
|
17
|
-
"Implementation Plan",
|
|
18
|
-
"Result",
|
|
19
|
-
"Test Result",
|
|
20
|
-
"Review Notes",
|
|
21
|
-
];
|
|
22
|
-
/**
|
|
23
|
-
* Convert an arbitrary string to a TASK-filename slug.
|
|
24
|
-
* - lowercase
|
|
25
|
-
* - ASCII-only (non-ASCII letters dropped)
|
|
26
|
-
* - non-alphanum collapsed to `-`
|
|
27
|
-
* - leading/trailing `-` removed, repeated `-` compressed
|
|
28
|
-
* - empty result → `task`
|
|
29
|
-
*/
|
|
30
|
-
export function slugify(input, fallback = "task") {
|
|
31
|
-
const cleaned = input
|
|
32
|
-
.toLowerCase()
|
|
33
|
-
.normalize("NFKD")
|
|
34
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
35
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
36
|
-
.replace(/^-+|-+$/g, "")
|
|
37
|
-
.replace(/-+/g, "-");
|
|
38
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
39
|
-
}
|
|
40
|
-
function header(inputs) {
|
|
41
|
-
const lines = [];
|
|
42
|
-
lines.push("# VibeOps Task-Generation Prompt — Cursor Planner Agent");
|
|
43
|
-
lines.push("");
|
|
44
|
-
lines.push("> This file was produced by `vibeops task generate`. Open a new chat in **Cursor** and paste the full contents of this file. VibeOps does not call LLM, Cursor CLI, Notion, or GitHub APIs directly — the **Planner Agent** receiving this prompt creates the `docs/tasks/TASK-*.md` files.");
|
|
45
|
-
lines.push("");
|
|
46
|
-
lines.push(`- VibeOps version: ${inputs.vibeopsVersion}`);
|
|
47
|
-
lines.push(`- Generated: ${inputs.generatedAt}`);
|
|
48
|
-
lines.push(`- Generated by: \`vibeops task generate\``);
|
|
49
|
-
lines.push(`- Prompt location: \`${inputs.outputRelative}\``);
|
|
50
|
-
lines.push(`- Schema version: ${TASK_GENERATE_SCHEMA_VERSION}`);
|
|
51
|
-
lines.push("");
|
|
52
|
-
return lines.join("\n");
|
|
53
|
-
}
|
|
54
|
-
function role() {
|
|
55
|
-
return `## Role: Planner Agent
|
|
56
|
-
|
|
57
|
-
Follow the definition in \`.vibeops/agents/planner.md\` if it is installed. You **do not write application code**. You produce \`docs/tasks/TASK-NNN-slug.md\` files. Each TASK must be small enough for a single Builder Agent to finish in one Cursor session.`;
|
|
58
|
-
}
|
|
59
|
-
function hardRules() {
|
|
60
|
-
return `## Hard rules (failure if violated)
|
|
61
|
-
|
|
62
|
-
- Never produce application code (\`src/**\`, \`lib/**\`, \`app/**\`, tests, build scripts, etc.). The deliverable for this round is \`docs/tasks/TASK-NNN-slug.md\` files only.
|
|
63
|
-
- Do not touch VibeOps' own config (\`.vibeops/\`, \`.vibeops.json\`, \`templates/\`).
|
|
64
|
-
- Never call LLM APIs, Cursor CLI, Notion APIs, or GitHub APIs directly. You only produce markdown.
|
|
65
|
-
- Source-of-truth rules:
|
|
66
|
-
- **\`docs/tasks/*.md\` = AI execution source of truth** (Builder/Reviewer/Tester agents only read these files).
|
|
67
|
-
- **\`docs/project/*.md\` = design + current-state baseline** (the input the Planner reads for this round).
|
|
68
|
-
- **Git commits/branches = change history**.
|
|
69
|
-
- **Notion = human dashboard** (exposes summary / status / priority / docs path only — never execution details).
|
|
70
|
-
- Do not trust chat memory.
|
|
71
|
-
- One TASK, one focus. Surface inter-TASK dependencies in the body (e.g. "TASK-014 depends on TASK-013").
|
|
72
|
-
- Never hide assumptions. Record them in the closing "Assumptions" section of the response.`;
|
|
73
|
-
}
|
|
74
|
-
function inventoryRow(slot) {
|
|
75
|
-
const flag = slot.content === null ? "·" : "✓";
|
|
76
|
-
const note = slot.content === null ? " _(missing)_" : "";
|
|
77
|
-
const primaryTag = slot.primary ? " **(primary)**" : "";
|
|
78
|
-
return `- [${flag}] \`${slot.relativePath}\` — ${slot.label}${primaryTag}${note}`;
|
|
79
|
-
}
|
|
80
|
-
function inventory(docs) {
|
|
81
|
-
const lines = ["## Input documents (for this round)"];
|
|
82
|
-
lines.push("");
|
|
83
|
-
if (docs.from)
|
|
84
|
-
lines.push(inventoryRow(docs.from));
|
|
85
|
-
for (const slot of docs.slots)
|
|
86
|
-
lines.push(inventoryRow(slot));
|
|
87
|
-
lines.push(inventoryRow(docs.brief));
|
|
88
|
-
lines.push("");
|
|
89
|
-
lines.push("> `[✓]` = file read from disk. `[·]` = missing or empty slot — its content is not inlined below. Do not over-guess for missing slots; record them in \"Assumptions\".");
|
|
90
|
-
return lines.join("\n");
|
|
91
|
-
}
|
|
92
|
-
function objective(inputs) {
|
|
93
|
-
const lines = ["## Objective"];
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push(`Read the inputs above and produce roughly **${inputs.count} new TASK files**. The first TASK number is \`${inputs.nextTaskId}\` (the next number after the highest existing \`docs/tasks/TASK-*.md\`). Skip any number that already exists on disk.`);
|
|
96
|
-
if (typeof inputs.phase === "string" && inputs.phase.length > 0) {
|
|
97
|
-
lines.push("");
|
|
98
|
-
lines.push(`- **Phase filter**: this round only produces TASKs whose phase is \`${inputs.phase}\`. Even if you see work for other phases, leave it for a separate round.`);
|
|
99
|
-
}
|
|
100
|
-
if (inputs.countWarning) {
|
|
101
|
-
lines.push("");
|
|
102
|
-
lines.push(`- ⚠️ A recommended count of ${inputs.count} is on the high side. Make sure each TASK really does fit a single Cursor session, and lower the count if anything is vague.`);
|
|
103
|
-
}
|
|
104
|
-
lines.push("");
|
|
105
|
-
lines.push("Each TASK must satisfy:");
|
|
106
|
-
lines.push("");
|
|
107
|
-
lines.push("- **Small, verifiable scope**. One Builder finishes it in one Cursor session.");
|
|
108
|
-
lines.push("- **Self-contained**. A reader can determine the files to change and the Acceptance Criteria from the TASK body alone.");
|
|
109
|
-
lines.push("- **Explicit dependencies**. If TASK depends on another, write \"depends on TASK-NNN\" in Background.");
|
|
110
|
-
return lines.join("\n");
|
|
111
|
-
}
|
|
112
|
-
function fileFormat(inputs) {
|
|
113
|
-
const sections = REQUIRED_TASK_SECTIONS.map((s) => ` - \`## ${s}\``).join("\n");
|
|
114
|
-
return `## Output format
|
|
115
|
-
|
|
116
|
-
Produce the response in this exact order:
|
|
117
|
-
|
|
118
|
-
1. **Plan summary** — 5 to 8 bullets. Priorities / decomposition derived from the inputs.
|
|
119
|
-
2. **TASK files** — each in its own fenced code block. The first line of each block must be \`<!-- file: docs/tasks/TASK-NNN-slug.md -->\` (an HTML comment) marking the path. Numbers increase sequentially from \`${inputs.nextTaskId}\`. Slugs are lowercase / ASCII / hyphen-separated (e.g. \`docs-and-readme\`).
|
|
120
|
-
3. **Changed file list** — every \`docs/tasks/TASK-NNN-*.md\` path produced above.
|
|
121
|
-
4. **Generated TASK summary** — table or bullet list: \`TASK-NNN · title · MVP Phase · one-line summary\`.
|
|
122
|
-
5. **Assumptions** — items a human needs to reconfirm (use \`(none)\` if there are none).
|
|
123
|
-
|
|
124
|
-
### Required sections per TASK file (same order, same heading text)
|
|
125
|
-
|
|
126
|
-
Each TASK markdown begins with \`# ${inputs.nextTaskId} · <one-line title>\` and includes **all** of the following \`##\` sections:
|
|
127
|
-
|
|
128
|
-
${sections}
|
|
129
|
-
|
|
130
|
-
Additional rules:
|
|
131
|
-
|
|
132
|
-
- The body of \`## Status\` is the single word \`planned\`.
|
|
133
|
-
- The body of \`## MVP Phase\` follows \`${typeof inputs.phase === "string" && inputs.phase.length > 0 ? inputs.phase : "MVP <n> · <phase name>"}\` (or \`(unassigned)\` when unknown).
|
|
134
|
-
- The body of \`## Git Context\` is \`(populated by \\\`vibeops task start TASK-NNN\\\`)\`.
|
|
135
|
-
- The body of \`## Notion Page\` is \`(populated by \\\`vibeops notion sync\\\`)\`.
|
|
136
|
-
- The body of \`## Result\` / \`## Test Result\` / \`## Review Notes\` is \`(not yet)\`.`;
|
|
137
|
-
}
|
|
138
|
-
function workflowReminders() {
|
|
139
|
-
return `## Workflow reminders
|
|
140
|
-
|
|
141
|
-
- **VibeOps source of truth = \`docs/tasks/\` + \`docs/project/\` + Git.** Notion is a human dashboard and is never used for AI execution.
|
|
142
|
-
- Once produced, each TASK must be ready for \`vibeops task start TASK-NNN\`. Keep slugs clean and numbers conflict-free.
|
|
143
|
-
- Never produce application code — a separate Builder Agent does that in a different round.
|
|
144
|
-
- Separate ambiguity into \`Risks\` (known unknowns) and \`Assumptions\` (need confirmation).`;
|
|
145
|
-
}
|
|
146
|
-
function fileSection(doc, fenceLang = "") {
|
|
147
|
-
if (doc.content === null) {
|
|
148
|
-
return `### \`${doc.relativePath}\` _(missing)_\n\n_(file not found — the Planner Agent must record this in Assumptions.)_`;
|
|
149
|
-
}
|
|
150
|
-
const fence = "```";
|
|
151
|
-
const lang = fenceLang.length > 0 ? fenceLang : "markdown";
|
|
152
|
-
return `### \`${doc.relativePath}\`\n\n${fence}${lang}\n${doc.content.trimEnd()}\n${fence}`;
|
|
153
|
-
}
|
|
154
|
-
function inlineInputs(docs) {
|
|
155
|
-
const parts = ["## Inline inputs"];
|
|
156
|
-
parts.push("");
|
|
157
|
-
parts.push("The code blocks below are the on-disk snapshot at prompt generation time. Cursor may open additional context, but **start from these**.");
|
|
158
|
-
parts.push("");
|
|
159
|
-
if (docs.from)
|
|
160
|
-
parts.push(fileSection(docs.from));
|
|
161
|
-
for (const slot of docs.slots)
|
|
162
|
-
parts.push(fileSection(slot));
|
|
163
|
-
parts.push(fileSection(docs.brief));
|
|
164
|
-
return parts.join("\n\n");
|
|
165
|
-
}
|
|
166
|
-
function footer() {
|
|
167
|
-
return `---
|
|
168
|
-
|
|
169
|
-
Apply the rules above and produce the response. After you respond, a human reviews the generated \`docs/tasks/TASK-*.md\` files via \`git diff\` and commits them, then starts the first TASK with \`vibeops task start TASK-NNN\`.`;
|
|
170
|
-
}
|
|
171
|
-
export function buildTaskGeneratePrompt(inputs) {
|
|
172
|
-
return [
|
|
173
|
-
header(inputs),
|
|
174
|
-
role(),
|
|
175
|
-
hardRules(),
|
|
176
|
-
inventory(inputs.docs),
|
|
177
|
-
objective(inputs),
|
|
178
|
-
fileFormat(inputs),
|
|
179
|
-
workflowReminders(),
|
|
180
|
-
inlineInputs(inputs.docs),
|
|
181
|
-
footer(),
|
|
182
|
-
].join("\n\n");
|
|
183
|
-
}
|
package/dist/lib/task-merge.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { ghAuthStatus, runGh } from "./github-cli.js";
|
|
2
|
-
import { gitBranchExists, gitCheckout, gitCommitsAhead, gitDeleteBranch, gitGovernanceOnlyDirty, gitRemoteUrl, readGitInfo, runGit, } from "./git.js";
|
|
3
|
-
import { bold, cyan, dim, log } from "./logger.js";
|
|
4
|
-
function mergeCommitMessage(taskId, taskTitle, taskBranch) {
|
|
5
|
-
const slug = taskTitle.replace(/^TASK-\d+\s*[·:\-]\s*/i, "").trim() || taskId;
|
|
6
|
-
return `merge(${taskId.toLowerCase()}): ${slug} (${taskBranch})`;
|
|
7
|
-
}
|
|
8
|
-
function prTitle(taskId, taskTitle) {
|
|
9
|
-
const slug = taskTitle.replace(/^TASK-\d+\s*[·:\-]\s*/i, "").trim() || taskId;
|
|
10
|
-
return `feat(${taskId.toLowerCase()}): ${slug}`;
|
|
11
|
-
}
|
|
12
|
-
async function gitPush(cwd, remote, ref, setUpstream) {
|
|
13
|
-
const args = ["push"];
|
|
14
|
-
if (setUpstream)
|
|
15
|
-
args.push("-u");
|
|
16
|
-
args.push(remote, ref);
|
|
17
|
-
await runGit(cwd, args);
|
|
18
|
-
}
|
|
19
|
-
async function findOpenPrNumber(baseBranch, headBranch) {
|
|
20
|
-
const res = await runGh([
|
|
21
|
-
"pr",
|
|
22
|
-
"list",
|
|
23
|
-
"--head",
|
|
24
|
-
headBranch,
|
|
25
|
-
"--base",
|
|
26
|
-
baseBranch,
|
|
27
|
-
"--state",
|
|
28
|
-
"open",
|
|
29
|
-
"--json",
|
|
30
|
-
"number,url",
|
|
31
|
-
"--limit",
|
|
32
|
-
"1",
|
|
33
|
-
]);
|
|
34
|
-
if (!res.ok)
|
|
35
|
-
return null;
|
|
36
|
-
try {
|
|
37
|
-
const rows = JSON.parse(res.stdout);
|
|
38
|
-
const row = rows[0];
|
|
39
|
-
if (row && typeof row.number === "number") {
|
|
40
|
-
return { number: row.number, url: row.url };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
export async function mergeTaskBranch(opts) {
|
|
49
|
-
const mode = opts.mode ?? "direct";
|
|
50
|
-
const remote = opts.remote ?? "origin";
|
|
51
|
-
const { cwd, gitCtx, taskId, taskTitle } = opts;
|
|
52
|
-
const { baseBranch, taskBranch } = gitCtx;
|
|
53
|
-
if (baseBranch === taskBranch) {
|
|
54
|
-
log.error("Task branch matches base branch; nothing to merge.");
|
|
55
|
-
return { ok: false, mode };
|
|
56
|
-
}
|
|
57
|
-
const git = await readGitInfo(cwd);
|
|
58
|
-
if (!git.isRepo) {
|
|
59
|
-
log.error("Not a git repository.");
|
|
60
|
-
return { ok: false, mode };
|
|
61
|
-
}
|
|
62
|
-
const remoteUrl = await gitRemoteUrl(cwd, remote);
|
|
63
|
-
if (!remoteUrl) {
|
|
64
|
-
log.error(`No git remote "${remote}". Run ${cyan("vibeops github init")} or add the remote first.`);
|
|
65
|
-
return { ok: false, mode };
|
|
66
|
-
}
|
|
67
|
-
const branchExists = await gitBranchExists(cwd, taskBranch);
|
|
68
|
-
if (!branchExists) {
|
|
69
|
-
log.error(`Local task branch not found: ${taskBranch}`);
|
|
70
|
-
return { ok: false, mode };
|
|
71
|
-
}
|
|
72
|
-
const ahead = await gitCommitsAhead(cwd, baseBranch, taskBranch);
|
|
73
|
-
if (ahead === 0) {
|
|
74
|
-
log.warn(`No commits on ${taskBranch} ahead of ${baseBranch}. Merge may be a no-op.`);
|
|
75
|
-
}
|
|
76
|
-
const planLines = [
|
|
77
|
-
`push ${remote} ${taskBranch}`,
|
|
78
|
-
mode === "direct"
|
|
79
|
-
? `checkout ${baseBranch} · pull · merge --no-ff ${taskBranch} · push ${remote} ${baseBranch}`
|
|
80
|
-
: `gh pr create (if needed) · gh pr merge --merge --delete-branch`,
|
|
81
|
-
`delete local branch ${taskBranch}`,
|
|
82
|
-
mode === "direct" ? `delete remote branch ${remote}/${taskBranch}` : "(remote branch removed by gh if supported)",
|
|
83
|
-
];
|
|
84
|
-
if (opts.dryRun === true) {
|
|
85
|
-
log.info(bold("dry-run — would perform:"));
|
|
86
|
-
for (const line of planLines) {
|
|
87
|
-
log.info(` · ${line}`);
|
|
88
|
-
}
|
|
89
|
-
return { ok: true, mode };
|
|
90
|
-
}
|
|
91
|
-
log.step(bold(`Merging ${cyan(taskBranch)} → ${cyan(baseBranch)} (${mode})`));
|
|
92
|
-
try {
|
|
93
|
-
log.step(`Pushing ${taskBranch} to ${remote}…`);
|
|
94
|
-
await gitPush(cwd, remote, taskBranch, true);
|
|
95
|
-
log.ok(`pushed ${taskBranch}`);
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
99
|
-
log.error(`git push failed: ${msg}`);
|
|
100
|
-
return { ok: false, mode };
|
|
101
|
-
}
|
|
102
|
-
if (mode === "pr") {
|
|
103
|
-
const auth = await ghAuthStatus();
|
|
104
|
-
if (!auth.authenticated) {
|
|
105
|
-
log.error(`gh is not authenticated. Run ${cyan("gh auth login")} or use direct merge (default).`);
|
|
106
|
-
return { ok: false, mode };
|
|
107
|
-
}
|
|
108
|
-
let pr = await findOpenPrNumber(baseBranch, taskBranch);
|
|
109
|
-
if (!pr) {
|
|
110
|
-
log.step("Opening pull request…");
|
|
111
|
-
const create = await runGh([
|
|
112
|
-
"pr",
|
|
113
|
-
"create",
|
|
114
|
-
"--base",
|
|
115
|
-
baseBranch,
|
|
116
|
-
"--head",
|
|
117
|
-
taskBranch,
|
|
118
|
-
"--title",
|
|
119
|
-
prTitle(taskId, taskTitle),
|
|
120
|
-
"--body",
|
|
121
|
-
`Automated via vibeops next for ${taskId}.`,
|
|
122
|
-
]);
|
|
123
|
-
if (!create.ok) {
|
|
124
|
-
log.error(`gh pr create failed: ${create.stderr.trim() || create.stdout.trim()}`);
|
|
125
|
-
return { ok: false, mode };
|
|
126
|
-
}
|
|
127
|
-
const url = create.stdout.trim();
|
|
128
|
-
log.ok(`PR created${url.length > 0 ? `: ${url}` : ""}`);
|
|
129
|
-
pr = await findOpenPrNumber(baseBranch, taskBranch);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
log.ok(`Using existing PR #${pr.number}`);
|
|
133
|
-
}
|
|
134
|
-
if (!pr) {
|
|
135
|
-
log.error("Could not find PR number after create.");
|
|
136
|
-
return { ok: false, mode };
|
|
137
|
-
}
|
|
138
|
-
log.step(`Merging PR #${pr.number}…`);
|
|
139
|
-
const merged = await runGh([
|
|
140
|
-
"pr",
|
|
141
|
-
"merge",
|
|
142
|
-
String(pr.number),
|
|
143
|
-
"--merge",
|
|
144
|
-
"--delete-branch",
|
|
145
|
-
]);
|
|
146
|
-
if (!merged.ok) {
|
|
147
|
-
log.error(`gh pr merge failed: ${merged.stderr.trim() || merged.stdout.trim()}`);
|
|
148
|
-
return { ok: false, mode };
|
|
149
|
-
}
|
|
150
|
-
log.ok(`PR #${pr.number} merged`);
|
|
151
|
-
try {
|
|
152
|
-
await gitCheckout(cwd, baseBranch);
|
|
153
|
-
await runGit(cwd, ["pull", remote, baseBranch]);
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
log.warn(`Run manually: git switch ${baseBranch} && git pull ${remote} ${baseBranch}`);
|
|
157
|
-
}
|
|
158
|
-
if (await gitBranchExists(cwd, taskBranch)) {
|
|
159
|
-
try {
|
|
160
|
-
await gitDeleteBranch(cwd, taskBranch, { force: true });
|
|
161
|
-
log.ok(`deleted local branch ${taskBranch}`);
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
log.warn(`Could not delete local branch ${taskBranch}.`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return { ok: true, mode, prUrl: pr.url };
|
|
168
|
-
}
|
|
169
|
-
// direct merge
|
|
170
|
-
if (git.dirty === true && opts.allowDirty !== true) {
|
|
171
|
-
const gov = await gitGovernanceOnlyDirty(cwd);
|
|
172
|
-
if (!gov.onlyGovernance) {
|
|
173
|
-
log.error("Working tree is dirty. Commit or stash before merging, or rerun with --allow-dirty.");
|
|
174
|
-
return { ok: false, mode };
|
|
175
|
-
}
|
|
176
|
-
log.warn("Dirty tree has only governance docs — proceeding.");
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
await runGit(cwd, ["fetch", remote]);
|
|
180
|
-
await gitCheckout(cwd, baseBranch);
|
|
181
|
-
await runGit(cwd, ["pull", remote, baseBranch]);
|
|
182
|
-
log.step(`Merging ${taskBranch} into ${baseBranch}…`);
|
|
183
|
-
await runGit(cwd, [
|
|
184
|
-
"merge",
|
|
185
|
-
"--no-ff",
|
|
186
|
-
taskBranch,
|
|
187
|
-
"-m",
|
|
188
|
-
mergeCommitMessage(taskId, taskTitle, taskBranch),
|
|
189
|
-
]);
|
|
190
|
-
await gitPush(cwd, remote, baseBranch, false);
|
|
191
|
-
log.ok(`merged and pushed ${baseBranch}`);
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
195
|
-
log.error(`merge failed: ${msg}`);
|
|
196
|
-
log.info(dim(` Resolve conflicts on ${baseBranch}, then commit and push manually.`));
|
|
197
|
-
return { ok: false, mode };
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
await gitDeleteBranch(cwd, taskBranch, { force: false });
|
|
201
|
-
log.ok(`deleted local branch ${taskBranch}`);
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
try {
|
|
205
|
-
await gitDeleteBranch(cwd, taskBranch, { force: true });
|
|
206
|
-
log.ok(`deleted local branch ${taskBranch} (force)`);
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
log.warn(`Could not delete local branch ${taskBranch}.`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
await runGit(cwd, ["push", remote, "--delete", taskBranch]);
|
|
214
|
-
log.ok(`deleted remote branch ${remote}/${taskBranch}`);
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
log.warn(dim(`Remote branch may already be gone. To remove: git push ${remote} --delete ${taskBranch}`));
|
|
218
|
-
}
|
|
219
|
-
return { ok: true, mode };
|
|
220
|
-
}
|
package/dist/lib/task-prompt.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { findAgent, listAgents } from "../agent/loader.js";
|
|
2
|
-
import { buildPrompt } from "../agent/prompt.js";
|
|
3
|
-
import { readConfig } from "./config.js";
|
|
4
|
-
import { readText } from "./filesystem.js";
|
|
5
|
-
import { readTaskFile } from "./task.js";
|
|
6
|
-
export async function buildTaskPromptString(inputs) {
|
|
7
|
-
const agent = await findAgent(inputs.agentsDir, inputs.agentName);
|
|
8
|
-
if (!agent) {
|
|
9
|
-
const available = (await listAgents(inputs.agentsDir)).map((a) => a.meta.name);
|
|
10
|
-
return { ok: false, reason: "agent-not-found", available };
|
|
11
|
-
}
|
|
12
|
-
const config = await readConfig(inputs.projectRoot);
|
|
13
|
-
const meta = await readTaskFile(inputs.taskFilePath);
|
|
14
|
-
const body = await readText(inputs.taskFilePath);
|
|
15
|
-
const prompt = await buildPrompt({
|
|
16
|
-
agent,
|
|
17
|
-
config,
|
|
18
|
-
task: { meta, body },
|
|
19
|
-
projectRoot: inputs.projectRoot,
|
|
20
|
-
contextPaths: inputs.contextPaths ?? [],
|
|
21
|
-
});
|
|
22
|
-
return { ok: true, prompt };
|
|
23
|
-
}
|