@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.
Files changed (2) hide show
  1. package/dist/server.js +846 -512
  2. 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, log = "error") {
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[log](`${msg}: option ${key}. ${checkOpts[opt]}`);
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, log = "error") {
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[log](`${msg}: option ${key}. ${checkOpts[opt]}`);
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(client, prefix = "") {
31571
- this.client = 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(client, prefix = "", accessDelegation) {
31640
- this.client = 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: (client) => {
35494
- return { data: client, error: null };
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: (client) => {
35515
- return { data: client, error: null };
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: (client) => {
35537
- return { data: client, error: null };
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: (client) => {
35578
- return { data: client, error: null };
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(client) {
36339
- this.client = 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) updates.slack_channel_id = params.slackChannelId || null;
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, client) => {
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(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
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(client) {
42871
- const logger = client.logger;
42872
- const logLevel = client.logLevel ?? "off";
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, client) {
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, client, "f");
42463
+ __classPrivateFieldSet(this, _Stream_client, client2, "f");
42917
42464
  }
42918
- static fromSSEResponse(response, controller, client) {
42465
+ static fromSSEResponse(response, controller, client2) {
42919
42466
  let consumed = false;
42920
- const logger = client ? loggerFor(client) : console;
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, client);
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, client) {
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, client);
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(client, props) {
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(client).debug("response", response.status, response.url, response.headers, response.body);
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(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
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(client, responsePromise, parseResponse = defaultParseResponse) {
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, client, "f");
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 (client, props) => addRequestID(transform2(await this.parseResponse(client, props), props), props.response));
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(client, response, body, options) {
42821
+ constructor(client2, response, body, options) {
43275
42822
  _AbstractPage_client.set(this, void 0);
43276
- __classPrivateFieldSet(this, _AbstractPage_client, client, "f");
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(client, request, Page2) {
43312
- super(client, request, async (client2, props) => new Page2(client2, props.response, await defaultParseResponse(client2, props), props.options));
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(client, response, body, options) {
43330
- super(client, response, body, options);
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(client, response, body, options) {
43374
- super(client, response, body, options);
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(client) {
43540
- this._client = 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(client, params, options) {
44400
+ constructor(client2, params, options) {
44854
44401
  _BetaToolRunner_instances.add(this);
44855
- this.client = 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 client = new this.constructor({
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 client;
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 client = new Anthropic({ apiKey: anthropicKey });
47228
- const response = await client.messages.create({
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 (project containers). Actions: list, get (by slug), create (detects name conflicts \u2014 use mode 'link' to share or 'create_new' for separate), link (add pod to existing workstream), unlink (remove pod from workstream), update, delete, get_suggestions.",
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).",