@goodtek/vibeops 0.2.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 +28 -0
- package/LICENSE +21 -0
- package/README.md +444 -0
- package/dist/agent/loader.js +71 -0
- package/dist/agent/prompt.js +66 -0
- package/dist/bootstrap/installer.js +149 -0
- package/dist/bootstrap/manifest.js +15 -0
- package/dist/bootstrap/substitute.js +35 -0
- package/dist/cli.js +241 -0
- package/dist/commands/agent-list.js +32 -0
- package/dist/commands/agent-prompt.js +59 -0
- package/dist/commands/agent-show.js +26 -0
- package/dist/commands/github-init.js +554 -0
- package/dist/commands/github-status.js +164 -0
- package/dist/commands/init.js +179 -0
- package/dist/commands/notion-init.js +764 -0
- package/dist/commands/notion-sync.js +405 -0
- package/dist/commands/notion-test.js +595 -0
- package/dist/commands/plan.js +114 -0
- package/dist/commands/status.js +17 -0
- package/dist/commands/task-check.js +155 -0
- package/dist/commands/task-done.js +98 -0
- package/dist/commands/task-generate.js +206 -0
- package/dist/commands/task-pull.js +277 -0
- package/dist/commands/task-rollback.js +174 -0
- package/dist/commands/task-start.js +90 -0
- package/dist/lib/brief.js +349 -0
- package/dist/lib/config.js +158 -0
- package/dist/lib/filesystem.js +67 -0
- package/dist/lib/git.js +237 -0
- package/dist/lib/github-cli.js +247 -0
- package/dist/lib/inquirer-helpers.js +111 -0
- package/dist/lib/logger.js +42 -0
- package/dist/lib/notion-client.js +459 -0
- package/dist/lib/notion-discovery.js +671 -0
- package/dist/lib/notion-env.js +140 -0
- package/dist/lib/notion-mappers.js +148 -0
- package/dist/lib/notion-schema.js +272 -0
- package/dist/lib/notion-sync.js +337 -0
- package/dist/lib/notion-target.js +247 -0
- package/dist/lib/package-json.js +133 -0
- package/dist/lib/paths.js +26 -0
- package/dist/lib/project-docs.js +95 -0
- package/dist/lib/prompt-builder.js +125 -0
- package/dist/lib/task-generator.js +183 -0
- package/dist/lib/task-prompt.js +23 -0
- package/dist/lib/task-pull.js +354 -0
- package/dist/lib/task-scaffold.js +128 -0
- package/dist/lib/task-summary.js +276 -0
- package/dist/lib/task.js +364 -0
- package/dist/status/collector.js +103 -0
- package/dist/status/format.js +177 -0
- package/dist/types/brief.js +126 -0
- package/dist/types/config.js +17 -0
- package/dist/types/task.js +1 -0
- package/dist/version.js +8 -0
- package/package.json +61 -0
- package/templates/.cursor/rules/00-project-governance.mdc +28 -0
- package/templates/.cursor/rules/01-agent-orchestration.mdc +48 -0
- package/templates/.cursor/rules/02-task-workflow.mdc +38 -0
- package/templates/.cursor/rules/03-git-safety.mdc +30 -0
- package/templates/.cursor/rules/04-docs-update.mdc +22 -0
- package/templates/.vibeops/agents/architect.md +47 -0
- package/templates/.vibeops/agents/builder.md +38 -0
- package/templates/.vibeops/agents/docs.md +54 -0
- package/templates/.vibeops/agents/orchestrator.md +40 -0
- package/templates/.vibeops/agents/planner.md +60 -0
- package/templates/.vibeops/agents/recovery.md +49 -0
- package/templates/.vibeops/agents/reviewer.md +47 -0
- package/templates/.vibeops/agents/tester.md +43 -0
- package/templates/.vibeops/prompts/create-plan.md +33 -0
- package/templates/.vibeops/prompts/generate-tasks.md +41 -0
- package/templates/.vibeops/prompts/implement-task.md +39 -0
- package/templates/.vibeops/prompts/review-task.md +34 -0
- package/templates/.vibeops/prompts/rollback.md +32 -0
- package/templates/.vibeops/prompts/start-project.md +39 -0
- package/templates/.vibeops/workflows/notion-sync.md +53 -0
- package/templates/.vibeops/workflows/project-start.md +73 -0
- package/templates/.vibeops/workflows/rollback.md +45 -0
- package/templates/.vibeops/workflows/task-lifecycle.md +71 -0
- package/templates/AGENTS.md +98 -0
- package/templates/docs/logs/README.md +38 -0
- package/templates/docs/project/00-overview.md +27 -0
- package/templates/docs/project/01-requirements.md +30 -0
- package/templates/docs/project/02-mvp-scope.md +36 -0
- package/templates/docs/project/03-architecture.md +34 -0
- package/templates/docs/project/04-tech-stack.md +29 -0
- package/templates/docs/project/05-current-state.md +35 -0
- package/templates/docs/project/06-decisions.md +20 -0
- package/templates/docs/project/07-backlog.md +23 -0
- package/templates/docs/project/08-env.md +29 -0
- package/templates/docs/project/09-deployment.md +28 -0
- package/templates/docs/tasks/TASK-000-template.md +72 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
import { isDirectory, isFile, pathExists } from "../lib/filesystem.js";
|
|
3
|
+
import { readConfig } from "../lib/config.js";
|
|
4
|
+
import { readGitInfo } from "../lib/git.js";
|
|
5
|
+
import { getNotionTokenSource } from "../lib/notion-env.js";
|
|
6
|
+
import { readPackageJson, } from "../lib/package-json.js";
|
|
7
|
+
import { countTasks, pickNextTask, scanTasks } from "../lib/task.js";
|
|
8
|
+
import { projectPaths } from "../lib/paths.js";
|
|
9
|
+
import { DEFAULT_GITHUB_CONFIG, } from "../types/config.js";
|
|
10
|
+
async function presenceCheck(label, path, required, asDir) {
|
|
11
|
+
const present = asDir ? await isDirectory(path) : await isFile(path);
|
|
12
|
+
return { label, path, present, required };
|
|
13
|
+
}
|
|
14
|
+
function snapshotNotion(notion, token) {
|
|
15
|
+
const projects = (notion?.projectsTargetId ?? "").length > 0 ||
|
|
16
|
+
(notion?.projectsDatabaseId ?? "").length > 0;
|
|
17
|
+
const tasks = (notion?.tasksTargetId ?? "").length > 0 ||
|
|
18
|
+
(notion?.tasksDatabaseId ?? "").length > 0;
|
|
19
|
+
return {
|
|
20
|
+
enabled: notion?.enabled ?? false,
|
|
21
|
+
hasToken: token.hasToken,
|
|
22
|
+
tokenSource: token.source,
|
|
23
|
+
hasProjectsTarget: projects,
|
|
24
|
+
hasTasksTarget: tasks,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function snapshotGithub(github) {
|
|
28
|
+
const g = github ?? DEFAULT_GITHUB_CONFIG;
|
|
29
|
+
return {
|
|
30
|
+
enabled: g.enabled,
|
|
31
|
+
mode: g.enabled ? g.mode : "",
|
|
32
|
+
owner: g.owner,
|
|
33
|
+
repo: g.repo,
|
|
34
|
+
remote: g.remote,
|
|
35
|
+
url: g.url,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function pickBinName(pkg) {
|
|
39
|
+
const bin = pkg.bin;
|
|
40
|
+
if (typeof bin === "string") {
|
|
41
|
+
return basename(bin).replace(/\.[A-Za-z0-9]+$/, "");
|
|
42
|
+
}
|
|
43
|
+
if (bin !== null && typeof bin === "object") {
|
|
44
|
+
const keys = Object.keys(bin);
|
|
45
|
+
if (keys.length > 0)
|
|
46
|
+
return keys[0];
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
function snapshotPackage(pkg) {
|
|
51
|
+
if (pkg === null) {
|
|
52
|
+
return { exists: false, name: "", version: "", bin: "" };
|
|
53
|
+
}
|
|
54
|
+
const data = pkg.data;
|
|
55
|
+
const name = typeof data.name === "string" ? data.name : "";
|
|
56
|
+
const version = typeof data.version === "string"
|
|
57
|
+
? (data.version ?? "")
|
|
58
|
+
: "";
|
|
59
|
+
return { exists: true, name, version, bin: pickBinName(data) };
|
|
60
|
+
}
|
|
61
|
+
export async function collectStatus(cwd) {
|
|
62
|
+
const paths = projectPaths(cwd);
|
|
63
|
+
const config = await readConfig(paths.root);
|
|
64
|
+
const checks = [
|
|
65
|
+
await presenceCheck(".vibeops.json", paths.config, true, false),
|
|
66
|
+
await presenceCheck("AGENTS.md", paths.agentsMd, true, false),
|
|
67
|
+
await presenceCheck(".cursor/rules/", paths.cursorRules, true, true),
|
|
68
|
+
await presenceCheck("docs/project/", paths.docsProject, true, true),
|
|
69
|
+
await presenceCheck("docs/tasks/", paths.docsTasks, true, true),
|
|
70
|
+
await presenceCheck("docs/logs/", paths.docsLogs, false, true),
|
|
71
|
+
await presenceCheck(".vibeops/agents/", paths.vibeopsAgents, true, true),
|
|
72
|
+
await presenceCheck(".vibeops/prompts/", paths.vibeopsPrompts, false, true),
|
|
73
|
+
await presenceCheck(".vibeops/workflows/", paths.vibeopsWorkflows, false, true),
|
|
74
|
+
await presenceCheck(".vibeops.env.example", paths.envExample, false, false),
|
|
75
|
+
];
|
|
76
|
+
const missingRequired = checks.filter((c) => c.required && !c.present);
|
|
77
|
+
const tasks = (await pathExists(paths.docsTasks))
|
|
78
|
+
? await scanTasks(paths.docsTasks)
|
|
79
|
+
: [];
|
|
80
|
+
const taskCounts = countTasks(tasks);
|
|
81
|
+
const nextTask = pickNextTask(tasks);
|
|
82
|
+
const git = await readGitInfo(paths.root);
|
|
83
|
+
const tokenProbe = await getNotionTokenSource(paths.root);
|
|
84
|
+
const notion = snapshotNotion(config?.notion, tokenProbe);
|
|
85
|
+
const github = snapshotGithub(config?.github);
|
|
86
|
+
const pkg = await readPackageJson(paths.root);
|
|
87
|
+
const packageSnapshot = snapshotPackage(pkg);
|
|
88
|
+
const isVibeopsProject = config !== null && missingRequired.length === 0;
|
|
89
|
+
return {
|
|
90
|
+
cwd: paths.root,
|
|
91
|
+
isVibeopsProject,
|
|
92
|
+
config,
|
|
93
|
+
checks,
|
|
94
|
+
missingRequired,
|
|
95
|
+
tasks,
|
|
96
|
+
taskCounts,
|
|
97
|
+
nextTask,
|
|
98
|
+
git,
|
|
99
|
+
notion,
|
|
100
|
+
github,
|
|
101
|
+
package: packageSnapshot,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { bold, cyan, dim, green, log, red, yellow } from "../lib/logger.js";
|
|
3
|
+
function relOrAbs(root, p) {
|
|
4
|
+
const r = relative(root, p);
|
|
5
|
+
if (r === "")
|
|
6
|
+
return ".";
|
|
7
|
+
if (r.startsWith(".."))
|
|
8
|
+
return p;
|
|
9
|
+
return r;
|
|
10
|
+
}
|
|
11
|
+
function statusBadge(value, label) {
|
|
12
|
+
if (value === 0)
|
|
13
|
+
return dim(`${label}:0`);
|
|
14
|
+
return `${label}:${value}`;
|
|
15
|
+
}
|
|
16
|
+
function pad(label, width) {
|
|
17
|
+
if (label.length >= width)
|
|
18
|
+
return label;
|
|
19
|
+
return label + " ".repeat(width - label.length);
|
|
20
|
+
}
|
|
21
|
+
function tokenLine(report) {
|
|
22
|
+
if (!report.notion.hasToken)
|
|
23
|
+
return red("missing");
|
|
24
|
+
return `${green("configured")} ${dim(`(${report.notion.tokenSource})`)}`;
|
|
25
|
+
}
|
|
26
|
+
function targetLine(present) {
|
|
27
|
+
return present ? green("configured") : red("missing");
|
|
28
|
+
}
|
|
29
|
+
function notionHint(report) {
|
|
30
|
+
if (!report.notion.enabled)
|
|
31
|
+
return "run `vibeops notion init`";
|
|
32
|
+
if (!report.notion.hasToken)
|
|
33
|
+
return "set NOTION_TOKEN in `.vibeops.env`";
|
|
34
|
+
if (!report.notion.hasProjectsTarget || !report.notion.hasTasksTarget) {
|
|
35
|
+
return "run `vibeops notion init` to pick a Projects / Tasks DB";
|
|
36
|
+
}
|
|
37
|
+
return "run `vibeops notion test`";
|
|
38
|
+
}
|
|
39
|
+
export function printHuman(report) {
|
|
40
|
+
log.info(bold("Project"));
|
|
41
|
+
if (report.config) {
|
|
42
|
+
log.info(` name ${report.config.name}`);
|
|
43
|
+
log.info(` vibeopsVersion ${report.config.vibeopsVersion}`);
|
|
44
|
+
log.info(` schemaVersion ${String(report.config.schemaVersion)}`);
|
|
45
|
+
log.info(` createdAt ${report.config.createdAt}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
log.info(` ${red("(no .vibeops.json — not a VibeOps project)")}`);
|
|
49
|
+
}
|
|
50
|
+
log.blank();
|
|
51
|
+
log.info(bold("Installation"));
|
|
52
|
+
for (const c of report.checks) {
|
|
53
|
+
const tag = c.present
|
|
54
|
+
? green("✓")
|
|
55
|
+
: c.required
|
|
56
|
+
? red("✗")
|
|
57
|
+
: yellow("·");
|
|
58
|
+
const reqHint = c.required ? "" : dim(" (optional)");
|
|
59
|
+
log.info(` ${tag} ${c.label}${reqHint} ${dim(relOrAbs(report.cwd, c.path))}`);
|
|
60
|
+
}
|
|
61
|
+
if (report.missingRequired.length > 0) {
|
|
62
|
+
log.blank();
|
|
63
|
+
log.warn(`${report.missingRequired.length} required path(s) missing.`);
|
|
64
|
+
}
|
|
65
|
+
log.blank();
|
|
66
|
+
log.info(bold("Tasks"));
|
|
67
|
+
const { total, planned, in_progress, review, blocked, done } = report.taskCounts;
|
|
68
|
+
log.info(` ${cyan(`total:${total}`)} ${statusBadge(planned, "planned")} ${statusBadge(in_progress, "in_progress")} ${statusBadge(review, "review")} ${statusBadge(blocked, "blocked")} ${statusBadge(done, "done")}`);
|
|
69
|
+
if (report.nextTask) {
|
|
70
|
+
log.info(` next ${report.nextTask.id} — ${report.nextTask.title || dim("(no title)")} ${dim(`[${report.nextTask.status}]`)}`);
|
|
71
|
+
}
|
|
72
|
+
else if (total === 0) {
|
|
73
|
+
log.info(` ${dim("no TASK files yet — run `vibeops task generate`")}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log.info(` ${dim("all tasks done")}`);
|
|
77
|
+
}
|
|
78
|
+
log.blank();
|
|
79
|
+
log.info(bold("Git"));
|
|
80
|
+
if (!report.git.isRepo) {
|
|
81
|
+
log.info(` ${dim("not a git repository")}`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (report.git.state === "unborn") {
|
|
85
|
+
log.info(` branch ${report.git.branch ?? "(unknown)"} ${dim("(unborn, no commits yet)")}`);
|
|
86
|
+
}
|
|
87
|
+
else if (report.git.state === "detached") {
|
|
88
|
+
log.info(` branch ${dim("(detached)")}${report.git.branch ? ` ${report.git.branch}` : ""}`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
log.info(` branch ${report.git.branch ?? dim("(unknown)")}`);
|
|
92
|
+
}
|
|
93
|
+
log.info(` status ${report.git.dirty ? yellow("dirty") : green("clean")}`);
|
|
94
|
+
if (report.git.state === "unborn") {
|
|
95
|
+
log.info(` hint create the first commit or run ${cyan("`vibeops init --git --initial-commit`")}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
log.blank();
|
|
99
|
+
// Notion: enabled / token / projects target / tasks target / hint.
|
|
100
|
+
// We intentionally do NOT print legacy env names (NOTION_API_KEY,
|
|
101
|
+
// NOTION_PROJECT_DB, NOTION_TASK_DB) — modern VibeOps only uses
|
|
102
|
+
// NOTION_TOKEN + notion.{projectsTargetId,tasksTargetId}.
|
|
103
|
+
log.info(bold("Notion"));
|
|
104
|
+
const notionLabelWidth = 16; // "projects target"
|
|
105
|
+
log.info(` ${pad("enabled", notionLabelWidth)} ${report.notion.enabled ? green("yes") : dim("no")}`);
|
|
106
|
+
log.info(` ${pad("token", notionLabelWidth)} ${tokenLine(report)}`);
|
|
107
|
+
log.info(` ${pad("projects target", notionLabelWidth)} ${targetLine(report.notion.hasProjectsTarget)}`);
|
|
108
|
+
log.info(` ${pad("tasks target", notionLabelWidth)} ${targetLine(report.notion.hasTasksTarget)}`);
|
|
109
|
+
log.info(` ${pad("hint", notionLabelWidth)} ${dim(notionHint(report))}`);
|
|
110
|
+
log.blank();
|
|
111
|
+
log.info(bold("GitHub"));
|
|
112
|
+
const githubLabelWidth = 11; // "owner/repo"
|
|
113
|
+
if (!report.github.enabled) {
|
|
114
|
+
log.info(` ${pad("enabled", githubLabelWidth)} ${dim("no")}`);
|
|
115
|
+
log.info(` ${pad("hint", githubLabelWidth)} ${dim("run `vibeops github init`")}`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
log.info(` ${pad("enabled", githubLabelWidth)} ${green("yes")}`);
|
|
119
|
+
if (report.github.mode.length > 0) {
|
|
120
|
+
log.info(` ${pad("mode", githubLabelWidth)} ${report.github.mode}`);
|
|
121
|
+
}
|
|
122
|
+
const slug = report.github.owner.length > 0 && report.github.repo.length > 0
|
|
123
|
+
? `${report.github.owner}/${report.github.repo}`
|
|
124
|
+
: dim("(unknown)");
|
|
125
|
+
log.info(` ${pad("owner/repo", githubLabelWidth)} ${slug}`);
|
|
126
|
+
log.info(` ${pad("remote", githubLabelWidth)} ${report.github.remote.length > 0 ? report.github.remote : dim("(none)")}`);
|
|
127
|
+
log.info(` ${pad("url", githubLabelWidth)} ${report.github.url.length > 0 ? report.github.url : dim("(none)")}`);
|
|
128
|
+
}
|
|
129
|
+
log.blank();
|
|
130
|
+
log.info(bold("Package"));
|
|
131
|
+
const pkgLabelWidth = 8; // "version"
|
|
132
|
+
if (!report.package.exists) {
|
|
133
|
+
log.info(` ${dim("package.json missing")}`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
log.info(` ${pad("name", pkgLabelWidth)} ${report.package.name.length > 0 ? report.package.name : dim("(unset)")}`);
|
|
137
|
+
log.info(` ${pad("version", pkgLabelWidth)} ${report.package.version.length > 0 ? report.package.version : dim("(unset)")}`);
|
|
138
|
+
log.info(` ${pad("bin", pkgLabelWidth)} ${report.package.bin.length > 0 ? report.package.bin : dim("(none)")}`);
|
|
139
|
+
}
|
|
140
|
+
log.blank();
|
|
141
|
+
if (!report.isVibeopsProject) {
|
|
142
|
+
log.error("Not a VibeOps project — run `vibeops init` first.");
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
log.ok("VibeOps project ready.");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function toJson(report) {
|
|
149
|
+
return `${JSON.stringify({
|
|
150
|
+
cwd: report.cwd,
|
|
151
|
+
isVibeopsProject: report.isVibeopsProject,
|
|
152
|
+
config: report.config,
|
|
153
|
+
installation: {
|
|
154
|
+
checks: report.checks.map((c) => ({
|
|
155
|
+
label: c.label,
|
|
156
|
+
path: c.path,
|
|
157
|
+
present: c.present,
|
|
158
|
+
required: c.required,
|
|
159
|
+
})),
|
|
160
|
+
missingRequired: report.missingRequired.map((c) => c.label),
|
|
161
|
+
},
|
|
162
|
+
tasks: {
|
|
163
|
+
counts: report.taskCounts,
|
|
164
|
+
next: report.nextTask
|
|
165
|
+
? {
|
|
166
|
+
id: report.nextTask.id,
|
|
167
|
+
title: report.nextTask.title,
|
|
168
|
+
status: report.nextTask.status,
|
|
169
|
+
}
|
|
170
|
+
: null,
|
|
171
|
+
},
|
|
172
|
+
git: report.git,
|
|
173
|
+
notion: report.notion,
|
|
174
|
+
github: report.github,
|
|
175
|
+
package: report.package,
|
|
176
|
+
}, null, 2)}\n`;
|
|
177
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export const PROJECT_BRIEF_SCHEMA_VERSION = 1;
|
|
2
|
+
export const PROJECT_TYPE_CHOICES = [
|
|
3
|
+
"SaaS",
|
|
4
|
+
"Web App",
|
|
5
|
+
"CLI Tool",
|
|
6
|
+
"Browser Automation",
|
|
7
|
+
"AI Agent",
|
|
8
|
+
"Internal Tool",
|
|
9
|
+
"Other",
|
|
10
|
+
];
|
|
11
|
+
export const TARGET_USER_CHOICES = [
|
|
12
|
+
"Solo founders",
|
|
13
|
+
"Developers",
|
|
14
|
+
"Marketers",
|
|
15
|
+
"Small business owners",
|
|
16
|
+
"Internal team",
|
|
17
|
+
"Other",
|
|
18
|
+
];
|
|
19
|
+
export const MVP_FEATURE_CHOICES = [
|
|
20
|
+
"Authentication",
|
|
21
|
+
"Dashboard",
|
|
22
|
+
"Project/workspace management",
|
|
23
|
+
"Task/job creation",
|
|
24
|
+
"Background worker",
|
|
25
|
+
"Browser automation",
|
|
26
|
+
"Scheduling",
|
|
27
|
+
"Execution logs",
|
|
28
|
+
"External integrations",
|
|
29
|
+
"Other",
|
|
30
|
+
];
|
|
31
|
+
export const OUT_OF_SCOPE_CHOICES = [
|
|
32
|
+
"Billing",
|
|
33
|
+
"Team workspace",
|
|
34
|
+
"Mobile app",
|
|
35
|
+
"Marketplace",
|
|
36
|
+
"Advanced analytics",
|
|
37
|
+
"Enterprise SSO",
|
|
38
|
+
"Public API",
|
|
39
|
+
"Real-time collaboration",
|
|
40
|
+
"Other",
|
|
41
|
+
];
|
|
42
|
+
export const FRONTEND_CHOICES = [
|
|
43
|
+
"Next.js",
|
|
44
|
+
"React + Vite",
|
|
45
|
+
"None / CLI only",
|
|
46
|
+
"Not sure",
|
|
47
|
+
"Other",
|
|
48
|
+
];
|
|
49
|
+
export const BACKEND_CHOICES = [
|
|
50
|
+
"NestJS",
|
|
51
|
+
"Next.js API routes",
|
|
52
|
+
"Node.js Fastify",
|
|
53
|
+
"Hono",
|
|
54
|
+
"Python FastAPI",
|
|
55
|
+
"None",
|
|
56
|
+
"Not sure",
|
|
57
|
+
"Other",
|
|
58
|
+
];
|
|
59
|
+
export const DATABASE_CHOICES = [
|
|
60
|
+
"PostgreSQL",
|
|
61
|
+
"SQLite",
|
|
62
|
+
"MySQL",
|
|
63
|
+
"Supabase",
|
|
64
|
+
"None",
|
|
65
|
+
"Not sure",
|
|
66
|
+
"Other",
|
|
67
|
+
];
|
|
68
|
+
export const DB_LAYER_CHOICES = [
|
|
69
|
+
"Drizzle",
|
|
70
|
+
"Prisma",
|
|
71
|
+
"Kysely",
|
|
72
|
+
"Raw SQL",
|
|
73
|
+
"None",
|
|
74
|
+
"Not sure",
|
|
75
|
+
"Other",
|
|
76
|
+
];
|
|
77
|
+
export const PACKAGE_MANAGER_CHOICES = ["pnpm", "npm", "yarn", "bun"];
|
|
78
|
+
export const DEPLOYMENT_TARGET_CHOICES = [
|
|
79
|
+
"VPS",
|
|
80
|
+
"Docker",
|
|
81
|
+
"Podman",
|
|
82
|
+
"Vercel",
|
|
83
|
+
"Cloudflare",
|
|
84
|
+
"AWS",
|
|
85
|
+
"Not sure",
|
|
86
|
+
"Other",
|
|
87
|
+
];
|
|
88
|
+
export const AUTH_REQUIREMENT_CHOICES = [
|
|
89
|
+
"Email/password",
|
|
90
|
+
"Google login",
|
|
91
|
+
"GitHub login",
|
|
92
|
+
"Magic link",
|
|
93
|
+
"Admin-only",
|
|
94
|
+
"No auth for MVP",
|
|
95
|
+
"Not sure",
|
|
96
|
+
"Other",
|
|
97
|
+
];
|
|
98
|
+
export const INTEGRATION_CHOICES = [
|
|
99
|
+
"Notion",
|
|
100
|
+
"GitHub",
|
|
101
|
+
"Google Drive",
|
|
102
|
+
"Gmail",
|
|
103
|
+
"Slack",
|
|
104
|
+
"Stripe",
|
|
105
|
+
"OpenAI",
|
|
106
|
+
"Anthropic",
|
|
107
|
+
"Browser / Playwright",
|
|
108
|
+
"None",
|
|
109
|
+
"Other",
|
|
110
|
+
];
|
|
111
|
+
export const AGENT_WORKFLOW_CHOICES = [
|
|
112
|
+
"Simple: Planner + Builder + Reviewer",
|
|
113
|
+
"Standard: Orchestrator + Planner + Builder + Reviewer + Docs",
|
|
114
|
+
"Advanced: Orchestrator + Planner + Architect + Builder + Tester + Reviewer + Docs + Recovery",
|
|
115
|
+
];
|
|
116
|
+
export const RISK_AREA_CHOICES = [
|
|
117
|
+
"Authentication/security",
|
|
118
|
+
"Browser automation reliability",
|
|
119
|
+
"Cost control",
|
|
120
|
+
"Scalability",
|
|
121
|
+
"Data privacy",
|
|
122
|
+
"Deployment complexity",
|
|
123
|
+
"Background jobs",
|
|
124
|
+
"AI hallucination",
|
|
125
|
+
"Other",
|
|
126
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const VIBEOPS_CONFIG_SCHEMA_VERSION = 1;
|
|
2
|
+
export const DEFAULT_NOTION_CONFIG = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
projectsTargetId: "",
|
|
5
|
+
tasksTargetId: "",
|
|
6
|
+
projectsDatabaseId: "",
|
|
7
|
+
tasksDatabaseId: "",
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_GITHUB_CONFIG = {
|
|
10
|
+
enabled: false,
|
|
11
|
+
mode: "gh-cli",
|
|
12
|
+
owner: "",
|
|
13
|
+
repo: "",
|
|
14
|
+
remote: "origin",
|
|
15
|
+
visibility: "",
|
|
16
|
+
url: "",
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const pkgPath = join(here, "..", "package.json");
|
|
6
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
7
|
+
export const VERSION = pkg.version;
|
|
8
|
+
export const NAME = pkg.name;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@goodtek/vibeops",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Workflow rails for Cursor-based vibe coding projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "VibeOps contributors",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/goodtekxyz/vibeops.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/goodtekxyz/vibeops#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/goodtekxyz/vibeops/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"vibeops": "dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"templates",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"cursor",
|
|
34
|
+
"ai",
|
|
35
|
+
"coding",
|
|
36
|
+
"cli",
|
|
37
|
+
"task",
|
|
38
|
+
"notion",
|
|
39
|
+
"vibeops",
|
|
40
|
+
"workflow"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@inquirer/prompts": "^8.4.3",
|
|
44
|
+
"@notionhq/client": "^5.20.0",
|
|
45
|
+
"commander": "^12.1.0",
|
|
46
|
+
"gray-matter": "^4.0.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.14.0",
|
|
50
|
+
"tsx": "^4.19.0",
|
|
51
|
+
"typescript": "^5.5.4"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"dev": "tsx src/cli.ts",
|
|
55
|
+
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
57
|
+
"start": "node dist/cli.js",
|
|
58
|
+
"smoke": "pnpm typecheck && pnpm build && node scripts/smoke.mjs",
|
|
59
|
+
"publish:dry": "pnpm publish --dry-run --no-git-checks"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Governance for {{PROJECT_NAME}} — source of truth, single TASK, MVP boundary.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Governance
|
|
7
|
+
|
|
8
|
+
## Source of truth
|
|
9
|
+
|
|
10
|
+
- **`docs/project/`** and **`docs/tasks/`** take precedence. Chat history is not trusted.
|
|
11
|
+
- When docs and chat disagree, align the docs **first**, then implement.
|
|
12
|
+
- The human dashboard (Notion) syncs **summary, status, priority, branch, docs path, and result summary only**. Full bodies live in `docs/tasks/*.md`.
|
|
13
|
+
|
|
14
|
+
## One TASK at a time
|
|
15
|
+
|
|
16
|
+
- Implementation must correspond to **a single TASK**.
|
|
17
|
+
- Stay inside the TASK's **Scope / Acceptance Criteria**.
|
|
18
|
+
- Use the TASK ID (`TASK-NNN`) consistently across commit messages, branch names, and log entries.
|
|
19
|
+
|
|
20
|
+
## Protect the MVP scope
|
|
21
|
+
|
|
22
|
+
- Do **not** add features that are not listed in `docs/project/02-mvp-scope.md`.
|
|
23
|
+
- "Convenience" items such as hosting, realtime sync, webhooks, and automatic LLM calls are introduced only via a dedicated TASK.
|
|
24
|
+
|
|
25
|
+
## Refactors and integrations
|
|
26
|
+
|
|
27
|
+
- Large refactors happen only when a dedicated TASK calls for them.
|
|
28
|
+
- External integrations (Git host APIs, Notion, messengers, …) are added only when the TASK explicitly says so.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: How 8 agents in .vibeops/agents/* collaborate on a single TASK.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Orchestration
|
|
7
|
+
|
|
8
|
+
This project defines 8 agents in `.vibeops/agents/*.md`. Every agent stays inside **its own role**.
|
|
9
|
+
|
|
10
|
+
## Flow
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
user / PM
|
|
14
|
+
│
|
|
15
|
+
▼
|
|
16
|
+
[orchestrator] ── picks the next TASK, calls the right agent
|
|
17
|
+
│
|
|
18
|
+
├──▶ [planner] idea → docs/project/{00,01,02,07}
|
|
19
|
+
├──▶ [architect] docs/project/{03,04}
|
|
20
|
+
├──▶ [builder] one TASK, code changes
|
|
21
|
+
├──▶ [reviewer] diff vs Acceptance Criteria
|
|
22
|
+
├──▶ [tester] run Test Plan → Test Result
|
|
23
|
+
├──▶ [docs] update 05-current-state / TASK Result / logs
|
|
24
|
+
└──▶ [recovery] diagnose rollback (destructive actions require --confirm)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How to invoke
|
|
28
|
+
|
|
29
|
+
Get an agent prompt from the VibeOps CLI:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
vibeops agent prompt <agent> <TASK-NNN>
|
|
33
|
+
vibeops task prompt <TASK-NNN> --agent <agent>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Paste the resulting markdown directly into the Cursor chat.
|
|
37
|
+
|
|
38
|
+
## Role boundaries
|
|
39
|
+
|
|
40
|
+
- `builder` does not do `reviewer`'s job (self-checks are fine, but a real review is a separate call).
|
|
41
|
+
- `planner` does not write code. `architect` does not decide business logic.
|
|
42
|
+
- `docs` does not make code changes — documentation only.
|
|
43
|
+
- No agent runs **multiple TASKs in parallel**.
|
|
44
|
+
|
|
45
|
+
## Forbidden
|
|
46
|
+
|
|
47
|
+
- "Not really my job but I'll just do it" — no. Call the right agent again.
|
|
48
|
+
- Editing the agent definition files (`.vibeops/agents/*.md`) without a TASK that authorizes it.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Task workflow — start, prompt, check, done lifecycle and dry-run discipline.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Task Workflow
|
|
7
|
+
|
|
8
|
+
## Before you start
|
|
9
|
+
|
|
10
|
+
1. Read `docs/project/05-current-state.md` and identify the next TASK to run.
|
|
11
|
+
2. Read the **entire** `docs/tasks/TASK-NNN-*.md` file.
|
|
12
|
+
3. Keep Scope / Out of Scope / Acceptance Criteria in mind.
|
|
13
|
+
4. **Search** the existing code for similar implementations and patterns (do not duplicate).
|
|
14
|
+
|
|
15
|
+
## While implementing
|
|
16
|
+
|
|
17
|
+
- Run **one TASK at a time**.
|
|
18
|
+
- Search the existing code once more before adding new files, functions, or patterns.
|
|
19
|
+
- **Every CLI command should support `--dry-run`** (or an equivalent) so the plan can be previewed without touching files, Git, or remotes.
|
|
20
|
+
- Do not edit the next TASK file before the current TASK is done.
|
|
21
|
+
|
|
22
|
+
## Lifecycle commands
|
|
23
|
+
|
|
24
|
+
| Step | Command |
|
|
25
|
+
| ---------- | -------------------------------------------------------------------- |
|
|
26
|
+
| Start | `vibeops task start TASK-NNN` |
|
|
27
|
+
| Work | `vibeops task prompt TASK-NNN --agent builder` → paste into Cursor |
|
|
28
|
+
| Check | `vibeops task check TASK-NNN` |
|
|
29
|
+
| Finish | `vibeops task done TASK-NNN` (no auto-merge) |
|
|
30
|
+
| Roll back | `vibeops task rollback TASK-NNN` (guidance by default, destructive paths need `--confirm`) |
|
|
31
|
+
|
|
32
|
+
## Definition of done
|
|
33
|
+
|
|
34
|
+
- All Acceptance Criteria pass.
|
|
35
|
+
- The TASK file's **Result** and **Test Result** are non-empty.
|
|
36
|
+
- `docs/project/05-current-state.md` and `docs/logs/YYYY-MM-DD.md` are updated.
|
|
37
|
+
|
|
38
|
+
A TASK is not considered complete until all three updates land.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Git safety — branch per TASK, rollback safety, never force-push.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Safety
|
|
7
|
+
|
|
8
|
+
## Branch policy
|
|
9
|
+
|
|
10
|
+
- One branch per TASK: `task/<task-id>-<slug>`, e.g. `task/002-init-command`.
|
|
11
|
+
- At TASK start, the base branch / base commit / task branch are recorded in `.vibeops/state/tasks/TASK-NNN.json` (automatic).
|
|
12
|
+
- After merge, the task branch is deleted by default (`git branch -d`). Discarding an unmerged branch goes through the rollback flow.
|
|
13
|
+
|
|
14
|
+
## Commit messages
|
|
15
|
+
|
|
16
|
+
- Include the TASK ID. Examples: `feat(TASK-002): vibeops init`, `docs(TASK-006): plan command spec`.
|
|
17
|
+
- One commit covers only one TASK's scope.
|
|
18
|
+
|
|
19
|
+
## Safety rules
|
|
20
|
+
|
|
21
|
+
- `vibeops task rollback` produces **guidance by default**. Destructive Git actions run only with `--confirm`.
|
|
22
|
+
- **No force-push** (especially on shared branches).
|
|
23
|
+
- No automatic merge, no automatic push. A human does both explicitly.
|
|
24
|
+
- `task start` refuses a dirty working tree (override with `--allow-dirty`).
|
|
25
|
+
|
|
26
|
+
## Forbidden
|
|
27
|
+
|
|
28
|
+
- Doing several TASKs on one branch.
|
|
29
|
+
- Omitting the TASK ID from a commit message.
|
|
30
|
+
- Committing directly to shared branches such as `main` / `master` — always go through the TASK branch.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: After implementation, update three documents together.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Docs Update
|
|
7
|
+
|
|
8
|
+
Update the three documents below **together** when the implementation is done. A TASK is not considered complete until all three are updated.
|
|
9
|
+
|
|
10
|
+
1. **`docs/project/05-current-state.md`** — facts only (implementation stage, paths, next TASK).
|
|
11
|
+
2. **`docs/tasks/TASK-NNN-*.md`** — Status, **Result**, **Test Result**.
|
|
12
|
+
3. **`docs/logs/YYYY-MM-DD.md`** — local-date filename; create the file if it does not exist. If multiple updates happen on the same day, add entries to the same file.
|
|
13
|
+
|
|
14
|
+
## Tone
|
|
15
|
+
|
|
16
|
+
- **Facts first**. No marketing copy, no self-praise.
|
|
17
|
+
- Record the changed file paths, the commands you ran with their results, and the decisions you made.
|
|
18
|
+
- A teammate (or your future self in five minutes) should grasp "where things stand" within a minute.
|
|
19
|
+
|
|
20
|
+
## Chat report
|
|
21
|
+
|
|
22
|
+
Follow the "Completion report" section in `AGENTS.md`.
|