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
|
@@ -9406,7 +9406,11 @@ var RestAPIAdapter = class {
|
|
|
9406
9406
|
this.headers[key] = this.interpolateEnvVars(value);
|
|
9407
9407
|
}
|
|
9408
9408
|
}
|
|
9409
|
-
|
|
9409
|
+
const raw = config.field_mapping || {};
|
|
9410
|
+
this.fieldMapping = {};
|
|
9411
|
+
for (const [k, v2] of Object.entries(raw)) {
|
|
9412
|
+
if (typeof v2 === "string") this.fieldMapping[k] = v2;
|
|
9413
|
+
}
|
|
9410
9414
|
this.transforms = config.transforms || {};
|
|
9411
9415
|
}
|
|
9412
9416
|
/**
|
|
@@ -9496,7 +9500,7 @@ var ManualAdapter = class {
|
|
|
9496
9500
|
};
|
|
9497
9501
|
|
|
9498
9502
|
// src/lib/kpi-adapters/posthog.ts
|
|
9499
|
-
var PostHogAdapter = class {
|
|
9503
|
+
var PostHogAdapter = class _PostHogAdapter {
|
|
9500
9504
|
id;
|
|
9501
9505
|
type = "posthog";
|
|
9502
9506
|
fieldMapping;
|
|
@@ -9508,7 +9512,8 @@ var PostHogAdapter = class {
|
|
|
9508
9512
|
this.fieldMapping = config.field_mapping || {};
|
|
9509
9513
|
const hostEnv = config.config?.url_env || "POSTHOG_API_HOST";
|
|
9510
9514
|
const projectEnv = config.config?.project_id_env || "POSTHOG_PROJECT_ID";
|
|
9511
|
-
|
|
9515
|
+
const rawHost = process.env[hostEnv] || process.env["NEXT_PUBLIC_POSTHOG_HOST"] || "https://us.posthog.com";
|
|
9516
|
+
this.host = rawHost.replace("://us.i.posthog.com", "://us.posthog.com");
|
|
9512
9517
|
this.projectId = process.env[projectEnv] || "";
|
|
9513
9518
|
this.apiKey = process.env["POSTHOG_PERSONAL_API_KEY"] || "";
|
|
9514
9519
|
if (!this.apiKey) {
|
|
@@ -9527,26 +9532,32 @@ var PostHogAdapter = class {
|
|
|
9527
9532
|
const metadata = {};
|
|
9528
9533
|
const end = endDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9529
9534
|
const start = startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
|
|
9530
|
-
const
|
|
9531
|
-
|
|
9535
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
9536
|
+
for (const [kpiId, mapping] of Object.entries(this.fieldMapping)) {
|
|
9537
|
+
const query = _PostHogAdapter.normalizeMapping(mapping);
|
|
9538
|
+
const key = _PostHogAdapter.queryCacheKey(query);
|
|
9539
|
+
const entry = queryMap.get(key);
|
|
9540
|
+
if (entry) {
|
|
9541
|
+
entry.kpiIds.push(kpiId);
|
|
9542
|
+
} else {
|
|
9543
|
+
queryMap.set(key, { query, kpiIds: [kpiId] });
|
|
9544
|
+
}
|
|
9545
|
+
}
|
|
9546
|
+
if (queryMap.size === 0) {
|
|
9532
9547
|
return { values, metadata, fetched_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9533
9548
|
}
|
|
9534
|
-
for (const
|
|
9549
|
+
for (const [key, { query, kpiIds }] of Array.from(queryMap.entries())) {
|
|
9535
9550
|
try {
|
|
9536
|
-
const count = await this.fetchEventCount(
|
|
9537
|
-
metadata[
|
|
9538
|
-
for (const
|
|
9539
|
-
|
|
9540
|
-
values[kpiId] = count;
|
|
9541
|
-
}
|
|
9551
|
+
const count = await this.fetchEventCount(query.event, start, end, query.properties);
|
|
9552
|
+
metadata[key] = { count, start, end, event: query.event, properties: query.properties };
|
|
9553
|
+
for (const kpiId of kpiIds) {
|
|
9554
|
+
values[kpiId] = count;
|
|
9542
9555
|
}
|
|
9543
9556
|
} catch (error) {
|
|
9544
9557
|
const msg = error instanceof Error ? error.message : String(error);
|
|
9545
|
-
console.warn(`PostHog: failed to fetch "${
|
|
9546
|
-
for (const
|
|
9547
|
-
|
|
9548
|
-
values[kpiId] = null;
|
|
9549
|
-
}
|
|
9558
|
+
console.warn(`PostHog: failed to fetch "${key}": ${msg}`);
|
|
9559
|
+
for (const kpiId of kpiIds) {
|
|
9560
|
+
values[kpiId] = null;
|
|
9550
9561
|
}
|
|
9551
9562
|
}
|
|
9552
9563
|
}
|
|
@@ -9556,6 +9567,21 @@ var PostHogAdapter = class {
|
|
|
9556
9567
|
fetched_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9557
9568
|
};
|
|
9558
9569
|
}
|
|
9570
|
+
/** Normalize a field_mapping value (string or object) into an EventQuery. */
|
|
9571
|
+
static normalizeMapping(mapping) {
|
|
9572
|
+
if (typeof mapping === "string") {
|
|
9573
|
+
return { event: mapping };
|
|
9574
|
+
}
|
|
9575
|
+
return { event: mapping.event, properties: mapping.properties };
|
|
9576
|
+
}
|
|
9577
|
+
/** Stable cache key for deduplicating identical queries. */
|
|
9578
|
+
static queryCacheKey(query) {
|
|
9579
|
+
if (!query.properties || Object.keys(query.properties).length === 0) {
|
|
9580
|
+
return query.event;
|
|
9581
|
+
}
|
|
9582
|
+
const sortedProps = Object.entries(query.properties).sort(([a], [b]) => a.localeCompare(b));
|
|
9583
|
+
return `${query.event}[${sortedProps.map(([k, v2]) => `${k}=${v2}`).join(",")}]`;
|
|
9584
|
+
}
|
|
9559
9585
|
async healthCheck() {
|
|
9560
9586
|
try {
|
|
9561
9587
|
const res = await fetch(`${this.host}/api/projects/${this.projectId}/`, {
|
|
@@ -9569,21 +9595,29 @@ var PostHogAdapter = class {
|
|
|
9569
9595
|
}
|
|
9570
9596
|
}
|
|
9571
9597
|
/**
|
|
9572
|
-
* Fetch total event count from PostHog
|
|
9598
|
+
* Fetch total event count from PostHog HogQL Query API for a given date range.
|
|
9599
|
+
* Optionally filters by event properties (e.g. { command: "init" }).
|
|
9573
9600
|
*/
|
|
9574
|
-
async fetchEventCount(eventName, dateFrom, dateTo) {
|
|
9575
|
-
const url = `${this.host}/api/projects/${this.projectId}/
|
|
9601
|
+
async fetchEventCount(eventName, dateFrom, dateTo, properties) {
|
|
9602
|
+
const url = `${this.host}/api/projects/${this.projectId}/query/`;
|
|
9603
|
+
const conditions = [
|
|
9604
|
+
`event = '${eventName.replace(/'/g, "\\'")}'`,
|
|
9605
|
+
`timestamp >= toDate('${dateFrom}')`,
|
|
9606
|
+
`timestamp <= toDate('${dateTo}') + INTERVAL 1 DAY`
|
|
9607
|
+
];
|
|
9608
|
+
if (properties) {
|
|
9609
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
9610
|
+
const safeKey = key.replace(/'/g, "\\'");
|
|
9611
|
+
const safeVal = value.replace(/'/g, "\\'");
|
|
9612
|
+
conditions.push(`properties['${safeKey}'] = '${safeVal}'`);
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9615
|
+
const hogql = `SELECT count() AS cnt FROM events WHERE ${conditions.join(" AND ")}`;
|
|
9576
9616
|
const body = {
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
type: "events"
|
|
9582
|
-
}
|
|
9583
|
-
],
|
|
9584
|
-
date_from: dateFrom,
|
|
9585
|
-
date_to: dateTo,
|
|
9586
|
-
display: "ActionsLineGraph"
|
|
9617
|
+
query: {
|
|
9618
|
+
kind: "HogQLQuery",
|
|
9619
|
+
query: hogql
|
|
9620
|
+
}
|
|
9587
9621
|
};
|
|
9588
9622
|
const res = await fetch(url, {
|
|
9589
9623
|
method: "POST",
|
|
@@ -9597,9 +9631,7 @@ var PostHogAdapter = class {
|
|
|
9597
9631
|
throw new Error(`PostHog API returned ${res.status}: ${await res.text()}`);
|
|
9598
9632
|
}
|
|
9599
9633
|
const data = await res.json();
|
|
9600
|
-
|
|
9601
|
-
if (!result) return 0;
|
|
9602
|
-
return result.count ?? result.aggregated_value ?? 0;
|
|
9634
|
+
return data?.results?.[0]?.[0] ?? 0;
|
|
9603
9635
|
}
|
|
9604
9636
|
};
|
|
9605
9637
|
|
|
@@ -9613,7 +9645,11 @@ var WaitlistFileAdapter = class {
|
|
|
9613
9645
|
fieldMapping;
|
|
9614
9646
|
constructor(config) {
|
|
9615
9647
|
this.id = config.id;
|
|
9616
|
-
|
|
9648
|
+
const raw = config.field_mapping || {};
|
|
9649
|
+
this.fieldMapping = {};
|
|
9650
|
+
for (const [k, v2] of Object.entries(raw)) {
|
|
9651
|
+
if (typeof v2 === "string") this.fieldMapping[k] = v2;
|
|
9652
|
+
}
|
|
9617
9653
|
const relativePath = config.config?.path || "website/data/waitlist.json";
|
|
9618
9654
|
this.filePath = path.isAbsolute(relativePath) ? relativePath : path.resolve(process.cwd(), relativePath);
|
|
9619
9655
|
}
|
|
@@ -30597,6 +30633,17 @@ var fs17 = __toESM(require("fs"));
|
|
|
30597
30633
|
var import_child_process6 = require("child_process");
|
|
30598
30634
|
var fs16 = __toESM(require("fs"));
|
|
30599
30635
|
var path11 = __toESM(require("path"));
|
|
30636
|
+
var CREDENTIALS_DIR = path11.join(process.env.HOME || "", ".vibebusiness");
|
|
30637
|
+
var CREDENTIALS_FILE = path11.join(CREDENTIALS_DIR, "credentials.json");
|
|
30638
|
+
function loadCredentials2() {
|
|
30639
|
+
try {
|
|
30640
|
+
if (fs16.existsSync(CREDENTIALS_FILE)) {
|
|
30641
|
+
return JSON.parse(fs16.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
30642
|
+
}
|
|
30643
|
+
} catch {
|
|
30644
|
+
}
|
|
30645
|
+
return {};
|
|
30646
|
+
}
|
|
30600
30647
|
function detectClaudeCLI(customPath) {
|
|
30601
30648
|
const claudeBin = customPath || "claude";
|
|
30602
30649
|
try {
|
|
@@ -30629,13 +30676,27 @@ function detectClaudeCLI(customPath) {
|
|
|
30629
30676
|
}
|
|
30630
30677
|
function detectBYOKKeys() {
|
|
30631
30678
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
30632
|
-
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY };
|
|
30679
|
+
return { provider: "anthropic-api", key: process.env.ANTHROPIC_API_KEY, source: "env" };
|
|
30633
30680
|
}
|
|
30634
30681
|
if (process.env.OPENAI_API_KEY) {
|
|
30635
|
-
return { provider: "openai-api", key: process.env.OPENAI_API_KEY };
|
|
30682
|
+
return { provider: "openai-api", key: process.env.OPENAI_API_KEY, source: "env" };
|
|
30636
30683
|
}
|
|
30637
30684
|
if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
|
|
30638
|
-
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY };
|
|
30685
|
+
return { provider: "google-api", key: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY, source: "env" };
|
|
30686
|
+
}
|
|
30687
|
+
const creds = loadCredentials2();
|
|
30688
|
+
if (creds.ANTHROPIC_API_KEY) {
|
|
30689
|
+
process.env.ANTHROPIC_API_KEY = creds.ANTHROPIC_API_KEY;
|
|
30690
|
+
return { provider: "anthropic-api", key: creds.ANTHROPIC_API_KEY, source: "credentials" };
|
|
30691
|
+
}
|
|
30692
|
+
if (creds.OPENAI_API_KEY) {
|
|
30693
|
+
process.env.OPENAI_API_KEY = creds.OPENAI_API_KEY;
|
|
30694
|
+
return { provider: "openai-api", key: creds.OPENAI_API_KEY, source: "credentials" };
|
|
30695
|
+
}
|
|
30696
|
+
if (creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY) {
|
|
30697
|
+
const key = creds.GOOGLE_API_KEY || creds.GEMINI_API_KEY;
|
|
30698
|
+
process.env.GOOGLE_API_KEY = key;
|
|
30699
|
+
return { provider: "google-api", key, source: "credentials" };
|
|
30639
30700
|
}
|
|
30640
30701
|
return null;
|
|
30641
30702
|
}
|
|
@@ -30684,6 +30745,9 @@ function invokeClaudeCLI(options, claudePath) {
|
|
|
30684
30745
|
if (options.model) {
|
|
30685
30746
|
args2.push("--model", options.model);
|
|
30686
30747
|
}
|
|
30748
|
+
if (options.jsonSchema) {
|
|
30749
|
+
args2.push("--output-format", "json", "--json-schema", JSON.stringify(options.jsonSchema));
|
|
30750
|
+
}
|
|
30687
30751
|
if (options.claudeFlags) {
|
|
30688
30752
|
args2.push(...options.claudeFlags);
|
|
30689
30753
|
}
|
|
@@ -30752,7 +30816,18 @@ async function invokeAnthropicAPI(options) {
|
|
|
30752
30816
|
};
|
|
30753
30817
|
}
|
|
30754
30818
|
try {
|
|
30755
|
-
const model = options.model || "claude-sonnet-4-
|
|
30819
|
+
const model = options.model || "claude-sonnet-4-6";
|
|
30820
|
+
let prompt = options.prompt;
|
|
30821
|
+
if (options.jsonSchema) {
|
|
30822
|
+
prompt += `
|
|
30823
|
+
|
|
30824
|
+
IMPORTANT: Respond with valid JSON matching this schema:
|
|
30825
|
+
${JSON.stringify(options.jsonSchema, null, 2)}
|
|
30826
|
+
|
|
30827
|
+
Output ONLY the JSON object, no markdown code blocks or other text.`;
|
|
30828
|
+
} else if (options.expectJson) {
|
|
30829
|
+
prompt += "\n\nIMPORTANT: Respond with valid JSON only. No markdown code blocks or other text.";
|
|
30830
|
+
}
|
|
30756
30831
|
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
30757
30832
|
method: "POST",
|
|
30758
30833
|
headers: {
|
|
@@ -30763,7 +30838,7 @@ async function invokeAnthropicAPI(options) {
|
|
|
30763
30838
|
body: JSON.stringify({
|
|
30764
30839
|
model,
|
|
30765
30840
|
max_tokens: 16384,
|
|
30766
|
-
messages: [{ role: "user", content:
|
|
30841
|
+
messages: [{ role: "user", content: prompt }]
|
|
30767
30842
|
}),
|
|
30768
30843
|
signal: AbortSignal.timeout(options.timeoutMs || 3e5)
|
|
30769
30844
|
});
|
|
@@ -30817,6 +30892,27 @@ async function invokeAI(options) {
|
|
|
30817
30892
|
};
|
|
30818
30893
|
}
|
|
30819
30894
|
}
|
|
30895
|
+
function requireClaudeCLI(feature) {
|
|
30896
|
+
const cli = detectClaudeCLI();
|
|
30897
|
+
if (cli.found) {
|
|
30898
|
+
return cli.path;
|
|
30899
|
+
}
|
|
30900
|
+
const featureLabel = feature ? ` (${feature})` : "";
|
|
30901
|
+
const byok = detectBYOKKeys();
|
|
30902
|
+
if (byok) {
|
|
30903
|
+
throw new Error(
|
|
30904
|
+
`This feature${featureLabel} requires Claude Code CLI for file and tool access.
|
|
30905
|
+
Your API key is detected and works for reasoning tasks (heartbeat, idea evaluation),
|
|
30906
|
+
but codebase analysis and implementation need the full CLI.
|
|
30907
|
+
Install it with: npm install -g @anthropic-ai/claude-code`
|
|
30908
|
+
);
|
|
30909
|
+
}
|
|
30910
|
+
throw new Error(
|
|
30911
|
+
`No AI provider found${featureLabel}.
|
|
30912
|
+
Install Claude Code CLI: npm install -g @anthropic-ai/claude-code
|
|
30913
|
+
Or set an API key: export ANTHROPIC_API_KEY=sk-ant-...`
|
|
30914
|
+
);
|
|
30915
|
+
}
|
|
30820
30916
|
|
|
30821
30917
|
// scripts/lib/social/ai-content-generator.ts
|
|
30822
30918
|
function readJsonSafe2(filePath, fallback) {
|
|
@@ -30878,6 +30974,8 @@ CONTEXT: ${opts.context}
|
|
|
30878
30974
|
HISTORICAL PERFORMANCE:
|
|
30879
30975
|
${formatPastPerformance(opts.pastPerformance ?? [])}
|
|
30880
30976
|
|
|
30977
|
+
IMPORTANT: Only reference facts and numbers from the CONTEXT above. Never fabricate personal history, timelines, or metrics not provided.
|
|
30978
|
+
|
|
30881
30979
|
Generate ONE tweet (max 280 characters). Focus on being genuinely interesting, not salesy.
|
|
30882
30980
|
|
|
30883
30981
|
Respond in this exact JSON format (no markdown, no code blocks):
|
|
@@ -30929,6 +31027,7 @@ RULES:
|
|
|
30929
31027
|
- Number threads (1/, 2/, etc.) only if it helps clarity
|
|
30930
31028
|
- Include #buildinpublic in the first or last tweet only
|
|
30931
31029
|
- Make each tweet stand alone \u2014 people may see any tweet in isolation
|
|
31030
|
+
- IMPORTANT: Only reference facts and numbers from the CONTEXT above. Never fabricate personal history, timelines, or metrics not provided.
|
|
30932
31031
|
|
|
30933
31032
|
Respond in this exact JSON format (no markdown, no code blocks):
|
|
30934
31033
|
{
|
|
@@ -30992,6 +31091,113 @@ Respond in this exact JSON format (no markdown, no code blocks):
|
|
|
30992
31091
|
};
|
|
30993
31092
|
}
|
|
30994
31093
|
}
|
|
31094
|
+
async function generateWeekPlan(opts) {
|
|
31095
|
+
const positioning = opts.positioning ?? readJsonSafe2(POSITIONING_FILE, null);
|
|
31096
|
+
const today = /* @__PURE__ */ new Date();
|
|
31097
|
+
const dayOfWeek = today.getDay();
|
|
31098
|
+
const daysUntilMonday = dayOfWeek === 0 ? 1 : dayOfWeek === 1 ? 0 : 8 - dayOfWeek;
|
|
31099
|
+
const monday = new Date(today);
|
|
31100
|
+
monday.setDate(today.getDate() + daysUntilMonday);
|
|
31101
|
+
const weekDates = [];
|
|
31102
|
+
const dayNames = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
|
|
31103
|
+
for (let i = 0; i < 7; i++) {
|
|
31104
|
+
const d2 = new Date(monday);
|
|
31105
|
+
d2.setDate(monday.getDate() + i);
|
|
31106
|
+
weekDates.push({ day: dayNames[i], date: d2.toISOString().split("T")[0] });
|
|
31107
|
+
}
|
|
31108
|
+
const prompt = `You are a social media strategist for a developer-focused indie hacker audience on X/Twitter.
|
|
31109
|
+
|
|
31110
|
+
${ALGORITHM_KNOWLEDGE}
|
|
31111
|
+
|
|
31112
|
+
${HOOK_PATTERNS}
|
|
31113
|
+
|
|
31114
|
+
PRODUCT CONTEXT:
|
|
31115
|
+
- Product: ${positioning?.current?.tagline ?? "VibeBusiness"}
|
|
31116
|
+
- Value prop: ${positioning?.current?.value_proposition ?? "AI-powered business automation"}
|
|
31117
|
+
- Target audience: ${positioning?.current?.target_audience?.primary ?? "Indie hackers and solo founders"}
|
|
31118
|
+
|
|
31119
|
+
CURRENT GOALS:
|
|
31120
|
+
${opts.goalsContext || "No specific goals set."}
|
|
31121
|
+
|
|
31122
|
+
RECENTLY SHIPPED:
|
|
31123
|
+
${opts.recentShips || "No recent ships."}
|
|
31124
|
+
|
|
31125
|
+
KPI HIGHLIGHTS:
|
|
31126
|
+
${opts.kpiHighlights || "No KPI data yet."}
|
|
31127
|
+
|
|
31128
|
+
HISTORICAL PERFORMANCE:
|
|
31129
|
+
${formatPastPerformance(opts.pastPerformance ?? [])}
|
|
31130
|
+
|
|
31131
|
+
WEEK DATES:
|
|
31132
|
+
${weekDates.map((d2) => `${d2.day}: ${d2.date}`).join("\n")}
|
|
31133
|
+
|
|
31134
|
+
CRITICAL \u2014 FACTUAL ACCURACY:
|
|
31135
|
+
- ONLY reference facts, numbers, timelines, and events explicitly present in the context above.
|
|
31136
|
+
- NEVER fabricate personal history, backstory, or timeline details (e.g., "6 months ago I...", "I was stuck for X months").
|
|
31137
|
+
- NEVER invent metrics, user counts, or milestones that aren't in the KPI HIGHLIGHTS or RECENTLY SHIPPED sections.
|
|
31138
|
+
- If the context doesn't provide enough detail for a story hook, use a different hook pattern (question, contrarian, list-tease) instead of inventing a narrative.
|
|
31139
|
+
- It's better to write a shorter, honest tweet than a longer, fabricated one.
|
|
31140
|
+
|
|
31141
|
+
Plan a full week of content (Monday-Sunday). Rules:
|
|
31142
|
+
- 5 content days (Mon-Fri) + 2 rest days (Sat-Sun)
|
|
31143
|
+
- Include 1-2 threads per week (threads get 200-300% more reach)
|
|
31144
|
+
- Mix content types: ship, viral, thread, milestone, insight, question
|
|
31145
|
+
- Align content with current goals (especially behind-schedule ones)
|
|
31146
|
+
- Use diverse hook patterns \u2014 don't repeat the same hook type consecutively
|
|
31147
|
+
- Write FULL tweet text for each content day (max 280 chars for single tweets)
|
|
31148
|
+
- For threads, provide all tweet texts (5-7 tweets each, max 280 chars per tweet)
|
|
31149
|
+
- Each rest day should have type "rest" with empty content
|
|
31150
|
+
|
|
31151
|
+
Respond in this exact JSON format (no markdown, no code blocks):
|
|
31152
|
+
{
|
|
31153
|
+
"days": [
|
|
31154
|
+
{
|
|
31155
|
+
"day": "monday",
|
|
31156
|
+
"date": "YYYY-MM-DD",
|
|
31157
|
+
"type": "thread",
|
|
31158
|
+
"topic": "topic description",
|
|
31159
|
+
"content": "First tweet / hook text",
|
|
31160
|
+
"thread_tweets": ["tweet 1", "tweet 2", "..."],
|
|
31161
|
+
"hook_type": "story",
|
|
31162
|
+
"rationale": "Why this content on this day"
|
|
31163
|
+
},
|
|
31164
|
+
{
|
|
31165
|
+
"day": "tuesday",
|
|
31166
|
+
"date": "YYYY-MM-DD",
|
|
31167
|
+
"type": "viral",
|
|
31168
|
+
"topic": "ship",
|
|
31169
|
+
"content": "Full tweet text here",
|
|
31170
|
+
"hook_type": "bold-claim",
|
|
31171
|
+
"rationale": "Why this content on this day"
|
|
31172
|
+
}
|
|
31173
|
+
],
|
|
31174
|
+
"strategy_summary": "One paragraph explaining the week's strategy"
|
|
31175
|
+
}`;
|
|
31176
|
+
const result = await invokeAI({ prompt, model: "sonnet" });
|
|
31177
|
+
try {
|
|
31178
|
+
const parsed = JSON.parse(extractJson(result.output));
|
|
31179
|
+
for (const day of parsed.days) {
|
|
31180
|
+
if (day.type === "rest") continue;
|
|
31181
|
+
if (day.content && day.content.length > 280) {
|
|
31182
|
+
day.content = day.content.slice(0, 277).trimEnd() + "...";
|
|
31183
|
+
}
|
|
31184
|
+
if (day.thread_tweets) {
|
|
31185
|
+
day.thread_tweets = day.thread_tweets.map(
|
|
31186
|
+
(t) => t.length > 280 ? t.slice(0, 277).trimEnd() + "..." : t
|
|
31187
|
+
);
|
|
31188
|
+
}
|
|
31189
|
+
}
|
|
31190
|
+
return {
|
|
31191
|
+
days: parsed.days || [],
|
|
31192
|
+
strategy_summary: parsed.strategy_summary || ""
|
|
31193
|
+
};
|
|
31194
|
+
} catch {
|
|
31195
|
+
return {
|
|
31196
|
+
days: [],
|
|
31197
|
+
strategy_summary: "AI failed to generate a valid week plan."
|
|
31198
|
+
};
|
|
31199
|
+
}
|
|
31200
|
+
}
|
|
30995
31201
|
function extractJson(text) {
|
|
30996
31202
|
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
30997
31203
|
if (codeBlockMatch) return codeBlockMatch[1].trim();
|
|
@@ -31078,6 +31284,7 @@ function createDraft(state, text, source, sourceRef, mediaPaths = null) {
|
|
|
31078
31284
|
}
|
|
31079
31285
|
var SOCIAL_PREFIXES = [
|
|
31080
31286
|
"social-setup-x",
|
|
31287
|
+
"social-plan-week",
|
|
31081
31288
|
"social-draft-ship-visual",
|
|
31082
31289
|
"social-draft-ship",
|
|
31083
31290
|
"social-draft-update",
|
|
@@ -31098,6 +31305,9 @@ async function executeSocialTask(taskId, description, _businessContext) {
|
|
|
31098
31305
|
if (taskId === "social-setup-x") {
|
|
31099
31306
|
return await executeSetupX();
|
|
31100
31307
|
}
|
|
31308
|
+
if (taskId === "social-plan-week") {
|
|
31309
|
+
return await executePlanWeek();
|
|
31310
|
+
}
|
|
31101
31311
|
if (taskId === "social-draft-ship-visual") {
|
|
31102
31312
|
return executeDraftShipVisual();
|
|
31103
31313
|
}
|
|
@@ -31161,6 +31371,10 @@ function readSocialFreshness() {
|
|
|
31161
31371
|
bestEngagementRate = best.engagement_rate;
|
|
31162
31372
|
}
|
|
31163
31373
|
const threadCount = state.drafts.filter((d2) => d2.is_thread && d2.status === "published").length;
|
|
31374
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
31375
|
+
const scheduledDraftCount = state.drafts.filter(
|
|
31376
|
+
(d2) => d2.status === "draft" && d2.scheduled_date && d2.scheduled_date >= today
|
|
31377
|
+
).length;
|
|
31164
31378
|
return {
|
|
31165
31379
|
configured: state.credentials_valid,
|
|
31166
31380
|
credentials_valid: state.credentials_valid,
|
|
@@ -31172,7 +31386,8 @@ function readSocialFreshness() {
|
|
|
31172
31386
|
avg_engagement_rate: avgEngagementRate,
|
|
31173
31387
|
best_tweet_id: bestTweetId,
|
|
31174
31388
|
best_engagement_rate: bestEngagementRate,
|
|
31175
|
-
thread_count: threadCount
|
|
31389
|
+
thread_count: threadCount,
|
|
31390
|
+
scheduled_draft_count: scheduledDraftCount
|
|
31176
31391
|
};
|
|
31177
31392
|
}
|
|
31178
31393
|
async function executeSetupX() {
|
|
@@ -31417,6 +31632,117 @@ async function executeDraftThread(topic, description) {
|
|
|
31417
31632
|
].join("\n")
|
|
31418
31633
|
};
|
|
31419
31634
|
}
|
|
31635
|
+
async function executePlanWeek() {
|
|
31636
|
+
const state = loadState3();
|
|
31637
|
+
const pastPerformance = collectEngagementHistory(state);
|
|
31638
|
+
let positioning = null;
|
|
31639
|
+
try {
|
|
31640
|
+
if (fs18.existsSync(POSITIONING_FILE)) {
|
|
31641
|
+
positioning = JSON.parse(fs18.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
31642
|
+
}
|
|
31643
|
+
} catch {
|
|
31644
|
+
}
|
|
31645
|
+
let goalsContext = "";
|
|
31646
|
+
try {
|
|
31647
|
+
if (fs18.existsSync(GOALS_FILE)) {
|
|
31648
|
+
const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
|
|
31649
|
+
const relevant = goals.filter((g2) => g2.status === "on_track" || g2.status === "at_risk" || g2.status === "behind").map((g2) => {
|
|
31650
|
+
const progress = g2.current_value != null && g2.target_value ? `${Math.round(g2.current_value / g2.target_value * 100)}%` : "unknown";
|
|
31651
|
+
return `- ${g2.title} (${g2.status}, ${progress} complete, deadline: ${g2.deadline})`;
|
|
31652
|
+
});
|
|
31653
|
+
goalsContext = relevant.length > 0 ? relevant.join("\n") : "No active goals.";
|
|
31654
|
+
}
|
|
31655
|
+
} catch {
|
|
31656
|
+
}
|
|
31657
|
+
let recentShips = "";
|
|
31658
|
+
try {
|
|
31659
|
+
if (fs18.existsSync(IDEAS_FILE)) {
|
|
31660
|
+
const { ideas } = JSON.parse(fs18.readFileSync(IDEAS_FILE, "utf-8"));
|
|
31661
|
+
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1e3).toISOString();
|
|
31662
|
+
const shipped = ideas.filter((i) => i.stage === "shipped" && i.updated_at > twoWeeksAgo).map((i) => `- ${i.title} (shipped ${i.updated_at.split("T")[0]})`);
|
|
31663
|
+
recentShips = shipped.length > 0 ? shipped.join("\n") : "No recent ships.";
|
|
31664
|
+
}
|
|
31665
|
+
} catch {
|
|
31666
|
+
}
|
|
31667
|
+
let kpiHighlights = "";
|
|
31668
|
+
try {
|
|
31669
|
+
if (fs18.existsSync(GOALS_FILE)) {
|
|
31670
|
+
const { goals } = JSON.parse(fs18.readFileSync(GOALS_FILE, "utf-8"));
|
|
31671
|
+
const highlights = [];
|
|
31672
|
+
for (const goal of goals) {
|
|
31673
|
+
for (const kpi of goal.kpis) {
|
|
31674
|
+
if (kpi.current_value != null && kpi.target_value != null) {
|
|
31675
|
+
const pct = Math.round(kpi.current_value / kpi.target_value * 100);
|
|
31676
|
+
highlights.push(`- ${kpi.name}: ${kpi.current_value}/${kpi.target_value} ${kpi.unit} (${pct}%)`);
|
|
31677
|
+
}
|
|
31678
|
+
}
|
|
31679
|
+
}
|
|
31680
|
+
kpiHighlights = highlights.length > 0 ? highlights.join("\n") : "No KPI data.";
|
|
31681
|
+
}
|
|
31682
|
+
} catch {
|
|
31683
|
+
}
|
|
31684
|
+
const plan = await generateWeekPlan({
|
|
31685
|
+
positioning,
|
|
31686
|
+
pastPerformance,
|
|
31687
|
+
goalsContext,
|
|
31688
|
+
recentShips,
|
|
31689
|
+
kpiHighlights
|
|
31690
|
+
});
|
|
31691
|
+
if (plan.days.length === 0) {
|
|
31692
|
+
return { success: false, output: "AI failed to generate a weekly content plan." };
|
|
31693
|
+
}
|
|
31694
|
+
const createdDrafts = [];
|
|
31695
|
+
for (const day of plan.days) {
|
|
31696
|
+
if (day.type === "rest" || !day.content) continue;
|
|
31697
|
+
if (day.type === "thread" && day.thread_tweets && day.thread_tweets.length > 0) {
|
|
31698
|
+
const draft = {
|
|
31699
|
+
id: `draft-${v4_default().slice(0, 8)}`,
|
|
31700
|
+
text: day.thread_tweets[0],
|
|
31701
|
+
source: "thread",
|
|
31702
|
+
source_ref: null,
|
|
31703
|
+
status: "draft",
|
|
31704
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31705
|
+
published_at: null,
|
|
31706
|
+
tweet_id: null,
|
|
31707
|
+
tweet_url: null,
|
|
31708
|
+
is_thread: true,
|
|
31709
|
+
thread_tweets: day.thread_tweets,
|
|
31710
|
+
scheduled_date: day.date
|
|
31711
|
+
};
|
|
31712
|
+
state.drafts.push(draft);
|
|
31713
|
+
createdDrafts.push({ day: day.day, date: day.date, type: "thread", id: draft.id });
|
|
31714
|
+
} else {
|
|
31715
|
+
const source = ["ship", "milestone", "update", "digest", "insight"].includes(day.topic) ? day.topic : "insight";
|
|
31716
|
+
const draft = createDraft(state, day.content, source, null);
|
|
31717
|
+
draft.scheduled_date = day.date;
|
|
31718
|
+
createdDrafts.push({ day: day.day, date: day.date, type: day.type, id: draft.id });
|
|
31719
|
+
}
|
|
31720
|
+
}
|
|
31721
|
+
saveState3(state);
|
|
31722
|
+
const monday = plan.days.find((d2) => d2.day === "monday")?.date ?? "";
|
|
31723
|
+
const sunday = plan.days.find((d2) => d2.day === "sunday")?.date ?? "";
|
|
31724
|
+
const dateRange = monday && sunday ? `${formatDateShort(monday)} - ${formatDateShort(sunday)}` : "This Week";
|
|
31725
|
+
const lines = [`Weekly Content Plan (${dateRange})`, ""];
|
|
31726
|
+
for (const day of plan.days) {
|
|
31727
|
+
const dayLabel = day.day.charAt(0).toUpperCase() + day.day.slice(1, 3);
|
|
31728
|
+
if (day.type === "rest") {
|
|
31729
|
+
lines.push(`${dayLabel} -- Rest`);
|
|
31730
|
+
} else {
|
|
31731
|
+
const threadInfo = day.type === "thread" && day.thread_tweets ? ` (${day.thread_tweets.length} tweets)` : "";
|
|
31732
|
+
const preview = day.content.length > 60 ? `"${day.content.slice(0, 57)}..."` : `"${day.content}"`;
|
|
31733
|
+
lines.push(`${dayLabel} [${day.type}] ${preview}${threadInfo}`);
|
|
31734
|
+
lines.push(` Hook: ${day.hook_type} | ${day.rationale}`);
|
|
31735
|
+
}
|
|
31736
|
+
}
|
|
31737
|
+
lines.push("");
|
|
31738
|
+
lines.push(`${createdDrafts.length} drafts created. Review and edit at: vibebusiness start -> /social`);
|
|
31739
|
+
return { success: true, output: lines.join("\n") };
|
|
31740
|
+
}
|
|
31741
|
+
function formatDateShort(isoDate) {
|
|
31742
|
+
const d2 = /* @__PURE__ */ new Date(isoDate + "T00:00:00");
|
|
31743
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
31744
|
+
return `${months[d2.getMonth()]} ${d2.getDate()}`;
|
|
31745
|
+
}
|
|
31420
31746
|
async function executeTrackMetrics() {
|
|
31421
31747
|
const state = loadState3();
|
|
31422
31748
|
const credentials = loadCredentials();
|
|
@@ -31539,6 +31865,7 @@ async function executeCheckStatus3() {
|
|
|
31539
31865
|
}
|
|
31540
31866
|
}
|
|
31541
31867
|
lines.push(` Pending drafts: ${freshness.draft_count}`);
|
|
31868
|
+
lines.push(` Scheduled drafts: ${freshness.scheduled_draft_count}`);
|
|
31542
31869
|
lines.push(` Post streak: ${freshness.post_streak_days} day(s)`);
|
|
31543
31870
|
lines.push(` Total posts: ${freshness.total_posts}`);
|
|
31544
31871
|
lines.push(` Threads published: ${freshness.thread_count}`);
|
|
@@ -32516,9 +32843,9 @@ function cleanGitState(workspacePath) {
|
|
|
32516
32843
|
}
|
|
32517
32844
|
}
|
|
32518
32845
|
}
|
|
32519
|
-
function
|
|
32846
|
+
function execGit(args2, cwd, timeoutMs) {
|
|
32520
32847
|
try {
|
|
32521
|
-
return (0, import_child_process9.
|
|
32848
|
+
return (0, import_child_process9.execFileSync)("git", args2, {
|
|
32522
32849
|
cwd,
|
|
32523
32850
|
encoding: "utf-8",
|
|
32524
32851
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -32526,11 +32853,58 @@ function exec2(cmd, cwd, timeoutMs) {
|
|
|
32526
32853
|
}).trim();
|
|
32527
32854
|
} catch (error) {
|
|
32528
32855
|
const execError = error;
|
|
32529
|
-
|
|
32530
|
-
${
|
|
32856
|
+
const stderr = typeof execError.stderr === "string" ? execError.stderr : execError.stderr?.toString() ?? "";
|
|
32857
|
+
throw new Error(`git ${args2[0]} failed: ${stderr || execError.message}`);
|
|
32531
32858
|
}
|
|
32532
32859
|
}
|
|
32533
32860
|
|
|
32861
|
+
// scripts/lib/shell-safe.ts
|
|
32862
|
+
var SAFE_BRANCH_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
32863
|
+
function truncate(s, max2 = 100) {
|
|
32864
|
+
return s.length > max2 ? s.slice(0, max2) + "..." : s;
|
|
32865
|
+
}
|
|
32866
|
+
var _rejectionCounts = { branch: 0, repo: 0, commitMsg: 0 };
|
|
32867
|
+
function recordRejection(type) {
|
|
32868
|
+
_rejectionCounts[type]++;
|
|
32869
|
+
console.warn(`[shell-safe] Rejection recorded: type=${type} total=${_rejectionCounts[type]}`);
|
|
32870
|
+
}
|
|
32871
|
+
function getRejectionCounts() {
|
|
32872
|
+
try {
|
|
32873
|
+
return { ..._rejectionCounts };
|
|
32874
|
+
} catch {
|
|
32875
|
+
return { branch: 0, repo: 0, commitMsg: 0 };
|
|
32876
|
+
}
|
|
32877
|
+
}
|
|
32878
|
+
function resetRejectionCounts() {
|
|
32879
|
+
_rejectionCounts.branch = 0;
|
|
32880
|
+
_rejectionCounts.repo = 0;
|
|
32881
|
+
_rejectionCounts.commitMsg = 0;
|
|
32882
|
+
}
|
|
32883
|
+
function validateBranchName(name) {
|
|
32884
|
+
if (!name || name.trim() === "") {
|
|
32885
|
+
console.warn("[shell-safe] validateBranchName: rejecting empty branch name");
|
|
32886
|
+
recordRejection("branch");
|
|
32887
|
+
throw new Error("Branch name cannot be empty");
|
|
32888
|
+
}
|
|
32889
|
+
if (!SAFE_BRANCH_RE.test(name)) {
|
|
32890
|
+
console.warn(`[shell-safe] Rejected branch name: ${truncate(name)}`);
|
|
32891
|
+
recordRejection("branch");
|
|
32892
|
+
throw new Error(
|
|
32893
|
+
`Invalid branch name: "${truncate(name)}" \u2014 only alphanumerics, dots, dashes, underscores, and slashes are allowed`
|
|
32894
|
+
);
|
|
32895
|
+
}
|
|
32896
|
+
return name;
|
|
32897
|
+
}
|
|
32898
|
+
function sanitizeForCommitMessage(msg) {
|
|
32899
|
+
const original = msg;
|
|
32900
|
+
let sanitized = msg.replace(/"/g, "\u201C\u201D"[0]).replace(/`/g, "\u2018").replace(/\$\(/g, "(").replace(/\$/g, "").replace(/\\/g, "/").replace(/!/g, "").replace(/[\n\r]/g, " ").trim();
|
|
32901
|
+
if (sanitized !== original) {
|
|
32902
|
+
console.warn(`[shell-safe] sanitizeForCommitMessage modified input: ${truncate(original)}`);
|
|
32903
|
+
recordRejection("commitMsg");
|
|
32904
|
+
}
|
|
32905
|
+
return sanitized;
|
|
32906
|
+
}
|
|
32907
|
+
|
|
32534
32908
|
// scripts/lib/worktree.ts
|
|
32535
32909
|
function getWorktreePaths(workspaceDir, repoName) {
|
|
32536
32910
|
return {
|
|
@@ -32540,17 +32914,17 @@ function getWorktreePaths(workspaceDir, repoName) {
|
|
|
32540
32914
|
}
|
|
32541
32915
|
function syncBaseClone(baseClonePath, defaultBranch) {
|
|
32542
32916
|
cleanGitState(baseClonePath);
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
32917
|
+
execGit(["fetch", "origin"], baseClonePath, 3e4);
|
|
32918
|
+
execGit(["checkout", validateBranchName(defaultBranch)], baseClonePath);
|
|
32919
|
+
execGit(["reset", "--hard", `origin/${validateBranchName(defaultBranch)}`], baseClonePath);
|
|
32546
32920
|
}
|
|
32547
32921
|
function ensureIdeaBranch(baseClonePath, branchName, defaultBranch) {
|
|
32548
32922
|
try {
|
|
32549
|
-
|
|
32923
|
+
execGit(["rev-parse", "--verify", validateBranchName(branchName)], baseClonePath);
|
|
32550
32924
|
} catch {
|
|
32551
|
-
|
|
32552
|
-
|
|
32553
|
-
|
|
32925
|
+
execGit(["checkout", validateBranchName(defaultBranch)], baseClonePath);
|
|
32926
|
+
execGit(["checkout", "-b", validateBranchName(branchName)], baseClonePath);
|
|
32927
|
+
execGit(["checkout", validateBranchName(defaultBranch)], baseClonePath);
|
|
32554
32928
|
}
|
|
32555
32929
|
}
|
|
32556
32930
|
function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIndex) {
|
|
@@ -32561,17 +32935,17 @@ function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIn
|
|
|
32561
32935
|
const worktreePath = path20.join(worktreeContainerDir, `${ideaBranch}-g${groupIndex}`);
|
|
32562
32936
|
if (fs25.existsSync(worktreePath)) {
|
|
32563
32937
|
try {
|
|
32564
|
-
|
|
32938
|
+
execGit(["worktree", "remove", "--force", worktreePath], baseClonePath, 1e4);
|
|
32565
32939
|
} catch {
|
|
32566
32940
|
fs25.rmSync(worktreePath, { recursive: true, force: true });
|
|
32567
32941
|
}
|
|
32568
32942
|
}
|
|
32569
32943
|
try {
|
|
32570
|
-
|
|
32944
|
+
execGit(["branch", "-D", tempBranch], baseClonePath);
|
|
32571
32945
|
} catch {
|
|
32572
32946
|
}
|
|
32573
|
-
|
|
32574
|
-
|
|
32947
|
+
execGit(
|
|
32948
|
+
["worktree", "add", "-b", tempBranch, worktreePath, ideaBranch],
|
|
32575
32949
|
baseClonePath,
|
|
32576
32950
|
15e3
|
|
32577
32951
|
);
|
|
@@ -32580,13 +32954,13 @@ function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIn
|
|
|
32580
32954
|
function mergeWorktreeIntoIdeaBranch(baseClonePath, ideaBranch, tempBranch) {
|
|
32581
32955
|
try {
|
|
32582
32956
|
cleanGitState(baseClonePath);
|
|
32583
|
-
|
|
32584
|
-
|
|
32957
|
+
execGit(["checkout", ideaBranch], baseClonePath);
|
|
32958
|
+
execGit(["merge", tempBranch, "--no-edit"], baseClonePath, 3e4);
|
|
32585
32959
|
return true;
|
|
32586
32960
|
} catch (error) {
|
|
32587
32961
|
console.log(`[worktree] Merge failed for ${tempBranch} into ${ideaBranch}: ${error.message}`);
|
|
32588
32962
|
try {
|
|
32589
|
-
|
|
32963
|
+
execGit(["merge", "--abort"], baseClonePath);
|
|
32590
32964
|
} catch {
|
|
32591
32965
|
}
|
|
32592
32966
|
return false;
|
|
@@ -32594,7 +32968,7 @@ function mergeWorktreeIntoIdeaBranch(baseClonePath, ideaBranch, tempBranch) {
|
|
|
32594
32968
|
}
|
|
32595
32969
|
function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
32596
32970
|
try {
|
|
32597
|
-
|
|
32971
|
+
execGit(["worktree", "remove", "--force", worktreePath], baseClonePath, 1e4);
|
|
32598
32972
|
} catch {
|
|
32599
32973
|
try {
|
|
32600
32974
|
fs25.rmSync(worktreePath, { recursive: true, force: true });
|
|
@@ -32602,7 +32976,7 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
32602
32976
|
}
|
|
32603
32977
|
}
|
|
32604
32978
|
try {
|
|
32605
|
-
|
|
32979
|
+
execGit(["branch", "-D", tempBranch], baseClonePath);
|
|
32606
32980
|
} catch {
|
|
32607
32981
|
}
|
|
32608
32982
|
}
|
|
@@ -32622,7 +32996,7 @@ function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
|
32622
32996
|
}
|
|
32623
32997
|
}
|
|
32624
32998
|
try {
|
|
32625
|
-
|
|
32999
|
+
execGit(["worktree", "prune"], baseClonePath, 1e4);
|
|
32626
33000
|
} catch {
|
|
32627
33001
|
}
|
|
32628
33002
|
try {
|
|
@@ -34127,6 +34501,7 @@ These tasks generate tweet drafts for the founder's build-in-public journey.
|
|
|
34127
34501
|
|
|
34128
34502
|
Task prefixes:
|
|
34129
34503
|
- "social-setup-x" \u2192 Validate X API credentials from .env, save username
|
|
34504
|
+
- "social-plan-week" \u2192 Generate a 7-day content calendar with AI-optimized drafts (5 content days + 2 rest)
|
|
34130
34505
|
- "social-draft-ship-visual" \u2192 Generate a "just shipped" tweet WITH ship card image or video attached
|
|
34131
34506
|
- "social-draft-ship" \u2192 Generate a "just shipped" tweet (text only, no media)
|
|
34132
34507
|
- "social-draft-update" \u2192 Generate a product update draft from positioning data
|
|
@@ -34150,6 +34525,7 @@ When to recommend social tasks:
|
|
|
34150
34525
|
7. If social.total_posts > 0 AND social.avg_engagement_rate == null \u2192 suggest "social-track-metrics" to start tracking
|
|
34151
34526
|
8. If social.thread_count == 0 AND social.total_posts >= 3 \u2192 suggest "social-draft-thread-{relevant-topic}" (threads get 200-300% more reach)
|
|
34152
34527
|
9. Prefer AI-generated drafts (social-draft-viral-*) over template drafts (social-draft-ship/update/milestone) for higher engagement
|
|
34528
|
+
10. If social.scheduled_draft_count == 0 AND social.configured == true \u2192 suggest "social-plan-week" to plan next week's content calendar
|
|
34153
34529
|
|
|
34154
34530
|
PROMO COPY TASKS:
|
|
34155
34531
|
The "promo_copy" field shows positioning-driven copy generation status.
|
|
@@ -34262,117 +34638,58 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
34262
34638
|
|
|
34263
34639
|
If there's nothing urgent, set next_action to null and new_todos to an empty array.
|
|
34264
34640
|
Focus on actionable, specific recommendations. Be concise.`;
|
|
34265
|
-
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
|
|
34271
|
-
|
|
34272
|
-
|
|
34273
|
-
|
|
34274
|
-
|
|
34275
|
-
|
|
34641
|
+
const startTime = Date.now();
|
|
34642
|
+
const promptSize = prompt.length;
|
|
34643
|
+
log(`Running Claude reasoning (prompt: ${promptSize} chars)...`);
|
|
34644
|
+
const reasoningSchema = {
|
|
34645
|
+
type: "object",
|
|
34646
|
+
properties: {
|
|
34647
|
+
analysis: { type: "string" },
|
|
34648
|
+
critical_issue: { type: ["string", "null"] },
|
|
34649
|
+
next_action: {
|
|
34650
|
+
type: ["object", "null"],
|
|
34651
|
+
properties: {
|
|
34652
|
+
task_id: { type: "string" },
|
|
34653
|
+
description: { type: "string" },
|
|
34654
|
+
autonomous: { type: "boolean" }
|
|
34655
|
+
},
|
|
34656
|
+
required: ["task_id", "description", "autonomous"]
|
|
34657
|
+
},
|
|
34658
|
+
new_todos: {
|
|
34659
|
+
type: "array",
|
|
34660
|
+
items: {
|
|
34661
|
+
type: "object",
|
|
34276
34662
|
properties: {
|
|
34277
|
-
|
|
34663
|
+
id: { type: "string" },
|
|
34278
34664
|
description: { type: "string" },
|
|
34279
|
-
|
|
34665
|
+
priority: { type: "string" }
|
|
34280
34666
|
},
|
|
34281
|
-
required: ["
|
|
34282
|
-
},
|
|
34283
|
-
new_todos: {
|
|
34284
|
-
type: "array",
|
|
34285
|
-
items: {
|
|
34286
|
-
type: "object",
|
|
34287
|
-
properties: {
|
|
34288
|
-
id: { type: "string" },
|
|
34289
|
-
description: { type: "string" },
|
|
34290
|
-
priority: { type: "string" }
|
|
34291
|
-
},
|
|
34292
|
-
required: ["id", "description", "priority"]
|
|
34293
|
-
}
|
|
34294
|
-
},
|
|
34295
|
-
learnings: { type: "array", items: { type: "string" } }
|
|
34296
|
-
},
|
|
34297
|
-
required: ["analysis", "next_action"]
|
|
34298
|
-
});
|
|
34299
|
-
const claude = (0, import_child_process10.spawn)("claude", [
|
|
34300
|
-
"--print",
|
|
34301
|
-
"--output-format",
|
|
34302
|
-
"json",
|
|
34303
|
-
"--json-schema",
|
|
34304
|
-
reasoningSchema
|
|
34305
|
-
], {
|
|
34306
|
-
cwd: ROOT_DIR,
|
|
34307
|
-
env: process.env,
|
|
34308
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
34309
|
-
});
|
|
34310
|
-
log(` Claude process started (PID: ${claude.pid})`);
|
|
34311
|
-
claude.stdin.write(prompt);
|
|
34312
|
-
claude.stdin.end();
|
|
34313
|
-
log(` Prompt sent via stdin (with --output-format json + --json-schema)`);
|
|
34314
|
-
let output = "";
|
|
34315
|
-
let errorOutput = "";
|
|
34316
|
-
let isResolved = false;
|
|
34317
|
-
const progressInterval = setInterval(() => {
|
|
34318
|
-
if (!isResolved) {
|
|
34319
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
34320
|
-
log(` ...Claude thinking (${elapsed}s elapsed)`);
|
|
34321
|
-
}
|
|
34322
|
-
}, 1e4);
|
|
34323
|
-
const timeout = setTimeout(() => {
|
|
34324
|
-
if (!isResolved) {
|
|
34325
|
-
isResolved = true;
|
|
34326
|
-
clearInterval(progressInterval);
|
|
34327
|
-
claude.kill("SIGTERM");
|
|
34328
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
34329
|
-
log(`Claude reasoning timed out after ${elapsed}s`);
|
|
34330
|
-
resolve2(null);
|
|
34331
|
-
}
|
|
34332
|
-
}, TIMEOUT_MS);
|
|
34333
|
-
claude.stdout.on("data", (data) => {
|
|
34334
|
-
const chunk = data.toString();
|
|
34335
|
-
output += chunk;
|
|
34336
|
-
if (output.length === chunk.length) {
|
|
34337
|
-
log(` Claude is responding...`);
|
|
34338
|
-
}
|
|
34339
|
-
});
|
|
34340
|
-
claude.stderr.on("data", (data) => {
|
|
34341
|
-
const text = data.toString();
|
|
34342
|
-
errorOutput += text;
|
|
34343
|
-
if (text.trim()) {
|
|
34344
|
-
log(` [Claude stderr]: ${text.trim().substring(0, 200)}`);
|
|
34345
|
-
}
|
|
34346
|
-
});
|
|
34347
|
-
claude.on("close", (code) => {
|
|
34348
|
-
if (isResolved) return;
|
|
34349
|
-
isResolved = true;
|
|
34350
|
-
clearInterval(progressInterval);
|
|
34351
|
-
clearTimeout(timeout);
|
|
34352
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
34353
|
-
if (code === 0) {
|
|
34354
|
-
const result = parseCliJsonOutput(output, "Reasoning");
|
|
34355
|
-
if (result) {
|
|
34356
|
-
log(`Claude completed in ${elapsed}s: ${result.analysis}`);
|
|
34357
|
-
resolve2(result);
|
|
34358
|
-
} else {
|
|
34359
|
-
log(`Claude completed in ${elapsed}s but no valid JSON found`);
|
|
34360
|
-
resolve2(null);
|
|
34667
|
+
required: ["id", "description", "priority"]
|
|
34361
34668
|
}
|
|
34362
|
-
}
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
34372
|
-
|
|
34373
|
-
resolve2(null);
|
|
34374
|
-
});
|
|
34669
|
+
},
|
|
34670
|
+
learnings: { type: "array", items: { type: "string" } }
|
|
34671
|
+
},
|
|
34672
|
+
required: ["analysis", "next_action"]
|
|
34673
|
+
};
|
|
34674
|
+
const aiResult = await invokeAI({
|
|
34675
|
+
prompt,
|
|
34676
|
+
cwd: ROOT_DIR,
|
|
34677
|
+
timeoutMs: TIMEOUT_MS,
|
|
34678
|
+
useStdin: true,
|
|
34679
|
+
jsonSchema: reasoningSchema
|
|
34375
34680
|
});
|
|
34681
|
+
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
34682
|
+
if (aiResult.error) {
|
|
34683
|
+
log(`Claude reasoning failed after ${elapsed}s: ${aiResult.error}`);
|
|
34684
|
+
return null;
|
|
34685
|
+
}
|
|
34686
|
+
const result = aiResult.provider === "claude-cli" ? parseCliJsonOutput(aiResult.output, "Reasoning") : robustJsonParse(aiResult.output, "Reasoning");
|
|
34687
|
+
if (result) {
|
|
34688
|
+
log(`Claude completed in ${elapsed}s (${aiResult.provider}): ${result.analysis}`);
|
|
34689
|
+
} else {
|
|
34690
|
+
log(`Claude completed in ${elapsed}s but no valid JSON found`);
|
|
34691
|
+
}
|
|
34692
|
+
return result;
|
|
34376
34693
|
}
|
|
34377
34694
|
function addTasksToTodo(tasks) {
|
|
34378
34695
|
if (tasks.length === 0) return;
|
|
@@ -35005,98 +35322,63 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
35005
35322
|
}
|
|
35006
35323
|
]
|
|
35007
35324
|
}`;
|
|
35008
|
-
|
|
35009
|
-
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
|
|
35013
|
-
|
|
35014
|
-
|
|
35015
|
-
|
|
35016
|
-
|
|
35017
|
-
|
|
35018
|
-
|
|
35019
|
-
|
|
35020
|
-
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
|
|
35024
|
-
|
|
35025
|
-
|
|
35026
|
-
required: ["id", "title", "description", "files_to_modify"]
|
|
35027
|
-
}
|
|
35028
|
-
}
|
|
35029
|
-
},
|
|
35030
|
-
required: ["sub_tasks"]
|
|
35031
|
-
});
|
|
35032
|
-
const claude = (0, import_child_process10.spawn)("claude", [
|
|
35033
|
-
"--print",
|
|
35034
|
-
"--output-format",
|
|
35035
|
-
"json",
|
|
35036
|
-
"--json-schema",
|
|
35037
|
-
decompSchema
|
|
35038
|
-
], {
|
|
35039
|
-
cwd: ROOT_DIR,
|
|
35040
|
-
env: process.env,
|
|
35041
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
35042
|
-
});
|
|
35043
|
-
claude.stdin.write(prompt);
|
|
35044
|
-
claude.stdin.end();
|
|
35045
|
-
let output = "";
|
|
35046
|
-
let isResolved = false;
|
|
35047
|
-
const progressInterval = setInterval(() => {
|
|
35048
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
35049
|
-
log(` ...Decomposing (${elapsed}s elapsed, ${output.length} chars received)`);
|
|
35050
|
-
}, 15e3);
|
|
35051
|
-
const timeout = setTimeout(() => {
|
|
35052
|
-
if (!isResolved) {
|
|
35053
|
-
isResolved = true;
|
|
35054
|
-
clearInterval(progressInterval);
|
|
35055
|
-
claude.kill("SIGTERM");
|
|
35056
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
35057
|
-
log(`Decomposition timed out after ${elapsed}s (limit: ${Math.round(TIMEOUT_MS / 1e3)}s)`);
|
|
35058
|
-
resolve2([]);
|
|
35059
|
-
}
|
|
35060
|
-
}, TIMEOUT_MS);
|
|
35061
|
-
claude.stdout.on("data", (data) => {
|
|
35062
|
-
output += data.toString();
|
|
35063
|
-
});
|
|
35064
|
-
claude.on("close", (code) => {
|
|
35065
|
-
if (isResolved) return;
|
|
35066
|
-
isResolved = true;
|
|
35067
|
-
clearTimeout(timeout);
|
|
35068
|
-
clearInterval(progressInterval);
|
|
35069
|
-
if (code === 0 && output.trim().length > 0) {
|
|
35070
|
-
const parsed = parseCliJsonOutput(output, "Decomposition");
|
|
35071
|
-
if (parsed?.sub_tasks?.length) {
|
|
35072
|
-
const subTasks = parsed.sub_tasks.slice(0, maxSubTasks).map((st, i) => ({
|
|
35073
|
-
id: st.id || `st-${String(i + 1).padStart(3, "0")}`,
|
|
35074
|
-
title: st.title,
|
|
35075
|
-
description: st.description,
|
|
35076
|
-
files_to_modify: st.files_to_modify || [],
|
|
35077
|
-
observability: st.observability || void 0,
|
|
35078
|
-
status: "pending",
|
|
35079
|
-
started_at: null,
|
|
35080
|
-
completed_at: null,
|
|
35081
|
-
error_message: null,
|
|
35082
|
-
commit_hash: null
|
|
35083
|
-
}));
|
|
35084
|
-
log(`Decomposed into ${subTasks.length} sub-tasks`);
|
|
35085
|
-
resolve2(subTasks);
|
|
35086
|
-
return;
|
|
35325
|
+
const TIMEOUT_MS = config.autonomy?.max_sub_task_timeout_ms || 6e5;
|
|
35326
|
+
log(`Decomposing idea into sub-tasks via Claude reasoning (timeout: ${Math.round(TIMEOUT_MS / 1e3)}s)...`);
|
|
35327
|
+
const startTime = Date.now();
|
|
35328
|
+
const decompSchema = {
|
|
35329
|
+
type: "object",
|
|
35330
|
+
properties: {
|
|
35331
|
+
sub_tasks: {
|
|
35332
|
+
type: "array",
|
|
35333
|
+
items: {
|
|
35334
|
+
type: "object",
|
|
35335
|
+
properties: {
|
|
35336
|
+
id: { type: "string" },
|
|
35337
|
+
title: { type: "string" },
|
|
35338
|
+
description: { type: "string" },
|
|
35339
|
+
files_to_modify: { type: "array", items: { type: "string" } },
|
|
35340
|
+
observability: { type: "string" }
|
|
35341
|
+
},
|
|
35342
|
+
required: ["id", "title", "description", "files_to_modify"]
|
|
35087
35343
|
}
|
|
35088
35344
|
}
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35098
|
-
});
|
|
35345
|
+
},
|
|
35346
|
+
required: ["sub_tasks"]
|
|
35347
|
+
};
|
|
35348
|
+
const aiResult = await invokeAI({
|
|
35349
|
+
prompt,
|
|
35350
|
+
cwd: ROOT_DIR,
|
|
35351
|
+
timeoutMs: TIMEOUT_MS,
|
|
35352
|
+
useStdin: true,
|
|
35353
|
+
jsonSchema: decompSchema
|
|
35099
35354
|
});
|
|
35355
|
+
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
35356
|
+
if (aiResult.error) {
|
|
35357
|
+
log(`Decomposition failed after ${elapsed}s: ${aiResult.error}`);
|
|
35358
|
+
return [];
|
|
35359
|
+
}
|
|
35360
|
+
if (!aiResult.output.trim()) {
|
|
35361
|
+
log(`Decomposition returned empty output after ${elapsed}s`);
|
|
35362
|
+
return [];
|
|
35363
|
+
}
|
|
35364
|
+
const parsed = aiResult.provider === "claude-cli" ? parseCliJsonOutput(aiResult.output, "Decomposition") : robustJsonParse(aiResult.output, "Decomposition");
|
|
35365
|
+
if (parsed?.sub_tasks?.length) {
|
|
35366
|
+
const subTasks = parsed.sub_tasks.slice(0, maxSubTasks).map((st, i) => ({
|
|
35367
|
+
id: st.id || `st-${String(i + 1).padStart(3, "0")}`,
|
|
35368
|
+
title: st.title,
|
|
35369
|
+
description: st.description,
|
|
35370
|
+
files_to_modify: st.files_to_modify || [],
|
|
35371
|
+
observability: st.observability || void 0,
|
|
35372
|
+
status: "pending",
|
|
35373
|
+
started_at: null,
|
|
35374
|
+
completed_at: null,
|
|
35375
|
+
error_message: null,
|
|
35376
|
+
commit_hash: null
|
|
35377
|
+
}));
|
|
35378
|
+
log(`Decomposed into ${subTasks.length} sub-tasks`);
|
|
35379
|
+
return subTasks;
|
|
35380
|
+
}
|
|
35381
|
+
return [];
|
|
35100
35382
|
}
|
|
35101
35383
|
function deferIdeaWithComment(ideaId, reason) {
|
|
35102
35384
|
const ideasData = loadJson(IDEAS_FILE, { ideas: [] });
|
|
@@ -35446,12 +35728,12 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
35446
35728
|
if (branchName2) {
|
|
35447
35729
|
try {
|
|
35448
35730
|
log(`Auto-merging branch ${branchName2} into ${defaultBranch2}...`);
|
|
35449
|
-
(0, import_child_process10.
|
|
35450
|
-
(0, import_child_process10.
|
|
35451
|
-
(0, import_child_process10.
|
|
35731
|
+
(0, import_child_process10.execFileSync)("git", ["checkout", validateBranchName(defaultBranch2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35732
|
+
(0, import_child_process10.execFileSync)("git", ["merge", validateBranchName(branchName2), "--squash", "--no-edit"], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35733
|
+
(0, import_child_process10.execFileSync)("git", ["commit", "-m", `feat: ${sanitizeForCommitMessage(idea.title)} (auto-merged from ${validateBranchName(branchName2)})`], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35452
35734
|
log(`Successfully merged ${branchName2} into ${defaultBranch2}`);
|
|
35453
35735
|
try {
|
|
35454
|
-
(0, import_child_process10.
|
|
35736
|
+
(0, import_child_process10.execFileSync)("git", ["branch", "-d", validateBranchName(branchName2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35455
35737
|
log(`Deleted branch ${branchName2}`);
|
|
35456
35738
|
} catch {
|
|
35457
35739
|
log(`Could not delete branch ${branchName2} (may need force delete)`);
|
|
@@ -35476,8 +35758,8 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
35476
35758
|
} catch (mergeError) {
|
|
35477
35759
|
log(`Auto-merge failed (likely conflicts): ${mergeError instanceof Error ? mergeError.message : "Unknown"}`);
|
|
35478
35760
|
try {
|
|
35479
|
-
(0, import_child_process10.
|
|
35480
|
-
(0, import_child_process10.
|
|
35761
|
+
(0, import_child_process10.execFileSync)("git", ["merge", "--abort"], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35762
|
+
(0, import_child_process10.execFileSync)("git", ["checkout", validateBranchName(branchName2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
35481
35763
|
} catch {
|
|
35482
35764
|
}
|
|
35483
35765
|
log(`Left idea ${ideaId} in testing stage for manual merge`);
|
|
@@ -35640,6 +35922,12 @@ async function executeSuggestEpics() {
|
|
|
35640
35922
|
}
|
|
35641
35923
|
}
|
|
35642
35924
|
async function executeMetaTask(taskId, description, businessContext, options) {
|
|
35925
|
+
try {
|
|
35926
|
+
requireClaudeCLI("meta-task execution");
|
|
35927
|
+
} catch (err2) {
|
|
35928
|
+
log(`Meta-task skipped: ${err2.message}`);
|
|
35929
|
+
return false;
|
|
35930
|
+
}
|
|
35643
35931
|
const failureCount = options?.failureCount || 0;
|
|
35644
35932
|
log(`Executing meta-task via Claude Code: ${taskId} (attempt ${failureCount + 1})`);
|
|
35645
35933
|
const allowedFiles = [
|
|
@@ -35912,6 +36200,12 @@ Return a JSON array of ideas:
|
|
|
35912
36200
|
Only output the JSON array, no other text.`;
|
|
35913
36201
|
}
|
|
35914
36202
|
async function executeGoalGapResearch(goalId) {
|
|
36203
|
+
try {
|
|
36204
|
+
requireClaudeCLI("goal-gap research");
|
|
36205
|
+
} catch (err2) {
|
|
36206
|
+
log(`Goal-gap research skipped: ${err2.message}`);
|
|
36207
|
+
return false;
|
|
36208
|
+
}
|
|
35915
36209
|
log(`Executing goal-gap research for ${goalId}...`);
|
|
35916
36210
|
const goalsData = loadJson(GOALS_FILE, { goals: [] });
|
|
35917
36211
|
const goal = goalsData.goals.find((g2) => g2.id === goalId);
|
|
@@ -36176,49 +36470,23 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
36176
36470
|
const liveKPIs = await fetchLiveKPIs();
|
|
36177
36471
|
const prompt = buildEvaluationPrompt(idea, goal, liveKPIs, daysShipped);
|
|
36178
36472
|
const TIMEOUT_MS = 12e4;
|
|
36179
|
-
const
|
|
36180
|
-
|
|
36181
|
-
|
|
36182
|
-
|
|
36183
|
-
|
|
36184
|
-
|
|
36185
|
-
claude.stdin.write(prompt);
|
|
36186
|
-
claude.stdin.end();
|
|
36187
|
-
let output = "";
|
|
36188
|
-
let isResolved = false;
|
|
36189
|
-
const timeout = setTimeout(() => {
|
|
36190
|
-
if (!isResolved) {
|
|
36191
|
-
isResolved = true;
|
|
36192
|
-
claude.kill("SIGTERM");
|
|
36193
|
-
log("Evaluation timed out");
|
|
36194
|
-
resolve2(null);
|
|
36195
|
-
}
|
|
36196
|
-
}, TIMEOUT_MS);
|
|
36197
|
-
claude.stdout.on("data", (data) => {
|
|
36198
|
-
output += data.toString();
|
|
36199
|
-
});
|
|
36200
|
-
claude.on("close", (code) => {
|
|
36201
|
-
if (isResolved) return;
|
|
36202
|
-
isResolved = true;
|
|
36203
|
-
clearTimeout(timeout);
|
|
36204
|
-
if (code === 0 && output.trim()) {
|
|
36205
|
-
const parsed = robustJsonParse(output, "Evaluation");
|
|
36206
|
-
if (parsed) {
|
|
36207
|
-
log(`Evaluation complete: status=${parsed.status}, confidence=${parsed.confidence}`);
|
|
36208
|
-
resolve2(parsed);
|
|
36209
|
-
return;
|
|
36210
|
-
}
|
|
36211
|
-
}
|
|
36212
|
-
resolve2(null);
|
|
36213
|
-
});
|
|
36214
|
-
claude.on("error", () => {
|
|
36215
|
-
if (!isResolved) {
|
|
36216
|
-
isResolved = true;
|
|
36217
|
-
clearTimeout(timeout);
|
|
36218
|
-
resolve2(null);
|
|
36219
|
-
}
|
|
36220
|
-
});
|
|
36473
|
+
const aiResult = await invokeAI({
|
|
36474
|
+
prompt,
|
|
36475
|
+
cwd: ROOT_DIR,
|
|
36476
|
+
timeoutMs: TIMEOUT_MS,
|
|
36477
|
+
useStdin: true,
|
|
36478
|
+
expectJson: true
|
|
36221
36479
|
});
|
|
36480
|
+
let result = null;
|
|
36481
|
+
if (!aiResult.error && aiResult.output.trim()) {
|
|
36482
|
+
const parsed = robustJsonParse(aiResult.output, "Evaluation");
|
|
36483
|
+
if (parsed) {
|
|
36484
|
+
log(`Evaluation complete (${aiResult.provider}): status=${parsed.status}, confidence=${parsed.confidence}`);
|
|
36485
|
+
result = parsed;
|
|
36486
|
+
}
|
|
36487
|
+
} else if (aiResult.error) {
|
|
36488
|
+
log(`Evaluation failed: ${aiResult.error}`);
|
|
36489
|
+
}
|
|
36222
36490
|
if (!result) {
|
|
36223
36491
|
log(`Evaluation failed for ${ideaId}`);
|
|
36224
36492
|
return false;
|
|
@@ -36570,6 +36838,14 @@ Respond with JSON only:
|
|
|
36570
36838
|
return tasks;
|
|
36571
36839
|
}
|
|
36572
36840
|
async function executeSingleResearchTask(task) {
|
|
36841
|
+
try {
|
|
36842
|
+
requireClaudeCLI("research task");
|
|
36843
|
+
} catch (err2) {
|
|
36844
|
+
task.status = "failed";
|
|
36845
|
+
task.error_message = err2.message;
|
|
36846
|
+
task.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
36847
|
+
return task;
|
|
36848
|
+
}
|
|
36573
36849
|
const TIMEOUT_MS = 3e5;
|
|
36574
36850
|
log(` Starting research task: ${task.type} - ${task.topic}`);
|
|
36575
36851
|
task.status = "running";
|
|
@@ -36685,30 +36961,21 @@ Respond with JSON only:
|
|
|
36685
36961
|
"recommendation": "proceed|revise|reject",
|
|
36686
36962
|
"recommendation_reasoning": "Why this recommendation"
|
|
36687
36963
|
}`;
|
|
36688
|
-
|
|
36689
|
-
|
|
36690
|
-
|
|
36691
|
-
|
|
36692
|
-
|
|
36693
|
-
|
|
36694
|
-
|
|
36695
|
-
|
|
36696
|
-
|
|
36697
|
-
|
|
36698
|
-
|
|
36699
|
-
|
|
36700
|
-
|
|
36701
|
-
|
|
36702
|
-
}
|
|
36703
|
-
claude.stdout.on("data", (data) => {
|
|
36704
|
-
output += data.toString();
|
|
36705
|
-
});
|
|
36706
|
-
claude.on("close", (code) => {
|
|
36707
|
-
clearTimeout(timeout);
|
|
36708
|
-
if (code === 0 && output.trim()) {
|
|
36709
|
-
const parsed = robustJsonParse(output, "ResearchAggregation");
|
|
36710
|
-
if (parsed) {
|
|
36711
|
-
research.summary = `**Executive Summary:** ${parsed.executive_summary}
|
|
36964
|
+
const aiResult = await invokeAI({
|
|
36965
|
+
prompt,
|
|
36966
|
+
cwd: ROOT_DIR,
|
|
36967
|
+
timeoutMs: TIMEOUT_MS,
|
|
36968
|
+
useStdin: true,
|
|
36969
|
+
expectJson: true
|
|
36970
|
+
});
|
|
36971
|
+
if (aiResult.error || !aiResult.output.trim()) {
|
|
36972
|
+
research.summary = findingsSummary;
|
|
36973
|
+
research.recommendation = "revise";
|
|
36974
|
+
return;
|
|
36975
|
+
}
|
|
36976
|
+
const parsed = robustJsonParse(aiResult.output, "ResearchAggregation");
|
|
36977
|
+
if (parsed) {
|
|
36978
|
+
research.summary = `**Executive Summary:** ${parsed.executive_summary}
|
|
36712
36979
|
|
|
36713
36980
|
**Key Insights:**
|
|
36714
36981
|
${parsed.key_insights.map((i) => `- ${i}`).join("\n")}
|
|
@@ -36717,25 +36984,12 @@ ${parsed.key_insights.map((i) => `- ${i}`).join("\n")}
|
|
|
36717
36984
|
${parsed.risks_identified?.map((r) => `- ${r}`).join("\n") || "None identified"}
|
|
36718
36985
|
|
|
36719
36986
|
**Reasoning:** ${parsed.recommendation_reasoning}`;
|
|
36720
|
-
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36724
|
-
|
|
36725
|
-
|
|
36726
|
-
} else {
|
|
36727
|
-
research.summary = findingsSummary;
|
|
36728
|
-
research.recommendation = "revise";
|
|
36729
|
-
}
|
|
36730
|
-
resolve2();
|
|
36731
|
-
});
|
|
36732
|
-
claude.on("error", () => {
|
|
36733
|
-
clearTimeout(timeout);
|
|
36734
|
-
research.summary = findingsSummary;
|
|
36735
|
-
research.recommendation = "revise";
|
|
36736
|
-
resolve2();
|
|
36737
|
-
});
|
|
36738
|
-
});
|
|
36987
|
+
research.recommendation = parsed.recommendation;
|
|
36988
|
+
log(`Research aggregation complete (${aiResult.provider}): ${research.recommendation}`);
|
|
36989
|
+
} else {
|
|
36990
|
+
research.summary = findingsSummary;
|
|
36991
|
+
research.recommendation = "revise";
|
|
36992
|
+
}
|
|
36739
36993
|
}
|
|
36740
36994
|
async function executeIdeaResearch(ideaId, config, businessContext) {
|
|
36741
36995
|
log(`Executing parallel research for idea: ${ideaId}`);
|
|
@@ -37004,6 +37258,20 @@ function generateStatusContent(state, alerts, nextTask, reasoning, systemHealth)
|
|
|
37004
37258
|
## System Health
|
|
37005
37259
|
|
|
37006
37260
|
${systemHealth.recovery_actions.map((a) => `- ${a}`).join("\n")}
|
|
37261
|
+
` : "";
|
|
37262
|
+
const rejections = (() => {
|
|
37263
|
+
try {
|
|
37264
|
+
return getRejectionCounts();
|
|
37265
|
+
} catch {
|
|
37266
|
+
return { branch: 0, repo: 0, commitMsg: 0 };
|
|
37267
|
+
}
|
|
37268
|
+
})();
|
|
37269
|
+
const hasRejections = rejections.branch > 0 || rejections.repo > 0 || rejections.commitMsg > 0;
|
|
37270
|
+
const securitySection = hasRejections ? `
|
|
37271
|
+
## Security
|
|
37272
|
+
|
|
37273
|
+
- Shell input rejections: {branch: ${rejections.branch}, repo: ${rejections.repo}, commitMsg: ${rejections.commitMsg}}
|
|
37274
|
+
- \u26A0\uFE0F Non-zero rejections detected \u2014 investigate source of malformed input
|
|
37007
37275
|
` : "";
|
|
37008
37276
|
const analysisSection = reasoning ? `**Analysis:** ${reasoning.analysis}
|
|
37009
37277
|
|
|
@@ -37052,7 +37320,7 @@ ${pendingRows}
|
|
|
37052
37320
|
## Alerts
|
|
37053
37321
|
|
|
37054
37322
|
${alertsSection}
|
|
37055
|
-
${healthSection}
|
|
37323
|
+
${healthSection}${securitySection}
|
|
37056
37324
|
## Recent Activity
|
|
37057
37325
|
|
|
37058
37326
|
| Date | Event |
|
|
@@ -37067,6 +37335,7 @@ ${activityRows}
|
|
|
37067
37335
|
async function runSingleHeartbeat(beatNumber = 1) {
|
|
37068
37336
|
const startTime = Date.now();
|
|
37069
37337
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
37338
|
+
resetRejectionCounts();
|
|
37070
37339
|
capturedLogs = [];
|
|
37071
37340
|
isCapturingLogs = true;
|
|
37072
37341
|
try {
|