edsger 0.30.4 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/.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/auth/auth-store.d.ts +13 -0
  5. package/dist/auth/auth-store.js +38 -1
  6. package/dist/index.js +19 -6
  7. package/dist/phases/code-refine/context.js +5 -4
  8. package/dist/phases/code-review/context.js +5 -4
  9. package/dist/phases/functional-testing/analyzer.js +3 -2
  10. package/dist/phases/functional-testing/http-fallback.js +5 -4
  11. package/dist/phases/functional-testing/test-report-creator.js +7 -6
  12. package/dist/phases/growth-analysis/agent.d.ts +1 -1
  13. package/dist/phases/growth-analysis/agent.js +2 -1
  14. package/dist/phases/growth-analysis/context.d.ts +1 -1
  15. package/dist/phases/growth-analysis/context.js +11 -3
  16. package/dist/phases/growth-analysis/index.js +26 -3
  17. package/dist/phases/growth-analysis/prompts.d.ts +2 -2
  18. package/dist/phases/growth-analysis/prompts.js +34 -17
  19. package/dist/services/feedbacks.js +5 -4
  20. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  21. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  22. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  23. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  24. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  25. package/dist/services/lifecycle-agent/index.js +25 -0
  26. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  27. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  28. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  29. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  30. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  31. package/dist/services/lifecycle-agent/types.js +12 -0
  32. package/dist/utils/validation.js +5 -4
  33. package/dist/utils/workflow-utils.js +3 -2
  34. package/package.json +1 -1
  35. package/.env.local +0 -12
  36. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  37. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  38. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  39. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  40. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  41. package/dist/commands/workflow/pipeline-runner.js +0 -393
  42. package/dist/commands/workflow/runner.d.ts +0 -26
  43. package/dist/commands/workflow/runner.js +0 -119
  44. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  45. package/dist/commands/workflow/workflow-runner.js +0 -119
  46. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  47. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  48. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  49. package/dist/phases/code-implementation/analyzer.js +0 -629
  50. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  51. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  52. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  53. package/dist/phases/code-implementation/mcp-server.js +0 -93
  54. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  55. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  56. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  57. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  58. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  59. package/dist/phases/code-refine/analyzer.js +0 -561
  60. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  61. package/dist/phases/code-refine/context-fetcher.js +0 -423
  62. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  63. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  64. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  65. package/dist/phases/code-refine-verification/verifier.js +0 -597
  66. package/dist/phases/code-review/analyzer.d.ts +0 -29
  67. package/dist/phases/code-review/analyzer.js +0 -363
  68. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  69. package/dist/phases/code-review/context-fetcher.js +0 -296
  70. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  71. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  72. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  73. package/dist/phases/feature-analysis/analyzer.js +0 -208
  74. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  75. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  76. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  77. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  78. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  79. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  80. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  81. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  82. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  83. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  84. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  85. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  86. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  87. package/dist/phases/technical-design/analyzer.js +0 -461
  88. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  89. package/dist/phases/technical-design/context-fetcher.js +0 -39
  90. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  91. package/dist/phases/technical-design/http-fallback.js +0 -151
  92. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  93. package/dist/phases/technical-design/mcp-server.js +0 -157
  94. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  95. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  96. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  97. package/dist/phases/technical-design-verification/verifier.js +0 -170
  98. package/dist/services/feature-branches.d.ts +0 -77
  99. package/dist/services/feature-branches.js +0 -205
  100. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  101. package/dist/workflow-runner/config/phase-configs.js +0 -120
  102. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  103. package/dist/workflow-runner/core/feature-filter.js +0 -46
  104. package/dist/workflow-runner/core/index.d.ts +0 -8
  105. package/dist/workflow-runner/core/index.js +0 -12
  106. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  107. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  108. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  109. package/dist/workflow-runner/core/state-manager.js +0 -42
  110. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  111. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  112. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  113. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  114. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  115. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  116. package/dist/workflow-runner/index.d.ts +0 -2
  117. package/dist/workflow-runner/index.js +0 -2
  118. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  119. package/dist/workflow-runner/pipeline-runner.js +0 -393
  120. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  121. 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
@@ -50,3 +50,16 @@ export declare function getEdsgerBaseUrl(): string;
50
50
  * Get the auth file path (for display purposes)
51
51
  */
52
52
  export declare function getAuthFilePath(): string;
53
+ /**
54
+ * Watch the auth file for external changes (e.g., from Edsger Desktop app).
55
+ * When the file changes, the in-memory cache is invalidated so the CLI
56
+ * picks up new auth credentials automatically.
57
+ *
58
+ * @param onChange - Optional callback invoked when auth config changes
59
+ * @returns A function to stop watching
60
+ */
61
+ export declare function watchAuthFile(onChange?: (config: AuthConfig | null) => void): () => void;
62
+ /**
63
+ * Stop watching the auth file
64
+ */
65
+ export declare function stopWatchingAuthFile(): void;
@@ -4,7 +4,7 @@
4
4
  * Stores auth credentials in ~/.edsger/auth.json
5
5
  * Provides read/write/clear operations for auth tokens
6
6
  */
7
- import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync } from 'fs';
7
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync, watch } from 'fs';
8
8
  import { join } from 'path';
9
9
  import { homedir } from 'os';
10
10
  const EDSGER_DIR = join(homedir(), '.edsger');
@@ -115,3 +115,40 @@ export function getEdsgerBaseUrl() {
115
115
  export function getAuthFilePath() {
116
116
  return AUTH_FILE;
117
117
  }
118
+ /** Active file watcher for auth file changes */
119
+ let _authWatcher = null;
120
+ /**
121
+ * Watch the auth file for external changes (e.g., from Edsger Desktop app).
122
+ * When the file changes, the in-memory cache is invalidated so the CLI
123
+ * picks up new auth credentials automatically.
124
+ *
125
+ * @param onChange - Optional callback invoked when auth config changes
126
+ * @returns A function to stop watching
127
+ */
128
+ export function watchAuthFile(onChange) {
129
+ // Stop any existing watcher
130
+ stopWatchingAuthFile();
131
+ ensureEdsgerDir();
132
+ _authWatcher = watch(EDSGER_DIR, (eventType, filename) => {
133
+ if (filename !== 'auth.json')
134
+ return;
135
+ // Invalidate cache so next loadAuth() reads from disk
136
+ _authCache = undefined;
137
+ const config = loadAuth();
138
+ if (onChange) {
139
+ onChange(config);
140
+ }
141
+ });
142
+ // Don't keep the process alive just for the watcher
143
+ _authWatcher.unref();
144
+ return () => stopWatchingAuthFile();
145
+ }
146
+ /**
147
+ * Stop watching the auth file
148
+ */
149
+ export function stopWatchingAuthFile() {
150
+ if (_authWatcher) {
151
+ _authWatcher.close();
152
+ _authWatcher = null;
153
+ }
154
+ }
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);
@@ -5,6 +5,7 @@
5
5
  import { Octokit } from '@octokit/rest';
6
6
  import { getFeature } from '../../api/features/get-feature.js';
7
7
  import { downloadImagesForClaudeCode } from '../../utils/image-downloader.js';
8
+ import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
8
9
  /**
9
10
  * Extract owner, repo, and PR number from GitHub PR URL
10
11
  */
@@ -183,8 +184,8 @@ export async function fetchUserStories(featureId, verbose) {
183
184
  if (verbose) {
184
185
  console.log(`📖 Fetching user stories for ${featureId}...`);
185
186
  }
186
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
187
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
187
+ const mcpServerUrl = getMcpServerUrl();
188
+ const mcpToken = getMcpToken();
188
189
  const response = await fetch(`${mcpServerUrl}/mcp`, {
189
190
  method: 'POST',
190
191
  headers: {
@@ -230,8 +231,8 @@ export async function fetchTestCases(featureId, verbose) {
230
231
  if (verbose) {
231
232
  console.log(`🧪 Fetching test cases for ${featureId}...`);
232
233
  }
233
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
234
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
234
+ const mcpServerUrl = getMcpServerUrl();
235
+ const mcpToken = getMcpToken();
235
236
  const response = await fetch(`${mcpServerUrl}/mcp`, {
236
237
  method: 'POST',
237
238
  headers: {
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { Octokit } from '@octokit/rest';
6
6
  import { getFeature } from '../../api/features/get-feature.js';
7
+ import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
7
8
  /**
8
9
  * Extract owner, repo, and PR number from GitHub PR URL
9
10
  */
@@ -79,8 +80,8 @@ export async function fetchUserStories(featureId, verbose) {
79
80
  if (verbose) {
80
81
  console.log(`📖 Fetching user stories for ${featureId}...`);
81
82
  }
82
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
83
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
83
+ const mcpServerUrl = getMcpServerUrl();
84
+ const mcpToken = getMcpToken();
84
85
  const response = await fetch(`${mcpServerUrl}/mcp`, {
85
86
  method: 'POST',
86
87
  headers: {
@@ -126,8 +127,8 @@ export async function fetchTestCases(featureId, verbose) {
126
127
  if (verbose) {
127
128
  console.log(`🧪 Fetching test cases for ${featureId}...`);
128
129
  }
129
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
130
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
130
+ const mcpServerUrl = getMcpServerUrl();
131
+ const mcpToken = getMcpToken();
131
132
  const response = await fetch(`${mcpServerUrl}/mcp`, {
132
133
  method: 'POST',
133
134
  headers: {
@@ -8,6 +8,7 @@ import { fetchFunctionalTestingContext, formatContextForPrompt, } from './contex
8
8
  import { updateFeatureStatus } from '../../api/features/index.js';
9
9
  import { createTestReport, } from './test-report-creator.js';
10
10
  import { preparePhaseGitEnvironment, prepareCustomBranchGitEnvironment, } from '../../utils/git-branch-manager.js';
11
+ import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
11
12
  import { getReadyForReviewBranch, } from '../../services/branches.js';
12
13
  function userMessage(content) {
13
14
  return {
@@ -299,8 +300,8 @@ export const runFunctionalTesting = async (options, config, checklistContext) =>
299
300
  structuredTestResult.test_cases.length > 0) {
300
301
  // Fetch test cases for this feature to get proper test_case_ids
301
302
  try {
302
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
303
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
303
+ const mcpServerUrl = getMcpServerUrl();
304
+ const mcpToken = getMcpToken();
304
305
  const testCasesResponse = await fetch(`${mcpServerUrl}/mcp`, {
305
306
  method: 'POST',
306
307
  headers: {
@@ -1,12 +1,13 @@
1
1
  import { logInfo, logError } from '../../utils/logger.js';
2
+ import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
2
3
  export async function saveFunctionalTestResultsViaHttp(options) {
3
4
  const { featureId, testStatus, testResults: _testResults, verbose } = options;
4
5
  try {
5
6
  if (verbose) {
6
7
  logInfo('🔄 Attempting to save functional test results via HTTP fallback...');
7
8
  }
8
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
9
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
9
+ const mcpServerUrl = getMcpServerUrl();
10
+ const mcpToken = getMcpToken();
10
11
  const response = await fetch(`${mcpServerUrl}/mcp`, {
11
12
  method: 'POST',
12
13
  headers: {
@@ -47,8 +48,8 @@ export async function verifyTestStatusSaved(featureId, verbose, expectedStatus)
47
48
  if (verbose) {
48
49
  logInfo('🔍 Verifying test status was saved...');
49
50
  }
50
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
51
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
51
+ const mcpServerUrl = getMcpServerUrl();
52
+ const mcpToken = getMcpToken();
52
53
  const response = await fetch(`${mcpServerUrl}/mcp`, {
53
54
  method: 'POST',
54
55
  headers: {
@@ -1,4 +1,5 @@
1
1
  import { logInfo, logError } from '../../utils/logger.js';
2
+ import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
2
3
  /**
3
4
  * Create a test report via MCP endpoint
4
5
  * Uses structured data generated by Claude Code
@@ -23,8 +24,8 @@ export async function createTestReport(options) {
23
24
  timestamp: new Date().toISOString(),
24
25
  };
25
26
  try {
26
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
27
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
27
+ const mcpServerUrl = getMcpServerUrl();
28
+ const mcpToken = getMcpToken();
28
29
  // Try to create test report via MCP endpoint
29
30
  // Claude Code should call this endpoint directly with the structured data
30
31
  const response = await fetch(`${mcpServerUrl}/mcp`, {
@@ -103,8 +104,8 @@ export async function createTestReportResults(options) {
103
104
  logInfo(`Creating ${testResults.length} test report results for report: ${testReportId}`);
104
105
  }
105
106
  try {
106
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
107
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
107
+ const mcpServerUrl = getMcpServerUrl();
108
+ const mcpToken = getMcpToken();
108
109
  const response = await fetch(`${mcpServerUrl}/mcp`, {
109
110
  method: 'POST',
110
111
  headers: {
@@ -157,8 +158,8 @@ async function manualMcpTestReportCreation(featureId, structuredReport, testResu
157
158
  logInfo('Manually calling MCP test_reports/create endpoint with structured data');
158
159
  }
159
160
  try {
160
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
161
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
161
+ const mcpServerUrl = getMcpServerUrl();
162
+ const mcpToken = getMcpToken();
162
163
  // Manually construct and send the MCP request
163
164
  const response = await fetch(`${mcpServerUrl}/mcp`, {
164
165
  method: 'POST',
@@ -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
  };
@@ -2,6 +2,7 @@
2
2
  * Feedbacks service for pipeline integration
3
3
  * Provides human-guided feedbacks to influence Claude Code behavior in each phase
4
4
  */
5
+ import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
5
6
  import { downloadImagesForClaudeCode } from '../utils/image-downloader.js';
6
7
  /**
7
8
  * Fetch feedbacks for a specific phase
@@ -10,8 +11,8 @@ import { downloadImagesForClaudeCode } from '../utils/image-downloader.js';
10
11
  */
11
12
  export async function getFeedbacksForPhase(options, phase, branchId) {
12
13
  const { featureId, verbose } = options;
13
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
14
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
14
+ const mcpServerUrl = getMcpServerUrl();
15
+ const mcpToken = getMcpToken();
15
16
  // Convert phase name from hyphen to underscore format for database compatibility
16
17
  // e.g., 'feature-analysis' -> 'feature_analysis'
17
18
  const dbPhase = phase.replace(/-/g, '_');
@@ -207,8 +208,8 @@ function getPriorityBadge(priority) {
207
208
  * Used after successful verification to track which feedbacks have been addressed
208
209
  */
209
210
  export async function resolveFeedbacks(options, feedbackIds, verbose) {
210
- const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL;
211
- const mcpToken = process.env.EDSGER_MCP_TOKEN;
211
+ const mcpServerUrl = getMcpServerUrl();
212
+ const mcpToken = getMcpToken();
212
213
  if (!feedbackIds || feedbackIds.length === 0) {
213
214
  return;
214
215
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for phase quality criteria definitions
3
+ */
4
+ export {};