@vailent/pulse-mcp 1.9.0 → 1.11.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/server.js +846 -512
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -4289,11 +4289,11 @@ var require_core = __commonJS({
|
|
|
4289
4289
|
Ajv2.ValidationError = validation_error_1.default;
|
|
4290
4290
|
Ajv2.MissingRefError = ref_error_1.default;
|
|
4291
4291
|
exports.default = Ajv2;
|
|
4292
|
-
function checkOptions(checkOpts, options, msg,
|
|
4292
|
+
function checkOptions(checkOpts, options, msg, log2 = "error") {
|
|
4293
4293
|
for (const key in checkOpts) {
|
|
4294
4294
|
const opt = key;
|
|
4295
4295
|
if (opt in options)
|
|
4296
|
-
this.logger[
|
|
4296
|
+
this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
|
|
4297
4297
|
}
|
|
4298
4298
|
}
|
|
4299
4299
|
function getSchEnv(keyRef) {
|
|
@@ -10187,11 +10187,11 @@ var require_core3 = __commonJS({
|
|
|
10187
10187
|
Ajv2.ValidationError = validation_error_1.default;
|
|
10188
10188
|
Ajv2.MissingRefError = ref_error_1.default;
|
|
10189
10189
|
exports.default = Ajv2;
|
|
10190
|
-
function checkOptions(checkOpts, options, msg,
|
|
10190
|
+
function checkOptions(checkOpts, options, msg, log2 = "error") {
|
|
10191
10191
|
for (const key in checkOpts) {
|
|
10192
10192
|
const opt = key;
|
|
10193
10193
|
if (opt in options)
|
|
10194
|
-
this.logger[
|
|
10194
|
+
this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
|
|
10195
10195
|
}
|
|
10196
10196
|
}
|
|
10197
10197
|
function getSchEnv(keyRef) {
|
|
@@ -31567,8 +31567,8 @@ function namespaceToPath(namespace) {
|
|
|
31567
31567
|
return namespace.join("");
|
|
31568
31568
|
}
|
|
31569
31569
|
var NamespaceOperations = class {
|
|
31570
|
-
constructor(
|
|
31571
|
-
this.client =
|
|
31570
|
+
constructor(client2, prefix = "") {
|
|
31571
|
+
this.client = client2;
|
|
31572
31572
|
this.prefix = prefix;
|
|
31573
31573
|
}
|
|
31574
31574
|
async listNamespaces(parent) {
|
|
@@ -31636,8 +31636,8 @@ function namespaceToPath2(namespace) {
|
|
|
31636
31636
|
return namespace.join("");
|
|
31637
31637
|
}
|
|
31638
31638
|
var TableOperations = class {
|
|
31639
|
-
constructor(
|
|
31640
|
-
this.client =
|
|
31639
|
+
constructor(client2, prefix = "", accessDelegation) {
|
|
31640
|
+
this.client = client2;
|
|
31641
31641
|
this.prefix = prefix;
|
|
31642
31642
|
this.accessDelegation = accessDelegation;
|
|
31643
31643
|
}
|
|
@@ -35490,8 +35490,8 @@ var GoTrueAdminApi = class {
|
|
|
35490
35490
|
return await _request(this.fetch, "POST", `${this.url}/admin/oauth/clients`, {
|
|
35491
35491
|
body: params,
|
|
35492
35492
|
headers: this.headers,
|
|
35493
|
-
xform: (
|
|
35494
|
-
return { data:
|
|
35493
|
+
xform: (client2) => {
|
|
35494
|
+
return { data: client2, error: null };
|
|
35495
35495
|
}
|
|
35496
35496
|
});
|
|
35497
35497
|
} catch (error2) {
|
|
@@ -35511,8 +35511,8 @@ var GoTrueAdminApi = class {
|
|
|
35511
35511
|
try {
|
|
35512
35512
|
return await _request(this.fetch, "GET", `${this.url}/admin/oauth/clients/${clientId}`, {
|
|
35513
35513
|
headers: this.headers,
|
|
35514
|
-
xform: (
|
|
35515
|
-
return { data:
|
|
35514
|
+
xform: (client2) => {
|
|
35515
|
+
return { data: client2, error: null };
|
|
35516
35516
|
}
|
|
35517
35517
|
});
|
|
35518
35518
|
} catch (error2) {
|
|
@@ -35533,8 +35533,8 @@ var GoTrueAdminApi = class {
|
|
|
35533
35533
|
return await _request(this.fetch, "PUT", `${this.url}/admin/oauth/clients/${clientId}`, {
|
|
35534
35534
|
body: params,
|
|
35535
35535
|
headers: this.headers,
|
|
35536
|
-
xform: (
|
|
35537
|
-
return { data:
|
|
35536
|
+
xform: (client2) => {
|
|
35537
|
+
return { data: client2, error: null };
|
|
35538
35538
|
}
|
|
35539
35539
|
});
|
|
35540
35540
|
} catch (error2) {
|
|
@@ -35574,8 +35574,8 @@ var GoTrueAdminApi = class {
|
|
|
35574
35574
|
try {
|
|
35575
35575
|
return await _request(this.fetch, "POST", `${this.url}/admin/oauth/clients/${clientId}/regenerate_secret`, {
|
|
35576
35576
|
headers: this.headers,
|
|
35577
|
-
xform: (
|
|
35578
|
-
return { data:
|
|
35577
|
+
xform: (client2) => {
|
|
35578
|
+
return { data: client2, error: null };
|
|
35579
35579
|
}
|
|
35580
35580
|
});
|
|
35581
35581
|
} catch (error2) {
|
|
@@ -36335,8 +36335,8 @@ function mergeCredentialRequestOptions(baseOptions, overrides) {
|
|
|
36335
36335
|
return deepMerge(DEFAULT_REQUEST_OPTIONS, baseOptions, overrides || {});
|
|
36336
36336
|
}
|
|
36337
36337
|
var WebAuthnApi = class {
|
|
36338
|
-
constructor(
|
|
36339
|
-
this.client =
|
|
36338
|
+
constructor(client2) {
|
|
36339
|
+
this.client = client2;
|
|
36340
36340
|
this.enroll = this._enroll.bind(this);
|
|
36341
36341
|
this.challenge = this._challenge.bind(this);
|
|
36342
36342
|
this.verify = this._verify.bind(this);
|
|
@@ -39920,7 +39920,9 @@ async function handlePods(params) {
|
|
|
39920
39920
|
if (params.name !== void 0) updates.name = params.name.trim();
|
|
39921
39921
|
if (params.color !== void 0) updates.color = params.color;
|
|
39922
39922
|
if (params.isActive !== void 0) updates.is_active = params.isActive;
|
|
39923
|
-
if (params.slackChannelId !== void 0)
|
|
39923
|
+
if (params.slackChannelId !== void 0) {
|
|
39924
|
+
updates.slack_channel_ids = params.slackChannelId ? [params.slackChannelId] : [];
|
|
39925
|
+
}
|
|
39924
39926
|
const { data, error: error2 } = await supabase.from("pods").update(updates).eq("id", podId).select().single();
|
|
39925
39927
|
if (error2) return err(error2.message);
|
|
39926
39928
|
return ok(data, `Updated pod: ${data?.name}`);
|
|
@@ -41863,461 +41865,6 @@ async function handleFeatures(params) {
|
|
|
41863
41865
|
}
|
|
41864
41866
|
}
|
|
41865
41867
|
|
|
41866
|
-
// tools/workstreams.ts
|
|
41867
|
-
async function handleWorkstreams(params) {
|
|
41868
|
-
const supabase = getAdminClient();
|
|
41869
|
-
const action = params.action;
|
|
41870
|
-
switch (action) {
|
|
41871
|
-
case "list": {
|
|
41872
|
-
let query = supabase.from("projects").select("*, project_pods(pod_id, is_primary)").order("name");
|
|
41873
|
-
if (params.status) query = query.eq("status", params.status);
|
|
41874
|
-
else query = query.eq("status", "active");
|
|
41875
|
-
const { data, error: error2 } = await query;
|
|
41876
|
-
if (error2) return err(error2.message);
|
|
41877
|
-
let results = data || [];
|
|
41878
|
-
if (params.podId) {
|
|
41879
|
-
results = results.filter((p) => {
|
|
41880
|
-
const pp = p.project_pods || [];
|
|
41881
|
-
return pp.some((r) => r.pod_id === params.podId) || p.pod_id === params.podId;
|
|
41882
|
-
});
|
|
41883
|
-
}
|
|
41884
|
-
return ok(results, `${results.length} workstreams`, results.length);
|
|
41885
|
-
}
|
|
41886
|
-
case "get": {
|
|
41887
|
-
const slug = params.slug;
|
|
41888
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41889
|
-
const { data, error: error2 } = await supabase.from("projects").select("*, project_pods(pod_id, is_primary)").eq("slug", slug).single();
|
|
41890
|
-
if (error2 || !data) return err("Workstream not found", "NOT_FOUND");
|
|
41891
|
-
return ok(data, `Workstream: ${data.name}`);
|
|
41892
|
-
}
|
|
41893
|
-
case "create": {
|
|
41894
|
-
const name = params.name;
|
|
41895
|
-
const podId = params.podId;
|
|
41896
|
-
const mode = params.mode;
|
|
41897
|
-
if (!name?.trim()) return err("name is required", "MISSING_PARAM");
|
|
41898
|
-
const trimmedName = name.trim();
|
|
41899
|
-
const { data: existing } = await supabase.from("projects").select("id, name, slug, project_pods(pod_id)").eq("name", trimmedName).maybeSingle();
|
|
41900
|
-
if (existing && !mode) {
|
|
41901
|
-
const existingPodIds = existing.project_pods?.map((r) => r.pod_id) || [];
|
|
41902
|
-
if (podId && existingPodIds.includes(podId)) {
|
|
41903
|
-
return ok(existing, `Workstream "${trimmedName}" already linked to this pod`);
|
|
41904
|
-
}
|
|
41905
|
-
return ok(
|
|
41906
|
-
{ conflict: true, existingProject: { id: existing.id, name: existing.name, slug: existing.slug, podIds: existingPodIds } },
|
|
41907
|
-
`Workstream "${trimmedName}" already exists in another pod. Use mode: "link" to share or mode: "create_new" to create a separate one.`
|
|
41908
|
-
);
|
|
41909
|
-
}
|
|
41910
|
-
if (existing && mode === "link") {
|
|
41911
|
-
if (!podId) return err("podId is required to link", "MISSING_PARAM");
|
|
41912
|
-
await supabase.from("project_pods").insert({ project_id: existing.id, pod_id: podId, is_primary: false });
|
|
41913
|
-
return ok(existing, `Linked "${trimmedName}" to pod`);
|
|
41914
|
-
}
|
|
41915
|
-
if (existing && mode === "create_new") {
|
|
41916
|
-
const { data: pod } = podId ? await supabase.from("pods").select("name").eq("id", podId).single() : { data: null };
|
|
41917
|
-
const suffix = pod?.name || podId || "";
|
|
41918
|
-
const newName = suffix ? `${trimmedName} (${suffix})` : trimmedName;
|
|
41919
|
-
const newSlug = newName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
41920
|
-
const { data: data2, error: error3 } = await supabase.from("projects").insert({
|
|
41921
|
-
name: newName,
|
|
41922
|
-
slug: newSlug,
|
|
41923
|
-
description: params.description || null,
|
|
41924
|
-
pod_id: podId || null,
|
|
41925
|
-
color: params.color || "#71717a",
|
|
41926
|
-
status: "active"
|
|
41927
|
-
}).select().single();
|
|
41928
|
-
if (error3) return err(error3.message);
|
|
41929
|
-
if (podId) await supabase.from("project_pods").insert({ project_id: data2.id, pod_id: podId, is_primary: true });
|
|
41930
|
-
return ok(data2, `Created separate workstream: ${newName}`);
|
|
41931
|
-
}
|
|
41932
|
-
const slug = trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
41933
|
-
const { data, error: error2 } = await supabase.from("projects").insert({
|
|
41934
|
-
name: trimmedName,
|
|
41935
|
-
slug,
|
|
41936
|
-
description: params.description || null,
|
|
41937
|
-
pod_id: podId || null,
|
|
41938
|
-
color: params.color || "#71717a",
|
|
41939
|
-
status: "active"
|
|
41940
|
-
}).select().single();
|
|
41941
|
-
if (error2) return err(error2.message);
|
|
41942
|
-
if (podId) await supabase.from("project_pods").insert({ project_id: data.id, pod_id: podId, is_primary: true });
|
|
41943
|
-
return ok(data, `Created workstream: ${trimmedName}`);
|
|
41944
|
-
}
|
|
41945
|
-
case "link": {
|
|
41946
|
-
const slug = params.slug;
|
|
41947
|
-
const podId = params.podId;
|
|
41948
|
-
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
41949
|
-
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
41950
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41951
|
-
const { error: error2 } = await supabase.from("project_pods").insert({
|
|
41952
|
-
project_id: project.id,
|
|
41953
|
-
pod_id: podId,
|
|
41954
|
-
is_primary: false
|
|
41955
|
-
});
|
|
41956
|
-
if (error2) return err(error2.message);
|
|
41957
|
-
return ok({ projectId: project.id, podId }, `Linked "${project.name}" to pod`);
|
|
41958
|
-
}
|
|
41959
|
-
case "unlink": {
|
|
41960
|
-
const slug = params.slug;
|
|
41961
|
-
const podId = params.podId;
|
|
41962
|
-
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
41963
|
-
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
41964
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41965
|
-
const { error: error2 } = await supabase.from("project_pods").delete().eq("project_id", project.id).eq("pod_id", podId);
|
|
41966
|
-
if (error2) return err(error2.message);
|
|
41967
|
-
return ok({ projectId: project.id, podId }, `Unlinked "${project.name}" from pod`);
|
|
41968
|
-
}
|
|
41969
|
-
case "update": {
|
|
41970
|
-
const slug = params.slug;
|
|
41971
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41972
|
-
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
41973
|
-
if (params.name !== void 0) updates.name = params.name;
|
|
41974
|
-
if (params.description !== void 0) updates.description = params.description;
|
|
41975
|
-
if (params.status !== void 0) updates.status = params.status;
|
|
41976
|
-
const { data, error: error2 } = await supabase.from("projects").update(updates).eq("slug", slug).select().single();
|
|
41977
|
-
if (error2) return err(error2.message);
|
|
41978
|
-
return ok(data, `Updated workstream: ${data?.name}`);
|
|
41979
|
-
}
|
|
41980
|
-
case "delete": {
|
|
41981
|
-
const slug = params.slug;
|
|
41982
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41983
|
-
const { error: error2 } = await supabase.from("projects").delete().eq("slug", slug);
|
|
41984
|
-
if (error2) return err(error2.message);
|
|
41985
|
-
return ok({ deleted: slug }, "Workstream deleted");
|
|
41986
|
-
}
|
|
41987
|
-
case "get_suggestions": {
|
|
41988
|
-
const slug = params.slug;
|
|
41989
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41990
|
-
const { data: project } = await supabase.from("projects").select("name").eq("slug", slug).single();
|
|
41991
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41992
|
-
const { data } = await supabase.from("ai_suggestions").select("*").eq("suggestion_type", "proactive").like("payload->>projectName", project.name).eq("status", "pending").order("created_at", { ascending: false }).limit(5);
|
|
41993
|
-
return ok(data || [], `${(data || []).length} suggestions for ${project.name}`, (data || []).length);
|
|
41994
|
-
}
|
|
41995
|
-
default:
|
|
41996
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
41997
|
-
}
|
|
41998
|
-
}
|
|
41999
|
-
|
|
42000
|
-
// tools/requests.ts
|
|
42001
|
-
function getCurrentWeekStart() {
|
|
42002
|
-
return format(startOfWeek(/* @__PURE__ */ new Date(), { weekStartsOn: 1 }), "yyyy-MM-dd");
|
|
42003
|
-
}
|
|
42004
|
-
async function handleRequests(params) {
|
|
42005
|
-
const supabase = getAdminClient();
|
|
42006
|
-
const action = params.action;
|
|
42007
|
-
switch (action) {
|
|
42008
|
-
case "list": {
|
|
42009
|
-
const podId = params.podId;
|
|
42010
|
-
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
42011
|
-
let query = supabase.from("work_items").select("*").eq("pod_id", podId);
|
|
42012
|
-
if (params.type) query = query.eq("type", params.type);
|
|
42013
|
-
if (params.status) query = query.eq("status", params.status);
|
|
42014
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
42015
|
-
const { data, error: error2 } = await query;
|
|
42016
|
-
if (error2) return err(error2.message);
|
|
42017
|
-
return ok(data, `${(data || []).length} requests`, (data || []).length);
|
|
42018
|
-
}
|
|
42019
|
-
case "create": {
|
|
42020
|
-
const podId = params.podId;
|
|
42021
|
-
const title = params.title;
|
|
42022
|
-
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
42023
|
-
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
42024
|
-
id: `f-${Date.now().toString(36)}`,
|
|
42025
|
-
title: title.trim(),
|
|
42026
|
-
description: params.description || null,
|
|
42027
|
-
pod_id: podId,
|
|
42028
|
-
project_name: params.projectName || null,
|
|
42029
|
-
requester_name: params.requesterName || null,
|
|
42030
|
-
source: params.source || "mcp",
|
|
42031
|
-
type: params.requestType || "feature_request",
|
|
42032
|
-
priority: params.priority || null,
|
|
42033
|
-
sentiment: params.sentiment || null,
|
|
42034
|
-
related_feature_title: params.relatedFeatureTitle || null
|
|
42035
|
-
}).select().single();
|
|
42036
|
-
if (error2) return err(error2.message);
|
|
42037
|
-
return ok(data, `Created ${params.requestType || "feature_request"}: ${title}`);
|
|
42038
|
-
}
|
|
42039
|
-
case "triage": {
|
|
42040
|
-
const requestId = params.requestId;
|
|
42041
|
-
const triageAction = params.triageAction;
|
|
42042
|
-
const podId = params.podId;
|
|
42043
|
-
if (!requestId || !triageAction) return err("requestId and triageAction are required", "MISSING_PARAM");
|
|
42044
|
-
if (triageAction === "accept") {
|
|
42045
|
-
const featureId = `f-${Date.now().toString(36)}`;
|
|
42046
|
-
const { data: req } = await supabase.from("work_items").select("title, pod_id").eq("id", requestId).single();
|
|
42047
|
-
if (!req) return err("Request not found", "NOT_FOUND");
|
|
42048
|
-
await supabase.from("work_items").insert({
|
|
42049
|
-
id: featureId,
|
|
42050
|
-
title: req.title,
|
|
42051
|
-
pod_id: req.pod_id,
|
|
42052
|
-
project_name: params.projectName || "",
|
|
42053
|
-
project_color: "#71717a",
|
|
42054
|
-
phase: params.phase || "discovery",
|
|
42055
|
-
track: "dev",
|
|
42056
|
-
status: "in_progress",
|
|
42057
|
-
has_blocker: false,
|
|
42058
|
-
size: "feature",
|
|
42059
|
-
week_start: params.weekStart || null
|
|
42060
|
-
});
|
|
42061
|
-
await supabase.from("work_items").update({
|
|
42062
|
-
status: "accepted",
|
|
42063
|
-
accepted_feature_id: featureId,
|
|
42064
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42065
|
-
}).eq("id", requestId);
|
|
42066
|
-
return ok({ requestId, featureId, action: "accepted" }, `Accepted request \u2192 feature created`);
|
|
42067
|
-
}
|
|
42068
|
-
const statusMap = {
|
|
42069
|
-
decline: "declined",
|
|
42070
|
-
defer: "deferred",
|
|
42071
|
-
acknowledge: "acknowledged",
|
|
42072
|
-
convert: "open"
|
|
42073
|
-
};
|
|
42074
|
-
const newStatus = statusMap[triageAction];
|
|
42075
|
-
if (!newStatus) return err(`Unknown triage action: ${triageAction}`, "INVALID_ACTION");
|
|
42076
|
-
const updates = { status: newStatus, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42077
|
-
if (triageAction === "convert") updates.type = "feature_request";
|
|
42078
|
-
await supabase.from("work_items").update(updates).eq("id", requestId);
|
|
42079
|
-
return ok({ requestId, action: triageAction }, `Request ${triageAction}d`);
|
|
42080
|
-
}
|
|
42081
|
-
default:
|
|
42082
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42083
|
-
}
|
|
42084
|
-
}
|
|
42085
|
-
async function handleBugs(params) {
|
|
42086
|
-
const supabase = getAdminClient();
|
|
42087
|
-
const action = params.action;
|
|
42088
|
-
switch (action) {
|
|
42089
|
-
case "list": {
|
|
42090
|
-
let query = supabase.from("work_items").select("*").eq("type", "bug");
|
|
42091
|
-
if (params.podId) query = query.eq("pod_id", params.podId);
|
|
42092
|
-
if (params.status) query = query.eq("status", params.status);
|
|
42093
|
-
else query = query.in("status", ["open", "in_progress"]);
|
|
42094
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
42095
|
-
const { data, error: error2 } = await query;
|
|
42096
|
-
if (error2) return err(error2.message);
|
|
42097
|
-
return ok(data, `${(data || []).length} bugs`, (data || []).length);
|
|
42098
|
-
}
|
|
42099
|
-
case "create": {
|
|
42100
|
-
const podId = params.podId;
|
|
42101
|
-
const title = params.title;
|
|
42102
|
-
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
42103
|
-
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
42104
|
-
id: `f-${Date.now().toString(36)}`,
|
|
42105
|
-
title: title.trim(),
|
|
42106
|
-
description: params.description || null,
|
|
42107
|
-
pod_id: podId,
|
|
42108
|
-
project_name: params.projectName || null,
|
|
42109
|
-
source: params.source || "mcp",
|
|
42110
|
-
type: "bug",
|
|
42111
|
-
priority: params.priority || "medium",
|
|
42112
|
-
status: "open"
|
|
42113
|
-
}).select().single();
|
|
42114
|
-
if (error2) return err(error2.message);
|
|
42115
|
-
return ok(data, `Created bug: ${title}`);
|
|
42116
|
-
}
|
|
42117
|
-
case "update": {
|
|
42118
|
-
const bugId = params.bugId;
|
|
42119
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42120
|
-
const { data: currentBug } = await supabase.from("work_items").select("status, phase, track, implementation_step, has_blocker, week_start, title, pod_id").eq("id", bugId).single();
|
|
42121
|
-
const raw = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42122
|
-
if (params.title !== void 0) raw.title = params.title;
|
|
42123
|
-
if (params.description !== void 0) raw.description = params.description;
|
|
42124
|
-
if (params.priority !== void 0) raw.priority = params.priority;
|
|
42125
|
-
if (params.status !== void 0) raw.status = params.status;
|
|
42126
|
-
if (params.podId !== void 0) raw.pod_id = params.podId;
|
|
42127
|
-
if (params.projectName !== void 0) raw.project_name = params.projectName;
|
|
42128
|
-
let autoPromoted = false;
|
|
42129
|
-
if (currentBug?.status === "open" && !currentBug.week_start && !params.status) {
|
|
42130
|
-
raw.status = "in_progress";
|
|
42131
|
-
raw.week_start = getCurrentWeekStart();
|
|
42132
|
-
autoPromoted = true;
|
|
42133
|
-
}
|
|
42134
|
-
const updates = normalizeWorkItemState(raw, currentBug || void 0);
|
|
42135
|
-
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
42136
|
-
if (error2) return err(error2.message);
|
|
42137
|
-
if (autoPromoted) {
|
|
42138
|
-
await supabase.from("events").insert({
|
|
42139
|
-
source: "mcp",
|
|
42140
|
-
event_type: "bug_promoted",
|
|
42141
|
-
raw_payload: { bugId, trigger: "update" },
|
|
42142
|
-
ai_extraction: { bugTitle: currentBug.title, podId: currentBug.pod_id, weekStart: updates.week_start },
|
|
42143
|
-
processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42144
|
-
});
|
|
42145
|
-
}
|
|
42146
|
-
return ok({ id: bugId, autoPromoted }, autoPromoted ? "Bug updated \u2014 auto-scheduled to this week" : "Bug updated");
|
|
42147
|
-
}
|
|
42148
|
-
case "delete": {
|
|
42149
|
-
const bugId = params.bugId;
|
|
42150
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42151
|
-
const { error: error2 } = await supabase.from("work_items").delete().eq("id", bugId);
|
|
42152
|
-
if (error2) return err(error2.message);
|
|
42153
|
-
return ok({ deleted: bugId }, "Bug deleted");
|
|
42154
|
-
}
|
|
42155
|
-
case "schedule": {
|
|
42156
|
-
const bugId = params.bugId;
|
|
42157
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42158
|
-
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42159
|
-
if (params.weekStart !== void 0) updates.week_start = params.weekStart;
|
|
42160
|
-
if (params.sortOrder !== void 0) updates.sort_order = params.sortOrder;
|
|
42161
|
-
if (params.weekStart) {
|
|
42162
|
-
const { data: bug } = await supabase.from("work_items").select("status").eq("id", bugId).single();
|
|
42163
|
-
if (bug?.status === "open") updates.status = "in_progress";
|
|
42164
|
-
}
|
|
42165
|
-
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
42166
|
-
if (error2) return err(error2.message);
|
|
42167
|
-
return ok({ id: bugId, ...updates }, `Bug scheduled${params.weekStart ? ` to week of ${params.weekStart}` : ""}`);
|
|
42168
|
-
}
|
|
42169
|
-
default:
|
|
42170
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42171
|
-
}
|
|
42172
|
-
}
|
|
42173
|
-
|
|
42174
|
-
// tools/suggestions.ts
|
|
42175
|
-
async function handleSuggestions(params) {
|
|
42176
|
-
const supabase = getAdminClient();
|
|
42177
|
-
const action = params.action;
|
|
42178
|
-
switch (action) {
|
|
42179
|
-
case "list": {
|
|
42180
|
-
const { data, error: error2 } = await supabase.from("ai_suggestions").select("*").eq("status", "pending").order("created_at", { ascending: false }).limit(params.limit || 20);
|
|
42181
|
-
if (error2) return err(error2.message);
|
|
42182
|
-
return ok(data, `${(data || []).length} pending suggestions`, (data || []).length);
|
|
42183
|
-
}
|
|
42184
|
-
case "approve": {
|
|
42185
|
-
const suggestionId = params.suggestionId;
|
|
42186
|
-
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
42187
|
-
const { data: suggestion } = await supabase.from("ai_suggestions").select("*").eq("id", suggestionId).single();
|
|
42188
|
-
if (!suggestion) return err("Suggestion not found", "NOT_FOUND");
|
|
42189
|
-
await supabase.from("ai_suggestions").update({
|
|
42190
|
-
status: "approved",
|
|
42191
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42192
|
-
}).eq("id", suggestionId);
|
|
42193
|
-
return ok({ id: suggestionId, status: "approved" }, `Suggestion approved: ${suggestion.title}`);
|
|
42194
|
-
}
|
|
42195
|
-
case "reject": {
|
|
42196
|
-
const suggestionId = params.suggestionId;
|
|
42197
|
-
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
42198
|
-
await supabase.from("ai_suggestions").update({
|
|
42199
|
-
status: "rejected",
|
|
42200
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42201
|
-
}).eq("id", suggestionId);
|
|
42202
|
-
return ok({ id: suggestionId, status: "rejected" }, "Suggestion rejected");
|
|
42203
|
-
}
|
|
42204
|
-
case "correct": {
|
|
42205
|
-
const suggestionId = params.suggestionId;
|
|
42206
|
-
const correction = params.correction;
|
|
42207
|
-
if (!suggestionId || !correction?.trim()) return err("suggestionId and correction are required", "MISSING_PARAM");
|
|
42208
|
-
await supabase.from("ai_suggestions").update({
|
|
42209
|
-
status: "corrected",
|
|
42210
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42211
|
-
}).eq("id", suggestionId);
|
|
42212
|
-
return ok({ id: suggestionId, correction: correction.trim() }, "Suggestion corrected");
|
|
42213
|
-
}
|
|
42214
|
-
default:
|
|
42215
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42216
|
-
}
|
|
42217
|
-
}
|
|
42218
|
-
|
|
42219
|
-
// tools/events.ts
|
|
42220
|
-
async function handleEvents(params) {
|
|
42221
|
-
const supabase = getAdminClient();
|
|
42222
|
-
const action = params.action;
|
|
42223
|
-
switch (action) {
|
|
42224
|
-
case "list": {
|
|
42225
|
-
const { data, error: error2 } = await supabase.from("events").select("id, source, event_type, ai_extraction, created_at").order("created_at", { ascending: false }).limit(params.limit || 20);
|
|
42226
|
-
if (error2) return err(error2.message);
|
|
42227
|
-
return ok(data, `${(data || []).length} events`, (data || []).length);
|
|
42228
|
-
}
|
|
42229
|
-
case "list_by_feature": {
|
|
42230
|
-
const featureTitle = params.featureTitle;
|
|
42231
|
-
const podId = params.podId;
|
|
42232
|
-
if (!featureTitle) return err("featureTitle is required", "MISSING_PARAM");
|
|
42233
|
-
let query = supabase.from("events").select("id, source, event_type, ai_extraction, created_at").filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
42234
|
-
if (podId) query = query.filter("ai_extraction->>podId", "eq", podId);
|
|
42235
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 10);
|
|
42236
|
-
const { data, error: error2 } = await query;
|
|
42237
|
-
if (error2) return err(error2.message);
|
|
42238
|
-
return ok(data, `${(data || []).length} events for "${featureTitle}"`, (data || []).length);
|
|
42239
|
-
}
|
|
42240
|
-
case "list_by_pod": {
|
|
42241
|
-
const podId = params.podId;
|
|
42242
|
-
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
42243
|
-
const { data, error: error2 } = await supabase.from("events").select("id, source, event_type, ai_extraction, created_at").filter("ai_extraction->>podId", "eq", podId).order("created_at", { ascending: false }).limit(params.limit || 10);
|
|
42244
|
-
if (error2) return err(error2.message);
|
|
42245
|
-
return ok(data, `${(data || []).length} events for pod ${podId}`, (data || []).length);
|
|
42246
|
-
}
|
|
42247
|
-
case "list_prs": {
|
|
42248
|
-
const featureTitle = params.featureTitle;
|
|
42249
|
-
const shortCode = params.shortCode;
|
|
42250
|
-
if (!featureTitle && !shortCode) return err("featureTitle or shortCode is required", "MISSING_PARAM");
|
|
42251
|
-
let query = supabase.from("events").select("event_type, raw_payload, ai_extraction, created_at").eq("source", "github").in("event_type", ["pr_opened", "pr_merged"]);
|
|
42252
|
-
if (shortCode) {
|
|
42253
|
-
query = query.filter("ai_extraction->>linked_short_code", "eq", shortCode);
|
|
42254
|
-
} else {
|
|
42255
|
-
query = query.filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
42256
|
-
}
|
|
42257
|
-
query = query.order("created_at", { ascending: false }).limit(50);
|
|
42258
|
-
const { data, error: error2 } = await query;
|
|
42259
|
-
if (error2) return err(error2.message);
|
|
42260
|
-
const opened = /* @__PURE__ */ new Set();
|
|
42261
|
-
const merged = /* @__PURE__ */ new Set();
|
|
42262
|
-
const prs = [];
|
|
42263
|
-
for (const ev of data || []) {
|
|
42264
|
-
const prNum = ev.raw_payload?.pr_number;
|
|
42265
|
-
if (prNum == null) continue;
|
|
42266
|
-
if (ev.event_type === "pr_opened") opened.add(prNum);
|
|
42267
|
-
if (ev.event_type === "pr_merged") merged.add(prNum);
|
|
42268
|
-
}
|
|
42269
|
-
const seen = /* @__PURE__ */ new Set();
|
|
42270
|
-
for (const ev of data || []) {
|
|
42271
|
-
const prNum = ev.raw_payload?.pr_number;
|
|
42272
|
-
if (prNum == null || seen.has(prNum)) continue;
|
|
42273
|
-
seen.add(prNum);
|
|
42274
|
-
prs.push({
|
|
42275
|
-
number: prNum,
|
|
42276
|
-
title: ev.raw_payload?.pr_title || ev.ai_extraction?.whatWasBuilt || "",
|
|
42277
|
-
status: merged.has(prNum) ? "merged" : "open",
|
|
42278
|
-
repo: ev.ai_extraction?.repo || "",
|
|
42279
|
-
date: ev.created_at
|
|
42280
|
-
});
|
|
42281
|
-
}
|
|
42282
|
-
return ok(
|
|
42283
|
-
{ prs, totalOpened: opened.size, totalMerged: merged.size, unmerged: opened.size - merged.size },
|
|
42284
|
-
`${prs.length} PR(s): ${merged.size} merged, ${opened.size - merged.size} open`,
|
|
42285
|
-
prs.length
|
|
42286
|
-
);
|
|
42287
|
-
}
|
|
42288
|
-
default:
|
|
42289
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42290
|
-
}
|
|
42291
|
-
}
|
|
42292
|
-
async function handleHistory(params) {
|
|
42293
|
-
const supabase = getAdminClient();
|
|
42294
|
-
const days = params.days || 90;
|
|
42295
|
-
const limit = params.limit || 50;
|
|
42296
|
-
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
42297
|
-
const items = [];
|
|
42298
|
-
if (!params.type || params.type === "feature") {
|
|
42299
|
-
let q = supabase.from("work_items").select("id, title, status, pod_id, project_name, week_start, updated_at").in("status", ["shipped", "missed", "killed"]).gte("updated_at", cutoff).order("updated_at", { ascending: false }).limit(limit);
|
|
42300
|
-
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
42301
|
-
if (params.projectName) q = q.eq("project_name", params.projectName);
|
|
42302
|
-
const { data } = await q;
|
|
42303
|
-
for (const f of data || []) {
|
|
42304
|
-
items.push({ id: f.id, itemType: "feature", title: f.title, status: f.status, podId: f.pod_id, projectName: f.project_name, date: f.updated_at });
|
|
42305
|
-
}
|
|
42306
|
-
}
|
|
42307
|
-
if (!params.type || ["bug", "request", "feedback"].includes(params.type)) {
|
|
42308
|
-
let q = supabase.from("work_items").select("id, title, type, status, pod_id, project_name, source, updated_at, created_at").not("status", "eq", "open").gte("created_at", cutoff).order("created_at", { ascending: false }).limit(limit);
|
|
42309
|
-
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
42310
|
-
const { data } = await q;
|
|
42311
|
-
for (const r of data || []) {
|
|
42312
|
-
const t = r.type === "feature_request" ? "request" : r.type === "bug" ? "bug" : "feedback";
|
|
42313
|
-
if (params.type && params.type !== t) continue;
|
|
42314
|
-
items.push({ id: r.id, itemType: t, title: r.title, status: r.status || "open", podId: r.pod_id, projectName: r.project_name, source: r.source, date: r.updated_at || r.created_at });
|
|
42315
|
-
}
|
|
42316
|
-
}
|
|
42317
|
-
items.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
|
42318
|
-
return ok(items.slice(0, limit), `${items.length} history items`, items.length);
|
|
42319
|
-
}
|
|
42320
|
-
|
|
42321
41868
|
// ../../node_modules/@anthropic-ai/sdk/internal/tslib.mjs
|
|
42322
41869
|
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
42323
41870
|
if (kind === "m")
|
|
@@ -42841,14 +42388,14 @@ var levelNumbers = {
|
|
|
42841
42388
|
info: 400,
|
|
42842
42389
|
debug: 500
|
|
42843
42390
|
};
|
|
42844
|
-
var parseLogLevel = (maybeLevel, sourceName,
|
|
42391
|
+
var parseLogLevel = (maybeLevel, sourceName, client2) => {
|
|
42845
42392
|
if (!maybeLevel) {
|
|
42846
42393
|
return void 0;
|
|
42847
42394
|
}
|
|
42848
42395
|
if (hasOwn(levelNumbers, maybeLevel)) {
|
|
42849
42396
|
return maybeLevel;
|
|
42850
42397
|
}
|
|
42851
|
-
loggerFor(
|
|
42398
|
+
loggerFor(client2).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
|
|
42852
42399
|
return void 0;
|
|
42853
42400
|
};
|
|
42854
42401
|
function noop3() {
|
|
@@ -42867,9 +42414,9 @@ var noopLogger = {
|
|
|
42867
42414
|
debug: noop3
|
|
42868
42415
|
};
|
|
42869
42416
|
var cachedLoggers = /* @__PURE__ */ new WeakMap();
|
|
42870
|
-
function loggerFor(
|
|
42871
|
-
const logger =
|
|
42872
|
-
const logLevel =
|
|
42417
|
+
function loggerFor(client2) {
|
|
42418
|
+
const logger = client2.logger;
|
|
42419
|
+
const logLevel = client2.logLevel ?? "off";
|
|
42873
42420
|
if (!logger) {
|
|
42874
42421
|
return noopLogger;
|
|
42875
42422
|
}
|
|
@@ -42909,15 +42456,15 @@ var formatRequestDetails = (details) => {
|
|
|
42909
42456
|
// ../../node_modules/@anthropic-ai/sdk/core/streaming.mjs
|
|
42910
42457
|
var _Stream_client;
|
|
42911
42458
|
var Stream = class _Stream {
|
|
42912
|
-
constructor(iterator, controller,
|
|
42459
|
+
constructor(iterator, controller, client2) {
|
|
42913
42460
|
this.iterator = iterator;
|
|
42914
42461
|
_Stream_client.set(this, void 0);
|
|
42915
42462
|
this.controller = controller;
|
|
42916
|
-
__classPrivateFieldSet(this, _Stream_client,
|
|
42463
|
+
__classPrivateFieldSet(this, _Stream_client, client2, "f");
|
|
42917
42464
|
}
|
|
42918
|
-
static fromSSEResponse(response, controller,
|
|
42465
|
+
static fromSSEResponse(response, controller, client2) {
|
|
42919
42466
|
let consumed = false;
|
|
42920
|
-
const logger =
|
|
42467
|
+
const logger = client2 ? loggerFor(client2) : console;
|
|
42921
42468
|
async function* iterator() {
|
|
42922
42469
|
if (consumed) {
|
|
42923
42470
|
throw new AnthropicError("Cannot iterate over a consumed stream, use `.tee()` to split the stream.");
|
|
@@ -42961,13 +42508,13 @@ var Stream = class _Stream {
|
|
|
42961
42508
|
controller.abort();
|
|
42962
42509
|
}
|
|
42963
42510
|
}
|
|
42964
|
-
return new _Stream(iterator, controller,
|
|
42511
|
+
return new _Stream(iterator, controller, client2);
|
|
42965
42512
|
}
|
|
42966
42513
|
/**
|
|
42967
42514
|
* Generates a Stream from a newline-separated ReadableStream
|
|
42968
42515
|
* where each item is a JSON value.
|
|
42969
42516
|
*/
|
|
42970
|
-
static fromReadableStream(readableStream, controller,
|
|
42517
|
+
static fromReadableStream(readableStream, controller, client2) {
|
|
42971
42518
|
let consumed = false;
|
|
42972
42519
|
async function* iterLines() {
|
|
42973
42520
|
const lineDecoder = new LineDecoder();
|
|
@@ -43004,7 +42551,7 @@ var Stream = class _Stream {
|
|
|
43004
42551
|
controller.abort();
|
|
43005
42552
|
}
|
|
43006
42553
|
}
|
|
43007
|
-
return new _Stream(iterator, controller,
|
|
42554
|
+
return new _Stream(iterator, controller, client2);
|
|
43008
42555
|
}
|
|
43009
42556
|
[(_Stream_client = /* @__PURE__ */ new WeakMap(), Symbol.asyncIterator)]() {
|
|
43010
42557
|
return this.iterator();
|
|
@@ -43156,11 +42703,11 @@ function partition(str, delimiter) {
|
|
|
43156
42703
|
}
|
|
43157
42704
|
|
|
43158
42705
|
// ../../node_modules/@anthropic-ai/sdk/internal/parse.mjs
|
|
43159
|
-
async function defaultParseResponse(
|
|
42706
|
+
async function defaultParseResponse(client2, props) {
|
|
43160
42707
|
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
|
|
43161
42708
|
const body = await (async () => {
|
|
43162
42709
|
if (props.options.stream) {
|
|
43163
|
-
loggerFor(
|
|
42710
|
+
loggerFor(client2).debug("response", response.status, response.url, response.headers, response.body);
|
|
43164
42711
|
if (props.options.__streamClass) {
|
|
43165
42712
|
return props.options.__streamClass.fromSSEResponse(response, props.controller);
|
|
43166
42713
|
}
|
|
@@ -43186,7 +42733,7 @@ async function defaultParseResponse(client, props) {
|
|
|
43186
42733
|
const text = await response.text();
|
|
43187
42734
|
return text;
|
|
43188
42735
|
})();
|
|
43189
|
-
loggerFor(
|
|
42736
|
+
loggerFor(client2).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
|
|
43190
42737
|
retryOfRequestLogID,
|
|
43191
42738
|
url: response.url,
|
|
43192
42739
|
status: response.status,
|
|
@@ -43208,17 +42755,17 @@ function addRequestID(value, response) {
|
|
|
43208
42755
|
// ../../node_modules/@anthropic-ai/sdk/core/api-promise.mjs
|
|
43209
42756
|
var _APIPromise_client;
|
|
43210
42757
|
var APIPromise = class _APIPromise extends Promise {
|
|
43211
|
-
constructor(
|
|
42758
|
+
constructor(client2, responsePromise, parseResponse = defaultParseResponse) {
|
|
43212
42759
|
super((resolve) => {
|
|
43213
42760
|
resolve(null);
|
|
43214
42761
|
});
|
|
43215
42762
|
this.responsePromise = responsePromise;
|
|
43216
42763
|
this.parseResponse = parseResponse;
|
|
43217
42764
|
_APIPromise_client.set(this, void 0);
|
|
43218
|
-
__classPrivateFieldSet(this, _APIPromise_client,
|
|
42765
|
+
__classPrivateFieldSet(this, _APIPromise_client, client2, "f");
|
|
43219
42766
|
}
|
|
43220
42767
|
_thenUnwrap(transform2) {
|
|
43221
|
-
return new _APIPromise(__classPrivateFieldGet(this, _APIPromise_client, "f"), this.responsePromise, async (
|
|
42768
|
+
return new _APIPromise(__classPrivateFieldGet(this, _APIPromise_client, "f"), this.responsePromise, async (client2, props) => addRequestID(transform2(await this.parseResponse(client2, props), props), props.response));
|
|
43222
42769
|
}
|
|
43223
42770
|
/**
|
|
43224
42771
|
* Gets the raw `Response` instance instead of parsing the response
|
|
@@ -43271,9 +42818,9 @@ _APIPromise_client = /* @__PURE__ */ new WeakMap();
|
|
|
43271
42818
|
// ../../node_modules/@anthropic-ai/sdk/core/pagination.mjs
|
|
43272
42819
|
var _AbstractPage_client;
|
|
43273
42820
|
var AbstractPage = class {
|
|
43274
|
-
constructor(
|
|
42821
|
+
constructor(client2, response, body, options) {
|
|
43275
42822
|
_AbstractPage_client.set(this, void 0);
|
|
43276
|
-
__classPrivateFieldSet(this, _AbstractPage_client,
|
|
42823
|
+
__classPrivateFieldSet(this, _AbstractPage_client, client2, "f");
|
|
43277
42824
|
this.options = options;
|
|
43278
42825
|
this.response = response;
|
|
43279
42826
|
this.body = body;
|
|
@@ -43308,8 +42855,8 @@ var AbstractPage = class {
|
|
|
43308
42855
|
}
|
|
43309
42856
|
};
|
|
43310
42857
|
var PagePromise = class extends APIPromise {
|
|
43311
|
-
constructor(
|
|
43312
|
-
super(
|
|
42858
|
+
constructor(client2, request, Page2) {
|
|
42859
|
+
super(client2, request, async (client3, props) => new Page2(client3, props.response, await defaultParseResponse(client3, props), props.options));
|
|
43313
42860
|
}
|
|
43314
42861
|
/**
|
|
43315
42862
|
* Allow auto-paginating iteration on an unawaited list call, eg:
|
|
@@ -43326,8 +42873,8 @@ var PagePromise = class extends APIPromise {
|
|
|
43326
42873
|
}
|
|
43327
42874
|
};
|
|
43328
42875
|
var Page = class extends AbstractPage {
|
|
43329
|
-
constructor(
|
|
43330
|
-
super(
|
|
42876
|
+
constructor(client2, response, body, options) {
|
|
42877
|
+
super(client2, response, body, options);
|
|
43331
42878
|
this.data = body.data || [];
|
|
43332
42879
|
this.has_more = body.has_more || false;
|
|
43333
42880
|
this.first_id = body.first_id || null;
|
|
@@ -43370,8 +42917,8 @@ var Page = class extends AbstractPage {
|
|
|
43370
42917
|
}
|
|
43371
42918
|
};
|
|
43372
42919
|
var PageCursor = class extends AbstractPage {
|
|
43373
|
-
constructor(
|
|
43374
|
-
super(
|
|
42920
|
+
constructor(client2, response, body, options) {
|
|
42921
|
+
super(client2, response, body, options);
|
|
43375
42922
|
this.data = body.data || [];
|
|
43376
42923
|
this.has_more = body.has_more || false;
|
|
43377
42924
|
this.next_page = body.next_page || null;
|
|
@@ -43536,8 +43083,8 @@ function propsForError(value) {
|
|
|
43536
43083
|
|
|
43537
43084
|
// ../../node_modules/@anthropic-ai/sdk/core/resource.mjs
|
|
43538
43085
|
var APIResource = class {
|
|
43539
|
-
constructor(
|
|
43540
|
-
this._client =
|
|
43086
|
+
constructor(client2) {
|
|
43087
|
+
this._client = client2;
|
|
43541
43088
|
}
|
|
43542
43089
|
};
|
|
43543
43090
|
|
|
@@ -44850,9 +44397,9 @@ function promiseWithResolvers() {
|
|
|
44850
44397
|
return { promise, resolve, reject };
|
|
44851
44398
|
}
|
|
44852
44399
|
var BetaToolRunner = class {
|
|
44853
|
-
constructor(
|
|
44400
|
+
constructor(client2, params, options) {
|
|
44854
44401
|
_BetaToolRunner_instances.add(this);
|
|
44855
|
-
this.client =
|
|
44402
|
+
this.client = client2;
|
|
44856
44403
|
_BetaToolRunner_consumed.set(this, false);
|
|
44857
44404
|
_BetaToolRunner_mutated.set(this, false);
|
|
44858
44405
|
_BetaToolRunner_state.set(this, void 0);
|
|
@@ -46735,7 +46282,7 @@ var BaseAnthropic = class {
|
|
|
46735
46282
|
* Create a new client instance re-using the same options given to the current client with optional overriding.
|
|
46736
46283
|
*/
|
|
46737
46284
|
withOptions(options) {
|
|
46738
|
-
const
|
|
46285
|
+
const client2 = new this.constructor({
|
|
46739
46286
|
...this._options,
|
|
46740
46287
|
baseURL: this.baseURL,
|
|
46741
46288
|
maxRetries: this.maxRetries,
|
|
@@ -46748,7 +46295,7 @@ var BaseAnthropic = class {
|
|
|
46748
46295
|
authToken: this.authToken,
|
|
46749
46296
|
...options
|
|
46750
46297
|
});
|
|
46751
|
-
return
|
|
46298
|
+
return client2;
|
|
46752
46299
|
}
|
|
46753
46300
|
defaultQuery() {
|
|
46754
46301
|
return this._options.defaultQuery;
|
|
@@ -47161,6 +46708,693 @@ Anthropic.Messages = Messages2;
|
|
|
47161
46708
|
Anthropic.Models = Models2;
|
|
47162
46709
|
Anthropic.Beta = Beta;
|
|
47163
46710
|
|
|
46711
|
+
// ../lib/ai/anthropic.ts
|
|
46712
|
+
var client = null;
|
|
46713
|
+
function getAnthropicClient() {
|
|
46714
|
+
if (!client) {
|
|
46715
|
+
client = new Anthropic({
|
|
46716
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
46717
|
+
});
|
|
46718
|
+
}
|
|
46719
|
+
return client;
|
|
46720
|
+
}
|
|
46721
|
+
|
|
46722
|
+
// ../lib/ai/parse-json.ts
|
|
46723
|
+
function parseAIResponse(raw) {
|
|
46724
|
+
try {
|
|
46725
|
+
return JSON.parse(raw);
|
|
46726
|
+
} catch {
|
|
46727
|
+
}
|
|
46728
|
+
const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
46729
|
+
if (fenceMatch) {
|
|
46730
|
+
return JSON.parse(fenceMatch[1].trim());
|
|
46731
|
+
}
|
|
46732
|
+
const objectMatch = raw.match(/(\{[\s\S]*\})/);
|
|
46733
|
+
if (objectMatch) {
|
|
46734
|
+
return JSON.parse(objectMatch[1].trim());
|
|
46735
|
+
}
|
|
46736
|
+
const arrayMatch = raw.match(/(\[[\s\S]*\])/);
|
|
46737
|
+
if (arrayMatch) {
|
|
46738
|
+
return JSON.parse(arrayMatch[1].trim());
|
|
46739
|
+
}
|
|
46740
|
+
throw new SyntaxError(`Could not extract JSON from AI response: ${raw.slice(0, 200)}`);
|
|
46741
|
+
}
|
|
46742
|
+
|
|
46743
|
+
// ../lib/logger.ts
|
|
46744
|
+
var LEVEL_PRIORITY = {
|
|
46745
|
+
debug: 0,
|
|
46746
|
+
info: 1,
|
|
46747
|
+
warn: 2,
|
|
46748
|
+
error: 3
|
|
46749
|
+
};
|
|
46750
|
+
var MIN_LEVEL = process.env.LOG_LEVEL || (process.env.NODE_ENV === "production" ? "info" : "debug");
|
|
46751
|
+
function shouldLog(level) {
|
|
46752
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[MIN_LEVEL];
|
|
46753
|
+
}
|
|
46754
|
+
function formatMessage(level, context, message2, data) {
|
|
46755
|
+
const entry = {
|
|
46756
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46757
|
+
level,
|
|
46758
|
+
ctx: context,
|
|
46759
|
+
msg: message2
|
|
46760
|
+
};
|
|
46761
|
+
if (data) Object.assign(entry, data);
|
|
46762
|
+
return JSON.stringify(entry);
|
|
46763
|
+
}
|
|
46764
|
+
function createLogger(context) {
|
|
46765
|
+
return {
|
|
46766
|
+
debug: (msg, data) => {
|
|
46767
|
+
if (shouldLog("debug")) console.log(formatMessage("debug", context, msg, data));
|
|
46768
|
+
},
|
|
46769
|
+
info: (msg, data) => {
|
|
46770
|
+
if (shouldLog("info")) console.log(formatMessage("info", context, msg, data));
|
|
46771
|
+
},
|
|
46772
|
+
warn: (msg, data) => {
|
|
46773
|
+
if (shouldLog("warn")) console.warn(formatMessage("warn", context, msg, data));
|
|
46774
|
+
},
|
|
46775
|
+
error: (msg, data) => {
|
|
46776
|
+
if (shouldLog("error")) console.error(formatMessage("error", context, msg, data));
|
|
46777
|
+
}
|
|
46778
|
+
};
|
|
46779
|
+
}
|
|
46780
|
+
|
|
46781
|
+
// ../lib/ai/extract-prd-features.ts
|
|
46782
|
+
var log = createLogger("ai-extract-prd");
|
|
46783
|
+
async function extractFeaturesFromPrd(prdText, existingFeatures) {
|
|
46784
|
+
const client2 = getAnthropicClient();
|
|
46785
|
+
const existingList = existingFeatures.length > 0 ? `
|
|
46786
|
+
|
|
46787
|
+
Existing features for this workstream (do NOT extract duplicates):
|
|
46788
|
+
${existingFeatures.map((f) => `- "${f.title}" [${f.status}]`).join("\n")}` : "";
|
|
46789
|
+
const response = await client2.messages.create({
|
|
46790
|
+
model: "claude-sonnet-4-20250514",
|
|
46791
|
+
max_tokens: 2e3,
|
|
46792
|
+
system: `You are analyzing a Product Requirements Document (PRD). Your job is to extract HIGH-LEVEL FEATURES \u2014 things a user can DO.
|
|
46793
|
+
|
|
46794
|
+
Rules:
|
|
46795
|
+
- Each feature should be a user capability: "User can bulk import companies from CSV"
|
|
46796
|
+
- NOT implementation details: "Add POST /api/import endpoint" is WRONG
|
|
46797
|
+
- NOT tasks: "Write unit tests" is WRONG
|
|
46798
|
+
- Features should be independent enough to be tracked as separate work items
|
|
46799
|
+
- If the PRD has a "Functional Requirements" section, those are your primary source
|
|
46800
|
+
- "User Flows" sections describe journeys \u2014 extract the capability, not each step
|
|
46801
|
+
- Respect "Out of Scope" / "Scope Boundaries" \u2014 do NOT extract features for out-of-scope items
|
|
46802
|
+
- Also extract any "Open Questions" from the PRD as a separate list
|
|
46803
|
+
|
|
46804
|
+
Confidence scoring:
|
|
46805
|
+
- 0.9+: Explicitly stated as a requirement ("User can X", "System shall Y")
|
|
46806
|
+
- 0.7-0.9: Clearly implied by user flows or problem statement
|
|
46807
|
+
- 0.5-0.7: Inferred but not explicitly stated
|
|
46808
|
+
- Below 0.5: Don't include${existingList}
|
|
46809
|
+
|
|
46810
|
+
Respond with JSON:
|
|
46811
|
+
{
|
|
46812
|
+
"features": [
|
|
46813
|
+
{ "title": "...", "description": "...", "confidence": 0.9 }
|
|
46814
|
+
],
|
|
46815
|
+
"openQuestions": ["Question from the PRD...", "..."]
|
|
46816
|
+
}`,
|
|
46817
|
+
messages: [{ role: "user", content: prdText }]
|
|
46818
|
+
});
|
|
46819
|
+
const text = response.content[0].type === "text" ? response.content[0].text : "";
|
|
46820
|
+
const result = parseAIResponse(text);
|
|
46821
|
+
log.info("extracted features from PRD", {
|
|
46822
|
+
featureCount: result.features?.length || 0,
|
|
46823
|
+
questionCount: result.openQuestions?.length || 0
|
|
46824
|
+
});
|
|
46825
|
+
return {
|
|
46826
|
+
features: result.features || [],
|
|
46827
|
+
openQuestions: result.openQuestions || []
|
|
46828
|
+
};
|
|
46829
|
+
}
|
|
46830
|
+
async function validateFeaturesAgainstPrd(prdText, existingFeatures) {
|
|
46831
|
+
const client2 = getAnthropicClient();
|
|
46832
|
+
const featureList = existingFeatures.map((f) => `- "${f.title}" [${f.status}]`).join("\n");
|
|
46833
|
+
const response = await client2.messages.create({
|
|
46834
|
+
model: "claude-sonnet-4-20250514",
|
|
46835
|
+
max_tokens: 1500,
|
|
46836
|
+
system: `You are comparing a PRD against a list of existing features to find gaps and mismatches.
|
|
46837
|
+
|
|
46838
|
+
For each requirement in the PRD, check if there's a matching feature.
|
|
46839
|
+
For each existing feature, check if it maps to a PRD requirement.
|
|
46840
|
+
|
|
46841
|
+
Respond with JSON:
|
|
46842
|
+
{
|
|
46843
|
+
"gaps": ["Requirement from PRD that has no matching feature..."],
|
|
46844
|
+
"unmapped": ["Existing feature title that doesn't map to any PRD requirement..."],
|
|
46845
|
+
"summary": "Brief overall assessment \u2014 are features complete, what's missing?"
|
|
46846
|
+
}
|
|
46847
|
+
|
|
46848
|
+
Rules:
|
|
46849
|
+
- "Out of scope" items should NOT be flagged as gaps
|
|
46850
|
+
- Shipped features that match a requirement are fine \u2014 don't flag them
|
|
46851
|
+
- Be specific in gap descriptions \u2014 reference the PRD section
|
|
46852
|
+
- If everything aligns, return empty arrays and a positive summary`,
|
|
46853
|
+
messages: [{
|
|
46854
|
+
role: "user",
|
|
46855
|
+
content: `PRD:
|
|
46856
|
+
${prdText}
|
|
46857
|
+
|
|
46858
|
+
Existing Features:
|
|
46859
|
+
${featureList}`
|
|
46860
|
+
}]
|
|
46861
|
+
});
|
|
46862
|
+
const text = response.content[0].type === "text" ? response.content[0].text : "";
|
|
46863
|
+
const result = parseAIResponse(text);
|
|
46864
|
+
log.info("validated features against PRD", {
|
|
46865
|
+
gaps: result.gaps?.length || 0,
|
|
46866
|
+
unmapped: result.unmapped?.length || 0
|
|
46867
|
+
});
|
|
46868
|
+
return {
|
|
46869
|
+
gaps: result.gaps || [],
|
|
46870
|
+
unmapped: result.unmapped || [],
|
|
46871
|
+
summary: result.summary || "Validation complete."
|
|
46872
|
+
};
|
|
46873
|
+
}
|
|
46874
|
+
|
|
46875
|
+
// tools/workstreams.ts
|
|
46876
|
+
async function handleWorkstreams(params) {
|
|
46877
|
+
const supabase = getAdminClient();
|
|
46878
|
+
const action = params.action;
|
|
46879
|
+
switch (action) {
|
|
46880
|
+
case "list": {
|
|
46881
|
+
let query = supabase.from("projects").select("*, project_pods(pod_id, is_primary)").order("name");
|
|
46882
|
+
if (params.status) query = query.eq("status", params.status);
|
|
46883
|
+
else query = query.eq("status", "active");
|
|
46884
|
+
const { data, error: error2 } = await query;
|
|
46885
|
+
if (error2) return err(error2.message);
|
|
46886
|
+
let results = data || [];
|
|
46887
|
+
if (params.podId) {
|
|
46888
|
+
results = results.filter((p) => {
|
|
46889
|
+
const pp = p.project_pods || [];
|
|
46890
|
+
return pp.some((r) => r.pod_id === params.podId) || p.pod_id === params.podId;
|
|
46891
|
+
});
|
|
46892
|
+
}
|
|
46893
|
+
return ok(results, `${results.length} workstreams`, results.length);
|
|
46894
|
+
}
|
|
46895
|
+
case "get": {
|
|
46896
|
+
const slug = params.slug;
|
|
46897
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
46898
|
+
const { data, error: error2 } = await supabase.from("projects").select("*, project_pods(pod_id, is_primary)").eq("slug", slug).single();
|
|
46899
|
+
if (error2 || !data) return err("Workstream not found", "NOT_FOUND");
|
|
46900
|
+
return ok(data, `Workstream: ${data.name}`);
|
|
46901
|
+
}
|
|
46902
|
+
case "create": {
|
|
46903
|
+
const name = params.name;
|
|
46904
|
+
const podId = params.podId;
|
|
46905
|
+
const mode = params.mode;
|
|
46906
|
+
if (!name?.trim()) return err("name is required", "MISSING_PARAM");
|
|
46907
|
+
const trimmedName = name.trim();
|
|
46908
|
+
const { data: existing } = await supabase.from("projects").select("id, name, slug, project_pods(pod_id)").eq("name", trimmedName).maybeSingle();
|
|
46909
|
+
if (existing && !mode) {
|
|
46910
|
+
const existingPodIds = existing.project_pods?.map((r) => r.pod_id) || [];
|
|
46911
|
+
if (podId && existingPodIds.includes(podId)) {
|
|
46912
|
+
return ok(existing, `Workstream "${trimmedName}" already linked to this pod`);
|
|
46913
|
+
}
|
|
46914
|
+
return ok(
|
|
46915
|
+
{ conflict: true, existingProject: { id: existing.id, name: existing.name, slug: existing.slug, podIds: existingPodIds } },
|
|
46916
|
+
`Workstream "${trimmedName}" already exists in another pod. Use mode: "link" to share or mode: "create_new" to create a separate one.`
|
|
46917
|
+
);
|
|
46918
|
+
}
|
|
46919
|
+
if (existing && mode === "link") {
|
|
46920
|
+
if (!podId) return err("podId is required to link", "MISSING_PARAM");
|
|
46921
|
+
await supabase.from("project_pods").insert({ project_id: existing.id, pod_id: podId, is_primary: false });
|
|
46922
|
+
return ok(existing, `Linked "${trimmedName}" to pod`);
|
|
46923
|
+
}
|
|
46924
|
+
if (existing && mode === "create_new") {
|
|
46925
|
+
const { data: pod } = podId ? await supabase.from("pods").select("name").eq("id", podId).single() : { data: null };
|
|
46926
|
+
const suffix = pod?.name || podId || "";
|
|
46927
|
+
const newName = suffix ? `${trimmedName} (${suffix})` : trimmedName;
|
|
46928
|
+
const newSlug = newName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
46929
|
+
const { data: data2, error: error3 } = await supabase.from("projects").insert({
|
|
46930
|
+
name: newName,
|
|
46931
|
+
slug: newSlug,
|
|
46932
|
+
description: params.description || null,
|
|
46933
|
+
pod_id: podId || null,
|
|
46934
|
+
color: params.color || "#71717a",
|
|
46935
|
+
status: "active"
|
|
46936
|
+
}).select().single();
|
|
46937
|
+
if (error3) return err(error3.message);
|
|
46938
|
+
if (podId) await supabase.from("project_pods").insert({ project_id: data2.id, pod_id: podId, is_primary: true });
|
|
46939
|
+
return ok(data2, `Created separate workstream: ${newName}`);
|
|
46940
|
+
}
|
|
46941
|
+
const slug = trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
46942
|
+
const { data, error: error2 } = await supabase.from("projects").insert({
|
|
46943
|
+
name: trimmedName,
|
|
46944
|
+
slug,
|
|
46945
|
+
description: params.description || null,
|
|
46946
|
+
pod_id: podId || null,
|
|
46947
|
+
color: params.color || "#71717a",
|
|
46948
|
+
status: "active"
|
|
46949
|
+
}).select().single();
|
|
46950
|
+
if (error2) return err(error2.message);
|
|
46951
|
+
if (podId) await supabase.from("project_pods").insert({ project_id: data.id, pod_id: podId, is_primary: true });
|
|
46952
|
+
return ok(data, `Created workstream: ${trimmedName}`);
|
|
46953
|
+
}
|
|
46954
|
+
case "link": {
|
|
46955
|
+
const slug = params.slug;
|
|
46956
|
+
const podId = params.podId;
|
|
46957
|
+
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
46958
|
+
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
46959
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
46960
|
+
const { error: error2 } = await supabase.from("project_pods").insert({
|
|
46961
|
+
project_id: project.id,
|
|
46962
|
+
pod_id: podId,
|
|
46963
|
+
is_primary: false
|
|
46964
|
+
});
|
|
46965
|
+
if (error2) return err(error2.message);
|
|
46966
|
+
return ok({ projectId: project.id, podId }, `Linked "${project.name}" to pod`);
|
|
46967
|
+
}
|
|
46968
|
+
case "unlink": {
|
|
46969
|
+
const slug = params.slug;
|
|
46970
|
+
const podId = params.podId;
|
|
46971
|
+
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
46972
|
+
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
46973
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
46974
|
+
const { error: error2 } = await supabase.from("project_pods").delete().eq("project_id", project.id).eq("pod_id", podId);
|
|
46975
|
+
if (error2) return err(error2.message);
|
|
46976
|
+
return ok({ projectId: project.id, podId }, `Unlinked "${project.name}" from pod`);
|
|
46977
|
+
}
|
|
46978
|
+
case "update": {
|
|
46979
|
+
const slug = params.slug;
|
|
46980
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
46981
|
+
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
46982
|
+
if (params.name !== void 0) updates.name = params.name;
|
|
46983
|
+
if (params.description !== void 0) updates.description = params.description;
|
|
46984
|
+
if (params.status !== void 0) updates.status = params.status;
|
|
46985
|
+
const { data, error: error2 } = await supabase.from("projects").update(updates).eq("slug", slug).select().single();
|
|
46986
|
+
if (error2) return err(error2.message);
|
|
46987
|
+
return ok(data, `Updated workstream: ${data?.name}`);
|
|
46988
|
+
}
|
|
46989
|
+
case "delete": {
|
|
46990
|
+
const slug = params.slug;
|
|
46991
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
46992
|
+
const { error: error2 } = await supabase.from("projects").delete().eq("slug", slug);
|
|
46993
|
+
if (error2) return err(error2.message);
|
|
46994
|
+
return ok({ deleted: slug }, "Workstream deleted");
|
|
46995
|
+
}
|
|
46996
|
+
case "get_suggestions": {
|
|
46997
|
+
const slug = params.slug;
|
|
46998
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
46999
|
+
const { data: project } = await supabase.from("projects").select("name").eq("slug", slug).single();
|
|
47000
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47001
|
+
const { data } = await supabase.from("ai_suggestions").select("*").eq("suggestion_type", "proactive").like("payload->>projectName", project.name).eq("status", "pending").order("created_at", { ascending: false }).limit(5);
|
|
47002
|
+
return ok(data || [], `${(data || []).length} suggestions for ${project.name}`, (data || []).length);
|
|
47003
|
+
}
|
|
47004
|
+
case "extract_features": {
|
|
47005
|
+
const slug = params.slug;
|
|
47006
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
47007
|
+
const { data: project } = await supabase.from("projects").select("id, name, description").eq("slug", slug).single();
|
|
47008
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47009
|
+
if (!project.description?.trim()) return err("No PRD content \u2014 add a description first", "MISSING_DATA");
|
|
47010
|
+
const { data: existing } = await supabase.from("work_items").select("title, status").eq("project_name", project.name);
|
|
47011
|
+
const result = await extractFeaturesFromPrd(project.description, (existing || []).map((f) => ({ title: f.title, status: f.status })));
|
|
47012
|
+
return ok(result, `Extracted ${result.features.length} features, ${result.openQuestions.length} open questions`);
|
|
47013
|
+
}
|
|
47014
|
+
case "create_features": {
|
|
47015
|
+
const slug = params.slug;
|
|
47016
|
+
const features = params.features;
|
|
47017
|
+
if (!slug || !features?.length) return err("slug and features array are required", "MISSING_PARAM");
|
|
47018
|
+
const { data: project } = await supabase.from("projects").select("id, name, color, pod_id, project_pods(pod_id, is_primary)").eq("slug", slug).single();
|
|
47019
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47020
|
+
const pp = project.project_pods;
|
|
47021
|
+
const primaryPod = pp?.find((r) => r.is_primary)?.pod_id || project.pod_id;
|
|
47022
|
+
const created = [];
|
|
47023
|
+
for (const f of features) {
|
|
47024
|
+
const id = `f-${Date.now().toString(36)}`;
|
|
47025
|
+
const { error: insertErr } = await supabase.from("work_items").insert({
|
|
47026
|
+
id,
|
|
47027
|
+
title: f.title.trim(),
|
|
47028
|
+
description: f.description || null,
|
|
47029
|
+
pod_id: primaryPod,
|
|
47030
|
+
project_name: project.name,
|
|
47031
|
+
project_color: project.color || "#71717a",
|
|
47032
|
+
phase: "discovery",
|
|
47033
|
+
track: "pre_dev",
|
|
47034
|
+
status: "in_progress",
|
|
47035
|
+
has_blocker: false,
|
|
47036
|
+
size: "feature",
|
|
47037
|
+
created_via: "prd_extraction"
|
|
47038
|
+
});
|
|
47039
|
+
if (!insertErr) created.push(id);
|
|
47040
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
47041
|
+
}
|
|
47042
|
+
return ok({ created, count: created.length }, `Created ${created.length} features from PRD`);
|
|
47043
|
+
}
|
|
47044
|
+
case "validate": {
|
|
47045
|
+
const slug = params.slug;
|
|
47046
|
+
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
47047
|
+
const { data: project } = await supabase.from("projects").select("id, name, description").eq("slug", slug).single();
|
|
47048
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47049
|
+
if (!project.description?.trim()) return err("No PRD content to validate against", "MISSING_DATA");
|
|
47050
|
+
const { data: existing } = await supabase.from("work_items").select("title, status").eq("project_name", project.name);
|
|
47051
|
+
const result = await validateFeaturesAgainstPrd(project.description, (existing || []).map((f) => ({ title: f.title, status: f.status })));
|
|
47052
|
+
return ok(result, result.summary);
|
|
47053
|
+
}
|
|
47054
|
+
case "comment": {
|
|
47055
|
+
const slug = params.slug;
|
|
47056
|
+
const note = params.note;
|
|
47057
|
+
if (!slug || !note?.trim()) return err("slug and note are required", "MISSING_PARAM");
|
|
47058
|
+
const { data: project } = await supabase.from("projects").select("slug, conversation").eq("slug", slug).single();
|
|
47059
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47060
|
+
const user = await getCurrentUser();
|
|
47061
|
+
const conversation = project.conversation || [];
|
|
47062
|
+
conversation.push({
|
|
47063
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47064
|
+
from: user?.fullName || "Engineer",
|
|
47065
|
+
role: params.role || "note",
|
|
47066
|
+
text: note.trim(),
|
|
47067
|
+
source: "mcp"
|
|
47068
|
+
});
|
|
47069
|
+
await supabase.from("projects").update({ conversation, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("slug", slug);
|
|
47070
|
+
return ok({ count: conversation.length }, "Comment added to workstream");
|
|
47071
|
+
}
|
|
47072
|
+
default:
|
|
47073
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47074
|
+
}
|
|
47075
|
+
}
|
|
47076
|
+
|
|
47077
|
+
// tools/requests.ts
|
|
47078
|
+
function getCurrentWeekStart() {
|
|
47079
|
+
return format(startOfWeek(/* @__PURE__ */ new Date(), { weekStartsOn: 1 }), "yyyy-MM-dd");
|
|
47080
|
+
}
|
|
47081
|
+
async function handleRequests(params) {
|
|
47082
|
+
const supabase = getAdminClient();
|
|
47083
|
+
const action = params.action;
|
|
47084
|
+
switch (action) {
|
|
47085
|
+
case "list": {
|
|
47086
|
+
const podId = params.podId;
|
|
47087
|
+
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
47088
|
+
let query = supabase.from("work_items").select("*").eq("pod_id", podId);
|
|
47089
|
+
if (params.type) query = query.eq("type", params.type);
|
|
47090
|
+
if (params.status) query = query.eq("status", params.status);
|
|
47091
|
+
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
47092
|
+
const { data, error: error2 } = await query;
|
|
47093
|
+
if (error2) return err(error2.message);
|
|
47094
|
+
return ok(data, `${(data || []).length} requests`, (data || []).length);
|
|
47095
|
+
}
|
|
47096
|
+
case "create": {
|
|
47097
|
+
const podId = params.podId;
|
|
47098
|
+
const title = params.title;
|
|
47099
|
+
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
47100
|
+
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
47101
|
+
id: `f-${Date.now().toString(36)}`,
|
|
47102
|
+
title: title.trim(),
|
|
47103
|
+
description: params.description || null,
|
|
47104
|
+
pod_id: podId,
|
|
47105
|
+
project_name: params.projectName || null,
|
|
47106
|
+
requester_name: params.requesterName || null,
|
|
47107
|
+
source: params.source || "mcp",
|
|
47108
|
+
type: params.requestType || "feature_request",
|
|
47109
|
+
priority: params.priority || null,
|
|
47110
|
+
sentiment: params.sentiment || null,
|
|
47111
|
+
related_feature_title: params.relatedFeatureTitle || null
|
|
47112
|
+
}).select().single();
|
|
47113
|
+
if (error2) return err(error2.message);
|
|
47114
|
+
return ok(data, `Created ${params.requestType || "feature_request"}: ${title}`);
|
|
47115
|
+
}
|
|
47116
|
+
case "triage": {
|
|
47117
|
+
const requestId = params.requestId;
|
|
47118
|
+
const triageAction = params.triageAction;
|
|
47119
|
+
const podId = params.podId;
|
|
47120
|
+
if (!requestId || !triageAction) return err("requestId and triageAction are required", "MISSING_PARAM");
|
|
47121
|
+
if (triageAction === "accept") {
|
|
47122
|
+
const featureId = `f-${Date.now().toString(36)}`;
|
|
47123
|
+
const { data: req } = await supabase.from("work_items").select("title, pod_id").eq("id", requestId).single();
|
|
47124
|
+
if (!req) return err("Request not found", "NOT_FOUND");
|
|
47125
|
+
await supabase.from("work_items").insert({
|
|
47126
|
+
id: featureId,
|
|
47127
|
+
title: req.title,
|
|
47128
|
+
pod_id: req.pod_id,
|
|
47129
|
+
project_name: params.projectName || "",
|
|
47130
|
+
project_color: "#71717a",
|
|
47131
|
+
phase: params.phase || "discovery",
|
|
47132
|
+
track: "dev",
|
|
47133
|
+
status: "in_progress",
|
|
47134
|
+
has_blocker: false,
|
|
47135
|
+
size: "feature",
|
|
47136
|
+
week_start: params.weekStart || null
|
|
47137
|
+
});
|
|
47138
|
+
await supabase.from("work_items").update({
|
|
47139
|
+
status: "accepted",
|
|
47140
|
+
accepted_feature_id: featureId,
|
|
47141
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47142
|
+
}).eq("id", requestId);
|
|
47143
|
+
return ok({ requestId, featureId, action: "accepted" }, `Accepted request \u2192 feature created`);
|
|
47144
|
+
}
|
|
47145
|
+
const statusMap = {
|
|
47146
|
+
decline: "declined",
|
|
47147
|
+
defer: "deferred",
|
|
47148
|
+
acknowledge: "acknowledged",
|
|
47149
|
+
convert: "open"
|
|
47150
|
+
};
|
|
47151
|
+
const newStatus = statusMap[triageAction];
|
|
47152
|
+
if (!newStatus) return err(`Unknown triage action: ${triageAction}`, "INVALID_ACTION");
|
|
47153
|
+
const updates = { status: newStatus, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
47154
|
+
if (triageAction === "convert") updates.type = "feature_request";
|
|
47155
|
+
await supabase.from("work_items").update(updates).eq("id", requestId);
|
|
47156
|
+
return ok({ requestId, action: triageAction }, `Request ${triageAction}d`);
|
|
47157
|
+
}
|
|
47158
|
+
default:
|
|
47159
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47160
|
+
}
|
|
47161
|
+
}
|
|
47162
|
+
async function handleBugs(params) {
|
|
47163
|
+
const supabase = getAdminClient();
|
|
47164
|
+
const action = params.action;
|
|
47165
|
+
switch (action) {
|
|
47166
|
+
case "list": {
|
|
47167
|
+
let query = supabase.from("work_items").select("*").eq("type", "bug");
|
|
47168
|
+
if (params.podId) query = query.eq("pod_id", params.podId);
|
|
47169
|
+
if (params.status) query = query.eq("status", params.status);
|
|
47170
|
+
else query = query.in("status", ["open", "in_progress"]);
|
|
47171
|
+
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
47172
|
+
const { data, error: error2 } = await query;
|
|
47173
|
+
if (error2) return err(error2.message);
|
|
47174
|
+
return ok(data, `${(data || []).length} bugs`, (data || []).length);
|
|
47175
|
+
}
|
|
47176
|
+
case "create": {
|
|
47177
|
+
const podId = params.podId;
|
|
47178
|
+
const title = params.title;
|
|
47179
|
+
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
47180
|
+
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
47181
|
+
id: `f-${Date.now().toString(36)}`,
|
|
47182
|
+
title: title.trim(),
|
|
47183
|
+
description: params.description || null,
|
|
47184
|
+
pod_id: podId,
|
|
47185
|
+
project_name: params.projectName || null,
|
|
47186
|
+
source: params.source || "mcp",
|
|
47187
|
+
type: "bug",
|
|
47188
|
+
priority: params.priority || "medium",
|
|
47189
|
+
status: "open"
|
|
47190
|
+
}).select().single();
|
|
47191
|
+
if (error2) return err(error2.message);
|
|
47192
|
+
return ok(data, `Created bug: ${title}`);
|
|
47193
|
+
}
|
|
47194
|
+
case "update": {
|
|
47195
|
+
const bugId = params.bugId;
|
|
47196
|
+
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
47197
|
+
const { data: currentBug } = await supabase.from("work_items").select("status, phase, track, implementation_step, has_blocker, week_start, title, pod_id").eq("id", bugId).single();
|
|
47198
|
+
const raw = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
47199
|
+
if (params.title !== void 0) raw.title = params.title;
|
|
47200
|
+
if (params.description !== void 0) raw.description = params.description;
|
|
47201
|
+
if (params.priority !== void 0) raw.priority = params.priority;
|
|
47202
|
+
if (params.status !== void 0) raw.status = params.status;
|
|
47203
|
+
if (params.podId !== void 0) raw.pod_id = params.podId;
|
|
47204
|
+
if (params.projectName !== void 0) raw.project_name = params.projectName;
|
|
47205
|
+
let autoPromoted = false;
|
|
47206
|
+
if (currentBug?.status === "open" && !currentBug.week_start && !params.status) {
|
|
47207
|
+
raw.status = "in_progress";
|
|
47208
|
+
raw.week_start = getCurrentWeekStart();
|
|
47209
|
+
autoPromoted = true;
|
|
47210
|
+
}
|
|
47211
|
+
const updates = normalizeWorkItemState(raw, currentBug || void 0);
|
|
47212
|
+
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
47213
|
+
if (error2) return err(error2.message);
|
|
47214
|
+
if (autoPromoted) {
|
|
47215
|
+
await supabase.from("events").insert({
|
|
47216
|
+
source: "mcp",
|
|
47217
|
+
event_type: "bug_promoted",
|
|
47218
|
+
raw_payload: { bugId, trigger: "update" },
|
|
47219
|
+
ai_extraction: { bugTitle: currentBug.title, podId: currentBug.pod_id, weekStart: updates.week_start },
|
|
47220
|
+
processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47221
|
+
});
|
|
47222
|
+
}
|
|
47223
|
+
return ok({ id: bugId, autoPromoted }, autoPromoted ? "Bug updated \u2014 auto-scheduled to this week" : "Bug updated");
|
|
47224
|
+
}
|
|
47225
|
+
case "delete": {
|
|
47226
|
+
const bugId = params.bugId;
|
|
47227
|
+
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
47228
|
+
const { error: error2 } = await supabase.from("work_items").delete().eq("id", bugId);
|
|
47229
|
+
if (error2) return err(error2.message);
|
|
47230
|
+
return ok({ deleted: bugId }, "Bug deleted");
|
|
47231
|
+
}
|
|
47232
|
+
case "schedule": {
|
|
47233
|
+
const bugId = params.bugId;
|
|
47234
|
+
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
47235
|
+
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
47236
|
+
if (params.weekStart !== void 0) updates.week_start = params.weekStart;
|
|
47237
|
+
if (params.sortOrder !== void 0) updates.sort_order = params.sortOrder;
|
|
47238
|
+
if (params.weekStart) {
|
|
47239
|
+
const { data: bug } = await supabase.from("work_items").select("status").eq("id", bugId).single();
|
|
47240
|
+
if (bug?.status === "open") updates.status = "in_progress";
|
|
47241
|
+
}
|
|
47242
|
+
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
47243
|
+
if (error2) return err(error2.message);
|
|
47244
|
+
return ok({ id: bugId, ...updates }, `Bug scheduled${params.weekStart ? ` to week of ${params.weekStart}` : ""}`);
|
|
47245
|
+
}
|
|
47246
|
+
default:
|
|
47247
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47248
|
+
}
|
|
47249
|
+
}
|
|
47250
|
+
|
|
47251
|
+
// tools/suggestions.ts
|
|
47252
|
+
async function handleSuggestions(params) {
|
|
47253
|
+
const supabase = getAdminClient();
|
|
47254
|
+
const action = params.action;
|
|
47255
|
+
switch (action) {
|
|
47256
|
+
case "list": {
|
|
47257
|
+
const { data, error: error2 } = await supabase.from("ai_suggestions").select("*").eq("status", "pending").order("created_at", { ascending: false }).limit(params.limit || 20);
|
|
47258
|
+
if (error2) return err(error2.message);
|
|
47259
|
+
return ok(data, `${(data || []).length} pending suggestions`, (data || []).length);
|
|
47260
|
+
}
|
|
47261
|
+
case "approve": {
|
|
47262
|
+
const suggestionId = params.suggestionId;
|
|
47263
|
+
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
47264
|
+
const { data: suggestion } = await supabase.from("ai_suggestions").select("*").eq("id", suggestionId).single();
|
|
47265
|
+
if (!suggestion) return err("Suggestion not found", "NOT_FOUND");
|
|
47266
|
+
await supabase.from("ai_suggestions").update({
|
|
47267
|
+
status: "approved",
|
|
47268
|
+
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47269
|
+
}).eq("id", suggestionId);
|
|
47270
|
+
return ok({ id: suggestionId, status: "approved" }, `Suggestion approved: ${suggestion.title}`);
|
|
47271
|
+
}
|
|
47272
|
+
case "reject": {
|
|
47273
|
+
const suggestionId = params.suggestionId;
|
|
47274
|
+
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
47275
|
+
await supabase.from("ai_suggestions").update({
|
|
47276
|
+
status: "rejected",
|
|
47277
|
+
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47278
|
+
}).eq("id", suggestionId);
|
|
47279
|
+
return ok({ id: suggestionId, status: "rejected" }, "Suggestion rejected");
|
|
47280
|
+
}
|
|
47281
|
+
case "correct": {
|
|
47282
|
+
const suggestionId = params.suggestionId;
|
|
47283
|
+
const correction = params.correction;
|
|
47284
|
+
if (!suggestionId || !correction?.trim()) return err("suggestionId and correction are required", "MISSING_PARAM");
|
|
47285
|
+
await supabase.from("ai_suggestions").update({
|
|
47286
|
+
status: "corrected",
|
|
47287
|
+
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47288
|
+
}).eq("id", suggestionId);
|
|
47289
|
+
return ok({ id: suggestionId, correction: correction.trim() }, "Suggestion corrected");
|
|
47290
|
+
}
|
|
47291
|
+
default:
|
|
47292
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47293
|
+
}
|
|
47294
|
+
}
|
|
47295
|
+
|
|
47296
|
+
// tools/events.ts
|
|
47297
|
+
async function handleEvents(params) {
|
|
47298
|
+
const supabase = getAdminClient();
|
|
47299
|
+
const action = params.action;
|
|
47300
|
+
switch (action) {
|
|
47301
|
+
case "list": {
|
|
47302
|
+
const { data, error: error2 } = await supabase.from("events").select("id, source, event_type, ai_extraction, created_at").order("created_at", { ascending: false }).limit(params.limit || 20);
|
|
47303
|
+
if (error2) return err(error2.message);
|
|
47304
|
+
return ok(data, `${(data || []).length} events`, (data || []).length);
|
|
47305
|
+
}
|
|
47306
|
+
case "list_by_feature": {
|
|
47307
|
+
const featureTitle = params.featureTitle;
|
|
47308
|
+
const podId = params.podId;
|
|
47309
|
+
if (!featureTitle) return err("featureTitle is required", "MISSING_PARAM");
|
|
47310
|
+
let query = supabase.from("events").select("id, source, event_type, ai_extraction, created_at").filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
47311
|
+
if (podId) query = query.filter("ai_extraction->>podId", "eq", podId);
|
|
47312
|
+
query = query.order("created_at", { ascending: false }).limit(params.limit || 10);
|
|
47313
|
+
const { data, error: error2 } = await query;
|
|
47314
|
+
if (error2) return err(error2.message);
|
|
47315
|
+
return ok(data, `${(data || []).length} events for "${featureTitle}"`, (data || []).length);
|
|
47316
|
+
}
|
|
47317
|
+
case "list_by_pod": {
|
|
47318
|
+
const podId = params.podId;
|
|
47319
|
+
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
47320
|
+
const { data, error: error2 } = await supabase.from("events").select("id, source, event_type, ai_extraction, created_at").filter("ai_extraction->>podId", "eq", podId).order("created_at", { ascending: false }).limit(params.limit || 10);
|
|
47321
|
+
if (error2) return err(error2.message);
|
|
47322
|
+
return ok(data, `${(data || []).length} events for pod ${podId}`, (data || []).length);
|
|
47323
|
+
}
|
|
47324
|
+
case "list_prs": {
|
|
47325
|
+
const featureTitle = params.featureTitle;
|
|
47326
|
+
const shortCode = params.shortCode;
|
|
47327
|
+
if (!featureTitle && !shortCode) return err("featureTitle or shortCode is required", "MISSING_PARAM");
|
|
47328
|
+
let query = supabase.from("events").select("event_type, raw_payload, ai_extraction, created_at").eq("source", "github").in("event_type", ["pr_opened", "pr_merged"]);
|
|
47329
|
+
if (shortCode) {
|
|
47330
|
+
query = query.filter("ai_extraction->>linked_short_code", "eq", shortCode);
|
|
47331
|
+
} else {
|
|
47332
|
+
query = query.filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
47333
|
+
}
|
|
47334
|
+
query = query.order("created_at", { ascending: false }).limit(50);
|
|
47335
|
+
const { data, error: error2 } = await query;
|
|
47336
|
+
if (error2) return err(error2.message);
|
|
47337
|
+
const opened = /* @__PURE__ */ new Set();
|
|
47338
|
+
const merged = /* @__PURE__ */ new Set();
|
|
47339
|
+
const prs = [];
|
|
47340
|
+
for (const ev of data || []) {
|
|
47341
|
+
const prNum = ev.raw_payload?.pr_number;
|
|
47342
|
+
if (prNum == null) continue;
|
|
47343
|
+
if (ev.event_type === "pr_opened") opened.add(prNum);
|
|
47344
|
+
if (ev.event_type === "pr_merged") merged.add(prNum);
|
|
47345
|
+
}
|
|
47346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47347
|
+
for (const ev of data || []) {
|
|
47348
|
+
const prNum = ev.raw_payload?.pr_number;
|
|
47349
|
+
if (prNum == null || seen.has(prNum)) continue;
|
|
47350
|
+
seen.add(prNum);
|
|
47351
|
+
prs.push({
|
|
47352
|
+
number: prNum,
|
|
47353
|
+
title: ev.raw_payload?.pr_title || ev.ai_extraction?.whatWasBuilt || "",
|
|
47354
|
+
status: merged.has(prNum) ? "merged" : "open",
|
|
47355
|
+
repo: ev.ai_extraction?.repo || "",
|
|
47356
|
+
date: ev.created_at
|
|
47357
|
+
});
|
|
47358
|
+
}
|
|
47359
|
+
return ok(
|
|
47360
|
+
{ prs, totalOpened: opened.size, totalMerged: merged.size, unmerged: opened.size - merged.size },
|
|
47361
|
+
`${prs.length} PR(s): ${merged.size} merged, ${opened.size - merged.size} open`,
|
|
47362
|
+
prs.length
|
|
47363
|
+
);
|
|
47364
|
+
}
|
|
47365
|
+
default:
|
|
47366
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47367
|
+
}
|
|
47368
|
+
}
|
|
47369
|
+
async function handleHistory(params) {
|
|
47370
|
+
const supabase = getAdminClient();
|
|
47371
|
+
const days = params.days || 90;
|
|
47372
|
+
const limit = params.limit || 50;
|
|
47373
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
47374
|
+
const items = [];
|
|
47375
|
+
if (!params.type || params.type === "feature") {
|
|
47376
|
+
let q = supabase.from("work_items").select("id, title, status, pod_id, project_name, week_start, updated_at").in("status", ["shipped", "missed", "killed"]).gte("updated_at", cutoff).order("updated_at", { ascending: false }).limit(limit);
|
|
47377
|
+
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
47378
|
+
if (params.projectName) q = q.eq("project_name", params.projectName);
|
|
47379
|
+
const { data } = await q;
|
|
47380
|
+
for (const f of data || []) {
|
|
47381
|
+
items.push({ id: f.id, itemType: "feature", title: f.title, status: f.status, podId: f.pod_id, projectName: f.project_name, date: f.updated_at });
|
|
47382
|
+
}
|
|
47383
|
+
}
|
|
47384
|
+
if (!params.type || ["bug", "request", "feedback"].includes(params.type)) {
|
|
47385
|
+
let q = supabase.from("work_items").select("id, title, type, status, pod_id, project_name, source, updated_at, created_at").not("status", "eq", "open").gte("created_at", cutoff).order("created_at", { ascending: false }).limit(limit);
|
|
47386
|
+
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
47387
|
+
const { data } = await q;
|
|
47388
|
+
for (const r of data || []) {
|
|
47389
|
+
const t = r.type === "feature_request" ? "request" : r.type === "bug" ? "bug" : "feedback";
|
|
47390
|
+
if (params.type && params.type !== t) continue;
|
|
47391
|
+
items.push({ id: r.id, itemType: t, title: r.title, status: r.status || "open", podId: r.pod_id, projectName: r.project_name, source: r.source, date: r.updated_at || r.created_at });
|
|
47392
|
+
}
|
|
47393
|
+
}
|
|
47394
|
+
items.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
|
47395
|
+
return ok(items.slice(0, limit), `${items.length} history items`, items.length);
|
|
47396
|
+
}
|
|
47397
|
+
|
|
47164
47398
|
// tools/briefing.ts
|
|
47165
47399
|
function getWeekStart(date3) {
|
|
47166
47400
|
return startOfWeek(date3, { weekStartsOn: 1 });
|
|
@@ -47224,8 +47458,8 @@ async function handleBriefing(params) {
|
|
|
47224
47458
|
}))
|
|
47225
47459
|
}, `Week of ${thisWeekKey}: ${thisWeekFeatures.length} features, ${(bugs || []).length} bugs, ${(requests || []).length} requests \u2014 set ANTHROPIC_API_KEY for AI briefing`);
|
|
47226
47460
|
}
|
|
47227
|
-
const
|
|
47228
|
-
const response = await
|
|
47461
|
+
const client2 = new Anthropic({ apiKey: anthropicKey });
|
|
47462
|
+
const response = await client2.messages.create({
|
|
47229
47463
|
model: "claude-sonnet-4-20250514",
|
|
47230
47464
|
max_tokens: 1500,
|
|
47231
47465
|
system: `You are Pulse, an AI project management assistant. Generate a concise daily briefing for a ${jobTitle}.
|
|
@@ -47386,6 +47620,87 @@ async function handleSearch(params) {
|
|
|
47386
47620
|
);
|
|
47387
47621
|
}
|
|
47388
47622
|
|
|
47623
|
+
// tools/milestones.ts
|
|
47624
|
+
async function handleMilestones(params) {
|
|
47625
|
+
const supabase = getAdminClient();
|
|
47626
|
+
const action = params.action;
|
|
47627
|
+
switch (action) {
|
|
47628
|
+
case "list": {
|
|
47629
|
+
const projectId = params.projectId;
|
|
47630
|
+
const slug = params.slug;
|
|
47631
|
+
let query = supabase.from("milestones").select("*").order("sort_order").order("target_date");
|
|
47632
|
+
if (projectId) {
|
|
47633
|
+
query = query.eq("project_id", projectId);
|
|
47634
|
+
} else if (slug) {
|
|
47635
|
+
const { data: project } = await supabase.from("projects").select("id").eq("slug", slug).single();
|
|
47636
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47637
|
+
query = query.eq("project_id", project.id);
|
|
47638
|
+
} else {
|
|
47639
|
+
return err("projectId or slug is required", "MISSING_PARAM");
|
|
47640
|
+
}
|
|
47641
|
+
const { data, error: error2 } = await query;
|
|
47642
|
+
if (error2) return err(error2.message);
|
|
47643
|
+
return ok(data, `${(data || []).length} milestones`, (data || []).length);
|
|
47644
|
+
}
|
|
47645
|
+
case "create": {
|
|
47646
|
+
const title = params.title;
|
|
47647
|
+
if (!title?.trim()) return err("title is required", "MISSING_PARAM");
|
|
47648
|
+
let projectId = params.projectId;
|
|
47649
|
+
if (!projectId && params.slug) {
|
|
47650
|
+
const { data: project } = await supabase.from("projects").select("id").eq("slug", params.slug).single();
|
|
47651
|
+
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
47652
|
+
projectId = project.id;
|
|
47653
|
+
}
|
|
47654
|
+
if (!projectId) return err("projectId or slug is required", "MISSING_PARAM");
|
|
47655
|
+
const { data, error: error2 } = await supabase.from("milestones").insert({
|
|
47656
|
+
project_id: projectId,
|
|
47657
|
+
title: title.trim(),
|
|
47658
|
+
description: params.description || null,
|
|
47659
|
+
target_date: params.targetDate || null,
|
|
47660
|
+
status: "pending"
|
|
47661
|
+
}).select().single();
|
|
47662
|
+
if (error2) return err(error2.message);
|
|
47663
|
+
return ok(data, `Created milestone: ${title}`);
|
|
47664
|
+
}
|
|
47665
|
+
case "update": {
|
|
47666
|
+
const milestoneId = params.milestoneId;
|
|
47667
|
+
if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
|
|
47668
|
+
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
47669
|
+
if (params.title !== void 0) updates.title = params.title;
|
|
47670
|
+
if (params.description !== void 0) updates.description = params.description;
|
|
47671
|
+
if (params.targetDate !== void 0) updates.target_date = params.targetDate;
|
|
47672
|
+
if (params.status !== void 0) {
|
|
47673
|
+
updates.status = params.status;
|
|
47674
|
+
updates.completed_at = params.status === "completed" ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
47675
|
+
}
|
|
47676
|
+
if (params.sortOrder !== void 0) updates.sort_order = params.sortOrder;
|
|
47677
|
+
const { error: error2 } = await supabase.from("milestones").update(updates).eq("id", milestoneId);
|
|
47678
|
+
if (error2) return err(error2.message);
|
|
47679
|
+
return ok({ id: milestoneId }, "Milestone updated");
|
|
47680
|
+
}
|
|
47681
|
+
case "complete": {
|
|
47682
|
+
const milestoneId = params.milestoneId;
|
|
47683
|
+
if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
|
|
47684
|
+
const { error: error2 } = await supabase.from("milestones").update({
|
|
47685
|
+
status: "completed",
|
|
47686
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47687
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47688
|
+
}).eq("id", milestoneId);
|
|
47689
|
+
if (error2) return err(error2.message);
|
|
47690
|
+
return ok({ id: milestoneId }, "Milestone completed");
|
|
47691
|
+
}
|
|
47692
|
+
case "delete": {
|
|
47693
|
+
const milestoneId = params.milestoneId;
|
|
47694
|
+
if (!milestoneId) return err("milestoneId is required", "MISSING_PARAM");
|
|
47695
|
+
const { error: error2 } = await supabase.from("milestones").delete().eq("id", milestoneId);
|
|
47696
|
+
if (error2) return err(error2.message);
|
|
47697
|
+
return ok({ deleted: milestoneId }, "Milestone deleted");
|
|
47698
|
+
}
|
|
47699
|
+
default:
|
|
47700
|
+
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
47701
|
+
}
|
|
47702
|
+
}
|
|
47703
|
+
|
|
47389
47704
|
// server.ts
|
|
47390
47705
|
try {
|
|
47391
47706
|
await import("./config-V5TD57MJ.js");
|
|
@@ -47466,19 +47781,38 @@ server.tool(
|
|
|
47466
47781
|
);
|
|
47467
47782
|
server.tool(
|
|
47468
47783
|
"pulse_workstreams",
|
|
47469
|
-
"Manage workstreams
|
|
47784
|
+
"Manage workstreams. Actions: list, get, create (conflict detection), link/unlink pods, update, delete, get_suggestions, extract_features (AI extracts features from PRD), create_features (bulk create from extraction), validate (check PRD\u2194features alignment), comment (post to discussion thread).",
|
|
47470
47785
|
{
|
|
47471
|
-
action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions", "link", "unlink"]),
|
|
47786
|
+
action: external_exports.enum(["list", "get", "create", "update", "delete", "get_suggestions", "link", "unlink", "extract_features", "create_features", "validate", "comment"]),
|
|
47472
47787
|
slug: external_exports.string().optional().describe("Workstream slug"),
|
|
47473
47788
|
podId: external_exports.string().optional().describe("Pod ID filter / target pod for link/unlink"),
|
|
47474
47789
|
name: external_exports.string().optional().describe("Workstream name"),
|
|
47475
|
-
description: external_exports.string().optional().describe("Description"),
|
|
47790
|
+
description: external_exports.string().optional().describe("Description / PRD text"),
|
|
47476
47791
|
color: external_exports.string().optional().describe("Hex color"),
|
|
47477
47792
|
status: external_exports.string().optional().describe("Status: active, archived, completed"),
|
|
47478
|
-
mode: external_exports.enum(["link", "create_new"]).optional().describe("When creating with a conflicting name: 'link' to share or 'create_new' for separate")
|
|
47793
|
+
mode: external_exports.enum(["link", "create_new"]).optional().describe("When creating with a conflicting name: 'link' to share or 'create_new' for separate"),
|
|
47794
|
+
features: external_exports.array(external_exports.object({ title: external_exports.string(), description: external_exports.string() })).optional().describe("Features to create (for create_features action)"),
|
|
47795
|
+
note: external_exports.string().optional().describe("Comment text (for comment action)"),
|
|
47796
|
+
role: external_exports.string().optional().describe("Comment role: question, answer, note")
|
|
47479
47797
|
},
|
|
47480
47798
|
async (params) => handleWorkstreams(params)
|
|
47481
47799
|
);
|
|
47800
|
+
server.tool(
|
|
47801
|
+
"pulse_milestones",
|
|
47802
|
+
"Manage workstream milestones (key dates/goals). Actions: list (by projectId or slug), create, update, complete, delete.",
|
|
47803
|
+
{
|
|
47804
|
+
action: external_exports.enum(["list", "create", "update", "complete", "delete"]),
|
|
47805
|
+
projectId: external_exports.string().optional().describe("Project UUID"),
|
|
47806
|
+
slug: external_exports.string().optional().describe("Workstream slug (alternative to projectId)"),
|
|
47807
|
+
milestoneId: external_exports.string().optional().describe("Milestone ID (for update/complete/delete)"),
|
|
47808
|
+
title: external_exports.string().optional().describe("Milestone title"),
|
|
47809
|
+
description: external_exports.string().optional().describe("Description"),
|
|
47810
|
+
targetDate: external_exports.string().optional().describe("Target date (YYYY-MM-DD)"),
|
|
47811
|
+
status: external_exports.string().optional().describe("Status: pending, completed"),
|
|
47812
|
+
sortOrder: external_exports.number().optional().describe("Sort order")
|
|
47813
|
+
},
|
|
47814
|
+
async (params) => handleMilestones(params)
|
|
47815
|
+
);
|
|
47482
47816
|
server.tool(
|
|
47483
47817
|
"pulse_requests",
|
|
47484
47818
|
"Manage feature requests and feedback. Actions: list (by pod), create, triage (accept/decline/defer/convert/acknowledge).",
|