@amityco/social-plus-vise 0.8.1 → 0.12.2
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/CHANGELOG.md +207 -0
- package/README.md +107 -40
- package/dist/capabilities.js +447 -0
- package/dist/outcomes.js +463 -5
- package/dist/server.js +115 -3
- package/dist/tools/ast.js +25 -0
- package/dist/tools/compliance.js +88 -20
- package/dist/tools/debug.js +267 -0
- package/dist/tools/design.js +1496 -0
- package/dist/tools/docs.js +9 -4
- package/dist/tools/harness.js +17 -1
- package/dist/tools/integration.js +83 -7
- package/dist/tools/project.js +872 -67
- package/dist/tools/sdkVersion.js +129 -0
- package/dist/types.js +4 -0
- package/package.json +27 -6
- package/rules/auth.yaml +298 -38
- package/rules/comments.yaml +0 -72
- package/rules/feed.yaml +1151 -12
- package/rules/live-data.yaml +316 -36
- package/rules/push.yaml +140 -0
- package/rules/sdk-lifecycle.yaml +1428 -138
- package/rules/security.yaml +60 -0
- package/skills/social-plus-vise/SKILL.md +98 -55
- package/skills/social-plus-vise/reference/debugging.md +39 -0
- package/skills/social-plus-vise/reference/operations.md +59 -0
- package/skills/vise-harness-engineer/SKILL.md +35 -0
- package/social.plus-vise.png +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { objectInput, optionalBooleanField, stringField, textResult } from "../types.js";
|
|
2
|
+
import { checkCompliance, rulesById } from "./compliance.js";
|
|
3
|
+
import { inspectProject } from "./project.js";
|
|
4
|
+
function sanitizeError(errorMessage) {
|
|
5
|
+
// Strip out local absolute file paths (e.g., /Users/admin/..., /home/user/...)
|
|
6
|
+
let sanitized = errorMessage.replace(/(?:\/(?:users|home)\/[^\s:]+)/gi, "[LOCAL_PATH]");
|
|
7
|
+
// Strip out thread IDs / Hex memory addresses
|
|
8
|
+
sanitized = sanitized.replace(/0x[0-9a-fA-F]+/g, "[HEX_ADDR]");
|
|
9
|
+
return sanitized;
|
|
10
|
+
}
|
|
11
|
+
function extractExceptionClass(errorMessage) {
|
|
12
|
+
// Try to find a Java/Kotlin exception (e.g. java.lang.NullPointerException or com.amity.AmityException)
|
|
13
|
+
const javaMatch = errorMessage.match(/([a-zA-Z0-9_.]+[A-Z][a-zA-Z0-9_]*Exception)/);
|
|
14
|
+
if (javaMatch)
|
|
15
|
+
return javaMatch[1];
|
|
16
|
+
// Try to find a JS Error (e.g. TypeError, Error)
|
|
17
|
+
const jsMatch = errorMessage.match(/([A-Z][a-zA-Z0-9]*Error):/);
|
|
18
|
+
if (jsMatch)
|
|
19
|
+
return jsMatch[1];
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
export async function debugIssue(repoPath, errorMessage, options = {}) {
|
|
23
|
+
const sanitizedError = sanitizeError(errorMessage);
|
|
24
|
+
const exceptionClass = extractExceptionClass(errorMessage);
|
|
25
|
+
// 1. Inspect Context
|
|
26
|
+
const inspection = await inspectProject(repoPath);
|
|
27
|
+
// 2. Version-mismatch heuristic (NOT used as benchmark evidence).
|
|
28
|
+
// This is a keyword-only heuristic, not real dependency inspection.
|
|
29
|
+
// Version-mismatch scenarios are excluded from the TypeScript pilot
|
|
30
|
+
// (see DEBUGGING_BENCHMARK_PLAN.md Section 5 "Explicitly excluded from the pilot").
|
|
31
|
+
// Do not use this branch as measured benchmark evidence.
|
|
32
|
+
let likelyCause = "";
|
|
33
|
+
if (errorMessage.includes("method not found") || errorMessage.includes("Unresolved reference")) {
|
|
34
|
+
likelyCause = "Potential Version Mismatch: The codebase is using a newer API pattern against an older SDK version installed in the project.";
|
|
35
|
+
}
|
|
36
|
+
// 3. Compliance History Correlation
|
|
37
|
+
const correlatedRules = [];
|
|
38
|
+
let checkResult = null;
|
|
39
|
+
try {
|
|
40
|
+
checkResult = await checkCompliance(repoPath);
|
|
41
|
+
const rulesMap = await rulesById();
|
|
42
|
+
for (const rule of checkResult.rules) {
|
|
43
|
+
if (rule.status === "deterministic-fail" || rule.status === "attestation-needed") {
|
|
44
|
+
// It failed. Check if symptoms match.
|
|
45
|
+
const ruleDef = rulesMap.get(rule.ruleId);
|
|
46
|
+
const symptoms = ruleDef?.symptoms || [];
|
|
47
|
+
if (symptoms.some(s => errorMessage.includes(s))) {
|
|
48
|
+
correlatedRules.push({
|
|
49
|
+
ruleId: rule.ruleId,
|
|
50
|
+
status: "failed",
|
|
51
|
+
impact: `This rule is currently failing and its known symptoms match the crash log.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (rule.status === "attested") {
|
|
56
|
+
// Check for false-positive attestations
|
|
57
|
+
const ruleDef = rulesMap.get(rule.ruleId);
|
|
58
|
+
const symptoms = ruleDef?.symptoms || [];
|
|
59
|
+
if (symptoms.some(s => errorMessage.includes(s))) {
|
|
60
|
+
correlatedRules.push({
|
|
61
|
+
ruleId: rule.ruleId,
|
|
62
|
+
status: "attested",
|
|
63
|
+
attestation: rule.attestation,
|
|
64
|
+
impact: `Warning: This rule was attested as passing, but a matching runtime exception was detected. The custom implementation may be flawed.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// Ignore error if compliance sidecar doesn't exist yet
|
|
72
|
+
}
|
|
73
|
+
if (!likelyCause && correlatedRules.length > 0) {
|
|
74
|
+
likelyCause = `The crash is likely caused by the non-compliant implementation of ${correlatedRules.map(r => r.ruleId).join(", ")}.`;
|
|
75
|
+
}
|
|
76
|
+
else if (!likelyCause) {
|
|
77
|
+
likelyCause = `An unknown ${exceptionClass || "runtime"} error occurred.`;
|
|
78
|
+
}
|
|
79
|
+
// Build rule-specific remediation guidance from rule definitions and validator findings.
|
|
80
|
+
const suggestedRemediation = buildRemediation(correlatedRules, checkResult);
|
|
81
|
+
const repairBrief = buildRepairBrief(correlatedRules, checkResult, inspection.platforms, likelyCause);
|
|
82
|
+
const payload = {
|
|
83
|
+
correlatedRules,
|
|
84
|
+
likelyCause,
|
|
85
|
+
sanitizedErrorSignature: sanitizedError,
|
|
86
|
+
extractedException: exceptionClass,
|
|
87
|
+
suggestedRemediation,
|
|
88
|
+
repairBrief,
|
|
89
|
+
relevantDocs: [
|
|
90
|
+
{
|
|
91
|
+
title: "Troubleshooting",
|
|
92
|
+
url: "https://learn.social.plus/troubleshooting"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
if (options.brief) {
|
|
97
|
+
return {
|
|
98
|
+
likelyCause: payload.likelyCause,
|
|
99
|
+
correlatedRules: payload.correlatedRules,
|
|
100
|
+
repairBrief: payload.repairBrief,
|
|
101
|
+
relevantDocs: payload.relevantDocs,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return payload;
|
|
105
|
+
}
|
|
106
|
+
function buildRemediation(correlatedRules, checkResult) {
|
|
107
|
+
if (correlatedRules.length === 0) {
|
|
108
|
+
return { action: "Review the correlated rules and ensure SDK integration matches the required patterns." };
|
|
109
|
+
}
|
|
110
|
+
const rules = correlatedRules.map(cr => {
|
|
111
|
+
const ruleResult = checkResult?.rules.find(r => r.ruleId === cr.ruleId);
|
|
112
|
+
// Prefer the validator's per-finding recommendation; fall back to a generic note.
|
|
113
|
+
const guidance = ruleResult?.recommendation
|
|
114
|
+
?? "Review the rule implementation and ensure the SDK integration matches the required pattern.";
|
|
115
|
+
return {
|
|
116
|
+
ruleId: cr.ruleId,
|
|
117
|
+
title: ruleResult?.title ?? cr.ruleId,
|
|
118
|
+
guidance,
|
|
119
|
+
file: ruleResult?.finding?.file,
|
|
120
|
+
docRef: `vise explain ${cr.ruleId}`,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
action: "Repair the compliance failure(s) identified above.",
|
|
125
|
+
rules,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function buildRepairBrief(correlatedRules, checkResult, platforms, likelyCause) {
|
|
129
|
+
const primaryRuleId = pickPrimaryRuleId(correlatedRules);
|
|
130
|
+
const verify = verificationCommands(platforms);
|
|
131
|
+
if (!primaryRuleId) {
|
|
132
|
+
return {
|
|
133
|
+
primaryRuleId: null,
|
|
134
|
+
why: likelyCause,
|
|
135
|
+
candidateFiles: [],
|
|
136
|
+
minimumPatchShape: [
|
|
137
|
+
"Inspect the SDK integration path mentioned in the symptom.",
|
|
138
|
+
"Repair only the smallest social.plus code path that explains the runtime failure.",
|
|
139
|
+
],
|
|
140
|
+
preserve: [
|
|
141
|
+
"Do not widen the patch beyond the failing social.plus integration path.",
|
|
142
|
+
"Preserve any existing compliant setup, auth, and pagination wiring unless the customer explicitly changes behavior.",
|
|
143
|
+
],
|
|
144
|
+
verify,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const ruleResult = checkResult?.rules.find((rule) => rule.ruleId === primaryRuleId);
|
|
148
|
+
const template = repairTemplateForRule(primaryRuleId);
|
|
149
|
+
return {
|
|
150
|
+
primaryRuleId,
|
|
151
|
+
why: template.why ?? likelyCause,
|
|
152
|
+
candidateFiles: candidateFilesForRule(primaryRuleId, checkResult),
|
|
153
|
+
minimumPatchShape: template.minimumPatchShape,
|
|
154
|
+
preserve: template.preserve,
|
|
155
|
+
verify,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function pickPrimaryRuleId(correlatedRules) {
|
|
159
|
+
const failed = correlatedRules.find((rule) => rule.status === "failed");
|
|
160
|
+
return failed?.ruleId ?? correlatedRules[0]?.ruleId ?? null;
|
|
161
|
+
}
|
|
162
|
+
function candidateFilesForRule(ruleId, checkResult) {
|
|
163
|
+
if (!checkResult) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const matches = checkResult.rules
|
|
167
|
+
.filter((rule) => rule.ruleId === ruleId)
|
|
168
|
+
.flatMap((rule) => {
|
|
169
|
+
const file = rule.finding?.file;
|
|
170
|
+
return typeof file === "string" && file.length > 0 ? [file] : [];
|
|
171
|
+
});
|
|
172
|
+
return Array.from(new Set(matches));
|
|
173
|
+
}
|
|
174
|
+
function verificationCommands(platforms) {
|
|
175
|
+
const commands = [];
|
|
176
|
+
if (platforms.includes("typescript") || platforms.includes("react-native")) {
|
|
177
|
+
commands.push("npm run typecheck");
|
|
178
|
+
}
|
|
179
|
+
commands.push("vise check . --ci");
|
|
180
|
+
commands.push("vise run-sensors .");
|
|
181
|
+
return commands;
|
|
182
|
+
}
|
|
183
|
+
function repairTemplateForRule(ruleId) {
|
|
184
|
+
const templates = {
|
|
185
|
+
"typescript.client.region": {
|
|
186
|
+
why: "The SDK client is initialized without an explicit region or endpoint, so runtime setup does not match the customer's social.plus project.",
|
|
187
|
+
minimumPatchShape: [
|
|
188
|
+
"Edit the existing client setup module or initialization function.",
|
|
189
|
+
"Pass apiRegion or apiEndpoint into the existing client initialization call, such as Client.create() or Client.createClient().",
|
|
190
|
+
"Prefer the app's existing config or environment source if one already exists.",
|
|
191
|
+
],
|
|
192
|
+
preserve: [
|
|
193
|
+
"Preserve the app's existing config or environment source of truth for region or endpoint.",
|
|
194
|
+
"Do not hardcode a guessed default region if the customer already has a region owner elsewhere in the app.",
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
"typescript.session.renewal": {
|
|
198
|
+
why: "The login path is missing a renewal handler, so the SDK cannot refresh expiring access tokens.",
|
|
199
|
+
minimumPatchShape: [
|
|
200
|
+
"Edit the existing Client.login call in the current login path.",
|
|
201
|
+
"Add sessionHandler.sessionWillRenewAccessToken(renewal).",
|
|
202
|
+
"Call renewal.renew() from that callback.",
|
|
203
|
+
],
|
|
204
|
+
preserve: [
|
|
205
|
+
"Preserve the existing session owner and login flow shape.",
|
|
206
|
+
"Retain the session handler for the full session lifetime instead of creating a short-lived local object.",
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
"typescript.live-collection.api-mismatch": {
|
|
210
|
+
why: "The feed is using a one-shot list query instead of a reactive LiveCollection, so new posts do not flow into the UI.",
|
|
211
|
+
minimumPatchShape: [
|
|
212
|
+
"Edit the existing feed query function instead of replacing the feed surface.",
|
|
213
|
+
"Convert the one-shot query into a reactive collection with onData.",
|
|
214
|
+
"Return cleanup that unsubscribes the collection.",
|
|
215
|
+
],
|
|
216
|
+
preserve: [
|
|
217
|
+
"Preserve existing pagination and query wiring such as pageToken, hasMore/loadMore, or infinite-query state.",
|
|
218
|
+
"Do not widen the patch into unrelated rendering or moderation logic while restoring live updates.",
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
"typescript.auth.logout-on-user-switch": {
|
|
222
|
+
why: "The app switches user identity by logging in directly without clearing the previous SDK session.",
|
|
223
|
+
minimumPatchShape: [
|
|
224
|
+
"Edit the existing user-switch flow.",
|
|
225
|
+
"Await Client.logout() before the next Client.login().",
|
|
226
|
+
"Keep the login call's existing renewal handler behavior intact.",
|
|
227
|
+
],
|
|
228
|
+
preserve: [
|
|
229
|
+
"Preserve the current sessionHandler and renewal path while inserting logout-before-login.",
|
|
230
|
+
"Do not widen the patch into unrelated auth or navigation state.",
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
return (templates[ruleId] ?? {
|
|
235
|
+
why: "A compliance rule correlated strongly with the reported social.plus failure.",
|
|
236
|
+
minimumPatchShape: [
|
|
237
|
+
"Edit the smallest code path associated with the correlated rule.",
|
|
238
|
+
"Bring the implementation back into compliance with the rule requirement.",
|
|
239
|
+
],
|
|
240
|
+
preserve: [
|
|
241
|
+
"Preserve adjacent compliant wiring while repairing the failing rule.",
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
export const debugIssueTool = {
|
|
246
|
+
name: "debug_issue",
|
|
247
|
+
description: "Debug a runtime crash, build failure, or logic issue against the social.plus SDK's specific context.",
|
|
248
|
+
inputSchema: {
|
|
249
|
+
type: "object",
|
|
250
|
+
properties: {
|
|
251
|
+
repoPath: { type: "string" },
|
|
252
|
+
errorMessage: { type: "string" },
|
|
253
|
+
brief: { type: "boolean" },
|
|
254
|
+
},
|
|
255
|
+
required: ["repoPath", "errorMessage"],
|
|
256
|
+
additionalProperties: false,
|
|
257
|
+
},
|
|
258
|
+
async call(input) {
|
|
259
|
+
const args = objectInput(input);
|
|
260
|
+
const repoPath = stringField(args, "repoPath");
|
|
261
|
+
const errorMessage = stringField(args, "errorMessage");
|
|
262
|
+
const result = await debugIssue(repoPath, errorMessage, {
|
|
263
|
+
brief: optionalBooleanField(args, "brief"),
|
|
264
|
+
});
|
|
265
|
+
return textResult(result);
|
|
266
|
+
},
|
|
267
|
+
};
|