@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +62 -382
  3. package/dist/bootstrap/installer.js +4 -16
  4. package/dist/cli.js +26 -160
  5. package/dist/commands/init.js +3 -3
  6. package/dist/commands/status.js +145 -8
  7. package/dist/commands/task-add.js +64 -164
  8. package/dist/commands/task-done.js +117 -74
  9. package/dist/lib/config.js +2 -118
  10. package/dist/lib/git.js +1 -1
  11. package/dist/lib/paths.js +7 -3
  12. package/dist/lib/plan-llm-openai.js +0 -63
  13. package/dist/lib/resolve-task.js +34 -0
  14. package/dist/lib/task-add-llm.js +46 -233
  15. package/dist/lib/task-context.js +68 -0
  16. package/dist/lib/task-done-llm.js +148 -0
  17. package/dist/lib/task-merge.js +54 -159
  18. package/dist/lib/task-scaffold.js +39 -43
  19. package/dist/lib/task-start.js +72 -0
  20. package/dist/lib/task-summary.js +12 -239
  21. package/dist/lib/task.js +15 -24
  22. package/dist/types/config.js +0 -16
  23. package/package.json +3 -5
  24. package/templates/.cursor/rules/01-task-source-of-truth.mdc +12 -0
  25. package/templates/.cursor/rules/02-git-safety.mdc +11 -0
  26. package/templates/.cursor/rules/03-docs-on-done.mdc +13 -0
  27. package/templates/.cursor/skills/implement-task/SKILL.md +22 -0
  28. package/templates/.cursor/skills/plan-task/SKILL.md +19 -0
  29. package/templates/AGENTS.md +26 -87
  30. package/templates/docs/logs/README.md +4 -35
  31. package/templates/docs/project/03-architecture.md +10 -29
  32. package/templates/docs/project/05-current-state.md +7 -27
  33. package/templates/docs/project/06-decisions.md +6 -17
  34. package/templates/docs/tasks/TASK-000-template.md +9 -44
  35. package/dist/agent/loader.js +0 -71
  36. package/dist/agent/prompt.js +0 -66
  37. package/dist/commands/agent-list.js +0 -32
  38. package/dist/commands/agent-prompt.js +0 -59
  39. package/dist/commands/agent-show.js +0 -26
  40. package/dist/commands/done.js +0 -160
  41. package/dist/commands/github-init.js +0 -554
  42. package/dist/commands/github-status.js +0 -164
  43. package/dist/commands/next.js +0 -283
  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 -146
  48. package/dist/commands/rollback.js +0 -126
  49. package/dist/commands/start.js +0 -129
  50. package/dist/commands/task-check.js +0 -155
  51. package/dist/commands/task-generate.js +0 -206
  52. package/dist/commands/task-pull.js +0 -277
  53. package/dist/commands/task-rollback.js +0 -174
  54. package/dist/commands/task-start.js +0 -102
  55. package/dist/commands/task-status.js +0 -122
  56. package/dist/lib/brief.js +0 -356
  57. package/dist/lib/cursor-task-prompt.js +0 -60
  58. package/dist/lib/done-follow-up.js +0 -308
  59. package/dist/lib/github-cli.js +0 -247
  60. package/dist/lib/guide-state.js +0 -69
  61. package/dist/lib/mvp-artifacts.js +0 -204
  62. package/dist/lib/mvp-constants.js +0 -15
  63. package/dist/lib/mvp-diff-summary.js +0 -43
  64. package/dist/lib/mvp-done-summary.js +0 -335
  65. package/dist/lib/notion-client.js +0 -459
  66. package/dist/lib/notion-discovery.js +0 -671
  67. package/dist/lib/notion-env.js +0 -140
  68. package/dist/lib/notion-mappers.js +0 -148
  69. package/dist/lib/notion-schema.js +0 -272
  70. package/dist/lib/notion-sync.js +0 -337
  71. package/dist/lib/notion-target.js +0 -247
  72. package/dist/lib/package-json.js +0 -133
  73. package/dist/lib/plan-apply-planner.js +0 -131
  74. package/dist/lib/plan-codex-pkce.js +0 -234
  75. package/dist/lib/plan-llm-models.js +0 -237
  76. package/dist/lib/plan-llm-normalize.js +0 -146
  77. package/dist/lib/plan-llm-session.js +0 -682
  78. package/dist/lib/plan-post-apply-setup.js +0 -116
  79. package/dist/lib/project-docs.js +0 -95
  80. package/dist/lib/prompt-builder.js +0 -135
  81. package/dist/lib/resolve-command-task.js +0 -40
  82. package/dist/lib/task-add-build-prompt.js +0 -39
  83. package/dist/lib/task-add-cursor.js +0 -81
  84. package/dist/lib/task-generator.js +0 -183
  85. package/dist/lib/task-prompt.js +0 -23
  86. package/dist/lib/task-pull.js +0 -354
  87. package/dist/lib/task-status-brief.js +0 -132
  88. package/dist/lib/workflow-guide.js +0 -485
  89. package/dist/status/collector.js +0 -103
  90. package/dist/status/format.js +0 -181
  91. package/dist/types/brief.js +0 -126
  92. package/templates/.cursor/rules/00-project-governance.mdc +0 -28
  93. package/templates/.cursor/rules/01-agent-orchestration.mdc +0 -48
  94. package/templates/.cursor/rules/02-task-workflow.mdc +0 -38
  95. package/templates/.cursor/rules/03-git-safety.mdc +0 -30
  96. package/templates/.cursor/rules/04-docs-update.mdc +0 -22
  97. package/templates/.vibeops/agents/architect.md +0 -47
  98. package/templates/.vibeops/agents/builder.md +0 -38
  99. package/templates/.vibeops/agents/docs.md +0 -54
  100. package/templates/.vibeops/agents/orchestrator.md +0 -40
  101. package/templates/.vibeops/agents/planner.md +0 -60
  102. package/templates/.vibeops/agents/recovery.md +0 -49
  103. package/templates/.vibeops/agents/reviewer.md +0 -47
  104. package/templates/.vibeops/agents/tester.md +0 -43
  105. package/templates/.vibeops/prompts/create-plan.md +0 -33
  106. package/templates/.vibeops/prompts/generate-tasks.md +0 -41
  107. package/templates/.vibeops/prompts/implement-task.md +0 -39
  108. package/templates/.vibeops/prompts/review-task.md +0 -34
  109. package/templates/.vibeops/prompts/rollback.md +0 -32
  110. package/templates/.vibeops/prompts/start-project.md +0 -39
  111. package/templates/.vibeops/workflows/notion-sync.md +0 -53
  112. package/templates/.vibeops/workflows/project-start.md +0 -73
  113. package/templates/.vibeops/workflows/rollback.md +0 -45
  114. package/templates/.vibeops/workflows/task-lifecycle.md +0 -71
  115. package/templates/docs/project/00-overview.md +0 -27
  116. package/templates/docs/project/01-requirements.md +0 -30
  117. package/templates/docs/project/02-mvp-scope.md +0 -36
  118. package/templates/docs/project/04-tech-stack.md +0 -29
  119. package/templates/docs/project/07-backlog.md +0 -23
  120. package/templates/docs/project/08-env.md +0 -29
  121. 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
- }