edsger 0.30.4 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.claude/settings.local.json +3 -23
  2. package/dist/api/github.d.ts +11 -0
  3. package/dist/api/github.js +44 -0
  4. package/dist/index.js +19 -6
  5. package/dist/phases/growth-analysis/agent.d.ts +1 -1
  6. package/dist/phases/growth-analysis/agent.js +2 -1
  7. package/dist/phases/growth-analysis/context.d.ts +1 -1
  8. package/dist/phases/growth-analysis/context.js +11 -3
  9. package/dist/phases/growth-analysis/index.js +26 -3
  10. package/dist/phases/growth-analysis/prompts.d.ts +2 -2
  11. package/dist/phases/growth-analysis/prompts.js +34 -17
  12. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  13. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  14. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  15. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  16. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  17. package/dist/services/lifecycle-agent/index.js +25 -0
  18. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  19. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  20. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  21. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  22. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  23. package/dist/services/lifecycle-agent/types.js +12 -0
  24. package/package.json +1 -1
  25. package/.env.local +0 -12
  26. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  27. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  28. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  29. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  30. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  31. package/dist/commands/workflow/pipeline-runner.js +0 -393
  32. package/dist/commands/workflow/runner.d.ts +0 -26
  33. package/dist/commands/workflow/runner.js +0 -119
  34. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  35. package/dist/commands/workflow/workflow-runner.js +0 -119
  36. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  37. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  38. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  39. package/dist/phases/code-implementation/analyzer.js +0 -629
  40. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  41. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  42. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  43. package/dist/phases/code-implementation/mcp-server.js +0 -93
  44. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  45. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  46. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  47. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  48. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  49. package/dist/phases/code-refine/analyzer.js +0 -561
  50. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  51. package/dist/phases/code-refine/context-fetcher.js +0 -423
  52. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  53. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  54. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  55. package/dist/phases/code-refine-verification/verifier.js +0 -597
  56. package/dist/phases/code-review/analyzer.d.ts +0 -29
  57. package/dist/phases/code-review/analyzer.js +0 -363
  58. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  59. package/dist/phases/code-review/context-fetcher.js +0 -296
  60. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  61. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  62. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  63. package/dist/phases/feature-analysis/analyzer.js +0 -208
  64. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  65. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  66. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  67. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  68. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  69. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  70. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  71. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  72. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  73. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  74. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  75. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  76. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  77. package/dist/phases/technical-design/analyzer.js +0 -461
  78. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  79. package/dist/phases/technical-design/context-fetcher.js +0 -39
  80. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  81. package/dist/phases/technical-design/http-fallback.js +0 -151
  82. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  83. package/dist/phases/technical-design/mcp-server.js +0 -157
  84. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  85. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  86. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  87. package/dist/phases/technical-design-verification/verifier.js +0 -170
  88. package/dist/services/feature-branches.d.ts +0 -77
  89. package/dist/services/feature-branches.js +0 -205
  90. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  91. package/dist/workflow-runner/config/phase-configs.js +0 -120
  92. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  93. package/dist/workflow-runner/core/feature-filter.js +0 -46
  94. package/dist/workflow-runner/core/index.d.ts +0 -8
  95. package/dist/workflow-runner/core/index.js +0 -12
  96. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  97. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  98. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  99. package/dist/workflow-runner/core/state-manager.js +0 -42
  100. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  101. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  102. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  103. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  104. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  105. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  106. package/dist/workflow-runner/index.d.ts +0 -2
  107. package/dist/workflow-runner/index.js +0 -2
  108. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  109. package/dist/workflow-runner/pipeline-runner.js +0 -393
  110. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  111. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -1,28 +1,8 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Read(//Users/steven/development/edsger/**)",
5
- "Bash(npm run build)",
6
- "Bash(node:*)",
7
- "Bash(git add:*)",
8
- "Bash(git commit:*)",
9
- "Bash(ls:*)",
10
- "Bash(cat:*)",
11
- "Bash(npm run typecheck:*)",
12
- "Bash(git diff:*)",
13
- "WebSearch",
14
- "WebFetch(domain:supabase.com)",
15
- "Bash(npm install:*)",
16
- "Bash(grep:*)",
17
- "Bash(npx supabase gen types typescript --help:*)",
18
- "Bash(git -C /Users/steven/development/edsger status)",
19
- "Bash(git -C /Users/steven/development/edsger diff)",
20
- "Bash(git -C /Users/steven/development/edsger log --oneline -5)",
21
- "Bash(git -C /Users/steven/development/edsger add supabase/migrations/20251231000000_drop_unused_views.sql)",
22
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views that are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
23
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views\nthat are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
24
- ],
25
- "deny": [],
26
- "ask": []
4
+ "Bash(npx tsc:*)",
5
+ "Bash(npm run:*)"
6
+ ]
27
7
  }
28
8
  }
@@ -39,6 +39,17 @@ export declare function getGitHubDeveloperConfig(featureId: string, verbose?: bo
39
39
  * Get installation access token for GitHub API operations
40
40
  */
41
41
  export declare function getGitHubInstallationToken(installationId: number, featureId: string, verbose?: boolean): Promise<GitHubInstallationToken | null>;
42
+ /**
43
+ * Get GitHub config and token by product ID (no feature required).
44
+ * Used for product-level operations like growth analysis.
45
+ */
46
+ export declare function getGitHubConfigByProduct(productId: string, verbose?: boolean): Promise<{
47
+ configured: boolean;
48
+ token?: string;
49
+ owner?: string;
50
+ repo?: string;
51
+ message?: string;
52
+ }>;
42
53
  /**
43
54
  * Get GitHub config and token in one call
44
55
  * This is the main entry point for getting GitHub config
@@ -59,6 +59,50 @@ export async function getGitHubInstallationToken(installationId, featureId, verb
59
59
  return null;
60
60
  }
61
61
  }
62
+ /**
63
+ * Get GitHub config and token by product ID (no feature required).
64
+ * Used for product-level operations like growth analysis.
65
+ */
66
+ export async function getGitHubConfigByProduct(productId, verbose) {
67
+ if (verbose) {
68
+ console.log(`🔍 Fetching GitHub config for product: ${productId}`);
69
+ }
70
+ try {
71
+ const result = (await callMcpEndpoint('github/config_and_token_by_product', {
72
+ product_id: productId,
73
+ }));
74
+ if (verbose) {
75
+ if (result.configured) {
76
+ console.log(`✅ GitHub ready: ${result.repository_full_name}`);
77
+ }
78
+ else {
79
+ console.log(`⚠️ GitHub not configured: ${result.message}`);
80
+ }
81
+ }
82
+ if (result.configured && result.token && result.owner && result.repo) {
83
+ return {
84
+ configured: true,
85
+ token: result.token,
86
+ owner: result.owner,
87
+ repo: result.repo,
88
+ };
89
+ }
90
+ return {
91
+ configured: false,
92
+ message: result.message ||
93
+ 'GitHub not configured for this product. Set repo and developer_id in product settings.',
94
+ };
95
+ }
96
+ catch (error) {
97
+ if (verbose) {
98
+ console.error('❌ Failed to get GitHub config for product:', error);
99
+ }
100
+ return {
101
+ configured: false,
102
+ message: error instanceof Error ? error.message : 'Failed to get GitHub config',
103
+ };
104
+ }
105
+ }
62
106
  /**
63
107
  * Get GitHub config and token in one call
64
108
  * This is the main entry point for getting GitHub config
package/dist/index.js CHANGED
@@ -72,6 +72,25 @@ program
72
72
  }
73
73
  });
74
74
  // ============================================================
75
+ // Subcommand: edsger growth <productId>
76
+ // ============================================================
77
+ program
78
+ .command('growth <productId>')
79
+ .description('Run AI-powered growth analysis for a product')
80
+ .option('-v, --verbose', 'Verbose output')
81
+ .action(async (productId, opts) => {
82
+ try {
83
+ await runGrowthAnalysis({
84
+ growthAnalysis: productId,
85
+ verbose: opts.verbose,
86
+ });
87
+ }
88
+ catch (error) {
89
+ logError(error instanceof Error ? error.message : String(error));
90
+ process.exit(1);
91
+ }
92
+ });
93
+ // ============================================================
75
94
  // Default command (options-based, backward compatible)
76
95
  // ============================================================
77
96
  program
@@ -80,7 +99,6 @@ program
80
99
  .option('-f, --files <patterns...>', 'Review specific file patterns')
81
100
  .option('--refactor', 'Refactor code in current directory')
82
101
  .option('--init', 'Initialize .edsger directory with project templates')
83
- .option('--growth-analysis <productId>', 'Run AI-powered growth analysis for a product')
84
102
  .option('--product-level', 'Use legacy product-level workflow (requires EDSGER_PRODUCT_ID, EDSGER_MCP_SERVER_URL, EDSGER_MCP_TOKEN env vars)')
85
103
  .option('--watch-tasks <productId>', 'Watch and execute pending tasks for a product')
86
104
  .option('--concurrency <number>', 'Max concurrent features to process (default: 3)', parseInt)
@@ -106,11 +124,6 @@ export const runEdsger = async (options) => {
106
124
  await runTaskWorker(options);
107
125
  return;
108
126
  }
109
- // Handle growth analysis mode
110
- if (options.growthAnalysis) {
111
- await runGrowthAnalysis(options);
112
- return;
113
- }
114
127
  // Handle refactor mode
115
128
  if (options.refactor) {
116
129
  await runRefactor(options);
@@ -1,2 +1,2 @@
1
1
  import { EdsgerConfig } from '../../types/index.js';
2
- export declare function executeGrowthAnalysisQuery(currentPrompt: string, systemPrompt: string, config: EdsgerConfig, verbose?: boolean): Promise<any | null>;
2
+ export declare function executeGrowthAnalysisQuery(currentPrompt: string, systemPrompt: string, config: EdsgerConfig, verbose?: boolean, cwd?: string): Promise<any | null>;
@@ -38,7 +38,7 @@ function userMessage(content) {
38
38
  async function* prompt(analysisPrompt) {
39
39
  yield userMessage(analysisPrompt);
40
40
  }
41
- export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, config, verbose) {
41
+ export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, config, verbose, cwd) {
42
42
  let lastAssistantResponse = '';
43
43
  let structuredResult = null;
44
44
  for await (const message of query({
@@ -52,6 +52,7 @@ export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, co
52
52
  model: DEFAULT_MODEL,
53
53
  maxTurns: 1000,
54
54
  permissionMode: 'bypassPermissions',
55
+ ...(cwd ? { cwd } : {}),
55
56
  },
56
57
  })) {
57
58
  if (verbose) {
@@ -15,7 +15,7 @@ export declare function formatContextForPrompt(context: GrowthAnalysisContext):
15
15
  /**
16
16
  * Prepare the full analysis prompt with all context
17
17
  */
18
- export declare function prepareGrowthAnalysisContext(productId: string, verbose?: boolean): Promise<{
18
+ export declare function prepareGrowthAnalysisContext(productId: string, verbose?: boolean, hasCodebase?: boolean): Promise<{
19
19
  context: GrowthAnalysisContext;
20
20
  analysisPrompt: string;
21
21
  }>;
@@ -40,6 +40,11 @@ export function formatContextForPrompt(context) {
40
40
  Created: ${c.created_at}`)
41
41
  .join('\n\n')
42
42
  : 'No previous campaigns.';
43
+ const featuresList = product.features && product.features.length > 0
44
+ ? product.features
45
+ .map((f) => `- **${f.name}**: ${f.description || 'No description'} (Status: ${f.status || 'unknown'})`)
46
+ .join('\n')
47
+ : 'No features listed.';
43
48
  return `# Growth Analysis Context
44
49
 
45
50
  ## Product Information
@@ -47,22 +52,25 @@ export function formatContextForPrompt(context) {
47
52
  - **Product ID**: ${product.id}
48
53
  - **Description**: ${product.description || 'No description provided'}
49
54
 
55
+ ## Product Features (${product.features?.length || 0})
56
+ ${featuresList}
57
+
50
58
  ## Previous Growth Campaigns (${previousCampaigns.length})
51
59
  ${campaignsList}
52
60
 
53
61
  ---
54
62
 
55
- **Important**: Analyze the product above and create a growth strategy. Each content suggestion must be DIFFERENT from all previous campaigns listed above.`;
63
+ **Important**: Analyze the product above and create a growth strategy. Each content suggestion must be DIFFERENT from all previous campaigns listed above. Use the product name, description, and features to write specific, concrete content — never use placeholder text.`;
56
64
  }
57
65
  /**
58
66
  * Prepare the full analysis prompt with all context
59
67
  */
60
- export async function prepareGrowthAnalysisContext(productId, verbose) {
68
+ export async function prepareGrowthAnalysisContext(productId, verbose, hasCodebase = false) {
61
69
  if (verbose) {
62
70
  logInfo('Fetching growth analysis context via MCP endpoints...');
63
71
  }
64
72
  const context = await fetchGrowthAnalysisContext(productId, verbose);
65
73
  const contextInfo = formatContextForPrompt(context);
66
- const analysisPrompt = createGrowthAnalysisPromptWithContext(productId, contextInfo);
74
+ const analysisPrompt = createGrowthAnalysisPromptWithContext(productId, contextInfo, hasCodebase);
67
75
  return { context, analysisPrompt };
68
76
  }
@@ -3,18 +3,41 @@ import { prepareGrowthAnalysisContext } from './context.js';
3
3
  import { createGrowthAnalysisSystemPrompt } from './prompts.js';
4
4
  import { executeGrowthAnalysisQuery } from './agent.js';
5
5
  import { saveGrowthAnalysis } from '../../api/growth.js';
6
+ import { getGitHubConfigByProduct } from '../../api/github.js';
7
+ import { ensureWorkspaceDir, cloneFeatureRepo, } from '../../workspace/workspace-manager.js';
6
8
  export const analyseGrowth = async (options, config) => {
7
9
  const { productId, verbose } = options;
8
10
  if (verbose) {
9
11
  logInfo(`Starting growth analysis for product ID: ${productId}`);
10
12
  }
11
13
  try {
12
- const { context, analysisPrompt } = await prepareGrowthAnalysisContext(productId, verbose);
13
- const systemPrompt = createGrowthAnalysisSystemPrompt();
14
+ // Clone product repo if GitHub is configured
15
+ let repoCwd;
16
+ try {
17
+ const githubConfig = await getGitHubConfigByProduct(productId, verbose);
18
+ if (githubConfig.configured &&
19
+ githubConfig.token &&
20
+ githubConfig.owner &&
21
+ githubConfig.repo) {
22
+ const workspaceRoot = ensureWorkspaceDir();
23
+ const { repoPath } = cloneFeatureRepo(workspaceRoot, `growth-${productId}`, githubConfig.owner, githubConfig.repo, githubConfig.token);
24
+ repoCwd = repoPath;
25
+ logInfo(`Repository cloned to: ${repoCwd}`);
26
+ }
27
+ else {
28
+ logInfo(`No GitHub repo configured for product, running without codebase access. ${githubConfig.message || ''}`);
29
+ }
30
+ }
31
+ catch (error) {
32
+ logInfo(`Could not clone repo (continuing without codebase): ${error instanceof Error ? error.message : String(error)}`);
33
+ }
34
+ const hasCodebase = !!repoCwd;
35
+ const { context, analysisPrompt } = await prepareGrowthAnalysisContext(productId, verbose, hasCodebase);
36
+ const systemPrompt = createGrowthAnalysisSystemPrompt(hasCodebase);
14
37
  if (verbose) {
15
38
  logInfo('Starting AI query for growth analysis...');
16
39
  }
17
- const analysisResult = await executeGrowthAnalysisQuery(analysisPrompt, systemPrompt, config, verbose);
40
+ const analysisResult = await executeGrowthAnalysisQuery(analysisPrompt, systemPrompt, config, verbose, repoCwd);
18
41
  if (!analysisResult) {
19
42
  throw new Error('No analysis results received');
20
43
  }
@@ -1,2 +1,2 @@
1
- export declare const createGrowthAnalysisSystemPrompt: () => string;
2
- export declare const createGrowthAnalysisPromptWithContext: (productId: string, contextInfo: string) => string;
1
+ export declare const createGrowthAnalysisSystemPrompt: (hasCodebase?: boolean) => string;
2
+ export declare const createGrowthAnalysisPromptWithContext: (productId: string, contextInfo: string, hasCodebase?: boolean) => string;
@@ -1,14 +1,16 @@
1
- export const createGrowthAnalysisSystemPrompt = () => {
1
+ export const createGrowthAnalysisSystemPrompt = (hasCodebase = false) => {
2
2
  return `You are an expert growth marketer and product strategist. Your task is to analyze a product and generate a comprehensive growth strategy with specific, actionable content recommendations for different channels.
3
3
 
4
4
  **Your Role**: Analyze the product, understand its target audience, review previous campaigns to avoid repetition, and create specific content pieces ready to be published on different platforms.
5
5
 
6
- **Analysis Process**:
7
- 1. **Understand the Product**: Analyze the product's purpose, target audience, unique value proposition, and competitive positioning
8
- 2. **Review Previous Campaigns**: Study past campaigns to understand what has been done, which channels were used, and avoid creating duplicate content
9
- 3. **Research Current Trends**: Consider current industry trends, news, and timely topics relevant to the product
10
- 4. **Identify Target Channels**: Recommend the best channels for growth based on the product's audience
11
- 5. **Create Content Suggestions**: Generate specific, ready-to-publish content pieces for each recommended channel
6
+ **Analysis Process**:${hasCodebase ? `
7
+ 1. **Explore the Codebase**: BEFORE writing any content, use your tools to explore the project's codebase. Read README files, CLAUDE.md, package.json, landing pages, documentation, and key source files to deeply understand what the product actually does, its features, technical architecture, and unique selling points. This is CRITICAL for writing specific, accurate content.
8
+ 2. **Understand the Product**: Based on your codebase exploration AND the provided context, build a complete picture of the product's purpose, target audience, unique value proposition, and competitive positioning` : `
9
+ 1. **Understand the Product**: Analyze the product's purpose, target audience, unique value proposition, and competitive positioning from the provided context`}
10
+ ${hasCodebase ? '3' : '2'}. **Review Previous Campaigns**: Study past campaigns to understand what has been done, which channels were used, and avoid creating duplicate content
11
+ ${hasCodebase ? '4' : '3'}. **Research Current Trends**: Consider current industry trends, news, and timely topics relevant to the product
12
+ ${hasCodebase ? '5' : '4'}. **Identify Target Channels**: Recommend the best channels for growth based on the product's audience
13
+ ${hasCodebase ? '6' : '5'}. **Create Content Suggestions**: Generate specific, ready-to-publish content pieces for each recommended channel${hasCodebase ? ', using real details from your codebase exploration' : ''}
12
14
 
13
15
  **Channel Expertise**:
14
16
  - Twitter/X: Short-form content, threads, engagement posts
@@ -30,6 +32,13 @@ export const createGrowthAnalysisSystemPrompt = () => {
30
32
  - Consider timing and relevance to current events
31
33
  - Content must be different from all previous campaigns
32
34
 
35
+ **CRITICAL - NO PLACEHOLDERS**:
36
+ - NEVER use placeholder brackets like [one-line value prop], [core problem], [link], [Screenshot], [Differentiator 1], etc.
37
+ - ALL content must be fully written and ready to publish as-is
38
+ - Use the actual product name, description, and features provided in the context to write concrete, specific content
39
+ - If you don't have enough information for a detail (e.g., a URL or screenshot), either omit that part entirely or write around it naturally
40
+ - The user should be able to copy-paste the content directly to the target platform without any edits
41
+
33
42
  **CRITICAL - Result Format**:
34
43
  You MUST return ONLY a JSON object. Do NOT include any text before or after the JSON.
35
44
 
@@ -67,22 +76,30 @@ You MUST return ONLY a JSON object. Do NOT include any text before or after the
67
76
  - Include a mix of content types (educational, promotional, community-building)
68
77
  - Consider the product's current stage and adjust strategy accordingly`;
69
78
  };
70
- export const createGrowthAnalysisPromptWithContext = (productId, contextInfo) => {
79
+ export const createGrowthAnalysisPromptWithContext = (productId, contextInfo, hasCodebase = false) => {
80
+ const codebaseInstructions = hasCodebase
81
+ ? `
82
+ **Step 1 - Explore the codebase FIRST**: Before writing anything, use your file reading and search tools to explore this project's codebase. Look at:
83
+ - README.md, CLAUDE.md, package.json for project overview
84
+ - Landing pages, marketing copy, or documentation for existing messaging
85
+ - Key source files to understand what the product actually does technically
86
+ - Any configuration or deployment files that reveal the tech stack
87
+
88
+ **Step 2 - Analyze and create strategy**: Using your deep understanding from Step 1 plus the context above:`
89
+ : `
90
+ **Analyze and create strategy**: Using the product context above:`;
71
91
  return `Please conduct comprehensive growth analysis for product ID: ${productId}
72
92
 
73
93
  ${contextInfo}
74
94
 
75
95
  ## Analysis Instructions
96
+ ${codebaseInstructions}
97
+ 1. Understand the product's real value proposition, target users, and competitive advantages
98
+ 2. Study the previous campaigns listed above - DO NOT suggest content that overlaps with what has already been published
99
+ 3. Identify the most effective channels for reaching this product's target audience
100
+ 4. Generate specific, ready-to-publish content pieces using REAL details${hasCodebase ? ' from the codebase (actual feature names, real technical details, concrete benefits)' : ' from the product context above'}
76
101
 
77
- Analyze the product above and create a growth strategy:
78
-
79
- 1. **Product Analysis**: Understand the product's value proposition, target users, and competitive advantages
80
- 2. **Previous Campaigns Review**: Study the previous campaigns listed above - DO NOT suggest content that overlaps with what has already been published
81
- 3. **Channel Strategy**: Identify the most effective channels for reaching this product's target audience
82
- 4. **Content Creation**: Generate specific, ready-to-publish content pieces for each recommended channel
83
- 5. **Differentiation**: Ensure every suggested content piece is fresh and different from previous campaigns
84
-
85
- Focus on actionable, specific content that can be published immediately. Avoid generic advice.
102
+ **CRITICAL**: Every content piece must be ready to copy-paste and publish. Use actual product details, not placeholders.
86
103
 
87
104
  Return ONLY the JSON response as specified in your instructions.`;
88
105
  };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for phase quality criteria definitions
3
+ */
4
+ export {};
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Unit tests for phase quality criteria definitions
3
+ */
4
+ import { describe, it } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import { DEFAULT_PHASE_CRITERIA, USER_STORIES_ANALYSIS_CRITERIA, TEST_CASES_ANALYSIS_CRITERIA, TECHNICAL_DESIGN_CRITERIA, BRANCH_PLANNING_CRITERIA, CODE_IMPLEMENTATION_CRITERIA, FUNCTIONAL_TESTING_CRITERIA, CODE_REVIEW_CRITERIA, getPhaseQualityCriteria, } from '../phase-criteria.js';
7
+ describe('Phase Quality Criteria', () => {
8
+ describe('DEFAULT_PHASE_CRITERIA', () => {
9
+ it('should cover all evaluable phases', () => {
10
+ const expectedPhases = [
11
+ 'user_stories_analysis',
12
+ 'test_cases_analysis',
13
+ 'technical_design',
14
+ 'branch_planning',
15
+ 'code_implementation',
16
+ 'functional_testing',
17
+ 'code_review',
18
+ ];
19
+ for (const phase of expectedPhases) {
20
+ assert.ok(phase in DEFAULT_PHASE_CRITERIA, `Should have criteria for phase: ${phase}`);
21
+ }
22
+ });
23
+ it('should have consistent phase names in criteria objects', () => {
24
+ for (const [key, criteria] of Object.entries(DEFAULT_PHASE_CRITERIA)) {
25
+ assert.strictEqual(criteria.phase, key, `Criteria for ${key} should have matching phase name`);
26
+ }
27
+ });
28
+ });
29
+ describe('Criteria Structural Validity', () => {
30
+ const allCriteria = [
31
+ USER_STORIES_ANALYSIS_CRITERIA,
32
+ TEST_CASES_ANALYSIS_CRITERIA,
33
+ TECHNICAL_DESIGN_CRITERIA,
34
+ BRANCH_PLANNING_CRITERIA,
35
+ CODE_IMPLEMENTATION_CRITERIA,
36
+ FUNCTIONAL_TESTING_CRITERIA,
37
+ CODE_REVIEW_CRITERIA,
38
+ ];
39
+ for (const phaseCriteria of allCriteria) {
40
+ describe(phaseCriteria.phase, () => {
41
+ it('should have advanceThreshold > escalateThreshold', () => {
42
+ assert.ok(phaseCriteria.advanceThreshold > phaseCriteria.escalateThreshold, `advanceThreshold (${phaseCriteria.advanceThreshold}) should be > escalateThreshold (${phaseCriteria.escalateThreshold})`);
43
+ });
44
+ it('should have thresholds in valid range (0-100)', () => {
45
+ assert.ok(phaseCriteria.advanceThreshold >= 0);
46
+ assert.ok(phaseCriteria.advanceThreshold <= 100);
47
+ assert.ok(phaseCriteria.escalateThreshold >= 0);
48
+ assert.ok(phaseCriteria.escalateThreshold <= 100);
49
+ });
50
+ it('should have maxAutoRetries >= 1', () => {
51
+ assert.ok(phaseCriteria.maxAutoRetries >= 1, `maxAutoRetries should be >= 1, got ${phaseCriteria.maxAutoRetries}`);
52
+ });
53
+ it('should have at least one criterion', () => {
54
+ assert.ok(phaseCriteria.criteria.length > 0, 'Should have at least one criterion');
55
+ });
56
+ it('should have criteria weights that approximately sum to 1', () => {
57
+ const totalWeight = phaseCriteria.criteria.reduce((sum, c) => sum + c.weight, 0);
58
+ assert.ok(Math.abs(totalWeight - 1.0) < 0.01, `Weights should sum to ~1.0, got ${totalWeight}`);
59
+ });
60
+ it('should have unique criterion IDs', () => {
61
+ const ids = phaseCriteria.criteria.map((c) => c.id);
62
+ const uniqueIds = new Set(ids);
63
+ assert.strictEqual(ids.length, uniqueIds.size, 'Criterion IDs should be unique');
64
+ });
65
+ it('should have valid criterion weights (0 < weight <= 1)', () => {
66
+ for (const criterion of phaseCriteria.criteria) {
67
+ assert.ok(criterion.weight > 0 && criterion.weight <= 1, `Weight for ${criterion.id} should be between 0 and 1, got ${criterion.weight}`);
68
+ }
69
+ });
70
+ it('should have valid minimum scores (0-100)', () => {
71
+ for (const criterion of phaseCriteria.criteria) {
72
+ assert.ok(criterion.minimumScore >= 0 && criterion.minimumScore <= 100, `minimumScore for ${criterion.id} should be 0-100, got ${criterion.minimumScore}`);
73
+ }
74
+ });
75
+ it('should have non-empty evaluation guidance', () => {
76
+ for (const criterion of phaseCriteria.criteria) {
77
+ assert.ok(criterion.evaluationGuidance.length > 0, `Criterion ${criterion.id} should have evaluation guidance`);
78
+ }
79
+ });
80
+ });
81
+ }
82
+ });
83
+ describe('getPhaseQualityCriteria', () => {
84
+ it('should return default criteria for known phases', () => {
85
+ const criteria = getPhaseQualityCriteria('user_stories_analysis');
86
+ assert.ok(criteria);
87
+ assert.strictEqual(criteria.phase, 'user_stories_analysis');
88
+ assert.strictEqual(criteria.advanceThreshold, USER_STORIES_ANALYSIS_CRITERIA.advanceThreshold);
89
+ });
90
+ it('should return null for unknown phases', () => {
91
+ const criteria = getPhaseQualityCriteria('nonexistent_phase');
92
+ assert.strictEqual(criteria, null);
93
+ });
94
+ it('should apply overrides when provided', () => {
95
+ const criteria = getPhaseQualityCriteria('user_stories_analysis', {
96
+ user_stories_analysis: {
97
+ advanceThreshold: 90,
98
+ maxAutoRetries: 5,
99
+ },
100
+ });
101
+ assert.ok(criteria);
102
+ assert.strictEqual(criteria.advanceThreshold, 90);
103
+ assert.strictEqual(criteria.maxAutoRetries, 5);
104
+ // Non-overridden values should remain from defaults
105
+ assert.strictEqual(criteria.escalateThreshold, USER_STORIES_ANALYSIS_CRITERIA.escalateThreshold);
106
+ });
107
+ it('should return defaults when override map does not include the phase', () => {
108
+ const criteria = getPhaseQualityCriteria('technical_design', {
109
+ user_stories_analysis: { advanceThreshold: 90 },
110
+ });
111
+ assert.ok(criteria);
112
+ assert.strictEqual(criteria.advanceThreshold, TECHNICAL_DESIGN_CRITERIA.advanceThreshold);
113
+ });
114
+ it('should override criteria array when provided', () => {
115
+ const customCriteria = [
116
+ {
117
+ id: 'custom_1',
118
+ name: 'Custom',
119
+ description: 'A custom criterion',
120
+ weight: 1.0,
121
+ minimumScore: 50,
122
+ evaluationGuidance: 'Custom guidance',
123
+ },
124
+ ];
125
+ const criteria = getPhaseQualityCriteria('technical_design', {
126
+ technical_design: { criteria: customCriteria },
127
+ });
128
+ assert.ok(criteria);
129
+ assert.strictEqual(criteria.criteria.length, 1);
130
+ assert.strictEqual(criteria.criteria[0].id, 'custom_1');
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for lifecycle agent transition rules and decision logic
3
+ */
4
+ export {};