@zibby/skills 0.1.70 → 0.1.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -69,6 +69,10 @@
69
69
  "./sentry": {
70
70
  "types": "./dist/sentry.d.ts",
71
71
  "default": "./dist/sentry.js"
72
+ },
73
+ "./review": {
74
+ "types": "./dist/review.d.ts",
75
+ "default": "./dist/review.js"
72
76
  }
73
77
  },
74
78
  "scripts": {
@@ -0,0 +1,2 @@
1
+ export { REVIEW_RECORD_SCHEMA_VERSION, REVIEW_RECORD_KIND, CONTENT_MAX_BYTES, FIELD_MAX_CHARS, SEVERITY_TIERS, FINDING_STATUSES, normalizeSeverity, buildReviewRecord, upsertReplyOutcome, serializeReviewRecord, parseReviewMemory, summarizeForPrompt } from "./reviewRecord.js";
2
+ export { scopeForReviewMemory, storeReviewRecord, recallReviewRecord } from "./reviewMemoryIo.js";
package/dist/review.js ADDED
@@ -0,0 +1,4 @@
1
+ var A=1,l="review-record",O=204800,T=2e3,I=["blocker","should-fix","nit"],_={blocker:0,"should-fix":1,nit:2},g=["open","conceded","held","resolved"];function v(e){let r=String(e??"").trim().toLowerCase();return r?r.includes("\u{1F534}")||r.includes("blocker")||r.includes("critical")||r.includes("high")?"blocker":r.includes("\u{1F7E2}")||r.includes("nit")||r.includes("minor")||r.includes("low")||r.includes("info")?"nit":(r.includes("\u{1F7E1}")||r.includes("should")||r.includes("medium")||r.includes("warn"),"should-fix"):"should-fix"}var c=(e,r)=>{let n=e==null?"":String(e);return n.length>r?n.slice(0,r):n},E=e=>typeof Buffer<"u"&&typeof Buffer.byteLength=="function"?Buffer.byteLength(e,"utf8"):new TextEncoder().encode(e).length;function k({headSha:e=null,verdict:r="COMMENT",objectivesChecked:n=!1,findings:i=[],nowIso:t=null}={}){let s=Array.isArray(i)?i:[];return{schemaVersion:1,kind:l,headSha:e||null,verdict:String(r||"COMMENT"),objectivesChecked:!!n,reviewedAt:t||null,findings:s.map((o,u)=>({id:`f${u+1}`,file:c(o?.file,512),line:o?.line==null?null:o.line,severity:v(o?.severity),category:c(o?.category,64),claim:c(o?.claim,2e3),evidence:c(o?.evidence,2e3),suggestion:c(o?.suggestion,2e3),confidence:typeof o?.confidence=="number"?o.confidence:null,status:"open"}))}}function C(e,{findingId:r,status:n,note:i}={}){if(!e||!Array.isArray(e.findings))return e;let t=g.includes(n)?n:"held";return{...e,findings:e.findings.map(s=>s.id===r?{...s,status:t,replyNote:c(i,2e3)}:s)}}function a(e){if(!e)return"";let r=[...e.findings||[]].sort((s,o)=>{let u=(_[s.severity]??9)-(_[o.severity]??9);if(u!==0)return u;let y=s.file||"",p=o.file||"";if(y!==p)return y<p?-1:1;let m=Number(s.line)||0,R=Number(o.line)||0;return m!==R?m-R:(s.id||"")<(o.id||"")?-1:1}),n={...e,findings:r},i=JSON.stringify(n);if(E(i)<=204800)return i;let t=[...r];for(n={...e,findings:t,truncated:!0};t.length>0&&E(JSON.stringify(n))>204800;)t.pop(),n={...e,findings:t,truncated:!0};return JSON.stringify(n)}var S=e=>e&&typeof e=="object"&&!Array.isArray(e)&&e.kind===l;function d(e){if(e==null)return{kind:"empty"};if(typeof e=="object"){if(!Array.isArray(e)&&typeof e.error=="string")return{kind:"empty"};if(S(e)){let i=Number(e.schemaVersion)>1;return{kind:"record",record:e,future:i}}return{kind:"legacy",legacyNote:h(e)}}if(typeof e!="string")return{kind:"empty"};let r=e.trim();if(!r)return{kind:"empty"};let n;try{n=JSON.parse(r)}catch{return{kind:"legacy",legacyNote:r}}if(n&&typeof n=="object"&&typeof n.error=="string")return{kind:"empty"};if(S(n)){let i=Number(n.schemaVersion)>1;return{kind:"record",record:n,future:i}}return{kind:"legacy",legacyNote:r}}function h(e){try{return JSON.stringify(e)}catch{return String(e)}}function w(e){if(!e||e.kind==="empty")return"";if(e.kind==="legacy")return`Prior review memory (legacy note, advisory only):
2
+ ${e.legacyNote}`;let r=e.record||{};if(e.future)return`Prior review memory (newer format ${r.schemaVersion} \u2014 advisory; verify against the live thread):
3
+ ${h(r).slice(0,2e3)}`;let n=[`Prior review of this change (verdict: ${r.verdict||"COMMENT"}${r.headSha?`, commit ${r.headSha}`:""}):`],i=(r.findings||[]).map(t=>{let s=t.file?`${t.file}${t.line!=null?`:${t.line}`:""}`:"(general)",o=t.status&&t.status!=="open"?` [${t.status}]`:"";return`- [${t.severity}] ${s} \u2014 ${t.claim||""}${o}`});return r.truncated&&i.push("- (\u2026older/lower-severity findings were truncated to fit memory)"),[...n,...i].join(`
4
+ `)}import{existsSync as M,readFileSync as x}from"node:fs";import{homedir as $}from"node:os";import{join as b}from"node:path";function B(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let e=b($(),".zibby","config.json");return M(e)&&JSON.parse(x(e,"utf-8")).sessionToken||null}catch{return null}}function V(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function D(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function f(e){return`${D()}:${e}`}async function N(e,r){let n=B();if(!n)return{__noToken:!0};let i=`${V()}/credits/review-memory`,t=await fetch(i,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({op:e,...r})});if(!t.ok){let s=await t.text().catch(()=>"");throw new Error(`review-memory ${e} failed (${t.status}): ${s.slice(0,300)}`)}return t.json()}async function F(e,r){try{let n=a(r);if(!n)return{ok:!1,reason:"empty-record"};let i=await N("store",{scope:f(e),content:n});return i&&i.__noToken?{ok:!1,reason:"no-token"}:{ok:!0}}catch(n){return{ok:!1,reason:n?.message||"store-error"}}}async function L(e){try{let r=await N("recall",{scope:f(e)});if(!r||r.__noToken)return{kind:"empty"};let n=r.found&&r.memory?r.memory:null;if(!n)return{kind:"empty"};let i=n.content!=null?n.content:n.metadata!=null?n.metadata:null;return d(i)}catch{return{kind:"empty"}}}export{O as CONTENT_MAX_BYTES,T as FIELD_MAX_CHARS,g as FINDING_STATUSES,l as REVIEW_RECORD_KIND,A as REVIEW_RECORD_SCHEMA_VERSION,I as SEVERITY_TIERS,k as buildReviewRecord,v as normalizeSeverity,d as parseReviewMemory,L as recallReviewRecord,f as scopeForReviewMemory,a as serializeReviewRecord,F as storeReviewRecord,w as summarizeForPrompt,C as upsertReplyOutcome};
@@ -0,0 +1,43 @@
1
+ /** Effective backend scope for a plain per-PR/MR key. IDENTICAL to kvMemory scopeFor. */
2
+ export function scopeForReviewMemory(key: any): string;
3
+ /**
4
+ * Store a review record under a plain per-PR/MR key. Serializes via
5
+ * serializeReviewRecord (deterministic, byte-capped 200KB) and POSTs
6
+ * { op:'store', scope, content }.
7
+ *
8
+ * BEST-EFFORT: never throws. { ok:true } on success; { ok:false, reason:'no-token' }
9
+ * with no backend credential (local dev / self-host); { ok:false, reason } otherwise.
10
+ * The caller must treat a false result as a no-op — memory NEVER blocks a run.
11
+ */
12
+ export function storeReviewRecord(key: any, record: any): Promise<{
13
+ ok: boolean;
14
+ reason?: undefined;
15
+ } | {
16
+ ok: boolean;
17
+ reason: any;
18
+ }>;
19
+ /**
20
+ * Recall the review record stored under a plain per-PR/MR key. POSTs
21
+ * { op:'recall', scope } and hands the returned `content` to the tolerant
22
+ * parseReviewMemory (which never throws over any input).
23
+ *
24
+ * BEST-EFFORT: never throws. Returns parseReviewMemory's result
25
+ * ({ kind:'empty' | 'record' | 'legacy', ... }); { kind:'empty' } on missing
26
+ * token or ANY failure, so the caller degrades to "no prior memory".
27
+ */
28
+ export function recallReviewRecord(key: any): Promise<{
29
+ kind: string;
30
+ record?: undefined;
31
+ future?: undefined;
32
+ legacyNote?: undefined;
33
+ } | {
34
+ kind: string;
35
+ record: any;
36
+ future: boolean;
37
+ legacyNote?: undefined;
38
+ } | {
39
+ kind: string;
40
+ legacyNote: string;
41
+ record?: undefined;
42
+ future?: undefined;
43
+ }>;
@@ -0,0 +1 @@
1
+ import{existsSync as S,readFileSync as v}from"node:fs";import{homedir as N}from"node:os";import{join as A}from"node:path";var R="review-record";var d={blocker:0,"should-fix":1,nit:2};var y=e=>typeof Buffer<"u"&&typeof Buffer.byteLength=="function"?Buffer.byteLength(e,"utf8"):new TextEncoder().encode(e).length;function m(e){if(!e)return"";let t=[...e.findings||[]].sort((o,s)=>{let c=(d[o.severity]??9)-(d[s.severity]??9);if(c!==0)return c;let u=o.file||"",l=s.file||"";if(u!==l)return u<l?-1:1;let a=Number(o.line)||0,f=Number(s.line)||0;return a!==f?a-f:(o.id||"")<(s.id||"")?-1:1}),n={...e,findings:t},r=JSON.stringify(n);if(y(r)<=204800)return r;let i=[...t];for(n={...e,findings:i,truncated:!0};i.length>0&&y(JSON.stringify(n))>204800;)i.pop(),n={...e,findings:i,truncated:!0};return JSON.stringify(n)}var p=e=>e&&typeof e=="object"&&!Array.isArray(e)&&e.kind===R;function _(e){if(e==null)return{kind:"empty"};if(typeof e=="object"){if(!Array.isArray(e)&&typeof e.error=="string")return{kind:"empty"};if(p(e)){let r=Number(e.schemaVersion)>1;return{kind:"record",record:e,future:r}}return{kind:"legacy",legacyNote:h(e)}}if(typeof e!="string")return{kind:"empty"};let t=e.trim();if(!t)return{kind:"empty"};let n;try{n=JSON.parse(t)}catch{return{kind:"legacy",legacyNote:t}}if(n&&typeof n=="object"&&typeof n.error=="string")return{kind:"empty"};if(p(n)){let r=Number(n.schemaVersion)>1;return{kind:"record",record:n,future:r}}return{kind:"legacy",legacyNote:t}}function h(e){try{return JSON.stringify(e)}catch{return String(e)}}function O(){if(process.env.PROJECT_API_TOKEN)return process.env.PROJECT_API_TOKEN;if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let e=A(N(),".zibby","config.json");return S(e)&&JSON.parse(v(e,"utf-8")).sessionToken||null}catch{return null}}function k(){return process.env.ZIBBY_ACCOUNT_API_URL?process.env.ZIBBY_ACCOUNT_API_URL.replace(/\/$/,""):(process.env.ZIBBY_ENV||"prod")==="local"?"http://localhost:3001":process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://api-prod.zibby.app"}function T(){return(typeof process.env.WORKFLOW_TYPE=="string"?process.env.WORKFLOW_TYPE.trim():"")||"agent"}function E(e){return`${T()}:${e}`}async function g(e,t){let n=O();if(!n)return{__noToken:!0};let r=`${k()}/credits/review-memory`,i=await fetch(r,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({op:e,...t})});if(!i.ok){let o=await i.text().catch(()=>"");throw new Error(`review-memory ${e} failed (${i.status}): ${o.slice(0,300)}`)}return i.json()}async function $(e,t){try{let n=m(t);if(!n)return{ok:!1,reason:"empty-record"};let r=await g("store",{scope:E(e),content:n});return r&&r.__noToken?{ok:!1,reason:"no-token"}:{ok:!0}}catch(n){return{ok:!1,reason:n?.message||"store-error"}}}async function b(e){try{let t=await g("recall",{scope:E(e)});if(!t||t.__noToken)return{kind:"empty"};let n=t.found&&t.memory?t.memory:null;if(!n)return{kind:"empty"};let r=n.content!=null?n.content:n.metadata!=null?n.metadata:null;return _(r)}catch{return{kind:"empty"}}}export{b as recallReviewRecord,E as scopeForReviewMemory,$ as storeReviewRecord};
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Normalize the review's free-form / emoji severity into a stable tier.
3
+ * The real review schema defaults severity to '🟡'; reviews also emit 🔴/🟢 and
4
+ * words. Anything unrecognized falls back to 'should-fix' (never throws).
5
+ */
6
+ export function normalizeSeverity(raw: any): "should-fix" | "blocker" | "nit";
7
+ /**
8
+ * Build a fresh review record from the review node's (post-verification)
9
+ * findings. `findings` is the REAL review shape:
10
+ * { file, line?, severity, category, claim, evidence, suggestion?, confidence? }
11
+ * Assigns stable ids (f1..fN), normalizes severity, caps long text, status:'open'.
12
+ * `nowIso` is injected (callers pass new Date().toISOString()) so this stays pure.
13
+ */
14
+ export function buildReviewRecord({ headSha, verdict, objectivesChecked, findings, nowIso, }?: {
15
+ headSha?: any;
16
+ verdict?: string;
17
+ objectivesChecked?: boolean;
18
+ findings?: any[];
19
+ nowIso?: any;
20
+ }): {
21
+ schemaVersion: number;
22
+ kind: string;
23
+ headSha: any;
24
+ verdict: string;
25
+ objectivesChecked: boolean;
26
+ reviewedAt: any;
27
+ findings: {
28
+ id: string;
29
+ file: string;
30
+ line: any;
31
+ severity: string;
32
+ category: string;
33
+ claim: string;
34
+ evidence: string;
35
+ suggestion: string;
36
+ confidence: any;
37
+ status: string;
38
+ }[];
39
+ };
40
+ /**
41
+ * Record the OUTCOME of a comment_reply against a finding. IDEMPOTENT: it SETS
42
+ * findings[id].status (and an optional one-line note) — running the same reply
43
+ * twice (webhook redelivery) yields the identical record, no double-append.
44
+ * Returns a NEW record (does not mutate the input). If findingId is unknown, the
45
+ * record is returned unchanged (best-effort, never throws).
46
+ */
47
+ export function upsertReplyOutcome(record: any, { findingId, status, note }?: {}): any;
48
+ /**
49
+ * Serialize a record to a string that FITS CONTENT_MAX_BYTES. Deterministic:
50
+ * findings are stably sorted by (severityRank, file, line, id); if still over
51
+ * budget, the LOWEST-priority findings are dropped until it fits and
52
+ * `truncated:true` is set. Text fields are already capped by buildReviewRecord.
53
+ */
54
+ export function serializeReviewRecord(record: any): string;
55
+ /**
56
+ * Tolerant reader — NEVER throws. Accepts whatever kv_recall returns for the
57
+ * value (a JSON string, an already-parsed metadata object, a legacy prose
58
+ * string, an {error} envelope, null/'').
59
+ * Returns one of:
60
+ * { kind:'empty' } — nothing usable
61
+ * { kind:'record', record, future?:bool } — a structured record (future=newer schemaVersion)
62
+ * { kind:'legacy', legacyNote:string } — an old freeform note; advisory only
63
+ */
64
+ export function parseReviewMemory(raw: any): {
65
+ kind: string;
66
+ record?: undefined;
67
+ future?: undefined;
68
+ legacyNote?: undefined;
69
+ } | {
70
+ kind: string;
71
+ record: any;
72
+ future: boolean;
73
+ legacyNote?: undefined;
74
+ } | {
75
+ kind: string;
76
+ legacyNote: string;
77
+ record?: undefined;
78
+ future?: undefined;
79
+ };
80
+ /**
81
+ * Render a parsed memory into prompt text for the review/reply LLM. Compact,
82
+ * human-readable, and it NEVER assumes a shape it didn't verify (a future
83
+ * schemaVersion is surfaced as advisory). Empty → '' (caller omits the block).
84
+ */
85
+ export function summarizeForPrompt(parsed: any): string;
86
+ /**
87
+ * reviewRecord — a GENERIC, provider-agnostic structured record for the
88
+ * code-review → comment_reply memory (shared by gitlab-code-review and
89
+ * github-code-review). Replaces the freeform prose kv note with a versioned,
90
+ * queryable record.
91
+ *
92
+ * DELIBERATELY TRANSPORT-AGNOSTIC. This module does NO I/O: it only builds,
93
+ * serializes, parses and renders the record. WHO stores/recalls it (the JS node
94
+ * vs the LLM via kv_store/kv_recall) is a separate wiring decision — these pure
95
+ * functions are correct either way. The per-PR/MR KEY stays provider-specific
96
+ * (reviewMemoryScopeFor in each review-node.js); the record BODY here carries
97
+ * zero provider fields (identity lives in the key, not the body).
98
+ *
99
+ * Contract facts this module mirrors (source of truth = backend
100
+ * src/handlers/review-memory.js + packages/skills/src/kvMemory.js):
101
+ * - kv `content` is a STRING, hard-capped at 200KB (CONTENT_MAX). We serialize
102
+ * to a string and truncate deterministically to fit — an over-cap store is
103
+ * rejected (HTTP 400) and SILENTLY lost, so truncation must happen here,
104
+ * before the write.
105
+ * - kv also has an optional `metadata` OBJECT channel; parseReviewMemory
106
+ * therefore accepts an already-parsed object too, so the record can ride in
107
+ * either channel without changing this code.
108
+ *
109
+ * Reviewed by two adversarial design agents (2026-07-04): schema aligned to the
110
+ * REAL finding shape ({file,line,severity,category,claim,evidence,suggestion,
111
+ * confidence}); replies modeled as an IDEMPOTENT findingId→status set (NOT an
112
+ * append log, which double-appends on webhook redelivery); headSha carried but
113
+ * treated as advisory (its plumbing is deferred — today it is undefined).
114
+ */
115
+ export const REVIEW_RECORD_SCHEMA_VERSION: 1;
116
+ export const REVIEW_RECORD_KIND: "review-record";
117
+ export const CONTENT_MAX_BYTES: number;
118
+ export const FIELD_MAX_CHARS: 2000;
119
+ export const SEVERITY_TIERS: string[];
120
+ export const FINDING_STATUSES: string[];
@@ -0,0 +1,4 @@
1
+ var h=1,m="review-record",_=204800,N=2e3,A=["blocker","should-fix","nit"],a={blocker:0,"should-fix":1,nit:2},S=["open","conceded","held","resolved"];function R(e){let n=String(e??"").trim().toLowerCase();return n?n.includes("\u{1F534}")||n.includes("blocker")||n.includes("critical")||n.includes("high")?"blocker":n.includes("\u{1F7E2}")||n.includes("nit")||n.includes("minor")||n.includes("low")||n.includes("info")?"nit":(n.includes("\u{1F7E1}")||n.includes("should")||n.includes("medium")||n.includes("warn"),"should-fix"):"should-fix"}var u=(e,n)=>{let t=e==null?"":String(e);return t.length>n?t.slice(0,n):t},g=e=>typeof Buffer<"u"&&typeof Buffer.byteLength=="function"?Buffer.byteLength(e,"utf8"):new TextEncoder().encode(e).length;function v({headSha:e=null,verdict:n="COMMENT",objectivesChecked:t=!1,findings:o=[],nowIso:i=null}={}){let s=Array.isArray(o)?o:[];return{schemaVersion:1,kind:m,headSha:e||null,verdict:String(n||"COMMENT"),objectivesChecked:!!t,reviewedAt:i||null,findings:s.map((r,c)=>({id:`f${c+1}`,file:u(r?.file,512),line:r?.line==null?null:r.line,severity:R(r?.severity),category:u(r?.category,64),claim:u(r?.claim,2e3),evidence:u(r?.evidence,2e3),suggestion:u(r?.suggestion,2e3),confidence:typeof r?.confidence=="number"?r.confidence:null,status:"open"}))}}function k(e,{findingId:n,status:t,note:o}={}){if(!e||!Array.isArray(e.findings))return e;let i=S.includes(t)?t:"held";return{...e,findings:e.findings.map(s=>s.id===n?{...s,status:i,replyNote:u(o,2e3)}:s)}}function C(e){if(!e)return"";let n=[...e.findings||[]].sort((s,r)=>{let c=(a[s.severity]??9)-(a[r.severity]??9);if(c!==0)return c;let l=s.file||"",d=r.file||"";if(l!==d)return l<d?-1:1;let f=Number(s.line)||0,y=Number(r.line)||0;return f!==y?f-y:(s.id||"")<(r.id||"")?-1:1}),t={...e,findings:n},o=JSON.stringify(t);if(g(o)<=204800)return o;let i=[...n];for(t={...e,findings:i,truncated:!0};i.length>0&&g(JSON.stringify(t))>204800;)i.pop(),t={...e,findings:i,truncated:!0};return JSON.stringify(t)}var E=e=>e&&typeof e=="object"&&!Array.isArray(e)&&e.kind===m;function I(e){if(e==null)return{kind:"empty"};if(typeof e=="object"){if(!Array.isArray(e)&&typeof e.error=="string")return{kind:"empty"};if(E(e)){let o=Number(e.schemaVersion)>1;return{kind:"record",record:e,future:o}}return{kind:"legacy",legacyNote:p(e)}}if(typeof e!="string")return{kind:"empty"};let n=e.trim();if(!n)return{kind:"empty"};let t;try{t=JSON.parse(n)}catch{return{kind:"legacy",legacyNote:n}}if(t&&typeof t=="object"&&typeof t.error=="string")return{kind:"empty"};if(E(t)){let o=Number(t.schemaVersion)>1;return{kind:"record",record:t,future:o}}return{kind:"legacy",legacyNote:n}}function p(e){try{return JSON.stringify(e)}catch{return String(e)}}function O(e){if(!e||e.kind==="empty")return"";if(e.kind==="legacy")return`Prior review memory (legacy note, advisory only):
2
+ ${e.legacyNote}`;let n=e.record||{};if(e.future)return`Prior review memory (newer format ${n.schemaVersion} \u2014 advisory; verify against the live thread):
3
+ ${p(n).slice(0,2e3)}`;let t=[`Prior review of this change (verdict: ${n.verdict||"COMMENT"}${n.headSha?`, commit ${n.headSha}`:""}):`],o=(n.findings||[]).map(i=>{let s=i.file?`${i.file}${i.line!=null?`:${i.line}`:""}`:"(general)",r=i.status&&i.status!=="open"?` [${i.status}]`:"";return`- [${i.severity}] ${s} \u2014 ${i.claim||""}${r}`});return n.truncated&&o.push("- (\u2026older/lower-severity findings were truncated to fit memory)"),[...t,...o].join(`
4
+ `)}export{_ as CONTENT_MAX_BYTES,N as FIELD_MAX_CHARS,S as FINDING_STATUSES,m as REVIEW_RECORD_KIND,h as REVIEW_RECORD_SCHEMA_VERSION,A as SEVERITY_TIERS,v as buildReviewRecord,R as normalizeSeverity,I as parseReviewMemory,C as serializeReviewRecord,O as summarizeForPrompt,k as upsertReplyOutcome};
package/dist/sentry.d.ts CHANGED
@@ -21,11 +21,24 @@ export function sentryListIssues({ query, sort, project, limit }?: {
21
21
  project?: string;
22
22
  limit?: number;
23
23
  }): Promise<any>;
24
+ /**
25
+ * Resolve an issue REFERENCE (numeric id OR shortId) to its NUMERIC issue
26
+ * (group) id. The global `/issues/<id>/` endpoints used by get/update/comment
27
+ * accept ONLY the numeric id — a shortId (e.g. `PROJECT-ABC`) 404s there. So a
28
+ * caller that hands us the shortId (which is what Sentry surfaces everywhere,
29
+ * and what our workflow input schemas advertise as "numeric or shortId") would
30
+ * silently fail. Sentry's shortId→group resolver is ORG-scoped:
31
+ * `GET /organizations/<org>/shortids/<shortId>/` → { groupId }.
32
+ *
33
+ * A purely-numeric ref returns immediately with NO API call, so the common path
34
+ * (callers already holding the numeric id from a fetched issue) is free.
35
+ */
36
+ export function resolveSentryIssueId(issueRef: any): Promise<string>;
24
37
  /**
25
38
  * Fetch one Sentry issue's details. Uses the global `/issues/<id>/`
26
39
  * endpoint (NOT under /organizations/<slug>/) — Sentry routes issue
27
40
  * details by ID without an org scope. Auth still uses the connected
28
- * integration's token.
41
+ * integration's token. Accepts a numeric id OR a shortId (resolved first).
29
42
  */
30
43
  export function sentryGetIssue(issueId: any): Promise<any>;
31
44
  /**
package/dist/sentry.js CHANGED
@@ -1,7 +1,7 @@
1
- import{existsSync as m}from"fs";import{fileURLToPath as g}from"url";import{dirname as _,resolve as h}from"path";import{resolveIntegrationToken as c}from"@zibby/core/backend-client.js";var u=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),T=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function S(){if(process.env.MCP_SENTRY_PATH)return process.env.MCP_SENTRY_PATH;let n=_(g(import.meta.url)),t=h(n,"..","bin","mcp-sentry.mjs");return m(t)?t:null}function d(n){return(n||process.env.SENTRY_URL||"https://sentry.io").trim().replace(/\/+$/,"")}function f(n){let t=n||process.env.SENTRY_ORG;if(!t)throw new Error('Sentry organization not resolved \u2014 reconnect Sentry, or set SENTRY_ORG (self-hosted; the default org slug is "sentry").');return t}async function l(n,t={}){let{token:e,organizationSlug:s,baseUrl:r}=await c("sentry"),i=`${d(r)}/api/0/organizations/${f(s)}${n}`,o={method:t.method||"GET",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"}};t.body!=null&&(o.body=typeof t.body=="string"?t.body:JSON.stringify(t.body));let a=await fetch(i,o);if(!a.ok){let y=await a.text().catch(()=>"");throw new Error(`Sentry API ${a.status}: ${y.slice(0,300)}`)}return a.json()}async function I(){return l("/projects/?per_page=50")}async function k({query:n="is:unresolved",sort:t="date",project:e,limit:s=25}={}){let r=`/issues/?query=${encodeURIComponent(n)}&sort=${t}&per_page=${s}`;return e&&(r+=`&project=${encodeURIComponent(e)}`),l(r)}async function w(n){if(!n)throw new Error("sentryGetIssue: issueId is required");let{token:t,baseUrl:e}=await c("sentry"),s=await fetch(`${d(e)}/api/0/issues/${n}/`,{headers:{Authorization:`Bearer ${t}`}});if(!s.ok){let r=await s.text().catch(()=>"");throw new Error(`Sentry API ${s.status}: ${r.slice(0,300)}`)}return s.json()}async function b(n,t={}){if(!n)throw new Error("sentryUpdateIssue: issueId is required");let e={};for(let o of["status","statusDetails","assignedTo","isBookmarked","hasSeen"])t[o]!==void 0&&(e[o]=t[o]);if(Object.keys(e).length===0)throw new Error("sentryUpdateIssue: nothing to update (pass status / statusDetails / assignedTo / isBookmarked / hasSeen)");let{token:s,baseUrl:r}=await c("sentry"),i=await fetch(`${d(r)}/api/0/issues/${n}/`,{method:"PUT",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify(e)});if(!i.ok){let o=await i.text().catch(()=>"");throw i.status===403?new Error(`Sentry API 403 updating issue ${n}: ${o.slice(0,200)}. The connected Sentry integration likely lacks the \`event:write\` scope \u2014 reconnect Sentry with write access to let Zibby resolve/comment on issues.`):new Error(`Sentry API ${i.status} updating issue ${n}: ${o.slice(0,300)}`)}return i.json()}async function N(n,t){if(!n)throw new Error("sentryAddComment: issueId is required");if(!t||!String(t).trim())throw new Error("sentryAddComment: text is required");let{token:e,baseUrl:s}=await c("sentry"),r=await fetch(`${d(s)}/api/0/issues/${n}/comments/`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({text:String(t)})});if(!r.ok){let i=await r.text().catch(()=>"");throw r.status===403?new Error(`Sentry API 403 commenting on issue ${n}: ${i.slice(0,200)}. The connected Sentry integration likely lacks the \`event:write\` scope \u2014 reconnect Sentry with write access.`):new Error(`Sentry API ${r.status} commenting on issue ${n}: ${i.slice(0,300)}`)}return r.json()}var p={id:"sentry",serverName:"sentry",allowedTools:["mcp__sentry__*"],requiresIntegration:u.SENTRY,description:"Sentry error tracking \u2014 projects, issues, events",envKeys:[],tools:[],promptFragment:`## Sentry (connected)
1
+ import{existsSync as h}from"fs";import{fileURLToPath as _}from"url";import{dirname as S,resolve as I}from"path";import{resolveIntegrationToken as c}from"@zibby/core/backend-client.js";var p=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),T=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function f(){if(process.env.MCP_SENTRY_PATH)return process.env.MCP_SENTRY_PATH;let n=S(_(import.meta.url)),e=I(n,"..","bin","mcp-sentry.mjs");return h(e)?e:null}function d(n){return(n||process.env.SENTRY_URL||"https://sentry.io").trim().replace(/\/+$/,"")}function m(n){let e=n||process.env.SENTRY_ORG;if(!e)throw new Error('Sentry organization not resolved \u2014 reconnect Sentry, or set SENTRY_ORG (self-hosted; the default org slug is "sentry").');return e}async function g(n,e={}){let{token:t,organizationSlug:s,baseUrl:i}=await c("sentry"),r=`${d(i)}/api/0/organizations/${m(s)}${n}`,o={method:e.method||"GET",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"}};e.body!=null&&(o.body=typeof e.body=="string"?e.body:JSON.stringify(e.body));let a=await fetch(r,o);if(!a.ok){let u=await a.text().catch(()=>"");throw new Error(`Sentry API ${a.status}: ${u.slice(0,300)}`)}return a.json()}async function w(){return g("/projects/?per_page=50")}async function k({query:n="is:unresolved",sort:e="date",project:t,limit:s=25}={}){let i=`/issues/?query=${encodeURIComponent(n)}&sort=${e}&per_page=${s}`;return t&&(i+=`&project=${encodeURIComponent(t)}`),g(i)}async function l(n){let e=String(n??"").trim();if(!e)throw new Error("resolveSentryIssueId: issueRef is required");if(/^\d+$/.test(e))return e;let{token:t,organizationSlug:s,baseUrl:i}=await c("sentry"),r=await fetch(`${d(i)}/api/0/organizations/${m(s)}/shortids/${encodeURIComponent(e)}/`,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok){let u=await r.text().catch(()=>"");throw new Error(`Sentry API ${r.status} resolving shortId "${e}": ${u.slice(0,200)}`)}let o=await r.json(),a=o.groupId||o.group&&o.group.id;if(!a)throw new Error(`Sentry shortId "${e}" did not resolve to a numeric issue id`);return String(a)}async function b(n){if(!n)throw new Error("sentryGetIssue: issueId is required");let e=await l(n),{token:t,baseUrl:s}=await c("sentry"),i=await fetch(`${d(s)}/api/0/issues/${e}/`,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok){let r=await i.text().catch(()=>"");throw new Error(`Sentry API ${i.status}: ${r.slice(0,300)}`)}return i.json()}async function v(n,e={}){if(!n)throw new Error("sentryUpdateIssue: issueId is required");let t={};for(let a of["status","statusDetails","assignedTo","isBookmarked","hasSeen"])e[a]!==void 0&&(t[a]=e[a]);if(Object.keys(t).length===0)throw new Error("sentryUpdateIssue: nothing to update (pass status / statusDetails / assignedTo / isBookmarked / hasSeen)");let s=await l(n),{token:i,baseUrl:r}=await c("sentry"),o=await fetch(`${d(r)}/api/0/issues/${s}/`,{method:"PUT",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify(t)});if(!o.ok){let a=await o.text().catch(()=>"");throw o.status===403?new Error(`Sentry API 403 updating issue ${n}: ${a.slice(0,200)}. The connected Sentry integration likely lacks the \`event:write\` scope \u2014 reconnect Sentry with write access to let Zibby resolve/comment on issues.`):new Error(`Sentry API ${o.status} updating issue ${n}: ${a.slice(0,300)}`)}return o.json()}async function N(n,e){if(!n)throw new Error("sentryAddComment: issueId is required");if(!e||!String(e).trim())throw new Error("sentryAddComment: text is required");let t=await l(n),{token:s,baseUrl:i}=await c("sentry"),r=await fetch(`${d(i)}/api/0/issues/${t}/comments/`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},body:JSON.stringify({text:String(e)})});if(!r.ok){let o=await r.text().catch(()=>"");throw r.status===403?new Error(`Sentry API 403 commenting on issue ${n}: ${o.slice(0,200)}. The connected Sentry integration likely lacks the \`event:write\` scope \u2014 reconnect Sentry with write access.`):new Error(`Sentry API ${r.status} commenting on issue ${n}: ${o.slice(0,300)}`)}return r.json()}var y={id:"sentry",serverName:"sentry",allowedTools:["mcp__sentry__*"],requiresIntegration:p.SENTRY,description:"Sentry error tracking \u2014 projects, issues, events",envKeys:[],tools:[],promptFragment:`## Sentry (connected)
2
2
  You have access to the user's Sentry. Use these tools:
3
3
  - sentry_list_projects: List projects in the organization
4
4
  - sentry_list_issues: List errors/issues (supports Sentry search query, project filter, sort)
5
5
  - sentry_get_issue: Get detailed info about a specific issue (requires issueId)
6
6
  - sentry_update_issue: Change an issue's status (resolved / resolvedInNextRelease / ignored / unresolved / muted), assignment, or bookmark (requires issueId; needs write scope)
7
- - sentry_add_comment: Post a comment/note on an issue (requires issueId + text; needs write scope)`,resolve(){let n=S();if(!n)return null;let t={};for(let e of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE","ZIBBY_SELF_HOST","SENTRY_URL","SENTRY_ORG","SENTRY_AUTH_TOKEN"])process.env[e]&&(t[e]=process.env[e]);return{type:"stdio",command:"node",args:[n],env:t,alwaysLoad:!0}},async handleToolCall(n,t={}){try{switch(n){case"sentry_list_projects":{let e=await I();return JSON.stringify({projects:e.map(s=>({slug:s.slug,name:s.name,platform:s.platform}))})}case"sentry_list_issues":{let e=await k({query:t.query,sort:t.sort,project:t.project,limit:t.limit});return JSON.stringify({issues:e.map(s=>({id:s.id,title:s.title,culprit:s.culprit,count:s.count,firstSeen:s.firstSeen,lastSeen:s.lastSeen,level:s.level,status:s.status}))})}case"sentry_get_issue":{let e=await w(t.issueId);return JSON.stringify({id:e.id,title:e.title,culprit:e.culprit,metadata:e.metadata,count:e.count,userCount:e.userCount,firstSeen:e.firstSeen,lastSeen:e.lastSeen,level:e.level,status:e.status,project:{slug:e.project?.slug,name:e.project?.name}})}case"sentry_update_issue":{let e=await b(t.issueId,{status:t.status,statusDetails:t.statusDetails,assignedTo:t.assignedTo,isBookmarked:t.isBookmarked,hasSeen:t.hasSeen});return JSON.stringify({ok:!0,id:e.id??t.issueId,status:e.status,assignedTo:e.assignedTo,isBookmarked:e.isBookmarked})}case"sentry_add_comment":{let e=await N(t.issueId,t.text);return JSON.stringify({ok:!0,id:e.id,issueId:t.issueId})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(e){return JSON.stringify({error:e.message})}},toolsForAssistant:[{name:"sentry_list_projects",description:"List Sentry projects",input_schema:{type:"object",properties:{}}},{name:"sentry_list_issues",description:"List Sentry issues (errors)",input_schema:{type:"object",properties:{project:{type:"string",description:"Project slug (optional)"},query:{type:"string",description:"Sentry search query (default: is:unresolved)"},sort:{type:"string",description:"Sort order: date, new, priority, freq, user (default: date)"},limit:{type:"number",description:"Max issues to return (default 25)"}}}},{name:"sentry_get_issue",description:"Get details of a specific Sentry issue",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"}},required:["issueId"]}},{name:"sentry_update_issue",description:"Update a Sentry issue's status, assignment, or bookmark (needs event:write scope)",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"},status:{type:"string",description:"resolved | resolvedInNextRelease | unresolved | ignored | muted"},statusDetails:{type:"object",description:'Optional status details, e.g. { "inRelease": "latest" }'},assignedTo:{type:"string",description:'Assignee actor id, e.g. "user:123" or "team:456" (optional)'},isBookmarked:{type:"boolean",description:"Bookmark/unbookmark the issue (optional)"},hasSeen:{type:"boolean",description:"Mark the issue seen/unseen (optional)"}},required:["issueId"]}},{name:"sentry_add_comment",description:"Post a comment/note on a Sentry issue (needs event:write scope)",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"},text:{type:"string",description:"Comment body (markdown)"}},required:["issueId","text"]}}]};p.tools=p.toolsForAssistant;export{N as sentryAddComment,l as sentryFetch,w as sentryGetIssue,k as sentryListIssues,I as sentryListProjects,p as sentrySkill,b as sentryUpdateIssue};
7
+ - sentry_add_comment: Post a comment/note on an issue (requires issueId + text; needs write scope)`,resolve(){let n=f();if(!n)return null;let e={};for(let t of["PROJECT_API_TOKEN","ZIBBY_USER_TOKEN","ZIBBY_ACCOUNT_API_URL","ZIBBY_ENV","ZIBBY_PROD_ACCOUNT_API_URL","PROGRESS_API_URL","EXECUTION_ID","PROJECT_ID","STAGE","ZIBBY_SELF_HOST","SENTRY_URL","SENTRY_ORG","SENTRY_AUTH_TOKEN"])process.env[t]&&(e[t]=process.env[t]);return{type:"stdio",command:"node",args:[n],env:e,alwaysLoad:!0}},async handleToolCall(n,e={}){try{switch(n){case"sentry_list_projects":{let t=await w();return JSON.stringify({projects:t.map(s=>({slug:s.slug,name:s.name,platform:s.platform}))})}case"sentry_list_issues":{let t=await k({query:e.query,sort:e.sort,project:e.project,limit:e.limit});return JSON.stringify({issues:t.map(s=>({id:s.id,title:s.title,culprit:s.culprit,count:s.count,firstSeen:s.firstSeen,lastSeen:s.lastSeen,level:s.level,status:s.status}))})}case"sentry_get_issue":{let t=await b(e.issueId);return JSON.stringify({id:t.id,title:t.title,culprit:t.culprit,metadata:t.metadata,count:t.count,userCount:t.userCount,firstSeen:t.firstSeen,lastSeen:t.lastSeen,level:t.level,status:t.status,project:{slug:t.project?.slug,name:t.project?.name}})}case"sentry_update_issue":{let t=await v(e.issueId,{status:e.status,statusDetails:e.statusDetails,assignedTo:e.assignedTo,isBookmarked:e.isBookmarked,hasSeen:e.hasSeen});return JSON.stringify({ok:!0,id:t.id??e.issueId,status:t.status,assignedTo:t.assignedTo,isBookmarked:t.isBookmarked})}case"sentry_add_comment":{let t=await N(e.issueId,e.text);return JSON.stringify({ok:!0,id:t.id,issueId:e.issueId})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(t){return JSON.stringify({error:t.message})}},toolsForAssistant:[{name:"sentry_list_projects",description:"List Sentry projects",input_schema:{type:"object",properties:{}}},{name:"sentry_list_issues",description:"List Sentry issues (errors)",input_schema:{type:"object",properties:{project:{type:"string",description:"Project slug (optional)"},query:{type:"string",description:"Sentry search query (default: is:unresolved)"},sort:{type:"string",description:"Sort order: date, new, priority, freq, user (default: date)"},limit:{type:"number",description:"Max issues to return (default 25)"}}}},{name:"sentry_get_issue",description:"Get details of a specific Sentry issue",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"}},required:["issueId"]}},{name:"sentry_update_issue",description:"Update a Sentry issue's status, assignment, or bookmark (needs event:write scope)",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"},status:{type:"string",description:"resolved | resolvedInNextRelease | unresolved | ignored | muted"},statusDetails:{type:"object",description:'Optional status details, e.g. { "inRelease": "latest" }'},assignedTo:{type:"string",description:'Assignee actor id, e.g. "user:123" or "team:456" (optional)'},isBookmarked:{type:"boolean",description:"Bookmark/unbookmark the issue (optional)"},hasSeen:{type:"boolean",description:"Mark the issue seen/unseen (optional)"}},required:["issueId"]}},{name:"sentry_add_comment",description:"Post a comment/note on a Sentry issue (needs event:write scope)",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"},text:{type:"string",description:"Comment body (markdown)"}},required:["issueId","text"]}}]};y.tools=y.toolsForAssistant;export{l as resolveSentryIssueId,N as sentryAddComment,g as sentryFetch,b as sentryGetIssue,k as sentryListIssues,w as sentryListProjects,y as sentrySkill,v as sentryUpdateIssue};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -69,6 +69,10 @@
69
69
  "./sentry": {
70
70
  "types": "./dist/sentry.d.ts",
71
71
  "default": "./dist/sentry.js"
72
+ },
73
+ "./review": {
74
+ "types": "./dist/review.d.ts",
75
+ "default": "./dist/review.js"
72
76
  }
73
77
  },
74
78
  "scripts": {