@vailent/pulse-mcp 1.10.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 +746 -511
- 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);
|
|
@@ -41865,461 +41865,6 @@ async function handleFeatures(params) {
|
|
|
41865
41865
|
}
|
|
41866
41866
|
}
|
|
41867
41867
|
|
|
41868
|
-
// tools/workstreams.ts
|
|
41869
|
-
async function handleWorkstreams(params) {
|
|
41870
|
-
const supabase = getAdminClient();
|
|
41871
|
-
const action = params.action;
|
|
41872
|
-
switch (action) {
|
|
41873
|
-
case "list": {
|
|
41874
|
-
let query = supabase.from("projects").select("*, project_pods(pod_id, is_primary)").order("name");
|
|
41875
|
-
if (params.status) query = query.eq("status", params.status);
|
|
41876
|
-
else query = query.eq("status", "active");
|
|
41877
|
-
const { data, error: error2 } = await query;
|
|
41878
|
-
if (error2) return err(error2.message);
|
|
41879
|
-
let results = data || [];
|
|
41880
|
-
if (params.podId) {
|
|
41881
|
-
results = results.filter((p) => {
|
|
41882
|
-
const pp = p.project_pods || [];
|
|
41883
|
-
return pp.some((r) => r.pod_id === params.podId) || p.pod_id === params.podId;
|
|
41884
|
-
});
|
|
41885
|
-
}
|
|
41886
|
-
return ok(results, `${results.length} workstreams`, results.length);
|
|
41887
|
-
}
|
|
41888
|
-
case "get": {
|
|
41889
|
-
const slug = params.slug;
|
|
41890
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41891
|
-
const { data, error: error2 } = await supabase.from("projects").select("*, project_pods(pod_id, is_primary)").eq("slug", slug).single();
|
|
41892
|
-
if (error2 || !data) return err("Workstream not found", "NOT_FOUND");
|
|
41893
|
-
return ok(data, `Workstream: ${data.name}`);
|
|
41894
|
-
}
|
|
41895
|
-
case "create": {
|
|
41896
|
-
const name = params.name;
|
|
41897
|
-
const podId = params.podId;
|
|
41898
|
-
const mode = params.mode;
|
|
41899
|
-
if (!name?.trim()) return err("name is required", "MISSING_PARAM");
|
|
41900
|
-
const trimmedName = name.trim();
|
|
41901
|
-
const { data: existing } = await supabase.from("projects").select("id, name, slug, project_pods(pod_id)").eq("name", trimmedName).maybeSingle();
|
|
41902
|
-
if (existing && !mode) {
|
|
41903
|
-
const existingPodIds = existing.project_pods?.map((r) => r.pod_id) || [];
|
|
41904
|
-
if (podId && existingPodIds.includes(podId)) {
|
|
41905
|
-
return ok(existing, `Workstream "${trimmedName}" already linked to this pod`);
|
|
41906
|
-
}
|
|
41907
|
-
return ok(
|
|
41908
|
-
{ conflict: true, existingProject: { id: existing.id, name: existing.name, slug: existing.slug, podIds: existingPodIds } },
|
|
41909
|
-
`Workstream "${trimmedName}" already exists in another pod. Use mode: "link" to share or mode: "create_new" to create a separate one.`
|
|
41910
|
-
);
|
|
41911
|
-
}
|
|
41912
|
-
if (existing && mode === "link") {
|
|
41913
|
-
if (!podId) return err("podId is required to link", "MISSING_PARAM");
|
|
41914
|
-
await supabase.from("project_pods").insert({ project_id: existing.id, pod_id: podId, is_primary: false });
|
|
41915
|
-
return ok(existing, `Linked "${trimmedName}" to pod`);
|
|
41916
|
-
}
|
|
41917
|
-
if (existing && mode === "create_new") {
|
|
41918
|
-
const { data: pod } = podId ? await supabase.from("pods").select("name").eq("id", podId).single() : { data: null };
|
|
41919
|
-
const suffix = pod?.name || podId || "";
|
|
41920
|
-
const newName = suffix ? `${trimmedName} (${suffix})` : trimmedName;
|
|
41921
|
-
const newSlug = newName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
41922
|
-
const { data: data2, error: error3 } = await supabase.from("projects").insert({
|
|
41923
|
-
name: newName,
|
|
41924
|
-
slug: newSlug,
|
|
41925
|
-
description: params.description || null,
|
|
41926
|
-
pod_id: podId || null,
|
|
41927
|
-
color: params.color || "#71717a",
|
|
41928
|
-
status: "active"
|
|
41929
|
-
}).select().single();
|
|
41930
|
-
if (error3) return err(error3.message);
|
|
41931
|
-
if (podId) await supabase.from("project_pods").insert({ project_id: data2.id, pod_id: podId, is_primary: true });
|
|
41932
|
-
return ok(data2, `Created separate workstream: ${newName}`);
|
|
41933
|
-
}
|
|
41934
|
-
const slug = trimmedName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
41935
|
-
const { data, error: error2 } = await supabase.from("projects").insert({
|
|
41936
|
-
name: trimmedName,
|
|
41937
|
-
slug,
|
|
41938
|
-
description: params.description || null,
|
|
41939
|
-
pod_id: podId || null,
|
|
41940
|
-
color: params.color || "#71717a",
|
|
41941
|
-
status: "active"
|
|
41942
|
-
}).select().single();
|
|
41943
|
-
if (error2) return err(error2.message);
|
|
41944
|
-
if (podId) await supabase.from("project_pods").insert({ project_id: data.id, pod_id: podId, is_primary: true });
|
|
41945
|
-
return ok(data, `Created workstream: ${trimmedName}`);
|
|
41946
|
-
}
|
|
41947
|
-
case "link": {
|
|
41948
|
-
const slug = params.slug;
|
|
41949
|
-
const podId = params.podId;
|
|
41950
|
-
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
41951
|
-
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
41952
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41953
|
-
const { error: error2 } = await supabase.from("project_pods").insert({
|
|
41954
|
-
project_id: project.id,
|
|
41955
|
-
pod_id: podId,
|
|
41956
|
-
is_primary: false
|
|
41957
|
-
});
|
|
41958
|
-
if (error2) return err(error2.message);
|
|
41959
|
-
return ok({ projectId: project.id, podId }, `Linked "${project.name}" to pod`);
|
|
41960
|
-
}
|
|
41961
|
-
case "unlink": {
|
|
41962
|
-
const slug = params.slug;
|
|
41963
|
-
const podId = params.podId;
|
|
41964
|
-
if (!slug || !podId) return err("slug and podId are required", "MISSING_PARAM");
|
|
41965
|
-
const { data: project } = await supabase.from("projects").select("id, name").eq("slug", slug).single();
|
|
41966
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41967
|
-
const { error: error2 } = await supabase.from("project_pods").delete().eq("project_id", project.id).eq("pod_id", podId);
|
|
41968
|
-
if (error2) return err(error2.message);
|
|
41969
|
-
return ok({ projectId: project.id, podId }, `Unlinked "${project.name}" from pod`);
|
|
41970
|
-
}
|
|
41971
|
-
case "update": {
|
|
41972
|
-
const slug = params.slug;
|
|
41973
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41974
|
-
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
41975
|
-
if (params.name !== void 0) updates.name = params.name;
|
|
41976
|
-
if (params.description !== void 0) updates.description = params.description;
|
|
41977
|
-
if (params.status !== void 0) updates.status = params.status;
|
|
41978
|
-
const { data, error: error2 } = await supabase.from("projects").update(updates).eq("slug", slug).select().single();
|
|
41979
|
-
if (error2) return err(error2.message);
|
|
41980
|
-
return ok(data, `Updated workstream: ${data?.name}`);
|
|
41981
|
-
}
|
|
41982
|
-
case "delete": {
|
|
41983
|
-
const slug = params.slug;
|
|
41984
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41985
|
-
const { error: error2 } = await supabase.from("projects").delete().eq("slug", slug);
|
|
41986
|
-
if (error2) return err(error2.message);
|
|
41987
|
-
return ok({ deleted: slug }, "Workstream deleted");
|
|
41988
|
-
}
|
|
41989
|
-
case "get_suggestions": {
|
|
41990
|
-
const slug = params.slug;
|
|
41991
|
-
if (!slug) return err("slug is required", "MISSING_PARAM");
|
|
41992
|
-
const { data: project } = await supabase.from("projects").select("name").eq("slug", slug).single();
|
|
41993
|
-
if (!project) return err("Workstream not found", "NOT_FOUND");
|
|
41994
|
-
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);
|
|
41995
|
-
return ok(data || [], `${(data || []).length} suggestions for ${project.name}`, (data || []).length);
|
|
41996
|
-
}
|
|
41997
|
-
default:
|
|
41998
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
41999
|
-
}
|
|
42000
|
-
}
|
|
42001
|
-
|
|
42002
|
-
// tools/requests.ts
|
|
42003
|
-
function getCurrentWeekStart() {
|
|
42004
|
-
return format(startOfWeek(/* @__PURE__ */ new Date(), { weekStartsOn: 1 }), "yyyy-MM-dd");
|
|
42005
|
-
}
|
|
42006
|
-
async function handleRequests(params) {
|
|
42007
|
-
const supabase = getAdminClient();
|
|
42008
|
-
const action = params.action;
|
|
42009
|
-
switch (action) {
|
|
42010
|
-
case "list": {
|
|
42011
|
-
const podId = params.podId;
|
|
42012
|
-
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
42013
|
-
let query = supabase.from("work_items").select("*").eq("pod_id", podId);
|
|
42014
|
-
if (params.type) query = query.eq("type", params.type);
|
|
42015
|
-
if (params.status) query = query.eq("status", params.status);
|
|
42016
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
42017
|
-
const { data, error: error2 } = await query;
|
|
42018
|
-
if (error2) return err(error2.message);
|
|
42019
|
-
return ok(data, `${(data || []).length} requests`, (data || []).length);
|
|
42020
|
-
}
|
|
42021
|
-
case "create": {
|
|
42022
|
-
const podId = params.podId;
|
|
42023
|
-
const title = params.title;
|
|
42024
|
-
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
42025
|
-
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
42026
|
-
id: `f-${Date.now().toString(36)}`,
|
|
42027
|
-
title: title.trim(),
|
|
42028
|
-
description: params.description || null,
|
|
42029
|
-
pod_id: podId,
|
|
42030
|
-
project_name: params.projectName || null,
|
|
42031
|
-
requester_name: params.requesterName || null,
|
|
42032
|
-
source: params.source || "mcp",
|
|
42033
|
-
type: params.requestType || "feature_request",
|
|
42034
|
-
priority: params.priority || null,
|
|
42035
|
-
sentiment: params.sentiment || null,
|
|
42036
|
-
related_feature_title: params.relatedFeatureTitle || null
|
|
42037
|
-
}).select().single();
|
|
42038
|
-
if (error2) return err(error2.message);
|
|
42039
|
-
return ok(data, `Created ${params.requestType || "feature_request"}: ${title}`);
|
|
42040
|
-
}
|
|
42041
|
-
case "triage": {
|
|
42042
|
-
const requestId = params.requestId;
|
|
42043
|
-
const triageAction = params.triageAction;
|
|
42044
|
-
const podId = params.podId;
|
|
42045
|
-
if (!requestId || !triageAction) return err("requestId and triageAction are required", "MISSING_PARAM");
|
|
42046
|
-
if (triageAction === "accept") {
|
|
42047
|
-
const featureId = `f-${Date.now().toString(36)}`;
|
|
42048
|
-
const { data: req } = await supabase.from("work_items").select("title, pod_id").eq("id", requestId).single();
|
|
42049
|
-
if (!req) return err("Request not found", "NOT_FOUND");
|
|
42050
|
-
await supabase.from("work_items").insert({
|
|
42051
|
-
id: featureId,
|
|
42052
|
-
title: req.title,
|
|
42053
|
-
pod_id: req.pod_id,
|
|
42054
|
-
project_name: params.projectName || "",
|
|
42055
|
-
project_color: "#71717a",
|
|
42056
|
-
phase: params.phase || "discovery",
|
|
42057
|
-
track: "dev",
|
|
42058
|
-
status: "in_progress",
|
|
42059
|
-
has_blocker: false,
|
|
42060
|
-
size: "feature",
|
|
42061
|
-
week_start: params.weekStart || null
|
|
42062
|
-
});
|
|
42063
|
-
await supabase.from("work_items").update({
|
|
42064
|
-
status: "accepted",
|
|
42065
|
-
accepted_feature_id: featureId,
|
|
42066
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42067
|
-
}).eq("id", requestId);
|
|
42068
|
-
return ok({ requestId, featureId, action: "accepted" }, `Accepted request \u2192 feature created`);
|
|
42069
|
-
}
|
|
42070
|
-
const statusMap = {
|
|
42071
|
-
decline: "declined",
|
|
42072
|
-
defer: "deferred",
|
|
42073
|
-
acknowledge: "acknowledged",
|
|
42074
|
-
convert: "open"
|
|
42075
|
-
};
|
|
42076
|
-
const newStatus = statusMap[triageAction];
|
|
42077
|
-
if (!newStatus) return err(`Unknown triage action: ${triageAction}`, "INVALID_ACTION");
|
|
42078
|
-
const updates = { status: newStatus, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42079
|
-
if (triageAction === "convert") updates.type = "feature_request";
|
|
42080
|
-
await supabase.from("work_items").update(updates).eq("id", requestId);
|
|
42081
|
-
return ok({ requestId, action: triageAction }, `Request ${triageAction}d`);
|
|
42082
|
-
}
|
|
42083
|
-
default:
|
|
42084
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42085
|
-
}
|
|
42086
|
-
}
|
|
42087
|
-
async function handleBugs(params) {
|
|
42088
|
-
const supabase = getAdminClient();
|
|
42089
|
-
const action = params.action;
|
|
42090
|
-
switch (action) {
|
|
42091
|
-
case "list": {
|
|
42092
|
-
let query = supabase.from("work_items").select("*").eq("type", "bug");
|
|
42093
|
-
if (params.podId) query = query.eq("pod_id", params.podId);
|
|
42094
|
-
if (params.status) query = query.eq("status", params.status);
|
|
42095
|
-
else query = query.in("status", ["open", "in_progress"]);
|
|
42096
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 50);
|
|
42097
|
-
const { data, error: error2 } = await query;
|
|
42098
|
-
if (error2) return err(error2.message);
|
|
42099
|
-
return ok(data, `${(data || []).length} bugs`, (data || []).length);
|
|
42100
|
-
}
|
|
42101
|
-
case "create": {
|
|
42102
|
-
const podId = params.podId;
|
|
42103
|
-
const title = params.title;
|
|
42104
|
-
if (!podId || !title?.trim()) return err("podId and title are required", "MISSING_PARAM");
|
|
42105
|
-
const { data, error: error2 } = await supabase.from("work_items").insert({
|
|
42106
|
-
id: `f-${Date.now().toString(36)}`,
|
|
42107
|
-
title: title.trim(),
|
|
42108
|
-
description: params.description || null,
|
|
42109
|
-
pod_id: podId,
|
|
42110
|
-
project_name: params.projectName || null,
|
|
42111
|
-
source: params.source || "mcp",
|
|
42112
|
-
type: "bug",
|
|
42113
|
-
priority: params.priority || "medium",
|
|
42114
|
-
status: "open"
|
|
42115
|
-
}).select().single();
|
|
42116
|
-
if (error2) return err(error2.message);
|
|
42117
|
-
return ok(data, `Created bug: ${title}`);
|
|
42118
|
-
}
|
|
42119
|
-
case "update": {
|
|
42120
|
-
const bugId = params.bugId;
|
|
42121
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42122
|
-
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();
|
|
42123
|
-
const raw = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42124
|
-
if (params.title !== void 0) raw.title = params.title;
|
|
42125
|
-
if (params.description !== void 0) raw.description = params.description;
|
|
42126
|
-
if (params.priority !== void 0) raw.priority = params.priority;
|
|
42127
|
-
if (params.status !== void 0) raw.status = params.status;
|
|
42128
|
-
if (params.podId !== void 0) raw.pod_id = params.podId;
|
|
42129
|
-
if (params.projectName !== void 0) raw.project_name = params.projectName;
|
|
42130
|
-
let autoPromoted = false;
|
|
42131
|
-
if (currentBug?.status === "open" && !currentBug.week_start && !params.status) {
|
|
42132
|
-
raw.status = "in_progress";
|
|
42133
|
-
raw.week_start = getCurrentWeekStart();
|
|
42134
|
-
autoPromoted = true;
|
|
42135
|
-
}
|
|
42136
|
-
const updates = normalizeWorkItemState(raw, currentBug || void 0);
|
|
42137
|
-
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
42138
|
-
if (error2) return err(error2.message);
|
|
42139
|
-
if (autoPromoted) {
|
|
42140
|
-
await supabase.from("events").insert({
|
|
42141
|
-
source: "mcp",
|
|
42142
|
-
event_type: "bug_promoted",
|
|
42143
|
-
raw_payload: { bugId, trigger: "update" },
|
|
42144
|
-
ai_extraction: { bugTitle: currentBug.title, podId: currentBug.pod_id, weekStart: updates.week_start },
|
|
42145
|
-
processed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42146
|
-
});
|
|
42147
|
-
}
|
|
42148
|
-
return ok({ id: bugId, autoPromoted }, autoPromoted ? "Bug updated \u2014 auto-scheduled to this week" : "Bug updated");
|
|
42149
|
-
}
|
|
42150
|
-
case "delete": {
|
|
42151
|
-
const bugId = params.bugId;
|
|
42152
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42153
|
-
const { error: error2 } = await supabase.from("work_items").delete().eq("id", bugId);
|
|
42154
|
-
if (error2) return err(error2.message);
|
|
42155
|
-
return ok({ deleted: bugId }, "Bug deleted");
|
|
42156
|
-
}
|
|
42157
|
-
case "schedule": {
|
|
42158
|
-
const bugId = params.bugId;
|
|
42159
|
-
if (!bugId) return err("bugId is required", "MISSING_PARAM");
|
|
42160
|
-
const updates = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42161
|
-
if (params.weekStart !== void 0) updates.week_start = params.weekStart;
|
|
42162
|
-
if (params.sortOrder !== void 0) updates.sort_order = params.sortOrder;
|
|
42163
|
-
if (params.weekStart) {
|
|
42164
|
-
const { data: bug } = await supabase.from("work_items").select("status").eq("id", bugId).single();
|
|
42165
|
-
if (bug?.status === "open") updates.status = "in_progress";
|
|
42166
|
-
}
|
|
42167
|
-
const { error: error2 } = await supabase.from("work_items").update(updates).eq("id", bugId);
|
|
42168
|
-
if (error2) return err(error2.message);
|
|
42169
|
-
return ok({ id: bugId, ...updates }, `Bug scheduled${params.weekStart ? ` to week of ${params.weekStart}` : ""}`);
|
|
42170
|
-
}
|
|
42171
|
-
default:
|
|
42172
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42173
|
-
}
|
|
42174
|
-
}
|
|
42175
|
-
|
|
42176
|
-
// tools/suggestions.ts
|
|
42177
|
-
async function handleSuggestions(params) {
|
|
42178
|
-
const supabase = getAdminClient();
|
|
42179
|
-
const action = params.action;
|
|
42180
|
-
switch (action) {
|
|
42181
|
-
case "list": {
|
|
42182
|
-
const { data, error: error2 } = await supabase.from("ai_suggestions").select("*").eq("status", "pending").order("created_at", { ascending: false }).limit(params.limit || 20);
|
|
42183
|
-
if (error2) return err(error2.message);
|
|
42184
|
-
return ok(data, `${(data || []).length} pending suggestions`, (data || []).length);
|
|
42185
|
-
}
|
|
42186
|
-
case "approve": {
|
|
42187
|
-
const suggestionId = params.suggestionId;
|
|
42188
|
-
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
42189
|
-
const { data: suggestion } = await supabase.from("ai_suggestions").select("*").eq("id", suggestionId).single();
|
|
42190
|
-
if (!suggestion) return err("Suggestion not found", "NOT_FOUND");
|
|
42191
|
-
await supabase.from("ai_suggestions").update({
|
|
42192
|
-
status: "approved",
|
|
42193
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42194
|
-
}).eq("id", suggestionId);
|
|
42195
|
-
return ok({ id: suggestionId, status: "approved" }, `Suggestion approved: ${suggestion.title}`);
|
|
42196
|
-
}
|
|
42197
|
-
case "reject": {
|
|
42198
|
-
const suggestionId = params.suggestionId;
|
|
42199
|
-
if (!suggestionId) return err("suggestionId is required", "MISSING_PARAM");
|
|
42200
|
-
await supabase.from("ai_suggestions").update({
|
|
42201
|
-
status: "rejected",
|
|
42202
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42203
|
-
}).eq("id", suggestionId);
|
|
42204
|
-
return ok({ id: suggestionId, status: "rejected" }, "Suggestion rejected");
|
|
42205
|
-
}
|
|
42206
|
-
case "correct": {
|
|
42207
|
-
const suggestionId = params.suggestionId;
|
|
42208
|
-
const correction = params.correction;
|
|
42209
|
-
if (!suggestionId || !correction?.trim()) return err("suggestionId and correction are required", "MISSING_PARAM");
|
|
42210
|
-
await supabase.from("ai_suggestions").update({
|
|
42211
|
-
status: "corrected",
|
|
42212
|
-
reviewed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
42213
|
-
}).eq("id", suggestionId);
|
|
42214
|
-
return ok({ id: suggestionId, correction: correction.trim() }, "Suggestion corrected");
|
|
42215
|
-
}
|
|
42216
|
-
default:
|
|
42217
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42218
|
-
}
|
|
42219
|
-
}
|
|
42220
|
-
|
|
42221
|
-
// tools/events.ts
|
|
42222
|
-
async function handleEvents(params) {
|
|
42223
|
-
const supabase = getAdminClient();
|
|
42224
|
-
const action = params.action;
|
|
42225
|
-
switch (action) {
|
|
42226
|
-
case "list": {
|
|
42227
|
-
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);
|
|
42228
|
-
if (error2) return err(error2.message);
|
|
42229
|
-
return ok(data, `${(data || []).length} events`, (data || []).length);
|
|
42230
|
-
}
|
|
42231
|
-
case "list_by_feature": {
|
|
42232
|
-
const featureTitle = params.featureTitle;
|
|
42233
|
-
const podId = params.podId;
|
|
42234
|
-
if (!featureTitle) return err("featureTitle is required", "MISSING_PARAM");
|
|
42235
|
-
let query = supabase.from("events").select("id, source, event_type, ai_extraction, created_at").filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
42236
|
-
if (podId) query = query.filter("ai_extraction->>podId", "eq", podId);
|
|
42237
|
-
query = query.order("created_at", { ascending: false }).limit(params.limit || 10);
|
|
42238
|
-
const { data, error: error2 } = await query;
|
|
42239
|
-
if (error2) return err(error2.message);
|
|
42240
|
-
return ok(data, `${(data || []).length} events for "${featureTitle}"`, (data || []).length);
|
|
42241
|
-
}
|
|
42242
|
-
case "list_by_pod": {
|
|
42243
|
-
const podId = params.podId;
|
|
42244
|
-
if (!podId) return err("podId is required", "MISSING_PARAM");
|
|
42245
|
-
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);
|
|
42246
|
-
if (error2) return err(error2.message);
|
|
42247
|
-
return ok(data, `${(data || []).length} events for pod ${podId}`, (data || []).length);
|
|
42248
|
-
}
|
|
42249
|
-
case "list_prs": {
|
|
42250
|
-
const featureTitle = params.featureTitle;
|
|
42251
|
-
const shortCode = params.shortCode;
|
|
42252
|
-
if (!featureTitle && !shortCode) return err("featureTitle or shortCode is required", "MISSING_PARAM");
|
|
42253
|
-
let query = supabase.from("events").select("event_type, raw_payload, ai_extraction, created_at").eq("source", "github").in("event_type", ["pr_opened", "pr_merged"]);
|
|
42254
|
-
if (shortCode) {
|
|
42255
|
-
query = query.filter("ai_extraction->>linked_short_code", "eq", shortCode);
|
|
42256
|
-
} else {
|
|
42257
|
-
query = query.filter("ai_extraction->>featureTitle", "eq", featureTitle);
|
|
42258
|
-
}
|
|
42259
|
-
query = query.order("created_at", { ascending: false }).limit(50);
|
|
42260
|
-
const { data, error: error2 } = await query;
|
|
42261
|
-
if (error2) return err(error2.message);
|
|
42262
|
-
const opened = /* @__PURE__ */ new Set();
|
|
42263
|
-
const merged = /* @__PURE__ */ new Set();
|
|
42264
|
-
const prs = [];
|
|
42265
|
-
for (const ev of data || []) {
|
|
42266
|
-
const prNum = ev.raw_payload?.pr_number;
|
|
42267
|
-
if (prNum == null) continue;
|
|
42268
|
-
if (ev.event_type === "pr_opened") opened.add(prNum);
|
|
42269
|
-
if (ev.event_type === "pr_merged") merged.add(prNum);
|
|
42270
|
-
}
|
|
42271
|
-
const seen = /* @__PURE__ */ new Set();
|
|
42272
|
-
for (const ev of data || []) {
|
|
42273
|
-
const prNum = ev.raw_payload?.pr_number;
|
|
42274
|
-
if (prNum == null || seen.has(prNum)) continue;
|
|
42275
|
-
seen.add(prNum);
|
|
42276
|
-
prs.push({
|
|
42277
|
-
number: prNum,
|
|
42278
|
-
title: ev.raw_payload?.pr_title || ev.ai_extraction?.whatWasBuilt || "",
|
|
42279
|
-
status: merged.has(prNum) ? "merged" : "open",
|
|
42280
|
-
repo: ev.ai_extraction?.repo || "",
|
|
42281
|
-
date: ev.created_at
|
|
42282
|
-
});
|
|
42283
|
-
}
|
|
42284
|
-
return ok(
|
|
42285
|
-
{ prs, totalOpened: opened.size, totalMerged: merged.size, unmerged: opened.size - merged.size },
|
|
42286
|
-
`${prs.length} PR(s): ${merged.size} merged, ${opened.size - merged.size} open`,
|
|
42287
|
-
prs.length
|
|
42288
|
-
);
|
|
42289
|
-
}
|
|
42290
|
-
default:
|
|
42291
|
-
return err(`Unknown action: ${action}`, "INVALID_ACTION");
|
|
42292
|
-
}
|
|
42293
|
-
}
|
|
42294
|
-
async function handleHistory(params) {
|
|
42295
|
-
const supabase = getAdminClient();
|
|
42296
|
-
const days = params.days || 90;
|
|
42297
|
-
const limit = params.limit || 50;
|
|
42298
|
-
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
42299
|
-
const items = [];
|
|
42300
|
-
if (!params.type || params.type === "feature") {
|
|
42301
|
-
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);
|
|
42302
|
-
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
42303
|
-
if (params.projectName) q = q.eq("project_name", params.projectName);
|
|
42304
|
-
const { data } = await q;
|
|
42305
|
-
for (const f of data || []) {
|
|
42306
|
-
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 });
|
|
42307
|
-
}
|
|
42308
|
-
}
|
|
42309
|
-
if (!params.type || ["bug", "request", "feedback"].includes(params.type)) {
|
|
42310
|
-
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);
|
|
42311
|
-
if (params.podId) q = q.eq("pod_id", params.podId);
|
|
42312
|
-
const { data } = await q;
|
|
42313
|
-
for (const r of data || []) {
|
|
42314
|
-
const t = r.type === "feature_request" ? "request" : r.type === "bug" ? "bug" : "feedback";
|
|
42315
|
-
if (params.type && params.type !== t) continue;
|
|
42316
|
-
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 });
|
|
42317
|
-
}
|
|
42318
|
-
}
|
|
42319
|
-
items.sort((a, b) => (b.date || "").localeCompare(a.date || ""));
|
|
42320
|
-
return ok(items.slice(0, limit), `${items.length} history items`, items.length);
|
|
42321
|
-
}
|
|
42322
|
-
|
|
42323
41868
|
// ../../node_modules/@anthropic-ai/sdk/internal/tslib.mjs
|
|
42324
41869
|
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
42325
41870
|
if (kind === "m")
|
|
@@ -42843,14 +42388,14 @@ var levelNumbers = {
|
|
|
42843
42388
|
info: 400,
|
|
42844
42389
|
debug: 500
|
|
42845
42390
|
};
|
|
42846
|
-
var parseLogLevel = (maybeLevel, sourceName,
|
|
42391
|
+
var parseLogLevel = (maybeLevel, sourceName, client2) => {
|
|
42847
42392
|
if (!maybeLevel) {
|
|
42848
42393
|
return void 0;
|
|
42849
42394
|
}
|
|
42850
42395
|
if (hasOwn(levelNumbers, maybeLevel)) {
|
|
42851
42396
|
return maybeLevel;
|
|
42852
42397
|
}
|
|
42853
|
-
loggerFor(
|
|
42398
|
+
loggerFor(client2).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
|
|
42854
42399
|
return void 0;
|
|
42855
42400
|
};
|
|
42856
42401
|
function noop3() {
|
|
@@ -42869,9 +42414,9 @@ var noopLogger = {
|
|
|
42869
42414
|
debug: noop3
|
|
42870
42415
|
};
|
|
42871
42416
|
var cachedLoggers = /* @__PURE__ */ new WeakMap();
|
|
42872
|
-
function loggerFor(
|
|
42873
|
-
const logger =
|
|
42874
|
-
const logLevel =
|
|
42417
|
+
function loggerFor(client2) {
|
|
42418
|
+
const logger = client2.logger;
|
|
42419
|
+
const logLevel = client2.logLevel ?? "off";
|
|
42875
42420
|
if (!logger) {
|
|
42876
42421
|
return noopLogger;
|
|
42877
42422
|
}
|
|
@@ -42911,15 +42456,15 @@ var formatRequestDetails = (details) => {
|
|
|
42911
42456
|
// ../../node_modules/@anthropic-ai/sdk/core/streaming.mjs
|
|
42912
42457
|
var _Stream_client;
|
|
42913
42458
|
var Stream = class _Stream {
|
|
42914
|
-
constructor(iterator, controller,
|
|
42459
|
+
constructor(iterator, controller, client2) {
|
|
42915
42460
|
this.iterator = iterator;
|
|
42916
42461
|
_Stream_client.set(this, void 0);
|
|
42917
42462
|
this.controller = controller;
|
|
42918
|
-
__classPrivateFieldSet(this, _Stream_client,
|
|
42463
|
+
__classPrivateFieldSet(this, _Stream_client, client2, "f");
|
|
42919
42464
|
}
|
|
42920
|
-
static fromSSEResponse(response, controller,
|
|
42465
|
+
static fromSSEResponse(response, controller, client2) {
|
|
42921
42466
|
let consumed = false;
|
|
42922
|
-
const logger =
|
|
42467
|
+
const logger = client2 ? loggerFor(client2) : console;
|
|
42923
42468
|
async function* iterator() {
|
|
42924
42469
|
if (consumed) {
|
|
42925
42470
|
throw new AnthropicError("Cannot iterate over a consumed stream, use `.tee()` to split the stream.");
|
|
@@ -42963,13 +42508,13 @@ var Stream = class _Stream {
|
|
|
42963
42508
|
controller.abort();
|
|
42964
42509
|
}
|
|
42965
42510
|
}
|
|
42966
|
-
return new _Stream(iterator, controller,
|
|
42511
|
+
return new _Stream(iterator, controller, client2);
|
|
42967
42512
|
}
|
|
42968
42513
|
/**
|
|
42969
42514
|
* Generates a Stream from a newline-separated ReadableStream
|
|
42970
42515
|
* where each item is a JSON value.
|
|
42971
42516
|
*/
|
|
42972
|
-
static fromReadableStream(readableStream, controller,
|
|
42517
|
+
static fromReadableStream(readableStream, controller, client2) {
|
|
42973
42518
|
let consumed = false;
|
|
42974
42519
|
async function* iterLines() {
|
|
42975
42520
|
const lineDecoder = new LineDecoder();
|
|
@@ -43006,7 +42551,7 @@ var Stream = class _Stream {
|
|
|
43006
42551
|
controller.abort();
|
|
43007
42552
|
}
|
|
43008
42553
|
}
|
|
43009
|
-
return new _Stream(iterator, controller,
|
|
42554
|
+
return new _Stream(iterator, controller, client2);
|
|
43010
42555
|
}
|
|
43011
42556
|
[(_Stream_client = /* @__PURE__ */ new WeakMap(), Symbol.asyncIterator)]() {
|
|
43012
42557
|
return this.iterator();
|
|
@@ -43158,11 +42703,11 @@ function partition(str, delimiter) {
|
|
|
43158
42703
|
}
|
|
43159
42704
|
|
|
43160
42705
|
// ../../node_modules/@anthropic-ai/sdk/internal/parse.mjs
|
|
43161
|
-
async function defaultParseResponse(
|
|
42706
|
+
async function defaultParseResponse(client2, props) {
|
|
43162
42707
|
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
|
|
43163
42708
|
const body = await (async () => {
|
|
43164
42709
|
if (props.options.stream) {
|
|
43165
|
-
loggerFor(
|
|
42710
|
+
loggerFor(client2).debug("response", response.status, response.url, response.headers, response.body);
|
|
43166
42711
|
if (props.options.__streamClass) {
|
|
43167
42712
|
return props.options.__streamClass.fromSSEResponse(response, props.controller);
|
|
43168
42713
|
}
|
|
@@ -43188,7 +42733,7 @@ async function defaultParseResponse(client, props) {
|
|
|
43188
42733
|
const text = await response.text();
|
|
43189
42734
|
return text;
|
|
43190
42735
|
})();
|
|
43191
|
-
loggerFor(
|
|
42736
|
+
loggerFor(client2).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
|
|
43192
42737
|
retryOfRequestLogID,
|
|
43193
42738
|
url: response.url,
|
|
43194
42739
|
status: response.status,
|
|
@@ -43210,17 +42755,17 @@ function addRequestID(value, response) {
|
|
|
43210
42755
|
// ../../node_modules/@anthropic-ai/sdk/core/api-promise.mjs
|
|
43211
42756
|
var _APIPromise_client;
|
|
43212
42757
|
var APIPromise = class _APIPromise extends Promise {
|
|
43213
|
-
constructor(
|
|
42758
|
+
constructor(client2, responsePromise, parseResponse = defaultParseResponse) {
|
|
43214
42759
|
super((resolve) => {
|
|
43215
42760
|
resolve(null);
|
|
43216
42761
|
});
|
|
43217
42762
|
this.responsePromise = responsePromise;
|
|
43218
42763
|
this.parseResponse = parseResponse;
|
|
43219
42764
|
_APIPromise_client.set(this, void 0);
|
|
43220
|
-
__classPrivateFieldSet(this, _APIPromise_client,
|
|
42765
|
+
__classPrivateFieldSet(this, _APIPromise_client, client2, "f");
|
|
43221
42766
|
}
|
|
43222
42767
|
_thenUnwrap(transform2) {
|
|
43223
|
-
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));
|
|
43224
42769
|
}
|
|
43225
42770
|
/**
|
|
43226
42771
|
* Gets the raw `Response` instance instead of parsing the response
|
|
@@ -43273,9 +42818,9 @@ _APIPromise_client = /* @__PURE__ */ new WeakMap();
|
|
|
43273
42818
|
// ../../node_modules/@anthropic-ai/sdk/core/pagination.mjs
|
|
43274
42819
|
var _AbstractPage_client;
|
|
43275
42820
|
var AbstractPage = class {
|
|
43276
|
-
constructor(
|
|
42821
|
+
constructor(client2, response, body, options) {
|
|
43277
42822
|
_AbstractPage_client.set(this, void 0);
|
|
43278
|
-
__classPrivateFieldSet(this, _AbstractPage_client,
|
|
42823
|
+
__classPrivateFieldSet(this, _AbstractPage_client, client2, "f");
|
|
43279
42824
|
this.options = options;
|
|
43280
42825
|
this.response = response;
|
|
43281
42826
|
this.body = body;
|
|
@@ -43310,8 +42855,8 @@ var AbstractPage = class {
|
|
|
43310
42855
|
}
|
|
43311
42856
|
};
|
|
43312
42857
|
var PagePromise = class extends APIPromise {
|
|
43313
|
-
constructor(
|
|
43314
|
-
super(
|
|
42858
|
+
constructor(client2, request, Page2) {
|
|
42859
|
+
super(client2, request, async (client3, props) => new Page2(client3, props.response, await defaultParseResponse(client3, props), props.options));
|
|
43315
42860
|
}
|
|
43316
42861
|
/**
|
|
43317
42862
|
* Allow auto-paginating iteration on an unawaited list call, eg:
|
|
@@ -43328,8 +42873,8 @@ var PagePromise = class extends APIPromise {
|
|
|
43328
42873
|
}
|
|
43329
42874
|
};
|
|
43330
42875
|
var Page = class extends AbstractPage {
|
|
43331
|
-
constructor(
|
|
43332
|
-
super(
|
|
42876
|
+
constructor(client2, response, body, options) {
|
|
42877
|
+
super(client2, response, body, options);
|
|
43333
42878
|
this.data = body.data || [];
|
|
43334
42879
|
this.has_more = body.has_more || false;
|
|
43335
42880
|
this.first_id = body.first_id || null;
|
|
@@ -43372,8 +42917,8 @@ var Page = class extends AbstractPage {
|
|
|
43372
42917
|
}
|
|
43373
42918
|
};
|
|
43374
42919
|
var PageCursor = class extends AbstractPage {
|
|
43375
|
-
constructor(
|
|
43376
|
-
super(
|
|
42920
|
+
constructor(client2, response, body, options) {
|
|
42921
|
+
super(client2, response, body, options);
|
|
43377
42922
|
this.data = body.data || [];
|
|
43378
42923
|
this.has_more = body.has_more || false;
|
|
43379
42924
|
this.next_page = body.next_page || null;
|
|
@@ -43538,8 +43083,8 @@ function propsForError(value) {
|
|
|
43538
43083
|
|
|
43539
43084
|
// ../../node_modules/@anthropic-ai/sdk/core/resource.mjs
|
|
43540
43085
|
var APIResource = class {
|
|
43541
|
-
constructor(
|
|
43542
|
-
this._client =
|
|
43086
|
+
constructor(client2) {
|
|
43087
|
+
this._client = client2;
|
|
43543
43088
|
}
|
|
43544
43089
|
};
|
|
43545
43090
|
|
|
@@ -44852,9 +44397,9 @@ function promiseWithResolvers() {
|
|
|
44852
44397
|
return { promise, resolve, reject };
|
|
44853
44398
|
}
|
|
44854
44399
|
var BetaToolRunner = class {
|
|
44855
|
-
constructor(
|
|
44400
|
+
constructor(client2, params, options) {
|
|
44856
44401
|
_BetaToolRunner_instances.add(this);
|
|
44857
|
-
this.client =
|
|
44402
|
+
this.client = client2;
|
|
44858
44403
|
_BetaToolRunner_consumed.set(this, false);
|
|
44859
44404
|
_BetaToolRunner_mutated.set(this, false);
|
|
44860
44405
|
_BetaToolRunner_state.set(this, void 0);
|
|
@@ -46737,7 +46282,7 @@ var BaseAnthropic = class {
|
|
|
46737
46282
|
* Create a new client instance re-using the same options given to the current client with optional overriding.
|
|
46738
46283
|
*/
|
|
46739
46284
|
withOptions(options) {
|
|
46740
|
-
const
|
|
46285
|
+
const client2 = new this.constructor({
|
|
46741
46286
|
...this._options,
|
|
46742
46287
|
baseURL: this.baseURL,
|
|
46743
46288
|
maxRetries: this.maxRetries,
|
|
@@ -46750,7 +46295,7 @@ var BaseAnthropic = class {
|
|
|
46750
46295
|
authToken: this.authToken,
|
|
46751
46296
|
...options
|
|
46752
46297
|
});
|
|
46753
|
-
return
|
|
46298
|
+
return client2;
|
|
46754
46299
|
}
|
|
46755
46300
|
defaultQuery() {
|
|
46756
46301
|
return this._options.defaultQuery;
|
|
@@ -47163,6 +46708,693 @@ Anthropic.Messages = Messages2;
|
|
|
47163
46708
|
Anthropic.Models = Models2;
|
|
47164
46709
|
Anthropic.Beta = Beta;
|
|
47165
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
|
+
|
|
47166
47398
|
// tools/briefing.ts
|
|
47167
47399
|
function getWeekStart(date3) {
|
|
47168
47400
|
return startOfWeek(date3, { weekStartsOn: 1 });
|
|
@@ -47226,8 +47458,8 @@ async function handleBriefing(params) {
|
|
|
47226
47458
|
}))
|
|
47227
47459
|
}, `Week of ${thisWeekKey}: ${thisWeekFeatures.length} features, ${(bugs || []).length} bugs, ${(requests || []).length} requests \u2014 set ANTHROPIC_API_KEY for AI briefing`);
|
|
47228
47460
|
}
|
|
47229
|
-
const
|
|
47230
|
-
const response = await
|
|
47461
|
+
const client2 = new Anthropic({ apiKey: anthropicKey });
|
|
47462
|
+
const response = await client2.messages.create({
|
|
47231
47463
|
model: "claude-sonnet-4-20250514",
|
|
47232
47464
|
max_tokens: 1500,
|
|
47233
47465
|
system: `You are Pulse, an AI project management assistant. Generate a concise daily briefing for a ${jobTitle}.
|
|
@@ -47549,16 +47781,19 @@ server.tool(
|
|
|
47549
47781
|
);
|
|
47550
47782
|
server.tool(
|
|
47551
47783
|
"pulse_workstreams",
|
|
47552
|
-
"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).",
|
|
47553
47785
|
{
|
|
47554
|
-
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"]),
|
|
47555
47787
|
slug: external_exports.string().optional().describe("Workstream slug"),
|
|
47556
47788
|
podId: external_exports.string().optional().describe("Pod ID filter / target pod for link/unlink"),
|
|
47557
47789
|
name: external_exports.string().optional().describe("Workstream name"),
|
|
47558
|
-
description: external_exports.string().optional().describe("Description"),
|
|
47790
|
+
description: external_exports.string().optional().describe("Description / PRD text"),
|
|
47559
47791
|
color: external_exports.string().optional().describe("Hex color"),
|
|
47560
47792
|
status: external_exports.string().optional().describe("Status: active, archived, completed"),
|
|
47561
|
-
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")
|
|
47562
47797
|
},
|
|
47563
47798
|
async (params) => handleWorkstreams(params)
|
|
47564
47799
|
);
|