edsger 0.56.3 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/chat.js +55 -2
- package/dist/api/cross-product.d.ts +8 -1
- package/dist/api/cross-product.js +44 -1
- package/dist/api/intelligence.js +98 -0
- package/dist/api/issues/get-issue.js +26 -0
- package/dist/api/issues/issue-utils.js +52 -0
- package/dist/api/issues/test-cases.js +89 -14
- package/dist/api/issues/update-issue.js +46 -8
- package/dist/api/issues/user-stories.js +89 -14
- package/dist/api/products/test-cases.d.ts +18 -0
- package/dist/api/products/test-cases.js +51 -0
- package/dist/api/products.js +21 -0
- package/dist/api/release-test-cases.js +38 -0
- package/dist/api/releases.js +86 -0
- package/dist/api/tasks.js +41 -4
- package/dist/api/test-reports.js +22 -4
- package/dist/api/user-psychology.d.ts +101 -0
- package/dist/api/user-psychology.js +143 -0
- package/dist/auth/auth-store.d.ts +33 -0
- package/dist/auth/auth-store.js +39 -0
- package/dist/commands/agent-workflow/chat-worker.js +187 -15
- package/dist/commands/agent-workflow/processor.d.ts +11 -0
- package/dist/commands/agent-workflow/processor.js +81 -2
- package/dist/commands/product-test-cases/index.d.ts +12 -0
- package/dist/commands/product-test-cases/index.js +40 -0
- package/dist/commands/screen-flow/index.d.ts +16 -0
- package/dist/commands/screen-flow/index.js +45 -0
- package/dist/commands/user-psychology/index.d.ts +7 -0
- package/dist/commands/user-psychology/index.js +51 -0
- package/dist/index.js +65 -0
- package/dist/phases/analyze-logs/index.js +27 -6
- package/dist/phases/bug-fixing/context-fetcher.js +26 -5
- package/dist/phases/find-features/index.js +53 -9
- package/dist/phases/find-shared/mcp.js +21 -0
- package/dist/phases/growth-analysis/context.d.ts +5 -3
- package/dist/phases/growth-analysis/context.js +52 -5
- package/dist/phases/output-contracts.js +140 -0
- package/dist/phases/pr-resolve/github-reply.d.ts +5 -2
- package/dist/phases/pr-resolve/github-reply.js +19 -3
- package/dist/phases/pr-resolve/index.js +19 -5
- package/dist/phases/pr-resolve/prompts.js +17 -18
- package/dist/phases/pr-shared/agent-utils.d.ts +11 -3
- package/dist/phases/pr-shared/agent-utils.js +48 -4
- package/dist/phases/product-test-cases/index.d.ts +25 -0
- package/dist/phases/product-test-cases/index.js +174 -0
- package/dist/phases/product-test-cases/prompts.d.ts +24 -0
- package/dist/phases/product-test-cases/prompts.js +80 -0
- package/dist/phases/product-test-cases/types.d.ts +17 -0
- package/dist/phases/product-test-cases/types.js +27 -0
- package/dist/phases/screen-flow/index.d.ts +23 -0
- package/dist/phases/screen-flow/index.js +285 -0
- package/dist/phases/screen-flow/mcp-server.d.ts +195 -0
- package/dist/phases/screen-flow/mcp-server.js +262 -0
- package/dist/phases/screen-flow/prompts.d.ts +19 -0
- package/dist/phases/screen-flow/prompts.js +41 -0
- package/dist/phases/screen-flow/theme.d.ts +19 -0
- package/dist/phases/screen-flow/theme.js +193 -0
- package/dist/phases/screen-flow/types.d.ts +130 -0
- package/dist/phases/screen-flow/types.js +81 -0
- package/dist/phases/user-psychology/agent.d.ts +16 -0
- package/dist/phases/user-psychology/agent.js +105 -0
- package/dist/phases/user-psychology/context.d.ts +10 -0
- package/dist/phases/user-psychology/context.js +65 -0
- package/dist/phases/user-psychology/index.d.ts +18 -0
- package/dist/phases/user-psychology/index.js +96 -0
- package/dist/phases/user-psychology/prompts.d.ts +2 -0
- package/dist/phases/user-psychology/prompts.js +41 -0
- package/dist/services/audit-logs.js +67 -9
- package/dist/services/branches.js +90 -14
- package/dist/services/phase-ratings.js +71 -9
- package/dist/services/product-logs.js +65 -5
- package/dist/services/pull-requests.js +74 -14
- package/dist/skills/phase/screen-flow/SKILL.md +78 -0
- package/dist/skills/phase/user-psychology/SKILL.md +135 -0
- package/dist/supabase/client.d.ts +23 -0
- package/dist/supabase/client.js +90 -0
- package/dist/system/session-manager.js +97 -24
- package/dist/types/index.d.ts +3 -0
- package/dist/utils/logger.js +24 -4
- package/package.json +4 -3
- package/vitest.config.ts +1 -0
package/dist/api/chat.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* MCP client wrappers for chat operations.
|
|
3
3
|
* Used by the chat-worker subprocess to interact with chat channels and messages.
|
|
4
4
|
*/
|
|
5
|
+
import { getSupabase, hasSupabaseSession } from '../supabase/client.js';
|
|
5
6
|
import { logError, logInfo } from '../utils/logger.js';
|
|
6
7
|
import { callMcpEndpoint } from './mcp-client.js';
|
|
7
8
|
// ============================================================
|
|
@@ -23,6 +24,32 @@ export async function listChannels(channelType, channelRefId, verbose) {
|
|
|
23
24
|
if (verbose) {
|
|
24
25
|
logInfo(`Listing channels: type=${channelType || 'all'}`);
|
|
25
26
|
}
|
|
27
|
+
if (hasSupabaseSession()) {
|
|
28
|
+
try {
|
|
29
|
+
// RLS (chat_channels_select_policy) already filters to channels the
|
|
30
|
+
// user is a member of — no extra membership join needed.
|
|
31
|
+
let query = getSupabase().from('chat_channels').select('*');
|
|
32
|
+
if (channelType) {
|
|
33
|
+
query = query.eq('channel_type', channelType);
|
|
34
|
+
}
|
|
35
|
+
if (channelRefId) {
|
|
36
|
+
query = query.eq('channel_ref_id', channelRefId);
|
|
37
|
+
}
|
|
38
|
+
const { data, error } = await query.order('created_at', {
|
|
39
|
+
ascending: false,
|
|
40
|
+
});
|
|
41
|
+
if (error) {
|
|
42
|
+
throw new Error(error.message);
|
|
43
|
+
}
|
|
44
|
+
return (data || []);
|
|
45
|
+
}
|
|
46
|
+
catch (sdkError) {
|
|
47
|
+
if (verbose) {
|
|
48
|
+
logError(`Direct chat_channels SELECT failed, falling back to MCP: ${sdkError instanceof Error ? sdkError.message : String(sdkError)}`);
|
|
49
|
+
}
|
|
50
|
+
// Fall through to MCP path
|
|
51
|
+
}
|
|
52
|
+
}
|
|
26
53
|
const result = (await callMcpEndpoint('chat/channels/list', {
|
|
27
54
|
channel_type: channelType,
|
|
28
55
|
channel_ref_id: channelRefId,
|
|
@@ -103,11 +130,37 @@ export async function claimPendingMessages(channelId, workerId, options = {}, ve
|
|
|
103
130
|
if (verbose) {
|
|
104
131
|
logInfo(`Claiming pending messages for channel ${channelId} (worker: ${workerId})`);
|
|
105
132
|
}
|
|
133
|
+
const limit = options.limit || 10;
|
|
134
|
+
const staleSeconds = options.staleTimeoutSeconds || 300;
|
|
135
|
+
if (hasSupabaseSession()) {
|
|
136
|
+
try {
|
|
137
|
+
// The JWT-callable RPC enforces `user_is_chat_channel_member(auth.uid(),
|
|
138
|
+
// channel_id)` internally. It uses FOR UPDATE SKIP LOCKED so concurrent
|
|
139
|
+
// workers never claim the same message.
|
|
140
|
+
const { data, error } = await getSupabase().rpc('claim_pending_chat_messages_jwt', {
|
|
141
|
+
p_channel_id: channelId,
|
|
142
|
+
p_worker_id: workerId,
|
|
143
|
+
p_limit: limit,
|
|
144
|
+
// Postgres `interval` accepts the canonical 'N seconds' format.
|
|
145
|
+
p_stale_timeout: `${staleSeconds} seconds`,
|
|
146
|
+
});
|
|
147
|
+
if (error) {
|
|
148
|
+
throw new Error(error.message);
|
|
149
|
+
}
|
|
150
|
+
return (data || []);
|
|
151
|
+
}
|
|
152
|
+
catch (sdkError) {
|
|
153
|
+
if (verbose) {
|
|
154
|
+
logError(`Direct chat claim failed, falling back to MCP: ${sdkError instanceof Error ? sdkError.message : String(sdkError)}`);
|
|
155
|
+
}
|
|
156
|
+
// Fall through to MCP path
|
|
157
|
+
}
|
|
158
|
+
}
|
|
106
159
|
const result = (await callMcpEndpoint('chat/messages/claim', {
|
|
107
160
|
channel_id: channelId,
|
|
108
161
|
worker_id: workerId,
|
|
109
|
-
limit
|
|
110
|
-
stale_timeout_seconds:
|
|
162
|
+
limit,
|
|
163
|
+
stale_timeout_seconds: staleSeconds,
|
|
111
164
|
}));
|
|
112
165
|
return result.messages || [];
|
|
113
166
|
}
|
|
@@ -21,6 +21,13 @@ export interface IssueWithProduct extends IssueInfo {
|
|
|
21
21
|
export declare function listProducts(verbose?: boolean): Promise<ProductSummary[]>;
|
|
22
22
|
/**
|
|
23
23
|
* List all ready_for_ai issues across all products
|
|
24
|
-
*
|
|
24
|
+
*
|
|
25
|
+
* Direct-SDK path: a single `issues` SELECT joining `products` for the name.
|
|
26
|
+
* RLS on `issues` already filters to products the user has access to, so the
|
|
27
|
+
* old "fetch products, then fetch issues per product" N+1 dance is gone — one
|
|
28
|
+
* round trip total.
|
|
29
|
+
*
|
|
30
|
+
* MCP fallback (no Supabase session): keeps the previous N+1 behaviour so
|
|
31
|
+
* legacy CLIs still work until the desktop-app rolls out.
|
|
25
32
|
*/
|
|
26
33
|
export declare function listAllReadyIssues(verbose?: boolean): Promise<IssueWithProduct[]>;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* processes issues from all products the user has access to,
|
|
6
6
|
* not just a single product.
|
|
7
7
|
*/
|
|
8
|
+
import { getSupabase, hasSupabaseSession } from '../supabase/client.js';
|
|
8
9
|
import { logError, logInfo } from '../utils/logger.js';
|
|
9
10
|
import { callMcpEndpoint } from './mcp-client.js';
|
|
10
11
|
/**
|
|
@@ -29,12 +30,54 @@ export async function listProducts(verbose) {
|
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* List all ready_for_ai issues across all products
|
|
32
|
-
*
|
|
33
|
+
*
|
|
34
|
+
* Direct-SDK path: a single `issues` SELECT joining `products` for the name.
|
|
35
|
+
* RLS on `issues` already filters to products the user has access to, so the
|
|
36
|
+
* old "fetch products, then fetch issues per product" N+1 dance is gone — one
|
|
37
|
+
* round trip total.
|
|
38
|
+
*
|
|
39
|
+
* MCP fallback (no Supabase session): keeps the previous N+1 behaviour so
|
|
40
|
+
* legacy CLIs still work until the desktop-app rolls out.
|
|
33
41
|
*/
|
|
34
42
|
export async function listAllReadyIssues(verbose) {
|
|
35
43
|
if (verbose) {
|
|
36
44
|
logInfo('Fetching ready_for_ai issues across all products...');
|
|
37
45
|
}
|
|
46
|
+
if (hasSupabaseSession()) {
|
|
47
|
+
try {
|
|
48
|
+
const { data, error } = await getSupabase()
|
|
49
|
+
.from('issues')
|
|
50
|
+
.select('*, product:products!inner(id, name)')
|
|
51
|
+
.eq('status', 'ready_for_ai')
|
|
52
|
+
.order('updated_at', { ascending: true });
|
|
53
|
+
if (error) {
|
|
54
|
+
throw new Error(error.message);
|
|
55
|
+
}
|
|
56
|
+
const allIssues = (data || []).map((row) => {
|
|
57
|
+
// The `product` join lands as a nested object; lift its name onto
|
|
58
|
+
// the row to keep the wire shape stable.
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- shape from PostgREST join
|
|
60
|
+
const r = row;
|
|
61
|
+
return {
|
|
62
|
+
...r,
|
|
63
|
+
product_name: r.product?.name ?? undefined,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
if (verbose) {
|
|
67
|
+
logInfo(`Found ${allIssues.length} ready_for_ai issue(s) across products`);
|
|
68
|
+
allIssues.forEach((f, i) => {
|
|
69
|
+
logInfo(` ${i + 1}. [${f.product_name}] ${f.name}`);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return allIssues;
|
|
73
|
+
}
|
|
74
|
+
catch (sdkError) {
|
|
75
|
+
if (verbose) {
|
|
76
|
+
logError(`Direct issues SELECT failed, falling back to MCP: ${sdkError instanceof Error ? sdkError.message : String(sdkError)}`);
|
|
77
|
+
}
|
|
78
|
+
// Fall through to MCP path below
|
|
79
|
+
}
|
|
80
|
+
}
|
|
38
81
|
try {
|
|
39
82
|
// First, get all products
|
|
40
83
|
const products = await listProducts(verbose);
|
package/dist/api/intelligence.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSupabase, hasSupabaseSession } from '../supabase/client.js';
|
|
1
2
|
import { logError, logInfo } from '../utils/logger.js';
|
|
2
3
|
import { callMcpEndpoint } from './mcp-client.js';
|
|
3
4
|
// =============================================================================
|
|
@@ -71,6 +72,23 @@ export async function updateCompetitor(competitorId, updates, verbose) {
|
|
|
71
72
|
logInfo(`Updating competitor: ${competitorId}`);
|
|
72
73
|
}
|
|
73
74
|
try {
|
|
75
|
+
if (hasSupabaseSession()) {
|
|
76
|
+
try {
|
|
77
|
+
const { data, error } = await getSupabase()
|
|
78
|
+
.from('product_competitors')
|
|
79
|
+
.update(updates)
|
|
80
|
+
.eq('id', competitorId)
|
|
81
|
+
.select()
|
|
82
|
+
.single();
|
|
83
|
+
if (error) {
|
|
84
|
+
throw new Error(error.message);
|
|
85
|
+
}
|
|
86
|
+
return data ?? null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Fall through to MCP
|
|
90
|
+
}
|
|
91
|
+
}
|
|
74
92
|
const result = await callMcpEndpoint('intelligence/competitors/update', {
|
|
75
93
|
competitor_id: competitorId,
|
|
76
94
|
...updates,
|
|
@@ -87,6 +105,21 @@ export async function deleteCompetitor(competitorId, verbose) {
|
|
|
87
105
|
logInfo(`Deleting competitor: ${competitorId}`);
|
|
88
106
|
}
|
|
89
107
|
try {
|
|
108
|
+
if (hasSupabaseSession()) {
|
|
109
|
+
try {
|
|
110
|
+
const { error } = await getSupabase()
|
|
111
|
+
.from('product_competitors')
|
|
112
|
+
.delete()
|
|
113
|
+
.eq('id', competitorId);
|
|
114
|
+
if (error) {
|
|
115
|
+
throw new Error(error.message);
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Fall through to MCP
|
|
121
|
+
}
|
|
122
|
+
}
|
|
90
123
|
await callMcpEndpoint('intelligence/competitors/delete', {
|
|
91
124
|
competitor_id: competitorId,
|
|
92
125
|
});
|
|
@@ -105,6 +138,29 @@ export async function getSnapshots(opts, verbose) {
|
|
|
105
138
|
logInfo(`Fetching snapshots`);
|
|
106
139
|
}
|
|
107
140
|
try {
|
|
141
|
+
if (hasSupabaseSession()) {
|
|
142
|
+
try {
|
|
143
|
+
let query = getSupabase().from('competitor_snapshots').select('*');
|
|
144
|
+
if (opts.competitorId) {
|
|
145
|
+
query = query.eq('competitor_id', opts.competitorId);
|
|
146
|
+
}
|
|
147
|
+
if (opts.productId) {
|
|
148
|
+
query = query.eq('product_id', opts.productId);
|
|
149
|
+
}
|
|
150
|
+
query = query.order('created_at', { ascending: false });
|
|
151
|
+
if (opts.limit) {
|
|
152
|
+
query = query.limit(opts.limit);
|
|
153
|
+
}
|
|
154
|
+
const { data, error } = await query;
|
|
155
|
+
if (error) {
|
|
156
|
+
throw new Error(error.message);
|
|
157
|
+
}
|
|
158
|
+
return (data || []);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Fall through to MCP
|
|
162
|
+
}
|
|
163
|
+
}
|
|
108
164
|
const result = await callMcpEndpoint('intelligence/snapshots/list', {
|
|
109
165
|
competitor_id: opts.competitorId,
|
|
110
166
|
product_id: opts.productId,
|
|
@@ -140,6 +196,32 @@ export async function getReports(productId, opts, verbose) {
|
|
|
140
196
|
logInfo(`Fetching intelligence reports for product: ${productId}`);
|
|
141
197
|
}
|
|
142
198
|
try {
|
|
199
|
+
if (hasSupabaseSession()) {
|
|
200
|
+
try {
|
|
201
|
+
let query = getSupabase()
|
|
202
|
+
.from('intelligence_reports')
|
|
203
|
+
.select('*')
|
|
204
|
+
.eq('product_id', productId);
|
|
205
|
+
if (opts?.reportType) {
|
|
206
|
+
query = query.eq('report_type', opts.reportType);
|
|
207
|
+
}
|
|
208
|
+
if (opts?.status) {
|
|
209
|
+
query = query.eq('status', opts.status);
|
|
210
|
+
}
|
|
211
|
+
query = query.order('created_at', { ascending: false });
|
|
212
|
+
if (opts?.limit) {
|
|
213
|
+
query = query.limit(opts.limit);
|
|
214
|
+
}
|
|
215
|
+
const { data, error } = await query;
|
|
216
|
+
if (error) {
|
|
217
|
+
throw new Error(error.message);
|
|
218
|
+
}
|
|
219
|
+
return (data || []);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Fall through to MCP
|
|
223
|
+
}
|
|
224
|
+
}
|
|
143
225
|
const result = await callMcpEndpoint('intelligence/reports/list', {
|
|
144
226
|
product_id: productId,
|
|
145
227
|
report_type: opts?.reportType,
|
|
@@ -160,6 +242,22 @@ export async function getReport(reportId, verbose) {
|
|
|
160
242
|
logInfo(`Fetching report: ${reportId}`);
|
|
161
243
|
}
|
|
162
244
|
try {
|
|
245
|
+
if (hasSupabaseSession()) {
|
|
246
|
+
try {
|
|
247
|
+
const { data, error } = await getSupabase()
|
|
248
|
+
.from('intelligence_reports')
|
|
249
|
+
.select('*')
|
|
250
|
+
.eq('id', reportId)
|
|
251
|
+
.maybeSingle();
|
|
252
|
+
if (error) {
|
|
253
|
+
throw new Error(error.message);
|
|
254
|
+
}
|
|
255
|
+
return data ?? null;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Fall through to MCP
|
|
259
|
+
}
|
|
260
|
+
}
|
|
163
261
|
const result = await callMcpEndpoint('intelligence/reports/get', {
|
|
164
262
|
report_id: reportId,
|
|
165
263
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSupabase, hasSupabaseSession } from '../../supabase/client.js';
|
|
1
2
|
import { logInfo } from '../../utils/logger.js';
|
|
2
3
|
import { callMcpEndpoint } from '../mcp-client.js';
|
|
3
4
|
/**
|
|
@@ -7,6 +8,31 @@ export async function getIssue(issueId, verbose) {
|
|
|
7
8
|
if (verbose) {
|
|
8
9
|
logInfo(`Fetching issue details for: ${issueId}`);
|
|
9
10
|
}
|
|
11
|
+
if (hasSupabaseSession()) {
|
|
12
|
+
try {
|
|
13
|
+
const { data, error } = await getSupabase()
|
|
14
|
+
.from('issues')
|
|
15
|
+
.select('*')
|
|
16
|
+
.eq('id', issueId)
|
|
17
|
+
.maybeSingle();
|
|
18
|
+
if (error) {
|
|
19
|
+
throw new Error(error.message);
|
|
20
|
+
}
|
|
21
|
+
if (!data) {
|
|
22
|
+
throw new Error(`Issue not found: ${issueId}`);
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
catch (sdkErr) {
|
|
27
|
+
// Surface "not found" immediately so callers don't double-fetch via
|
|
28
|
+
// MCP for an issue that genuinely doesn't exist.
|
|
29
|
+
if (sdkErr instanceof Error &&
|
|
30
|
+
sdkErr.message === `Issue not found: ${issueId}`) {
|
|
31
|
+
throw sdkErr;
|
|
32
|
+
}
|
|
33
|
+
// Other errors fall through to MCP
|
|
34
|
+
}
|
|
35
|
+
}
|
|
10
36
|
const result = (await callMcpEndpoint('issues/get', {
|
|
11
37
|
issue_id: issueId,
|
|
12
38
|
}));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getUserId } from '../../auth/auth-store.js';
|
|
2
|
+
import { getSupabase, hasSupabaseSession } from '../../supabase/client.js';
|
|
1
3
|
import { logError, logInfo } from '../../utils/logger.js';
|
|
2
4
|
import { callMcpEndpoint } from '../mcp-client.js';
|
|
3
5
|
/**
|
|
@@ -17,6 +19,56 @@ export async function claimNextIssue(productId, verbose) {
|
|
|
17
19
|
? `Attempting to claim next ready_for_ai issue for product: ${productId}`
|
|
18
20
|
: 'Attempting to claim next ready_for_ai issue across all products');
|
|
19
21
|
}
|
|
22
|
+
// Direct-SDK path: call the existing claim_next_ready_issue RPC. The RPC is
|
|
23
|
+
// SECURITY INVOKER and gates access by `developer_id = p_worker_id`, so the
|
|
24
|
+
// user JWT is honoured naturally.
|
|
25
|
+
const userId = getUserId();
|
|
26
|
+
if (hasSupabaseSession() && userId) {
|
|
27
|
+
try {
|
|
28
|
+
const { data, error } = await getSupabase().rpc('claim_next_ready_issue', {
|
|
29
|
+
p_worker_id: userId,
|
|
30
|
+
p_product_id: productId ?? null,
|
|
31
|
+
});
|
|
32
|
+
if (error) {
|
|
33
|
+
throw new Error(error.message);
|
|
34
|
+
}
|
|
35
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
36
|
+
if (!row) {
|
|
37
|
+
if (verbose) {
|
|
38
|
+
logInfo('No issues available for processing');
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// RPC returns rows with `issue_*` prefixed column names — unwrap to
|
|
43
|
+
// the IssueInfo shape callers already consume.
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- raw RPC row
|
|
45
|
+
const r = row;
|
|
46
|
+
const issue = {
|
|
47
|
+
id: r.issue_id,
|
|
48
|
+
name: r.issue_name,
|
|
49
|
+
description: r.issue_description,
|
|
50
|
+
technical_design: r.issue_technical_design,
|
|
51
|
+
status: r.issue_status,
|
|
52
|
+
execution_mode: r.issue_execution_mode,
|
|
53
|
+
workflow: r.issue_workflow,
|
|
54
|
+
product_id: r.issue_product_id,
|
|
55
|
+
created_at: r.issue_created_at,
|
|
56
|
+
updated_at: r.issue_updated_at,
|
|
57
|
+
autonomous_hours: r.issue_autonomous_hours,
|
|
58
|
+
execution_plan: r.issue_execution_plan,
|
|
59
|
+
};
|
|
60
|
+
if (verbose) {
|
|
61
|
+
logInfo(`✅ Claimed issue: ${issue.name} (${issue.id})`);
|
|
62
|
+
}
|
|
63
|
+
return issue;
|
|
64
|
+
}
|
|
65
|
+
catch (sdkError) {
|
|
66
|
+
if (verbose) {
|
|
67
|
+
logError(`Direct issues/claim failed, falling back to MCP: ${sdkError instanceof Error ? sdkError.message : String(sdkError)}`);
|
|
68
|
+
}
|
|
69
|
+
// Fall through to MCP path
|
|
70
|
+
}
|
|
71
|
+
}
|
|
20
72
|
try {
|
|
21
73
|
const result = (await callMcpEndpoint('issues/claim', productId ? { product_id: productId } : {}));
|
|
22
74
|
if (result.issue) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSupabase, hasSupabaseSession } from '../../supabase/client.js';
|
|
1
2
|
import { logError, logInfo } from '../../utils/logger.js';
|
|
2
3
|
import { callMcpEndpoint } from '../mcp-client.js';
|
|
3
4
|
/**
|
|
@@ -7,6 +8,22 @@ export async function getTestCases(issueId, verbose) {
|
|
|
7
8
|
if (verbose) {
|
|
8
9
|
logInfo(`Fetching test cases for issue: ${issueId}`);
|
|
9
10
|
}
|
|
11
|
+
if (hasSupabaseSession()) {
|
|
12
|
+
try {
|
|
13
|
+
const { data, error } = await getSupabase()
|
|
14
|
+
.from('test_cases')
|
|
15
|
+
.select('*')
|
|
16
|
+
.eq('issue_id', issueId)
|
|
17
|
+
.order('created_at', { ascending: true });
|
|
18
|
+
if (error) {
|
|
19
|
+
throw new Error(error.message);
|
|
20
|
+
}
|
|
21
|
+
return (data || []);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Fall through to MCP
|
|
25
|
+
}
|
|
26
|
+
}
|
|
10
27
|
const result = (await callMcpEndpoint('test_cases/list', {
|
|
11
28
|
issue_id: issueId,
|
|
12
29
|
}));
|
|
@@ -20,16 +37,38 @@ export async function createTestCase(issueId, testCase, verbose) {
|
|
|
20
37
|
if (verbose) {
|
|
21
38
|
logInfo(`Creating test case for issue: ${issueId}`);
|
|
22
39
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
40
|
+
let usedSdk = false;
|
|
41
|
+
if (hasSupabaseSession()) {
|
|
42
|
+
try {
|
|
43
|
+
const { error } = await getSupabase()
|
|
44
|
+
.from('test_cases')
|
|
45
|
+
.insert({
|
|
46
|
+
issue_id: issueId,
|
|
27
47
|
name: testCase.name,
|
|
28
48
|
description: testCase.description,
|
|
29
49
|
is_critical: testCase.is_critical || false,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
});
|
|
51
|
+
if (error) {
|
|
52
|
+
throw new Error(error.message);
|
|
53
|
+
}
|
|
54
|
+
usedSdk = true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fall through to MCP
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!usedSdk) {
|
|
61
|
+
await callMcpEndpoint('test_cases/create', {
|
|
62
|
+
issue_id: issueId,
|
|
63
|
+
test_cases: [
|
|
64
|
+
{
|
|
65
|
+
name: testCase.name,
|
|
66
|
+
description: testCase.description,
|
|
67
|
+
is_critical: testCase.is_critical || false,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
33
72
|
if (verbose) {
|
|
34
73
|
logInfo('✅ Test case created successfully');
|
|
35
74
|
}
|
|
@@ -71,9 +110,27 @@ export async function deleteTestCase(testCaseId, verbose) {
|
|
|
71
110
|
if (verbose) {
|
|
72
111
|
logInfo(`Deleting test case: ${testCaseId}`);
|
|
73
112
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
113
|
+
let usedSdk = false;
|
|
114
|
+
if (hasSupabaseSession()) {
|
|
115
|
+
try {
|
|
116
|
+
const { error } = await getSupabase()
|
|
117
|
+
.from('test_cases')
|
|
118
|
+
.delete()
|
|
119
|
+
.eq('id', testCaseId);
|
|
120
|
+
if (error) {
|
|
121
|
+
throw new Error(error.message);
|
|
122
|
+
}
|
|
123
|
+
usedSdk = true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Fall through to MCP
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!usedSdk) {
|
|
130
|
+
await callMcpEndpoint('test_cases/delete', {
|
|
131
|
+
test_case_id: testCaseId,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
77
134
|
if (verbose) {
|
|
78
135
|
logInfo('✅ Test case deleted successfully');
|
|
79
136
|
}
|
|
@@ -93,10 +150,28 @@ export async function updateTestCaseStatus(testCaseId, status, verbose) {
|
|
|
93
150
|
if (verbose) {
|
|
94
151
|
logInfo(`Updating test case ${testCaseId} status to: ${status}`);
|
|
95
152
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
153
|
+
let usedSdk = false;
|
|
154
|
+
if (hasSupabaseSession()) {
|
|
155
|
+
try {
|
|
156
|
+
const { error } = await getSupabase()
|
|
157
|
+
.from('test_cases')
|
|
158
|
+
.update({ status })
|
|
159
|
+
.eq('id', testCaseId);
|
|
160
|
+
if (error) {
|
|
161
|
+
throw new Error(error.message);
|
|
162
|
+
}
|
|
163
|
+
usedSdk = true;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Fall through to MCP
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!usedSdk) {
|
|
170
|
+
await callMcpEndpoint('test_cases/update_status', {
|
|
171
|
+
test_case_id: testCaseId,
|
|
172
|
+
status,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
100
175
|
if (verbose) {
|
|
101
176
|
logInfo('✅ Test case status updated successfully');
|
|
102
177
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSupabase, hasSupabaseSession } from '../../supabase/client.js';
|
|
1
2
|
import { logError, logInfo } from '../../utils/logger.js';
|
|
2
3
|
import { callMcpEndpoint } from '../mcp-client.js';
|
|
3
4
|
/**
|
|
@@ -8,10 +9,28 @@ export async function updateIssue(issueId, updates, verbose) {
|
|
|
8
9
|
if (verbose) {
|
|
9
10
|
logInfo(`Updating issue: ${issueId}`);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
let usedSdk = false;
|
|
13
|
+
if (hasSupabaseSession()) {
|
|
14
|
+
try {
|
|
15
|
+
const { error } = await getSupabase()
|
|
16
|
+
.from('issues')
|
|
17
|
+
.update(updates)
|
|
18
|
+
.eq('id', issueId);
|
|
19
|
+
if (error) {
|
|
20
|
+
throw new Error(error.message);
|
|
21
|
+
}
|
|
22
|
+
usedSdk = true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Fall through to MCP
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!usedSdk) {
|
|
29
|
+
await callMcpEndpoint('issues/update', {
|
|
30
|
+
issue_id: issueId,
|
|
31
|
+
...updates,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
15
34
|
if (verbose) {
|
|
16
35
|
logInfo('✅ Issue updated successfully');
|
|
17
36
|
}
|
|
@@ -55,10 +74,29 @@ export async function markWorkflowPhaseCompleted(issueId, phaseName, verbose) {
|
|
|
55
74
|
logInfo(`Marking workflow phase '${normalizedPhaseName}' as completed for issue: ${issueId}`);
|
|
56
75
|
}
|
|
57
76
|
// Fetch current issue to get workflow
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
let issue = null;
|
|
78
|
+
if (hasSupabaseSession()) {
|
|
79
|
+
try {
|
|
80
|
+
const { data, error } = await getSupabase()
|
|
81
|
+
.from('issues')
|
|
82
|
+
.select('workflow')
|
|
83
|
+
.eq('id', issueId)
|
|
84
|
+
.maybeSingle();
|
|
85
|
+
if (error) {
|
|
86
|
+
throw new Error(error.message);
|
|
87
|
+
}
|
|
88
|
+
issue = data;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Fall through to MCP
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (issue == null) {
|
|
95
|
+
const response = (await callMcpEndpoint('issues/get', {
|
|
96
|
+
issue_id: issueId,
|
|
97
|
+
}));
|
|
98
|
+
issue = response?.issues?.[0] || null;
|
|
99
|
+
}
|
|
62
100
|
if (!issue) {
|
|
63
101
|
logError(`Issue not found: ${issueId}`);
|
|
64
102
|
return false;
|