@goodtek/vibeops 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +198 -1
  2. package/README.md +62 -402
  3. package/dist/bootstrap/installer.js +4 -16
  4. package/dist/cli.js +52 -244
  5. package/dist/commands/init.js +3 -3
  6. package/dist/commands/status.js +145 -8
  7. package/dist/commands/task-add.js +100 -0
  8. package/dist/commands/task-done.js +117 -74
  9. package/dist/lib/config.js +2 -118
  10. package/dist/lib/git.js +190 -8
  11. package/dist/lib/llm-complete.js +62 -0
  12. package/dist/lib/paths.js +7 -3
  13. package/dist/lib/plan-llm-openai.js +0 -63
  14. package/dist/lib/resolve-task.js +34 -0
  15. package/dist/lib/slug.js +11 -0
  16. package/dist/lib/task-add-llm.js +81 -0
  17. package/dist/lib/task-context.js +68 -0
  18. package/dist/lib/task-done-llm.js +148 -0
  19. package/dist/lib/task-merge.js +115 -0
  20. package/dist/lib/task-scaffold.js +70 -92
  21. package/dist/lib/task-start.js +72 -0
  22. package/dist/lib/task-summary.js +12 -239
  23. package/dist/lib/task.js +80 -37
  24. package/dist/types/config.js +0 -16
  25. package/package.json +3 -4
  26. package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  27. package/templates/.cursor/rules/02-git-safety.mdc +11 -0
  28. package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
  29. package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
  30. package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
  31. package/templates/AGENTS.md +26 -87
  32. package/templates/docs/logs/README.md +4 -35
  33. package/templates/docs/project/03-architecture.md +10 -29
  34. package/templates/docs/project/05-current-state.md +7 -27
  35. package/templates/docs/project/06-decisions.md +6 -17
  36. package/templates/docs/tasks/TASK-000-template.md +9 -44
  37. package/dist/agent/loader.js +0 -71
  38. package/dist/agent/prompt.js +0 -66
  39. package/dist/commands/agent-list.js +0 -32
  40. package/dist/commands/agent-prompt.js +0 -59
  41. package/dist/commands/agent-show.js +0 -26
  42. package/dist/commands/github-init.js +0 -554
  43. package/dist/commands/github-status.js +0 -164
  44. package/dist/commands/notion-init.js +0 -764
  45. package/dist/commands/notion-sync.js +0 -405
  46. package/dist/commands/notion-test.js +0 -595
  47. package/dist/commands/plan.js +0 -343
  48. package/dist/commands/task-check.js +0 -155
  49. package/dist/commands/task-generate.js +0 -206
  50. package/dist/commands/task-pull.js +0 -277
  51. package/dist/commands/task-rollback.js +0 -174
  52. package/dist/commands/task-start.js +0 -102
  53. package/dist/lib/brief.js +0 -356
  54. package/dist/lib/github-cli.js +0 -247
  55. package/dist/lib/notion-client.js +0 -459
  56. package/dist/lib/notion-discovery.js +0 -671
  57. package/dist/lib/notion-env.js +0 -140
  58. package/dist/lib/notion-mappers.js +0 -148
  59. package/dist/lib/notion-schema.js +0 -272
  60. package/dist/lib/notion-sync.js +0 -337
  61. package/dist/lib/notion-target.js +0 -247
  62. package/dist/lib/package-json.js +0 -133
  63. package/dist/lib/plan-apply-planner.js +0 -131
  64. package/dist/lib/plan-codex-pkce.js +0 -234
  65. package/dist/lib/plan-llm-models.js +0 -237
  66. package/dist/lib/plan-llm-normalize.js +0 -146
  67. package/dist/lib/plan-llm-session.js +0 -682
  68. package/dist/lib/plan-post-apply-setup.js +0 -116
  69. package/dist/lib/project-docs.js +0 -95
  70. package/dist/lib/prompt-builder.js +0 -135
  71. package/dist/lib/task-generator.js +0 -183
  72. package/dist/lib/task-prompt.js +0 -23
  73. package/dist/lib/task-pull.js +0 -354
  74. package/dist/status/collector.js +0 -103
  75. package/dist/status/format.js +0 -177
  76. package/dist/types/brief.js +0 -126
  77. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  78. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  79. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  80. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  81. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  82. package/templates/.vibeops/agents/architect.md +0 -47
  83. package/templates/.vibeops/agents/builder.md +0 -38
  84. package/templates/.vibeops/agents/docs.md +0 -54
  85. package/templates/.vibeops/agents/orchestrator.md +0 -40
  86. package/templates/.vibeops/agents/planner.md +0 -60
  87. package/templates/.vibeops/agents/recovery.md +0 -49
  88. package/templates/.vibeops/agents/reviewer.md +0 -47
  89. package/templates/.vibeops/agents/tester.md +0 -43
  90. package/templates/.vibeops/prompts/create-plan.md +0 -33
  91. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  92. package/templates/.vibeops/prompts/implement-task.md +0 -39
  93. package/templates/.vibeops/prompts/review-task.md +0 -34
  94. package/templates/.vibeops/prompts/rollback.md +0 -32
  95. package/templates/.vibeops/prompts/start-project.md +0 -39
  96. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  97. package/templates/.vibeops/workflows/project-start.md +0 -73
  98. package/templates/.vibeops/workflows/rollback.md +0 -45
  99. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  100. package/templates/docs/project/00-overview.md +0 -27
  101. package/templates/docs/project/01-requirements.md +0 -30
  102. package/templates/docs/project/02-mvp-scope.md +0 -36
  103. package/templates/docs/project/04-tech-stack.md +0 -29
  104. package/templates/docs/project/07-backlog.md +0 -23
  105. package/templates/docs/project/08-env.md +0 -29
  106. package/templates/docs/project/09-deployment.md +0 -28
@@ -1,237 +0,0 @@
1
- /**
2
- * Interactive `vibeops plan`: discover model ids from the active provider
3
- * (OpenAI / Codex ChatGPT OAuth / Cursor Agent CLI), then let the user pick.
4
- */
5
- import { execFile } from "node:child_process";
6
- import { promisify } from "node:util";
7
- import { resolveCodexOAuthAccessToken } from "./plan-codex-auth.js";
8
- import { codexResponsesBaseUrl } from "./plan-codex-responses.js";
9
- import { askInput, askSelect } from "./inquirer-helpers.js";
10
- import { dim, log, yellow } from "./logger.js";
11
- const execFileAsync = promisify(execFile);
12
- const DEFAULT_PICK_LABEL = "(Use environment / VibeOps default)";
13
- const MAX_OPENAI_MODELS = 100;
14
- const MAX_CODEX_MODELS = 200;
15
- const MAX_SELECT_CHOICES = 120;
16
- /** Codex `GET /models` requires `client_version` (same query as official Codex CLI). */
17
- const DEFAULT_CODEX_CLIENT_VERSION = "0.130.0";
18
- async function resolveCodexClientVersion() {
19
- const env = process.env.VIBEOPS_CODEX_CLIENT_VERSION?.trim();
20
- if (env)
21
- return env;
22
- try {
23
- const { stdout } = await execFileAsync("codex", ["--version"], {
24
- maxBuffer: 64 * 1024,
25
- env: process.env,
26
- timeout: 4000,
27
- });
28
- const line = stdout
29
- .trim()
30
- .split(/\r?\n/)
31
- .find((l) => l.trim().length > 0);
32
- if (line) {
33
- const m = /(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)/.exec(line);
34
- if (m?.[1])
35
- return m[1];
36
- }
37
- }
38
- catch {
39
- /* codex not on PATH */
40
- }
41
- return DEFAULT_CODEX_CLIENT_VERSION;
42
- }
43
- function agentBin() {
44
- return process.env.VIBEOPS_CURSOR_AGENT_BIN?.trim() || "agent";
45
- }
46
- /** Best-effort parse for Codex `/models` and similar JSON shapes. */
47
- export function extractModelIdsFromUnknownJson(json) {
48
- const ids = [];
49
- const fromArray = (arr) => {
50
- for (const item of arr) {
51
- if (typeof item === "string" && item.trim().length > 0) {
52
- ids.push(item.trim());
53
- continue;
54
- }
55
- if (!item || typeof item !== "object" || Array.isArray(item))
56
- continue;
57
- const o = item;
58
- for (const k of ["id", "slug", "model", "name", "model_id"]) {
59
- const v = o[k];
60
- if (typeof v === "string" && v.trim().length > 0) {
61
- ids.push(v.trim());
62
- break;
63
- }
64
- }
65
- }
66
- };
67
- if (Array.isArray(json)) {
68
- fromArray(json);
69
- }
70
- else if (json && typeof json === "object") {
71
- const o = json;
72
- for (const key of ["data", "models", "items", "results"]) {
73
- const a = o[key];
74
- if (Array.isArray(a))
75
- fromArray(a);
76
- }
77
- }
78
- return [...new Set(ids)].sort((a, b) => a.localeCompare(b));
79
- }
80
- function isReasonableOpenAiChatModel(id) {
81
- const x = id.toLowerCase();
82
- if (!x)
83
- return false;
84
- if (/embedding|embed|whisper|tts|realtime|transcribe|audio-in|audio-out|image|dall-e|moderation|davinci|babbage|ada-|text-search|code-search|omni-moderation/.test(x)) {
85
- return false;
86
- }
87
- return /^gpt-|^o[0-9]|^chatgpt-/.test(x);
88
- }
89
- function rankOpenAiPlanModel(id) {
90
- if (/gpt-5\.[45]/.test(id))
91
- return 1000;
92
- if (/gpt-5/.test(id))
93
- return 900;
94
- if (/gpt-4o/.test(id))
95
- return 800;
96
- if (/gpt-4/.test(id))
97
- return 700;
98
- if (/^o[0-9]/.test(id))
99
- return 650;
100
- if (/gpt-3/.test(id))
101
- return 300;
102
- return 100;
103
- }
104
- export async function fetchOpenAiPlatformModelIds() {
105
- const key = process.env.OPENAI_API_KEY?.trim();
106
- if (!key) {
107
- throw new Error("OPENAI_API_KEY is not set.");
108
- }
109
- const res = await fetch("https://api.openai.com/v1/models", {
110
- headers: { Authorization: `Bearer ${key}` },
111
- });
112
- const text = await res.text();
113
- if (!res.ok) {
114
- throw new Error(`OpenAI models HTTP ${res.status}: ${text.slice(0, 400)}`);
115
- }
116
- const j = JSON.parse(text);
117
- const raw = (j.data ?? []).map((x) => x.id).filter((x) => typeof x === "string");
118
- const filtered = raw.filter(isReasonableOpenAiChatModel);
119
- const sorted = [...new Set(filtered)].sort((a, b) => {
120
- const d = rankOpenAiPlanModel(b) - rankOpenAiPlanModel(a);
121
- return d !== 0 ? d : a.localeCompare(b);
122
- });
123
- return sorted.slice(0, MAX_OPENAI_MODELS);
124
- }
125
- export async function fetchCodexOAuthModelIds() {
126
- const { accessToken } = await resolveCodexOAuthAccessToken();
127
- const clientVersion = await resolveCodexClientVersion();
128
- const base = codexResponsesBaseUrl();
129
- const url = new URL(`${base}/models`);
130
- url.searchParams.set("client_version", clientVersion);
131
- const res = await fetch(url.toString(), {
132
- method: "GET",
133
- headers: {
134
- Authorization: `Bearer ${accessToken}`,
135
- Accept: "application/json",
136
- },
137
- });
138
- const text = await res.text();
139
- if (!res.ok) {
140
- throw new Error(`Codex models HTTP ${res.status}: ${text.slice(0, 400)}`);
141
- }
142
- let j;
143
- try {
144
- j = JSON.parse(text);
145
- }
146
- catch {
147
- throw new Error("Codex models response was not JSON.");
148
- }
149
- const ids = extractModelIdsFromUnknownJson(j);
150
- if (ids.length === 0) {
151
- throw new Error("Codex models response contained no model ids.");
152
- }
153
- return ids.slice(0, MAX_CODEX_MODELS);
154
- }
155
- /** Parses `agent models` / `agent --list-models` human-readable listing. */
156
- export function parseAgentModelsStdout(text) {
157
- const rows = [];
158
- for (const line of text.split("\n")) {
159
- const trimmed = line.trim();
160
- const m = /^([a-zA-Z0-9._-]+)\s+-\s+(.+)$/.exec(trimmed);
161
- if (!m)
162
- continue;
163
- const id = m[1];
164
- const rest = m[2].trim();
165
- if (id === "Available")
166
- continue;
167
- rows.push({ id, label: `${id} — ${rest}` });
168
- }
169
- return rows;
170
- }
171
- export async function fetchCursorAgentModelRows(cwd) {
172
- const command = agentBin();
173
- const { stdout, stderr } = await execFileAsync(command, ["models"], {
174
- cwd,
175
- maxBuffer: 4 * 1024 * 1024,
176
- env: process.env,
177
- });
178
- const rows = parseAgentModelsStdout(`${stdout}\n${stderr}`);
179
- if (rows.length === 0) {
180
- throw new Error(`${command} models produced no parseable lines.`);
181
- }
182
- return rows;
183
- }
184
- async function pickFromLabels(rows, message) {
185
- const cap = MAX_SELECT_CHOICES - 1;
186
- const slice = rows.slice(0, cap);
187
- if (rows.length > cap) {
188
- log.info(dim(`Showing first ${cap} of ${rows.length} models.`));
189
- }
190
- const choices = [...slice.map((r) => r.label), DEFAULT_PICK_LABEL];
191
- const picked = await askSelect({
192
- message,
193
- nonInteractive: false,
194
- choices,
195
- default: slice[0]?.label ?? DEFAULT_PICK_LABEL,
196
- });
197
- if (picked === DEFAULT_PICK_LABEL)
198
- return undefined;
199
- const hit = slice.find((r) => r.label === picked);
200
- return hit?.value;
201
- }
202
- /**
203
- * After the user picks a provider (and auth exists), query the provider for
204
- * model ids and ask which one to use. Returns `undefined` to keep env / built-in defaults.
205
- */
206
- export async function pickPlanExecutionModel(params) {
207
- if (!params.interactive)
208
- return undefined;
209
- log.blank();
210
- log.step("Fetching available models from the provider…");
211
- try {
212
- if (params.provider === "openai") {
213
- const ids = await fetchOpenAiPlatformModelIds();
214
- const rows = ids.map((id) => ({ value: id, label: id }));
215
- return await pickFromLabels(rows, "Choose OpenAI model for planning (↑/↓ · Enter)");
216
- }
217
- if (params.provider === "codex-oauth") {
218
- const ids = await fetchCodexOAuthModelIds();
219
- const rows = ids.map((id) => ({ value: id, label: id }));
220
- return await pickFromLabels(rows, "Choose Codex (ChatGPT) model for planning (↑/↓ · Enter)");
221
- }
222
- const rows = await fetchCursorAgentModelRows(params.cwd);
223
- return await pickFromLabels(rows.map((r) => ({ value: r.id, label: r.label })), "Choose Cursor Agent model for planning (↑/↓ · Enter)");
224
- }
225
- catch (e) {
226
- const msg = e instanceof Error ? e.message : String(e);
227
- log.warn(`${yellow("!")} Model list could not be loaded: ${msg}`);
228
- log.info(dim("Enter a model id manually, or press Enter for the environment / built-in default."));
229
- const typed = await askInput({
230
- message: "Model id (optional)",
231
- nonInteractive: false,
232
- default: "",
233
- });
234
- const t = typed.trim();
235
- return t.length > 0 ? t : undefined;
236
- }
237
- }
@@ -1,146 +0,0 @@
1
- import { AGENT_WORKFLOW_CHOICES, AUTH_REQUIREMENT_CHOICES, BACKEND_CHOICES, DATABASE_CHOICES, DB_LAYER_CHOICES, DEPLOYMENT_TARGET_CHOICES, FRONTEND_CHOICES, INTEGRATION_CHOICES, MVP_FEATURE_CHOICES, OUT_OF_SCOPE_CHOICES, PACKAGE_MANAGER_CHOICES, PROJECT_TYPE_CHOICES, RISK_AREA_CHOICES, TARGET_USER_CHOICES, } from "../types/brief.js";
2
- const PLACEHOLDER_PROJECT_NAME = "Unnamed Project";
3
- const PLACEHOLDER_IDEA = "(no idea provided — Planner Agent must fill this in)";
4
- const PLACEHOLDER_PROBLEM = "(no core problem provided — Planner Agent must fill this in)";
5
- const PLACEHOLDER_SUCCESS = "(no success criteria provided — Planner Agent must fill this in)";
6
- function asStr(v, fallback) {
7
- if (typeof v === "string")
8
- return v.trim();
9
- if (typeof v === "number" || typeof v === "boolean")
10
- return String(v);
11
- return fallback;
12
- }
13
- function asStrArray(v) {
14
- if (!Array.isArray(v))
15
- return [];
16
- const out = [];
17
- for (const x of v) {
18
- if (typeof x === "string" && x.trim().length > 0)
19
- out.push(x.trim());
20
- else if (typeof x === "number" || typeof x === "boolean")
21
- out.push(String(x));
22
- }
23
- return out;
24
- }
25
- function asBool(v, fallback) {
26
- if (typeof v === "boolean")
27
- return v;
28
- if (typeof v === "string") {
29
- const t = v.trim().toLowerCase();
30
- if (t === "true" || t === "yes" || t === "y" || t === "1")
31
- return true;
32
- if (t === "false" || t === "no" || t === "n" || t === "0")
33
- return false;
34
- }
35
- return fallback;
36
- }
37
- function pickNearestOrOriginal(value, choices) {
38
- const t = value.trim();
39
- if (t.length === 0)
40
- return choices[0] ?? "Other";
41
- const hit = choices.find((c) => c.toLowerCase() === t.toLowerCase());
42
- if (hit)
43
- return hit;
44
- return t;
45
- }
46
- function normalizeToChoices(values, choices) {
47
- const out = [];
48
- for (const v of values) {
49
- out.push(pickNearestOrOriginal(v, choices));
50
- }
51
- return Array.from(new Set(out));
52
- }
53
- /**
54
- * Coerces a partial LLM `projectBrief` object into a full `ProjectBrief`,
55
- * snapping enum-like fields to known choice lists when possible.
56
- */
57
- export function normalizeLlmProjectBrief(raw) {
58
- const assumptions = [];
59
- let projectName = asStr(raw.projectName, "");
60
- if (projectName.length === 0) {
61
- projectName = PLACEHOLDER_PROJECT_NAME;
62
- assumptions.push('projectName: missing from LLM output — defaulted to "Unnamed Project"');
63
- }
64
- let oneLineIdea = asStr(raw.oneLineIdea, "");
65
- if (oneLineIdea.length === 0) {
66
- oneLineIdea = PLACEHOLDER_IDEA;
67
- assumptions.push("oneLineIdea: missing — placeholder for Planner Agent");
68
- }
69
- const projectType = pickNearestOrOriginal(asStr(raw.projectType, "SaaS"), PROJECT_TYPE_CHOICES);
70
- let targetUsers = normalizeToChoices(asStrArray(raw.targetUsers), TARGET_USER_CHOICES);
71
- if (targetUsers.length === 0) {
72
- targetUsers = ["Developers"];
73
- assumptions.push("targetUsers: empty — defaulted to Developers");
74
- }
75
- let coreProblem = asStr(raw.coreProblem, "");
76
- if (coreProblem.length === 0) {
77
- coreProblem = PLACEHOLDER_PROBLEM;
78
- assumptions.push("coreProblem: missing — placeholder for Planner Agent");
79
- }
80
- let mvpFeatures = normalizeToChoices(asStrArray(raw.mvpFeatures), MVP_FEATURE_CHOICES);
81
- if (mvpFeatures.length === 0) {
82
- assumptions.push("mvpFeatures: empty — Planner Agent should expand from conversation");
83
- }
84
- let outOfScope = normalizeToChoices(asStrArray(raw.outOfScope), OUT_OF_SCOPE_CHOICES);
85
- const frontend = pickNearestOrOriginal(asStr(raw.frontend, "Not sure"), FRONTEND_CHOICES);
86
- const backend = pickNearestOrOriginal(asStr(raw.backend, "Not sure"), BACKEND_CHOICES);
87
- const database = pickNearestOrOriginal(asStr(raw.database, "Not sure"), DATABASE_CHOICES);
88
- const dbLayer = pickNearestOrOriginal(asStr(raw.dbLayer, "Not sure"), DB_LAYER_CHOICES);
89
- const packageManager = pickNearestOrOriginal(asStr(raw.packageManager, "pnpm"), PACKAGE_MANAGER_CHOICES);
90
- let deploymentTargets = normalizeToChoices(asStrArray(raw.deploymentTargets), DEPLOYMENT_TARGET_CHOICES);
91
- if (deploymentTargets.length === 0) {
92
- deploymentTargets = ["Not sure"];
93
- assumptions.push("deploymentTargets: empty — defaulted to Not sure");
94
- }
95
- let authRequirements = normalizeToChoices(asStrArray(raw.authRequirements), AUTH_REQUIREMENT_CHOICES);
96
- if (authRequirements.length === 0) {
97
- authRequirements = ["Not sure"];
98
- }
99
- let integrations = normalizeToChoices(asStrArray(raw.integrations), INTEGRATION_CHOICES);
100
- if (integrations.length === 0) {
101
- integrations = ["None"];
102
- }
103
- const useNotion = asBool(raw.useNotion, true);
104
- const useGitWorkflow = asBool(raw.useGitWorkflow, true);
105
- let agentWorkflowLevel = asStr(raw.agentWorkflowLevel, "");
106
- if (!AGENT_WORKFLOW_CHOICES.some((c) => c === agentWorkflowLevel)) {
107
- agentWorkflowLevel = pickNearestOrOriginal(agentWorkflowLevel, AGENT_WORKFLOW_CHOICES);
108
- }
109
- if (!AGENT_WORKFLOW_CHOICES.some((c) => c === agentWorkflowLevel)) {
110
- agentWorkflowLevel = AGENT_WORKFLOW_CHOICES[2];
111
- assumptions.push("agentWorkflowLevel: LLM value unknown — defaulted to Advanced lineup");
112
- }
113
- let risks = normalizeToChoices(asStrArray(raw.risks), RISK_AREA_CHOICES);
114
- if (risks.length === 0) {
115
- risks = ["Other"];
116
- assumptions.push("risks: empty — placeholder");
117
- }
118
- let successCriteria = asStr(raw.successCriteria, "");
119
- if (successCriteria.length === 0) {
120
- successCriteria = PLACEHOLDER_SUCCESS;
121
- assumptions.push("successCriteria: missing — placeholder for Planner Agent");
122
- }
123
- const brief = {
124
- projectName,
125
- oneLineIdea,
126
- projectType,
127
- targetUsers,
128
- coreProblem,
129
- mvpFeatures,
130
- outOfScope,
131
- frontend,
132
- backend,
133
- database,
134
- dbLayer,
135
- packageManager,
136
- deploymentTargets,
137
- authRequirements,
138
- integrations,
139
- useNotion,
140
- useGitWorkflow,
141
- agentWorkflowLevel,
142
- risks,
143
- successCriteria,
144
- };
145
- return { brief, assumptions };
146
- }