vibebusiness 1.2.49 → 1.2.53
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/.next/standalone/.env +1 -1
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +20 -20
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +4 -4
- package/.next/standalone/.next/prerender-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/api/analyze/route.js +1 -1
- package/.next/standalone/.next/server/app/api/analyze/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/epics/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/kpis/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/goals/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/hypotheses/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/comments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/implement/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/[id]/transition/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ideas/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/implementations/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js +1 -1
- package/.next/standalone/.next/server/app/api/kpis/refresh/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/provider-status/route.js +1 -0
- package/.next/standalone/.next/server/app/api/provider-status/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/[id]/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/goals/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/goals/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/hypotheses/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page.js +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/ideas/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/roadmap/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +12 -11
- package/.next/standalone/.next/server/chunks/3794.js +92 -17
- package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/data/business-context.json +60 -6
- package/.next/standalone/data/goals.json +50 -10
- package/.next/standalone/data/ideas.json +1278 -597
- package/.next/standalone/data/implementations.json +101 -0
- package/.next/standalone/data/posthog.json +68 -0
- package/.next/standalone/data/sessions.json +5 -4
- package/.next/standalone/data/social.json +86 -3
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/analyze.ts +10 -0
- package/.next/standalone/scripts/implement.ts +37 -30
- package/.next/standalone/scripts/skills/social-media.ts +172 -2
- package/.next/static/chunks/59-76eecfb6e8216043.js +1 -0
- package/.next/static/chunks/app/goals/[id]/page-051064c3146131cc.js +1 -0
- package/.next/static/chunks/app/ideas/[id]/page-adaeb619cd0425e9.js +1 -0
- package/.next/static/chunks/app/roadmap/[id]/page-3822586c0d64fff1.js +1 -0
- package/.next/static/chunks/app/settings/page-2204cc936ec9474b.js +1 -0
- package/.next/static/chunks/app/social/page-018893f87b308651.js +1 -0
- package/.next/static/chunks/{main-61d2f25883998186.js → main-c99f3473a63aa803.js} +1 -1
- package/.next/static/css/d8bd6d69d1ff97e3.css +3 -0
- package/dist/bin/vibebusiness.js +12 -0
- package/dist/scripts/analyze.js +170 -68
- package/dist/scripts/chat.js +137 -36
- package/dist/scripts/heartbeat.js +617 -348
- package/dist/scripts/implement.js +218 -70
- package/dist/scripts/init.js +164 -16
- package/dist/scripts/provider.js +428 -0
- package/dist/scripts/scan.js +44 -5
- package/package.json +1 -1
- package/.next/static/chunks/59-a053b1c0e85128de.js +0 -1
- package/.next/static/chunks/app/goals/[id]/page-8dbeab5cc8cf0988.js +0 -1
- package/.next/static/chunks/app/ideas/[id]/page-89e3625db9017166.js +0 -1
- package/.next/static/chunks/app/roadmap/[id]/page-f437e783039534c4.js +0 -1
- package/.next/static/chunks/app/settings/page-d2d630a799b6b495.js +0 -1
- package/.next/static/chunks/app/social/page-69e480936711ccf2.js +0 -1
- package/.next/static/css/179dd3e0738c2fe4.css +0 -3
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → p1Sl4kPMQcYC3c1LgbNSP}/_buildManifest.js +0 -0
- /package/.next/static/{zIIaTqrawBK1MmHg37JOr → p1Sl4kPMQcYC3c1LgbNSP}/_ssgManifest.js +0 -0
|
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// scripts/implement.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
27
|
+
var import_child_process3 = require("child_process");
|
|
28
|
+
var fs5 = __toESM(require("fs"));
|
|
29
|
+
var path4 = __toESM(require("path"));
|
|
30
30
|
|
|
31
31
|
// scripts/lib/git-utils.ts
|
|
32
32
|
var import_child_process = require("child_process");
|
|
@@ -82,6 +82,20 @@ function cleanGitState(workspacePath) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
function execGit(args, cwd, timeoutMs) {
|
|
86
|
+
try {
|
|
87
|
+
return (0, import_child_process.execFileSync)("git", args, {
|
|
88
|
+
cwd,
|
|
89
|
+
encoding: "utf-8",
|
|
90
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
91
|
+
...timeoutMs ? { timeout: timeoutMs } : {}
|
|
92
|
+
}).trim();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const execError = error;
|
|
95
|
+
const stderr = typeof execError.stderr === "string" ? execError.stderr : execError.stderr?.toString() ?? "";
|
|
96
|
+
throw new Error(`git ${args[0]} failed: ${stderr || execError.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
85
99
|
function exec(cmd, cwd, timeoutMs) {
|
|
86
100
|
try {
|
|
87
101
|
return (0, import_child_process.execSync)(cmd, {
|
|
@@ -97,6 +111,41 @@ ${execError.stderr || execError.message}`);
|
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
113
|
|
|
114
|
+
// scripts/lib/shell-safe.ts
|
|
115
|
+
var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
116
|
+
function truncate(s, max = 100) {
|
|
117
|
+
return s.length > max ? s.slice(0, max) + "..." : s;
|
|
118
|
+
}
|
|
119
|
+
var _rejectionCounts = { branch: 0, repo: 0, commitMsg: 0 };
|
|
120
|
+
function recordRejection(type) {
|
|
121
|
+
_rejectionCounts[type]++;
|
|
122
|
+
console.warn(`[shell-safe] Rejection recorded: type=${type} total=${_rejectionCounts[type]}`);
|
|
123
|
+
}
|
|
124
|
+
function validateBranchName(name) {
|
|
125
|
+
if (!name || name.trim() === "") {
|
|
126
|
+
console.warn("[shell-safe] validateBranchName: rejecting empty branch name");
|
|
127
|
+
recordRejection("branch");
|
|
128
|
+
throw new Error("Branch name cannot be empty");
|
|
129
|
+
}
|
|
130
|
+
if (!SAFE_BRANCH_RE.test(name)) {
|
|
131
|
+
console.warn(`[shell-safe] Rejected branch name: ${truncate(name)}`);
|
|
132
|
+
recordRejection("branch");
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Invalid branch name: "${truncate(name)}" \u2014 only alphanumerics, dots, dashes, underscores, and slashes are allowed`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return name;
|
|
138
|
+
}
|
|
139
|
+
function sanitizeForCommitMessage(msg) {
|
|
140
|
+
const original = msg;
|
|
141
|
+
let sanitized = msg.replace(/"/g, "\u201C\u201D"[0]).replace(/`/g, "\u2018").replace(/\$\(/g, "(").replace(/\$/g, "").replace(/\\/g, "/").replace(/!/g, "").replace(/[\n\r]/g, " ").trim();
|
|
142
|
+
if (sanitized !== original) {
|
|
143
|
+
console.warn(`[shell-safe] sanitizeForCommitMessage modified input: ${truncate(original)}`);
|
|
144
|
+
recordRejection("commitMsg");
|
|
145
|
+
}
|
|
146
|
+
return sanitized;
|
|
147
|
+
}
|
|
148
|
+
|
|
100
149
|
// scripts/lib/json-lock.ts
|
|
101
150
|
var fs2 = __toESM(require("fs"));
|
|
102
151
|
function atomicWriteFileSync(filePath, content) {
|
|
@@ -105,21 +154,116 @@ function atomicWriteFileSync(filePath, content) {
|
|
|
105
154
|
fs2.renameSync(tmpPath, filePath);
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
// scripts/lib/
|
|
109
|
-
var
|
|
157
|
+
// scripts/lib/ai-provider.ts
|
|
158
|
+
var import_child_process2 = require("child_process");
|
|
110
159
|
var fs3 = __toESM(require("fs"));
|
|
160
|
+
var path2 = __toESM(require("path"));
|
|
161
|
+
var CREDENTIALS_DIR = path2.join(process.env.HOME || "", ".vibebusiness");
|
|
162
|
+
var CREDENTIALS_FILE = path2.join(CREDENTIALS_DIR, "credentials.json");
|
|
163
|
+
function loadCredentials() {
|
|
164
|
+
try {
|
|
165
|
+
if (fs3.existsSync(CREDENTIALS_FILE)) {
|
|
166
|
+
return JSON.parse(fs3.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
function detectClaudeCLI(customPath) {
|
|
173
|
+
const claudeBin = customPath || "claude";
|
|
174
|
+
try {
|
|
175
|
+
const version = (0, import_child_process2.execSync)(`${claudeBin} --version 2>/dev/null`, {
|
|
176
|
+
timeout: 1e4,
|
|
177
|
+
encoding: "utf-8"
|
|
178
|
+
}).trim();
|
|
179
|
+
return { found: true, path: claudeBin, version };
|
|
180
|
+
} catch {
|
|
181
|
+
const commonPaths = [
|
|
182
|
+
"/usr/local/bin/claude",
|
|
183
|
+
path2.join(process.env.HOME || "", ".claude", "bin", "claude"),
|
|
184
|
+
path2.join(process.env.HOME || "", ".local", "bin", "claude")
|
|
185
|
+
];
|
|
186
|
+
for (const p of commonPaths) {
|
|
187
|
+
try {
|
|
188
|
+
if (fs3.existsSync(p)) {
|
|
189
|
+
const version = (0, import_child_process2.execSync)(`${p} --version 2>/dev/null`, {
|
|
190
|
+
timeout: 1e4,
|
|
191
|
+
encoding: "utf-8"
|
|
192
|
+
}).trim();
|
|
193
|
+
return { found: true, path: p, version };
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { found: false, path: claudeBin };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function detectBYOKKeys() {
|
|
203
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
204
|
+
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
|
|
205
|
+
}
|
|
206
|
+
if (process.env.OPENAI_API_KEY) {
|
|
207
|
+
return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
|
|
208
|
+
}
|
|
209
|
+
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
210
|
+
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
|
|
211
|
+
}
|
|
212
|
+
const creds = loadCredentials();
|
|
213
|
+
if (creds.ANTHROPIC_API_KEY) {
|
|
214
|
+
process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
|
|
215
|
+
return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
|
|
216
|
+
}
|
|
217
|
+
if (creds.OPENAI_API_KEY) {
|
|
218
|
+
process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
|
|
219
|
+
return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
|
|
220
|
+
}
|
|
221
|
+
if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
|
|
222
|
+
const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
|
|
223
|
+
process.env.GOOGLE_API_KEY = key;
|
|
224
|
+
return { provider: "google-api", key, source: "credentials" };
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
var CONFIG_DIR = path2.join(process.env.HOME || "", ".ai-analyst");
|
|
229
|
+
var PROVIDER_CONFIG_FILE = path2.join(CONFIG_DIR, "provider.json");
|
|
230
|
+
function requireClaudeCLI(feature) {
|
|
231
|
+
const cli = detectClaudeCLI();
|
|
232
|
+
if (cli.found) {
|
|
233
|
+
return cli.path;
|
|
234
|
+
}
|
|
235
|
+
const featureLabel = feature ? ` (${feature})` : "";
|
|
236
|
+
const byok = detectBYOKKeys();
|
|
237
|
+
if (byok) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`This feature${featureLabel} requires Claude Code CLI for file and tool access.
|
|
240
|
+
Your API key is detected and works for reasoning tasks (heartbeat, idea evaluation),
|
|
241
|
+
but codebase analysis and implementation need the full CLI.
|
|
242
|
+
Install it with: npm install -g @anthropic-ai/claude-code`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
throw new Error(
|
|
246
|
+
`No AI provider found${featureLabel}.
|
|
247
|
+
Install Claude Code CLI: npm install -g @anthropic-ai/claude-code
|
|
248
|
+
Or set an API key: export ANTHROPIC_API_KEY=sk-ant-...`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// scripts/lib/paths.ts
|
|
253
|
+
var path3 = __toESM(require("path"));
|
|
254
|
+
var fs4 = __toESM(require("fs"));
|
|
111
255
|
function findProjectRoot() {
|
|
112
256
|
const cwd = process.cwd();
|
|
113
|
-
if (
|
|
257
|
+
if (fs4.existsSync(path3.join(cwd, "data", "config.json"))) {
|
|
114
258
|
return cwd;
|
|
115
259
|
}
|
|
116
260
|
try {
|
|
117
|
-
const entries =
|
|
261
|
+
const entries = fs4.readdirSync(cwd, { withFileTypes: true });
|
|
118
262
|
for (const entry of entries) {
|
|
119
263
|
if (entry.isDirectory()) {
|
|
120
|
-
const candidate =
|
|
121
|
-
if (
|
|
122
|
-
return
|
|
264
|
+
const candidate = path3.join(cwd, entry.name, "data", "config.json");
|
|
265
|
+
if (fs4.existsSync(candidate)) {
|
|
266
|
+
return path3.join(cwd, entry.name);
|
|
123
267
|
}
|
|
124
268
|
}
|
|
125
269
|
}
|
|
@@ -127,9 +271,9 @@ function findProjectRoot() {
|
|
|
127
271
|
}
|
|
128
272
|
let dir = cwd;
|
|
129
273
|
for (let i = 0; i < 5; i++) {
|
|
130
|
-
const parent =
|
|
274
|
+
const parent = path3.dirname(dir);
|
|
131
275
|
if (parent === dir) break;
|
|
132
|
-
if (
|
|
276
|
+
if (fs4.existsSync(path3.join(parent, "data", "config.json"))) {
|
|
133
277
|
return parent;
|
|
134
278
|
}
|
|
135
279
|
dir = parent;
|
|
@@ -137,26 +281,26 @@ function findProjectRoot() {
|
|
|
137
281
|
return cwd;
|
|
138
282
|
}
|
|
139
283
|
var PROJECT_DIR = findProjectRoot();
|
|
140
|
-
var DATA_DIR =
|
|
141
|
-
var STATUS_FILE =
|
|
142
|
-
var TODO_FILE =
|
|
143
|
-
var MEMORY_FILE =
|
|
144
|
-
var IDEAS_FILE =
|
|
145
|
-
var GOALS_FILE =
|
|
146
|
-
var SESSIONS_FILE =
|
|
147
|
-
var CONFIG_FILE =
|
|
148
|
-
var BUSINESS_CONTEXT_FILE =
|
|
149
|
-
var HYPOTHESES_FILE =
|
|
150
|
-
var IMPLEMENTATIONS_FILE =
|
|
151
|
-
var ROADMAP_FILE =
|
|
152
|
-
var COMPETITORS_FILE =
|
|
153
|
-
var POSITIONING_FILE =
|
|
154
|
-
var PAGES_FILE =
|
|
155
|
-
var PAYMENTS_FILE =
|
|
156
|
-
var POSTHOG_FILE =
|
|
157
|
-
var SOCIAL_FILE =
|
|
158
|
-
var TEMPLATES_DIR =
|
|
159
|
-
var COMMAND_TEMPLATES_DIR =
|
|
284
|
+
var DATA_DIR = path3.join(PROJECT_DIR, "data");
|
|
285
|
+
var STATUS_FILE = path3.join(PROJECT_DIR, "STATUS.md");
|
|
286
|
+
var TODO_FILE = path3.join(PROJECT_DIR, "TODO.md");
|
|
287
|
+
var MEMORY_FILE = path3.join(PROJECT_DIR, "MEMORY.md");
|
|
288
|
+
var IDEAS_FILE = path3.join(DATA_DIR, "ideas.json");
|
|
289
|
+
var GOALS_FILE = path3.join(DATA_DIR, "goals.json");
|
|
290
|
+
var SESSIONS_FILE = path3.join(DATA_DIR, "sessions.json");
|
|
291
|
+
var CONFIG_FILE = path3.join(DATA_DIR, "config.json");
|
|
292
|
+
var BUSINESS_CONTEXT_FILE = path3.join(DATA_DIR, "business-context.json");
|
|
293
|
+
var HYPOTHESES_FILE = path3.join(DATA_DIR, "hypotheses.json");
|
|
294
|
+
var IMPLEMENTATIONS_FILE = path3.join(DATA_DIR, "implementations.json");
|
|
295
|
+
var ROADMAP_FILE = path3.join(DATA_DIR, "roadmap.json");
|
|
296
|
+
var COMPETITORS_FILE = path3.join(DATA_DIR, "competitors.json");
|
|
297
|
+
var POSITIONING_FILE = path3.join(DATA_DIR, "positioning.json");
|
|
298
|
+
var PAGES_FILE = path3.join(DATA_DIR, "pages.json");
|
|
299
|
+
var PAYMENTS_FILE = path3.join(DATA_DIR, "payments.json");
|
|
300
|
+
var POSTHOG_FILE = path3.join(DATA_DIR, "posthog.json");
|
|
301
|
+
var SOCIAL_FILE = path3.join(DATA_DIR, "social.json");
|
|
302
|
+
var TEMPLATES_DIR = path3.join(__dirname, "..", "..", "templates");
|
|
303
|
+
var COMMAND_TEMPLATES_DIR = path3.join(TEMPLATES_DIR, "commands");
|
|
160
304
|
|
|
161
305
|
// scripts/implement.ts
|
|
162
306
|
function parseArgs() {
|
|
@@ -202,7 +346,7 @@ function parseArgs() {
|
|
|
202
346
|
return { ideaId, repoName, scope, skipPr, createPrOnly, timeout, model, workspacePath };
|
|
203
347
|
}
|
|
204
348
|
function loadJson(filePath) {
|
|
205
|
-
return JSON.parse(
|
|
349
|
+
return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
|
|
206
350
|
}
|
|
207
351
|
function saveJson(filePath, data) {
|
|
208
352
|
atomicWriteFileSync(filePath, JSON.stringify(data, null, 2));
|
|
@@ -266,7 +410,7 @@ function detectTargetRepo(idea, config) {
|
|
|
266
410
|
const filesAnalyzed = idea.source.files_analyzed || [];
|
|
267
411
|
for (const file of filesAnalyzed) {
|
|
268
412
|
for (const repo of config.repos) {
|
|
269
|
-
if (file.includes(repo.name) || file.includes(
|
|
413
|
+
if (file.includes(repo.name) || file.includes(path4.basename(repo.path))) {
|
|
270
414
|
return repo;
|
|
271
415
|
}
|
|
272
416
|
}
|
|
@@ -280,47 +424,48 @@ function detectTargetRepo(idea, config) {
|
|
|
280
424
|
return config.repos[0];
|
|
281
425
|
}
|
|
282
426
|
function setupWorkspace(repo, workspaceDir, implId) {
|
|
283
|
-
const repoWorkspace =
|
|
284
|
-
if (!
|
|
427
|
+
const repoWorkspace = path4.join(workspaceDir, repo.name);
|
|
428
|
+
if (!fs5.existsSync(workspaceDir)) {
|
|
285
429
|
addLog(implId, `Creating workspace directory: ${workspaceDir}`);
|
|
286
|
-
|
|
430
|
+
fs5.mkdirSync(workspaceDir, { recursive: true });
|
|
287
431
|
}
|
|
288
|
-
if (
|
|
432
|
+
if (fs5.existsSync(repoWorkspace)) {
|
|
289
433
|
cleanGitState(repoWorkspace);
|
|
290
434
|
if (repo.github_url) {
|
|
291
435
|
try {
|
|
292
436
|
const currentOrigin = exec("git remote get-url origin", repoWorkspace);
|
|
293
437
|
if (!currentOrigin.includes("github.com")) {
|
|
294
438
|
addLog(implId, `Repointing origin from local path to ${repo.github_url}`);
|
|
295
|
-
|
|
439
|
+
execGit(["remote", "set-url", "origin", repo.github_url], repoWorkspace);
|
|
296
440
|
}
|
|
297
441
|
} catch {
|
|
298
|
-
|
|
442
|
+
execGit(["remote", "add", "origin", repo.github_url], repoWorkspace);
|
|
299
443
|
}
|
|
300
444
|
}
|
|
301
445
|
addLog(implId, `Updating existing clone at ${repoWorkspace}`);
|
|
302
|
-
exec(
|
|
303
|
-
|
|
304
|
-
|
|
446
|
+
exec("git fetch origin", repoWorkspace);
|
|
447
|
+
execGit(["checkout", repo.default_branch || "main"], repoWorkspace);
|
|
448
|
+
execGit(["pull", "origin", repo.default_branch || "main"], repoWorkspace);
|
|
305
449
|
} else {
|
|
306
450
|
const cloneSource = repo.github_url || repo.path;
|
|
307
451
|
if (!cloneSource) {
|
|
308
452
|
throw new Error(`No github_url or path configured for repo: ${repo.name}`);
|
|
309
453
|
}
|
|
310
454
|
addLog(implId, `Cloning ${cloneSource} to ${repoWorkspace}`);
|
|
311
|
-
|
|
455
|
+
execGit(["clone", cloneSource, repoWorkspace]);
|
|
312
456
|
}
|
|
313
457
|
return repoWorkspace;
|
|
314
458
|
}
|
|
315
459
|
function createBranch(workspacePath, branchName, defaultBranch, implId) {
|
|
460
|
+
validateBranchName(branchName);
|
|
316
461
|
addLog(implId, `Creating branch: ${branchName}`);
|
|
317
|
-
|
|
462
|
+
execGit(["checkout", defaultBranch], workspacePath);
|
|
318
463
|
try {
|
|
319
|
-
|
|
464
|
+
execGit(["rev-parse", "--verify", branchName], workspacePath);
|
|
320
465
|
addLog(implId, `Branch ${branchName} already exists, checking out`);
|
|
321
|
-
|
|
466
|
+
execGit(["checkout", branchName], workspacePath);
|
|
322
467
|
} catch {
|
|
323
|
-
|
|
468
|
+
execGit(["checkout", "-b", branchName], workspacePath);
|
|
324
469
|
}
|
|
325
470
|
}
|
|
326
471
|
function generatePrompt(idea, scopeBase64) {
|
|
@@ -413,6 +558,7 @@ IMPORTANT:
|
|
|
413
558
|
`;
|
|
414
559
|
}
|
|
415
560
|
async function runImplementation(workspacePath, prompt, implId, timeoutMs = 3e5, options = {}) {
|
|
561
|
+
requireClaudeCLI("implementation");
|
|
416
562
|
const modelLabel = options.model ? ` (model: ${options.model})` : "";
|
|
417
563
|
addLog(implId, `Running Claude Code to implement changes (timeout: ${timeoutMs / 1e3}s${modelLabel})...`);
|
|
418
564
|
const claudeArgs = [
|
|
@@ -427,7 +573,7 @@ async function runImplementation(workspacePath, prompt, implId, timeoutMs = 3e5,
|
|
|
427
573
|
}
|
|
428
574
|
claudeArgs.push("-p", prompt);
|
|
429
575
|
return new Promise((resolve) => {
|
|
430
|
-
const claude = (0,
|
|
576
|
+
const claude = (0, import_child_process3.spawn)("claude", claudeArgs, {
|
|
431
577
|
cwd: workspacePath,
|
|
432
578
|
env: { ...process.env },
|
|
433
579
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -502,7 +648,7 @@ function hasChanges(workspacePath) {
|
|
|
502
648
|
}
|
|
503
649
|
function createPR(workspacePath, branchName, defaultBranch, idea, implId) {
|
|
504
650
|
try {
|
|
505
|
-
const ahead =
|
|
651
|
+
const ahead = execGit(["rev-list", "--count", `${defaultBranch}..${branchName}`], workspacePath);
|
|
506
652
|
if (parseInt(ahead, 10) === 0) {
|
|
507
653
|
addLog(implId, "No commits to push, skipping PR creation");
|
|
508
654
|
return null;
|
|
@@ -511,7 +657,7 @@ function createPR(workspacePath, branchName, defaultBranch, idea, implId) {
|
|
|
511
657
|
addLog(implId, "Could not determine commit count, attempting PR anyway");
|
|
512
658
|
}
|
|
513
659
|
addLog(implId, `Pushing branch ${branchName} to origin`);
|
|
514
|
-
|
|
660
|
+
execGit(["push", "-u", "origin", branchName], workspacePath, 3e4);
|
|
515
661
|
addLog(implId, "Creating PR with gh CLI");
|
|
516
662
|
const title = `[AI] ${idea.title}`;
|
|
517
663
|
const body = `## Summary
|
|
@@ -532,11 +678,11 @@ This PR was automatically generated by the AI Product Manager.
|
|
|
532
678
|
Idea ID: \`${idea.id}\`
|
|
533
679
|
`;
|
|
534
680
|
try {
|
|
535
|
-
const prOutput =
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
3e4
|
|
539
|
-
);
|
|
681
|
+
const prOutput = (0, import_child_process3.execFileSync)(
|
|
682
|
+
"gh",
|
|
683
|
+
["pr", "create", "--title", title, "--body", body, "--base", defaultBranch],
|
|
684
|
+
{ cwd: workspacePath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 3e4 }
|
|
685
|
+
).trim();
|
|
540
686
|
const urlMatch = prOutput.match(/https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
|
|
541
687
|
if (urlMatch) {
|
|
542
688
|
return {
|
|
@@ -593,18 +739,18 @@ async function main() {
|
|
|
593
739
|
const existingImpl = implData.implementations.find(
|
|
594
740
|
(i) => i.idea_id === ideaId && i.status !== "failed" && i.status !== "completed"
|
|
595
741
|
);
|
|
596
|
-
const workspacePath = workspacePathOverride ||
|
|
742
|
+
const workspacePath = workspacePathOverride || path4.join(config.workspace_dir, targetRepo.name);
|
|
597
743
|
const isWorktreeMode = !!workspacePathOverride;
|
|
598
744
|
const impl = existingImpl || createImplementation(ideaId, targetRepo.name, branchName, workspacePath);
|
|
599
745
|
try {
|
|
600
746
|
cleanGitState(workspacePath);
|
|
601
747
|
if (isWorktreeMode && scope) {
|
|
602
|
-
if (!
|
|
748
|
+
if (!fs5.existsSync(workspacePath)) {
|
|
603
749
|
throw new Error(`Worktree path does not exist: ${workspacePath}`);
|
|
604
750
|
}
|
|
605
751
|
addLog(impl.id, `Using pre-configured worktree: ${workspacePath}`);
|
|
606
752
|
} else if (scope || createPrOnly) {
|
|
607
|
-
if (!
|
|
753
|
+
if (!fs5.existsSync(workspacePath)) {
|
|
608
754
|
updateImplementation(impl.id, { status: "cloning" });
|
|
609
755
|
setupWorkspace(targetRepo, config.workspace_dir, impl.id);
|
|
610
756
|
}
|
|
@@ -613,20 +759,21 @@ async function main() {
|
|
|
613
759
|
exec("git fetch origin", workspacePath);
|
|
614
760
|
} catch {
|
|
615
761
|
}
|
|
762
|
+
validateBranchName(branchName);
|
|
616
763
|
addLog(impl.id, `Checking out branch: ${branchName}`);
|
|
617
764
|
try {
|
|
618
|
-
|
|
765
|
+
execGit(["checkout", branchName], workspacePath);
|
|
619
766
|
addLog(impl.id, `Merging origin/${defaultBranch} into ${branchName}...`);
|
|
620
767
|
try {
|
|
621
|
-
|
|
768
|
+
execGit(["merge", `origin/${defaultBranch}`, "--no-edit"], workspacePath);
|
|
622
769
|
} catch {
|
|
623
770
|
addLog(impl.id, "Merge conflict detected, aborting merge");
|
|
624
|
-
|
|
771
|
+
execGit(["merge", "--abort"], workspacePath);
|
|
625
772
|
}
|
|
626
773
|
} catch {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
774
|
+
execGit(["checkout", defaultBranch], workspacePath);
|
|
775
|
+
execGit(["pull", "origin", defaultBranch], workspacePath);
|
|
776
|
+
execGit(["checkout", "-b", branchName], workspacePath);
|
|
630
777
|
}
|
|
631
778
|
} else {
|
|
632
779
|
updateImplementation(impl.id, { status: "cloning" });
|
|
@@ -670,21 +817,22 @@ async function main() {
|
|
|
670
817
|
addLog(impl.id, `Skipping junk file: ${file}`);
|
|
671
818
|
continue;
|
|
672
819
|
}
|
|
673
|
-
|
|
820
|
+
execGit(["add", file], workspacePath);
|
|
674
821
|
}
|
|
675
822
|
} else {
|
|
676
823
|
exec("git add -A", workspacePath);
|
|
677
824
|
}
|
|
678
825
|
const staged = exec("git diff --cached --stat", workspacePath);
|
|
679
826
|
if (staged.trim().length > 0) {
|
|
680
|
-
const
|
|
827
|
+
const safeTitle = sanitizeForCommitMessage(idea.title);
|
|
828
|
+
const commitMsg = scope ? `feat: ${safeTitle} (sub-task)
|
|
681
829
|
|
|
682
830
|
Implemented by AI Product Manager
|
|
683
|
-
Idea: ${idea.id}` : `feat: ${
|
|
831
|
+
Idea: ${idea.id}` : `feat: ${safeTitle}
|
|
684
832
|
|
|
685
833
|
Implemented by AI Product Manager
|
|
686
834
|
Idea: ${idea.id}`;
|
|
687
|
-
|
|
835
|
+
execGit(["commit", "-m", commitMsg], workspacePath);
|
|
688
836
|
} else {
|
|
689
837
|
addLog(impl.id, "No staged changes after filtering, skipping commit");
|
|
690
838
|
}
|
|
@@ -694,7 +842,7 @@ Idea: ${idea.id}`;
|
|
|
694
842
|
if (!isWorktreeMode) {
|
|
695
843
|
try {
|
|
696
844
|
addLog(impl.id, `Pushing branch ${branchName} to origin`);
|
|
697
|
-
|
|
845
|
+
execGit(["push", "-u", "origin", branchName], workspacePath, 3e4);
|
|
698
846
|
} catch (pushError) {
|
|
699
847
|
addLog(impl.id, `Warning: Failed to push branch: ${pushError.message}`);
|
|
700
848
|
}
|