@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.
Files changed (2) hide show
  1. package/dist/server.js +746 -511
  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);
@@ -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, client) => {
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(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))}`);
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(client) {
42873
- const logger = client.logger;
42874
- const logLevel = client.logLevel ?? "off";
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, client) {
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, client, "f");
42463
+ __classPrivateFieldSet(this, _Stream_client, client2, "f");
42919
42464
  }
42920
- static fromSSEResponse(response, controller, client) {
42465
+ static fromSSEResponse(response, controller, client2) {
42921
42466
  let consumed = false;
42922
- const logger = client ? loggerFor(client) : console;
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, client);
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, client) {
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, client);
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(client, props) {
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(client).debug("response", response.status, response.url, response.headers, response.body);
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(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
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(client, responsePromise, parseResponse = defaultParseResponse) {
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, client, "f");
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 (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));
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(client, response, body, options) {
42821
+ constructor(client2, response, body, options) {
43277
42822
  _AbstractPage_client.set(this, void 0);
43278
- __classPrivateFieldSet(this, _AbstractPage_client, client, "f");
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(client, request, Page2) {
43314
- 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));
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(client, response, body, options) {
43332
- super(client, response, body, options);
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(client, response, body, options) {
43376
- super(client, response, body, options);
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(client) {
43542
- this._client = 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(client, params, options) {
44400
+ constructor(client2, params, options) {
44856
44401
  _BetaToolRunner_instances.add(this);
44857
- this.client = 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 client = new this.constructor({
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 client;
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 client = new Anthropic({ apiKey: anthropicKey });
47230
- const response = await client.messages.create({
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 (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).",
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
  );