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.
- package/.claude/settings.local.json +3 -23
- package/dist/api/github.d.ts +11 -0
- package/dist/api/github.js +44 -0
- package/dist/auth/auth-store.d.ts +13 -0
- package/dist/auth/auth-store.js +38 -1
- package/dist/index.js +19 -6
- package/dist/phases/code-refine/context.js +5 -4
- package/dist/phases/code-review/context.js +5 -4
- package/dist/phases/functional-testing/analyzer.js +3 -2
- package/dist/phases/functional-testing/http-fallback.js +5 -4
- package/dist/phases/functional-testing/test-report-creator.js +7 -6
- package/dist/phases/growth-analysis/agent.d.ts +1 -1
- package/dist/phases/growth-analysis/agent.js +2 -1
- package/dist/phases/growth-analysis/context.d.ts +1 -1
- package/dist/phases/growth-analysis/context.js +11 -3
- package/dist/phases/growth-analysis/index.js +26 -3
- package/dist/phases/growth-analysis/prompts.d.ts +2 -2
- package/dist/phases/growth-analysis/prompts.js +34 -17
- package/dist/services/feedbacks.js +5 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
- package/dist/services/lifecycle-agent/index.d.ts +24 -0
- package/dist/services/lifecycle-agent/index.js +25 -0
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
- package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
- package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
- package/dist/services/lifecycle-agent/transition-rules.js +184 -0
- package/dist/services/lifecycle-agent/types.d.ts +190 -0
- package/dist/services/lifecycle-agent/types.js +12 -0
- package/dist/utils/validation.js +5 -4
- package/dist/utils/workflow-utils.js +3 -2
- package/package.json +1 -1
- package/.env.local +0 -12
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
- package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
- package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
- package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
- package/dist/commands/workflow/pipeline-runner.js +0 -393
- package/dist/commands/workflow/runner.d.ts +0 -26
- package/dist/commands/workflow/runner.js +0 -119
- package/dist/commands/workflow/workflow-runner.d.ts +0 -26
- package/dist/commands/workflow/workflow-runner.js +0 -119
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
- package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
- package/dist/phases/code-implementation/analyzer.d.ts +0 -32
- package/dist/phases/code-implementation/analyzer.js +0 -629
- package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
- package/dist/phases/code-implementation/context-fetcher.js +0 -86
- package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
- package/dist/phases/code-implementation/mcp-server.js +0 -93
- package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
- package/dist/phases/code-implementation/prompts-improvement.js +0 -108
- package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
- package/dist/phases/code-implementation-verification/verifier.js +0 -196
- package/dist/phases/code-refine/analyzer.d.ts +0 -41
- package/dist/phases/code-refine/analyzer.js +0 -561
- package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
- package/dist/phases/code-refine/context-fetcher.js +0 -423
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
- package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
- package/dist/phases/code-refine-verification/verifier.js +0 -597
- package/dist/phases/code-review/analyzer.d.ts +0 -29
- package/dist/phases/code-review/analyzer.js +0 -363
- package/dist/phases/code-review/context-fetcher.d.ts +0 -92
- package/dist/phases/code-review/context-fetcher.js +0 -296
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
- package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
- package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
- package/dist/phases/feature-analysis/analyzer.js +0 -208
- package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
- package/dist/phases/feature-analysis/context-fetcher.js +0 -134
- package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
- package/dist/phases/feature-analysis/http-fallback.js +0 -95
- package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
- package/dist/phases/feature-analysis/mcp-server.js +0 -144
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
- package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
- package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
- package/dist/phases/feature-analysis-verification/verifier.js +0 -147
- package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
- package/dist/phases/technical-design/analyzer-helpers.js +0 -39
- package/dist/phases/technical-design/analyzer.d.ts +0 -21
- package/dist/phases/technical-design/analyzer.js +0 -461
- package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
- package/dist/phases/technical-design/context-fetcher.js +0 -39
- package/dist/phases/technical-design/http-fallback.d.ts +0 -17
- package/dist/phases/technical-design/http-fallback.js +0 -151
- package/dist/phases/technical-design/mcp-server.d.ts +0 -1
- package/dist/phases/technical-design/mcp-server.js +0 -157
- package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
- package/dist/phases/technical-design/prompts-improvement.js +0 -93
- package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
- package/dist/phases/technical-design-verification/verifier.js +0 -170
- package/dist/services/feature-branches.d.ts +0 -77
- package/dist/services/feature-branches.js +0 -205
- package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
- package/dist/workflow-runner/config/phase-configs.js +0 -120
- package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
- package/dist/workflow-runner/core/feature-filter.js +0 -46
- package/dist/workflow-runner/core/index.d.ts +0 -8
- package/dist/workflow-runner/core/index.js +0 -12
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
- package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
- package/dist/workflow-runner/core/state-manager.d.ts +0 -24
- package/dist/workflow-runner/core/state-manager.js +0 -42
- package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
- package/dist/workflow-runner/core/workflow-logger.js +0 -65
- package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
- package/dist/workflow-runner/executors/phase-executor.js +0 -248
- package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
- package/dist/workflow-runner/feature-workflow-runner.js +0 -119
- package/dist/workflow-runner/index.d.ts +0 -2
- package/dist/workflow-runner/index.js +0 -2
- package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
- package/dist/workflow-runner/pipeline-runner.js +0 -393
- package/dist/workflow-runner/workflow-processor.d.ts +0 -54
- package/dist/workflow-runner/workflow-processor.js +0 -170
|
@@ -1,28 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"Bash(npm run
|
|
6
|
-
|
|
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
|
}
|
package/dist/api/github.d.ts
CHANGED
|
@@ -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
|
package/dist/api/github.js
CHANGED
|
@@ -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;
|
package/dist/auth/auth-store.js
CHANGED
|
@@ -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 =
|
|
187
|
-
const mcpToken =
|
|
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 =
|
|
234
|
-
const mcpToken =
|
|
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 =
|
|
83
|
-
const mcpToken =
|
|
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 =
|
|
130
|
-
const mcpToken =
|
|
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 =
|
|
303
|
-
const mcpToken =
|
|
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 =
|
|
9
|
-
const mcpToken =
|
|
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 =
|
|
51
|
-
const mcpToken =
|
|
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 =
|
|
27
|
-
const mcpToken =
|
|
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 =
|
|
107
|
-
const mcpToken =
|
|
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 =
|
|
161
|
-
const mcpToken =
|
|
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
|
-
|
|
13
|
-
|
|
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. **
|
|
8
|
-
2. **
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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 =
|
|
14
|
-
const mcpToken =
|
|
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 =
|
|
211
|
-
const mcpToken =
|
|
211
|
+
const mcpServerUrl = getMcpServerUrl();
|
|
212
|
+
const mcpToken = getMcpToken();
|
|
212
213
|
if (!feedbackIds || feedbackIds.length === 0) {
|
|
213
214
|
return;
|
|
214
215
|
}
|