vibebusiness 1.2.89 → 1.2.91
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/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +37 -37
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- 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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/briefing/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]/card/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.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 +4 -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.nft.json +1 -1
- 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/briefing/page_client-reference-manifest.js +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.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/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +1 -1
- package/.next/standalone/.next/server/app/kanban/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/kanban/page_client-reference-manifest.js +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/investors/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/investors/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/roadmap/public/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/roadmap/public/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_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +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/updates/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/updates/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/updates/new/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/updates/new.html +1 -1
- package/.next/standalone/.next/server/app/updates/new.rsc +2 -2
- package/.next/standalone/.next/server/app/updates/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/updates/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +17 -17
- package/.next/standalone/.next/server/chunks/7151.js +7 -7
- 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/pages-manifest.json +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/data/codebase-snapshot.json +44 -61
- package/.next/standalone/data/ideas.json +729 -0
- package/.next/standalone/data/sessions.json +32 -0
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/analyze.ts +35 -4
- package/.next/standalone/templates/commands/growth-activate.md +64 -0
- package/.next/standalone/templates/commands/vision.md +68 -0
- package/.next/static/chunks/147-271b001e9a7f5e6c.js +1 -0
- package/.next/static/chunks/159-82cfa8b54cbd2c11.js +1 -0
- package/.next/static/chunks/47-bab84ed5cfb8fd9d.js +1 -0
- package/.next/static/chunks/879-ae588e66e8e0b1d0.js +1 -0
- package/.next/static/chunks/app/briefing/page-a38c83adfb5ef570.js +1 -0
- package/.next/static/chunks/app/goals/[id]/page-ce4e225f25e7294d.js +1 -0
- package/.next/static/chunks/app/ideas/[id]/page-6b28cf279c62a569.js +1 -0
- package/.next/static/chunks/app/roadmap/[id]/page-381809badf3910a3.js +1 -0
- package/.next/static/chunks/app/social/page-2d71af5da2476063.js +1 -0
- package/.next/static/chunks/app/updates/new/page-0acea8b7d66043a5.js +1 -0
- package/dist/scripts/analyze.js +525 -76
- package/dist/scripts/chat.js +10 -0
- package/dist/scripts/generate-idea.js +3 -0
- package/dist/scripts/heartbeat.js +1896 -353
- package/dist/scripts/implement.js +3 -0
- package/dist/scripts/init.js +3 -0
- package/dist/scripts/scan.js +3 -0
- package/dist/scripts/social-routine.js +3 -0
- package/package.json +1 -1
- package/templates/commands/growth-activate.md +64 -0
- package/templates/commands/vision.md +68 -0
- package/.next/static/chunks/147-b00f2ac2bbec93ae.js +0 -1
- package/.next/static/chunks/159-4ce492ccac6de8f7.js +0 -1
- package/.next/static/chunks/47-eba0f8b4f9b17641.js +0 -1
- package/.next/static/chunks/879-7fbd95e93ddc4636.js +0 -1
- package/.next/static/chunks/app/briefing/page-683aba0c52e910d7.js +0 -1
- package/.next/static/chunks/app/goals/[id]/page-40818dc7e710eeda.js +0 -1
- package/.next/static/chunks/app/ideas/[id]/page-5f33e0ecf590ddec.js +0 -1
- package/.next/static/chunks/app/roadmap/[id]/page-9ba8a537e30c633c.js +0 -1
- package/.next/static/chunks/app/social/page-21daeca0cf8af46b.js +0 -1
- package/.next/static/chunks/app/updates/new/page-ac5b966024ce0ddc.js +0 -1
- /package/.next/static/{UaMf1fmArAE78vyk_zQVK → ixOS0rCuseEALBC-pPhha}/_buildManifest.js +0 -0
- /package/.next/static/{UaMf1fmArAE78vyk_zQVK → ixOS0rCuseEALBC-pPhha}/_ssgManifest.js +0 -0
|
@@ -105,8 +105,8 @@ var require_package = __commonJS({
|
|
|
105
105
|
var require_main = __commonJS({
|
|
106
106
|
"node_modules/dotenv/lib/main.js"(exports2, module2) {
|
|
107
107
|
"use strict";
|
|
108
|
-
var
|
|
109
|
-
var
|
|
108
|
+
var fs43 = require("fs");
|
|
109
|
+
var path35 = require("path");
|
|
110
110
|
var os3 = require("os");
|
|
111
111
|
var crypto5 = require("crypto");
|
|
112
112
|
var packageJson = require_package();
|
|
@@ -244,7 +244,7 @@ var require_main = __commonJS({
|
|
|
244
244
|
if (options && options.path && options.path.length > 0) {
|
|
245
245
|
if (Array.isArray(options.path)) {
|
|
246
246
|
for (const filepath of options.path) {
|
|
247
|
-
if (
|
|
247
|
+
if (fs43.existsSync(filepath)) {
|
|
248
248
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -252,15 +252,15 @@ var require_main = __commonJS({
|
|
|
252
252
|
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
253
253
|
}
|
|
254
254
|
} else {
|
|
255
|
-
possibleVaultPath =
|
|
255
|
+
possibleVaultPath = path35.resolve(process.cwd(), ".env.vault");
|
|
256
256
|
}
|
|
257
|
-
if (
|
|
257
|
+
if (fs43.existsSync(possibleVaultPath)) {
|
|
258
258
|
return possibleVaultPath;
|
|
259
259
|
}
|
|
260
260
|
return null;
|
|
261
261
|
}
|
|
262
262
|
function _resolveHome(envPath) {
|
|
263
|
-
return envPath[0] === "~" ?
|
|
263
|
+
return envPath[0] === "~" ? path35.join(os3.homedir(), envPath.slice(1)) : envPath;
|
|
264
264
|
}
|
|
265
265
|
function _configVault(options) {
|
|
266
266
|
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
@@ -277,7 +277,7 @@ var require_main = __commonJS({
|
|
|
277
277
|
return { parsed };
|
|
278
278
|
}
|
|
279
279
|
function configDotenv(options) {
|
|
280
|
-
const dotenvPath =
|
|
280
|
+
const dotenvPath = path35.resolve(process.cwd(), ".env");
|
|
281
281
|
let encoding = "utf8";
|
|
282
282
|
let processEnv = process.env;
|
|
283
283
|
if (options && options.processEnv != null) {
|
|
@@ -305,13 +305,13 @@ var require_main = __commonJS({
|
|
|
305
305
|
}
|
|
306
306
|
let lastError;
|
|
307
307
|
const parsedAll = {};
|
|
308
|
-
for (const
|
|
308
|
+
for (const path36 of optionPaths) {
|
|
309
309
|
try {
|
|
310
|
-
const parsed = DotenvModule.parse(
|
|
310
|
+
const parsed = DotenvModule.parse(fs43.readFileSync(path36, { encoding }));
|
|
311
311
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
312
312
|
} catch (e) {
|
|
313
313
|
if (debug) {
|
|
314
|
-
_debug(`Failed to load ${
|
|
314
|
+
_debug(`Failed to load ${path36} ${e.message}`);
|
|
315
315
|
}
|
|
316
316
|
lastError = e;
|
|
317
317
|
}
|
|
@@ -324,7 +324,7 @@ var require_main = __commonJS({
|
|
|
324
324
|
const shortPaths = [];
|
|
325
325
|
for (const filePath of optionPaths) {
|
|
326
326
|
try {
|
|
327
|
-
const relative =
|
|
327
|
+
const relative = path35.relative(process.cwd(), filePath);
|
|
328
328
|
shortPaths.push(relative);
|
|
329
329
|
} catch (e) {
|
|
330
330
|
if (debug) {
|
|
@@ -535,7 +535,7 @@ function findProjectRoot() {
|
|
|
535
535
|
}
|
|
536
536
|
return cwd;
|
|
537
537
|
}
|
|
538
|
-
var path2, fs3, PROJECT_DIR, DATA_DIR, STATUS_FILE, TODO_FILE, MEMORY_FILE, IDEAS_FILE, GOALS_FILE, SESSIONS_FILE, CONFIG_FILE, BUSINESS_CONTEXT_FILE, HYPOTHESES_FILE, IMPLEMENTATIONS_FILE, ROADMAP_FILE, COMPETITORS_FILE, POSITIONING_FILE, PAGES_FILE, PAYMENTS_FILE, POSTHOG_FILE, SOCIAL_FILE, TEMPLATES_DIR, COMMAND_TEMPLATES_DIR;
|
|
538
|
+
var path2, fs3, PROJECT_DIR, DATA_DIR, STATUS_FILE, TODO_FILE, MEMORY_FILE, IDEAS_FILE, GOALS_FILE, SESSIONS_FILE, CONFIG_FILE, BUSINESS_CONTEXT_FILE, HYPOTHESES_FILE, IMPLEMENTATIONS_FILE, ROADMAP_FILE, PRODUCT_VISION_FILE, COMPETITORS_FILE, POSITIONING_FILE, PAGES_FILE, PAYMENTS_FILE, POSTHOG_FILE, SOCIAL_FILE, MARKETING_STRATEGIES_FILE, CAMPAIGNS_DIR, TEMPLATES_DIR, COMMAND_TEMPLATES_DIR;
|
|
539
539
|
var init_paths = __esm({
|
|
540
540
|
"scripts/lib/paths.ts"() {
|
|
541
541
|
"use strict";
|
|
@@ -554,12 +554,15 @@ var init_paths = __esm({
|
|
|
554
554
|
HYPOTHESES_FILE = path2.join(DATA_DIR, "hypotheses.json");
|
|
555
555
|
IMPLEMENTATIONS_FILE = path2.join(DATA_DIR, "implementations.json");
|
|
556
556
|
ROADMAP_FILE = path2.join(DATA_DIR, "roadmap.json");
|
|
557
|
+
PRODUCT_VISION_FILE = path2.join(DATA_DIR, "product-vision.json");
|
|
557
558
|
COMPETITORS_FILE = path2.join(DATA_DIR, "competitors.json");
|
|
558
559
|
POSITIONING_FILE = path2.join(DATA_DIR, "positioning.json");
|
|
559
560
|
PAGES_FILE = path2.join(DATA_DIR, "pages.json");
|
|
560
561
|
PAYMENTS_FILE = path2.join(DATA_DIR, "payments.json");
|
|
561
562
|
POSTHOG_FILE = path2.join(DATA_DIR, "posthog.json");
|
|
562
563
|
SOCIAL_FILE = path2.join(DATA_DIR, "social.json");
|
|
564
|
+
MARKETING_STRATEGIES_FILE = path2.join(DATA_DIR, "marketing-strategies.json");
|
|
565
|
+
CAMPAIGNS_DIR = path2.join(DATA_DIR, "campaigns");
|
|
563
566
|
TEMPLATES_DIR = path2.join(__dirname, "..", "..", "templates");
|
|
564
567
|
COMMAND_TEMPLATES_DIR = path2.join(TEMPLATES_DIR, "commands");
|
|
565
568
|
}
|
|
@@ -3786,12 +3789,12 @@ var init_request_handler_helper = __esm({
|
|
|
3786
3789
|
this.req.destroy(new Error("Request timeout."));
|
|
3787
3790
|
}
|
|
3788
3791
|
/* Response event handlers */
|
|
3789
|
-
classicResponseHandler(
|
|
3792
|
+
classicResponseHandler(resolve4, reject, res) {
|
|
3790
3793
|
this.res = res;
|
|
3791
3794
|
const dataStream = this.getResponseDataStream(res);
|
|
3792
3795
|
dataStream.on("data", (chunk) => this.responseData.push(chunk));
|
|
3793
|
-
dataStream.on("end", this.onResponseEndHandler.bind(this,
|
|
3794
|
-
dataStream.on("close", this.onResponseCloseHandler.bind(this,
|
|
3796
|
+
dataStream.on("end", this.onResponseEndHandler.bind(this, resolve4, reject));
|
|
3797
|
+
dataStream.on("close", this.onResponseCloseHandler.bind(this, resolve4, reject));
|
|
3795
3798
|
if (this.requestData.requestEventDebugHandler) {
|
|
3796
3799
|
this.requestData.requestEventDebugHandler("response", { res });
|
|
3797
3800
|
res.on("aborted", (error) => this.requestData.requestEventDebugHandler("response-aborted", { error }));
|
|
@@ -3800,7 +3803,7 @@ var init_request_handler_helper = __esm({
|
|
|
3800
3803
|
res.on("end", () => this.requestData.requestEventDebugHandler("response-end"));
|
|
3801
3804
|
}
|
|
3802
3805
|
}
|
|
3803
|
-
onResponseEndHandler(
|
|
3806
|
+
onResponseEndHandler(resolve4, reject) {
|
|
3804
3807
|
const rateLimit = this.getRateLimitFromResponse(this.res);
|
|
3805
3808
|
let data;
|
|
3806
3809
|
try {
|
|
@@ -3818,18 +3821,18 @@ var init_request_handler_helper = __esm({
|
|
|
3818
3821
|
TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${this.res.statusCode}`);
|
|
3819
3822
|
TwitterApiV2Settings.logger.log("Response body:", data);
|
|
3820
3823
|
}
|
|
3821
|
-
|
|
3824
|
+
resolve4({
|
|
3822
3825
|
data,
|
|
3823
3826
|
headers: this.res.headers,
|
|
3824
3827
|
rateLimit
|
|
3825
3828
|
});
|
|
3826
3829
|
}
|
|
3827
|
-
onResponseCloseHandler(
|
|
3830
|
+
onResponseCloseHandler(resolve4, reject) {
|
|
3828
3831
|
const res = this.res;
|
|
3829
3832
|
if (res.aborted) {
|
|
3830
3833
|
try {
|
|
3831
3834
|
this.getParsedResponse(this.res);
|
|
3832
|
-
return this.onResponseEndHandler(
|
|
3835
|
+
return this.onResponseEndHandler(resolve4, reject);
|
|
3833
3836
|
} catch (e) {
|
|
3834
3837
|
return reject(this.createPartialResponseError(e, true));
|
|
3835
3838
|
}
|
|
@@ -3838,14 +3841,14 @@ var init_request_handler_helper = __esm({
|
|
|
3838
3841
|
return reject(this.createPartialResponseError(new Error("Response has been interrupted before response could be parsed."), true));
|
|
3839
3842
|
}
|
|
3840
3843
|
}
|
|
3841
|
-
streamResponseHandler(
|
|
3844
|
+
streamResponseHandler(resolve4, reject, res) {
|
|
3842
3845
|
const code = res.statusCode;
|
|
3843
3846
|
if (code < 400) {
|
|
3844
3847
|
if (TwitterApiV2Settings.debug) {
|
|
3845
3848
|
TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode} (starting stream)`);
|
|
3846
3849
|
}
|
|
3847
3850
|
const dataStream = this.getResponseDataStream(res);
|
|
3848
|
-
|
|
3851
|
+
resolve4({ req: this.req, res: dataStream, originalResponse: res, requestData: this.requestData });
|
|
3849
3852
|
} else {
|
|
3850
3853
|
this.classicResponseHandler(() => void 0, reject, res);
|
|
3851
3854
|
}
|
|
@@ -3903,7 +3906,7 @@ var init_request_handler_helper = __esm({
|
|
|
3903
3906
|
makeRequest() {
|
|
3904
3907
|
this.buildRequest();
|
|
3905
3908
|
return new Promise((_resolve, _reject) => {
|
|
3906
|
-
const
|
|
3909
|
+
const resolve4 = (value) => {
|
|
3907
3910
|
cleanupListener.emit("complete");
|
|
3908
3911
|
_resolve(value);
|
|
3909
3912
|
};
|
|
@@ -3915,7 +3918,7 @@ var init_request_handler_helper = __esm({
|
|
|
3915
3918
|
const req = this.req;
|
|
3916
3919
|
req.on("error", this.requestErrorHandler.bind(this, reject));
|
|
3917
3920
|
req.on("socket", this.onSocketEventHandler.bind(this, reject, cleanupListener));
|
|
3918
|
-
req.on("response", this.classicResponseHandler.bind(this,
|
|
3921
|
+
req.on("response", this.classicResponseHandler.bind(this, resolve4, reject));
|
|
3919
3922
|
if (this.requestData.options.timeout) {
|
|
3920
3923
|
req.on("timeout", this.timeoutErrorHandler.bind(this));
|
|
3921
3924
|
}
|
|
@@ -3934,10 +3937,10 @@ var init_request_handler_helper = __esm({
|
|
|
3934
3937
|
}
|
|
3935
3938
|
makeRequestAndResolveWhenReady() {
|
|
3936
3939
|
this.buildRequest();
|
|
3937
|
-
return new Promise((
|
|
3940
|
+
return new Promise((resolve4, reject) => {
|
|
3938
3941
|
const req = this.req;
|
|
3939
3942
|
req.on("error", this.requestErrorHandler.bind(this, reject));
|
|
3940
|
-
req.on("response", this.streamResponseHandler.bind(this,
|
|
3943
|
+
req.on("response", this.streamResponseHandler.bind(this, resolve4, reject));
|
|
3941
3944
|
if (this.requestData.body) {
|
|
3942
3945
|
req.write(this.requestData.body);
|
|
3943
3946
|
}
|
|
@@ -6296,12 +6299,12 @@ var init_client_v1_read = __esm({
|
|
|
6296
6299
|
async function readFileIntoBuffer(file) {
|
|
6297
6300
|
const handle = await getFileHandle(file);
|
|
6298
6301
|
if (typeof handle === "number") {
|
|
6299
|
-
return new Promise((
|
|
6302
|
+
return new Promise((resolve4, reject) => {
|
|
6300
6303
|
fs13.readFile(handle, (err2, data) => {
|
|
6301
6304
|
if (err2) {
|
|
6302
6305
|
return reject(err2);
|
|
6303
6306
|
}
|
|
6304
|
-
|
|
6307
|
+
resolve4(data);
|
|
6305
6308
|
});
|
|
6306
6309
|
});
|
|
6307
6310
|
} else if (handle instanceof Buffer) {
|
|
@@ -6325,11 +6328,11 @@ function getFileHandle(file) {
|
|
|
6325
6328
|
}
|
|
6326
6329
|
async function getFileSizeFromFileHandle(fileHandle) {
|
|
6327
6330
|
if (typeof fileHandle === "number") {
|
|
6328
|
-
const stats = await new Promise((
|
|
6331
|
+
const stats = await new Promise((resolve4, reject) => {
|
|
6329
6332
|
fs13.fstat(fileHandle, (err2, stats2) => {
|
|
6330
6333
|
if (err2)
|
|
6331
6334
|
reject(err2);
|
|
6332
|
-
|
|
6335
|
+
resolve4(stats2);
|
|
6333
6336
|
});
|
|
6334
6337
|
});
|
|
6335
6338
|
return stats.size;
|
|
@@ -6406,7 +6409,7 @@ function getMediaCategoryByMime(name, target) {
|
|
|
6406
6409
|
return target === "tweet" ? "TweetImage" : "DmImage";
|
|
6407
6410
|
}
|
|
6408
6411
|
function sleepSecs(seconds) {
|
|
6409
|
-
return new Promise((
|
|
6412
|
+
return new Promise((resolve4) => setTimeout(resolve4, seconds * 1e3));
|
|
6410
6413
|
}
|
|
6411
6414
|
async function readNextPartOf(file, chunkLength, bufferOffset = 0, buffer) {
|
|
6412
6415
|
if (file instanceof Buffer) {
|
|
@@ -6418,11 +6421,11 @@ async function readNextPartOf(file, chunkLength, bufferOffset = 0, buffer) {
|
|
|
6418
6421
|
}
|
|
6419
6422
|
let bytesRead;
|
|
6420
6423
|
if (typeof file === "number") {
|
|
6421
|
-
bytesRead = await new Promise((
|
|
6424
|
+
bytesRead = await new Promise((resolve4, reject) => {
|
|
6422
6425
|
fs13.read(file, buffer, 0, chunkLength, bufferOffset, (err2, nread) => {
|
|
6423
6426
|
if (err2)
|
|
6424
6427
|
reject(err2);
|
|
6425
|
-
|
|
6428
|
+
resolve4(nread);
|
|
6426
6429
|
});
|
|
6427
6430
|
});
|
|
6428
6431
|
} else {
|
|
@@ -8338,7 +8341,7 @@ var init_client_v2_read = __esm({
|
|
|
8338
8341
|
if (runningJob.status === "expired" || runningJob.status === "failed") {
|
|
8339
8342
|
throw new Error("Job failed to be completed.");
|
|
8340
8343
|
}
|
|
8341
|
-
await new Promise((
|
|
8344
|
+
await new Promise((resolve4) => setTimeout(resolve4, 3500));
|
|
8342
8345
|
runningJob = (await this.complianceJob(job.id)).data;
|
|
8343
8346
|
}
|
|
8344
8347
|
const result = await this.get(job.download_url, void 0, {
|
|
@@ -8547,7 +8550,7 @@ var init_client_v2_write = __esm({
|
|
|
8547
8550
|
case "in_progress": {
|
|
8548
8551
|
const waitTime = info === null || info === void 0 ? void 0 : info.check_after_secs;
|
|
8549
8552
|
if (waitTime && waitTime > 0) {
|
|
8550
|
-
await new Promise((
|
|
8553
|
+
await new Promise((resolve4) => setTimeout(resolve4, waitTime * 1e3));
|
|
8551
8554
|
await this.waitForMediaProcessing(mediaId);
|
|
8552
8555
|
}
|
|
8553
8556
|
}
|
|
@@ -9579,7 +9582,7 @@ function invokeClaudeCLI(options, claudePath) {
|
|
|
9579
9582
|
}
|
|
9580
9583
|
const startTime = Date.now();
|
|
9581
9584
|
const timeoutMs = options.timeoutMs || 3e5;
|
|
9582
|
-
return new Promise((
|
|
9585
|
+
return new Promise((resolve4) => {
|
|
9583
9586
|
const child = (0, import_child_process6.spawn)(bin, args2, {
|
|
9584
9587
|
cwd: options.cwd || process.cwd(),
|
|
9585
9588
|
env: process.env,
|
|
@@ -9599,7 +9602,7 @@ function invokeClaudeCLI(options, claudePath) {
|
|
|
9599
9602
|
}
|
|
9600
9603
|
const timeout = setTimeout(() => {
|
|
9601
9604
|
child.kill("SIGTERM");
|
|
9602
|
-
|
|
9605
|
+
resolve4({
|
|
9603
9606
|
output: stdout,
|
|
9604
9607
|
provider: "claude-cli",
|
|
9605
9608
|
durationMs: Date.now() - startTime,
|
|
@@ -9608,7 +9611,7 @@ function invokeClaudeCLI(options, claudePath) {
|
|
|
9608
9611
|
}, timeoutMs);
|
|
9609
9612
|
child.on("close", (code) => {
|
|
9610
9613
|
clearTimeout(timeout);
|
|
9611
|
-
|
|
9614
|
+
resolve4({
|
|
9612
9615
|
output: stdout.trim(),
|
|
9613
9616
|
provider: "claude-cli",
|
|
9614
9617
|
durationMs: Date.now() - startTime,
|
|
@@ -9617,7 +9620,7 @@ function invokeClaudeCLI(options, claudePath) {
|
|
|
9617
9620
|
});
|
|
9618
9621
|
child.on("error", (err2) => {
|
|
9619
9622
|
clearTimeout(timeout);
|
|
9620
|
-
|
|
9623
|
+
resolve4({
|
|
9621
9624
|
output: "",
|
|
9622
9625
|
provider: "claude-cli",
|
|
9623
9626
|
durationMs: Date.now() - startTime,
|
|
@@ -9865,10 +9868,10 @@ __export(screenshot_exports, {
|
|
|
9865
9868
|
captureIdeaScreenshot: () => captureIdeaScreenshot
|
|
9866
9869
|
});
|
|
9867
9870
|
async function captureIdeaScreenshot(ideaId, previewUrl, options) {
|
|
9868
|
-
const outputDir = options?.outputDir ??
|
|
9871
|
+
const outputDir = options?.outputDir ?? path32.join(DATA_DIR, "reports");
|
|
9869
9872
|
const timeoutMs = options?.timeoutMs ?? 15e3;
|
|
9870
|
-
const outputPath =
|
|
9871
|
-
if (
|
|
9873
|
+
const outputPath = path32.join(outputDir, `${ideaId}-screenshot.png`);
|
|
9874
|
+
if (fs40.existsSync(outputPath)) return outputPath;
|
|
9872
9875
|
try {
|
|
9873
9876
|
const controller = new AbortController();
|
|
9874
9877
|
const probeTimeout = setTimeout(() => controller.abort(), 3e3);
|
|
@@ -9888,7 +9891,7 @@ async function captureIdeaScreenshot(ideaId, previewUrl, options) {
|
|
|
9888
9891
|
});
|
|
9889
9892
|
await page.goto(previewUrl, { waitUntil: "networkidle", timeout: timeoutMs });
|
|
9890
9893
|
await page.waitForTimeout(500);
|
|
9891
|
-
|
|
9894
|
+
fs40.mkdirSync(outputDir, { recursive: true });
|
|
9892
9895
|
await page.screenshot({ path: outputPath });
|
|
9893
9896
|
return outputPath;
|
|
9894
9897
|
} finally {
|
|
@@ -9898,12 +9901,12 @@ async function captureIdeaScreenshot(ideaId, previewUrl, options) {
|
|
|
9898
9901
|
return null;
|
|
9899
9902
|
}
|
|
9900
9903
|
}
|
|
9901
|
-
var
|
|
9904
|
+
var fs40, path32;
|
|
9902
9905
|
var init_screenshot = __esm({
|
|
9903
9906
|
"scripts/lib/screenshot.ts"() {
|
|
9904
9907
|
"use strict";
|
|
9905
|
-
|
|
9906
|
-
|
|
9908
|
+
fs40 = __toESM(require("fs"));
|
|
9909
|
+
path32 = __toESM(require("path"));
|
|
9907
9910
|
init_paths();
|
|
9908
9911
|
}
|
|
9909
9912
|
});
|
|
@@ -9915,10 +9918,10 @@ __export(marketing_integration_exports, {
|
|
|
9915
9918
|
integrateMarketingPlanToUI: () => integrateMarketingPlanToUI
|
|
9916
9919
|
});
|
|
9917
9920
|
async function addMarketingIdeasToDashboard(plan, dataDir) {
|
|
9918
|
-
const ideasPath =
|
|
9921
|
+
const ideasPath = path33.join(dataDir, "ideas.json");
|
|
9919
9922
|
let ideasData = { ideas: [] };
|
|
9920
|
-
if (
|
|
9921
|
-
ideasData = JSON.parse(
|
|
9923
|
+
if (fs41.existsSync(ideasPath)) {
|
|
9924
|
+
ideasData = JSON.parse(fs41.readFileSync(ideasPath, "utf-8"));
|
|
9922
9925
|
}
|
|
9923
9926
|
const newIdeasPartial = generateIdeasFromPlan(plan);
|
|
9924
9927
|
let added = 0;
|
|
@@ -9969,7 +9972,7 @@ async function addMarketingIdeasToDashboard(plan, dataDir) {
|
|
|
9969
9972
|
ideasData.ideas.push(newIdea);
|
|
9970
9973
|
added++;
|
|
9971
9974
|
}
|
|
9972
|
-
|
|
9975
|
+
fs41.writeFileSync(ideasPath, JSON.stringify(ideasData, null, 2));
|
|
9973
9976
|
return { added, skipped };
|
|
9974
9977
|
}
|
|
9975
9978
|
function generateIdeaId() {
|
|
@@ -9978,23 +9981,23 @@ function generateIdeaId() {
|
|
|
9978
9981
|
return `idea-${timestamp}-${random}`;
|
|
9979
9982
|
}
|
|
9980
9983
|
async function integrateMarketingPlanToUI(planId, dataDir) {
|
|
9981
|
-
const planPath =
|
|
9982
|
-
if (!
|
|
9984
|
+
const planPath = path33.join(dataDir, "marketing-plans", `${planId}.json`);
|
|
9985
|
+
if (!fs41.existsSync(planPath)) {
|
|
9983
9986
|
throw new Error(`Plan ${planId} not found`);
|
|
9984
9987
|
}
|
|
9985
|
-
const plan = JSON.parse(
|
|
9988
|
+
const plan = JSON.parse(fs41.readFileSync(planPath, "utf-8"));
|
|
9986
9989
|
const result = await addMarketingIdeasToDashboard(plan, dataDir);
|
|
9987
9990
|
console.log(`\u2713 Added ${result.added} marketing ideas to dashboard`);
|
|
9988
9991
|
if (result.skipped > 0) {
|
|
9989
9992
|
console.log(` (Skipped ${result.skipped} duplicate ideas)`);
|
|
9990
9993
|
}
|
|
9991
9994
|
}
|
|
9992
|
-
var
|
|
9995
|
+
var fs41, path33;
|
|
9993
9996
|
var init_marketing_integration = __esm({
|
|
9994
9997
|
"scripts/skills/marketing-integration.ts"() {
|
|
9995
9998
|
"use strict";
|
|
9996
|
-
|
|
9997
|
-
|
|
9999
|
+
fs41 = __toESM(require("fs"));
|
|
10000
|
+
path33 = __toESM(require("path"));
|
|
9998
10001
|
init_marketing_strategy();
|
|
9999
10002
|
}
|
|
10000
10003
|
});
|
|
@@ -10011,9 +10014,9 @@ var init_marketing_integration = __esm({
|
|
|
10011
10014
|
})();
|
|
10012
10015
|
|
|
10013
10016
|
// scripts/heartbeat.ts
|
|
10014
|
-
var
|
|
10015
|
-
var
|
|
10016
|
-
var
|
|
10017
|
+
var fs42 = __toESM(require("fs"));
|
|
10018
|
+
var path34 = __toESM(require("path"));
|
|
10019
|
+
var import_child_process13 = require("child_process");
|
|
10017
10020
|
var import_util = require("util");
|
|
10018
10021
|
|
|
10019
10022
|
// src/lib/kpi-adapters/rest-api.ts
|
|
@@ -13634,8 +13637,8 @@ function argument(predicate, message) {
|
|
|
13634
13637
|
}
|
|
13635
13638
|
}
|
|
13636
13639
|
var check = { fail, argument, assert: argument };
|
|
13637
|
-
function getPathDefinition(glyph,
|
|
13638
|
-
var _path =
|
|
13640
|
+
function getPathDefinition(glyph, path35) {
|
|
13641
|
+
var _path = path35 || new Path();
|
|
13639
13642
|
return {
|
|
13640
13643
|
configurable: true,
|
|
13641
13644
|
get: function() {
|
|
@@ -13858,9 +13861,9 @@ function ttfGlyphLoader(font, index, parseGlyph2, data, position, buildPath2) {
|
|
|
13858
13861
|
var glyph = new Glyph({ index, font });
|
|
13859
13862
|
glyph.path = function() {
|
|
13860
13863
|
parseGlyph2(glyph, data, position);
|
|
13861
|
-
var
|
|
13862
|
-
|
|
13863
|
-
return
|
|
13864
|
+
var path35 = buildPath2(font.glyphs, glyph);
|
|
13865
|
+
path35.unitsPerEm = font.unitsPerEm;
|
|
13866
|
+
return path35;
|
|
13864
13867
|
};
|
|
13865
13868
|
defineDependentProperty(glyph, "xMin", "_xMin");
|
|
13866
13869
|
defineDependentProperty(glyph, "xMax", "_xMax");
|
|
@@ -13873,9 +13876,9 @@ function cffGlyphLoader(font, index, parseCFFCharstring2, charstring) {
|
|
|
13873
13876
|
return function() {
|
|
13874
13877
|
var glyph = new Glyph({ index, font });
|
|
13875
13878
|
glyph.path = function() {
|
|
13876
|
-
var
|
|
13877
|
-
|
|
13878
|
-
return
|
|
13879
|
+
var path35 = parseCFFCharstring2(font, glyph, charstring);
|
|
13880
|
+
path35.unitsPerEm = font.unitsPerEm;
|
|
13881
|
+
return path35;
|
|
13879
13882
|
};
|
|
13880
13883
|
return glyph;
|
|
13881
13884
|
};
|
|
@@ -30994,8 +30997,8 @@ async function postTweet(client, text) {
|
|
|
30994
30997
|
};
|
|
30995
30998
|
}
|
|
30996
30999
|
async function uploadMedia(client, filePath) {
|
|
30997
|
-
const
|
|
30998
|
-
if (!
|
|
31000
|
+
const fs43 = require("fs");
|
|
31001
|
+
if (!fs43.existsSync(filePath)) {
|
|
30999
31002
|
throw new Error(`Media file not found: ${filePath}`);
|
|
31000
31003
|
}
|
|
31001
31004
|
const mediaId = await client.v1.uploadMedia(filePath);
|
|
@@ -33662,7 +33665,7 @@ function readPromoVideoFreshness() {
|
|
|
33662
33665
|
var fs23 = __toESM(require("fs"));
|
|
33663
33666
|
var path17 = __toESM(require("path"));
|
|
33664
33667
|
init_paths();
|
|
33665
|
-
var
|
|
33668
|
+
var CAMPAIGNS_DIR2 = path17.join(DATA_DIR, "campaigns");
|
|
33666
33669
|
var CAMPAIGN_PREFIXES = [
|
|
33667
33670
|
"campaign-launch-",
|
|
33668
33671
|
"campaign-milestone-",
|
|
@@ -33699,14 +33702,14 @@ function detectChannels(businessContext) {
|
|
|
33699
33702
|
return channels;
|
|
33700
33703
|
}
|
|
33701
33704
|
function saveCampaign(campaign) {
|
|
33702
|
-
fs23.mkdirSync(
|
|
33703
|
-
const filePath = path17.join(
|
|
33705
|
+
fs23.mkdirSync(CAMPAIGNS_DIR2, { recursive: true });
|
|
33706
|
+
const filePath = path17.join(CAMPAIGNS_DIR2, `${campaign.id}.json`);
|
|
33704
33707
|
fs23.writeFileSync(filePath, JSON.stringify(campaign, null, 2) + "\n");
|
|
33705
33708
|
}
|
|
33706
33709
|
function loadCampaigns() {
|
|
33707
|
-
if (!fs23.existsSync(
|
|
33710
|
+
if (!fs23.existsSync(CAMPAIGNS_DIR2)) return [];
|
|
33708
33711
|
try {
|
|
33709
|
-
return fs23.readdirSync(
|
|
33712
|
+
return fs23.readdirSync(CAMPAIGNS_DIR2).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs23.readFileSync(path17.join(CAMPAIGNS_DIR2, f), "utf-8"))).sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
33710
33713
|
} catch {
|
|
33711
33714
|
return [];
|
|
33712
33715
|
}
|
|
@@ -33970,16 +33973,1087 @@ function readCampaignFreshness() {
|
|
|
33970
33973
|
};
|
|
33971
33974
|
}
|
|
33972
33975
|
|
|
33976
|
+
// scripts/skills/growth-campaign.ts
|
|
33977
|
+
var fs26 = __toESM(require("fs"));
|
|
33978
|
+
var path20 = __toESM(require("path"));
|
|
33979
|
+
init_paths();
|
|
33980
|
+
init_ai_provider();
|
|
33981
|
+
|
|
33982
|
+
// scripts/lib/growth/campaign-state.ts
|
|
33983
|
+
var fs24 = __toESM(require("fs"));
|
|
33984
|
+
var path18 = __toESM(require("path"));
|
|
33985
|
+
init_paths();
|
|
33986
|
+
function createGrowthCampaign(params) {
|
|
33987
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
33988
|
+
const campaign = {
|
|
33989
|
+
id: `growth-${params.ideaId}-${today}`,
|
|
33990
|
+
type: "growth",
|
|
33991
|
+
source_idea_id: params.ideaId,
|
|
33992
|
+
source_idea_title: params.ideaTitle,
|
|
33993
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33994
|
+
status: "researching",
|
|
33995
|
+
product: params.product,
|
|
33996
|
+
research: null,
|
|
33997
|
+
channels: params.channels,
|
|
33998
|
+
sub_tasks: params.subTasks,
|
|
33999
|
+
utm_campaign: `growth-${params.ideaId}-${today}`
|
|
34000
|
+
};
|
|
34001
|
+
fs24.mkdirSync(CAMPAIGNS_DIR, { recursive: true });
|
|
34002
|
+
const filePath = path18.join(CAMPAIGNS_DIR, `${campaign.id}.json`);
|
|
34003
|
+
fs24.writeFileSync(filePath, JSON.stringify(campaign, null, 2) + "\n");
|
|
34004
|
+
return campaign;
|
|
34005
|
+
}
|
|
34006
|
+
function loadGrowthCampaignByIdeaId(ideaId) {
|
|
34007
|
+
const campaigns = listGrowthCampaigns();
|
|
34008
|
+
return campaigns.find((c2) => c2.source_idea_id === ideaId) || null;
|
|
34009
|
+
}
|
|
34010
|
+
function updateGrowthCampaign(campaign) {
|
|
34011
|
+
fs24.mkdirSync(CAMPAIGNS_DIR, { recursive: true });
|
|
34012
|
+
const filePath = path18.join(CAMPAIGNS_DIR, `${campaign.id}.json`);
|
|
34013
|
+
fs24.writeFileSync(filePath, JSON.stringify(campaign, null, 2) + "\n");
|
|
34014
|
+
}
|
|
34015
|
+
function listGrowthCampaigns() {
|
|
34016
|
+
if (!fs24.existsSync(CAMPAIGNS_DIR)) return [];
|
|
34017
|
+
try {
|
|
34018
|
+
return fs24.readdirSync(CAMPAIGNS_DIR).filter((f) => f.startsWith("growth-") && f.endsWith(".json")).map((f) => JSON.parse(fs24.readFileSync(path18.join(CAMPAIGNS_DIR, f), "utf-8"))).sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
34019
|
+
} catch {
|
|
34020
|
+
return [];
|
|
34021
|
+
}
|
|
34022
|
+
}
|
|
34023
|
+
function loadProductInfo() {
|
|
34024
|
+
try {
|
|
34025
|
+
if (!fs24.existsSync(POSITIONING_FILE)) return null;
|
|
34026
|
+
const positioning = JSON.parse(fs24.readFileSync(POSITIONING_FILE, "utf-8"));
|
|
34027
|
+
const current = positioning?.current;
|
|
34028
|
+
if (!current) return null;
|
|
34029
|
+
return {
|
|
34030
|
+
name: current.product_name || current.name || "Unknown",
|
|
34031
|
+
tagline: current.tagline || "",
|
|
34032
|
+
value_proposition: current.value_proposition || current.positioning_statement || "",
|
|
34033
|
+
differentiators: current.differentiators || current.key_differentiators || [],
|
|
34034
|
+
target_audience: current.target_audience || current.ideal_customer || ""
|
|
34035
|
+
};
|
|
34036
|
+
} catch {
|
|
34037
|
+
return null;
|
|
34038
|
+
}
|
|
34039
|
+
}
|
|
34040
|
+
function loadCompetitors() {
|
|
34041
|
+
try {
|
|
34042
|
+
if (!fs24.existsSync(COMPETITORS_FILE)) return [];
|
|
34043
|
+
const data = JSON.parse(fs24.readFileSync(COMPETITORS_FILE, "utf-8"));
|
|
34044
|
+
return data?.competitors || data || [];
|
|
34045
|
+
} catch {
|
|
34046
|
+
return [];
|
|
34047
|
+
}
|
|
34048
|
+
}
|
|
34049
|
+
function loadMarketingStrategies() {
|
|
34050
|
+
try {
|
|
34051
|
+
if (!fs24.existsSync(MARKETING_STRATEGIES_FILE)) return null;
|
|
34052
|
+
return JSON.parse(fs24.readFileSync(MARKETING_STRATEGIES_FILE, "utf-8"));
|
|
34053
|
+
} catch {
|
|
34054
|
+
return null;
|
|
34055
|
+
}
|
|
34056
|
+
}
|
|
34057
|
+
var DEFAULT_CHANNELS = ["twitter_thread", "linkedin", "reddit"];
|
|
34058
|
+
var CHANNEL_SCHEDULE = {
|
|
34059
|
+
twitter_thread: 0,
|
|
34060
|
+
linkedin: 1,
|
|
34061
|
+
reddit: 2,
|
|
34062
|
+
indie_hackers: 3,
|
|
34063
|
+
show_hn: 5,
|
|
34064
|
+
devto: 7,
|
|
34065
|
+
product_hunt: 7
|
|
34066
|
+
};
|
|
34067
|
+
function resolveChannels(ideaTags, marketingStrategies) {
|
|
34068
|
+
const tagChannels = ideaTags.filter((t) => t.startsWith("channel:")).map((t) => t.replace("channel:", "")).filter((c2) => c2 in CHANNEL_SCHEDULE);
|
|
34069
|
+
let channels = tagChannels.length > 0 ? tagChannels : DEFAULT_CHANNELS;
|
|
34070
|
+
if (marketingStrategies) {
|
|
34071
|
+
const excluded = marketingStrategies.excluded_channels || [];
|
|
34072
|
+
const preferred = marketingStrategies.preferred_channels;
|
|
34073
|
+
if (excluded.length > 0) {
|
|
34074
|
+
channels = channels.filter((c2) => !excluded.includes(c2));
|
|
34075
|
+
}
|
|
34076
|
+
if (preferred && preferred.length > 0) {
|
|
34077
|
+
channels.sort((a, b) => {
|
|
34078
|
+
const aP = preferred.includes(a) ? 0 : 1;
|
|
34079
|
+
const bP = preferred.includes(b) ? 0 : 1;
|
|
34080
|
+
return aP - bP;
|
|
34081
|
+
});
|
|
34082
|
+
}
|
|
34083
|
+
}
|
|
34084
|
+
return channels;
|
|
34085
|
+
}
|
|
34086
|
+
function buildChannelDrafts(channels, ideaTags) {
|
|
34087
|
+
return channels.map((channel) => {
|
|
34088
|
+
const draft = {
|
|
34089
|
+
channel,
|
|
34090
|
+
status: "pending",
|
|
34091
|
+
scheduled_day: CHANNEL_SCHEDULE[channel] ?? 0
|
|
34092
|
+
};
|
|
34093
|
+
if (channel === "reddit") {
|
|
34094
|
+
const subTag = ideaTags.find((t) => t.startsWith("subreddit:"));
|
|
34095
|
+
if (subTag) {
|
|
34096
|
+
draft.subreddit = subTag.replace("subreddit:", "");
|
|
34097
|
+
}
|
|
34098
|
+
}
|
|
34099
|
+
return draft;
|
|
34100
|
+
});
|
|
34101
|
+
}
|
|
34102
|
+
|
|
34103
|
+
// scripts/lib/growth/channel-adapters.ts
|
|
34104
|
+
function productContext(product) {
|
|
34105
|
+
return `PRODUCT CONTEXT:
|
|
34106
|
+
- Name: ${product.name}
|
|
34107
|
+
- Tagline: ${product.tagline}
|
|
34108
|
+
- Value Proposition: ${product.value_proposition}
|
|
34109
|
+
- Differentiators: ${product.differentiators.join(", ")}
|
|
34110
|
+
- Target Audience: ${product.target_audience}`;
|
|
34111
|
+
}
|
|
34112
|
+
function researchContext(research) {
|
|
34113
|
+
if (!research) return "RESEARCH: No research data available yet.";
|
|
34114
|
+
const parts = ["RESEARCH DATA:"];
|
|
34115
|
+
if (research.subreddit_research.length > 0) {
|
|
34116
|
+
parts.push("\nSubreddits:");
|
|
34117
|
+
for (const sub of research.subreddit_research) {
|
|
34118
|
+
parts.push(`- r/${sub.name}: ${sub.subscribers ?? "?"} subscribers, self-promo: ${sub.self_promo_policy}`);
|
|
34119
|
+
if (sub.top_post_patterns.length > 0) {
|
|
34120
|
+
parts.push(` Top patterns: ${sub.top_post_patterns.join("; ")}`);
|
|
34121
|
+
}
|
|
34122
|
+
}
|
|
34123
|
+
}
|
|
34124
|
+
if (research.keyword_research.length > 0) {
|
|
34125
|
+
parts.push("\nKeywords:");
|
|
34126
|
+
for (const kw of research.keyword_research) {
|
|
34127
|
+
parts.push(`- "${kw.keyword}": volume=${kw.volume || "?"}, CPC=${kw.cpc || "?"}, competition=${kw.competition || "?"}`);
|
|
34128
|
+
}
|
|
34129
|
+
}
|
|
34130
|
+
if (research.hn_research) {
|
|
34131
|
+
parts.push("\nHacker News:");
|
|
34132
|
+
parts.push(`- Similar posts: ${research.hn_research.successful_similar_posts.join("; ")}`);
|
|
34133
|
+
parts.push(`- Upvote patterns: ${research.hn_research.upvote_patterns}`);
|
|
34134
|
+
}
|
|
34135
|
+
if (research.competitor_content.length > 0) {
|
|
34136
|
+
parts.push("\nCompetitor Content:");
|
|
34137
|
+
for (const cc of research.competitor_content) {
|
|
34138
|
+
parts.push(`- ${cc.competitor}: ${cc.content_examples.slice(0, 2).join("; ")}`);
|
|
34139
|
+
}
|
|
34140
|
+
}
|
|
34141
|
+
if (research.trending_topics.length > 0) {
|
|
34142
|
+
parts.push(`
|
|
34143
|
+
Trending: ${research.trending_topics.join(", ")}`);
|
|
34144
|
+
}
|
|
34145
|
+
return parts.join("\n");
|
|
34146
|
+
}
|
|
34147
|
+
function buildShowHNPrompt(ctx) {
|
|
34148
|
+
return `You are writing a Show HN post for Hacker News.
|
|
34149
|
+
|
|
34150
|
+
${productContext(ctx.product)}
|
|
34151
|
+
|
|
34152
|
+
IDEA CONTEXT:
|
|
34153
|
+
- Title: ${ctx.idea.title}
|
|
34154
|
+
- Summary: ${ctx.idea.summary}
|
|
34155
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34156
|
+
|
|
34157
|
+
${researchContext(ctx.research)}
|
|
34158
|
+
|
|
34159
|
+
HACKER NEWS RULES (CRITICAL \u2014 follow exactly):
|
|
34160
|
+
1. Title MUST start with "Show HN: " followed by a concise description (max 80 chars total)
|
|
34161
|
+
2. The HN post body should be 2-4 short paragraphs explaining what you built and why
|
|
34162
|
+
3. Be technical and honest \u2014 HN readers detect and punish marketing speak instantly
|
|
34163
|
+
4. Acknowledge limitations openly \u2014 this builds trust
|
|
34164
|
+
5. Explain the technical approach briefly
|
|
34165
|
+
6. No exclamation marks, no superlatives, no "revolutionary/game-changing"
|
|
34166
|
+
7. End with a question to invite discussion (e.g., "Curious what you think about X approach")
|
|
34167
|
+
|
|
34168
|
+
FIRST COMMENT (CRITICAL):
|
|
34169
|
+
- Write a 500-800 character "maker comment" that goes deeper
|
|
34170
|
+
- Share the backstory: why you built this, what problem you hit
|
|
34171
|
+
- Include one specific technical detail or metric
|
|
34172
|
+
- Be genuine and conversational
|
|
34173
|
+
|
|
34174
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34175
|
+
{
|
|
34176
|
+
"title": "Show HN: ...",
|
|
34177
|
+
"body": "The HN post body text",
|
|
34178
|
+
"first_comment": "The maker's first comment",
|
|
34179
|
+
"cta": "A subtle link/reference, NOT a hard sell",
|
|
34180
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=hackernews&utm_medium=show_hn&utm_campaign=${ctx.utmCampaign}",
|
|
34181
|
+
"platform_constraints": "Title max 80 chars. No marketing speak. Technical honesty required."
|
|
34182
|
+
}`;
|
|
34183
|
+
}
|
|
34184
|
+
function buildRedditPrompt(ctx, subreddit) {
|
|
34185
|
+
const subName = subreddit || "relevant subreddit";
|
|
34186
|
+
const subResearch = ctx.research?.subreddit_research.find(
|
|
34187
|
+
(s) => s.name.toLowerCase() === (subreddit || "").toLowerCase()
|
|
34188
|
+
);
|
|
34189
|
+
let subRules = "";
|
|
34190
|
+
if (subResearch) {
|
|
34191
|
+
subRules = `
|
|
34192
|
+
SUBREDDIT RESEARCH for r/${subResearch.name}:
|
|
34193
|
+
- Subscribers: ${subResearch.subscribers ?? "unknown"}
|
|
34194
|
+
- Self-promo policy: ${subResearch.self_promo_policy}
|
|
34195
|
+
- Rules: ${subResearch.rules_summary}
|
|
34196
|
+
- Top post patterns: ${subResearch.top_post_patterns.join("; ")}`;
|
|
34197
|
+
}
|
|
34198
|
+
return `You are writing a Reddit post for r/${subName}.
|
|
34199
|
+
|
|
34200
|
+
${productContext(ctx.product)}
|
|
34201
|
+
|
|
34202
|
+
IDEA CONTEXT:
|
|
34203
|
+
- Title: ${ctx.idea.title}
|
|
34204
|
+
- Summary: ${ctx.idea.summary}
|
|
34205
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34206
|
+
|
|
34207
|
+
${researchContext(ctx.research)}
|
|
34208
|
+
${subRules}
|
|
34209
|
+
|
|
34210
|
+
REDDIT POST RULES (CRITICAL):
|
|
34211
|
+
1. Title should be a question or story hook \u2014 NOT a product announcement
|
|
34212
|
+
2. Lead with the problem/pain you experienced, not the solution
|
|
34213
|
+
3. Provide genuine value FIRST: share insights, data, or a useful approach
|
|
34214
|
+
4. Mention your product only once, near the end, as "I built X to solve this"
|
|
34215
|
+
5. Match the subreddit's culture \u2014 read the top posts for tone
|
|
34216
|
+
6. Use paragraphs (Reddit markdown), not walls of text
|
|
34217
|
+
7. If the subreddit bans self-promotion, frame as asking for feedback instead
|
|
34218
|
+
8. Be a community member first, promoter never
|
|
34219
|
+
|
|
34220
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34221
|
+
{
|
|
34222
|
+
"title": "Post title (question or story hook)",
|
|
34223
|
+
"body": "Full post body with Reddit markdown formatting",
|
|
34224
|
+
"cta": "Soft mention of the product, woven into the story",
|
|
34225
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=reddit&utm_medium=r_${subreddit || "post"}&utm_campaign=${ctx.utmCampaign}",
|
|
34226
|
+
"platform_constraints": "Community-first. Value before promotion. Match subreddit tone."
|
|
34227
|
+
}`;
|
|
34228
|
+
}
|
|
34229
|
+
function buildLinkedInPrompt(ctx) {
|
|
34230
|
+
return `You are writing a LinkedIn post.
|
|
34231
|
+
|
|
34232
|
+
${productContext(ctx.product)}
|
|
34233
|
+
|
|
34234
|
+
IDEA CONTEXT:
|
|
34235
|
+
- Title: ${ctx.idea.title}
|
|
34236
|
+
- Summary: ${ctx.idea.summary}
|
|
34237
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34238
|
+
|
|
34239
|
+
${researchContext(ctx.research)}
|
|
34240
|
+
|
|
34241
|
+
LINKEDIN ALGORITHM KNOWLEDGE (maximize reach):
|
|
34242
|
+
1. First line = hook \u2014 must stop the scroll. Use a bold claim, surprising stat, or contrarian take
|
|
34243
|
+
2. Use short paragraphs (1-2 sentences each) with blank lines between
|
|
34244
|
+
3. Whitespace is your friend \u2014 dense text gets scrolled past
|
|
34245
|
+
4. Total length: 1200-1500 characters (sweet spot for engagement)
|
|
34246
|
+
5. End with a question to drive comments (comments > likes for reach)
|
|
34247
|
+
6. No external links in the post body (kills reach by 50%)
|
|
34248
|
+
7. Put the link in the first comment instead
|
|
34249
|
+
8. Use "I" statements \u2014 personal stories outperform corporate announcements
|
|
34250
|
+
9. Optional: use a list format (numbered insights) for scanability
|
|
34251
|
+
10. No hashtags in the post (they look spammy and don't help anymore)
|
|
34252
|
+
|
|
34253
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34254
|
+
{
|
|
34255
|
+
"title": "Not used on LinkedIn, but store the hook line here",
|
|
34256
|
+
"body": "The full LinkedIn post text, formatted with line breaks",
|
|
34257
|
+
"cta": "Question to ask at the end + note to put link in first comment",
|
|
34258
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=linkedin&utm_medium=post&utm_campaign=${ctx.utmCampaign}",
|
|
34259
|
+
"platform_constraints": "No links in body. 1200-1500 chars. Hook first line. End with question."
|
|
34260
|
+
}`;
|
|
34261
|
+
}
|
|
34262
|
+
function buildTwitterThreadPrompt(ctx) {
|
|
34263
|
+
return `You are writing a Twitter/X thread.
|
|
34264
|
+
|
|
34265
|
+
${productContext(ctx.product)}
|
|
34266
|
+
|
|
34267
|
+
IDEA CONTEXT:
|
|
34268
|
+
- Title: ${ctx.idea.title}
|
|
34269
|
+
- Summary: ${ctx.idea.summary}
|
|
34270
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34271
|
+
|
|
34272
|
+
${researchContext(ctx.research)}
|
|
34273
|
+
|
|
34274
|
+
TWITTER THREAD RULES (maximize engagement):
|
|
34275
|
+
1. Tweet 1 (hook): Must be a standalone banger. Bold claim, surprising data, or contrarian take. Max 280 chars.
|
|
34276
|
+
2. Thread length: 5-7 tweets total (sweet spot \u2014 shorter feels thin, longer loses readers)
|
|
34277
|
+
3. Each tweet should be self-contained AND flow into the next
|
|
34278
|
+
4. Use numbers and data where possible \u2014 "We increased X by 47%" > "We improved X"
|
|
34279
|
+
5. Tweet structure: Hook \u2192 Problem \u2192 Insight 1 \u2192 Insight 2 \u2192 How \u2192 Result \u2192 CTA
|
|
34280
|
+
6. Last tweet = CTA with link. "If you want to try this: [link]" or "Follow me for more on X"
|
|
34281
|
+
7. No thread-style numbering (1/7, 2/7) \u2014 it looks robotic
|
|
34282
|
+
8. Use line breaks within tweets for readability
|
|
34283
|
+
9. Optional: one tweet can be an image/screenshot placeholder "[screenshot of dashboard]"
|
|
34284
|
+
|
|
34285
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34286
|
+
{
|
|
34287
|
+
"body": "Tweet 1 text (the hook)",
|
|
34288
|
+
"thread_tweets": ["Tweet 2", "Tweet 3", "Tweet 4", "Tweet 5", "Tweet 6 (CTA)"],
|
|
34289
|
+
"cta": "The CTA text from the last tweet",
|
|
34290
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=twitter&utm_medium=thread&utm_campaign=${ctx.utmCampaign}",
|
|
34291
|
+
"platform_constraints": "Max 280 chars per tweet. 5-7 tweets. Hook tweet must stand alone."
|
|
34292
|
+
}`;
|
|
34293
|
+
}
|
|
34294
|
+
function buildIndieHackersPrompt(ctx) {
|
|
34295
|
+
return `You are writing an Indie Hackers post.
|
|
34296
|
+
|
|
34297
|
+
${productContext(ctx.product)}
|
|
34298
|
+
|
|
34299
|
+
IDEA CONTEXT:
|
|
34300
|
+
- Title: ${ctx.idea.title}
|
|
34301
|
+
- Summary: ${ctx.idea.summary}
|
|
34302
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34303
|
+
|
|
34304
|
+
${researchContext(ctx.research)}
|
|
34305
|
+
|
|
34306
|
+
INDIE HACKERS RULES:
|
|
34307
|
+
1. IH audience = founders, solopreneurs, bootstrappers. They want REAL numbers.
|
|
34308
|
+
2. Use milestone format: "How I [achieved X] \u2014 the full breakdown"
|
|
34309
|
+
3. Share real metrics: revenue, users, conversion rates, time spent
|
|
34310
|
+
4. Be transparent about what DIDN'T work \u2014 this builds massive trust
|
|
34311
|
+
5. Structure: Background \u2192 What I tried \u2192 What worked \u2192 Metrics \u2192 Lessons \u2192 What's next
|
|
34312
|
+
6. Length: 1500-2500 words (IH readers love detail)
|
|
34313
|
+
7. End with specific lessons (numbered list) and a question for the community
|
|
34314
|
+
8. Don't be afraid to show vulnerability \u2014 "I almost gave up when..."
|
|
34315
|
+
|
|
34316
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34317
|
+
{
|
|
34318
|
+
"title": "How I [achieved result] \u2014 [compelling detail]",
|
|
34319
|
+
"body": "Full post body with markdown formatting",
|
|
34320
|
+
"cta": "Question for the community + soft product mention",
|
|
34321
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=indiehackers&utm_medium=post&utm_campaign=${ctx.utmCampaign}",
|
|
34322
|
+
"platform_constraints": "Milestone format. Real numbers required. 1500-2500 words. Transparency wins."
|
|
34323
|
+
}`;
|
|
34324
|
+
}
|
|
34325
|
+
function buildDevToPrompt(ctx) {
|
|
34326
|
+
return `You are writing a Dev.to article.
|
|
34327
|
+
|
|
34328
|
+
${productContext(ctx.product)}
|
|
34329
|
+
|
|
34330
|
+
IDEA CONTEXT:
|
|
34331
|
+
- Title: ${ctx.idea.title}
|
|
34332
|
+
- Summary: ${ctx.idea.summary}
|
|
34333
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34334
|
+
- Implementation plan: ${ctx.idea.implementation_plan}
|
|
34335
|
+
|
|
34336
|
+
${researchContext(ctx.research)}
|
|
34337
|
+
|
|
34338
|
+
DEV.TO RULES:
|
|
34339
|
+
1. Dev.to audience = developers. They want to LEARN something.
|
|
34340
|
+
2. Frame as a tutorial or technical deep-dive, not a product announcement
|
|
34341
|
+
3. Title format: "How to [solve problem] with [approach]" or "Building [thing]: [technical detail]"
|
|
34342
|
+
4. Length: 2000-3000 words with code examples
|
|
34343
|
+
5. Include architecture decisions and trade-offs ("We chose X over Y because...")
|
|
34344
|
+
6. Use proper markdown: headers, code blocks with syntax highlighting, lists
|
|
34345
|
+
7. Add a cover image suggestion and 3-4 relevant tags
|
|
34346
|
+
8. Front matter should include: title, published, description, tags, cover_image
|
|
34347
|
+
9. End with "Next Steps" and links to further reading
|
|
34348
|
+
|
|
34349
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34350
|
+
{
|
|
34351
|
+
"title": "How to [approach] \u2014 [benefit]",
|
|
34352
|
+
"body": "Full article body with markdown, code blocks, and headers",
|
|
34353
|
+
"cta": "Next steps section with product link woven in naturally",
|
|
34354
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=devto&utm_medium=article&utm_campaign=${ctx.utmCampaign}",
|
|
34355
|
+
"platform_constraints": "Tutorial format. 2000-3000 words. Code examples required. Dev audience."
|
|
34356
|
+
}`;
|
|
34357
|
+
}
|
|
34358
|
+
function buildProductHuntPrompt(ctx) {
|
|
34359
|
+
return `You are preparing a Product Hunt launch.
|
|
34360
|
+
|
|
34361
|
+
${productContext(ctx.product)}
|
|
34362
|
+
|
|
34363
|
+
IDEA CONTEXT:
|
|
34364
|
+
- Title: ${ctx.idea.title}
|
|
34365
|
+
- Summary: ${ctx.idea.summary}
|
|
34366
|
+
- Rationale: ${ctx.idea.rationale}
|
|
34367
|
+
|
|
34368
|
+
${researchContext(ctx.research)}
|
|
34369
|
+
|
|
34370
|
+
PRODUCT HUNT RULES:
|
|
34371
|
+
1. Tagline: max 60 characters. Benefit-focused, not feature-focused.
|
|
34372
|
+
2. Description: max 260 characters. What it does + who it's for.
|
|
34373
|
+
3. First comment (maker comment): 500-800 chars. Story of why you built it.
|
|
34374
|
+
4. Suggest 5 gallery image concepts (screenshots, diagrams, before/after)
|
|
34375
|
+
5. Choose 3 topics/categories that fit
|
|
34376
|
+
6. Hunter's note: write as if someone else is featuring your product
|
|
34377
|
+
7. Timing: launch at 12:01 AM PST for maximum visibility
|
|
34378
|
+
|
|
34379
|
+
OUTPUT FORMAT (respond with valid JSON only):
|
|
34380
|
+
{
|
|
34381
|
+
"title": "${ctx.product.name}",
|
|
34382
|
+
"body": "260-char description",
|
|
34383
|
+
"first_comment": "Maker's first comment (500-800 chars)",
|
|
34384
|
+
"cta": "60-char tagline",
|
|
34385
|
+
"utm_url": "${ctx.productUrl || "https://yoursite.com"}?utm_source=producthunt&utm_medium=launch&utm_campaign=${ctx.utmCampaign}",
|
|
34386
|
+
"platform_constraints": "Tagline max 60 chars. Description max 260 chars. Launch at 12:01 AM PST.",
|
|
34387
|
+
"gallery_suggestions": ["Suggestion 1", "Suggestion 2", "Suggestion 3", "Suggestion 4", "Suggestion 5"],
|
|
34388
|
+
"topics": ["Topic 1", "Topic 2", "Topic 3"]
|
|
34389
|
+
}`;
|
|
34390
|
+
}
|
|
34391
|
+
function getChannelPrompt(channel, ctx, subreddit) {
|
|
34392
|
+
switch (channel) {
|
|
34393
|
+
case "show_hn":
|
|
34394
|
+
return buildShowHNPrompt(ctx);
|
|
34395
|
+
case "reddit":
|
|
34396
|
+
return buildRedditPrompt(ctx, subreddit);
|
|
34397
|
+
case "linkedin":
|
|
34398
|
+
return buildLinkedInPrompt(ctx);
|
|
34399
|
+
case "twitter_thread":
|
|
34400
|
+
return buildTwitterThreadPrompt(ctx);
|
|
34401
|
+
case "indie_hackers":
|
|
34402
|
+
return buildIndieHackersPrompt(ctx);
|
|
34403
|
+
case "devto":
|
|
34404
|
+
return buildDevToPrompt(ctx);
|
|
34405
|
+
case "product_hunt":
|
|
34406
|
+
return buildProductHuntPrompt(ctx);
|
|
34407
|
+
default:
|
|
34408
|
+
return buildLinkedInPrompt(ctx);
|
|
34409
|
+
}
|
|
34410
|
+
}
|
|
34411
|
+
|
|
34412
|
+
// scripts/lib/growth/research.ts
|
|
34413
|
+
var import_child_process10 = require("child_process");
|
|
34414
|
+
init_paths();
|
|
34415
|
+
function executeGrowthResearch(params) {
|
|
34416
|
+
const channelNames = params.channels.map((c2) => c2.channel);
|
|
34417
|
+
const subreddits = params.channels.filter((c2) => c2.channel === "reddit" && c2.subreddit).map((c2) => c2.subreddit);
|
|
34418
|
+
const prompt = buildResearchPrompt({
|
|
34419
|
+
product: params.product,
|
|
34420
|
+
channels: channelNames,
|
|
34421
|
+
subreddits,
|
|
34422
|
+
ideaTitle: params.ideaTitle,
|
|
34423
|
+
ideaSummary: params.ideaSummary,
|
|
34424
|
+
competitors: params.competitors
|
|
34425
|
+
});
|
|
34426
|
+
const result = (0, import_child_process10.spawnSync)("claude", [
|
|
34427
|
+
"--print",
|
|
34428
|
+
"--dangerously-skip-permissions",
|
|
34429
|
+
"--allowedTools",
|
|
34430
|
+
"WebSearch WebFetch",
|
|
34431
|
+
"--output-format",
|
|
34432
|
+
"text"
|
|
34433
|
+
], {
|
|
34434
|
+
cwd: PROJECT_DIR,
|
|
34435
|
+
input: prompt,
|
|
34436
|
+
encoding: "utf-8",
|
|
34437
|
+
timeout: 3e5,
|
|
34438
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
34439
|
+
env: process.env
|
|
34440
|
+
});
|
|
34441
|
+
if (result.status !== 0) {
|
|
34442
|
+
console.error(`[growth-research] Claude Code exited with code ${result.status}`);
|
|
34443
|
+
console.error(`[growth-research] stderr: ${(result.stderr || "").slice(-500)}`);
|
|
34444
|
+
return null;
|
|
34445
|
+
}
|
|
34446
|
+
const output = result.stdout || result.stderr || "";
|
|
34447
|
+
const jsonMatch = output.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) || [null, output];
|
|
34448
|
+
const jsonStr = (jsonMatch[1] || output).trim();
|
|
34449
|
+
try {
|
|
34450
|
+
const parsed = JSON.parse(jsonStr);
|
|
34451
|
+
parsed.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34452
|
+
return parsed;
|
|
34453
|
+
} catch {
|
|
34454
|
+
const objMatch = output.match(/\{[\s\S]*\}/);
|
|
34455
|
+
if (objMatch) {
|
|
34456
|
+
try {
|
|
34457
|
+
const parsed = JSON.parse(objMatch[0]);
|
|
34458
|
+
parsed.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34459
|
+
return parsed;
|
|
34460
|
+
} catch {
|
|
34461
|
+
console.error("[growth-research] Failed to parse research output as JSON");
|
|
34462
|
+
console.error("[growth-research] Raw output:", output.slice(-1e3));
|
|
34463
|
+
return null;
|
|
34464
|
+
}
|
|
34465
|
+
}
|
|
34466
|
+
console.error("[growth-research] No JSON found in output");
|
|
34467
|
+
return null;
|
|
34468
|
+
}
|
|
34469
|
+
}
|
|
34470
|
+
function buildResearchPrompt(params) {
|
|
34471
|
+
const parts = [];
|
|
34472
|
+
parts.push(`You are a growth researcher. Gather REAL data using WebSearch for a marketing campaign.
|
|
34473
|
+
|
|
34474
|
+
PRODUCT: ${params.product.name} \u2014 ${params.product.tagline}
|
|
34475
|
+
VALUE PROP: ${params.product.value_proposition}
|
|
34476
|
+
TARGET AUDIENCE: ${params.product.target_audience}
|
|
34477
|
+
|
|
34478
|
+
CAMPAIGN IDEA: ${params.ideaTitle}
|
|
34479
|
+
${params.ideaSummary}
|
|
34480
|
+
|
|
34481
|
+
TARGET CHANNELS: ${params.channels.join(", ")}
|
|
34482
|
+
`);
|
|
34483
|
+
if (params.channels.includes("reddit") || params.subreddits.length > 0) {
|
|
34484
|
+
const subs = params.subreddits.length > 0 ? params.subreddits.join(", ") : "Find 2-3 relevant subreddits for this product";
|
|
34485
|
+
parts.push(`REDDIT RESEARCH:
|
|
34486
|
+
- Search for subreddits related to "${params.product.target_audience}"
|
|
34487
|
+
- Target subreddits: ${subs}
|
|
34488
|
+
- For each subreddit: find subscriber count, self-promotion rules, top post patterns
|
|
34489
|
+
- Search: "site:reddit.com ${params.product.name}" to see existing mentions`);
|
|
34490
|
+
}
|
|
34491
|
+
if (params.channels.includes("show_hn")) {
|
|
34492
|
+
parts.push(`HACKER NEWS RESEARCH:
|
|
34493
|
+
- Search: "site:news.ycombinator.com Show HN ${params.product.name}" or similar products
|
|
34494
|
+
- Find 3-5 successful Show HN posts in the same category
|
|
34495
|
+
- Note their titles, upvote counts, and first comment patterns
|
|
34496
|
+
- Check what time of day successful posts were submitted`);
|
|
34497
|
+
}
|
|
34498
|
+
if (params.competitors.length > 0) {
|
|
34499
|
+
const compNames = params.competitors.map((c2) => c2.name || String(c2)).slice(0, 5);
|
|
34500
|
+
parts.push(`COMPETITOR RESEARCH:
|
|
34501
|
+
- Competitors: ${compNames.join(", ")}
|
|
34502
|
+
- Search for their recent social media posts and engagement
|
|
34503
|
+
- Find what content angles work for them`);
|
|
34504
|
+
}
|
|
34505
|
+
parts.push(`KEYWORD RESEARCH:
|
|
34506
|
+
- Find 3-5 keywords related to: "${params.ideaTitle}"
|
|
34507
|
+
- Look for search volume estimates, competition level
|
|
34508
|
+
- Identify high-intent keywords for this audience`);
|
|
34509
|
+
parts.push(`TRENDING TOPICS:
|
|
34510
|
+
- Search for current trends related to: ${params.product.target_audience}
|
|
34511
|
+
- Find trending topics on Twitter/X, Reddit, HN that could be leveraged`);
|
|
34512
|
+
parts.push(`OUTPUT FORMAT \u2014 respond with ONLY valid JSON, no other text:
|
|
34513
|
+
{
|
|
34514
|
+
"completed_at": "${(/* @__PURE__ */ new Date()).toISOString()}",
|
|
34515
|
+
"subreddit_research": [
|
|
34516
|
+
{
|
|
34517
|
+
"name": "subreddit_name",
|
|
34518
|
+
"subscribers": 100000,
|
|
34519
|
+
"rules_summary": "Key rules...",
|
|
34520
|
+
"self_promo_policy": "Allowed on Saturdays / Never / With flair",
|
|
34521
|
+
"top_post_patterns": ["Pattern 1", "Pattern 2"]
|
|
34522
|
+
}
|
|
34523
|
+
],
|
|
34524
|
+
"keyword_research": [
|
|
34525
|
+
{
|
|
34526
|
+
"keyword": "example keyword",
|
|
34527
|
+
"volume": "1K-10K monthly",
|
|
34528
|
+
"cpc": "$2.50",
|
|
34529
|
+
"competition": "medium",
|
|
34530
|
+
"intent": "commercial"
|
|
34531
|
+
}
|
|
34532
|
+
],
|
|
34533
|
+
"hn_research": {
|
|
34534
|
+
"successful_similar_posts": ["Show HN: Product (500 pts)", "Show HN: Similar (200 pts)"],
|
|
34535
|
+
"upvote_patterns": "Best posted Tue-Thu, 8-10am PST",
|
|
34536
|
+
"first_comment_patterns": "Technical backstory + specific metrics"
|
|
34537
|
+
},
|
|
34538
|
+
"competitor_content": [
|
|
34539
|
+
{
|
|
34540
|
+
"competitor": "Competitor Name",
|
|
34541
|
+
"content_examples": ["They posted X on LinkedIn (500 likes)", "Their blog on Y"],
|
|
34542
|
+
"engagement_level": "high"
|
|
34543
|
+
}
|
|
34544
|
+
],
|
|
34545
|
+
"trending_topics": ["Topic 1 trending on X", "Topic 2 popular on HN"]
|
|
34546
|
+
}`);
|
|
34547
|
+
return parts.join("\n\n");
|
|
34548
|
+
}
|
|
34549
|
+
|
|
34550
|
+
// src/lib/social-storage.ts
|
|
34551
|
+
var import_fs = require("fs");
|
|
34552
|
+
var import_path = __toESM(require("path"));
|
|
34553
|
+
function getDataDir() {
|
|
34554
|
+
return process.env.AI_ANALYST_DATA_DIR || import_path.default.join(process.cwd(), "data");
|
|
34555
|
+
}
|
|
34556
|
+
function getSocialFilePath() {
|
|
34557
|
+
return import_path.default.join(getDataDir(), "social.json");
|
|
34558
|
+
}
|
|
34559
|
+
var EMPTY_STATE4 = {
|
|
34560
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34561
|
+
platform: "x",
|
|
34562
|
+
username: null,
|
|
34563
|
+
credentials_valid: false,
|
|
34564
|
+
drafts: [],
|
|
34565
|
+
metrics: {
|
|
34566
|
+
followers_count: null,
|
|
34567
|
+
post_streak_days: 0,
|
|
34568
|
+
total_posts: 0,
|
|
34569
|
+
last_post_date: null
|
|
34570
|
+
}
|
|
34571
|
+
};
|
|
34572
|
+
async function readState() {
|
|
34573
|
+
try {
|
|
34574
|
+
const content = await import_fs.promises.readFile(getSocialFilePath(), "utf-8");
|
|
34575
|
+
return JSON.parse(content);
|
|
34576
|
+
} catch {
|
|
34577
|
+
return { ...EMPTY_STATE4, drafts: [], metrics: { ...EMPTY_STATE4.metrics } };
|
|
34578
|
+
}
|
|
34579
|
+
}
|
|
34580
|
+
async function writeState(state) {
|
|
34581
|
+
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
34582
|
+
const dir = getDataDir();
|
|
34583
|
+
try {
|
|
34584
|
+
await import_fs.promises.access(dir);
|
|
34585
|
+
} catch {
|
|
34586
|
+
await import_fs.promises.mkdir(dir, { recursive: true });
|
|
34587
|
+
}
|
|
34588
|
+
await import_fs.promises.writeFile(getSocialFilePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
34589
|
+
}
|
|
34590
|
+
async function createSocialDraft(text, opts) {
|
|
34591
|
+
const state = await readState();
|
|
34592
|
+
const isThread = !!(opts?.threadTweets && opts.threadTweets.length > 0);
|
|
34593
|
+
const draft = {
|
|
34594
|
+
id: `draft-${v4_default().slice(0, 8)}`,
|
|
34595
|
+
text,
|
|
34596
|
+
source: isThread ? "thread" : "manual",
|
|
34597
|
+
source_ref: null,
|
|
34598
|
+
status: "draft",
|
|
34599
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34600
|
+
published_at: null,
|
|
34601
|
+
tweet_id: null,
|
|
34602
|
+
tweet_url: null,
|
|
34603
|
+
is_thread: isThread,
|
|
34604
|
+
thread_tweets: opts?.threadTweets
|
|
34605
|
+
};
|
|
34606
|
+
state.drafts.push(draft);
|
|
34607
|
+
await writeState(state);
|
|
34608
|
+
return draft;
|
|
34609
|
+
}
|
|
34610
|
+
|
|
34611
|
+
// scripts/skills/growth-campaign.ts
|
|
34612
|
+
var GROWTH_PREFIXES = [
|
|
34613
|
+
"growth-activate-",
|
|
34614
|
+
"growth-research-",
|
|
34615
|
+
"growth-draft-",
|
|
34616
|
+
"growth-publish-",
|
|
34617
|
+
"growth-check-status"
|
|
34618
|
+
];
|
|
34619
|
+
function isGrowthCampaignTask(taskId) {
|
|
34620
|
+
return GROWTH_PREFIXES.some((prefix) => taskId.startsWith(prefix));
|
|
34621
|
+
}
|
|
34622
|
+
function appendToTodo2(tasks) {
|
|
34623
|
+
let content = "";
|
|
34624
|
+
if (fs26.existsSync(TODO_FILE)) {
|
|
34625
|
+
content = fs26.readFileSync(TODO_FILE, "utf-8");
|
|
34626
|
+
}
|
|
34627
|
+
const newLines = tasks.map(
|
|
34628
|
+
(t) => `- [ ] **${t.taskId}** \u2014 ${t.description}`
|
|
34629
|
+
).join("\n");
|
|
34630
|
+
if (content.includes("## High Priority")) {
|
|
34631
|
+
const idx = content.indexOf("## High Priority");
|
|
34632
|
+
const nextSection = content.indexOf("\n## ", idx + 1);
|
|
34633
|
+
const insertPos = nextSection > 0 ? nextSection : content.length;
|
|
34634
|
+
content = content.slice(0, insertPos) + "\n" + newLines + "\n" + content.slice(insertPos);
|
|
34635
|
+
} else {
|
|
34636
|
+
content += `
|
|
34637
|
+
## High Priority
|
|
34638
|
+
${newLines}
|
|
34639
|
+
`;
|
|
34640
|
+
}
|
|
34641
|
+
fs26.writeFileSync(TODO_FILE, content);
|
|
34642
|
+
}
|
|
34643
|
+
async function executeGrowthCampaignTask(taskId, description, businessContext) {
|
|
34644
|
+
try {
|
|
34645
|
+
if (taskId.startsWith("growth-activate-")) {
|
|
34646
|
+
const ideaId = taskId.replace("growth-activate-", "");
|
|
34647
|
+
return executeActivate(ideaId);
|
|
34648
|
+
}
|
|
34649
|
+
if (taskId.startsWith("growth-research-")) {
|
|
34650
|
+
const ideaId = taskId.replace("growth-research-", "");
|
|
34651
|
+
return executeResearch(ideaId);
|
|
34652
|
+
}
|
|
34653
|
+
if (taskId.startsWith("growth-draft-")) {
|
|
34654
|
+
const rest = taskId.replace("growth-draft-", "");
|
|
34655
|
+
const dashIdx = rest.indexOf("-");
|
|
34656
|
+
if (dashIdx === -1) return { success: false, output: `Invalid draft task ID: ${taskId}` };
|
|
34657
|
+
const ideaId = rest.slice(0, dashIdx);
|
|
34658
|
+
const channel = rest.slice(dashIdx + 1);
|
|
34659
|
+
return await executeDraft(ideaId, channel);
|
|
34660
|
+
}
|
|
34661
|
+
if (taskId.startsWith("growth-publish-")) {
|
|
34662
|
+
const rest = taskId.replace("growth-publish-", "");
|
|
34663
|
+
const dashIdx = rest.indexOf("-");
|
|
34664
|
+
if (dashIdx === -1) return { success: false, output: `Invalid publish task ID: ${taskId}` };
|
|
34665
|
+
const ideaId = rest.slice(0, dashIdx);
|
|
34666
|
+
const channel = rest.slice(dashIdx + 1);
|
|
34667
|
+
return await executePublish2(ideaId, channel);
|
|
34668
|
+
}
|
|
34669
|
+
if (taskId === "growth-check-status") {
|
|
34670
|
+
return executeCheckStatus6();
|
|
34671
|
+
}
|
|
34672
|
+
return { success: false, output: `Unknown growth campaign task: ${taskId}` };
|
|
34673
|
+
} catch (error) {
|
|
34674
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
34675
|
+
return { success: false, output: `Growth campaign task failed: ${errMsg}` };
|
|
34676
|
+
}
|
|
34677
|
+
}
|
|
34678
|
+
function executeActivate(ideaId) {
|
|
34679
|
+
if (!ideaId) {
|
|
34680
|
+
return { success: false, output: "No idea ID provided" };
|
|
34681
|
+
}
|
|
34682
|
+
let idea;
|
|
34683
|
+
try {
|
|
34684
|
+
const ideasData = JSON.parse(fs26.readFileSync(IDEAS_FILE, "utf-8"));
|
|
34685
|
+
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
34686
|
+
} catch {
|
|
34687
|
+
return { success: false, output: `Failed to load ideas from ${IDEAS_FILE}` };
|
|
34688
|
+
}
|
|
34689
|
+
if (!idea) {
|
|
34690
|
+
return { success: false, output: `Idea not found: ${ideaId}` };
|
|
34691
|
+
}
|
|
34692
|
+
if (idea.category !== "growth") {
|
|
34693
|
+
return { success: false, output: `Idea ${ideaId} is not a growth idea (category: ${idea.category})` };
|
|
34694
|
+
}
|
|
34695
|
+
if (idea.stage !== "approved") {
|
|
34696
|
+
return { success: false, output: `Idea ${ideaId} is not approved (stage: ${idea.stage})` };
|
|
34697
|
+
}
|
|
34698
|
+
const existing = loadGrowthCampaignByIdeaId(ideaId);
|
|
34699
|
+
if (existing && existing.status !== "completed") {
|
|
34700
|
+
return { success: false, output: `Active growth campaign already exists for ${ideaId}: ${existing.id}` };
|
|
34701
|
+
}
|
|
34702
|
+
const product = loadProductInfo();
|
|
34703
|
+
if (!product) {
|
|
34704
|
+
return { success: false, output: 'No positioning data found. Run "vibebusiness analyze --type=research" first.' };
|
|
34705
|
+
}
|
|
34706
|
+
const marketingStrategies = loadMarketingStrategies();
|
|
34707
|
+
const channels = resolveChannels(idea.tags, marketingStrategies);
|
|
34708
|
+
if (channels.length === 0) {
|
|
34709
|
+
return { success: false, output: "No target channels resolved. Add channel:xxx tags or configure marketing strategies." };
|
|
34710
|
+
}
|
|
34711
|
+
const channelDrafts = buildChannelDrafts(channels, idea.tags);
|
|
34712
|
+
const subTasks = [];
|
|
34713
|
+
subTasks.push({
|
|
34714
|
+
task_id: `growth-research-${ideaId}`,
|
|
34715
|
+
description: `Research channels and competitors for "${idea.title}"`,
|
|
34716
|
+
status: "queued",
|
|
34717
|
+
depends_on: []
|
|
34718
|
+
});
|
|
34719
|
+
for (const ch of channelDrafts) {
|
|
34720
|
+
const channelLabel = ch.subreddit ? `${ch.channel}_${ch.subreddit}` : ch.channel;
|
|
34721
|
+
subTasks.push({
|
|
34722
|
+
task_id: `growth-draft-${ideaId}-${channelLabel}`,
|
|
34723
|
+
description: `Draft ${ch.channel} content for "${idea.title}"`,
|
|
34724
|
+
status: "queued",
|
|
34725
|
+
depends_on: [`growth-research-${ideaId}`]
|
|
34726
|
+
});
|
|
34727
|
+
}
|
|
34728
|
+
for (const ch of channelDrafts) {
|
|
34729
|
+
const channelLabel = ch.subreddit ? `${ch.channel}_${ch.subreddit}` : ch.channel;
|
|
34730
|
+
subTasks.push({
|
|
34731
|
+
task_id: `growth-publish-${ideaId}-${channelLabel}`,
|
|
34732
|
+
description: `Publish ${ch.channel} content for "${idea.title}" (Day ${ch.scheduled_day})`,
|
|
34733
|
+
status: "queued",
|
|
34734
|
+
depends_on: [`growth-draft-${ideaId}-${channelLabel}`]
|
|
34735
|
+
});
|
|
34736
|
+
}
|
|
34737
|
+
const campaign = createGrowthCampaign({
|
|
34738
|
+
ideaId,
|
|
34739
|
+
ideaTitle: idea.title,
|
|
34740
|
+
channels: channelDrafts,
|
|
34741
|
+
subTasks,
|
|
34742
|
+
product
|
|
34743
|
+
});
|
|
34744
|
+
try {
|
|
34745
|
+
const ideasData = JSON.parse(fs26.readFileSync(IDEAS_FILE, "utf-8"));
|
|
34746
|
+
const ideaIdx = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
34747
|
+
if (ideaIdx >= 0) {
|
|
34748
|
+
ideasData.ideas[ideaIdx].stage = "in_progress";
|
|
34749
|
+
ideasData.ideas[ideaIdx].updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34750
|
+
fs26.writeFileSync(IDEAS_FILE, JSON.stringify(ideasData, null, 2) + "\n");
|
|
34751
|
+
}
|
|
34752
|
+
} catch {
|
|
34753
|
+
}
|
|
34754
|
+
appendToTodo2([
|
|
34755
|
+
{
|
|
34756
|
+
taskId: `growth-research-${ideaId}`,
|
|
34757
|
+
description: `Research channels and competitors for "${idea.title}"`
|
|
34758
|
+
}
|
|
34759
|
+
]);
|
|
34760
|
+
const lines = [
|
|
34761
|
+
`Growth campaign created: ${campaign.id}`,
|
|
34762
|
+
`Channels: ${channels.join(", ")}`,
|
|
34763
|
+
`Sub-tasks queued: ${subTasks.length}`,
|
|
34764
|
+
"",
|
|
34765
|
+
"Next: growth-research will run on the next heartbeat.",
|
|
34766
|
+
"Then: drafts and publishing will follow on subsequent heartbeats."
|
|
34767
|
+
];
|
|
34768
|
+
return { success: true, output: lines.join("\n") };
|
|
34769
|
+
}
|
|
34770
|
+
function executeResearch(ideaId) {
|
|
34771
|
+
const campaign = loadGrowthCampaignByIdeaId(ideaId);
|
|
34772
|
+
if (!campaign) {
|
|
34773
|
+
return { success: false, output: `No growth campaign found for idea ${ideaId}` };
|
|
34774
|
+
}
|
|
34775
|
+
const competitors = loadCompetitors();
|
|
34776
|
+
let idea;
|
|
34777
|
+
try {
|
|
34778
|
+
const ideasData = JSON.parse(fs26.readFileSync(IDEAS_FILE, "utf-8"));
|
|
34779
|
+
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
34780
|
+
} catch {
|
|
34781
|
+
}
|
|
34782
|
+
const research = executeGrowthResearch({
|
|
34783
|
+
product: campaign.product,
|
|
34784
|
+
channels: campaign.channels,
|
|
34785
|
+
ideaTitle: idea?.title || campaign.source_idea_title,
|
|
34786
|
+
ideaSummary: idea?.summary || "",
|
|
34787
|
+
competitors
|
|
34788
|
+
});
|
|
34789
|
+
if (!research) {
|
|
34790
|
+
return { success: false, output: "Research failed \u2014 Claude Code did not return valid JSON." };
|
|
34791
|
+
}
|
|
34792
|
+
campaign.research = research;
|
|
34793
|
+
campaign.status = "drafting";
|
|
34794
|
+
const researchTask = campaign.sub_tasks.find((t) => t.task_id === `growth-research-${ideaId}`);
|
|
34795
|
+
if (researchTask) researchTask.status = "completed";
|
|
34796
|
+
updateGrowthCampaign(campaign);
|
|
34797
|
+
const draftTasks = campaign.sub_tasks.filter((t) => t.task_id.startsWith(`growth-draft-${ideaId}`)).map((t) => ({ taskId: t.task_id, description: t.description }));
|
|
34798
|
+
if (draftTasks.length > 0) {
|
|
34799
|
+
appendToTodo2(draftTasks);
|
|
34800
|
+
}
|
|
34801
|
+
return {
|
|
34802
|
+
success: true,
|
|
34803
|
+
output: [
|
|
34804
|
+
`Research completed for campaign ${campaign.id}`,
|
|
34805
|
+
`Subreddits found: ${research.subreddit_research.length}`,
|
|
34806
|
+
`Keywords found: ${research.keyword_research.length}`,
|
|
34807
|
+
`Competitor insights: ${research.competitor_content.length}`,
|
|
34808
|
+
`Trending topics: ${research.trending_topics.length}`,
|
|
34809
|
+
"",
|
|
34810
|
+
`Queued ${draftTasks.length} draft tasks for next heartbeats.`
|
|
34811
|
+
].join("\n")
|
|
34812
|
+
};
|
|
34813
|
+
}
|
|
34814
|
+
async function executeDraft(ideaId, channel) {
|
|
34815
|
+
const campaign = loadGrowthCampaignByIdeaId(ideaId);
|
|
34816
|
+
if (!campaign) {
|
|
34817
|
+
return { success: false, output: `No growth campaign found for idea ${ideaId}` };
|
|
34818
|
+
}
|
|
34819
|
+
const channelDraft = campaign.channels.find((c2) => {
|
|
34820
|
+
const label = c2.subreddit ? `${c2.channel}_${c2.subreddit}` : c2.channel;
|
|
34821
|
+
return label === channel;
|
|
34822
|
+
});
|
|
34823
|
+
if (!channelDraft) {
|
|
34824
|
+
return { success: false, output: `Channel ${channel} not found in campaign ${campaign.id}` };
|
|
34825
|
+
}
|
|
34826
|
+
let idea;
|
|
34827
|
+
try {
|
|
34828
|
+
const ideasData = JSON.parse(fs26.readFileSync(IDEAS_FILE, "utf-8"));
|
|
34829
|
+
idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
34830
|
+
} catch {
|
|
34831
|
+
return { success: false, output: `Failed to load ideas from ${IDEAS_FILE}` };
|
|
34832
|
+
}
|
|
34833
|
+
if (!idea) {
|
|
34834
|
+
return { success: false, output: `Idea not found: ${ideaId}` };
|
|
34835
|
+
}
|
|
34836
|
+
const promptCtx = {
|
|
34837
|
+
idea,
|
|
34838
|
+
product: campaign.product,
|
|
34839
|
+
research: campaign.research,
|
|
34840
|
+
utmCampaign: campaign.utm_campaign
|
|
34841
|
+
};
|
|
34842
|
+
const prompt = getChannelPrompt(channelDraft.channel, promptCtx, channelDraft.subreddit);
|
|
34843
|
+
const result = await invokeAI({
|
|
34844
|
+
prompt,
|
|
34845
|
+
timeoutMs: 12e4,
|
|
34846
|
+
expectJson: true
|
|
34847
|
+
});
|
|
34848
|
+
if (result.error) {
|
|
34849
|
+
return { success: false, output: `AI invocation failed for ${channel}: ${result.error}` };
|
|
34850
|
+
}
|
|
34851
|
+
let content;
|
|
34852
|
+
try {
|
|
34853
|
+
const jsonMatch = result.output.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) || [null, result.output];
|
|
34854
|
+
content = JSON.parse((jsonMatch[1] || result.output).trim());
|
|
34855
|
+
} catch {
|
|
34856
|
+
const objMatch = result.output.match(/\{[\s\S]*\}/);
|
|
34857
|
+
if (objMatch) {
|
|
34858
|
+
try {
|
|
34859
|
+
content = JSON.parse(objMatch[0]);
|
|
34860
|
+
} catch {
|
|
34861
|
+
return { success: false, output: `Failed to parse AI output as JSON for ${channel}` };
|
|
34862
|
+
}
|
|
34863
|
+
} else {
|
|
34864
|
+
return { success: false, output: `No JSON found in AI output for ${channel}` };
|
|
34865
|
+
}
|
|
34866
|
+
}
|
|
34867
|
+
channelDraft.title = content.title || void 0;
|
|
34868
|
+
channelDraft.body = content.body || "";
|
|
34869
|
+
channelDraft.first_comment = content.first_comment || void 0;
|
|
34870
|
+
channelDraft.cta = content.cta || void 0;
|
|
34871
|
+
channelDraft.utm_url = content.utm_url || void 0;
|
|
34872
|
+
channelDraft.platform_constraints = content.platform_constraints || void 0;
|
|
34873
|
+
channelDraft.status = "drafted";
|
|
34874
|
+
channelDraft.drafted_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34875
|
+
const taskId = `growth-draft-${ideaId}-${channel}`;
|
|
34876
|
+
const subTask = campaign.sub_tasks.find((t) => t.task_id === taskId);
|
|
34877
|
+
if (subTask) subTask.status = "completed";
|
|
34878
|
+
if (channelDraft.channel === "twitter_thread" && channelDraft.body) {
|
|
34879
|
+
try {
|
|
34880
|
+
const threadTweets = content.thread_tweets || [];
|
|
34881
|
+
const draft = await createSocialDraft(channelDraft.body, {
|
|
34882
|
+
threadTweets: threadTweets.length > 0 ? threadTweets : void 0
|
|
34883
|
+
});
|
|
34884
|
+
channelDraft.social_draft_id = draft.id;
|
|
34885
|
+
} catch (err2) {
|
|
34886
|
+
console.error("[growth-campaign] Failed to create social draft:", err2);
|
|
34887
|
+
}
|
|
34888
|
+
}
|
|
34889
|
+
const allDrafted = campaign.channels.every((c2) => c2.status === "drafted" || c2.status === "published");
|
|
34890
|
+
if (allDrafted) {
|
|
34891
|
+
campaign.status = "publishing";
|
|
34892
|
+
}
|
|
34893
|
+
updateGrowthCampaign(campaign);
|
|
34894
|
+
const publishTaskId = `growth-publish-${ideaId}-${channel}`;
|
|
34895
|
+
const publishSubTask = campaign.sub_tasks.find((t) => t.task_id === publishTaskId);
|
|
34896
|
+
if (publishSubTask) {
|
|
34897
|
+
appendToTodo2([{
|
|
34898
|
+
taskId: publishTaskId,
|
|
34899
|
+
description: publishSubTask.description
|
|
34900
|
+
}]);
|
|
34901
|
+
}
|
|
34902
|
+
return {
|
|
34903
|
+
success: true,
|
|
34904
|
+
output: [
|
|
34905
|
+
`Draft generated for ${channelDraft.channel} (campaign ${campaign.id})`,
|
|
34906
|
+
channelDraft.title ? `Title: ${channelDraft.title}` : "",
|
|
34907
|
+
`Body length: ${channelDraft.body?.length || 0} chars`,
|
|
34908
|
+
channelDraft.social_draft_id ? `Twitter draft: ${channelDraft.social_draft_id}` : ""
|
|
34909
|
+
].filter(Boolean).join("\n")
|
|
34910
|
+
};
|
|
34911
|
+
}
|
|
34912
|
+
async function executePublish2(ideaId, channel) {
|
|
34913
|
+
const campaign = loadGrowthCampaignByIdeaId(ideaId);
|
|
34914
|
+
if (!campaign) {
|
|
34915
|
+
return { success: false, output: `No growth campaign found for idea ${ideaId}` };
|
|
34916
|
+
}
|
|
34917
|
+
const channelDraft = campaign.channels.find((c2) => {
|
|
34918
|
+
const label = c2.subreddit ? `${c2.channel}_${c2.subreddit}` : c2.channel;
|
|
34919
|
+
return label === channel;
|
|
34920
|
+
});
|
|
34921
|
+
if (!channelDraft) {
|
|
34922
|
+
return { success: false, output: `Channel ${channel} not found in campaign ${campaign.id}` };
|
|
34923
|
+
}
|
|
34924
|
+
if (channelDraft.status !== "drafted") {
|
|
34925
|
+
return { success: false, output: `Channel ${channel} is not drafted yet (status: ${channelDraft.status})` };
|
|
34926
|
+
}
|
|
34927
|
+
if (channelDraft.channel === "twitter_thread" && channelDraft.social_draft_id) {
|
|
34928
|
+
appendToTodo2([{
|
|
34929
|
+
taskId: `social-publish-${channelDraft.social_draft_id}`,
|
|
34930
|
+
description: `Publish Twitter thread for growth campaign "${campaign.source_idea_title}"`
|
|
34931
|
+
}]);
|
|
34932
|
+
channelDraft.status = "published";
|
|
34933
|
+
channelDraft.published_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34934
|
+
const publishTask2 = campaign.sub_tasks.find((t) => t.task_id === `growth-publish-${ideaId}-${channel}`);
|
|
34935
|
+
if (publishTask2) publishTask2.status = "completed";
|
|
34936
|
+
updateGrowthCampaign(campaign);
|
|
34937
|
+
return {
|
|
34938
|
+
success: true,
|
|
34939
|
+
output: `Twitter publish delegated to social-publish-${channelDraft.social_draft_id}`
|
|
34940
|
+
};
|
|
34941
|
+
}
|
|
34942
|
+
const outputDir = path20.join(DATA_DIR, "campaigns", "output");
|
|
34943
|
+
fs26.mkdirSync(outputDir, { recursive: true });
|
|
34944
|
+
const outputFile = path20.join(outputDir, `${campaign.id}-${channel}.md`);
|
|
34945
|
+
const outputLines = [
|
|
34946
|
+
`# ${channelDraft.channel.replace(/_/g, " ").toUpperCase()} \u2014 Ready to Publish`,
|
|
34947
|
+
"",
|
|
34948
|
+
`**Campaign:** ${campaign.source_idea_title}`,
|
|
34949
|
+
`**Channel:** ${channelDraft.channel}`,
|
|
34950
|
+
channelDraft.subreddit ? `**Subreddit:** r/${channelDraft.subreddit}` : "",
|
|
34951
|
+
`**Generated:** ${channelDraft.drafted_at || "unknown"}`,
|
|
34952
|
+
`**UTM URL:** ${channelDraft.utm_url || "N/A"}`,
|
|
34953
|
+
"",
|
|
34954
|
+
"---",
|
|
34955
|
+
""
|
|
34956
|
+
];
|
|
34957
|
+
if (channelDraft.title) {
|
|
34958
|
+
outputLines.push(`## Title
|
|
34959
|
+
|
|
34960
|
+
${channelDraft.title}
|
|
34961
|
+
`);
|
|
34962
|
+
}
|
|
34963
|
+
if (channelDraft.body) {
|
|
34964
|
+
outputLines.push(`## Content
|
|
34965
|
+
|
|
34966
|
+
${channelDraft.body}
|
|
34967
|
+
`);
|
|
34968
|
+
}
|
|
34969
|
+
if (channelDraft.first_comment) {
|
|
34970
|
+
outputLines.push(`## First Comment
|
|
34971
|
+
|
|
34972
|
+
${channelDraft.first_comment}
|
|
34973
|
+
`);
|
|
34974
|
+
}
|
|
34975
|
+
if (channelDraft.cta) {
|
|
34976
|
+
outputLines.push(`## CTA
|
|
34977
|
+
|
|
34978
|
+
${channelDraft.cta}
|
|
34979
|
+
`);
|
|
34980
|
+
}
|
|
34981
|
+
if (channelDraft.platform_constraints) {
|
|
34982
|
+
outputLines.push(`## Platform Notes
|
|
34983
|
+
|
|
34984
|
+
${channelDraft.platform_constraints}
|
|
34985
|
+
`);
|
|
34986
|
+
}
|
|
34987
|
+
fs26.writeFileSync(outputFile, outputLines.filter((l2) => l2 !== "").join("\n") + "\n");
|
|
34988
|
+
channelDraft.status = "published";
|
|
34989
|
+
channelDraft.published_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
34990
|
+
const publishTask = campaign.sub_tasks.find((t) => t.task_id === `growth-publish-${ideaId}-${channel}`);
|
|
34991
|
+
if (publishTask) publishTask.status = "completed";
|
|
34992
|
+
const allPublished = campaign.channels.every((c2) => c2.status === "published");
|
|
34993
|
+
if (allPublished) {
|
|
34994
|
+
campaign.status = "completed";
|
|
34995
|
+
try {
|
|
34996
|
+
const ideasData = JSON.parse(fs26.readFileSync(IDEAS_FILE, "utf-8"));
|
|
34997
|
+
const ideaIdx = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
34998
|
+
if (ideaIdx >= 0 && ideasData.ideas[ideaIdx].stage === "in_progress") {
|
|
34999
|
+
ideasData.ideas[ideaIdx].stage = "testing";
|
|
35000
|
+
ideasData.ideas[ideaIdx].updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
35001
|
+
fs26.writeFileSync(IDEAS_FILE, JSON.stringify(ideasData, null, 2) + "\n");
|
|
35002
|
+
}
|
|
35003
|
+
} catch {
|
|
35004
|
+
}
|
|
35005
|
+
}
|
|
35006
|
+
updateGrowthCampaign(campaign);
|
|
35007
|
+
return {
|
|
35008
|
+
success: true,
|
|
35009
|
+
output: [
|
|
35010
|
+
`Content ready for ${channelDraft.channel}: ${outputFile}`,
|
|
35011
|
+
allPublished ? "All channels published \u2014 campaign completed!" : ""
|
|
35012
|
+
].filter(Boolean).join("\n")
|
|
35013
|
+
};
|
|
35014
|
+
}
|
|
35015
|
+
function executeCheckStatus6() {
|
|
35016
|
+
const campaigns = listGrowthCampaigns();
|
|
35017
|
+
if (campaigns.length === 0) {
|
|
35018
|
+
return { success: true, output: "No growth campaigns found." };
|
|
35019
|
+
}
|
|
35020
|
+
const lines = ["## Growth Campaign Status\n"];
|
|
35021
|
+
for (const campaign of campaigns.slice(0, 10)) {
|
|
35022
|
+
const completed = campaign.sub_tasks.filter((t) => t.status === "completed").length;
|
|
35023
|
+
const total = campaign.sub_tasks.length;
|
|
35024
|
+
const pct = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
35025
|
+
lines.push(`### ${campaign.id} (${campaign.status})`);
|
|
35026
|
+
lines.push(`- Idea: ${campaign.source_idea_title}`);
|
|
35027
|
+
lines.push(`- Created: ${campaign.created_at.split("T")[0]}`);
|
|
35028
|
+
lines.push(`- Channels: ${campaign.channels.map((c2) => c2.channel).join(", ")}`);
|
|
35029
|
+
lines.push(`- Progress: ${completed}/${total} tasks (${pct}%)`);
|
|
35030
|
+
lines.push(`- Research: ${campaign.research ? "completed" : "pending"}`);
|
|
35031
|
+
for (const ch of campaign.channels) {
|
|
35032
|
+
lines.push(` - ${ch.channel}: ${ch.status}${ch.body ? ` (${ch.body.length} chars)` : ""}`);
|
|
35033
|
+
}
|
|
35034
|
+
lines.push("");
|
|
35035
|
+
}
|
|
35036
|
+
return { success: true, output: lines.join("\n") };
|
|
35037
|
+
}
|
|
35038
|
+
function readGrowthCampaignFreshness() {
|
|
35039
|
+
const campaigns = listGrowthCampaigns();
|
|
35040
|
+
return {
|
|
35041
|
+
active_growth_campaigns: campaigns.filter((c2) => c2.status !== "completed").length,
|
|
35042
|
+
completed_growth_campaigns: campaigns.filter((c2) => c2.status === "completed").length,
|
|
35043
|
+
last_growth_campaign_date: campaigns.length > 0 ? campaigns[0].created_at.split("T")[0] : null
|
|
35044
|
+
};
|
|
35045
|
+
}
|
|
35046
|
+
|
|
33973
35047
|
// scripts/heartbeat.ts
|
|
33974
35048
|
init_marketing_strategy();
|
|
33975
35049
|
init_ai_provider();
|
|
33976
35050
|
|
|
33977
35051
|
// scripts/lib/run.ts
|
|
33978
|
-
var
|
|
35052
|
+
var path21 = __toESM(require("path"));
|
|
33979
35053
|
function resolveScript(baseDir, scriptName) {
|
|
33980
|
-
const isCompiled =
|
|
35054
|
+
const isCompiled = path21.extname(__filename) === ".js";
|
|
33981
35055
|
const resolvedName = isCompiled ? scriptName.replace(/\.ts$/, ".js") : scriptName;
|
|
33982
|
-
const scriptPath =
|
|
35056
|
+
const scriptPath = path21.join(baseDir, resolvedName);
|
|
33983
35057
|
return isCompiled ? { command: "node", args: [scriptPath] } : { command: "npx", args: ["tsx", scriptPath] };
|
|
33984
35058
|
}
|
|
33985
35059
|
|
|
@@ -34033,8 +35107,8 @@ function groupTasksByConflicts(tasks) {
|
|
|
34033
35107
|
}
|
|
34034
35108
|
|
|
34035
35109
|
// scripts/lib/json-lock.ts
|
|
34036
|
-
var
|
|
34037
|
-
var
|
|
35110
|
+
var fs27 = __toESM(require("fs"));
|
|
35111
|
+
var path22 = __toESM(require("path"));
|
|
34038
35112
|
var DEFAULT_MAX_WAIT_MS = 1e4;
|
|
34039
35113
|
var BASE_BACKOFF_MS = 50;
|
|
34040
35114
|
var MAX_BACKOFF_MS = 150;
|
|
@@ -34042,10 +35116,10 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
34042
35116
|
const startTime = Date.now();
|
|
34043
35117
|
while (true) {
|
|
34044
35118
|
try {
|
|
34045
|
-
|
|
35119
|
+
fs27.mkdirSync(lockPath, { recursive: false });
|
|
34046
35120
|
try {
|
|
34047
|
-
|
|
34048
|
-
|
|
35121
|
+
fs27.writeFileSync(
|
|
35122
|
+
path22.join(lockPath, "owner"),
|
|
34049
35123
|
JSON.stringify({ pid: process.pid, acquired: Date.now() })
|
|
34050
35124
|
);
|
|
34051
35125
|
} catch {
|
|
@@ -34074,16 +35148,16 @@ function withFileLock(lockPath, fn, maxWaitMs = DEFAULT_MAX_WAIT_MS) {
|
|
|
34074
35148
|
}
|
|
34075
35149
|
function atomicWriteFileSync(filePath, content) {
|
|
34076
35150
|
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
34077
|
-
|
|
34078
|
-
|
|
35151
|
+
fs27.writeFileSync(tmpPath, content, "utf-8");
|
|
35152
|
+
fs27.renameSync(tmpPath, filePath);
|
|
34079
35153
|
}
|
|
34080
35154
|
function releaseLock(lockPath) {
|
|
34081
35155
|
try {
|
|
34082
|
-
const ownerFile =
|
|
34083
|
-
if (
|
|
34084
|
-
|
|
35156
|
+
const ownerFile = path22.join(lockPath, "owner");
|
|
35157
|
+
if (fs27.existsSync(ownerFile)) {
|
|
35158
|
+
fs27.unlinkSync(ownerFile);
|
|
34085
35159
|
}
|
|
34086
|
-
|
|
35160
|
+
fs27.rmdirSync(lockPath);
|
|
34087
35161
|
} catch {
|
|
34088
35162
|
}
|
|
34089
35163
|
}
|
|
@@ -34094,57 +35168,57 @@ function sleepSync(ms2) {
|
|
|
34094
35168
|
}
|
|
34095
35169
|
|
|
34096
35170
|
// scripts/lib/worktree.ts
|
|
34097
|
-
var
|
|
34098
|
-
var
|
|
35171
|
+
var fs29 = __toESM(require("fs"));
|
|
35172
|
+
var path24 = __toESM(require("path"));
|
|
34099
35173
|
|
|
34100
35174
|
// scripts/lib/git-utils.ts
|
|
34101
|
-
var
|
|
34102
|
-
var
|
|
34103
|
-
var
|
|
35175
|
+
var import_child_process11 = require("child_process");
|
|
35176
|
+
var fs28 = __toESM(require("fs"));
|
|
35177
|
+
var path23 = __toESM(require("path"));
|
|
34104
35178
|
function cleanGitState(workspacePath) {
|
|
34105
|
-
if (!
|
|
34106
|
-
const gitDir =
|
|
34107
|
-
if (!
|
|
35179
|
+
if (!fs28.existsSync(workspacePath)) return;
|
|
35180
|
+
const gitDir = path23.join(workspacePath, ".git");
|
|
35181
|
+
if (!fs28.existsSync(gitDir)) return;
|
|
34108
35182
|
let actualGitDir = gitDir;
|
|
34109
35183
|
try {
|
|
34110
|
-
const stat =
|
|
35184
|
+
const stat = fs28.statSync(gitDir);
|
|
34111
35185
|
if (stat.isFile()) {
|
|
34112
|
-
const content =
|
|
35186
|
+
const content = fs28.readFileSync(gitDir, "utf-8").trim();
|
|
34113
35187
|
const match = content.match(/^gitdir:\s+(.+)$/);
|
|
34114
35188
|
if (match) actualGitDir = match[1];
|
|
34115
35189
|
}
|
|
34116
35190
|
} catch {
|
|
34117
35191
|
return;
|
|
34118
35192
|
}
|
|
34119
|
-
const lockFile =
|
|
34120
|
-
if (
|
|
35193
|
+
const lockFile = path23.join(actualGitDir, "index.lock");
|
|
35194
|
+
if (fs28.existsSync(lockFile)) {
|
|
34121
35195
|
try {
|
|
34122
|
-
const result = (0,
|
|
35196
|
+
const result = (0, import_child_process11.execSync)(
|
|
34123
35197
|
`lsof "${lockFile}" 2>/dev/null || true`,
|
|
34124
35198
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
34125
35199
|
).trim();
|
|
34126
35200
|
if (!result) {
|
|
34127
|
-
|
|
35201
|
+
fs28.unlinkSync(lockFile);
|
|
34128
35202
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
34129
35203
|
}
|
|
34130
35204
|
} catch {
|
|
34131
35205
|
try {
|
|
34132
|
-
|
|
35206
|
+
fs28.unlinkSync(lockFile);
|
|
34133
35207
|
console.log(`[pre-flight] Removed stale .git/index.lock in ${workspacePath}`);
|
|
34134
35208
|
} catch {
|
|
34135
35209
|
}
|
|
34136
35210
|
}
|
|
34137
35211
|
}
|
|
34138
35212
|
const staleOps = [
|
|
34139
|
-
{ marker:
|
|
34140
|
-
{ marker:
|
|
34141
|
-
{ marker:
|
|
34142
|
-
{ marker:
|
|
35213
|
+
{ marker: path23.join(actualGitDir, "MERGE_HEAD"), abort: "git merge --abort" },
|
|
35214
|
+
{ marker: path23.join(actualGitDir, "rebase-merge"), abort: "git rebase --abort" },
|
|
35215
|
+
{ marker: path23.join(actualGitDir, "rebase-apply"), abort: "git rebase --abort" },
|
|
35216
|
+
{ marker: path23.join(actualGitDir, "CHERRY_PICK_HEAD"), abort: "git cherry-pick --abort" }
|
|
34143
35217
|
];
|
|
34144
35218
|
for (const { marker, abort } of staleOps) {
|
|
34145
|
-
if (
|
|
35219
|
+
if (fs28.existsSync(marker)) {
|
|
34146
35220
|
try {
|
|
34147
|
-
(0,
|
|
35221
|
+
(0, import_child_process11.execSync)(abort, { cwd: workspacePath, encoding: "utf-8", timeout: 1e4, stdio: "pipe" });
|
|
34148
35222
|
console.log(`[pre-flight] Aborted stale operation: ${abort} in ${workspacePath}`);
|
|
34149
35223
|
} catch {
|
|
34150
35224
|
}
|
|
@@ -34153,7 +35227,7 @@ function cleanGitState(workspacePath) {
|
|
|
34153
35227
|
}
|
|
34154
35228
|
function execGit(args2, cwd, timeoutMs) {
|
|
34155
35229
|
try {
|
|
34156
|
-
return (0,
|
|
35230
|
+
return (0, import_child_process11.execFileSync)("git", args2, {
|
|
34157
35231
|
cwd,
|
|
34158
35232
|
encoding: "utf-8",
|
|
34159
35233
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -34216,8 +35290,8 @@ function sanitizeForCommitMessage(msg) {
|
|
|
34216
35290
|
// scripts/lib/worktree.ts
|
|
34217
35291
|
function getWorktreePaths(workspaceDir, repoName) {
|
|
34218
35292
|
return {
|
|
34219
|
-
baseClonePath:
|
|
34220
|
-
worktreeContainerDir:
|
|
35293
|
+
baseClonePath: path24.join(workspaceDir, repoName),
|
|
35294
|
+
worktreeContainerDir: path24.join(workspaceDir, `${repoName}-worktrees`)
|
|
34221
35295
|
};
|
|
34222
35296
|
}
|
|
34223
35297
|
function syncBaseClone(baseClonePath, defaultBranch) {
|
|
@@ -34236,16 +35310,16 @@ function ensureIdeaBranch(baseClonePath, branchName, defaultBranch) {
|
|
|
34236
35310
|
}
|
|
34237
35311
|
}
|
|
34238
35312
|
function createWorktree(baseClonePath, worktreeContainerDir, ideaBranch, groupIndex) {
|
|
34239
|
-
if (!
|
|
34240
|
-
|
|
35313
|
+
if (!fs29.existsSync(worktreeContainerDir)) {
|
|
35314
|
+
fs29.mkdirSync(worktreeContainerDir, { recursive: true });
|
|
34241
35315
|
}
|
|
34242
35316
|
const tempBranch = `wt/${ideaBranch}-g${groupIndex}`;
|
|
34243
|
-
const worktreePath =
|
|
34244
|
-
if (
|
|
35317
|
+
const worktreePath = path24.join(worktreeContainerDir, `${ideaBranch}-g${groupIndex}`);
|
|
35318
|
+
if (fs29.existsSync(worktreePath)) {
|
|
34245
35319
|
try {
|
|
34246
35320
|
execGit(["worktree", "remove", "--force", worktreePath], baseClonePath, 1e4);
|
|
34247
35321
|
} catch {
|
|
34248
|
-
|
|
35322
|
+
fs29.rmSync(worktreePath, { recursive: true, force: true });
|
|
34249
35323
|
}
|
|
34250
35324
|
}
|
|
34251
35325
|
try {
|
|
@@ -34279,7 +35353,7 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
34279
35353
|
execGit(["worktree", "remove", "--force", worktreePath], baseClonePath, 1e4);
|
|
34280
35354
|
} catch {
|
|
34281
35355
|
try {
|
|
34282
|
-
|
|
35356
|
+
fs29.rmSync(worktreePath, { recursive: true, force: true });
|
|
34283
35357
|
} catch {
|
|
34284
35358
|
}
|
|
34285
35359
|
}
|
|
@@ -34289,12 +35363,12 @@ function removeWorktree(baseClonePath, worktreePath, tempBranch) {
|
|
|
34289
35363
|
}
|
|
34290
35364
|
}
|
|
34291
35365
|
function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
34292
|
-
if (
|
|
35366
|
+
if (fs29.existsSync(worktreeContainerDir)) {
|
|
34293
35367
|
try {
|
|
34294
|
-
const entries =
|
|
35368
|
+
const entries = fs29.readdirSync(worktreeContainerDir);
|
|
34295
35369
|
for (const entry of entries) {
|
|
34296
35370
|
if (entry.startsWith(`${ideaBranch}-g`)) {
|
|
34297
|
-
const wtPath =
|
|
35371
|
+
const wtPath = path24.join(worktreeContainerDir, entry);
|
|
34298
35372
|
const groupMatch = entry.match(/-g(\d+)$/);
|
|
34299
35373
|
const tempBranch = `wt/${ideaBranch}-g${groupMatch?.[1] ?? "0"}`;
|
|
34300
35374
|
removeWorktree(baseClonePath, wtPath, tempBranch);
|
|
@@ -34308,9 +35382,9 @@ function cleanupIdeaWorktrees(baseClonePath, worktreeContainerDir, ideaBranch) {
|
|
|
34308
35382
|
} catch {
|
|
34309
35383
|
}
|
|
34310
35384
|
try {
|
|
34311
|
-
const remaining =
|
|
35385
|
+
const remaining = fs29.readdirSync(worktreeContainerDir);
|
|
34312
35386
|
if (remaining.length === 0) {
|
|
34313
|
-
|
|
35387
|
+
fs29.rmdirSync(worktreeContainerDir);
|
|
34314
35388
|
}
|
|
34315
35389
|
} catch {
|
|
34316
35390
|
}
|
|
@@ -34320,24 +35394,24 @@ function getTempBranchName(ideaBranch, groupIndex) {
|
|
|
34320
35394
|
}
|
|
34321
35395
|
|
|
34322
35396
|
// scripts/lib/telemetry.ts
|
|
34323
|
-
var
|
|
34324
|
-
var
|
|
34325
|
-
var CONFIG_DIR2 =
|
|
35397
|
+
var fs30 = __toESM(require("fs"));
|
|
35398
|
+
var path25 = __toESM(require("path"));
|
|
35399
|
+
var CONFIG_DIR2 = path25.join(
|
|
34326
35400
|
process.env.HOME || process.env.USERPROFILE || "~",
|
|
34327
35401
|
".vibebusiness"
|
|
34328
35402
|
);
|
|
34329
|
-
var TELEMETRY_CONFIG_FILE =
|
|
34330
|
-
var EVENTS_FILE =
|
|
35403
|
+
var TELEMETRY_CONFIG_FILE = path25.join(CONFIG_DIR2, "telemetry.json");
|
|
35404
|
+
var EVENTS_FILE = path25.join(CONFIG_DIR2, "events.jsonl");
|
|
34331
35405
|
function getVersion() {
|
|
34332
35406
|
try {
|
|
34333
35407
|
let dir = __dirname;
|
|
34334
35408
|
for (let i = 0; i < 4; i++) {
|
|
34335
|
-
const pkgPath =
|
|
34336
|
-
if (
|
|
34337
|
-
const pkg = JSON.parse(
|
|
35409
|
+
const pkgPath = path25.join(dir, "package.json");
|
|
35410
|
+
if (fs30.existsSync(pkgPath)) {
|
|
35411
|
+
const pkg = JSON.parse(fs30.readFileSync(pkgPath, "utf-8"));
|
|
34338
35412
|
return pkg.version || "0.0.0";
|
|
34339
35413
|
}
|
|
34340
|
-
dir =
|
|
35414
|
+
dir = path25.dirname(dir);
|
|
34341
35415
|
}
|
|
34342
35416
|
} catch {
|
|
34343
35417
|
}
|
|
@@ -34345,8 +35419,8 @@ function getVersion() {
|
|
|
34345
35419
|
}
|
|
34346
35420
|
function getTelemetryConfig() {
|
|
34347
35421
|
try {
|
|
34348
|
-
if (!
|
|
34349
|
-
return JSON.parse(
|
|
35422
|
+
if (!fs30.existsSync(TELEMETRY_CONFIG_FILE)) return null;
|
|
35423
|
+
return JSON.parse(fs30.readFileSync(TELEMETRY_CONFIG_FILE, "utf-8"));
|
|
34350
35424
|
} catch {
|
|
34351
35425
|
return null;
|
|
34352
35426
|
}
|
|
@@ -34367,31 +35441,31 @@ function trackEvent(event, properties = {}) {
|
|
|
34367
35441
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34368
35442
|
version: getVersion()
|
|
34369
35443
|
};
|
|
34370
|
-
|
|
34371
|
-
|
|
35444
|
+
fs30.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
35445
|
+
fs30.appendFileSync(EVENTS_FILE, JSON.stringify(entry) + "\n");
|
|
34372
35446
|
} catch {
|
|
34373
35447
|
}
|
|
34374
35448
|
}
|
|
34375
35449
|
|
|
34376
35450
|
// src/lib/storage.ts
|
|
34377
|
-
var
|
|
34378
|
-
var
|
|
34379
|
-
function
|
|
34380
|
-
return process.env.AI_ANALYST_DATA_DIR ||
|
|
35451
|
+
var import_fs2 = require("fs");
|
|
35452
|
+
var import_path2 = __toESM(require("path"));
|
|
35453
|
+
function getDataDir2() {
|
|
35454
|
+
return process.env.AI_ANALYST_DATA_DIR || import_path2.default.join(process.cwd(), "data");
|
|
34381
35455
|
}
|
|
34382
35456
|
function getFilePath(filename) {
|
|
34383
|
-
return
|
|
35457
|
+
return import_path2.default.join(getDataDir2(), filename);
|
|
34384
35458
|
}
|
|
34385
35459
|
async function ensureDataDir() {
|
|
34386
35460
|
try {
|
|
34387
|
-
await
|
|
35461
|
+
await import_fs2.promises.access(getDataDir2());
|
|
34388
35462
|
} catch {
|
|
34389
|
-
await
|
|
35463
|
+
await import_fs2.promises.mkdir(getDataDir2(), { recursive: true });
|
|
34390
35464
|
}
|
|
34391
35465
|
}
|
|
34392
35466
|
async function readJsonFile(filePath, defaultValue) {
|
|
34393
35467
|
try {
|
|
34394
|
-
const content = await
|
|
35468
|
+
const content = await import_fs2.promises.readFile(filePath, "utf-8");
|
|
34395
35469
|
return JSON.parse(content);
|
|
34396
35470
|
} catch {
|
|
34397
35471
|
return defaultValue;
|
|
@@ -34399,7 +35473,7 @@ async function readJsonFile(filePath, defaultValue) {
|
|
|
34399
35473
|
}
|
|
34400
35474
|
async function writeJsonFile(filePath, data) {
|
|
34401
35475
|
await ensureDataDir();
|
|
34402
|
-
await
|
|
35476
|
+
await import_fs2.promises.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
34403
35477
|
}
|
|
34404
35478
|
async function getIdeas() {
|
|
34405
35479
|
const store = await readJsonFile(getFilePath("ideas.json"), { ideas: [] });
|
|
@@ -34600,8 +35674,8 @@ async function suggestEpics(opts) {
|
|
|
34600
35674
|
}
|
|
34601
35675
|
|
|
34602
35676
|
// src/lib/social-hooks.ts
|
|
34603
|
-
var
|
|
34604
|
-
var
|
|
35677
|
+
var import_fs3 = require("fs");
|
|
35678
|
+
var import_path3 = __toESM(require("path"));
|
|
34605
35679
|
|
|
34606
35680
|
// src/lib/social.ts
|
|
34607
35681
|
function scoreDraft2(text) {
|
|
@@ -34641,10 +35715,10 @@ function emptySocialState() {
|
|
|
34641
35715
|
}
|
|
34642
35716
|
async function triggerSocialDraftForShip(ideaId, idea) {
|
|
34643
35717
|
try {
|
|
34644
|
-
const socialFile =
|
|
35718
|
+
const socialFile = import_path3.default.join(getDataDir2(), "social.json");
|
|
34645
35719
|
let state;
|
|
34646
35720
|
try {
|
|
34647
|
-
const raw = await
|
|
35721
|
+
const raw = await import_fs3.promises.readFile(socialFile, "utf-8");
|
|
34648
35722
|
state = JSON.parse(raw);
|
|
34649
35723
|
} catch {
|
|
34650
35724
|
state = emptySocialState();
|
|
@@ -34677,8 +35751,8 @@ async function triggerSocialDraftForShip(ideaId, idea) {
|
|
|
34677
35751
|
const draft = isHighPotential ? { ...draftBase, high_potential: true } : draftBase;
|
|
34678
35752
|
state.drafts.push(draft);
|
|
34679
35753
|
state.last_updated = now;
|
|
34680
|
-
await
|
|
34681
|
-
await
|
|
35754
|
+
await import_fs3.promises.mkdir(import_path3.default.dirname(socialFile), { recursive: true });
|
|
35755
|
+
await import_fs3.promises.writeFile(socialFile, JSON.stringify(state, null, 2), "utf-8");
|
|
34682
35756
|
console.log(`[social-hooks] ship draft created draft_id=${draftId} idea_id=${ideaId}`);
|
|
34683
35757
|
} catch (err2) {
|
|
34684
35758
|
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
@@ -34687,22 +35761,22 @@ async function triggerSocialDraftForShip(ideaId, idea) {
|
|
|
34687
35761
|
}
|
|
34688
35762
|
|
|
34689
35763
|
// scripts/lib/scaffold.ts
|
|
34690
|
-
var
|
|
34691
|
-
var
|
|
35764
|
+
var fs33 = __toESM(require("fs"));
|
|
35765
|
+
var path28 = __toESM(require("path"));
|
|
34692
35766
|
function scaffoldSlashCommands(rootDir) {
|
|
34693
|
-
let templatesDir =
|
|
34694
|
-
if (!
|
|
34695
|
-
templatesDir =
|
|
34696
|
-
}
|
|
34697
|
-
if (!
|
|
34698
|
-
const targetDir =
|
|
34699
|
-
|
|
34700
|
-
const templates =
|
|
35767
|
+
let templatesDir = path28.join(__dirname, "..", "..", "templates", "commands");
|
|
35768
|
+
if (!fs33.existsSync(templatesDir)) {
|
|
35769
|
+
templatesDir = path28.join(__dirname, "..", "..", "..", "templates", "commands");
|
|
35770
|
+
}
|
|
35771
|
+
if (!fs33.existsSync(templatesDir)) return;
|
|
35772
|
+
const targetDir = path28.join(rootDir, ".claude", "commands");
|
|
35773
|
+
fs33.mkdirSync(targetDir, { recursive: true });
|
|
35774
|
+
const templates = fs33.readdirSync(templatesDir).filter((f) => f.endsWith(".md"));
|
|
34701
35775
|
let copied = 0;
|
|
34702
35776
|
for (const file of templates) {
|
|
34703
|
-
const src =
|
|
34704
|
-
const dest =
|
|
34705
|
-
|
|
35777
|
+
const src = path28.join(templatesDir, file);
|
|
35778
|
+
const dest = path28.join(targetDir, file);
|
|
35779
|
+
fs33.copyFileSync(src, dest);
|
|
34706
35780
|
copied++;
|
|
34707
35781
|
}
|
|
34708
35782
|
if (copied > 0) {
|
|
@@ -34711,23 +35785,23 @@ function scaffoldSlashCommands(rootDir) {
|
|
|
34711
35785
|
}
|
|
34712
35786
|
|
|
34713
35787
|
// scripts/lib/vibe-credits.ts
|
|
34714
|
-
var
|
|
34715
|
-
var
|
|
35788
|
+
var fs34 = __toESM(require("fs"));
|
|
35789
|
+
var path29 = __toESM(require("path"));
|
|
34716
35790
|
var WORKER_URL = "https://vibe-credits.luis-e13.workers.dev";
|
|
34717
|
-
var CONFIG_DIR3 =
|
|
34718
|
-
var BUFFER_FILE =
|
|
35791
|
+
var CONFIG_DIR3 = path29.join(process.env.HOME || "", ".vibebusiness");
|
|
35792
|
+
var BUFFER_FILE = path29.join(CONFIG_DIR3, "vibe-buffer.json");
|
|
34719
35793
|
var MAX_BUFFER = 5;
|
|
34720
35794
|
var REQUEST_TIMEOUT_MS = 5e3;
|
|
34721
35795
|
function getClientVersion() {
|
|
34722
35796
|
try {
|
|
34723
35797
|
let dir = __dirname;
|
|
34724
35798
|
for (let i = 0; i < 4; i++) {
|
|
34725
|
-
const pkgPath =
|
|
34726
|
-
if (
|
|
34727
|
-
const pkg = JSON.parse(
|
|
35799
|
+
const pkgPath = path29.join(dir, "package.json");
|
|
35800
|
+
if (fs34.existsSync(pkgPath)) {
|
|
35801
|
+
const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
|
|
34728
35802
|
return pkg.version || "0.0.0";
|
|
34729
35803
|
}
|
|
34730
|
-
dir =
|
|
35804
|
+
dir = path29.dirname(dir);
|
|
34731
35805
|
}
|
|
34732
35806
|
} catch {
|
|
34733
35807
|
}
|
|
@@ -34736,18 +35810,18 @@ function getClientVersion() {
|
|
|
34736
35810
|
var CLIENT_VERSION = getClientVersion();
|
|
34737
35811
|
function loadBuffer() {
|
|
34738
35812
|
try {
|
|
34739
|
-
if (
|
|
34740
|
-
return JSON.parse(
|
|
35813
|
+
if (fs34.existsSync(BUFFER_FILE)) {
|
|
35814
|
+
return JSON.parse(fs34.readFileSync(BUFFER_FILE, "utf-8"));
|
|
34741
35815
|
}
|
|
34742
35816
|
} catch {
|
|
34743
35817
|
}
|
|
34744
35818
|
return { vibes: 0, last_synced: "" };
|
|
34745
35819
|
}
|
|
34746
35820
|
function saveBuffer(buffer) {
|
|
34747
|
-
if (!
|
|
34748
|
-
|
|
35821
|
+
if (!fs34.existsSync(CONFIG_DIR3)) {
|
|
35822
|
+
fs34.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
34749
35823
|
}
|
|
34750
|
-
|
|
35824
|
+
fs34.writeFileSync(BUFFER_FILE, JSON.stringify(buffer, null, 2));
|
|
34751
35825
|
}
|
|
34752
35826
|
function syncBuffer(vibesRemaining) {
|
|
34753
35827
|
const buffered = Math.min(vibesRemaining, MAX_BUFFER);
|
|
@@ -34821,14 +35895,14 @@ async function consumeVibe(instanceId) {
|
|
|
34821
35895
|
}
|
|
34822
35896
|
|
|
34823
35897
|
// scripts/lib/license.ts
|
|
34824
|
-
var
|
|
34825
|
-
var
|
|
34826
|
-
var CONFIG_DIR4 =
|
|
34827
|
-
var LICENSE_FILE =
|
|
35898
|
+
var fs35 = __toESM(require("fs"));
|
|
35899
|
+
var path30 = __toESM(require("path"));
|
|
35900
|
+
var CONFIG_DIR4 = path30.join(process.env.HOME || "", ".vibebusiness");
|
|
35901
|
+
var LICENSE_FILE = path30.join(CONFIG_DIR4, "license.json");
|
|
34828
35902
|
function loadStoredLicense() {
|
|
34829
35903
|
try {
|
|
34830
|
-
if (
|
|
34831
|
-
return JSON.parse(
|
|
35904
|
+
if (fs35.existsSync(LICENSE_FILE)) {
|
|
35905
|
+
return JSON.parse(fs35.readFileSync(LICENSE_FILE, "utf-8"));
|
|
34832
35906
|
}
|
|
34833
35907
|
} catch {
|
|
34834
35908
|
}
|
|
@@ -34846,14 +35920,361 @@ function getInstanceId() {
|
|
|
34846
35920
|
return generateInstanceId();
|
|
34847
35921
|
}
|
|
34848
35922
|
|
|
35923
|
+
// scripts/lib/vision/validation.ts
|
|
35924
|
+
var fs38 = __toESM(require("fs"));
|
|
35925
|
+
init_paths();
|
|
35926
|
+
|
|
35927
|
+
// scripts/lib/vision/define.ts
|
|
35928
|
+
var fs36 = __toESM(require("fs"));
|
|
35929
|
+
init_ai_provider();
|
|
35930
|
+
init_paths();
|
|
35931
|
+
var PROJECT_DIR2 = PRODUCT_VISION_FILE.replace(/\/data\/product-vision\.json$/, "");
|
|
35932
|
+
function loadProductVision() {
|
|
35933
|
+
if (!fs36.existsSync(PRODUCT_VISION_FILE)) return null;
|
|
35934
|
+
try {
|
|
35935
|
+
return JSON.parse(fs36.readFileSync(PRODUCT_VISION_FILE, "utf-8"));
|
|
35936
|
+
} catch {
|
|
35937
|
+
return null;
|
|
35938
|
+
}
|
|
35939
|
+
}
|
|
35940
|
+
|
|
35941
|
+
// scripts/lib/vision/hypotheses.ts
|
|
35942
|
+
var fs37 = __toESM(require("fs"));
|
|
35943
|
+
init_paths();
|
|
35944
|
+
function loadJson(filePath, defaultValue) {
|
|
35945
|
+
try {
|
|
35946
|
+
if (!fs37.existsSync(filePath)) return defaultValue;
|
|
35947
|
+
return JSON.parse(fs37.readFileSync(filePath, "utf-8"));
|
|
35948
|
+
} catch {
|
|
35949
|
+
return defaultValue;
|
|
35950
|
+
}
|
|
35951
|
+
}
|
|
35952
|
+
function saveJson(filePath, data) {
|
|
35953
|
+
fs37.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
35954
|
+
}
|
|
35955
|
+
function updateHypothesisEvidence(hypothesisId, evidence, direction) {
|
|
35956
|
+
const store = loadJson(HYPOTHESES_FILE, { hypotheses: [] });
|
|
35957
|
+
const hypothesis = store.hypotheses.find((h3) => h3.id === hypothesisId);
|
|
35958
|
+
if (!hypothesis) return false;
|
|
35959
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
35960
|
+
const entry = `[${now.split("T")[0]}] ${evidence}`;
|
|
35961
|
+
if (direction === "for") {
|
|
35962
|
+
hypothesis.evidence_for.push(entry);
|
|
35963
|
+
} else {
|
|
35964
|
+
hypothesis.evidence_against.push(entry);
|
|
35965
|
+
}
|
|
35966
|
+
const forCount = hypothesis.evidence_for.length;
|
|
35967
|
+
const againstCount = hypothesis.evidence_against.length;
|
|
35968
|
+
if (forCount >= 3 && againstCount === 0) {
|
|
35969
|
+
hypothesis.status = "validated";
|
|
35970
|
+
} else if (againstCount >= 2 && forCount <= againstCount) {
|
|
35971
|
+
hypothesis.status = "invalidated";
|
|
35972
|
+
} else if (forCount > 0 || againstCount > 0) {
|
|
35973
|
+
hypothesis.status = "testing";
|
|
35974
|
+
}
|
|
35975
|
+
hypothesis.updated_at = now;
|
|
35976
|
+
saveJson(HYPOTHESES_FILE, store);
|
|
35977
|
+
return true;
|
|
35978
|
+
}
|
|
35979
|
+
|
|
35980
|
+
// scripts/lib/vision/validation.ts
|
|
35981
|
+
function loadJson2(filePath, defaultValue) {
|
|
35982
|
+
try {
|
|
35983
|
+
if (!fs38.existsSync(filePath)) return defaultValue;
|
|
35984
|
+
return JSON.parse(fs38.readFileSync(filePath, "utf-8"));
|
|
35985
|
+
} catch {
|
|
35986
|
+
return defaultValue;
|
|
35987
|
+
}
|
|
35988
|
+
}
|
|
35989
|
+
function evaluateVisionHealth(vision, hypotheses) {
|
|
35990
|
+
const v2 = vision || loadProductVision();
|
|
35991
|
+
const store = loadJson2(HYPOTHESES_FILE, { hypotheses: [] });
|
|
35992
|
+
const hyps = hypotheses || store.hypotheses;
|
|
35993
|
+
if (!v2 || hyps.length === 0) {
|
|
35994
|
+
return {
|
|
35995
|
+
confidence: 0,
|
|
35996
|
+
summary: v2 ? "No hypotheses to evaluate" : "No product vision defined",
|
|
35997
|
+
breakdown: { stated: 0, testing: 0, validated: 0, invalidated: 0, deferred: 0 }
|
|
35998
|
+
};
|
|
35999
|
+
}
|
|
36000
|
+
const breakdown = {
|
|
36001
|
+
stated: 0,
|
|
36002
|
+
testing: 0,
|
|
36003
|
+
validated: 0,
|
|
36004
|
+
invalidated: 0,
|
|
36005
|
+
deferred: 0
|
|
36006
|
+
};
|
|
36007
|
+
for (const h3 of hyps) {
|
|
36008
|
+
breakdown[h3.status] = (breakdown[h3.status] || 0) + 1;
|
|
36009
|
+
}
|
|
36010
|
+
const activeHyps = hyps.filter((h3) => h3.status !== "deferred");
|
|
36011
|
+
if (activeHyps.length === 0) {
|
|
36012
|
+
return { confidence: 50, summary: "All hypotheses deferred", breakdown };
|
|
36013
|
+
}
|
|
36014
|
+
const validated = breakdown.validated || 0;
|
|
36015
|
+
const invalidated = breakdown.invalidated || 0;
|
|
36016
|
+
const testing = breakdown.testing || 0;
|
|
36017
|
+
const stated = breakdown.stated || 0;
|
|
36018
|
+
const total = activeHyps.length;
|
|
36019
|
+
const validatedRatio = validated / total;
|
|
36020
|
+
const invalidatedRatio = invalidated / total;
|
|
36021
|
+
const testedRatio = (validated + invalidated + testing) / total;
|
|
36022
|
+
let confidence = 50;
|
|
36023
|
+
confidence += Math.round(validatedRatio * 40);
|
|
36024
|
+
confidence -= Math.round(invalidatedRatio * 30);
|
|
36025
|
+
confidence += Math.round(testedRatio * 10);
|
|
36026
|
+
confidence = Math.max(0, Math.min(100, confidence));
|
|
36027
|
+
const parts = [];
|
|
36028
|
+
if (validated > 0) parts.push(`${validated} validated`);
|
|
36029
|
+
if (testing > 0) parts.push(`${testing} testing`);
|
|
36030
|
+
if (stated > 0) parts.push(`${stated} untested`);
|
|
36031
|
+
if (invalidated > 0) parts.push(`${invalidated} invalidated`);
|
|
36032
|
+
const summary = `Vision confidence: ${confidence}/100 (${parts.join(", ")})`;
|
|
36033
|
+
return { confidence, summary, breakdown };
|
|
36034
|
+
}
|
|
36035
|
+
function suggestVisionPivot(invalidatedHypotheses) {
|
|
36036
|
+
if (invalidatedHypotheses.length === 0) {
|
|
36037
|
+
return { pivot_areas: [], suggestions: [] };
|
|
36038
|
+
}
|
|
36039
|
+
const pivotAreas = /* @__PURE__ */ new Set();
|
|
36040
|
+
const suggestions = [];
|
|
36041
|
+
for (const h3 of invalidatedHypotheses) {
|
|
36042
|
+
switch (h3.funnel_stage) {
|
|
36043
|
+
case "acquisition":
|
|
36044
|
+
pivotAreas.add("market_category");
|
|
36045
|
+
pivotAreas.add("best_fit_customers");
|
|
36046
|
+
suggestions.push(`Acquisition hypothesis "${h3.title}" failed \u2014 reconsider target audience or market category`);
|
|
36047
|
+
break;
|
|
36048
|
+
case "engagement":
|
|
36049
|
+
pivotAreas.add("value_delivered");
|
|
36050
|
+
pivotAreas.add("unique_attributes");
|
|
36051
|
+
suggestions.push(`Engagement hypothesis "${h3.title}" failed \u2014 reconsider value proposition or unique attributes`);
|
|
36052
|
+
break;
|
|
36053
|
+
case "conversion":
|
|
36054
|
+
pivotAreas.add("call_to_action");
|
|
36055
|
+
pivotAreas.add("plan");
|
|
36056
|
+
suggestions.push(`Conversion hypothesis "${h3.title}" failed \u2014 reconsider onboarding plan or CTA`);
|
|
36057
|
+
break;
|
|
36058
|
+
case "monetization":
|
|
36059
|
+
pivotAreas.add("positioning_statement");
|
|
36060
|
+
suggestions.push(`Monetization hypothesis "${h3.title}" failed \u2014 reconsider pricing or positioning`);
|
|
36061
|
+
break;
|
|
36062
|
+
default:
|
|
36063
|
+
pivotAreas.add("general");
|
|
36064
|
+
suggestions.push(`Hypothesis "${h3.title}" (${h3.funnel_stage}) failed \u2014 review vision alignment`);
|
|
36065
|
+
}
|
|
36066
|
+
}
|
|
36067
|
+
return {
|
|
36068
|
+
pivot_areas: Array.from(pivotAreas),
|
|
36069
|
+
suggestions
|
|
36070
|
+
};
|
|
36071
|
+
}
|
|
36072
|
+
function generateValidationReport() {
|
|
36073
|
+
const vision = loadProductVision();
|
|
36074
|
+
const store = loadJson2(HYPOTHESES_FILE, { hypotheses: [] });
|
|
36075
|
+
const hyps = store.hypotheses;
|
|
36076
|
+
const health = evaluateVisionHealth(vision, hyps);
|
|
36077
|
+
const uncertain = hyps.filter((h3) => h3.status === "stated" || h3.status === "testing").sort((a, b) => {
|
|
36078
|
+
const aEvidence = a.evidence_for.length + a.evidence_against.length;
|
|
36079
|
+
const bEvidence = b.evidence_for.length + b.evidence_against.length;
|
|
36080
|
+
return aEvidence - bEvidence;
|
|
36081
|
+
}).slice(0, 3).map((h3) => ({ id: h3.id, title: h3.title, funnel_stage: h3.funnel_stage }));
|
|
36082
|
+
const invalidated = hyps.filter((h3) => h3.status === "invalidated");
|
|
36083
|
+
const pivot = suggestVisionPivot(invalidated);
|
|
36084
|
+
return {
|
|
36085
|
+
vision_exists: !!vision,
|
|
36086
|
+
one_liner: vision?.one_liner || null,
|
|
36087
|
+
focus_metric: vision?.focus_metric || null,
|
|
36088
|
+
confidence_score: health.confidence,
|
|
36089
|
+
hypotheses: {
|
|
36090
|
+
total: hyps.length,
|
|
36091
|
+
validated: health.breakdown.validated || 0,
|
|
36092
|
+
invalidated: health.breakdown.invalidated || 0,
|
|
36093
|
+
testing: health.breakdown.testing || 0,
|
|
36094
|
+
untested: health.breakdown.stated || 0,
|
|
36095
|
+
top_uncertain: uncertain
|
|
36096
|
+
},
|
|
36097
|
+
pivot_needed: invalidated.length >= 2,
|
|
36098
|
+
pivot_suggestions: pivot.suggestions
|
|
36099
|
+
};
|
|
36100
|
+
}
|
|
36101
|
+
function updateHypothesisFromEvaluation(hypothesisId, verificationStatus, evaluationSummary) {
|
|
36102
|
+
if (!hypothesisId) return false;
|
|
36103
|
+
const direction = verificationStatus === "validated" ? "for" : verificationStatus === "invalidated" ? "against" : verificationStatus === "needs_investigation" ? "against" : "for";
|
|
36104
|
+
const evidence = `[${verificationStatus}] ${evaluationSummary.slice(0, 200)}`;
|
|
36105
|
+
return updateHypothesisEvidence(hypothesisId, evidence, direction);
|
|
36106
|
+
}
|
|
36107
|
+
|
|
36108
|
+
// scripts/lib/vision/acceptance-tests.ts
|
|
36109
|
+
var fs39 = __toESM(require("fs"));
|
|
36110
|
+
var path31 = __toESM(require("path"));
|
|
36111
|
+
var import_child_process12 = require("child_process");
|
|
36112
|
+
init_ai_provider();
|
|
36113
|
+
var PROJECT_DIR3 = path31.resolve(__dirname, "..", "..", "..");
|
|
36114
|
+
async function generateAcceptanceTests(idea, targetRepo) {
|
|
36115
|
+
if (!idea.success_metrics || idea.success_metrics.length === 0) {
|
|
36116
|
+
return { file: null, content: "", error: "No success metrics defined" };
|
|
36117
|
+
}
|
|
36118
|
+
const workspacePath = targetRepo.path;
|
|
36119
|
+
let existingPatterns = "";
|
|
36120
|
+
try {
|
|
36121
|
+
const e2eDir = path31.join(workspacePath, "e2e");
|
|
36122
|
+
const testsDir = path31.join(workspacePath, "tests");
|
|
36123
|
+
const searchDir = fs39.existsSync(e2eDir) ? e2eDir : fs39.existsSync(testsDir) ? testsDir : null;
|
|
36124
|
+
if (searchDir) {
|
|
36125
|
+
const files = fs39.readdirSync(searchDir).filter((f) => f.endsWith(".spec.ts") || f.endsWith(".test.ts"));
|
|
36126
|
+
if (files.length > 0) {
|
|
36127
|
+
const sample = fs39.readFileSync(path31.join(searchDir, files[0]), "utf-8").slice(0, 2e3);
|
|
36128
|
+
existingPatterns = `
|
|
36129
|
+
EXISTING TEST PATTERN (follow this style):
|
|
36130
|
+
\`\`\`typescript
|
|
36131
|
+
${sample}
|
|
36132
|
+
\`\`\``;
|
|
36133
|
+
}
|
|
36134
|
+
}
|
|
36135
|
+
} catch {
|
|
36136
|
+
}
|
|
36137
|
+
const prompt = `You are a QA engineer. Generate Playwright acceptance tests for a feature.
|
|
36138
|
+
|
|
36139
|
+
IDEA: ${idea.title}
|
|
36140
|
+
SUMMARY: ${idea.summary}
|
|
36141
|
+
|
|
36142
|
+
SUCCESS METRICS (each must have at least one test):
|
|
36143
|
+
${idea.success_metrics.map((m2, i) => `${i + 1}. ${m2}`).join("\n")}
|
|
36144
|
+
|
|
36145
|
+
IMPLEMENTATION PLAN:
|
|
36146
|
+
${idea.implementation_plan}
|
|
36147
|
+
${existingPatterns}
|
|
36148
|
+
|
|
36149
|
+
Generate a complete Playwright test file. Requirements:
|
|
36150
|
+
- Use @playwright/test imports
|
|
36151
|
+
- Each success metric should map to at least one test
|
|
36152
|
+
- Use descriptive test names that reference the metric
|
|
36153
|
+
- Use page.goto(), locators, and expect() assertions
|
|
36154
|
+
- Tests should be independent (no shared state)
|
|
36155
|
+
- Include reasonable timeouts and waits
|
|
36156
|
+
- Use data-testid selectors when specific selectors are unknown
|
|
36157
|
+
|
|
36158
|
+
Respond with ONLY the TypeScript code (no markdown code blocks, no explanation):`;
|
|
36159
|
+
const aiResult = await invokeAI({
|
|
36160
|
+
prompt,
|
|
36161
|
+
cwd: PROJECT_DIR3,
|
|
36162
|
+
timeoutMs: 12e4,
|
|
36163
|
+
useStdin: true
|
|
36164
|
+
});
|
|
36165
|
+
if (aiResult.error || !aiResult.output.trim()) {
|
|
36166
|
+
return { file: null, content: "", error: aiResult.error || "empty output" };
|
|
36167
|
+
}
|
|
36168
|
+
let content = aiResult.output.trim();
|
|
36169
|
+
const codeMatch = content.match(/```(?:typescript|ts)?\s*([\s\S]*?)```/);
|
|
36170
|
+
if (codeMatch) {
|
|
36171
|
+
content = codeMatch[1].trim();
|
|
36172
|
+
}
|
|
36173
|
+
const slug = idea.id.replace(/^idea-/, "").replace(/[^a-z0-9-]/g, "-").slice(0, 40);
|
|
36174
|
+
const fileName = `acceptance-${slug}.spec.ts`;
|
|
36175
|
+
return { file: fileName, content };
|
|
36176
|
+
}
|
|
36177
|
+
function writeAcceptanceTests(testResult, targetRepoPath) {
|
|
36178
|
+
if (!testResult.file || !testResult.content) return null;
|
|
36179
|
+
const e2eDir = path31.join(targetRepoPath, "e2e");
|
|
36180
|
+
if (!fs39.existsSync(e2eDir)) {
|
|
36181
|
+
fs39.mkdirSync(e2eDir, { recursive: true });
|
|
36182
|
+
}
|
|
36183
|
+
const filePath = path31.join(e2eDir, testResult.file);
|
|
36184
|
+
fs39.writeFileSync(filePath, testResult.content + "\n");
|
|
36185
|
+
return filePath;
|
|
36186
|
+
}
|
|
36187
|
+
function runAcceptanceTests(testFile, workspacePath) {
|
|
36188
|
+
const absolutePath = path31.isAbsolute(testFile) ? testFile : path31.join(workspacePath, testFile);
|
|
36189
|
+
if (!fs39.existsSync(absolutePath)) {
|
|
36190
|
+
return {
|
|
36191
|
+
passed: false,
|
|
36192
|
+
passCount: 0,
|
|
36193
|
+
failCount: 0,
|
|
36194
|
+
totalCount: 0,
|
|
36195
|
+
output: `Test file not found: ${absolutePath}`
|
|
36196
|
+
};
|
|
36197
|
+
}
|
|
36198
|
+
try {
|
|
36199
|
+
const npxPath = path31.join(workspacePath, "node_modules", ".bin", "playwright");
|
|
36200
|
+
const useLocalPlaywright = fs39.existsSync(npxPath);
|
|
36201
|
+
const cmd = useLocalPlaywright ? `npx playwright test "${absolutePath}" --reporter=json` : `npx playwright test "${absolutePath}" --reporter=json`;
|
|
36202
|
+
const output = (0, import_child_process12.execSync)(cmd, {
|
|
36203
|
+
cwd: workspacePath,
|
|
36204
|
+
encoding: "utf-8",
|
|
36205
|
+
timeout: 3e5,
|
|
36206
|
+
// 5 min timeout
|
|
36207
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
36208
|
+
});
|
|
36209
|
+
try {
|
|
36210
|
+
const report = JSON.parse(output);
|
|
36211
|
+
const suites = report.suites || [];
|
|
36212
|
+
let passCount = 0;
|
|
36213
|
+
let failCount = 0;
|
|
36214
|
+
const countTests = (suite) => {
|
|
36215
|
+
for (const spec of suite.specs || []) {
|
|
36216
|
+
for (const test of spec.tests || []) {
|
|
36217
|
+
const lastResult = test.results?.[test.results.length - 1];
|
|
36218
|
+
if (lastResult?.status === "passed") passCount++;
|
|
36219
|
+
else failCount++;
|
|
36220
|
+
}
|
|
36221
|
+
}
|
|
36222
|
+
for (const child of suite.suites || []) {
|
|
36223
|
+
countTests(child);
|
|
36224
|
+
}
|
|
36225
|
+
};
|
|
36226
|
+
for (const suite of suites) countTests(suite);
|
|
36227
|
+
const totalCount = passCount + failCount;
|
|
36228
|
+
return {
|
|
36229
|
+
passed: failCount === 0 && totalCount > 0,
|
|
36230
|
+
passCount,
|
|
36231
|
+
failCount,
|
|
36232
|
+
totalCount,
|
|
36233
|
+
output: output.slice(-2e3)
|
|
36234
|
+
};
|
|
36235
|
+
} catch {
|
|
36236
|
+
return {
|
|
36237
|
+
passed: true,
|
|
36238
|
+
passCount: 1,
|
|
36239
|
+
failCount: 0,
|
|
36240
|
+
totalCount: 1,
|
|
36241
|
+
output: output.slice(-2e3)
|
|
36242
|
+
};
|
|
36243
|
+
}
|
|
36244
|
+
} catch (error) {
|
|
36245
|
+
const err2 = error;
|
|
36246
|
+
const output = (err2.stdout || "") + "\n" + (err2.stderr || "");
|
|
36247
|
+
let passCount = 0;
|
|
36248
|
+
let failCount = 1;
|
|
36249
|
+
const passMatch = output.match(/(\d+) passed/);
|
|
36250
|
+
const failMatch = output.match(/(\d+) failed/);
|
|
36251
|
+
if (passMatch) passCount = parseInt(passMatch[1]);
|
|
36252
|
+
if (failMatch) failCount = parseInt(failMatch[1]);
|
|
36253
|
+
const totalCount = passCount + failCount || 1;
|
|
36254
|
+
return {
|
|
36255
|
+
passed: false,
|
|
36256
|
+
passCount,
|
|
36257
|
+
failCount,
|
|
36258
|
+
totalCount,
|
|
36259
|
+
output: output.slice(-2e3)
|
|
36260
|
+
};
|
|
36261
|
+
}
|
|
36262
|
+
}
|
|
36263
|
+
async function generateAndWriteAcceptanceTests(idea, targetRepo, workspacePath) {
|
|
36264
|
+
const result = await generateAcceptanceTests(idea, targetRepo);
|
|
36265
|
+
if (!result.file) return result;
|
|
36266
|
+
const writtenPath = writeAcceptanceTests(result, workspacePath);
|
|
36267
|
+
return { ...result, writtenPath: writtenPath || void 0 };
|
|
36268
|
+
}
|
|
36269
|
+
|
|
34849
36270
|
// scripts/heartbeat.ts
|
|
34850
36271
|
init_paths();
|
|
34851
36272
|
var ROOT_DIR = PROJECT_DIR;
|
|
34852
|
-
var execAsync = (0, import_util.promisify)(
|
|
36273
|
+
var execAsync = (0, import_util.promisify)(import_child_process13.exec);
|
|
34853
36274
|
var DEFAULT_MAX_DECOMP_ATTEMPTS = 2;
|
|
34854
36275
|
function spawnAsync(cmd, args2, options) {
|
|
34855
|
-
return new Promise((
|
|
34856
|
-
const child = (0,
|
|
36276
|
+
return new Promise((resolve4, reject) => {
|
|
36277
|
+
const child = (0, import_child_process13.spawn)(cmd, args2, {
|
|
34857
36278
|
cwd: options.cwd,
|
|
34858
36279
|
stdio: ["pipe", "pipe", "pipe"]
|
|
34859
36280
|
});
|
|
@@ -34899,7 +36320,7 @@ function spawnAsync(cmd, args2, options) {
|
|
|
34899
36320
|
reject(err2);
|
|
34900
36321
|
return;
|
|
34901
36322
|
}
|
|
34902
|
-
|
|
36323
|
+
resolve4({ stdout, stderr });
|
|
34903
36324
|
});
|
|
34904
36325
|
});
|
|
34905
36326
|
}
|
|
@@ -34955,17 +36376,17 @@ function log(message) {
|
|
|
34955
36376
|
}
|
|
34956
36377
|
}
|
|
34957
36378
|
function sleep(ms2) {
|
|
34958
|
-
return new Promise((
|
|
36379
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms2));
|
|
34959
36380
|
}
|
|
34960
|
-
function
|
|
36381
|
+
function loadJson3(filePath, defaultValue) {
|
|
34961
36382
|
try {
|
|
34962
|
-
const content =
|
|
36383
|
+
const content = fs42.readFileSync(filePath, "utf-8");
|
|
34963
36384
|
return JSON.parse(content);
|
|
34964
36385
|
} catch {
|
|
34965
36386
|
return defaultValue;
|
|
34966
36387
|
}
|
|
34967
36388
|
}
|
|
34968
|
-
function
|
|
36389
|
+
function saveJson2(filePath, data) {
|
|
34969
36390
|
atomicWriteFileSync(filePath, JSON.stringify(data, null, 2));
|
|
34970
36391
|
}
|
|
34971
36392
|
var HUMAN_TASK_PATTERNS = [
|
|
@@ -35014,7 +36435,7 @@ function isMetaTaskFalseCompletion(output) {
|
|
|
35014
36435
|
}
|
|
35015
36436
|
function reclassifyAsHumanDependent(taskId) {
|
|
35016
36437
|
try {
|
|
35017
|
-
let content =
|
|
36438
|
+
let content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
35018
36439
|
const taskPattern = new RegExp(`^- \\[[ x]\\] \`${taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\`.*$`, "m");
|
|
35019
36440
|
const match = content.match(taskPattern);
|
|
35020
36441
|
if (!match) return;
|
|
@@ -35027,7 +36448,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
35027
36448
|
const insertIndex = content.indexOf("\n", blockedIndex) + 1;
|
|
35028
36449
|
content = content.slice(0, insertIndex) + "\n" + uncheckedLine + content.slice(insertIndex);
|
|
35029
36450
|
}
|
|
35030
|
-
|
|
36451
|
+
fs42.writeFileSync(TODO_FILE, content);
|
|
35031
36452
|
log(`Reclassified task ${taskId} as human-dependent (moved to Blocked)`);
|
|
35032
36453
|
} catch (error) {
|
|
35033
36454
|
log(`Failed to reclassify task: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
@@ -35035,7 +36456,7 @@ function reclassifyAsHumanDependent(taskId) {
|
|
|
35035
36456
|
}
|
|
35036
36457
|
function getMetaTaskFailureCount(taskId) {
|
|
35037
36458
|
try {
|
|
35038
|
-
const content =
|
|
36459
|
+
const content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
35039
36460
|
const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35040
36461
|
const match = content.match(new RegExp(`\`${escapedId}\`.*\\[failed:(\\d+)\\]`));
|
|
35041
36462
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -35045,7 +36466,7 @@ function getMetaTaskFailureCount(taskId) {
|
|
|
35045
36466
|
}
|
|
35046
36467
|
function incrementMetaTaskFailureCount(taskId) {
|
|
35047
36468
|
try {
|
|
35048
|
-
let content =
|
|
36469
|
+
let content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
35049
36470
|
const escapedId = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35050
36471
|
const linePattern = new RegExp(`^(- \\[[ x]\\] \`${escapedId}\`.*)$`, "m");
|
|
35051
36472
|
const lineMatch = content.match(linePattern);
|
|
@@ -35061,7 +36482,7 @@ function incrementMetaTaskFailureCount(taskId) {
|
|
|
35061
36482
|
newLine = `${line} [failed:${newCount}]`;
|
|
35062
36483
|
}
|
|
35063
36484
|
content = content.replace(line, newLine);
|
|
35064
|
-
|
|
36485
|
+
fs42.writeFileSync(TODO_FILE, content);
|
|
35065
36486
|
log(`Meta-task ${taskId} failure count: ${newCount}`);
|
|
35066
36487
|
return newCount;
|
|
35067
36488
|
} catch (error) {
|
|
@@ -35071,7 +36492,7 @@ function incrementMetaTaskFailureCount(taskId) {
|
|
|
35071
36492
|
}
|
|
35072
36493
|
function parseTodoFile() {
|
|
35073
36494
|
try {
|
|
35074
|
-
const content =
|
|
36495
|
+
const content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
35075
36496
|
const tasks = [];
|
|
35076
36497
|
let currentSection = "high_priority";
|
|
35077
36498
|
const lines = content.split("\n");
|
|
@@ -35103,9 +36524,9 @@ function parseTodoFile() {
|
|
|
35103
36524
|
}
|
|
35104
36525
|
}
|
|
35105
36526
|
function loadState4() {
|
|
35106
|
-
const ideasData =
|
|
35107
|
-
const goalsData =
|
|
35108
|
-
const sessionsData =
|
|
36527
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
36528
|
+
const goalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
36529
|
+
const sessionsData = loadJson3(SESSIONS_FILE, { sessions: [] });
|
|
35109
36530
|
const todos = parseTodoFile();
|
|
35110
36531
|
return {
|
|
35111
36532
|
ideas: ideasData.ideas || [],
|
|
@@ -35129,12 +36550,12 @@ var DEFAULT_BUSINESS_CONTEXT = {
|
|
|
35129
36550
|
key_constraints: []
|
|
35130
36551
|
};
|
|
35131
36552
|
function loadBusinessContext() {
|
|
35132
|
-
return
|
|
36553
|
+
return loadJson3(BUSINESS_CONTEXT_FILE, DEFAULT_BUSINESS_CONTEXT);
|
|
35133
36554
|
}
|
|
35134
36555
|
function updateBusinessContext(ctx, kpis) {
|
|
35135
36556
|
updateFunnelStageStatuses(ctx, kpis);
|
|
35136
36557
|
ctx.last_auto_update = (/* @__PURE__ */ new Date()).toISOString();
|
|
35137
|
-
|
|
36558
|
+
saveJson2(BUSINESS_CONTEXT_FILE, ctx);
|
|
35138
36559
|
}
|
|
35139
36560
|
function checkThreshold(threshold, kpiValue) {
|
|
35140
36561
|
if (kpiValue === null || kpiValue === void 0) return false;
|
|
@@ -35221,10 +36642,10 @@ function captureGitDiffInfo(workspacePath) {
|
|
|
35221
36642
|
has_uncommitted_changes: false
|
|
35222
36643
|
};
|
|
35223
36644
|
try {
|
|
35224
|
-
const status = (0,
|
|
36645
|
+
const status = (0, import_child_process13.execSync)("git status --porcelain", { cwd: workspacePath, encoding: "utf-8" });
|
|
35225
36646
|
result.has_uncommitted_changes = status.trim().length > 0;
|
|
35226
36647
|
try {
|
|
35227
|
-
const diffStat = (0,
|
|
36648
|
+
const diffStat = (0, import_child_process13.execSync)("git diff --stat HEAD~1 HEAD 2>/dev/null || git diff --stat --cached 2>/dev/null || git diff --stat 2>/dev/null", {
|
|
35228
36649
|
cwd: workspacePath,
|
|
35229
36650
|
encoding: "utf-8",
|
|
35230
36651
|
shell: "/bin/bash"
|
|
@@ -35312,7 +36733,7 @@ function checkAlerts(state) {
|
|
|
35312
36733
|
}
|
|
35313
36734
|
}
|
|
35314
36735
|
}
|
|
35315
|
-
const epics =
|
|
36736
|
+
const epics = loadJson3(ROADMAP_FILE, { epics: [] }).epics || [];
|
|
35316
36737
|
for (const epic of epics) {
|
|
35317
36738
|
if (epic.status === "active" && epic.target_end) {
|
|
35318
36739
|
const daysOver = daysSince(epic.target_end);
|
|
@@ -35408,14 +36829,14 @@ function checkSystemHealth(state, maxRetries) {
|
|
|
35408
36829
|
}
|
|
35409
36830
|
}
|
|
35410
36831
|
if (health.recovery_actions.some((a) => a.startsWith("STALE SUB-TASK"))) {
|
|
35411
|
-
const ideasData =
|
|
36832
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
35412
36833
|
for (const idea of inProgressIdeas) {
|
|
35413
36834
|
const stored = ideasData.ideas.find((i) => i.id === idea.id);
|
|
35414
36835
|
if (stored) {
|
|
35415
36836
|
stored.implementation.sub_tasks = idea.implementation.sub_tasks;
|
|
35416
36837
|
}
|
|
35417
36838
|
}
|
|
35418
|
-
|
|
36839
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
35419
36840
|
log(`Persisted stale sub-task recovery changes`);
|
|
35420
36841
|
}
|
|
35421
36842
|
if (inProgressIdeas.length > 0) {
|
|
@@ -35438,7 +36859,7 @@ function checkSystemHealth(state, maxRetries) {
|
|
|
35438
36859
|
}
|
|
35439
36860
|
function autoRecoverStuckIdeas(stuckIdeaIds) {
|
|
35440
36861
|
if (stuckIdeaIds.length === 0) return;
|
|
35441
|
-
const ideasData =
|
|
36862
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
35442
36863
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
35443
36864
|
for (const ideaId of stuckIdeaIds) {
|
|
35444
36865
|
const idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
@@ -35455,10 +36876,10 @@ function autoRecoverStuckIdeas(stuckIdeaIds) {
|
|
|
35455
36876
|
});
|
|
35456
36877
|
log(`Auto-deferred stuck idea: ${ideaId} \u2014 ${idea.title}`);
|
|
35457
36878
|
}
|
|
35458
|
-
|
|
36879
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
35459
36880
|
}
|
|
35460
36881
|
function autoRecoverDeferredParseFailures() {
|
|
35461
|
-
const ideasData =
|
|
36882
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
35462
36883
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
35463
36884
|
let recovered = 0;
|
|
35464
36885
|
for (const idea of ideasData.ideas) {
|
|
@@ -35485,25 +36906,25 @@ function autoRecoverDeferredParseFailures() {
|
|
|
35485
36906
|
recovered++;
|
|
35486
36907
|
}
|
|
35487
36908
|
if (recovered > 0) {
|
|
35488
|
-
|
|
36909
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
35489
36910
|
log(`Auto-recovered ${recovered} deferred idea(s) for retry`);
|
|
35490
36911
|
}
|
|
35491
36912
|
}
|
|
35492
36913
|
async function generateMissingShipCards(ideas) {
|
|
35493
36914
|
const shippedIdeas = ideas.filter((i) => i.stage === "shipped");
|
|
35494
36915
|
if (shippedIdeas.length === 0) return;
|
|
35495
|
-
const assetsDir =
|
|
35496
|
-
const hasRegular =
|
|
35497
|
-
const hasBold =
|
|
36916
|
+
const assetsDir = path34.join(__dirname, "assets");
|
|
36917
|
+
const hasRegular = fs42.existsSync(path34.join(assetsDir, "Inter-Regular.ttf"));
|
|
36918
|
+
const hasBold = fs42.existsSync(path34.join(assetsDir, "Inter-Bold.ttf"));
|
|
35498
36919
|
if (!hasRegular && !hasBold) return;
|
|
35499
|
-
const visualsDir =
|
|
36920
|
+
const visualsDir = path34.join(DATA_DIR, "reports", "visuals");
|
|
35500
36921
|
let generated = 0;
|
|
35501
36922
|
for (const idea of shippedIdeas) {
|
|
35502
|
-
const cardPath =
|
|
35503
|
-
if (
|
|
36923
|
+
const cardPath = path34.join(visualsDir, `${idea.id}-card.png`);
|
|
36924
|
+
if (fs42.existsSync(cardPath)) continue;
|
|
35504
36925
|
if (idea.implementation?.preview_url) {
|
|
35505
|
-
const screenshotPath =
|
|
35506
|
-
if (!
|
|
36926
|
+
const screenshotPath = path34.join(DATA_DIR, "reports", `${idea.id}-screenshot.png`);
|
|
36927
|
+
if (!fs42.existsSync(screenshotPath)) {
|
|
35507
36928
|
try {
|
|
35508
36929
|
const { captureIdeaScreenshot: captureIdeaScreenshot2 } = await Promise.resolve().then(() => (init_screenshot(), screenshot_exports));
|
|
35509
36930
|
await captureIdeaScreenshot2(idea.id, idea.implementation.preview_url);
|
|
@@ -35551,7 +36972,7 @@ async function fetchLiveKPIs() {
|
|
|
35551
36972
|
}
|
|
35552
36973
|
}
|
|
35553
36974
|
function updateGoalsWithKPIs(kpis) {
|
|
35554
|
-
const goalsData =
|
|
36975
|
+
const goalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
35555
36976
|
const today = formatDate(/* @__PURE__ */ new Date());
|
|
35556
36977
|
for (const goal of goalsData.goals) {
|
|
35557
36978
|
let kpisUpdated = 0;
|
|
@@ -35594,7 +37015,7 @@ function updateGoalsWithKPIs(kpis) {
|
|
|
35594
37015
|
}
|
|
35595
37016
|
}
|
|
35596
37017
|
}
|
|
35597
|
-
|
|
37018
|
+
saveJson2(GOALS_FILE, goalsData);
|
|
35598
37019
|
log("Updated goals.json with live KPIs");
|
|
35599
37020
|
}
|
|
35600
37021
|
function detectMarketingTriggersIfNeeded(goals) {
|
|
@@ -35606,17 +37027,17 @@ function detectMarketingTriggersIfNeeded(goals) {
|
|
|
35606
37027
|
}
|
|
35607
37028
|
async function syncMarketingPlansToIdeas() {
|
|
35608
37029
|
const { addMarketingIdeasToDashboard: addMarketingIdeasToDashboard2 } = await Promise.resolve().then(() => (init_marketing_integration(), marketing_integration_exports));
|
|
35609
|
-
const plansDir =
|
|
35610
|
-
if (!
|
|
37030
|
+
const plansDir = path34.join(DATA_DIR, "marketing-plans");
|
|
37031
|
+
if (!fs42.existsSync(plansDir)) {
|
|
35611
37032
|
return;
|
|
35612
37033
|
}
|
|
35613
|
-
const planFiles =
|
|
37034
|
+
const planFiles = fs42.readdirSync(plansDir).filter((f) => f.endsWith(".json"));
|
|
35614
37035
|
let totalAdded = 0;
|
|
35615
37036
|
let totalSkipped = 0;
|
|
35616
37037
|
for (const file of planFiles) {
|
|
35617
37038
|
try {
|
|
35618
|
-
const planPath =
|
|
35619
|
-
const plan = JSON.parse(
|
|
37039
|
+
const planPath = path34.join(plansDir, file);
|
|
37040
|
+
const plan = JSON.parse(fs42.readFileSync(planPath, "utf-8"));
|
|
35620
37041
|
if (plan.status === "approved" || plan.status === "active") {
|
|
35621
37042
|
const result = await addMarketingIdeasToDashboard2(plan, DATA_DIR);
|
|
35622
37043
|
totalAdded += result.added;
|
|
@@ -35631,10 +37052,10 @@ async function syncMarketingPlansToIdeas() {
|
|
|
35631
37052
|
}
|
|
35632
37053
|
}
|
|
35633
37054
|
function loadCodebaseSnapshot() {
|
|
35634
|
-
const snapshotPath =
|
|
37055
|
+
const snapshotPath = path34.join(DATA_DIR, "codebase-snapshot.json");
|
|
35635
37056
|
try {
|
|
35636
|
-
if (
|
|
35637
|
-
return JSON.parse(
|
|
37057
|
+
if (fs42.existsSync(snapshotPath)) {
|
|
37058
|
+
return JSON.parse(fs42.readFileSync(snapshotPath, "utf-8"));
|
|
35638
37059
|
}
|
|
35639
37060
|
} catch {
|
|
35640
37061
|
}
|
|
@@ -35642,8 +37063,8 @@ function loadCodebaseSnapshot() {
|
|
|
35642
37063
|
}
|
|
35643
37064
|
function buildContextForClaude(state, alerts, businessContext) {
|
|
35644
37065
|
const codebaseSnapshot = loadCodebaseSnapshot();
|
|
35645
|
-
const hypotheses =
|
|
35646
|
-
const epics =
|
|
37066
|
+
const hypotheses = loadJson3(HYPOTHESES_FILE, { hypotheses: [] }).hypotheses || [];
|
|
37067
|
+
const epics = loadJson3(ROADMAP_FILE, { epics: [] }).epics || [];
|
|
35647
37068
|
const deriveEpicStatus = (epic) => {
|
|
35648
37069
|
if (epic.status_override) return epic.status;
|
|
35649
37070
|
const childIdeas = state.ideas.filter((i) => epic.idea_ids.includes(i.id));
|
|
@@ -35794,9 +37215,18 @@ function buildContextForClaude(state, alerts, businessContext) {
|
|
|
35794
37215
|
social: readSocialFreshness(),
|
|
35795
37216
|
promo_copy: readPromoCopyFreshness(),
|
|
35796
37217
|
promo_video: readPromoVideoFreshness(),
|
|
35797
|
-
campaigns: readCampaignFreshness()
|
|
37218
|
+
campaigns: readCampaignFreshness(),
|
|
37219
|
+
growth_campaigns: readGrowthCampaignFreshness(),
|
|
37220
|
+
product_vision: readVisionFreshness()
|
|
35798
37221
|
};
|
|
35799
37222
|
}
|
|
37223
|
+
function readVisionFreshness() {
|
|
37224
|
+
try {
|
|
37225
|
+
return generateValidationReport();
|
|
37226
|
+
} catch {
|
|
37227
|
+
return null;
|
|
37228
|
+
}
|
|
37229
|
+
}
|
|
35800
37230
|
function buildBusinessConstraints(businessContext) {
|
|
35801
37231
|
if (!businessContext) {
|
|
35802
37232
|
return `BUSINESS CONTEXT:
|
|
@@ -36101,6 +37531,17 @@ Rules:
|
|
|
36101
37531
|
5. Ideas without prerequisites (tech debt, infrastructure) can proceed normally.
|
|
36102
37532
|
6. For revenue ideas: "convert existing free users to paid" > "build new revenue streams" when the user base is tiny.
|
|
36103
37533
|
|
|
37534
|
+
PRODUCT VISION ALIGNMENT:
|
|
37535
|
+
The "product_vision" field shows the current product vision status (Obviously Awesome + StoryBrand).
|
|
37536
|
+
Rules:
|
|
37537
|
+
1. Prioritize ideas that test unvalidated hypotheses over new feature ideas.
|
|
37538
|
+
2. Every recommended idea must link to a hypothesis or explain why it doesn't need one.
|
|
37539
|
+
3. Quality gate: do not recommend implementing an idea without clear acceptance criteria (success_metrics).
|
|
37540
|
+
4. If product_vision.pivot_needed == true, prioritize a "vision-review" task to reassess the vision.
|
|
37541
|
+
5. If product_vision.vision_exists == false, recommend running "analyze --type=vision" before approving new ideas.
|
|
37542
|
+
6. If product_vision.confidence_score < 30, focus on hypothesis validation over new feature development.
|
|
37543
|
+
7. Monitor product_vision.hypotheses.top_uncertain \u2014 these represent the biggest unknowns to resolve.
|
|
37544
|
+
|
|
36104
37545
|
HYPOTHESIS VALIDATION:
|
|
36105
37546
|
The "hypotheses" field contains business hypotheses that need validation through experiments.
|
|
36106
37547
|
Rules:
|
|
@@ -36201,9 +37642,9 @@ Focus on actionable, specific recommendations. Be concise.`;
|
|
|
36201
37642
|
}
|
|
36202
37643
|
function addTasksToTodo(tasks) {
|
|
36203
37644
|
if (tasks.length === 0) return;
|
|
36204
|
-
if (!
|
|
37645
|
+
if (!fs42.existsSync(TODO_FILE)) return;
|
|
36205
37646
|
try {
|
|
36206
|
-
let content =
|
|
37647
|
+
let content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
36207
37648
|
if (!content.includes("## High Priority (Do Now)")) {
|
|
36208
37649
|
log('TODO.md missing "## High Priority (Do Now)" section \u2014 creating it');
|
|
36209
37650
|
const scheduledIdx = content.indexOf("## Scheduled");
|
|
@@ -36303,15 +37744,15 @@ function addTasksToTodo(tasks) {
|
|
|
36303
37744
|
}
|
|
36304
37745
|
log(`Added TODO: ${task.id} \u2014 ${task.description}`);
|
|
36305
37746
|
}
|
|
36306
|
-
|
|
37747
|
+
fs42.writeFileSync(TODO_FILE, content);
|
|
36307
37748
|
} catch (error) {
|
|
36308
37749
|
log(`Failed to add tasks to TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
36309
37750
|
}
|
|
36310
37751
|
}
|
|
36311
37752
|
function archiveCompletedTodos() {
|
|
36312
37753
|
try {
|
|
36313
|
-
if (!
|
|
36314
|
-
let content =
|
|
37754
|
+
if (!fs42.existsSync(TODO_FILE)) return;
|
|
37755
|
+
let content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
36315
37756
|
const lines = content.split("\n");
|
|
36316
37757
|
const completedLines = [];
|
|
36317
37758
|
const newLines = [];
|
|
@@ -36337,7 +37778,7 @@ function archiveCompletedTodos() {
|
|
|
36337
37778
|
const archiveBlock = completedLines.join("\n") + "\n";
|
|
36338
37779
|
content = content.slice(0, insertIndex) + archiveBlock + content.slice(insertIndex);
|
|
36339
37780
|
}
|
|
36340
|
-
|
|
37781
|
+
fs42.writeFileSync(TODO_FILE, content);
|
|
36341
37782
|
log(`Archived ${completedLines.length} completed TODO(s) to Completed This Week`);
|
|
36342
37783
|
} catch (error) {
|
|
36343
37784
|
log(`Failed to archive completed TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -36345,8 +37786,8 @@ function archiveCompletedTodos() {
|
|
|
36345
37786
|
}
|
|
36346
37787
|
function pruneStaleScheduledTodos(state) {
|
|
36347
37788
|
try {
|
|
36348
|
-
if (!
|
|
36349
|
-
const content =
|
|
37789
|
+
if (!fs42.existsSync(TODO_FILE)) return;
|
|
37790
|
+
const content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
36350
37791
|
const lines = content.split("\n");
|
|
36351
37792
|
const prunedIds = [];
|
|
36352
37793
|
const terminalIdeaIds = new Set(
|
|
@@ -36382,7 +37823,7 @@ function pruneStaleScheduledTodos(state) {
|
|
|
36382
37823
|
return true;
|
|
36383
37824
|
});
|
|
36384
37825
|
if (prunedIds.length === 0) return;
|
|
36385
|
-
|
|
37826
|
+
fs42.writeFileSync(TODO_FILE, newLines.join("\n"));
|
|
36386
37827
|
log(`Pruned ${prunedIds.length} stale TODO(s): ${prunedIds.join(", ")}`);
|
|
36387
37828
|
} catch (error) {
|
|
36388
37829
|
log(`Failed to prune stale TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -36390,8 +37831,8 @@ function pruneStaleScheduledTodos(state) {
|
|
|
36390
37831
|
}
|
|
36391
37832
|
function deduplicateActiveTodos() {
|
|
36392
37833
|
try {
|
|
36393
|
-
if (!
|
|
36394
|
-
const content =
|
|
37834
|
+
if (!fs42.existsSync(TODO_FILE)) return;
|
|
37835
|
+
const content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
36395
37836
|
const lines = content.split("\n");
|
|
36396
37837
|
const removedIds = [];
|
|
36397
37838
|
let currentSection = "";
|
|
@@ -36428,7 +37869,7 @@ function deduplicateActiveTodos() {
|
|
|
36428
37869
|
if (linesToRemove.size === 0) return;
|
|
36429
37870
|
const newLines = lines.filter((_, i) => !linesToRemove.has(i));
|
|
36430
37871
|
const cleaned = newLines.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
36431
|
-
|
|
37872
|
+
fs42.writeFileSync(TODO_FILE, cleaned);
|
|
36432
37873
|
log(`Deduplicated ${removedIds.length} TODO(s): ${removedIds.join(", ")}`);
|
|
36433
37874
|
} catch (error) {
|
|
36434
37875
|
log(`Failed to deduplicate TODOs: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -36436,8 +37877,8 @@ function deduplicateActiveTodos() {
|
|
|
36436
37877
|
}
|
|
36437
37878
|
function readMemoryContext() {
|
|
36438
37879
|
try {
|
|
36439
|
-
if (!
|
|
36440
|
-
const content =
|
|
37880
|
+
if (!fs42.existsSync(MEMORY_FILE)) return "";
|
|
37881
|
+
const content = fs42.readFileSync(MEMORY_FILE, "utf-8");
|
|
36441
37882
|
const MAX_TOTAL_LINES = 100;
|
|
36442
37883
|
const MIN_LEARNING_ENTRIES = 15;
|
|
36443
37884
|
const learningsSplit = content.indexOf("### Heartbeat Learnings");
|
|
@@ -36458,14 +37899,14 @@ function readMemoryContext() {
|
|
|
36458
37899
|
}
|
|
36459
37900
|
function appendToMemory(learnings) {
|
|
36460
37901
|
if (learnings.length === 0) return;
|
|
36461
|
-
if (!
|
|
37902
|
+
if (!fs42.existsSync(MEMORY_FILE)) return;
|
|
36462
37903
|
const MAX_LEARNINGS_PER_HEARTBEAT = 2;
|
|
36463
37904
|
let filtered = learnings.slice(0, MAX_LEARNINGS_PER_HEARTBEAT);
|
|
36464
37905
|
if (learnings.length > MAX_LEARNINGS_PER_HEARTBEAT) {
|
|
36465
37906
|
log(`Capped learnings from ${learnings.length} to ${MAX_LEARNINGS_PER_HEARTBEAT}`);
|
|
36466
37907
|
}
|
|
36467
37908
|
try {
|
|
36468
|
-
let content =
|
|
37909
|
+
let content = fs42.readFileSync(MEMORY_FILE, "utf-8");
|
|
36469
37910
|
const existingLines = content.split("\n").filter((l2) => l2.match(/^- \*\*/));
|
|
36470
37911
|
const KEY_PHRASE_PATTERN = /(?:idea-[a-f0-9]{8}|idea-[a-z0-9-]+|hyp-[a-z0-9-]+|goal-[a-z0-9-]+|pipeline starvation|critical path|batch triage|cooldown|decompos|timeout|inbox bloat|dedup|meta-task|heartbeat|autonomy gate|false completion)/gi;
|
|
36471
37912
|
filtered = filtered.filter((learning) => {
|
|
@@ -36515,7 +37956,7 @@ function appendToMemory(learnings) {
|
|
|
36515
37956
|
const subsectionEnd = subsectionEndMatch ? subsectionIndex + 22 + (subsectionEndMatch.index || 0) : insertPoint;
|
|
36516
37957
|
content = content.slice(0, subsectionEnd) + newEntries + "\n" + content.slice(subsectionEnd);
|
|
36517
37958
|
}
|
|
36518
|
-
|
|
37959
|
+
fs42.writeFileSync(MEMORY_FILE, content);
|
|
36519
37960
|
log(`Added ${filtered.length} learning(s) to MEMORY.md`);
|
|
36520
37961
|
}
|
|
36521
37962
|
} catch (error) {
|
|
@@ -36524,8 +37965,8 @@ function appendToMemory(learnings) {
|
|
|
36524
37965
|
}
|
|
36525
37966
|
function pruneMemoryLearnings() {
|
|
36526
37967
|
try {
|
|
36527
|
-
if (!
|
|
36528
|
-
const content =
|
|
37968
|
+
if (!fs42.existsSync(MEMORY_FILE)) return;
|
|
37969
|
+
const content = fs42.readFileSync(MEMORY_FILE, "utf-8");
|
|
36529
37970
|
const learningsHeader = "### Heartbeat Learnings";
|
|
36530
37971
|
const learningsIdx = content.indexOf(learningsHeader);
|
|
36531
37972
|
if (learningsIdx === -1) return;
|
|
@@ -36561,7 +38002,7 @@ function pruneMemoryLearnings() {
|
|
|
36561
38002
|
if (prunedCount === 0) return;
|
|
36562
38003
|
const newLearningsBody = headerLine + "\n\n" + finalEntries.join("\n") + "\n";
|
|
36563
38004
|
const newContent = beforeLearnings + newLearningsBody + afterLearnings;
|
|
36564
|
-
|
|
38005
|
+
fs42.writeFileSync(MEMORY_FILE, newContent);
|
|
36565
38006
|
log(`Pruned ${prunedCount} duplicate/stale learnings (${entryLines.length} \u2192 ${finalEntries.length})`);
|
|
36566
38007
|
} catch (error) {
|
|
36567
38008
|
log(`Failed to prune memory learnings: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -36651,8 +38092,8 @@ function determineWork(state) {
|
|
|
36651
38092
|
};
|
|
36652
38093
|
}
|
|
36653
38094
|
}
|
|
36654
|
-
const hypotheses =
|
|
36655
|
-
const epicsForScoring =
|
|
38095
|
+
const hypotheses = loadJson3(HYPOTHESES_FILE, { hypotheses: [] }).hypotheses || [];
|
|
38096
|
+
const epicsForScoring = loadJson3(ROADMAP_FILE, { epics: [] }).epics || [];
|
|
36656
38097
|
const approvedIdeas = state.ideas.filter(
|
|
36657
38098
|
(i) => i.stage === "approved" && (i.implementation?.decomposition_attempts || 0) < maxRetriesCheck
|
|
36658
38099
|
).sort((a, b) => scoreIdea(b, state.goals, hypotheses, epicsForScoring) - scoreIdea(a, state.goals, hypotheses, epicsForScoring));
|
|
@@ -36670,7 +38111,7 @@ function determineWork(state) {
|
|
|
36670
38111
|
return null;
|
|
36671
38112
|
}
|
|
36672
38113
|
function loadConfig() {
|
|
36673
|
-
return
|
|
38114
|
+
return loadJson3(CONFIG_FILE, {
|
|
36674
38115
|
repos: [],
|
|
36675
38116
|
workspace_dir: "",
|
|
36676
38117
|
schedules: {}
|
|
@@ -36680,7 +38121,7 @@ function detectTargetRepo(idea, config) {
|
|
|
36680
38121
|
const filesAnalyzed = idea.source.files_analyzed || [];
|
|
36681
38122
|
for (const file of filesAnalyzed) {
|
|
36682
38123
|
for (const repo of config.repos) {
|
|
36683
|
-
if (file.includes(repo.name) || file.includes(
|
|
38124
|
+
if (file.includes(repo.name) || file.includes(path34.basename(repo.path))) {
|
|
36684
38125
|
return repo;
|
|
36685
38126
|
}
|
|
36686
38127
|
}
|
|
@@ -36913,7 +38354,7 @@ Respond with JSON only (no markdown code blocks, just raw JSON):
|
|
|
36913
38354
|
return [];
|
|
36914
38355
|
}
|
|
36915
38356
|
function deferIdeaWithComment(ideaId, reason) {
|
|
36916
|
-
const ideasData =
|
|
38357
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
36917
38358
|
const idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
36918
38359
|
if (!idea) return false;
|
|
36919
38360
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -36929,7 +38370,7 @@ function deferIdeaWithComment(ideaId, reason) {
|
|
|
36929
38370
|
});
|
|
36930
38371
|
const ideaIndex = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
36931
38372
|
ideasData.ideas[ideaIndex] = idea;
|
|
36932
|
-
|
|
38373
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
36933
38374
|
log(`Deferred idea ${ideaId}: ${reason}`);
|
|
36934
38375
|
return false;
|
|
36935
38376
|
}
|
|
@@ -36973,17 +38414,17 @@ function runTestsForRepo(workspacePath, testCommands, sourceRepoPath) {
|
|
|
36973
38414
|
const allOutput = [];
|
|
36974
38415
|
const env = { ...process.env };
|
|
36975
38416
|
if (sourceRepoPath) {
|
|
36976
|
-
const venvBin =
|
|
36977
|
-
if (
|
|
38417
|
+
const venvBin = path34.join(sourceRepoPath, ".venv", "bin");
|
|
38418
|
+
if (fs42.existsSync(venvBin)) {
|
|
36978
38419
|
env.PATH = `${venvBin}:${env.PATH}`;
|
|
36979
|
-
env.VIRTUAL_ENV =
|
|
38420
|
+
env.VIRTUAL_ENV = path34.join(sourceRepoPath, ".venv");
|
|
36980
38421
|
log(`Using venv from ${sourceRepoPath}/.venv`);
|
|
36981
38422
|
}
|
|
36982
38423
|
}
|
|
36983
38424
|
for (const cmd of testCommands) {
|
|
36984
38425
|
log(`Running test: ${cmd}`);
|
|
36985
38426
|
try {
|
|
36986
|
-
const result = (0,
|
|
38427
|
+
const result = (0, import_child_process13.execSync)(cmd, {
|
|
36987
38428
|
cwd: workspacePath,
|
|
36988
38429
|
encoding: "utf-8",
|
|
36989
38430
|
env,
|
|
@@ -37011,7 +38452,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
37011
38452
|
subTask.started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
37012
38453
|
const lockPath = IDEAS_FILE + ".lock";
|
|
37013
38454
|
withFileLock(lockPath, () => {
|
|
37014
|
-
const freshData =
|
|
38455
|
+
const freshData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37015
38456
|
const idx = freshData.ideas.findIndex((i) => i.id === ideaId);
|
|
37016
38457
|
if (idx !== -1) {
|
|
37017
38458
|
const st = freshData.ideas[idx].implementation.sub_tasks.find((s) => s.id === subTask.id);
|
|
@@ -37019,7 +38460,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
37019
38460
|
st.status = "in_progress";
|
|
37020
38461
|
st.started_at = subTask.started_at;
|
|
37021
38462
|
}
|
|
37022
|
-
|
|
38463
|
+
saveJson2(IDEAS_FILE, freshData);
|
|
37023
38464
|
}
|
|
37024
38465
|
});
|
|
37025
38466
|
const scopePayload = JSON.stringify({
|
|
@@ -37029,14 +38470,14 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
37029
38470
|
});
|
|
37030
38471
|
const scopeBase64 = Buffer.from(scopePayload).toString("base64");
|
|
37031
38472
|
const timeoutMs = autonomy.max_sub_task_timeout_ms || 3e5;
|
|
37032
|
-
const workspacePath = options?.worktreePath ||
|
|
38473
|
+
const workspacePath = options?.worktreePath || path34.join(config.workspace_dir, targetRepo.name);
|
|
37033
38474
|
const executionStartTime = Date.now();
|
|
37034
38475
|
const updateSubTaskWithDetails = (status, updateOpts = {}) => {
|
|
37035
38476
|
const endTime = Date.now();
|
|
37036
38477
|
const durationMs = endTime - executionStartTime;
|
|
37037
38478
|
const gitInfo = captureGitDiffInfo(workspacePath);
|
|
37038
38479
|
withFileLock(lockPath, () => {
|
|
37039
|
-
const reloadedIdeas =
|
|
38480
|
+
const reloadedIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37040
38481
|
const reloadedIndex = reloadedIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
37041
38482
|
if (reloadedIndex !== -1) {
|
|
37042
38483
|
const reloadedSubTask = reloadedIdeas.ideas[reloadedIndex].implementation.sub_tasks.find(
|
|
@@ -37062,7 +38503,7 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
37062
38503
|
if (updateOpts.commitHash !== void 0) {
|
|
37063
38504
|
reloadedSubTask.commit_hash = updateOpts.commitHash;
|
|
37064
38505
|
}
|
|
37065
|
-
|
|
38506
|
+
saveJson2(IDEAS_FILE, reloadedIdeas);
|
|
37066
38507
|
}
|
|
37067
38508
|
}
|
|
37068
38509
|
});
|
|
@@ -37105,9 +38546,20 @@ async function executeSingleSubTask(subTask, ideaId, idea, ideasData, autonomy,
|
|
|
37105
38546
|
return false;
|
|
37106
38547
|
}
|
|
37107
38548
|
}
|
|
38549
|
+
if (idea.implementation.acceptance_test_file) {
|
|
38550
|
+
try {
|
|
38551
|
+
const acceptResult = runAcceptanceTests(
|
|
38552
|
+
idea.implementation.acceptance_test_file,
|
|
38553
|
+
workspacePath
|
|
38554
|
+
);
|
|
38555
|
+
log(`Acceptance test progress after ${subTask.id}: ${acceptResult.passCount}/${acceptResult.totalCount} passed`);
|
|
38556
|
+
} catch (acceptError) {
|
|
38557
|
+
log(`Acceptance test run skipped: ${acceptError instanceof Error ? acceptError.message : "Unknown"}`);
|
|
38558
|
+
}
|
|
38559
|
+
}
|
|
37108
38560
|
let commitHash = null;
|
|
37109
38561
|
try {
|
|
37110
|
-
commitHash = (0,
|
|
38562
|
+
commitHash = (0, import_child_process13.execSync)("git rev-parse HEAD", { cwd: workspacePath, encoding: "utf-8" }).trim();
|
|
37111
38563
|
} catch {
|
|
37112
38564
|
}
|
|
37113
38565
|
updateSubTaskWithDetails("completed", {
|
|
@@ -37151,7 +38603,7 @@ async function analyzeAndRetrySubTask(subTask, ideaId, maxRetries) {
|
|
|
37151
38603
|
"Be specific about what went wrong and how to fix it. Include any workarounds needed."
|
|
37152
38604
|
].filter(Boolean).join("\n");
|
|
37153
38605
|
try {
|
|
37154
|
-
const spawnResult = (0,
|
|
38606
|
+
const spawnResult = (0, import_child_process13.spawnSync)("claude", ["--print", "--model", "sonnet", "-p", errorContext], {
|
|
37155
38607
|
cwd: ROOT_DIR,
|
|
37156
38608
|
encoding: "utf-8",
|
|
37157
38609
|
timeout: 6e4,
|
|
@@ -37163,7 +38615,7 @@ async function analyzeAndRetrySubTask(subTask, ideaId, maxRetries) {
|
|
|
37163
38615
|
log(`Retry analysis returned empty result for ${subTask.id}`);
|
|
37164
38616
|
return false;
|
|
37165
38617
|
}
|
|
37166
|
-
const ideasData =
|
|
38618
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37167
38619
|
const idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
37168
38620
|
if (!idea) return false;
|
|
37169
38621
|
const st = idea.implementation.sub_tasks.find((s) => s.id === subTask.id);
|
|
@@ -37176,20 +38628,20 @@ async function analyzeAndRetrySubTask(subTask, ideaId, maxRetries) {
|
|
|
37176
38628
|
st.output_snippet = null;
|
|
37177
38629
|
st.retry_count = currentRetries + 1;
|
|
37178
38630
|
st.description = `[Retry ${currentRetries + 1}] ${result}`;
|
|
37179
|
-
|
|
38631
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
37180
38632
|
log(`Sub-task ${subTask.id} reset to pending for retry (attempt ${currentRetries + 1})`);
|
|
37181
38633
|
return true;
|
|
37182
38634
|
} catch (error) {
|
|
37183
38635
|
log(`Retry analysis failed for ${subTask.id}: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
37184
38636
|
try {
|
|
37185
|
-
const fallbackIdeas =
|
|
38637
|
+
const fallbackIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37186
38638
|
const fallbackIdea = fallbackIdeas.ideas.find((i) => i.id === ideaId);
|
|
37187
38639
|
if (fallbackIdea) {
|
|
37188
38640
|
const fallbackSt = fallbackIdea.implementation.sub_tasks.find((s) => s.id === subTask.id);
|
|
37189
38641
|
if (fallbackSt) {
|
|
37190
38642
|
fallbackSt.retry_count = (fallbackSt.retry_count || 0) + 1;
|
|
37191
38643
|
fallbackSt.error_message = `Retry analysis failed: ${error instanceof Error ? error.message : "Unknown"}`;
|
|
37192
|
-
|
|
38644
|
+
saveJson2(IDEAS_FILE, fallbackIdeas);
|
|
37193
38645
|
log(`Incremented retry_count to ${fallbackSt.retry_count} for ${subTask.id} after analysis failure`);
|
|
37194
38646
|
}
|
|
37195
38647
|
}
|
|
@@ -37209,7 +38661,7 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37209
38661
|
log(`Autonomy not enabled, skipping implementation for ${ideaId}`);
|
|
37210
38662
|
return false;
|
|
37211
38663
|
}
|
|
37212
|
-
const ideasData =
|
|
38664
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37213
38665
|
let idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
37214
38666
|
if (!idea && !ideaId.startsWith("idea-")) {
|
|
37215
38667
|
const prefixedId = `idea-${ideaId}`;
|
|
@@ -37236,7 +38688,7 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37236
38688
|
idea.implementation.decomposition_attempts = attempts + 1;
|
|
37237
38689
|
const ideaIdx = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
37238
38690
|
ideasData.ideas[ideaIdx] = idea;
|
|
37239
|
-
|
|
38691
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
37240
38692
|
log(`Decomposition attempt ${attempts + 1}/${maxDecompAttempts} for ${ideaId}...`);
|
|
37241
38693
|
const newSubTasks = await decomposeIdea(idea, config);
|
|
37242
38694
|
if (newSubTasks.length === 0) {
|
|
@@ -37247,23 +38699,94 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37247
38699
|
subTasks = newSubTasks;
|
|
37248
38700
|
const ideaIndex = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
37249
38701
|
ideasData.ideas[ideaIndex] = idea;
|
|
37250
|
-
|
|
38702
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
37251
38703
|
log(`Saved ${newSubTasks.length} sub-tasks for ${ideaId}, executing first sub-task now`);
|
|
37252
38704
|
}
|
|
38705
|
+
if (!idea.implementation.acceptance_tests_generated && idea.success_metrics?.length > 0) {
|
|
38706
|
+
const targetRepoForTests = detectTargetRepo(idea, config);
|
|
38707
|
+
if (targetRepoForTests) {
|
|
38708
|
+
const workspacePath = path34.join(config.workspace_dir, targetRepoForTests.name);
|
|
38709
|
+
try {
|
|
38710
|
+
log(`Generating acceptance tests for ${ideaId}...`);
|
|
38711
|
+
const testResult = await generateAndWriteAcceptanceTests(idea, targetRepoForTests, workspacePath);
|
|
38712
|
+
if (testResult.file && testResult.writtenPath) {
|
|
38713
|
+
idea.implementation.acceptance_test_file = testResult.writtenPath;
|
|
38714
|
+
idea.implementation.acceptance_tests_generated = true;
|
|
38715
|
+
const ideaIndex = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
38716
|
+
if (ideaIndex !== -1) {
|
|
38717
|
+
ideasData.ideas[ideaIndex] = idea;
|
|
38718
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
38719
|
+
}
|
|
38720
|
+
log(`Acceptance tests written to ${testResult.writtenPath}`);
|
|
38721
|
+
} else {
|
|
38722
|
+
log(`Acceptance test generation skipped: ${testResult.error || "no file generated"}`);
|
|
38723
|
+
}
|
|
38724
|
+
} catch (testGenError) {
|
|
38725
|
+
log(`Acceptance test generation failed (non-blocking): ${testGenError instanceof Error ? testGenError.message : "Unknown"}`);
|
|
38726
|
+
}
|
|
38727
|
+
}
|
|
38728
|
+
}
|
|
37253
38729
|
const pendingTasks = subTasks.filter((st) => st.status === "pending");
|
|
37254
38730
|
const completedTasks = subTasks.filter((st) => st.status === "completed");
|
|
37255
38731
|
const failedTasks = subTasks.filter((st) => st.status === "failed");
|
|
37256
38732
|
const inProgressTasks = subTasks.filter((st) => st.status === "in_progress");
|
|
37257
38733
|
if (pendingTasks.length === 0 && inProgressTasks.length === 0 && failedTasks.length === 0) {
|
|
37258
|
-
log(`All ${completedTasks.length} sub-tasks completed for ${ideaId},
|
|
38734
|
+
log(`All ${completedTasks.length} sub-tasks completed for ${ideaId}, running quality gate...`);
|
|
37259
38735
|
const targetRepo2 = detectTargetRepo(idea, config);
|
|
37260
38736
|
if (!targetRepo2) {
|
|
37261
38737
|
log("Could not determine target repo");
|
|
37262
38738
|
return false;
|
|
37263
38739
|
}
|
|
38740
|
+
if (idea.implementation.acceptance_test_file) {
|
|
38741
|
+
const workspacePath = path34.join(config.workspace_dir, targetRepo2.name);
|
|
38742
|
+
try {
|
|
38743
|
+
const finalResult = runAcceptanceTests(
|
|
38744
|
+
idea.implementation.acceptance_test_file,
|
|
38745
|
+
workspacePath
|
|
38746
|
+
);
|
|
38747
|
+
const qgIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38748
|
+
const qgIdx = qgIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
38749
|
+
if (qgIdx !== -1) {
|
|
38750
|
+
qgIdeas.ideas[qgIdx].implementation.acceptance_test_results = {
|
|
38751
|
+
passed: finalResult.passCount,
|
|
38752
|
+
failed: finalResult.failCount,
|
|
38753
|
+
last_run: (/* @__PURE__ */ new Date()).toISOString()
|
|
38754
|
+
};
|
|
38755
|
+
saveJson2(IDEAS_FILE, qgIdeas);
|
|
38756
|
+
}
|
|
38757
|
+
if (!finalResult.passed) {
|
|
38758
|
+
log(`QUALITY GATE BLOCKED: ${finalResult.passCount}/${finalResult.totalCount} acceptance tests passed`);
|
|
38759
|
+
const fixTask = {
|
|
38760
|
+
id: `st-fix-acceptance-${Date.now().toString(36)}`,
|
|
38761
|
+
title: "Fix failing acceptance tests",
|
|
38762
|
+
description: `Acceptance tests failed (${finalResult.failCount} failures). Fix the implementation to pass all tests in ${idea.implementation.acceptance_test_file}.
|
|
38763
|
+
|
|
38764
|
+
Failures:
|
|
38765
|
+
${finalResult.output.slice(-1500)}`,
|
|
38766
|
+
files_to_modify: [],
|
|
38767
|
+
status: "pending",
|
|
38768
|
+
started_at: null,
|
|
38769
|
+
completed_at: null,
|
|
38770
|
+
error_message: null,
|
|
38771
|
+
commit_hash: null
|
|
38772
|
+
};
|
|
38773
|
+
const fixIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38774
|
+
const fixIdx = fixIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
38775
|
+
if (fixIdx !== -1) {
|
|
38776
|
+
fixIdeas.ideas[fixIdx].implementation.sub_tasks.push(fixTask);
|
|
38777
|
+
saveJson2(IDEAS_FILE, fixIdeas);
|
|
38778
|
+
}
|
|
38779
|
+
log(`Queued fix-tests sub-task. Next heartbeat will attempt the fix.`);
|
|
38780
|
+
return false;
|
|
38781
|
+
}
|
|
38782
|
+
log(`QUALITY GATE PASSED: ${finalResult.totalCount}/${finalResult.totalCount} acceptance tests passed`);
|
|
38783
|
+
} catch (qgError) {
|
|
38784
|
+
log(`Quality gate check failed (proceeding with PR): ${qgError instanceof Error ? qgError.message : "Unknown"}`);
|
|
38785
|
+
}
|
|
38786
|
+
}
|
|
37264
38787
|
try {
|
|
37265
38788
|
const { command, args: args2 } = resolveScript(__dirname, "implement.ts");
|
|
37266
|
-
(0,
|
|
38789
|
+
(0, import_child_process13.execSync)(
|
|
37267
38790
|
[command, ...args2, `--idea=${ideaId}`, `--repo=${targetRepo2.name}`, "--create-pr-only"].join(" "),
|
|
37268
38791
|
{ cwd: ROOT_DIR, encoding: "utf-8", stdio: "inherit", timeout: 12e4 }
|
|
37269
38792
|
);
|
|
@@ -37271,27 +38794,27 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37271
38794
|
if (autonomy.auto_merge) {
|
|
37272
38795
|
const branchName2 = idea.implementation.branch_name;
|
|
37273
38796
|
const defaultBranch2 = targetRepo2.default_branch || "main";
|
|
37274
|
-
const workspacePath =
|
|
38797
|
+
const workspacePath = path34.join(config.workspace_dir, targetRepo2.name);
|
|
37275
38798
|
if (branchName2) {
|
|
37276
38799
|
try {
|
|
37277
38800
|
log(`Auto-merging branch ${branchName2} into ${defaultBranch2}...`);
|
|
37278
|
-
(0,
|
|
37279
|
-
(0,
|
|
37280
|
-
(0,
|
|
38801
|
+
(0, import_child_process13.execFileSync)("git", ["checkout", validateBranchName(defaultBranch2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
38802
|
+
(0, import_child_process13.execFileSync)("git", ["merge", validateBranchName(branchName2), "--squash", "--no-edit"], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
38803
|
+
(0, import_child_process13.execFileSync)("git", ["commit", "-m", `feat: ${sanitizeForCommitMessage(idea.title)} (auto-merged from ${validateBranchName(branchName2)})`], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
37281
38804
|
log(`Successfully merged ${branchName2} into ${defaultBranch2}`);
|
|
37282
38805
|
try {
|
|
37283
|
-
(0,
|
|
38806
|
+
(0, import_child_process13.execFileSync)("git", ["branch", "-d", validateBranchName(branchName2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
37284
38807
|
log(`Deleted branch ${branchName2}`);
|
|
37285
38808
|
} catch {
|
|
37286
38809
|
log(`Could not delete branch ${branchName2} (may need force delete)`);
|
|
37287
38810
|
}
|
|
37288
|
-
const mergedIdeas =
|
|
38811
|
+
const mergedIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37289
38812
|
const mergedIndex = mergedIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
37290
38813
|
if (mergedIndex !== -1) {
|
|
37291
38814
|
mergedIdeas.ideas[mergedIndex].stage = "shipped";
|
|
37292
38815
|
mergedIdeas.ideas[mergedIndex].updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
37293
38816
|
mergedIdeas.ideas[mergedIndex].implementation.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
37294
|
-
|
|
38817
|
+
saveJson2(IDEAS_FILE, mergedIdeas);
|
|
37295
38818
|
log(`Idea ${ideaId} moved to shipped stage`);
|
|
37296
38819
|
try {
|
|
37297
38820
|
const visualResult = await executeMarketingVisualTask(`generate-visual-${ideaId}`, "");
|
|
@@ -37312,25 +38835,25 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37312
38835
|
} catch (mergeError) {
|
|
37313
38836
|
log(`Auto-merge failed (likely conflicts): ${mergeError instanceof Error ? mergeError.message : "Unknown"}`);
|
|
37314
38837
|
try {
|
|
37315
|
-
(0,
|
|
37316
|
-
(0,
|
|
38838
|
+
(0, import_child_process13.execFileSync)("git", ["merge", "--abort"], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
38839
|
+
(0, import_child_process13.execFileSync)("git", ["checkout", validateBranchName(branchName2)], { cwd: workspacePath, encoding: "utf-8", stdio: "pipe" });
|
|
37317
38840
|
} catch {
|
|
37318
38841
|
}
|
|
37319
38842
|
log(`Left idea ${ideaId} in testing stage for manual merge`);
|
|
37320
38843
|
}
|
|
37321
38844
|
}
|
|
37322
38845
|
}
|
|
37323
|
-
const reloadedIdeas =
|
|
38846
|
+
const reloadedIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37324
38847
|
const reloadedIndex = reloadedIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
37325
38848
|
if (reloadedIndex !== -1 && reloadedIdeas.ideas[reloadedIndex].stage !== "shipped") {
|
|
37326
38849
|
reloadedIdeas.ideas[reloadedIndex].stage = "testing";
|
|
37327
38850
|
reloadedIdeas.ideas[reloadedIndex].updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
37328
|
-
|
|
38851
|
+
saveJson2(IDEAS_FILE, reloadedIdeas);
|
|
37329
38852
|
}
|
|
37330
38853
|
return true;
|
|
37331
38854
|
} catch (error) {
|
|
37332
38855
|
log(`PR creation failed: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
37333
|
-
const prIdeas =
|
|
38856
|
+
const prIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37334
38857
|
const prIdx = prIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
37335
38858
|
if (prIdx !== -1) {
|
|
37336
38859
|
const attempts = (prIdeas.ideas[prIdx].implementation.pr_creation_attempts || 0) + 1;
|
|
@@ -37349,7 +38872,7 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37349
38872
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
37350
38873
|
});
|
|
37351
38874
|
}
|
|
37352
|
-
|
|
38875
|
+
saveJson2(IDEAS_FILE, prIdeas);
|
|
37353
38876
|
}
|
|
37354
38877
|
return false;
|
|
37355
38878
|
}
|
|
@@ -37369,11 +38892,11 @@ async function executeAutonomousImplementation(ideaId) {
|
|
|
37369
38892
|
if (targetRepo2) {
|
|
37370
38893
|
try {
|
|
37371
38894
|
const { command, args: args2 } = resolveScript(__dirname, "implement.ts");
|
|
37372
|
-
(0,
|
|
38895
|
+
(0, import_child_process13.execSync)(
|
|
37373
38896
|
[command, ...args2, `--idea=${ideaId}`, `--repo=${targetRepo2.name}`, "--create-pr-only"].join(" "),
|
|
37374
38897
|
{ cwd: ROOT_DIR, encoding: "utf-8", stdio: "inherit", timeout: 12e4 }
|
|
37375
38898
|
);
|
|
37376
|
-
const partialIdeas =
|
|
38899
|
+
const partialIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37377
38900
|
const partialIdx = partialIdeas.ideas.findIndex((i) => i.id === ideaId);
|
|
37378
38901
|
if (partialIdx !== -1) {
|
|
37379
38902
|
const failedList = exhaustedTasks.map((st) => `- ${st.title} (${st.error_message || "unknown error"})`).join("\n");
|
|
@@ -37388,7 +38911,7 @@ ${failedList}`,
|
|
|
37388
38911
|
});
|
|
37389
38912
|
partialIdeas.ideas[partialIdx].stage = "testing";
|
|
37390
38913
|
partialIdeas.ideas[partialIdx].updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
37391
|
-
|
|
38914
|
+
saveJson2(IDEAS_FILE, partialIdeas);
|
|
37392
38915
|
}
|
|
37393
38916
|
return true;
|
|
37394
38917
|
} catch (prError) {
|
|
@@ -37410,7 +38933,7 @@ ${failedList}`,
|
|
|
37410
38933
|
}
|
|
37411
38934
|
}
|
|
37412
38935
|
if (anyRetried) {
|
|
37413
|
-
const retriedIdeas =
|
|
38936
|
+
const retriedIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37414
38937
|
const retriedIdea = retriedIdeas.ideas.find((i) => i.id === ideaId);
|
|
37415
38938
|
if (!retriedIdea) return false;
|
|
37416
38939
|
const retriedPending = retriedIdea.implementation.sub_tasks.filter((st) => st.status === "pending");
|
|
@@ -37460,7 +38983,7 @@ ${failedList}`,
|
|
|
37460
38983
|
const ideaIndex = ideasData.ideas.findIndex((i) => i.id === ideaId);
|
|
37461
38984
|
if (ideaIndex !== -1) {
|
|
37462
38985
|
ideasData.ideas[ideaIndex] = idea;
|
|
37463
|
-
|
|
38986
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
37464
38987
|
}
|
|
37465
38988
|
}
|
|
37466
38989
|
log(`${groups.length} conflict groups, executing in parallel via worktrees`);
|
|
@@ -37587,8 +39110,8 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
37587
39110
|
6. When modifying TODO.md, preserve the markdown checkbox format: - [ ] or - [x]
|
|
37588
39111
|
7. Be direct \u2014 read the file, make the edit, done
|
|
37589
39112
|
`;
|
|
37590
|
-
return new Promise((
|
|
37591
|
-
const claude = (0,
|
|
39113
|
+
return new Promise((resolve4) => {
|
|
39114
|
+
const claude = (0, import_child_process13.spawn)("claude", [
|
|
37592
39115
|
"--print",
|
|
37593
39116
|
"--dangerously-skip-permissions",
|
|
37594
39117
|
"--model",
|
|
@@ -37616,7 +39139,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
37616
39139
|
} catch {
|
|
37617
39140
|
}
|
|
37618
39141
|
}, 5e3);
|
|
37619
|
-
|
|
39142
|
+
resolve4(false);
|
|
37620
39143
|
}
|
|
37621
39144
|
}, timeoutMs);
|
|
37622
39145
|
claude.stdout.on("data", (data) => {
|
|
@@ -37634,14 +39157,14 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
37634
39157
|
log(`Meta-task false completion detected: ${taskId} \u2014 output indicates blocked state`);
|
|
37635
39158
|
log(` Output: ${output.substring(0, 200)}`);
|
|
37636
39159
|
reclassifyAsHumanDependent(taskId);
|
|
37637
|
-
|
|
39160
|
+
resolve4(false);
|
|
37638
39161
|
return;
|
|
37639
39162
|
}
|
|
37640
39163
|
log(`Meta-task completed: ${output.substring(0, 200)}`);
|
|
37641
|
-
|
|
39164
|
+
resolve4(true);
|
|
37642
39165
|
} else {
|
|
37643
39166
|
log(`Meta-task failed (exit ${code})`);
|
|
37644
|
-
|
|
39167
|
+
resolve4(false);
|
|
37645
39168
|
}
|
|
37646
39169
|
});
|
|
37647
39170
|
claude.on("error", (err2) => {
|
|
@@ -37649,7 +39172,7 @@ ${allowedFiles.map((f) => `- ${f}`).join("\n")}
|
|
|
37649
39172
|
isResolved = true;
|
|
37650
39173
|
clearTimeout(timeout);
|
|
37651
39174
|
log(`Meta-task error: ${err2.message}`);
|
|
37652
|
-
|
|
39175
|
+
resolve4(false);
|
|
37653
39176
|
});
|
|
37654
39177
|
});
|
|
37655
39178
|
}
|
|
@@ -37657,7 +39180,7 @@ async function executeResearchTask(taskId, description) {
|
|
|
37657
39180
|
const topicSlug = taskId.replace("research-", "").replace(/-/g, " ");
|
|
37658
39181
|
const topic = description || topicSlug;
|
|
37659
39182
|
log(`Executing research task: ${topic}`);
|
|
37660
|
-
const goalsData =
|
|
39183
|
+
const goalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
37661
39184
|
const goalsContext = goalsData.goals.map((g2) => {
|
|
37662
39185
|
const kpiLines = g2.kpis.map(
|
|
37663
39186
|
(k) => ` - ${k.name}: ${k.current_value ?? "N/A"} / ${k.target_value} ${k.unit}`
|
|
@@ -37671,9 +39194,9 @@ ${kpiLines}`;
|
|
|
37671
39194
|
goals: goalsContext
|
|
37672
39195
|
});
|
|
37673
39196
|
const contextBase64 = Buffer.from(contextPayload).toString("base64");
|
|
37674
|
-
return new Promise((
|
|
39197
|
+
return new Promise((resolve4) => {
|
|
37675
39198
|
const { command: analyzeCmd, args: analyzeArgs } = resolveScript(__dirname, "analyze.ts");
|
|
37676
|
-
const childProcess = (0,
|
|
39199
|
+
const childProcess = (0, import_child_process13.spawn)(analyzeCmd, [
|
|
37677
39200
|
...analyzeArgs,
|
|
37678
39201
|
`--type=research`,
|
|
37679
39202
|
`--topic=${topic}`,
|
|
@@ -37685,22 +39208,22 @@ ${kpiLines}`;
|
|
|
37685
39208
|
const timeout = setTimeout(() => {
|
|
37686
39209
|
childProcess.kill("SIGTERM");
|
|
37687
39210
|
log("Research task timed out after 15 minutes");
|
|
37688
|
-
|
|
39211
|
+
resolve4(false);
|
|
37689
39212
|
}, 9e5);
|
|
37690
39213
|
childProcess.on("close", (code) => {
|
|
37691
39214
|
clearTimeout(timeout);
|
|
37692
39215
|
if (code === 0) {
|
|
37693
39216
|
log("Research task completed successfully");
|
|
37694
|
-
|
|
39217
|
+
resolve4(true);
|
|
37695
39218
|
} else {
|
|
37696
39219
|
log(`Research task failed with code ${code}`);
|
|
37697
|
-
|
|
39220
|
+
resolve4(false);
|
|
37698
39221
|
}
|
|
37699
39222
|
});
|
|
37700
39223
|
childProcess.on("error", (error) => {
|
|
37701
39224
|
clearTimeout(timeout);
|
|
37702
39225
|
log(`Research task error: ${error.message}`);
|
|
37703
|
-
|
|
39226
|
+
resolve4(false);
|
|
37704
39227
|
});
|
|
37705
39228
|
});
|
|
37706
39229
|
}
|
|
@@ -37726,7 +39249,7 @@ function buildGoalGapPrompt(goal, existingIdeas, liveKPIs, config, businessConte
|
|
|
37726
39249
|
const funnelSummary = businessContext.funnels.map(
|
|
37727
39250
|
(f) => `- **${f.name}** (${f.status}): ${f.key_insight}`
|
|
37728
39251
|
).join("\n");
|
|
37729
|
-
const goalsForHealth =
|
|
39252
|
+
const goalsForHealth = loadJson3(GOALS_FILE, { goals: [] }).goals;
|
|
37730
39253
|
const health = computeFunnelHealth(businessContext, goalsForHealth);
|
|
37731
39254
|
const earliestBroken = health.earliest_broken_stage || "unknown";
|
|
37732
39255
|
const activeLawyers = businessContext.funnels.reduce((sum, f) => sum + (f.active_lawyers?.length || 0), 0);
|
|
@@ -37823,7 +39346,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37823
39346
|
return false;
|
|
37824
39347
|
}
|
|
37825
39348
|
log(`Executing goal-gap research for ${goalId}...`);
|
|
37826
|
-
const goalsData =
|
|
39349
|
+
const goalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
37827
39350
|
const goal = goalsData.goals.find((g2) => g2.id === goalId);
|
|
37828
39351
|
if (!goal) {
|
|
37829
39352
|
log(`Goal ${goalId} not found`);
|
|
@@ -37833,17 +39356,17 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37833
39356
|
log(`Goal ${goalId} is not behind or at_risk (status: ${goal.status}), skipping`);
|
|
37834
39357
|
return false;
|
|
37835
39358
|
}
|
|
37836
|
-
const ideasData =
|
|
39359
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37837
39360
|
const existingIdeas = ideasData.ideas.filter((i) => i.goal_id === goalId);
|
|
37838
39361
|
const liveKPIs = await fetchLiveKPIs();
|
|
37839
39362
|
const config = loadConfig();
|
|
37840
39363
|
const businessContext = loadBusinessContext();
|
|
37841
39364
|
const prompt = buildGoalGapPrompt(goal, existingIdeas, liveKPIs, config, businessContext);
|
|
37842
39365
|
log(`Goal-gap prompt built (${prompt.length} chars), spawning Claude Code...`);
|
|
37843
|
-
return new Promise((
|
|
39366
|
+
return new Promise((resolve4) => {
|
|
37844
39367
|
const TIMEOUT_MS = 9e5;
|
|
37845
39368
|
const startTime = Date.now();
|
|
37846
|
-
const claude = (0,
|
|
39369
|
+
const claude = (0, import_child_process13.spawn)("claude", [
|
|
37847
39370
|
"--print",
|
|
37848
39371
|
"--dangerously-skip-permissions"
|
|
37849
39372
|
], {
|
|
@@ -37867,7 +39390,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37867
39390
|
clearInterval(progressInterval);
|
|
37868
39391
|
claude.kill("SIGTERM");
|
|
37869
39392
|
log("Goal-gap research timed out after 15 minutes");
|
|
37870
|
-
|
|
39393
|
+
resolve4(false);
|
|
37871
39394
|
}
|
|
37872
39395
|
}, TIMEOUT_MS);
|
|
37873
39396
|
claude.stdout.on("data", (data) => {
|
|
@@ -37887,7 +39410,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37887
39410
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
37888
39411
|
if (code !== 0) {
|
|
37889
39412
|
log(`Goal-gap research failed with code ${code} after ${elapsed}s`);
|
|
37890
|
-
|
|
39413
|
+
resolve4(false);
|
|
37891
39414
|
return;
|
|
37892
39415
|
}
|
|
37893
39416
|
log(`Goal-gap research completed in ${elapsed}s, parsing ideas...`);
|
|
@@ -37895,7 +39418,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37895
39418
|
const jsonMatch = sanitized.match(/\[\s*\{[\s\S]*\}\s*\]/);
|
|
37896
39419
|
if (!jsonMatch) {
|
|
37897
39420
|
log("No JSON array found in goal-gap research response");
|
|
37898
|
-
|
|
39421
|
+
resolve4(false);
|
|
37899
39422
|
return;
|
|
37900
39423
|
}
|
|
37901
39424
|
try {
|
|
@@ -37937,27 +39460,27 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37937
39460
|
}));
|
|
37938
39461
|
if (newIdeas.length === 0) {
|
|
37939
39462
|
log("No ideas parsed from goal-gap research");
|
|
37940
|
-
|
|
39463
|
+
resolve4(false);
|
|
37941
39464
|
return;
|
|
37942
39465
|
}
|
|
37943
|
-
const freshIdeasData =
|
|
39466
|
+
const freshIdeasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
37944
39467
|
freshIdeasData.ideas.push(...newIdeas);
|
|
37945
|
-
|
|
37946
|
-
const freshGoalsData =
|
|
39468
|
+
saveJson2(IDEAS_FILE, freshIdeasData);
|
|
39469
|
+
const freshGoalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
37947
39470
|
const freshGoal = freshGoalsData.goals.find((g2) => g2.id === goalId);
|
|
37948
39471
|
if (freshGoal) {
|
|
37949
39472
|
if (!freshGoal.related_ideas) freshGoal.related_ideas = [];
|
|
37950
39473
|
freshGoal.related_ideas.push(...newIdeas.map((i) => i.id));
|
|
37951
|
-
|
|
39474
|
+
saveJson2(GOALS_FILE, freshGoalsData);
|
|
37952
39475
|
}
|
|
37953
39476
|
log(`Goal-gap research generated ${newIdeas.length} ideas for ${goalId}:`);
|
|
37954
39477
|
newIdeas.forEach((idea, i) => {
|
|
37955
39478
|
log(` ${i + 1}. ${idea.title}`);
|
|
37956
39479
|
});
|
|
37957
|
-
|
|
39480
|
+
resolve4(true);
|
|
37958
39481
|
} catch (error) {
|
|
37959
39482
|
log(`Failed to parse goal-gap research ideas: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
37960
|
-
|
|
39483
|
+
resolve4(false);
|
|
37961
39484
|
}
|
|
37962
39485
|
});
|
|
37963
39486
|
claude.on("error", (error) => {
|
|
@@ -37966,7 +39489,7 @@ async function executeGoalGapResearch(goalId) {
|
|
|
37966
39489
|
clearInterval(progressInterval);
|
|
37967
39490
|
clearTimeout(timeout);
|
|
37968
39491
|
log(`Goal-gap research error: ${error.message}`);
|
|
37969
|
-
|
|
39492
|
+
resolve4(false);
|
|
37970
39493
|
}
|
|
37971
39494
|
});
|
|
37972
39495
|
});
|
|
@@ -38067,7 +39590,7 @@ Respond with JSON only (no markdown code blocks):
|
|
|
38067
39590
|
}
|
|
38068
39591
|
async function executeShippedEvaluation(ideaId) {
|
|
38069
39592
|
log(`Starting post-ship evaluation for idea: ${ideaId}`);
|
|
38070
|
-
const ideasData =
|
|
39593
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38071
39594
|
const idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
38072
39595
|
if (!idea) {
|
|
38073
39596
|
log(`Idea not found: ${ideaId}`);
|
|
@@ -38080,7 +39603,7 @@ async function executeShippedEvaluation(ideaId) {
|
|
|
38080
39603
|
const daysShipped = daysSince(idea.implementation?.completed_at || idea.updated_at);
|
|
38081
39604
|
let goal = null;
|
|
38082
39605
|
if (idea.goal_id) {
|
|
38083
|
-
const goalsData =
|
|
39606
|
+
const goalsData = loadJson3(GOALS_FILE, { goals: [] });
|
|
38084
39607
|
goal = goalsData.goals.find((g2) => g2.id === idea.goal_id) || null;
|
|
38085
39608
|
}
|
|
38086
39609
|
const liveKPIs = await fetchLiveKPIs();
|
|
@@ -38222,8 +39745,22 @@ ${result.metric_evaluations.map((m2) => `- ${m2.status.toUpperCase()}: ${m2.metr
|
|
|
38222
39745
|
});
|
|
38223
39746
|
ideasData.ideas[ideaIndex].comments = comments;
|
|
38224
39747
|
}
|
|
38225
|
-
|
|
39748
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
38226
39749
|
log(`Evaluation saved for ${ideaId}: status=${result.status}, next_eval=${nextEvalDate.toISOString().split("T")[0]}`);
|
|
39750
|
+
if (idea.hypothesis_id) {
|
|
39751
|
+
try {
|
|
39752
|
+
const updated = updateHypothesisFromEvaluation(
|
|
39753
|
+
idea.hypothesis_id,
|
|
39754
|
+
result.status,
|
|
39755
|
+
result.summary || ""
|
|
39756
|
+
);
|
|
39757
|
+
if (updated) {
|
|
39758
|
+
log(`Hypothesis ${idea.hypothesis_id} evidence updated (${result.status})`);
|
|
39759
|
+
}
|
|
39760
|
+
} catch (hypError) {
|
|
39761
|
+
log(`Hypothesis update failed (non-fatal): ${hypError instanceof Error ? hypError.message : "Unknown"}`);
|
|
39762
|
+
}
|
|
39763
|
+
}
|
|
38227
39764
|
return true;
|
|
38228
39765
|
}
|
|
38229
39766
|
function generateResearchTasks(idea, config, businessContext) {
|
|
@@ -38466,8 +40003,8 @@ async function executeSingleResearchTask(task) {
|
|
|
38466
40003
|
log(` Starting research task: ${task.type} - ${task.topic}`);
|
|
38467
40004
|
task.status = "running";
|
|
38468
40005
|
task.started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
38469
|
-
return new Promise((
|
|
38470
|
-
const claude = (0,
|
|
40006
|
+
return new Promise((resolve4) => {
|
|
40007
|
+
const claude = (0, import_child_process13.spawn)("claude", ["--print", "--dangerously-skip-permissions"], {
|
|
38471
40008
|
cwd: ROOT_DIR,
|
|
38472
40009
|
env: process.env,
|
|
38473
40010
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -38482,7 +40019,7 @@ async function executeSingleResearchTask(task) {
|
|
|
38482
40019
|
task.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
38483
40020
|
task.error_message = "Timed out after 5 minutes";
|
|
38484
40021
|
log(` Research task timed out: ${task.type}`);
|
|
38485
|
-
|
|
40022
|
+
resolve4(task);
|
|
38486
40023
|
}, TIMEOUT_MS);
|
|
38487
40024
|
claude.stdout.on("data", (data) => {
|
|
38488
40025
|
output += data.toString();
|
|
@@ -38511,7 +40048,7 @@ async function executeSingleResearchTask(task) {
|
|
|
38511
40048
|
task.error_message = errorOutput || `Exited with code ${code}`;
|
|
38512
40049
|
log(` Research task failed: ${task.type} - ${task.error_message}`);
|
|
38513
40050
|
}
|
|
38514
|
-
|
|
40051
|
+
resolve4(task);
|
|
38515
40052
|
});
|
|
38516
40053
|
claude.on("error", (error) => {
|
|
38517
40054
|
clearTimeout(timeout);
|
|
@@ -38519,7 +40056,7 @@ async function executeSingleResearchTask(task) {
|
|
|
38519
40056
|
task.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
38520
40057
|
task.error_message = error.message;
|
|
38521
40058
|
log(` Research task error: ${task.type} - ${error.message}`);
|
|
38522
|
-
|
|
40059
|
+
resolve4(task);
|
|
38523
40060
|
});
|
|
38524
40061
|
});
|
|
38525
40062
|
}
|
|
@@ -38609,7 +40146,7 @@ ${parsed.risks_identified?.map((r) => `- ${r}`).join("\n") || "None identified"}
|
|
|
38609
40146
|
}
|
|
38610
40147
|
async function executeIdeaResearch(ideaId, config, businessContext) {
|
|
38611
40148
|
log(`Executing parallel research for idea: ${ideaId}`);
|
|
38612
|
-
const ideasData =
|
|
40149
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38613
40150
|
const idea = ideasData.ideas.find((i) => i.id === ideaId);
|
|
38614
40151
|
if (!idea) {
|
|
38615
40152
|
log(`Idea not found: ${ideaId}`);
|
|
@@ -38620,7 +40157,7 @@ async function executeIdeaResearch(ideaId, config, businessContext) {
|
|
|
38620
40157
|
log(`Auto-transitioning idea ${ideaId} from inbox to under_review for research`);
|
|
38621
40158
|
idea.stage = "under_review";
|
|
38622
40159
|
idea.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
38623
|
-
|
|
40160
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
38624
40161
|
} else {
|
|
38625
40162
|
log(`Idea ${ideaId} is not in under_review stage (current: ${idea.stage})`);
|
|
38626
40163
|
return false;
|
|
@@ -38663,7 +40200,7 @@ ${research.summary}
|
|
|
38663
40200
|
});
|
|
38664
40201
|
idea.comments = comments;
|
|
38665
40202
|
}
|
|
38666
|
-
|
|
40203
|
+
saveJson2(IDEAS_FILE, ideasData);
|
|
38667
40204
|
log(`Research saved for idea ${ideaId}: recommendation = ${research.recommendation}`);
|
|
38668
40205
|
return true;
|
|
38669
40206
|
}
|
|
@@ -38704,7 +40241,7 @@ async function executeTask(task, config, businessContext) {
|
|
|
38704
40241
|
const ideaId = taskId.replace("evaluate-shipped-", "");
|
|
38705
40242
|
if (!ideaId.startsWith("idea-")) {
|
|
38706
40243
|
log(`Batch evaluation requested (parsed "${ideaId}" from task "${taskId}")`);
|
|
38707
|
-
const batchIdeas =
|
|
40244
|
+
const batchIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38708
40245
|
const shippedReady = batchIdeas.ideas.filter((i) => {
|
|
38709
40246
|
if (i.stage !== "shipped") return false;
|
|
38710
40247
|
const v2 = i.verification;
|
|
@@ -38800,6 +40337,12 @@ async function executeTask(task, config, businessContext) {
|
|
|
38800
40337
|
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
38801
40338
|
return result.success;
|
|
38802
40339
|
}
|
|
40340
|
+
if (isGrowthCampaignTask(taskId)) {
|
|
40341
|
+
const result = await executeGrowthCampaignTask(taskId, taskDesc, businessContext);
|
|
40342
|
+
log(`Growth campaign task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
40343
|
+
trackEvent("heartbeat_task", { taskType: taskTypePrefix, success: result.success });
|
|
40344
|
+
return result.success;
|
|
40345
|
+
}
|
|
38803
40346
|
if (isLaunchCampaignTask(taskId)) {
|
|
38804
40347
|
const result = await executeLaunchCampaignTask(taskId, taskDesc, businessContext);
|
|
38805
40348
|
log(`Campaign task: ${result.success ? "SUCCESS" : "FAILED"} \u2014 ${result.output.slice(0, 200)}`);
|
|
@@ -38828,7 +40371,7 @@ async function executeTask(task, config, businessContext) {
|
|
|
38828
40371
|
}
|
|
38829
40372
|
}
|
|
38830
40373
|
async function executeReviewInbox() {
|
|
38831
|
-
const ideasData =
|
|
40374
|
+
const ideasData = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
38832
40375
|
const inboxIdeas = ideasData.ideas.filter((i) => i.stage === "inbox");
|
|
38833
40376
|
if (inboxIdeas.length === 0) {
|
|
38834
40377
|
log("No ideas in inbox to review");
|
|
@@ -38842,17 +40385,17 @@ async function executeReviewInbox() {
|
|
|
38842
40385
|
log("Inbox review complete (manual prioritization needed)");
|
|
38843
40386
|
}
|
|
38844
40387
|
async function runAnalysis(type) {
|
|
38845
|
-
return new Promise((
|
|
40388
|
+
return new Promise((resolve4, reject) => {
|
|
38846
40389
|
log(`Running ${type} analysis...`);
|
|
38847
40390
|
const { command: runCmd, args: runArgs } = resolveScript(__dirname, "analyze.ts");
|
|
38848
|
-
const childProcess = (0,
|
|
40391
|
+
const childProcess = (0, import_child_process13.spawn)(runCmd, [...runArgs, `--type=${type}`], {
|
|
38849
40392
|
cwd: ROOT_DIR,
|
|
38850
40393
|
stdio: "inherit"
|
|
38851
40394
|
});
|
|
38852
40395
|
childProcess.on("close", (code) => {
|
|
38853
40396
|
if (code === 0) {
|
|
38854
40397
|
log(`${type} analysis completed successfully`);
|
|
38855
|
-
|
|
40398
|
+
resolve4();
|
|
38856
40399
|
} else {
|
|
38857
40400
|
reject(new Error(`Analysis exited with code ${code}`));
|
|
38858
40401
|
}
|
|
@@ -38864,7 +40407,7 @@ async function runAnalysis(type) {
|
|
|
38864
40407
|
}
|
|
38865
40408
|
function updateTodoFile(taskId, completed) {
|
|
38866
40409
|
try {
|
|
38867
|
-
let content =
|
|
40410
|
+
let content = fs42.readFileSync(TODO_FILE, "utf-8");
|
|
38868
40411
|
const uncheckedPattern = new RegExp(`- \\[ \\] \`${taskId}\``, "g");
|
|
38869
40412
|
const checkedPattern = new RegExp(`- \\[x\\] \`${taskId}\``, "g");
|
|
38870
40413
|
if (completed) {
|
|
@@ -38872,7 +40415,7 @@ function updateTodoFile(taskId, completed) {
|
|
|
38872
40415
|
} else {
|
|
38873
40416
|
content = content.replace(checkedPattern, `- [ ] \`${taskId}\``);
|
|
38874
40417
|
}
|
|
38875
|
-
|
|
40418
|
+
fs42.writeFileSync(TODO_FILE, content);
|
|
38876
40419
|
log(`Updated TODO.md: ${taskId} marked as ${completed ? "completed" : "pending"}`);
|
|
38877
40420
|
} catch (error) {
|
|
38878
40421
|
log(`Failed to update TODO.md: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -38991,8 +40534,8 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
38991
40534
|
if (vibeResult.warning) log(vibeResult.warning);
|
|
38992
40535
|
log(`Vibe consumed (${vibeResult.vibes_remaining} remaining${vibeResult.plan ? `, plan: ${vibeResult.plan}` : ""}${vibeResult.offline ? ", offline" : ""})`);
|
|
38993
40536
|
}
|
|
38994
|
-
const commandsDir =
|
|
38995
|
-
if (!
|
|
40537
|
+
const commandsDir = path34.join(PROJECT_DIR, ".claude", "commands");
|
|
40538
|
+
if (!fs42.existsSync(commandsDir)) {
|
|
38996
40539
|
log("Slash commands missing \u2014 auto-scaffolding...");
|
|
38997
40540
|
scaffoldSlashCommands(PROJECT_DIR);
|
|
38998
40541
|
}
|
|
@@ -39029,7 +40572,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
39029
40572
|
if (systemHealth.all_stuck && !DRY_RUN) {
|
|
39030
40573
|
log(`Auto-recovering: deferring ${systemHealth.stuck_idea_ids.length} stuck idea(s)...`);
|
|
39031
40574
|
autoRecoverStuckIdeas(systemHealth.stuck_idea_ids);
|
|
39032
|
-
const reloadedIdeas =
|
|
40575
|
+
const reloadedIdeas = loadJson3(IDEAS_FILE, { ideas: [] });
|
|
39033
40576
|
state.ideas = reloadedIdeas.ideas;
|
|
39034
40577
|
}
|
|
39035
40578
|
const liveKPIs = await fetchLiveKPIs();
|
|
@@ -39040,7 +40583,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
39040
40583
|
};
|
|
39041
40584
|
if (liveKPIs && !DRY_RUN) {
|
|
39042
40585
|
updateGoalsWithKPIs(liveKPIs);
|
|
39043
|
-
const updatedGoals =
|
|
40586
|
+
const updatedGoals = loadJson3(GOALS_FILE, { goals: [] });
|
|
39044
40587
|
state.goals = updatedGoals.goals;
|
|
39045
40588
|
updateBusinessContext(businessContext, liveKPIs);
|
|
39046
40589
|
log("Updated business context with live KPIs");
|
|
@@ -39176,8 +40719,8 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
39176
40719
|
}
|
|
39177
40720
|
}
|
|
39178
40721
|
if (!inProgressIdeas.some((i) => (i.implementation.sub_tasks || []).some((st) => st.status === "pending"))) {
|
|
39179
|
-
const fallbackHypotheses =
|
|
39180
|
-
const fallbackEpics =
|
|
40722
|
+
const fallbackHypotheses = loadJson3(HYPOTHESES_FILE, { hypotheses: [] }).hypotheses || [];
|
|
40723
|
+
const fallbackEpics = loadJson3(ROADMAP_FILE, { epics: [] }).epics || [];
|
|
39181
40724
|
const approvedIdeas = state.ideas.filter(
|
|
39182
40725
|
(i) => i.stage === "approved" && (i.implementation?.decomposition_attempts || 0) < fallbackMaxRetries
|
|
39183
40726
|
).sort((a, b) => scoreIdea(b, state.goals, fallbackHypotheses, fallbackEpics) - scoreIdea(a, state.goals, fallbackHypotheses, fallbackEpics));
|
|
@@ -39205,7 +40748,7 @@ async function runSingleHeartbeat(beatNumber = 1) {
|
|
|
39205
40748
|
}
|
|
39206
40749
|
if (!DRY_RUN) {
|
|
39207
40750
|
const statusContent = generateStatusContent(state, alerts, taskToDo, reasoning, systemHealth);
|
|
39208
|
-
|
|
40751
|
+
fs42.writeFileSync(STATUS_FILE, statusContent);
|
|
39209
40752
|
log("Updated STATUS.md");
|
|
39210
40753
|
} else {
|
|
39211
40754
|
log("[DRY RUN] Would update STATUS.md");
|
|
@@ -39320,13 +40863,13 @@ ${"=".repeat(60)}
|
|
|
39320
40863
|
`);
|
|
39321
40864
|
}
|
|
39322
40865
|
function saveSessionToJson(summary) {
|
|
39323
|
-
const sessionsFile =
|
|
39324
|
-
const sessionsData =
|
|
40866
|
+
const sessionsFile = path34.join(DATA_DIR, "heartbeat-sessions.json");
|
|
40867
|
+
const sessionsData = loadJson3(sessionsFile, { sessions: [] });
|
|
39325
40868
|
sessionsData.sessions.push(summary);
|
|
39326
40869
|
if (sessionsData.sessions.length > 50) {
|
|
39327
40870
|
sessionsData.sessions = sessionsData.sessions.slice(-50);
|
|
39328
40871
|
}
|
|
39329
|
-
|
|
40872
|
+
saveJson2(sessionsFile, sessionsData);
|
|
39330
40873
|
log(`Session saved to ${sessionsFile}`);
|
|
39331
40874
|
}
|
|
39332
40875
|
async function runContinuousSession() {
|