@singi-labs/sifa-sdk 0.19.80 → 0.19.81
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.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/jev/error.cjs +96 -0
- package/dist/jev/error.cjs.map +1 -0
- package/dist/jev/error.d.cts +98 -0
- package/dist/jev/error.d.ts +98 -0
- package/dist/jev/error.js +94 -0
- package/dist/jev/error.js.map +1 -0
- package/package.json +11 -1
package/dist/index.cjs
CHANGED
|
@@ -7313,7 +7313,7 @@ function describeSifaRecord(collection, value) {
|
|
|
7313
7313
|
}
|
|
7314
7314
|
|
|
7315
7315
|
// src/index.ts
|
|
7316
|
-
var SIFA_SDK_VERSION = "0.19.
|
|
7316
|
+
var SIFA_SDK_VERSION = "0.19.81";
|
|
7317
7317
|
|
|
7318
7318
|
exports.ACTIVITY_TIERS = ACTIVITY_TIERS;
|
|
7319
7319
|
exports.ACTIVITY_VERBS = ACTIVITY_VERBS;
|
package/dist/index.js
CHANGED
|
@@ -370,7 +370,7 @@ import {
|
|
|
370
370
|
roadmapIssueUrl
|
|
371
371
|
} from "./roadmap/index.js";
|
|
372
372
|
import { SIFA_REPO_GROUPS, describeSifaRecord, repoGroupForCollection } from "./repo/index.js";
|
|
373
|
-
var SIFA_SDK_VERSION = "0.19.
|
|
373
|
+
var SIFA_SDK_VERSION = "0.19.81";
|
|
374
374
|
export {
|
|
375
375
|
ACTIVITY_TIERS,
|
|
376
376
|
ACTIVITY_VERBS,
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/jev/error.ts
|
|
4
|
+
var DID_RE = /did:(?:plc|web):[A-Za-z0-9._:%-]+/g;
|
|
5
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
6
|
+
var HANDLE_RE = /@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
|
|
7
|
+
var PROFILE_PATH_RE = /(\/(?:p|profile)\/)[^/\s?#]+/g;
|
|
8
|
+
var AT_URI_RE = /at:\/\/[^\s/]+/g;
|
|
9
|
+
function scrubErrorText(text) {
|
|
10
|
+
return text.replace(AT_URI_RE, "at://<id>").replace(DID_RE, "<did>").replace(EMAIL_RE, "<email>").replace(HANDLE_RE, "<handle>").replace(PROFILE_PATH_RE, "$1<handle>");
|
|
11
|
+
}
|
|
12
|
+
var ERROR_MAX_FRAMES = 8;
|
|
13
|
+
var ISSUE_CANDIDATE_BODY_MAX_CHARS = 2e3;
|
|
14
|
+
function frameState(frames) {
|
|
15
|
+
const inApp = frames.filter((f) => f.inApp !== false);
|
|
16
|
+
const chosen = (inApp.length > 0 ? inApp : frames).slice(0, ERROR_MAX_FRAMES);
|
|
17
|
+
const out = chosen.map((f) => {
|
|
18
|
+
const s = {};
|
|
19
|
+
if (f.filename) s.filename = scrubErrorText(f.filename);
|
|
20
|
+
if (f.function) s.function = scrubErrorText(f.function);
|
|
21
|
+
if (typeof f.lineNo === "number") s.lineNo = f.lineNo;
|
|
22
|
+
return s;
|
|
23
|
+
});
|
|
24
|
+
return out.length > 0 ? out : void 0;
|
|
25
|
+
}
|
|
26
|
+
function buildErrorReportState(report) {
|
|
27
|
+
const state = { title: scrubErrorText(report.title.trim()) };
|
|
28
|
+
if (report.culprit) state.culprit = scrubErrorText(report.culprit);
|
|
29
|
+
if (report.level) state.level = report.level;
|
|
30
|
+
if (report.type) state.type = scrubErrorText(report.type);
|
|
31
|
+
if (report.value) state.value = scrubErrorText(report.value.trim());
|
|
32
|
+
if (report.release) state.release = report.release;
|
|
33
|
+
const frames = report.frames ? frameState(report.frames) : void 0;
|
|
34
|
+
if (frames) state.frames = frames;
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
function buildIssueCandidateState(issue) {
|
|
38
|
+
const state = {
|
|
39
|
+
number: issue.number,
|
|
40
|
+
title: scrubErrorText(issue.title.trim())
|
|
41
|
+
};
|
|
42
|
+
const body = issue.body?.trim();
|
|
43
|
+
if (body) state.body = scrubErrorText(body.slice(0, ISSUE_CANDIDATE_BODY_MAX_CHARS));
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
function buildErrorIssuePairState(report, issue) {
|
|
47
|
+
return { error: buildErrorReportState(report), issue: buildIssueCandidateState(issue) };
|
|
48
|
+
}
|
|
49
|
+
var ERROR_SAME_BUG_NOUL_QUESTION = {
|
|
50
|
+
type: "noul",
|
|
51
|
+
instructions: "The state holds an error report (title, culprit, exception, top stack frames) and an existing GitHub issue. Does the issue describe the same underlying bug as the error report?",
|
|
52
|
+
criteria: {
|
|
53
|
+
true: "Same root cause: the same exception at the same code path, or the issue text names the same failure even if line numbers or messages differ slightly.",
|
|
54
|
+
false: "A different bug: different exception type or code path, a different feature, or an issue that only shares generic words (error, 500, profile) with the report."
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var SAME_BUG_THRESHOLD = 0.85;
|
|
58
|
+
function isLikelySameBug(noul, threshold = SAME_BUG_THRESHOLD) {
|
|
59
|
+
return noul >= threshold;
|
|
60
|
+
}
|
|
61
|
+
function buildSameBugQuestions(candidates) {
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const c of candidates) {
|
|
64
|
+
out[`sameBug:${c.number}`] = {
|
|
65
|
+
type: "noul",
|
|
66
|
+
instructions: `${ERROR_SAME_BUG_NOUL_QUESTION.instructions} Judge the candidate issue with number ${c.number} in state.candidates.`,
|
|
67
|
+
...ERROR_SAME_BUG_NOUL_QUESTION.criteria ? { criteria: ERROR_SAME_BUG_NOUL_QUESTION.criteria } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function buildErrorCandidatesState(report, candidates) {
|
|
73
|
+
return {
|
|
74
|
+
error: buildErrorReportState(report),
|
|
75
|
+
candidates: candidates.map(buildIssueCandidateState)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function issueNumberFromSameBugKey(key) {
|
|
79
|
+
const m = /^sameBug:(\d+)$/.exec(key);
|
|
80
|
+
return m ? Number(m[1]) : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
exports.ERROR_MAX_FRAMES = ERROR_MAX_FRAMES;
|
|
84
|
+
exports.ERROR_SAME_BUG_NOUL_QUESTION = ERROR_SAME_BUG_NOUL_QUESTION;
|
|
85
|
+
exports.ISSUE_CANDIDATE_BODY_MAX_CHARS = ISSUE_CANDIDATE_BODY_MAX_CHARS;
|
|
86
|
+
exports.SAME_BUG_THRESHOLD = SAME_BUG_THRESHOLD;
|
|
87
|
+
exports.buildErrorCandidatesState = buildErrorCandidatesState;
|
|
88
|
+
exports.buildErrorIssuePairState = buildErrorIssuePairState;
|
|
89
|
+
exports.buildErrorReportState = buildErrorReportState;
|
|
90
|
+
exports.buildIssueCandidateState = buildIssueCandidateState;
|
|
91
|
+
exports.buildSameBugQuestions = buildSameBugQuestions;
|
|
92
|
+
exports.isLikelySameBug = isLikelySameBug;
|
|
93
|
+
exports.issueNumberFromSameBugKey = issueNumberFromSameBugKey;
|
|
94
|
+
exports.scrubErrorText = scrubErrorText;
|
|
95
|
+
//# sourceMappingURL=error.cjs.map
|
|
96
|
+
//# sourceMappingURL=error.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/jev/error.ts"],"names":[],"mappings":"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","file":"error.cjs","sourcesContent":["/**\n * Error-report judgments for the GlitchTip relay (sifa-workspace decision\n * 2026-10-05, background agents tier policy, Phase 2). Pure builders: the\n * state for \"is this error event the same bug as an existing issue?\", the\n * Noul question, the threshold, and the scrubber that removes identifiers\n * before anything leaves the server. No network, no key.\n *\n * Privacy: GlitchTip frames and messages can embed handles, DIDs, emails and\n * URLs with user paths. {@link scrubErrorText} runs over every string placed\n * in the state; callers must not bypass it.\n */\n\nimport type { NoulQuestion } from './index.js';\n\n// ---------------------------------------------------------------------------\n// Scrubbing\n// ---------------------------------------------------------------------------\n\nconst DID_RE = /did:(?:plc|web):[A-Za-z0-9._:%-]+/g;\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n/** `@handle.tld` mentions; a bare `@` inside code (decorators) has no dot-tld. */\nconst HANDLE_RE = /@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)+/g;\n/** `/p/<handle>` and `/profile/<handle>` path segments on Sifa URLs. */\nconst PROFILE_PATH_RE = /(\\/(?:p|profile)\\/)[^/\\s?#]+/g;\n/** at:// URIs carry a DID or handle as authority. */\nconst AT_URI_RE = /at:\\/\\/[^\\s/]+/g;\n\n/** Replace identifiers with stable placeholders. Idempotent. */\nexport function scrubErrorText(text: string): string {\n return text\n .replace(AT_URI_RE, 'at://<id>')\n .replace(DID_RE, '<did>')\n .replace(EMAIL_RE, '<email>')\n .replace(HANDLE_RE, '<handle>')\n .replace(PROFILE_PATH_RE, '$1<handle>');\n}\n\n// ---------------------------------------------------------------------------\n// Input and state\n// ---------------------------------------------------------------------------\n\n/** One stack frame, already reduced to what a same-bug judgment needs. */\nexport interface ErrorFrameInput {\n filename?: string;\n function?: string;\n lineNo?: number;\n inApp?: boolean;\n}\n\n/** A GlitchTip issue (error group) as seen by the relay. */\nexport interface ErrorReportInput {\n title: string;\n culprit?: string;\n level?: string;\n /** Exception type, e.g. `TypeError`. */\n type?: string;\n /** Exception value / message. */\n value?: string;\n frames?: ErrorFrameInput[];\n /** Release or deploy identifier, when GlitchTip has it. */\n release?: string;\n}\n\n/** An existing GitHub issue candidate (title + body, labelled `glitchtip`). */\nexport interface IssueCandidateInput {\n number: number;\n title: string;\n body?: string;\n}\n\n/** Keep the judgment cheap and bounded: top in-app frames only. */\nexport const ERROR_MAX_FRAMES = 8;\nexport const ISSUE_CANDIDATE_BODY_MAX_CHARS = 2000;\n\nexport interface ErrorReportState {\n title: string;\n culprit?: string;\n level?: string;\n type?: string;\n value?: string;\n release?: string;\n frames?: Array<{ filename?: string; function?: string; lineNo?: number }>;\n}\n\nexport interface IssueCandidateState {\n number: number;\n title: string;\n body?: string;\n}\n\nfunction frameState(frames: ErrorFrameInput[]): ErrorReportState['frames'] {\n const inApp = frames.filter((f) => f.inApp !== false);\n const chosen = (inApp.length > 0 ? inApp : frames).slice(0, ERROR_MAX_FRAMES);\n const out = chosen.map((f) => {\n const s: { filename?: string; function?: string; lineNo?: number } = {};\n if (f.filename) s.filename = scrubErrorText(f.filename);\n if (f.function) s.function = scrubErrorText(f.function);\n if (typeof f.lineNo === 'number') s.lineNo = f.lineNo;\n return s;\n });\n return out.length > 0 ? out : undefined;\n}\n\n/** Scrubbed, bounded state for the error side of the pair. */\nexport function buildErrorReportState(report: ErrorReportInput): ErrorReportState {\n const state: ErrorReportState = { title: scrubErrorText(report.title.trim()) };\n if (report.culprit) state.culprit = scrubErrorText(report.culprit);\n if (report.level) state.level = report.level;\n if (report.type) state.type = scrubErrorText(report.type);\n if (report.value) state.value = scrubErrorText(report.value.trim());\n if (report.release) state.release = report.release;\n const frames = report.frames ? frameState(report.frames) : undefined;\n if (frames) state.frames = frames;\n return state;\n}\n\n/** Scrubbed, bounded state for the GitHub issue side of the pair. */\nexport function buildIssueCandidateState(issue: IssueCandidateInput): IssueCandidateState {\n const state: IssueCandidateState = {\n number: issue.number,\n title: scrubErrorText(issue.title.trim()),\n };\n const body = issue.body?.trim();\n if (body) state.body = scrubErrorText(body.slice(0, ISSUE_CANDIDATE_BODY_MAX_CHARS));\n return state;\n}\n\n/** State for one error-vs-issue pair, both under stable named fields. */\nexport function buildErrorIssuePairState(\n report: ErrorReportInput,\n issue: IssueCandidateInput,\n): { error: ErrorReportState; issue: IssueCandidateState } {\n return { error: buildErrorReportState(report), issue: buildIssueCandidateState(issue) };\n}\n\n// ---------------------------------------------------------------------------\n// Question and threshold\n// ---------------------------------------------------------------------------\n\n/** Static Noul question paired with {@link buildErrorIssuePairState}. */\nexport const ERROR_SAME_BUG_NOUL_QUESTION: NoulQuestion = {\n type: 'noul',\n instructions:\n 'The state holds an error report (title, culprit, exception, top stack frames) and an existing GitHub issue. Does the issue describe the same underlying bug as the error report?',\n criteria: {\n true: 'Same root cause: the same exception at the same code path, or the issue text names the same failure even if line numbers or messages differ slightly.',\n false:\n 'A different bug: different exception type or code path, a different feature, or an issue that only shares generic words (error, 500, profile) with the report.',\n },\n};\n\n/**\n * Precision over recall: a false \"same bug\" hides a new error behind an old\n * issue; a false \"new\" costs one duplicate issue a human closes. Validate on\n * labelled GlitchTip-event-to-issue pairs before widening.\n */\nexport const SAME_BUG_THRESHOLD = 0.85;\n\n/** True when the Noul probability clears the same-bug threshold. */\nexport function isLikelySameBug(noul: number, threshold: number = SAME_BUG_THRESHOLD): boolean {\n return noul >= threshold;\n}\n\n/**\n * Question ids for a batched call over several candidates: one Noul per\n * candidate, keyed `sameBug:<issueNumber>`. Pair with one state holding\n * `error` plus `candidates[]`; each question names its candidate number.\n */\nexport function buildSameBugQuestions(\n candidates: IssueCandidateInput[],\n): Record<string, NoulQuestion> {\n const out: Record<string, NoulQuestion> = {};\n for (const c of candidates) {\n out[`sameBug:${c.number}`] = {\n type: 'noul',\n instructions: `${ERROR_SAME_BUG_NOUL_QUESTION.instructions} Judge the candidate issue with number ${c.number} in state.candidates.`,\n ...(ERROR_SAME_BUG_NOUL_QUESTION.criteria\n ? { criteria: ERROR_SAME_BUG_NOUL_QUESTION.criteria }\n : {}),\n };\n }\n return out;\n}\n\n/** State for the batched form: one error against several candidates. */\nexport function buildErrorCandidatesState(\n report: ErrorReportInput,\n candidates: IssueCandidateInput[],\n): { error: ErrorReportState; candidates: IssueCandidateState[] } {\n return {\n error: buildErrorReportState(report),\n candidates: candidates.map(buildIssueCandidateState),\n };\n}\n\n/** Parse `sameBug:<n>` back to the issue number, or null for a foreign key. */\nexport function issueNumberFromSameBugKey(key: string): number | null {\n const m = /^sameBug:(\\d+)$/.exec(key);\n return m ? Number(m[1]) : null;\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NoulQuestion } from './index.cjs';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Error-report judgments for the GlitchTip relay (sifa-workspace decision
|
|
6
|
+
* 2026-10-05, background agents tier policy, Phase 2). Pure builders: the
|
|
7
|
+
* state for "is this error event the same bug as an existing issue?", the
|
|
8
|
+
* Noul question, the threshold, and the scrubber that removes identifiers
|
|
9
|
+
* before anything leaves the server. No network, no key.
|
|
10
|
+
*
|
|
11
|
+
* Privacy: GlitchTip frames and messages can embed handles, DIDs, emails and
|
|
12
|
+
* URLs with user paths. {@link scrubErrorText} runs over every string placed
|
|
13
|
+
* in the state; callers must not bypass it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Replace identifiers with stable placeholders. Idempotent. */
|
|
17
|
+
declare function scrubErrorText(text: string): string;
|
|
18
|
+
/** One stack frame, already reduced to what a same-bug judgment needs. */
|
|
19
|
+
interface ErrorFrameInput {
|
|
20
|
+
filename?: string;
|
|
21
|
+
function?: string;
|
|
22
|
+
lineNo?: number;
|
|
23
|
+
inApp?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** A GlitchTip issue (error group) as seen by the relay. */
|
|
26
|
+
interface ErrorReportInput {
|
|
27
|
+
title: string;
|
|
28
|
+
culprit?: string;
|
|
29
|
+
level?: string;
|
|
30
|
+
/** Exception type, e.g. `TypeError`. */
|
|
31
|
+
type?: string;
|
|
32
|
+
/** Exception value / message. */
|
|
33
|
+
value?: string;
|
|
34
|
+
frames?: ErrorFrameInput[];
|
|
35
|
+
/** Release or deploy identifier, when GlitchTip has it. */
|
|
36
|
+
release?: string;
|
|
37
|
+
}
|
|
38
|
+
/** An existing GitHub issue candidate (title + body, labelled `glitchtip`). */
|
|
39
|
+
interface IssueCandidateInput {
|
|
40
|
+
number: number;
|
|
41
|
+
title: string;
|
|
42
|
+
body?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Keep the judgment cheap and bounded: top in-app frames only. */
|
|
45
|
+
declare const ERROR_MAX_FRAMES = 8;
|
|
46
|
+
declare const ISSUE_CANDIDATE_BODY_MAX_CHARS = 2000;
|
|
47
|
+
interface ErrorReportState {
|
|
48
|
+
title: string;
|
|
49
|
+
culprit?: string;
|
|
50
|
+
level?: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
value?: string;
|
|
53
|
+
release?: string;
|
|
54
|
+
frames?: Array<{
|
|
55
|
+
filename?: string;
|
|
56
|
+
function?: string;
|
|
57
|
+
lineNo?: number;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
interface IssueCandidateState {
|
|
61
|
+
number: number;
|
|
62
|
+
title: string;
|
|
63
|
+
body?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Scrubbed, bounded state for the error side of the pair. */
|
|
66
|
+
declare function buildErrorReportState(report: ErrorReportInput): ErrorReportState;
|
|
67
|
+
/** Scrubbed, bounded state for the GitHub issue side of the pair. */
|
|
68
|
+
declare function buildIssueCandidateState(issue: IssueCandidateInput): IssueCandidateState;
|
|
69
|
+
/** State for one error-vs-issue pair, both under stable named fields. */
|
|
70
|
+
declare function buildErrorIssuePairState(report: ErrorReportInput, issue: IssueCandidateInput): {
|
|
71
|
+
error: ErrorReportState;
|
|
72
|
+
issue: IssueCandidateState;
|
|
73
|
+
};
|
|
74
|
+
/** Static Noul question paired with {@link buildErrorIssuePairState}. */
|
|
75
|
+
declare const ERROR_SAME_BUG_NOUL_QUESTION: NoulQuestion;
|
|
76
|
+
/**
|
|
77
|
+
* Precision over recall: a false "same bug" hides a new error behind an old
|
|
78
|
+
* issue; a false "new" costs one duplicate issue a human closes. Validate on
|
|
79
|
+
* labelled GlitchTip-event-to-issue pairs before widening.
|
|
80
|
+
*/
|
|
81
|
+
declare const SAME_BUG_THRESHOLD = 0.85;
|
|
82
|
+
/** True when the Noul probability clears the same-bug threshold. */
|
|
83
|
+
declare function isLikelySameBug(noul: number, threshold?: number): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Question ids for a batched call over several candidates: one Noul per
|
|
86
|
+
* candidate, keyed `sameBug:<issueNumber>`. Pair with one state holding
|
|
87
|
+
* `error` plus `candidates[]`; each question names its candidate number.
|
|
88
|
+
*/
|
|
89
|
+
declare function buildSameBugQuestions(candidates: IssueCandidateInput[]): Record<string, NoulQuestion>;
|
|
90
|
+
/** State for the batched form: one error against several candidates. */
|
|
91
|
+
declare function buildErrorCandidatesState(report: ErrorReportInput, candidates: IssueCandidateInput[]): {
|
|
92
|
+
error: ErrorReportState;
|
|
93
|
+
candidates: IssueCandidateState[];
|
|
94
|
+
};
|
|
95
|
+
/** Parse `sameBug:<n>` back to the issue number, or null for a foreign key. */
|
|
96
|
+
declare function issueNumberFromSameBugKey(key: string): number | null;
|
|
97
|
+
|
|
98
|
+
export { ERROR_MAX_FRAMES, ERROR_SAME_BUG_NOUL_QUESTION, type ErrorFrameInput, type ErrorReportInput, type ErrorReportState, ISSUE_CANDIDATE_BODY_MAX_CHARS, type IssueCandidateInput, type IssueCandidateState, SAME_BUG_THRESHOLD, buildErrorCandidatesState, buildErrorIssuePairState, buildErrorReportState, buildIssueCandidateState, buildSameBugQuestions, isLikelySameBug, issueNumberFromSameBugKey, scrubErrorText };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NoulQuestion } from './index.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Error-report judgments for the GlitchTip relay (sifa-workspace decision
|
|
6
|
+
* 2026-10-05, background agents tier policy, Phase 2). Pure builders: the
|
|
7
|
+
* state for "is this error event the same bug as an existing issue?", the
|
|
8
|
+
* Noul question, the threshold, and the scrubber that removes identifiers
|
|
9
|
+
* before anything leaves the server. No network, no key.
|
|
10
|
+
*
|
|
11
|
+
* Privacy: GlitchTip frames and messages can embed handles, DIDs, emails and
|
|
12
|
+
* URLs with user paths. {@link scrubErrorText} runs over every string placed
|
|
13
|
+
* in the state; callers must not bypass it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Replace identifiers with stable placeholders. Idempotent. */
|
|
17
|
+
declare function scrubErrorText(text: string): string;
|
|
18
|
+
/** One stack frame, already reduced to what a same-bug judgment needs. */
|
|
19
|
+
interface ErrorFrameInput {
|
|
20
|
+
filename?: string;
|
|
21
|
+
function?: string;
|
|
22
|
+
lineNo?: number;
|
|
23
|
+
inApp?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** A GlitchTip issue (error group) as seen by the relay. */
|
|
26
|
+
interface ErrorReportInput {
|
|
27
|
+
title: string;
|
|
28
|
+
culprit?: string;
|
|
29
|
+
level?: string;
|
|
30
|
+
/** Exception type, e.g. `TypeError`. */
|
|
31
|
+
type?: string;
|
|
32
|
+
/** Exception value / message. */
|
|
33
|
+
value?: string;
|
|
34
|
+
frames?: ErrorFrameInput[];
|
|
35
|
+
/** Release or deploy identifier, when GlitchTip has it. */
|
|
36
|
+
release?: string;
|
|
37
|
+
}
|
|
38
|
+
/** An existing GitHub issue candidate (title + body, labelled `glitchtip`). */
|
|
39
|
+
interface IssueCandidateInput {
|
|
40
|
+
number: number;
|
|
41
|
+
title: string;
|
|
42
|
+
body?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Keep the judgment cheap and bounded: top in-app frames only. */
|
|
45
|
+
declare const ERROR_MAX_FRAMES = 8;
|
|
46
|
+
declare const ISSUE_CANDIDATE_BODY_MAX_CHARS = 2000;
|
|
47
|
+
interface ErrorReportState {
|
|
48
|
+
title: string;
|
|
49
|
+
culprit?: string;
|
|
50
|
+
level?: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
value?: string;
|
|
53
|
+
release?: string;
|
|
54
|
+
frames?: Array<{
|
|
55
|
+
filename?: string;
|
|
56
|
+
function?: string;
|
|
57
|
+
lineNo?: number;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
interface IssueCandidateState {
|
|
61
|
+
number: number;
|
|
62
|
+
title: string;
|
|
63
|
+
body?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Scrubbed, bounded state for the error side of the pair. */
|
|
66
|
+
declare function buildErrorReportState(report: ErrorReportInput): ErrorReportState;
|
|
67
|
+
/** Scrubbed, bounded state for the GitHub issue side of the pair. */
|
|
68
|
+
declare function buildIssueCandidateState(issue: IssueCandidateInput): IssueCandidateState;
|
|
69
|
+
/** State for one error-vs-issue pair, both under stable named fields. */
|
|
70
|
+
declare function buildErrorIssuePairState(report: ErrorReportInput, issue: IssueCandidateInput): {
|
|
71
|
+
error: ErrorReportState;
|
|
72
|
+
issue: IssueCandidateState;
|
|
73
|
+
};
|
|
74
|
+
/** Static Noul question paired with {@link buildErrorIssuePairState}. */
|
|
75
|
+
declare const ERROR_SAME_BUG_NOUL_QUESTION: NoulQuestion;
|
|
76
|
+
/**
|
|
77
|
+
* Precision over recall: a false "same bug" hides a new error behind an old
|
|
78
|
+
* issue; a false "new" costs one duplicate issue a human closes. Validate on
|
|
79
|
+
* labelled GlitchTip-event-to-issue pairs before widening.
|
|
80
|
+
*/
|
|
81
|
+
declare const SAME_BUG_THRESHOLD = 0.85;
|
|
82
|
+
/** True when the Noul probability clears the same-bug threshold. */
|
|
83
|
+
declare function isLikelySameBug(noul: number, threshold?: number): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Question ids for a batched call over several candidates: one Noul per
|
|
86
|
+
* candidate, keyed `sameBug:<issueNumber>`. Pair with one state holding
|
|
87
|
+
* `error` plus `candidates[]`; each question names its candidate number.
|
|
88
|
+
*/
|
|
89
|
+
declare function buildSameBugQuestions(candidates: IssueCandidateInput[]): Record<string, NoulQuestion>;
|
|
90
|
+
/** State for the batched form: one error against several candidates. */
|
|
91
|
+
declare function buildErrorCandidatesState(report: ErrorReportInput, candidates: IssueCandidateInput[]): {
|
|
92
|
+
error: ErrorReportState;
|
|
93
|
+
candidates: IssueCandidateState[];
|
|
94
|
+
};
|
|
95
|
+
/** Parse `sameBug:<n>` back to the issue number, or null for a foreign key. */
|
|
96
|
+
declare function issueNumberFromSameBugKey(key: string): number | null;
|
|
97
|
+
|
|
98
|
+
export { ERROR_MAX_FRAMES, ERROR_SAME_BUG_NOUL_QUESTION, type ErrorFrameInput, type ErrorReportInput, type ErrorReportState, ISSUE_CANDIDATE_BODY_MAX_CHARS, type IssueCandidateInput, type IssueCandidateState, SAME_BUG_THRESHOLD, buildErrorCandidatesState, buildErrorIssuePairState, buildErrorReportState, buildIssueCandidateState, buildSameBugQuestions, isLikelySameBug, issueNumberFromSameBugKey, scrubErrorText };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// src/jev/error.ts
|
|
2
|
+
var DID_RE = /did:(?:plc|web):[A-Za-z0-9._:%-]+/g;
|
|
3
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
4
|
+
var HANDLE_RE = /@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
|
|
5
|
+
var PROFILE_PATH_RE = /(\/(?:p|profile)\/)[^/\s?#]+/g;
|
|
6
|
+
var AT_URI_RE = /at:\/\/[^\s/]+/g;
|
|
7
|
+
function scrubErrorText(text) {
|
|
8
|
+
return text.replace(AT_URI_RE, "at://<id>").replace(DID_RE, "<did>").replace(EMAIL_RE, "<email>").replace(HANDLE_RE, "<handle>").replace(PROFILE_PATH_RE, "$1<handle>");
|
|
9
|
+
}
|
|
10
|
+
var ERROR_MAX_FRAMES = 8;
|
|
11
|
+
var ISSUE_CANDIDATE_BODY_MAX_CHARS = 2e3;
|
|
12
|
+
function frameState(frames) {
|
|
13
|
+
const inApp = frames.filter((f) => f.inApp !== false);
|
|
14
|
+
const chosen = (inApp.length > 0 ? inApp : frames).slice(0, ERROR_MAX_FRAMES);
|
|
15
|
+
const out = chosen.map((f) => {
|
|
16
|
+
const s = {};
|
|
17
|
+
if (f.filename) s.filename = scrubErrorText(f.filename);
|
|
18
|
+
if (f.function) s.function = scrubErrorText(f.function);
|
|
19
|
+
if (typeof f.lineNo === "number") s.lineNo = f.lineNo;
|
|
20
|
+
return s;
|
|
21
|
+
});
|
|
22
|
+
return out.length > 0 ? out : void 0;
|
|
23
|
+
}
|
|
24
|
+
function buildErrorReportState(report) {
|
|
25
|
+
const state = { title: scrubErrorText(report.title.trim()) };
|
|
26
|
+
if (report.culprit) state.culprit = scrubErrorText(report.culprit);
|
|
27
|
+
if (report.level) state.level = report.level;
|
|
28
|
+
if (report.type) state.type = scrubErrorText(report.type);
|
|
29
|
+
if (report.value) state.value = scrubErrorText(report.value.trim());
|
|
30
|
+
if (report.release) state.release = report.release;
|
|
31
|
+
const frames = report.frames ? frameState(report.frames) : void 0;
|
|
32
|
+
if (frames) state.frames = frames;
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
function buildIssueCandidateState(issue) {
|
|
36
|
+
const state = {
|
|
37
|
+
number: issue.number,
|
|
38
|
+
title: scrubErrorText(issue.title.trim())
|
|
39
|
+
};
|
|
40
|
+
const body = issue.body?.trim();
|
|
41
|
+
if (body) state.body = scrubErrorText(body.slice(0, ISSUE_CANDIDATE_BODY_MAX_CHARS));
|
|
42
|
+
return state;
|
|
43
|
+
}
|
|
44
|
+
function buildErrorIssuePairState(report, issue) {
|
|
45
|
+
return { error: buildErrorReportState(report), issue: buildIssueCandidateState(issue) };
|
|
46
|
+
}
|
|
47
|
+
var ERROR_SAME_BUG_NOUL_QUESTION = {
|
|
48
|
+
type: "noul",
|
|
49
|
+
instructions: "The state holds an error report (title, culprit, exception, top stack frames) and an existing GitHub issue. Does the issue describe the same underlying bug as the error report?",
|
|
50
|
+
criteria: {
|
|
51
|
+
true: "Same root cause: the same exception at the same code path, or the issue text names the same failure even if line numbers or messages differ slightly.",
|
|
52
|
+
false: "A different bug: different exception type or code path, a different feature, or an issue that only shares generic words (error, 500, profile) with the report."
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var SAME_BUG_THRESHOLD = 0.85;
|
|
56
|
+
function isLikelySameBug(noul, threshold = SAME_BUG_THRESHOLD) {
|
|
57
|
+
return noul >= threshold;
|
|
58
|
+
}
|
|
59
|
+
function buildSameBugQuestions(candidates) {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (const c of candidates) {
|
|
62
|
+
out[`sameBug:${c.number}`] = {
|
|
63
|
+
type: "noul",
|
|
64
|
+
instructions: `${ERROR_SAME_BUG_NOUL_QUESTION.instructions} Judge the candidate issue with number ${c.number} in state.candidates.`,
|
|
65
|
+
...ERROR_SAME_BUG_NOUL_QUESTION.criteria ? { criteria: ERROR_SAME_BUG_NOUL_QUESTION.criteria } : {}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function buildErrorCandidatesState(report, candidates) {
|
|
71
|
+
return {
|
|
72
|
+
error: buildErrorReportState(report),
|
|
73
|
+
candidates: candidates.map(buildIssueCandidateState)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function issueNumberFromSameBugKey(key) {
|
|
77
|
+
const m = /^sameBug:(\d+)$/.exec(key);
|
|
78
|
+
return m ? Number(m[1]) : null;
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
ERROR_MAX_FRAMES,
|
|
82
|
+
ERROR_SAME_BUG_NOUL_QUESTION,
|
|
83
|
+
ISSUE_CANDIDATE_BODY_MAX_CHARS,
|
|
84
|
+
SAME_BUG_THRESHOLD,
|
|
85
|
+
buildErrorCandidatesState,
|
|
86
|
+
buildErrorIssuePairState,
|
|
87
|
+
buildErrorReportState,
|
|
88
|
+
buildIssueCandidateState,
|
|
89
|
+
buildSameBugQuestions,
|
|
90
|
+
isLikelySameBug,
|
|
91
|
+
issueNumberFromSameBugKey,
|
|
92
|
+
scrubErrorText
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/jev/error.ts"],"sourcesContent":["/**\n * Error-report judgments for the GlitchTip relay (sifa-workspace decision\n * 2026-10-05, background agents tier policy, Phase 2). Pure builders: the\n * state for \"is this error event the same bug as an existing issue?\", the\n * Noul question, the threshold, and the scrubber that removes identifiers\n * before anything leaves the server. No network, no key.\n *\n * Privacy: GlitchTip frames and messages can embed handles, DIDs, emails and\n * URLs with user paths. {@link scrubErrorText} runs over every string placed\n * in the state; callers must not bypass it.\n */\n\nimport type { NoulQuestion } from './index.js';\n\n// ---------------------------------------------------------------------------\n// Scrubbing\n// ---------------------------------------------------------------------------\n\nconst DID_RE = /did:(?:plc|web):[A-Za-z0-9._:%-]+/g;\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n/** `@handle.tld` mentions; a bare `@` inside code (decorators) has no dot-tld. */\nconst HANDLE_RE = /@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)+/g;\n/** `/p/<handle>` and `/profile/<handle>` path segments on Sifa URLs. */\nconst PROFILE_PATH_RE = /(\\/(?:p|profile)\\/)[^/\\s?#]+/g;\n/** at:// URIs carry a DID or handle as authority. */\nconst AT_URI_RE = /at:\\/\\/[^\\s/]+/g;\n\n/** Replace identifiers with stable placeholders. Idempotent. */\nexport function scrubErrorText(text: string): string {\n return text\n .replace(AT_URI_RE, 'at://<id>')\n .replace(DID_RE, '<did>')\n .replace(EMAIL_RE, '<email>')\n .replace(HANDLE_RE, '<handle>')\n .replace(PROFILE_PATH_RE, '$1<handle>');\n}\n\n// ---------------------------------------------------------------------------\n// Input and state\n// ---------------------------------------------------------------------------\n\n/** One stack frame, already reduced to what a same-bug judgment needs. */\nexport interface ErrorFrameInput {\n filename?: string;\n function?: string;\n lineNo?: number;\n inApp?: boolean;\n}\n\n/** A GlitchTip issue (error group) as seen by the relay. */\nexport interface ErrorReportInput {\n title: string;\n culprit?: string;\n level?: string;\n /** Exception type, e.g. `TypeError`. */\n type?: string;\n /** Exception value / message. */\n value?: string;\n frames?: ErrorFrameInput[];\n /** Release or deploy identifier, when GlitchTip has it. */\n release?: string;\n}\n\n/** An existing GitHub issue candidate (title + body, labelled `glitchtip`). */\nexport interface IssueCandidateInput {\n number: number;\n title: string;\n body?: string;\n}\n\n/** Keep the judgment cheap and bounded: top in-app frames only. */\nexport const ERROR_MAX_FRAMES = 8;\nexport const ISSUE_CANDIDATE_BODY_MAX_CHARS = 2000;\n\nexport interface ErrorReportState {\n title: string;\n culprit?: string;\n level?: string;\n type?: string;\n value?: string;\n release?: string;\n frames?: Array<{ filename?: string; function?: string; lineNo?: number }>;\n}\n\nexport interface IssueCandidateState {\n number: number;\n title: string;\n body?: string;\n}\n\nfunction frameState(frames: ErrorFrameInput[]): ErrorReportState['frames'] {\n const inApp = frames.filter((f) => f.inApp !== false);\n const chosen = (inApp.length > 0 ? inApp : frames).slice(0, ERROR_MAX_FRAMES);\n const out = chosen.map((f) => {\n const s: { filename?: string; function?: string; lineNo?: number } = {};\n if (f.filename) s.filename = scrubErrorText(f.filename);\n if (f.function) s.function = scrubErrorText(f.function);\n if (typeof f.lineNo === 'number') s.lineNo = f.lineNo;\n return s;\n });\n return out.length > 0 ? out : undefined;\n}\n\n/** Scrubbed, bounded state for the error side of the pair. */\nexport function buildErrorReportState(report: ErrorReportInput): ErrorReportState {\n const state: ErrorReportState = { title: scrubErrorText(report.title.trim()) };\n if (report.culprit) state.culprit = scrubErrorText(report.culprit);\n if (report.level) state.level = report.level;\n if (report.type) state.type = scrubErrorText(report.type);\n if (report.value) state.value = scrubErrorText(report.value.trim());\n if (report.release) state.release = report.release;\n const frames = report.frames ? frameState(report.frames) : undefined;\n if (frames) state.frames = frames;\n return state;\n}\n\n/** Scrubbed, bounded state for the GitHub issue side of the pair. */\nexport function buildIssueCandidateState(issue: IssueCandidateInput): IssueCandidateState {\n const state: IssueCandidateState = {\n number: issue.number,\n title: scrubErrorText(issue.title.trim()),\n };\n const body = issue.body?.trim();\n if (body) state.body = scrubErrorText(body.slice(0, ISSUE_CANDIDATE_BODY_MAX_CHARS));\n return state;\n}\n\n/** State for one error-vs-issue pair, both under stable named fields. */\nexport function buildErrorIssuePairState(\n report: ErrorReportInput,\n issue: IssueCandidateInput,\n): { error: ErrorReportState; issue: IssueCandidateState } {\n return { error: buildErrorReportState(report), issue: buildIssueCandidateState(issue) };\n}\n\n// ---------------------------------------------------------------------------\n// Question and threshold\n// ---------------------------------------------------------------------------\n\n/** Static Noul question paired with {@link buildErrorIssuePairState}. */\nexport const ERROR_SAME_BUG_NOUL_QUESTION: NoulQuestion = {\n type: 'noul',\n instructions:\n 'The state holds an error report (title, culprit, exception, top stack frames) and an existing GitHub issue. Does the issue describe the same underlying bug as the error report?',\n criteria: {\n true: 'Same root cause: the same exception at the same code path, or the issue text names the same failure even if line numbers or messages differ slightly.',\n false:\n 'A different bug: different exception type or code path, a different feature, or an issue that only shares generic words (error, 500, profile) with the report.',\n },\n};\n\n/**\n * Precision over recall: a false \"same bug\" hides a new error behind an old\n * issue; a false \"new\" costs one duplicate issue a human closes. Validate on\n * labelled GlitchTip-event-to-issue pairs before widening.\n */\nexport const SAME_BUG_THRESHOLD = 0.85;\n\n/** True when the Noul probability clears the same-bug threshold. */\nexport function isLikelySameBug(noul: number, threshold: number = SAME_BUG_THRESHOLD): boolean {\n return noul >= threshold;\n}\n\n/**\n * Question ids for a batched call over several candidates: one Noul per\n * candidate, keyed `sameBug:<issueNumber>`. Pair with one state holding\n * `error` plus `candidates[]`; each question names its candidate number.\n */\nexport function buildSameBugQuestions(\n candidates: IssueCandidateInput[],\n): Record<string, NoulQuestion> {\n const out: Record<string, NoulQuestion> = {};\n for (const c of candidates) {\n out[`sameBug:${c.number}`] = {\n type: 'noul',\n instructions: `${ERROR_SAME_BUG_NOUL_QUESTION.instructions} Judge the candidate issue with number ${c.number} in state.candidates.`,\n ...(ERROR_SAME_BUG_NOUL_QUESTION.criteria\n ? { criteria: ERROR_SAME_BUG_NOUL_QUESTION.criteria }\n : {}),\n };\n }\n return out;\n}\n\n/** State for the batched form: one error against several candidates. */\nexport function buildErrorCandidatesState(\n report: ErrorReportInput,\n candidates: IssueCandidateInput[],\n): { error: ErrorReportState; candidates: IssueCandidateState[] } {\n return {\n error: buildErrorReportState(report),\n candidates: candidates.map(buildIssueCandidateState),\n };\n}\n\n/** Parse `sameBug:<n>` back to the issue number, or null for a foreign key. */\nexport function issueNumberFromSameBugKey(key: string): number | null {\n const m = /^sameBug:(\\d+)$/.exec(key);\n return m ? Number(m[1]) : null;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@singi-labs/sifa-sdk",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.81",
|
|
4
4
|
"description": "Sifa SDK — public client library for the Sifa AppView on AT Protocol. Shared by sifa-web and sifa-app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -182,6 +182,16 @@
|
|
|
182
182
|
"default": "./dist/jev/issue.cjs"
|
|
183
183
|
}
|
|
184
184
|
},
|
|
185
|
+
"./jev/error": {
|
|
186
|
+
"import": {
|
|
187
|
+
"types": "./dist/jev/error.d.ts",
|
|
188
|
+
"default": "./dist/jev/error.js"
|
|
189
|
+
},
|
|
190
|
+
"require": {
|
|
191
|
+
"types": "./dist/jev/error.d.cts",
|
|
192
|
+
"default": "./dist/jev/error.cjs"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
185
195
|
"./rpg": {
|
|
186
196
|
"import": {
|
|
187
197
|
"types": "./dist/rpg/index.d.ts",
|