edsger 0.53.0 → 0.54.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/README.md +20 -0
- package/dist/api/financing.d.ts +47 -0
- package/dist/api/financing.js +37 -0
- package/dist/api/issues/approval-checker.d.ts +11 -9
- package/dist/api/issues/approval-checker.js +30 -41
- package/dist/api/issues/status-updater.d.ts +47 -20
- package/dist/api/issues/status-updater.js +114 -46
- package/dist/api/issues/update-issue.d.ts +5 -0
- package/dist/api/issues/update-issue.js +6 -0
- package/dist/commands/agent-workflow/processor.js +5 -1
- package/dist/commands/checklists/index.d.ts +5 -2
- package/dist/commands/checklists/index.js +73 -12
- package/dist/commands/checklists/tools.d.ts +14 -7
- package/dist/commands/checklists/tools.js +15 -208
- package/dist/commands/financing-deck/index.d.ts +8 -0
- package/dist/commands/financing-deck/index.js +66 -0
- package/dist/commands/find-architecture/index.d.ts +13 -0
- package/dist/commands/find-architecture/index.js +41 -0
- package/dist/commands/sync-github-issues/index.d.ts +11 -0
- package/dist/commands/sync-github-issues/index.js +42 -0
- package/dist/commands/sync-sentry-issues/index.d.ts +14 -0
- package/dist/commands/sync-sentry-issues/index.js +73 -0
- package/dist/commands/workflow/executors/phase-executor.js +6 -4
- package/dist/commands/workflow/phase-orchestrator.js +0 -1
- package/dist/config/issue-status.d.ts +18 -45
- package/dist/config/issue-status.js +21 -107
- package/dist/index.js +97 -3
- package/dist/phases/app-store-generation/agent.js +2 -1
- package/dist/phases/app-store-generation/index.js +11 -3
- package/dist/phases/autonomous/index.js +9 -6
- package/dist/phases/branch-planning/index.js +1 -2
- package/dist/phases/branch-planning/prompts.d.ts +1 -1
- package/dist/phases/branch-planning/prompts.js +3 -2
- package/dist/phases/bug-fixing/analyzer.js +6 -3
- package/dist/phases/bug-fixing/mcp-server.d.ts +18 -1
- package/dist/phases/bug-fixing/mcp-server.js +19 -76
- package/dist/phases/chat-processor/product-tools.d.ts +5 -8
- package/dist/phases/chat-processor/product-tools.js +6 -512
- package/dist/phases/chat-processor/tools.d.ts +5 -9
- package/dist/phases/chat-processor/tools.js +6 -704
- package/dist/phases/code-implementation/branch-pr-creator.js +7 -5
- package/dist/phases/code-implementation/index.js +6 -3
- package/dist/phases/code-implementation-verification/agent.js +6 -1
- package/dist/phases/code-refine/index.js +8 -6
- package/dist/phases/code-refine/refine-iteration.js +2 -1
- package/dist/phases/code-review/index.js +8 -6
- package/dist/phases/code-testing/analyzer.js +11 -8
- package/dist/phases/financing-deck/agent.d.ts +1 -0
- package/dist/phases/financing-deck/agent.js +96 -0
- package/dist/phases/financing-deck/context.d.ts +13 -0
- package/dist/phases/financing-deck/context.js +69 -0
- package/dist/phases/financing-deck/index.d.ts +15 -0
- package/dist/phases/financing-deck/index.js +89 -0
- package/dist/phases/financing-deck/prompts.d.ts +2 -0
- package/dist/phases/financing-deck/prompts.js +94 -0
- package/dist/phases/find-architecture/index.d.ts +44 -0
- package/dist/phases/find-architecture/index.js +248 -0
- package/dist/phases/find-architecture/prompts.d.ts +31 -0
- package/dist/phases/find-architecture/prompts.js +128 -0
- package/dist/phases/find-architecture/state.d.ts +21 -0
- package/dist/phases/find-architecture/state.js +17 -0
- package/dist/phases/find-architecture/types.d.ts +55 -0
- package/dist/phases/find-architecture/types.js +69 -0
- package/dist/phases/find-bugs/index.js +13 -4
- package/dist/phases/find-features/index.js +10 -5
- package/dist/phases/find-smells/index.js +10 -3
- package/dist/phases/functional-testing/analyzer.js +27 -17
- package/dist/phases/functional-testing/http-fallback.d.ts +1 -1
- package/dist/phases/functional-testing/http-fallback.js +32 -16
- package/dist/phases/functional-testing/mcp-server.d.ts +9 -1
- package/dist/phases/functional-testing/mcp-server.js +13 -132
- package/dist/phases/growth-analysis/agent.js +2 -2
- package/dist/phases/growth-analysis/index.js +9 -3
- package/dist/phases/intelligence-analysis/agent.js +2 -2
- package/dist/phases/intelligence-analysis/index.js +9 -2
- package/dist/phases/issue-analysis/agent.d.ts +9 -1
- package/dist/phases/issue-analysis/agent.js +68 -27
- package/dist/phases/issue-analysis/context.d.ts +5 -9
- package/dist/phases/issue-analysis/context.js +31 -76
- package/dist/phases/issue-analysis/index.js +32 -84
- package/dist/phases/issue-analysis/outcome.d.ts +3 -33
- package/dist/phases/issue-analysis/outcome.js +15 -253
- package/dist/phases/issue-analysis/prompts.d.ts +3 -5
- package/dist/phases/issue-analysis/prompts.js +45 -158
- package/dist/phases/issue-analysis-verification/agent.d.ts +4 -4
- package/dist/phases/issue-analysis-verification/agent.js +5 -5
- package/dist/phases/issue-analysis-verification/index.d.ts +4 -2
- package/dist/phases/issue-analysis-verification/index.js +9 -22
- package/dist/phases/issue-analysis-verification/prompts.d.ts +1 -2
- package/dist/phases/issue-analysis-verification/prompts.js +21 -46
- package/dist/phases/output-contracts.js +66 -78
- package/dist/phases/pr-execution/context.d.ts +2 -0
- package/dist/phases/pr-execution/context.js +1 -0
- package/dist/phases/pr-execution/index.js +28 -19
- package/dist/phases/pr-execution/prompts.d.ts +2 -1
- package/dist/phases/pr-execution/prompts.js +12 -10
- package/dist/phases/pr-resolve/index.js +2 -8
- package/dist/phases/pr-splitting/index.js +3 -3
- package/dist/phases/pr-splitting/prompts.d.ts +1 -1
- package/dist/phases/pr-splitting/prompts.js +3 -2
- package/dist/phases/pull-request/creator.js +10 -7
- package/dist/phases/pull-request/handler.js +3 -1
- package/dist/phases/release-sync/index.js +52 -43
- package/dist/phases/run-sheet/index.js +2 -1
- package/dist/phases/smoke-test/agent.js +2 -1
- package/dist/phases/smoke-test/index.js +4 -1
- package/dist/phases/sync-github-issues/index.d.ts +41 -0
- package/dist/phases/sync-github-issues/index.js +187 -0
- package/dist/phases/sync-github-issues/state.d.ts +26 -0
- package/dist/phases/sync-github-issues/state.js +18 -0
- package/dist/phases/sync-github-issues/types.d.ts +35 -0
- package/dist/phases/sync-github-issues/types.js +6 -0
- package/dist/phases/sync-sentry-issues/index.d.ts +29 -0
- package/dist/phases/sync-sentry-issues/index.js +153 -0
- package/dist/phases/sync-sentry-issues/sentry-client.d.ts +66 -0
- package/dist/phases/sync-sentry-issues/sentry-client.js +221 -0
- package/dist/phases/sync-sentry-issues/state.d.ts +23 -0
- package/dist/phases/sync-sentry-issues/state.js +18 -0
- package/dist/phases/sync-sentry-issues/types.d.ts +46 -0
- package/dist/phases/sync-sentry-issues/types.js +6 -0
- package/dist/phases/sync-shared/mcp.d.ts +81 -0
- package/dist/phases/sync-shared/mcp.js +111 -0
- package/dist/phases/technical-design/index.js +0 -1
- package/dist/phases/test-cases-analysis/agent.js +2 -1
- package/dist/phases/test-cases-analysis/index.js +0 -1
- package/dist/phases/user-stories-analysis/agent.js +2 -1
- package/dist/phases/user-stories-analysis/index.js +0 -1
- package/dist/services/coaching/coaching-agent.js +29 -4
- package/dist/services/feedbacks.d.ts +1 -1
- package/dist/services/repo-config.d.ts +17 -0
- package/dist/services/repo-config.js +50 -0
- package/dist/skills/phase/issue-analysis/SKILL.md +48 -92
- package/dist/skills/phase/issue-analysis-verification/SKILL.md +46 -31
- package/dist/tools/bootstrap.d.ts +45 -0
- package/dist/tools/bootstrap.js +50 -0
- package/dist/types/external-sources.d.ts +22 -0
- package/dist/types/external-sources.js +23 -0
- package/dist/types/index.d.ts +5 -10
- package/dist/types/issues.d.ts +2 -0
- package/dist/types/llm-responses.d.ts +1 -14
- package/dist/utils/formatters.js +1 -7
- package/dist/utils/git-branch-manager-async.js +9 -5
- package/dist/utils/git-branch-manager.js +17 -7
- package/dist/workspace/workspace-manager.d.ts +10 -0
- package/dist/workspace/workspace-manager.js +22 -1
- package/package.json +6 -2
- package/vitest.config.ts +4 -0
- package/.env.local +0 -12
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the find-architecture phase.
|
|
3
|
+
*
|
|
4
|
+
* Where find-smells covers local "the code would be better if changed" findings
|
|
5
|
+
* (refactor candidates, dead code, perf cliffs, etc.), this phase zooms out to
|
|
6
|
+
* **architectural** problems and improvements — module boundaries, layering,
|
|
7
|
+
* coupling, dependency direction, responsibility creep, missing or duplicated
|
|
8
|
+
* abstractions, cross-cutting concerns. Findings span multiple files by nature.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Authoritative list of allowed concern types. Kept as a `const` array so the
|
|
12
|
+
* type and the runtime whitelist can never drift apart.
|
|
13
|
+
*/
|
|
14
|
+
export const ARCHITECTURE_CONCERNS = [
|
|
15
|
+
'layering',
|
|
16
|
+
'coupling',
|
|
17
|
+
'cohesion',
|
|
18
|
+
'cyclic_dependency',
|
|
19
|
+
'boundary',
|
|
20
|
+
'duplication',
|
|
21
|
+
'missing_abstraction',
|
|
22
|
+
'responsibility_creep',
|
|
23
|
+
'cross_cutting',
|
|
24
|
+
'inconsistency',
|
|
25
|
+
'scalability',
|
|
26
|
+
'other',
|
|
27
|
+
];
|
|
28
|
+
export function isArchitectureConcern(value) {
|
|
29
|
+
return (typeof value === 'string' &&
|
|
30
|
+
ARCHITECTURE_CONCERNS.includes(value));
|
|
31
|
+
}
|
|
32
|
+
export function isDeferredFinding(value) {
|
|
33
|
+
if (!value || typeof value !== 'object') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const v = value;
|
|
37
|
+
return typeof v.title === 'string' && typeof v.reason === 'string';
|
|
38
|
+
}
|
|
39
|
+
export function isScanResult(value) {
|
|
40
|
+
if (!value || typeof value !== 'object') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const v = value;
|
|
44
|
+
if (typeof v.summary !== 'string' || !Array.isArray(v.findings)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const findingsOk = v.findings.every((f) => f &&
|
|
48
|
+
typeof f === 'object' &&
|
|
49
|
+
typeof f.title === 'string' &&
|
|
50
|
+
typeof f.description === 'string' &&
|
|
51
|
+
typeof f.file === 'string');
|
|
52
|
+
if (!findingsOk) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
for (const key of [
|
|
56
|
+
'deferred_to_bugs',
|
|
57
|
+
'deferred_to_features',
|
|
58
|
+
'deferred_to_smells',
|
|
59
|
+
]) {
|
|
60
|
+
const arr = v[key];
|
|
61
|
+
if (arr === undefined) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(arr) || !arr.every(isDeferredFinding)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
7
7
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
8
8
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
9
|
-
import { cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
9
|
+
import { cleanupIssueRepo, cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
10
10
|
import { detectDefaultBranch, gitRevParse, isAncestor, listChangedPaths, } from '../find-shared/git.js';
|
|
11
11
|
import { createIssue, fetchOpenIssues, fetchProductBasics, } from '../find-shared/mcp.js';
|
|
12
12
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
@@ -42,15 +42,18 @@ export async function scanForBugs(options) {
|
|
|
42
42
|
message: 'Another bug scan is already in progress for this product',
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
let repoPath;
|
|
46
|
+
let scanSucceeded = false;
|
|
45
47
|
try {
|
|
46
48
|
updateFindBugsState(productId, {
|
|
47
49
|
lastAttemptedAt: new Date().toISOString(),
|
|
48
50
|
});
|
|
49
51
|
const workspaceRoot = ensureWorkspaceDir();
|
|
50
|
-
//
|
|
52
|
+
// Each run re-clones into a per-product directory and removes it on
|
|
53
|
+
// success. Incremental scope is recovered from the persisted state file
|
|
54
|
+
// (~/.edsger/find-bugs-state/<productId>.json), not from the workspace.
|
|
51
55
|
const repoKey = `${WORKSPACE_KEY}-${productId}`;
|
|
52
|
-
|
|
53
|
-
const { repoPath } = cloned;
|
|
56
|
+
({ repoPath } = cloneIssueRepo(workspaceRoot, repoKey, owner, repo, githubToken));
|
|
54
57
|
const branch = options.branch ?? detectDefaultBranch(repoPath);
|
|
55
58
|
logInfo(`Syncing ${owner}/${repo} to branch ${branch}`);
|
|
56
59
|
syncRepoToRef(repoPath, { branch }, githubToken);
|
|
@@ -72,6 +75,7 @@ export async function scanForBugs(options) {
|
|
|
72
75
|
lastScannedAt: new Date().toISOString(),
|
|
73
76
|
lastError: undefined,
|
|
74
77
|
});
|
|
78
|
+
scanSucceeded = true;
|
|
75
79
|
return {
|
|
76
80
|
status: 'success',
|
|
77
81
|
message: 'No changes since last scan',
|
|
@@ -87,6 +91,7 @@ export async function scanForBugs(options) {
|
|
|
87
91
|
}
|
|
88
92
|
else if (baseSha === headSha) {
|
|
89
93
|
logSuccess('HEAD unchanged since last scan; nothing to do.');
|
|
94
|
+
scanSucceeded = true;
|
|
90
95
|
return {
|
|
91
96
|
status: 'success',
|
|
92
97
|
message: 'HEAD unchanged since last scan',
|
|
@@ -171,6 +176,7 @@ export async function scanForBugs(options) {
|
|
|
171
176
|
lastScannedAt: new Date().toISOString(),
|
|
172
177
|
lastError: undefined,
|
|
173
178
|
});
|
|
179
|
+
scanSucceeded = true;
|
|
174
180
|
return {
|
|
175
181
|
status: 'success',
|
|
176
182
|
message: `Filed ${created} of ${bugs.length} candidate bugs`,
|
|
@@ -190,6 +196,9 @@ export async function scanForBugs(options) {
|
|
|
190
196
|
};
|
|
191
197
|
}
|
|
192
198
|
finally {
|
|
199
|
+
if (scanSucceeded) {
|
|
200
|
+
cleanupIssueRepo(repoPath);
|
|
201
|
+
}
|
|
193
202
|
lock.release();
|
|
194
203
|
}
|
|
195
204
|
}
|
|
@@ -16,7 +16,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
|
16
16
|
import { callMcpEndpoint } from '../../api/mcp-client.js';
|
|
17
17
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
18
18
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
19
|
-
import { cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
19
|
+
import { cleanupIssueRepo, cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
20
20
|
import { detectDefaultBranch } from '../find-shared/git.js';
|
|
21
21
|
import { createIssue, fetchOpenIssues, fetchProductBasics, } from '../find-shared/mcp.js';
|
|
22
22
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
@@ -48,17 +48,18 @@ export async function scanForFeatures(options) {
|
|
|
48
48
|
message: 'Another feature discovery run is already in progress for this product',
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
let repoPath;
|
|
52
|
+
let scanSucceeded = false;
|
|
51
53
|
try {
|
|
52
54
|
updateFindFeaturesState(productId, {
|
|
53
55
|
lastAttemptedAt: new Date().toISOString(),
|
|
54
56
|
});
|
|
55
57
|
// Clone / refresh the repo so the model can verify existing functionality
|
|
56
|
-
// before proposing new features.
|
|
57
|
-
//
|
|
58
|
+
// before proposing new features. Cleaned up after a successful run; the
|
|
59
|
+
// worker re-clones on the next invocation.
|
|
58
60
|
const workspaceRoot = ensureWorkspaceDir();
|
|
59
61
|
const repoKey = `${WORKSPACE_KEY}-${productId}`;
|
|
60
|
-
|
|
61
|
-
const { repoPath } = cloned;
|
|
62
|
+
({ repoPath } = cloneIssueRepo(workspaceRoot, repoKey, owner, repo, githubToken));
|
|
62
63
|
const branch = options.branch ?? detectDefaultBranch(repoPath);
|
|
63
64
|
logInfo(`Syncing ${owner}/${repo} to branch ${branch}`);
|
|
64
65
|
syncRepoToRef(repoPath, { branch }, githubToken);
|
|
@@ -149,6 +150,7 @@ export async function scanForFeatures(options) {
|
|
|
149
150
|
lastRunAt: new Date().toISOString(),
|
|
150
151
|
lastError: undefined,
|
|
151
152
|
});
|
|
153
|
+
scanSucceeded = true;
|
|
152
154
|
return {
|
|
153
155
|
status: 'success',
|
|
154
156
|
message: `Filed ${created} of ${opportunities.length} opportunities`,
|
|
@@ -167,6 +169,9 @@ export async function scanForFeatures(options) {
|
|
|
167
169
|
};
|
|
168
170
|
}
|
|
169
171
|
finally {
|
|
172
|
+
if (scanSucceeded) {
|
|
173
|
+
cleanupIssueRepo(repoPath);
|
|
174
|
+
}
|
|
170
175
|
lock.release();
|
|
171
176
|
}
|
|
172
177
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
11
11
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
12
12
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
13
|
-
import { cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
13
|
+
import { cleanupIssueRepo, cloneIssueRepo, ensureWorkspaceDir, syncRepoToRef, } from '../../workspace/workspace-manager.js';
|
|
14
14
|
import { detectDefaultBranch, gitRevParse, isAncestor, listChangedPaths, } from '../find-shared/git.js';
|
|
15
15
|
import { createIssue, fetchOpenIssues, fetchProductBasics, } from '../find-shared/mcp.js';
|
|
16
16
|
import { createPromptGenerator, extractTextFromContent, tryExtractResult, } from '../pr-shared/agent-utils.js';
|
|
@@ -41,14 +41,15 @@ export async function scanForSmells(options) {
|
|
|
41
41
|
message: 'Another smell scan is already in progress for this product',
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
let repoPath;
|
|
45
|
+
let scanSucceeded = false;
|
|
44
46
|
try {
|
|
45
47
|
updateFindSmellsState(productId, {
|
|
46
48
|
lastAttemptedAt: new Date().toISOString(),
|
|
47
49
|
});
|
|
48
50
|
const workspaceRoot = ensureWorkspaceDir();
|
|
49
51
|
const repoKey = `${WORKSPACE_KEY}-${productId}`;
|
|
50
|
-
|
|
51
|
-
const { repoPath } = cloned;
|
|
52
|
+
({ repoPath } = cloneIssueRepo(workspaceRoot, repoKey, owner, repo, githubToken));
|
|
52
53
|
const branch = options.branch ?? detectDefaultBranch(repoPath);
|
|
53
54
|
logInfo(`Syncing ${owner}/${repo} to branch ${branch}`);
|
|
54
55
|
syncRepoToRef(repoPath, { branch }, githubToken);
|
|
@@ -70,6 +71,7 @@ export async function scanForSmells(options) {
|
|
|
70
71
|
lastScannedAt: new Date().toISOString(),
|
|
71
72
|
lastError: undefined,
|
|
72
73
|
});
|
|
74
|
+
scanSucceeded = true;
|
|
73
75
|
return {
|
|
74
76
|
status: 'success',
|
|
75
77
|
message: 'No changes since last scan',
|
|
@@ -92,6 +94,7 @@ export async function scanForSmells(options) {
|
|
|
92
94
|
lastScannedAt: new Date().toISOString(),
|
|
93
95
|
lastError: undefined,
|
|
94
96
|
});
|
|
97
|
+
scanSucceeded = true;
|
|
95
98
|
return {
|
|
96
99
|
status: 'success',
|
|
97
100
|
message: 'HEAD unchanged since last scan',
|
|
@@ -201,6 +204,7 @@ export async function scanForSmells(options) {
|
|
|
201
204
|
lastError: undefined,
|
|
202
205
|
});
|
|
203
206
|
const droppedCount = droppedCategories.reduce((acc, d) => acc + d.count, 0);
|
|
207
|
+
scanSucceeded = true;
|
|
204
208
|
return {
|
|
205
209
|
status: 'success',
|
|
206
210
|
message: `Filed ${created} of ${filteredSmells.length} candidate smells (${droppedCount} dropped by category filter; ${deferredBugs.length} deferred to bugs, ${deferredFeatures.length} to features)`,
|
|
@@ -222,6 +226,9 @@ export async function scanForSmells(options) {
|
|
|
222
226
|
};
|
|
223
227
|
}
|
|
224
228
|
finally {
|
|
229
|
+
if (scanSucceeded) {
|
|
230
|
+
cleanupIssueRepo(repoPath);
|
|
231
|
+
}
|
|
225
232
|
lock.release();
|
|
226
233
|
}
|
|
227
234
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable max-lines -- orchestration module with test execution, result processing, and report generation */
|
|
2
2
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
3
|
-
import {
|
|
3
|
+
import { setPhaseState } from '../../api/issues/index.js';
|
|
4
4
|
import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
|
|
5
5
|
import { DEFAULT_MODEL } from '../../constants.js';
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -279,11 +279,21 @@ async function saveTestResults(issueId, testStatus, structuredTestResult, lastAs
|
|
|
279
279
|
if (verbose) {
|
|
280
280
|
logInfo('Saving test results...');
|
|
281
281
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
282
|
+
// testStatus is 'testing_passed' | 'testing_failed'. Under the 2D model
|
|
283
|
+
// both map to a single phase-state update on functional_testing —
|
|
284
|
+
// 'completed' for passed, 'failed' for failed. issues.status stays
|
|
285
|
+
// 'in_progress'; humans decide when to ship.
|
|
286
|
+
const phaseState = testStatus === 'testing_passed' ? 'completed' : 'failed';
|
|
287
|
+
let statusSaved = true;
|
|
288
|
+
try {
|
|
289
|
+
await setPhaseState(issueId, 'functional_testing', phaseState, verbose);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
statusSaved = false;
|
|
293
|
+
if (verbose) {
|
|
294
|
+
logError(`Direct setPhaseState failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
287
297
|
if (!statusSaved) {
|
|
288
298
|
if (verbose) {
|
|
289
299
|
logInfo('Direct status update failed, trying HTTP fallback...');
|
|
@@ -326,7 +336,6 @@ async function saveTestResults(issueId, testStatus, structuredTestResult, lastAs
|
|
|
326
336
|
}
|
|
327
337
|
return testReportResult;
|
|
328
338
|
}
|
|
329
|
-
// eslint-disable-next-line complexity
|
|
330
339
|
export const runFunctionalTesting = async (options, config, checklistContext) => {
|
|
331
340
|
const { issueId, verbose } = options;
|
|
332
341
|
if (verbose) {
|
|
@@ -428,12 +437,13 @@ export const runFunctionalTesting = async (options, config, checklistContext) =>
|
|
|
428
437
|
catch (error) {
|
|
429
438
|
logError(`Functional testing failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
430
439
|
try {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
await setPhaseState(issueId, 'functional_testing', 'failed', verbose);
|
|
441
|
+
}
|
|
442
|
+
catch (rpcErr) {
|
|
443
|
+
if (verbose) {
|
|
444
|
+
logError(`Direct setPhaseState failed (${rpcErr instanceof Error ? rpcErr.message : String(rpcErr)}); trying HTTP fallback...`);
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
437
447
|
await saveFunctionalTestResultsWithRetry({
|
|
438
448
|
issueId,
|
|
439
449
|
testStatus: 'testing_failed',
|
|
@@ -441,10 +451,10 @@ export const runFunctionalTesting = async (options, config, checklistContext) =>
|
|
|
441
451
|
verbose,
|
|
442
452
|
});
|
|
443
453
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
454
|
+
catch (fallbackError) {
|
|
455
|
+
if (verbose) {
|
|
456
|
+
logError(`❌ Failed to save error status: ${fallbackError}`);
|
|
457
|
+
}
|
|
448
458
|
}
|
|
449
459
|
}
|
|
450
460
|
return {
|
|
@@ -5,6 +5,6 @@ interface SaveFunctionalTestResultsOptions {
|
|
|
5
5
|
verbose?: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function saveFunctionalTestResultsViaHttp(options: SaveFunctionalTestResultsOptions): Promise<boolean>;
|
|
8
|
-
export declare function verifyTestStatusSaved(issueId: string, verbose?: boolean, expectedStatus?:
|
|
8
|
+
export declare function verifyTestStatusSaved(issueId: string, verbose?: boolean, expectedStatus?: SaveFunctionalTestResultsOptions['testStatus']): Promise<boolean>;
|
|
9
9
|
export declare function saveFunctionalTestResultsWithRetry(options: SaveFunctionalTestResultsOptions, maxRetries?: number): Promise<boolean>;
|
|
10
10
|
export {};
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { getMcpServerUrl, getMcpToken } from '../../auth/auth-store.js';
|
|
2
2
|
import { logError, logInfo } from '../../utils/logger.js';
|
|
3
|
+
function legacyTestStatusToPhaseState(testStatus) {
|
|
4
|
+
switch (testStatus) {
|
|
5
|
+
case 'testing_in_progress':
|
|
6
|
+
return 'running';
|
|
7
|
+
case 'testing_passed':
|
|
8
|
+
return 'completed';
|
|
9
|
+
case 'testing_failed':
|
|
10
|
+
return 'failed';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
3
13
|
export async function saveFunctionalTestResultsViaHttp(options) {
|
|
4
|
-
const { issueId, testStatus,
|
|
14
|
+
const { issueId, testStatus, verbose } = options;
|
|
15
|
+
const phaseState = legacyTestStatusToPhaseState(testStatus);
|
|
5
16
|
try {
|
|
6
17
|
if (verbose) {
|
|
7
18
|
logInfo('🔄 Attempting to save functional test results via HTTP fallback...');
|
|
@@ -16,10 +27,11 @@ export async function saveFunctionalTestResultsViaHttp(options) {
|
|
|
16
27
|
},
|
|
17
28
|
body: JSON.stringify({
|
|
18
29
|
jsonrpc: '2.0',
|
|
19
|
-
method: 'issues/
|
|
30
|
+
method: 'issues/set_phase_state',
|
|
20
31
|
params: {
|
|
21
32
|
issue_id: issueId,
|
|
22
|
-
|
|
33
|
+
phase: 'functional_testing',
|
|
34
|
+
state: phaseState,
|
|
23
35
|
},
|
|
24
36
|
id: Math.random().toString(36).substring(7),
|
|
25
37
|
}),
|
|
@@ -32,7 +44,7 @@ export async function saveFunctionalTestResultsViaHttp(options) {
|
|
|
32
44
|
throw new Error(data.error.message || 'HTTP call failed');
|
|
33
45
|
}
|
|
34
46
|
if (verbose) {
|
|
35
|
-
logInfo(`✅ Functional test results saved successfully via HTTP fallback (
|
|
47
|
+
logInfo(`✅ Functional test results saved successfully via HTTP fallback (functional_testing → ${phaseState})`);
|
|
36
48
|
}
|
|
37
49
|
return true;
|
|
38
50
|
}
|
|
@@ -73,29 +85,33 @@ export async function verifyTestStatusSaved(issueId, verbose, expectedStatus) {
|
|
|
73
85
|
throw new Error(data.error.message || 'Verification failed');
|
|
74
86
|
}
|
|
75
87
|
const issue = data.result?.issues?.[0];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
// Look up functional_testing phase status in the workflow array
|
|
89
|
+
const workflow = issue?.workflow;
|
|
90
|
+
const ftPhase = workflow?.find((p) => p.phase === 'functional_testing');
|
|
91
|
+
const actualPhaseState = ftPhase?.status;
|
|
92
|
+
if (expectedStatus && actualPhaseState) {
|
|
93
|
+
const expected = legacyTestStatusToPhaseState(expectedStatus);
|
|
94
|
+
const matches = actualPhaseState === expected;
|
|
79
95
|
if (verbose) {
|
|
80
|
-
if (
|
|
81
|
-
logInfo(`✅ Test
|
|
96
|
+
if (matches) {
|
|
97
|
+
logInfo(`✅ Test phase verified - functional_testing.status=${expected}`);
|
|
82
98
|
}
|
|
83
99
|
else {
|
|
84
|
-
logInfo(`⚠️ Test
|
|
100
|
+
logInfo(`⚠️ Test phase exists but differs - got ${actualPhaseState}, expected ${expected}`);
|
|
85
101
|
}
|
|
86
102
|
}
|
|
87
|
-
return
|
|
103
|
+
return matches;
|
|
88
104
|
}
|
|
89
|
-
const
|
|
105
|
+
const hasTestPhase = !!actualPhaseState;
|
|
90
106
|
if (verbose) {
|
|
91
|
-
if (
|
|
92
|
-
logInfo(`✅ Test
|
|
107
|
+
if (hasTestPhase) {
|
|
108
|
+
logInfo(`✅ Test phase verified - functional_testing.status=${actualPhaseState}`);
|
|
93
109
|
}
|
|
94
110
|
else {
|
|
95
|
-
logInfo('⚠️ Test
|
|
111
|
+
logInfo('⚠️ Test phase verification failed - functional_testing not in workflow');
|
|
96
112
|
}
|
|
97
113
|
}
|
|
98
|
-
return
|
|
114
|
+
return hasTestPhase;
|
|
99
115
|
}
|
|
100
116
|
catch (error) {
|
|
101
117
|
if (verbose) {
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Functional-testing MCP server — CLI entry point.
|
|
3
|
+
*
|
|
4
|
+
* Tool implementations live in `edsger-tools`. This shim adapts the CLI's
|
|
5
|
+
* `callMcpEndpoint` transport into the `ToolDeps` shape and reads the
|
|
6
|
+
* legacy env-var credential overrides into the deps `context`.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk';
|
|
9
|
+
export declare const createFunctionalTestingMcpServer: () => McpSdkServerConfigWithInstance;
|
|
@@ -1,132 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.describe('Issue ID to get testing information for'),
|
|
15
|
-
}, async (args) => {
|
|
16
|
-
try {
|
|
17
|
-
// Get issue details
|
|
18
|
-
const issueResult = (await callMcpEndpoint('issues/get', {
|
|
19
|
-
issue_id: args.issue_id,
|
|
20
|
-
}));
|
|
21
|
-
if (!issueResult.issues || issueResult.issues.length === 0) {
|
|
22
|
-
throw new Error('Issue not found');
|
|
23
|
-
}
|
|
24
|
-
const issue = issueResult.issues[0];
|
|
25
|
-
// Get product details
|
|
26
|
-
const productResult = (await callMcpEndpoint('resources/read', {
|
|
27
|
-
uri: `product://${issue.product_id}`,
|
|
28
|
-
}));
|
|
29
|
-
const productText = productResult.contents?.[0]?.text || '{}';
|
|
30
|
-
let productInfo;
|
|
31
|
-
try {
|
|
32
|
-
productInfo = JSON.parse(productText);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
productInfo = {
|
|
36
|
-
id: issue.product_id,
|
|
37
|
-
name: 'Unknown Product',
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Get user stories
|
|
41
|
-
const userStoriesResult = (await callMcpEndpoint('user_stories/list', { issue_id: args.issue_id }));
|
|
42
|
-
// Get test cases
|
|
43
|
-
const testCasesResult = (await callMcpEndpoint('test_cases/list', {
|
|
44
|
-
issue_id: args.issue_id,
|
|
45
|
-
}));
|
|
46
|
-
const testingInfo = {
|
|
47
|
-
issue,
|
|
48
|
-
product: productInfo,
|
|
49
|
-
user_stories: userStoriesResult.user_stories || [],
|
|
50
|
-
test_cases: testCasesResult.test_cases || [],
|
|
51
|
-
technical_design: issue.technical_design || null,
|
|
52
|
-
current_test_status: issue.status || 'backlog',
|
|
53
|
-
};
|
|
54
|
-
return {
|
|
55
|
-
content: [
|
|
56
|
-
{
|
|
57
|
-
type: 'text',
|
|
58
|
-
text: JSON.stringify(testingInfo, null, 2),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
logError(`Error in get_issue_testing_info: ${error}`);
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}),
|
|
68
|
-
tool('update_test_status', 'Update the functional test status of an issue', {
|
|
69
|
-
issue_id: z.string().describe('Issue ID to update'),
|
|
70
|
-
test_status: z
|
|
71
|
-
.enum(['testing_in_progress', 'testing_passed', 'testing_failed'])
|
|
72
|
-
.describe('New test status'),
|
|
73
|
-
test_results: z
|
|
74
|
-
.string()
|
|
75
|
-
.optional()
|
|
76
|
-
.describe('Optional test results or error details'),
|
|
77
|
-
}, async (args) => {
|
|
78
|
-
try {
|
|
79
|
-
const result = await callMcpEndpoint('issues/update', {
|
|
80
|
-
issue_id: args.issue_id,
|
|
81
|
-
status: args.test_status,
|
|
82
|
-
});
|
|
83
|
-
return {
|
|
84
|
-
content: [
|
|
85
|
-
{
|
|
86
|
-
type: 'text',
|
|
87
|
-
text: JSON.stringify({
|
|
88
|
-
success: true,
|
|
89
|
-
issue_id: args.issue_id,
|
|
90
|
-
test_status: args.test_status,
|
|
91
|
-
test_results: args.test_results || null,
|
|
92
|
-
message: `Issue test status updated to: ${args.test_status}`,
|
|
93
|
-
result,
|
|
94
|
-
}, null, 2),
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
logError(`Error updating test status: ${error}`);
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
}),
|
|
104
|
-
tool('get_environment_config', 'Get testing environment configuration including login credentials', {
|
|
105
|
-
environment: z
|
|
106
|
-
.string()
|
|
107
|
-
.optional()
|
|
108
|
-
.default('testing')
|
|
109
|
-
.describe('Environment name (testing, staging, production)'),
|
|
110
|
-
}, (args) => {
|
|
111
|
-
// Get environment variables for testing configuration
|
|
112
|
-
const testingConfig = {
|
|
113
|
-
environment: args.environment || 'testing',
|
|
114
|
-
login_username: process.env.TESTING_LOGIN_USERNAME || '',
|
|
115
|
-
login_password: process.env.TESTING_LOGIN_PASSWORD || '',
|
|
116
|
-
};
|
|
117
|
-
// Validate required configuration
|
|
118
|
-
if (!testingConfig.login_username || !testingConfig.login_password) {
|
|
119
|
-
throw new Error('Testing credentials not configured. Set TESTING_LOGIN_USERNAME and TESTING_LOGIN_PASSWORD environment variables.');
|
|
120
|
-
}
|
|
121
|
-
return Promise.resolve({
|
|
122
|
-
content: [
|
|
123
|
-
{
|
|
124
|
-
type: 'text',
|
|
125
|
-
text: JSON.stringify(testingConfig, null, 2),
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
}),
|
|
130
|
-
],
|
|
131
|
-
});
|
|
132
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Functional-testing MCP server — CLI entry point.
|
|
3
|
+
*
|
|
4
|
+
* Tool implementations live in `edsger-tools`. This shim adapts the CLI's
|
|
5
|
+
* `callMcpEndpoint` transport into the `ToolDeps` shape and reads the
|
|
6
|
+
* legacy env-var credential overrides into the deps `context`.
|
|
7
|
+
*/
|
|
8
|
+
import { createFunctionalTestingMcpServer as createFunctionalTestingMcpServerCore } from 'edsger-tools';
|
|
9
|
+
import { getToolDeps } from '../../tools/bootstrap.js';
|
|
10
|
+
export const createFunctionalTestingMcpServer = () => createFunctionalTestingMcpServerCore(getToolDeps(false, {
|
|
11
|
+
testingLoginUsername: process.env.TESTING_LOGIN_USERNAME,
|
|
12
|
+
testingLoginPassword: process.env.TESTING_LOGIN_PASSWORD,
|
|
13
|
+
}));
|
|
@@ -38,7 +38,6 @@ function userMessage(content) {
|
|
|
38
38
|
async function* prompt(analysisPrompt) {
|
|
39
39
|
yield userMessage(analysisPrompt);
|
|
40
40
|
}
|
|
41
|
-
// eslint-disable-next-line complexity -- agent loop with message type handling
|
|
42
41
|
export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, config, verbose, cwd) {
|
|
43
42
|
let lastAssistantResponse = '';
|
|
44
43
|
let structuredResult = null;
|
|
@@ -69,7 +68,8 @@ export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, co
|
|
|
69
68
|
logDebug(`${content.text}`, verbose);
|
|
70
69
|
}
|
|
71
70
|
else if (content.type === 'tool_use') {
|
|
72
|
-
const
|
|
71
|
+
const input = (content.input ?? {});
|
|
72
|
+
const desc = input.description ?? input.command ?? 'Running...';
|
|
73
73
|
logInfo(`[Turn ${turnCount}] ${content.name}: ${typeof desc === 'string' ? desc.slice(0, 120) : 'Running...'}`);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -2,7 +2,7 @@ import { getGitHubConfigByProduct } from '../../api/github.js';
|
|
|
2
2
|
import { saveGrowthAnalysis, updateGrowthAnalysis } from '../../api/growth.js';
|
|
3
3
|
import { generateGrowthVideo, } from '../../services/video/index.js';
|
|
4
4
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
5
|
-
import { cloneIssueRepo, ensureWorkspaceDir, } from '../../workspace/workspace-manager.js';
|
|
5
|
+
import { cleanupIssueRepo, cloneIssueRepo, ensureWorkspaceDir, } from '../../workspace/workspace-manager.js';
|
|
6
6
|
import { executeGrowthAnalysisQuery } from './agent.js';
|
|
7
7
|
import { prepareGrowthAnalysisContext } from './context.js';
|
|
8
8
|
import { createGrowthAnalysisSystemPrompt } from './prompts.js';
|
|
@@ -49,15 +49,15 @@ contentSuggestions) {
|
|
|
49
49
|
}
|
|
50
50
|
return plans;
|
|
51
51
|
}
|
|
52
|
-
// eslint-disable-next-line complexity
|
|
53
52
|
export const analyseGrowth = async (options, config) => {
|
|
54
53
|
const { productId, verbose, guidance, analysisId } = options;
|
|
55
54
|
if (verbose) {
|
|
56
55
|
logInfo(`Starting growth analysis for product ID: ${productId}`);
|
|
57
56
|
}
|
|
57
|
+
let repoCwd;
|
|
58
|
+
let analysisSucceeded = false;
|
|
58
59
|
try {
|
|
59
60
|
// Clone product repo if GitHub is configured
|
|
60
|
-
let repoCwd;
|
|
61
61
|
try {
|
|
62
62
|
const githubConfig = await getGitHubConfigByProduct(productId, verbose);
|
|
63
63
|
if (githubConfig.configured &&
|
|
@@ -174,6 +174,7 @@ export const analyseGrowth = async (options, config) => {
|
|
|
174
174
|
: {}),
|
|
175
175
|
};
|
|
176
176
|
});
|
|
177
|
+
analysisSucceeded = true;
|
|
177
178
|
return {
|
|
178
179
|
productId,
|
|
179
180
|
status: 'success',
|
|
@@ -193,6 +194,11 @@ export const analyseGrowth = async (options, config) => {
|
|
|
193
194
|
contentSuggestions: [],
|
|
194
195
|
};
|
|
195
196
|
}
|
|
197
|
+
finally {
|
|
198
|
+
if (analysisSucceeded) {
|
|
199
|
+
cleanupIssueRepo(repoCwd);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
196
202
|
};
|
|
197
203
|
/**
|
|
198
204
|
* Run async tasks with a concurrency limit.
|