@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/index.js +89 -89
- package/dist/package.json +5 -1
- package/dist/review.d.ts +2 -0
- package/dist/review.js +4 -0
- package/dist/reviewMemoryIo.d.ts +43 -0
- package/dist/reviewMemoryIo.js +1 -0
- package/dist/reviewRecord.d.ts +120 -0
- package/dist/reviewRecord.js +4 -0
- package/dist/sentry.d.ts +14 -1
- package/dist/sentry.js +2 -2
- package/package.json +5 -1
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/skills",
|
|
3
|
-
"version": "0.1.
|
|
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": {
|
package/dist/review.d.ts
ADDED
|
@@ -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
|
|
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=
|
|
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.
|
|
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": {
|