@amityco/social-plus-vise 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +10 -4
- package/dist/flow.js +6 -0
- package/dist/server.js +71 -18
- package/dist/tools/blocks.js +82 -9
- package/dist/tools/compliance.js +315 -27
- package/dist/tools/internalWorkspace.js +44 -0
- package/dist/tools/project.js +116 -29
- package/dist/tools/sdkFacts.js +233 -7
- package/dist/tools/sensors.js +102 -36
- package/package.json +2 -2
- package/packages/intelligence/catalog/catalog.schema.json +6 -0
- package/packages/intelligence/catalog/experience-objects.json +11 -11
- package/sdk-surface/manifest.json +4 -4
- package/sdk-surface/models.typescript.json +2903 -463
- package/skills/social-plus-vise/SKILL.md +20 -0
package/dist/tools/project.js
CHANGED
|
@@ -39,7 +39,7 @@ export async function inspectProject(repoPath, surfacePath) {
|
|
|
39
39
|
const surfaces = await detectProjectSurfaces(repoRoot);
|
|
40
40
|
const effectiveRoot = resolveSurfaceRoot(repoRoot, surfacePath);
|
|
41
41
|
const selectedSurface = surfacePath ? await surfaceForPath(repoRoot, effectiveRoot) : surfaceMatchingRoot(surfaces, repoRoot);
|
|
42
|
-
const { platforms, signals, designSignals } = await inspectRoot(effectiveRoot);
|
|
42
|
+
const { platforms, signals, designSignals, scanCoverage } = await inspectRoot(effectiveRoot);
|
|
43
43
|
return {
|
|
44
44
|
repoRoot,
|
|
45
45
|
effectiveRoot,
|
|
@@ -48,6 +48,7 @@ export async function inspectProject(repoPath, surfacePath) {
|
|
|
48
48
|
signals,
|
|
49
49
|
designSignals,
|
|
50
50
|
surfaces,
|
|
51
|
+
scanCoverage,
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
function resolveSurfaceRoot(repoRoot, surfacePath) {
|
|
@@ -63,6 +64,7 @@ function resolveSurfaceRoot(repoRoot, surfacePath) {
|
|
|
63
64
|
}
|
|
64
65
|
async function inspectRoot(root) {
|
|
65
66
|
const signals = [];
|
|
67
|
+
const scanContext = { validationRoot: root, scans: [] };
|
|
66
68
|
const candidates = [
|
|
67
69
|
["android", "settings.gradle", "Gradle project settings"],
|
|
68
70
|
["android", "settings.gradle.kts", "Gradle Kotlin project settings"],
|
|
@@ -100,7 +102,7 @@ async function inspectRoot(root) {
|
|
|
100
102
|
}
|
|
101
103
|
const hasJsSignal = signals.some((signal) => signal.platform === "typescript" || signal.platform === "react-native");
|
|
102
104
|
if (!hasJsSignal) {
|
|
103
|
-
const sourceSignal = await detectSdkSignalFromSource(root);
|
|
105
|
+
const sourceSignal = await detectSdkSignalFromSource(root, scanContext);
|
|
104
106
|
if (sourceSignal) {
|
|
105
107
|
signals.push(sourceSignal);
|
|
106
108
|
}
|
|
@@ -113,10 +115,18 @@ async function inspectRoot(root) {
|
|
|
113
115
|
: hasAndroid
|
|
114
116
|
? rawPlatforms.filter((p) => p !== "typescript")
|
|
115
117
|
: rawPlatforms;
|
|
116
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
platforms,
|
|
120
|
+
signals,
|
|
121
|
+
designSignals: await detectDesignSignals(root),
|
|
122
|
+
scanCoverage: {
|
|
123
|
+
complete: scanContext.scans.every((scan) => !scan.truncated),
|
|
124
|
+
scans: scanContext.scans,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
117
127
|
}
|
|
118
|
-
async function detectSdkSignalFromSource(root) {
|
|
119
|
-
const sourceFiles = await findFiles(root, [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".html", ".htm"], 500);
|
|
128
|
+
async function detectSdkSignalFromSource(root, scanContext) {
|
|
129
|
+
const sourceFiles = await findFiles(root, [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".html", ".htm"], 500, scanContext, "platform-detection.javascript-source");
|
|
120
130
|
if (sourceFiles.length === 0) {
|
|
121
131
|
return null;
|
|
122
132
|
}
|
|
@@ -269,7 +279,25 @@ async function installedBlockEntryStillValid(root, entry) {
|
|
|
269
279
|
return false;
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
|
-
|
|
282
|
+
const publicEntrypoint = typeof entry.publicEntrypoint === "string" ? entry.publicEntrypoint : "";
|
|
283
|
+
const importName = typeof entry.importName === "string" ? entry.importName : "";
|
|
284
|
+
const mountFiles = Array.isArray(entry.mountFiles) ? entry.mountFiles : [];
|
|
285
|
+
if (!publicEntrypoint || !importName || mountFiles.length === 0) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
const usagePattern = platform === "flutter"
|
|
289
|
+
? new RegExp(`\\b${escapeRegExp(importName)}\\s*\\(`)
|
|
290
|
+
: new RegExp(`<\\s*${escapeRegExp(importName)}(?:\\s|/|>)|(?:React\\.)?createElement\\s*\\(\\s*${escapeRegExp(importName)}\\b|\\b${escapeRegExp(importName)}\\s*\\(`);
|
|
291
|
+
for (const mountFile of mountFiles) {
|
|
292
|
+
if (typeof mountFile !== "string" || mountFile.trim() === "") {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const source = await readIfExists(path.join(root, mountFile));
|
|
296
|
+
if (source.includes(publicEntrypoint) && usagePattern.test(source)) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
273
301
|
}
|
|
274
302
|
async function sidecarOutcomeInScope(root, outcome) {
|
|
275
303
|
const dir = sidecarDir(root);
|
|
@@ -354,29 +382,56 @@ export const validateSetupTool = {
|
|
|
354
382
|
const repoPath = stringField(args, "repoPath");
|
|
355
383
|
const inspection = await inspectProject(repoPath, optionalStringField(args, "surfacePath"));
|
|
356
384
|
const platform = optionalStringField(args, "platform") ?? inspection.platforms[0] ?? "unknown";
|
|
357
|
-
const
|
|
385
|
+
const validation = await validateSetupWithCoverage(inspection.effectiveRoot, platform);
|
|
386
|
+
const platformWasExplicit = optionalStringField(args, "platform") !== undefined;
|
|
387
|
+
const scanCoverage = platformWasExplicit
|
|
388
|
+
? validation.scanCoverage
|
|
389
|
+
: {
|
|
390
|
+
complete: inspection.scanCoverage.complete && validation.scanCoverage.complete,
|
|
391
|
+
scans: [...inspection.scanCoverage.scans, ...validation.scanCoverage.scans],
|
|
392
|
+
};
|
|
393
|
+
const findings = [...validation.findings];
|
|
394
|
+
if (!platformWasExplicit && !inspection.scanCoverage.complete && !findings.some((finding) => finding.ruleId === "project.scan.complete")) {
|
|
395
|
+
findings.push(incompleteInspectionFinding(inspection.scanCoverage));
|
|
396
|
+
}
|
|
358
397
|
return textResult({
|
|
359
398
|
platform,
|
|
360
399
|
surfacePath: inspection.selectedSurface?.path,
|
|
361
400
|
status: statusForFindings(findings),
|
|
362
401
|
findings,
|
|
402
|
+
scanCoverage,
|
|
363
403
|
});
|
|
364
404
|
},
|
|
365
405
|
};
|
|
366
406
|
export async function validateSetup(repoPath, platform) {
|
|
407
|
+
return (await validateSetupWithCoverage(repoPath, platform)).findings;
|
|
408
|
+
}
|
|
409
|
+
function incompleteInspectionFinding(scanCoverage) {
|
|
410
|
+
const truncated = scanCoverage.scans.filter((scan) => scan.truncated);
|
|
411
|
+
return {
|
|
412
|
+
ruleId: "project.scan.complete",
|
|
413
|
+
severity: "error",
|
|
414
|
+
message: `Vise could not inspect the complete project inventory: ${truncated
|
|
415
|
+
.map((scan) => `${scan.scope} reached its ${scan.limit}-file limit under ${scan.root}`)
|
|
416
|
+
.join("; ")}.`,
|
|
417
|
+
recommendation: "Point Vise at the specific app with --surface/--surface-path before planning, initializing, validating, or selecting sensors.",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
export async function validateSetupWithCoverage(repoPath, platform) {
|
|
367
421
|
const root = path.resolve(repoPath);
|
|
368
422
|
const findings = [];
|
|
423
|
+
const scanContext = { validationRoot: root, scans: [] };
|
|
369
424
|
if (platform === "android") {
|
|
370
|
-
findings.push(...(await validateAndroid(root)));
|
|
425
|
+
findings.push(...(await validateAndroid(root, scanContext)));
|
|
371
426
|
}
|
|
372
427
|
if (platform === "flutter") {
|
|
373
|
-
findings.push(...(await validateFlutter(root)));
|
|
428
|
+
findings.push(...(await validateFlutter(root, scanContext)));
|
|
374
429
|
}
|
|
375
430
|
if (platform === "typescript" || platform === "react-native") {
|
|
376
|
-
findings.push(...(await validateTypeScript(root, platform)));
|
|
431
|
+
findings.push(...(await validateTypeScript(root, platform, scanContext)));
|
|
377
432
|
}
|
|
378
433
|
if (platform === "ios") {
|
|
379
|
-
findings.push(...(await validateIos(root)));
|
|
434
|
+
findings.push(...(await validateIos(root, scanContext)));
|
|
380
435
|
}
|
|
381
436
|
if (platform === "unknown") {
|
|
382
437
|
findings.push({
|
|
@@ -386,21 +441,38 @@ export async function validateSetup(repoPath, platform) {
|
|
|
386
441
|
recommendation: "Point repoPath at the app root or provide a platform override.",
|
|
387
442
|
});
|
|
388
443
|
}
|
|
389
|
-
|
|
444
|
+
const truncatedScans = scanContext.scans.filter((scan) => scan.truncated);
|
|
445
|
+
if (truncatedScans.length > 0) {
|
|
446
|
+
findings.push({
|
|
447
|
+
ruleId: "project.scan.complete",
|
|
448
|
+
severity: "error",
|
|
449
|
+
message: `Vise could not inspect the complete source surface: ${truncatedScans
|
|
450
|
+
.map((scan) => `${scan.scope} reached its ${scan.limit}-file limit under ${scan.root}`)
|
|
451
|
+
.join("; ")}.`,
|
|
452
|
+
recommendation: "Point Vise at the specific app with --surface/--surface-path, remove generated or vendored source from that surface, or raise the governed scan limit before relying on absence-based validation.",
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
findings,
|
|
457
|
+
scanCoverage: {
|
|
458
|
+
complete: truncatedScans.length === 0,
|
|
459
|
+
scans: scanContext.scans,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
390
462
|
}
|
|
391
|
-
async function validateAndroid(root) {
|
|
463
|
+
async function validateAndroid(root, scanContext) {
|
|
392
464
|
const findings = [];
|
|
393
465
|
const manifestPath = "app/src/main/AndroidManifest.xml";
|
|
394
466
|
const manifest = await readIfExists(path.join(root, manifestPath));
|
|
395
|
-
const gradleScripts = (await findFiles(root, [".gradle", ".kts"], 200)).filter((f) => /(?:^|[\\/])(?:build|settings)\.gradle(?:\.kts)?$/.test(f));
|
|
467
|
+
const gradleScripts = (await findFiles(root, [".gradle", ".kts"], 200, scanContext, "android.gradle-manifests")).filter((f) => /(?:^|[\\/])(?:build|settings)\.gradle(?:\.kts)?$/.test(f));
|
|
396
468
|
const buildFiles = [...new Set([
|
|
397
469
|
...(await existingFiles(root, ["app/build.gradle", "app/build.gradle.kts", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"])),
|
|
398
470
|
...gradleScripts,
|
|
399
471
|
])];
|
|
400
|
-
const versionCatalogFiles = (await findFiles(root, [".toml"], 50)).filter((f) => /(?:^|[\\/])gradle[\\/].*\.versions\.toml$/.test(f));
|
|
472
|
+
const versionCatalogFiles = (await findFiles(root, [".toml"], 50, scanContext, "android.version-catalogs")).filter((f) => /(?:^|[\\/])gradle[\\/].*\.versions\.toml$/.test(f));
|
|
401
473
|
const buildContent = await readMany(buildFiles);
|
|
402
474
|
const versionCatalogContent = await readMany(versionCatalogFiles);
|
|
403
|
-
const sourceFiles = await findFiles(root, [".kt", ".java"], 300);
|
|
475
|
+
const sourceFiles = await findFiles(root, [".kt", ".java"], 300, scanContext, "android.source");
|
|
404
476
|
const sourceContent = await readMany(sourceFiles);
|
|
405
477
|
const setupFiles = filesMatching(sourceContent, [/AmityCoreClient\s*\.\s*setup/, /AmityClient\s*\.\s*setup/, /AmityUIKit\d*Manager\s*\.\s*setup/]);
|
|
406
478
|
const coreLoginFiles = filesMatching(sourceContent, [/AmityCoreClient\s*\.\s*login/, /AmityClient\s*\.\s*login/]);
|
|
@@ -518,11 +590,11 @@ async function validateAndroid(root) {
|
|
|
518
590
|
findings.push(...(await validateEnvSecretHygiene(root, "android", sourceContent)));
|
|
519
591
|
return findings;
|
|
520
592
|
}
|
|
521
|
-
async function validateFlutter(root) {
|
|
593
|
+
async function validateFlutter(root, scanContext) {
|
|
522
594
|
const findings = [];
|
|
523
595
|
const pubspec = await readIfExists(path.join(root, "pubspec.yaml"));
|
|
524
596
|
const pubspecLock = await readIfExists(path.join(root, "pubspec.lock"));
|
|
525
|
-
const dartFiles = await findFiles(path.join(root, "lib"), [".dart"], 300);
|
|
597
|
+
const dartFiles = await findFiles(path.join(root, "lib"), [".dart"], 300, scanContext, "flutter.source");
|
|
526
598
|
const dartContent = await readMany(dartFiles);
|
|
527
599
|
const setupFiles = filesMatching(dartContent, [/AmityCoreClient\s*\.\s*setup/, /AmityUIKit\d*Manager\s*\.\s*setup/]);
|
|
528
600
|
const coreLoginFiles = filesMatching(dartContent, [/AmityCoreClient\s*\.\s*login/]);
|
|
@@ -643,10 +715,10 @@ async function validateFlutter(root) {
|
|
|
643
715
|
findings.push(...(await validateEnvSecretHygiene(root, "flutter", dartContent)));
|
|
644
716
|
return findings;
|
|
645
717
|
}
|
|
646
|
-
async function validateTypeScript(root, platform) {
|
|
718
|
+
async function validateTypeScript(root, platform, scanContext) {
|
|
647
719
|
const findings = [];
|
|
648
720
|
const packageJson = await readIfExists(path.join(root, "package.json"));
|
|
649
|
-
const sourceFiles = await findFiles(root, [".ts", ".tsx", ".js", ".jsx"], 500);
|
|
721
|
+
const sourceFiles = await findFiles(root, [".ts", ".tsx", ".js", ".jsx"], 500, scanContext, `${platform}.source`);
|
|
650
722
|
const sourceContent = await readMany(sourceFiles);
|
|
651
723
|
const setupFiles = filesMatching(sourceContent, [/Client\s*\.\s*createClient/, /Client\s*\.\s*create\s*\(/, /createClient\s*\(/, /Amity(?:UIKit|UiKit)Provider/]);
|
|
652
724
|
const rawLoginFiles = filesMatching(sourceContent, [/Client\s*\.\s*login/, /\.login\s*\(/]);
|
|
@@ -3252,7 +3324,7 @@ function iosAmitySdkIsFloating(s) {
|
|
|
3252
3324
|
return true;
|
|
3253
3325
|
return false;
|
|
3254
3326
|
}
|
|
3255
|
-
async function validateIos(root) {
|
|
3327
|
+
async function validateIos(root, scanContext) {
|
|
3256
3328
|
const findings = [];
|
|
3257
3329
|
const joinExisting = async (names) => [...(await readMany(await existingFiles(root, names))).values()].join("\n");
|
|
3258
3330
|
const podText = await joinExisting(["Podfile"]);
|
|
@@ -3277,7 +3349,7 @@ async function validateIos(root) {
|
|
|
3277
3349
|
];
|
|
3278
3350
|
const manifestCombined = [podText, pkgSwiftText, projectYmlText, pbxprojText].join("\n");
|
|
3279
3351
|
const hasManifest = manifestCombined.trim().length > 0;
|
|
3280
|
-
const swiftFiles = await findFiles(root, [".swift", ".xcconfig", ".plist"], 400);
|
|
3352
|
+
const swiftFiles = await findFiles(root, [".swift", ".xcconfig", ".plist"], 400, scanContext, "ios.source");
|
|
3281
3353
|
const swiftContent = await readMany(swiftFiles);
|
|
3282
3354
|
const setupFiles = filesMatching(swiftContent, [/AmityClient\s*\(/, /AmityClient\s*\.\s*setup/, /AmityUIKit\d*Manager\s*\.\s*setup/]);
|
|
3283
3355
|
const coreLoginFiles = filesMatching(swiftContent, [/\.login\s*\(/, /client\.login\s*\(/]);
|
|
@@ -3603,10 +3675,11 @@ function inferDesignKind(relativePath) {
|
|
|
3603
3675
|
}
|
|
3604
3676
|
return "theme";
|
|
3605
3677
|
}
|
|
3606
|
-
async function findFiles(root, extensions, maxFiles) {
|
|
3678
|
+
async function findFiles(root, extensions, maxFiles, scanContext, scope = "source") {
|
|
3607
3679
|
const found = [];
|
|
3680
|
+
let truncated = false;
|
|
3608
3681
|
async function walk(directory) {
|
|
3609
|
-
if (
|
|
3682
|
+
if (truncated) {
|
|
3610
3683
|
return;
|
|
3611
3684
|
}
|
|
3612
3685
|
let entries;
|
|
@@ -3616,8 +3689,8 @@ async function findFiles(root, extensions, maxFiles) {
|
|
|
3616
3689
|
catch {
|
|
3617
3690
|
return;
|
|
3618
3691
|
}
|
|
3619
|
-
for (const entry of entries) {
|
|
3620
|
-
if (
|
|
3692
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3693
|
+
if (truncated) {
|
|
3621
3694
|
return;
|
|
3622
3695
|
}
|
|
3623
3696
|
if (shouldSkipDirectory(entry.name)) {
|
|
@@ -3628,11 +3701,25 @@ async function findFiles(root, extensions, maxFiles) {
|
|
|
3628
3701
|
await walk(entryPath);
|
|
3629
3702
|
}
|
|
3630
3703
|
else if (extensions.some((extension) => entry.name.endsWith(extension))) {
|
|
3704
|
+
if (found.length >= maxFiles) {
|
|
3705
|
+
truncated = true;
|
|
3706
|
+
return;
|
|
3707
|
+
}
|
|
3631
3708
|
found.push(entryPath);
|
|
3632
3709
|
}
|
|
3633
3710
|
}
|
|
3634
3711
|
}
|
|
3635
3712
|
await walk(root);
|
|
3713
|
+
if (scanContext) {
|
|
3714
|
+
scanContext.scans.push({
|
|
3715
|
+
scope,
|
|
3716
|
+
root: path.relative(scanContext.validationRoot, root) || ".",
|
|
3717
|
+
extensions: [...extensions],
|
|
3718
|
+
scanned: found.length,
|
|
3719
|
+
limit: maxFiles,
|
|
3720
|
+
truncated,
|
|
3721
|
+
});
|
|
3722
|
+
}
|
|
3636
3723
|
return found;
|
|
3637
3724
|
}
|
|
3638
3725
|
async function readMany(files) {
|
|
@@ -4151,7 +4238,7 @@ function validateCustomPostTypeDataTypeDeclared(root, platform, sourceContent) {
|
|
|
4151
4238
|
if (!matches)
|
|
4152
4239
|
continue;
|
|
4153
4240
|
for (const match of matches) {
|
|
4154
|
-
if (/data\s*[:=(]\s*[\{\[]\s*['"]?text['"]?\s
|
|
4241
|
+
if (/data\s*[:=(]\s*[\{\[]\s*['"]?text['"]?\s*(?::|[,}])/i.test(match))
|
|
4155
4242
|
continue;
|
|
4156
4243
|
if (!/dataType\s*[:=\(]/i.test(match)) {
|
|
4157
4244
|
findings.push(finding(ruleId, "warning", `File ${rel} creates a custom data post but does not declare a dataType.`, rel, `When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.`));
|
|
@@ -4221,8 +4308,8 @@ function validatePostBodyRendered(root, platform, sourceContent) {
|
|
|
4221
4308
|
android: /\b(?:Text\s*\(|TextView|\.text\s*=|setText\s*\()/,
|
|
4222
4309
|
};
|
|
4223
4310
|
const postSummaryPat = {
|
|
4224
|
-
typescript: /\bpost\s*\??\.\s*(?:postId|commentsCount|reactionsCount)\b
|
|
4225
|
-
"react-native": /\bpost\s*\??\.\s*(?:postId|commentsCount|reactionsCount)\b
|
|
4311
|
+
typescript: /\bpost\s*\??\.\s*(?:postId|commentsCount|reactionsCount)\b/,
|
|
4312
|
+
"react-native": /\bpost\s*\??\.\s*(?:postId|commentsCount|reactionsCount)\b/,
|
|
4226
4313
|
flutter: /\b(?:post|_posts\s*\[[^\]]+\])\s*\.\s*(?:postId|commentsCount|reactionsCount)\b/,
|
|
4227
4314
|
ios: /\bpost\s*\.\s*(?:postId|commentsCount|reactionsCount)\b/,
|
|
4228
4315
|
android: /\bpost\s*\.\s*(?:postId|commentsCount|reactionsCount|getPostId|getCommentCount|getReactionCount)\b/,
|
package/dist/tools/sdkFacts.js
CHANGED
|
@@ -70,6 +70,119 @@ const CAPABILITIES = {
|
|
|
70
70
|
],
|
|
71
71
|
models: [{ name: "Post", kind: "model", owner: "Amity" }],
|
|
72
72
|
},
|
|
73
|
+
community: {
|
|
74
|
+
required: [
|
|
75
|
+
{ name: "CommunityRepository", kind: "type" },
|
|
76
|
+
{ name: "getCommunities", kind: "member", owner: "CommunityRepository" },
|
|
77
|
+
{ name: "joinCommunity", kind: "member", owner: "CommunityRepository" },
|
|
78
|
+
],
|
|
79
|
+
optional: [
|
|
80
|
+
{ name: "getCommunity", kind: "member", owner: "CommunityRepository" },
|
|
81
|
+
{ name: "createCommunity", kind: "member", owner: "CommunityRepository" },
|
|
82
|
+
{ name: "leaveCommunity", kind: "member", owner: "CommunityRepository" },
|
|
83
|
+
{ name: "updateCommunity", kind: "member", owner: "CommunityRepository" },
|
|
84
|
+
{ name: "deleteCommunity", kind: "member", owner: "CommunityRepository" },
|
|
85
|
+
{ name: "searchCommunities", kind: "member", owner: "CommunityRepository" },
|
|
86
|
+
{ name: "getRecommendedCommunities", kind: "member", owner: "CommunityRepository" },
|
|
87
|
+
{ name: "getTrendingCommunities", kind: "member", owner: "CommunityRepository" },
|
|
88
|
+
{ name: "onCommunityCreated", kind: "member", owner: "CommunityRepository" },
|
|
89
|
+
{ name: "onCommunityUpdated", kind: "member", owner: "CommunityRepository" },
|
|
90
|
+
{ name: "onCommunityDeleted", kind: "member", owner: "CommunityRepository" },
|
|
91
|
+
],
|
|
92
|
+
models: [{ name: "Community", kind: "model", owner: "Amity" }],
|
|
93
|
+
},
|
|
94
|
+
chat: {
|
|
95
|
+
required: [
|
|
96
|
+
{ name: "ChannelRepository", kind: "type" },
|
|
97
|
+
{ name: "MessageRepository", kind: "type" },
|
|
98
|
+
{ name: "getChannels", kind: "member", owner: "ChannelRepository" },
|
|
99
|
+
{ name: "getMessages", kind: "member", owner: "MessageRepository" },
|
|
100
|
+
{ name: "createMessage", kind: "member", owner: "MessageRepository" },
|
|
101
|
+
],
|
|
102
|
+
optional: [
|
|
103
|
+
{ name: "getChannel", kind: "member", owner: "ChannelRepository" },
|
|
104
|
+
{ name: "createChannel", kind: "member", owner: "ChannelRepository" },
|
|
105
|
+
{ name: "joinChannel", kind: "member", owner: "ChannelRepository" },
|
|
106
|
+
{ name: "leaveChannel", kind: "member", owner: "ChannelRepository" },
|
|
107
|
+
{ name: "archiveChannel", kind: "member", owner: "ChannelRepository" },
|
|
108
|
+
{ name: "unarchiveChannel", kind: "member", owner: "ChannelRepository" },
|
|
109
|
+
{ name: "muteChannel", kind: "member", owner: "ChannelRepository" },
|
|
110
|
+
{ name: "searchChannels", kind: "member", owner: "ChannelRepository" },
|
|
111
|
+
{ name: "getSubChannels", kind: "member", owner: "SubChannelRepository" },
|
|
112
|
+
{ name: "editMessage", kind: "member", owner: "MessageRepository" },
|
|
113
|
+
{ name: "deleteMessage", kind: "member", owner: "MessageRepository" },
|
|
114
|
+
{ name: "flagMessage", kind: "member", owner: "MessageRepository" },
|
|
115
|
+
{ name: "unflagMessage", kind: "member", owner: "MessageRepository" },
|
|
116
|
+
{ name: "isMessageFlaggedByMe", kind: "member", owner: "MessageRepository" },
|
|
117
|
+
{ name: "searchMessage", kind: "member", owner: "MessageRepository" },
|
|
118
|
+
{ name: "markAsDelivered", kind: "member", owner: "MessageRepository" },
|
|
119
|
+
{ name: "onMessageCreatedMqtt", kind: "member", owner: "MessageRepository" },
|
|
120
|
+
{ name: "onMessageUpdated", kind: "member", owner: "MessageRepository" },
|
|
121
|
+
{ name: "onMessageDeleted", kind: "member", owner: "MessageRepository" },
|
|
122
|
+
],
|
|
123
|
+
models: [
|
|
124
|
+
{ name: "Channel", kind: "model", owner: "Amity" },
|
|
125
|
+
{ name: "Message", kind: "model", owner: "Amity" },
|
|
126
|
+
{ name: "SubChannel", kind: "model", owner: "Amity" },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
livestream: {
|
|
130
|
+
required: [
|
|
131
|
+
{ name: "RoomRepository", kind: "type" },
|
|
132
|
+
{ name: "getRoom", kind: "member", owner: "RoomRepository" },
|
|
133
|
+
{ name: "getRooms", kind: "member", owner: "RoomRepository" },
|
|
134
|
+
],
|
|
135
|
+
optional: [
|
|
136
|
+
{ name: "createRoom", kind: "member", owner: "RoomRepository" },
|
|
137
|
+
{ name: "updateRoom", kind: "member", owner: "RoomRepository" },
|
|
138
|
+
{ name: "deleteRoom", kind: "member", owner: "RoomRepository" },
|
|
139
|
+
{ name: "leaveRoom", kind: "member", owner: "RoomRepository" },
|
|
140
|
+
{ name: "stopRoom", kind: "member", owner: "RoomRepository" },
|
|
141
|
+
{ name: "removeParticipant", kind: "member", owner: "RoomRepository" },
|
|
142
|
+
{ name: "getBroadcasterData", kind: "member", owner: "RoomRepository" },
|
|
143
|
+
{ name: "getRecordedUrl", kind: "member", owner: "RoomRepository" },
|
|
144
|
+
{ name: "getPlayer", kind: "member", owner: "LiveStreamPlayer" },
|
|
145
|
+
{ name: "getRoomOnlineUsers", kind: "member", owner: "RoomPresenceRepository" },
|
|
146
|
+
{ name: "getRoomUserCount", kind: "member", owner: "RoomPresenceRepository" },
|
|
147
|
+
],
|
|
148
|
+
models: [{ name: "Room", kind: "model", owner: "Amity" }],
|
|
149
|
+
},
|
|
150
|
+
"notification-tray": {
|
|
151
|
+
required: [
|
|
152
|
+
{ name: "notificationTray", kind: "type" },
|
|
153
|
+
{ name: "getNotificationTrayItems", kind: "member", owner: "notificationTray" },
|
|
154
|
+
],
|
|
155
|
+
optional: [
|
|
156
|
+
{ name: "getNotificationTraySeen", kind: "member", owner: "notificationTray" },
|
|
157
|
+
{ name: "markItemsSeen", kind: "member", owner: "notificationTray" },
|
|
158
|
+
{ name: "markTraySeen", kind: "member", owner: "notificationTray" },
|
|
159
|
+
{ name: "onNotificationTraySeenUpdated", kind: "member", owner: "notificationTray" },
|
|
160
|
+
],
|
|
161
|
+
models: [
|
|
162
|
+
{ name: "NotificationTrayItem", kind: "model", owner: "Amity" },
|
|
163
|
+
{ name: "NotificationTraySeen", kind: "model", owner: "Amity" },
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
events: {
|
|
167
|
+
required: [
|
|
168
|
+
{ name: "EventRepository", kind: "type" },
|
|
169
|
+
{ name: "getEvents", kind: "member", owner: "EventRepository" },
|
|
170
|
+
{ name: "getRSVPs", kind: "member", owner: "EventRepository" },
|
|
171
|
+
],
|
|
172
|
+
optional: [
|
|
173
|
+
{ name: "getEvent", kind: "member", owner: "EventRepository" },
|
|
174
|
+
{ name: "getMyEvents", kind: "member", owner: "EventRepository" },
|
|
175
|
+
{ name: "createEvent", kind: "member", owner: "EventRepository" },
|
|
176
|
+
{ name: "updateEvent", kind: "member", owner: "EventRepository" },
|
|
177
|
+
{ name: "deleteEvent", kind: "member", owner: "EventRepository" },
|
|
178
|
+
{ name: "onEventCreated", kind: "member", owner: "EventRepository" },
|
|
179
|
+
{ name: "onEventUpdated", kind: "member", owner: "EventRepository" },
|
|
180
|
+
{ name: "onEventDeleted", kind: "member", owner: "EventRepository" },
|
|
181
|
+
{ name: "onRSVPCreated", kind: "member", owner: "EventRepository" },
|
|
182
|
+
{ name: "onRSVPUpdated", kind: "member", owner: "EventRepository" },
|
|
183
|
+
],
|
|
184
|
+
models: [{ name: "Event", kind: "model", owner: "Amity" }],
|
|
185
|
+
},
|
|
73
186
|
users: {
|
|
74
187
|
required: [
|
|
75
188
|
{ name: "UserRepository", kind: "type" },
|
|
@@ -88,6 +201,113 @@ const CAPABILITIES = {
|
|
|
88
201
|
models: [{ name: "User", kind: "model", owner: "Amity" }],
|
|
89
202
|
},
|
|
90
203
|
};
|
|
204
|
+
const FLUTTER_CAPABILITIES = {
|
|
205
|
+
comments: {
|
|
206
|
+
required: [
|
|
207
|
+
{ name: "CommentRepository", kind: "type" },
|
|
208
|
+
{ name: "getComments", kind: "member", owner: "CommentRepository" },
|
|
209
|
+
{ name: "createComment", kind: "member", owner: "CommentRepository" },
|
|
210
|
+
],
|
|
211
|
+
optional: [
|
|
212
|
+
{ name: "getComment", kind: "member", owner: "CommentRepository" },
|
|
213
|
+
{ name: "updateComment", kind: "member", owner: "CommentRepository" },
|
|
214
|
+
],
|
|
215
|
+
models: [{ name: "AmityComment", kind: "model" }],
|
|
216
|
+
},
|
|
217
|
+
reactions: {
|
|
218
|
+
required: [
|
|
219
|
+
{ name: "AmityReactionRepository", kind: "type" },
|
|
220
|
+
{ name: "addReaction", kind: "member", owner: "AmityReactionRepository" },
|
|
221
|
+
{ name: "removeReaction", kind: "member", owner: "AmityReactionRepository" },
|
|
222
|
+
],
|
|
223
|
+
optional: [
|
|
224
|
+
{ name: "getReactions", kind: "member", owner: "AmityReactionRepository" },
|
|
225
|
+
],
|
|
226
|
+
models: [{ name: "AmityReaction", kind: "model" }],
|
|
227
|
+
},
|
|
228
|
+
posts: {
|
|
229
|
+
required: [
|
|
230
|
+
{ name: "PostRepository", kind: "type" },
|
|
231
|
+
{ name: "getPosts", kind: "member", owner: "PostRepository" },
|
|
232
|
+
{ name: "createPost", kind: "member", owner: "PostRepository" },
|
|
233
|
+
],
|
|
234
|
+
optional: [
|
|
235
|
+
{ name: "getPost", kind: "member", owner: "PostRepository" },
|
|
236
|
+
{ name: "deletePost", kind: "member", owner: "PostRepository" },
|
|
237
|
+
{ name: "reviewPost", kind: "member", owner: "PostRepository" },
|
|
238
|
+
{ name: "getPinnedPosts", kind: "member", owner: "PostRepository" },
|
|
239
|
+
{ name: "isFlaggedByMe", kind: "member", owner: "PostRepository" },
|
|
240
|
+
],
|
|
241
|
+
models: [{ name: "AmityPost", kind: "model" }],
|
|
242
|
+
},
|
|
243
|
+
users: {
|
|
244
|
+
required: [
|
|
245
|
+
{ name: "UserRepository", kind: "type" },
|
|
246
|
+
{ name: "getUser", kind: "member", owner: "UserRepository" },
|
|
247
|
+
],
|
|
248
|
+
optional: [
|
|
249
|
+
{ name: "getUsers", kind: "member", owner: "UserRepository" },
|
|
250
|
+
{ name: "searchUserByDisplayName", kind: "member", owner: "UserRepository" },
|
|
251
|
+
{ name: "updateUser", kind: "member", owner: "UserRepository" },
|
|
252
|
+
{ name: "getBlockedUsers", kind: "member", owner: "UserRepository" },
|
|
253
|
+
{ name: "report", kind: "member", owner: "UserRepository" },
|
|
254
|
+
{ name: "follow", kind: "member", owner: "AmityUserRelationshipRepository" },
|
|
255
|
+
{ name: "getFollowInfo", kind: "member", owner: "AmityUserRelationshipRepository" },
|
|
256
|
+
],
|
|
257
|
+
models: [{ name: "AmityUser", kind: "model" }],
|
|
258
|
+
},
|
|
259
|
+
community: {
|
|
260
|
+
required: [
|
|
261
|
+
{ name: "AmityCommunityRepository", kind: "type" },
|
|
262
|
+
{ name: "getCommunities", kind: "member", owner: "AmityCommunityRepository" },
|
|
263
|
+
{ name: "joinCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
264
|
+
],
|
|
265
|
+
optional: [
|
|
266
|
+
{ name: "getCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
267
|
+
{ name: "createCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
268
|
+
{ name: "leaveCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
269
|
+
{ name: "updateCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
270
|
+
{ name: "deleteCommunity", kind: "member", owner: "AmityCommunityRepository" },
|
|
271
|
+
{ name: "getRecommendedCommunities", kind: "member", owner: "AmityCommunityRepository" },
|
|
272
|
+
{ name: "getTrendingCommunities", kind: "member", owner: "AmityCommunityRepository" },
|
|
273
|
+
],
|
|
274
|
+
models: [{ name: "AmityCommunity", kind: "model" }],
|
|
275
|
+
},
|
|
276
|
+
chat: {
|
|
277
|
+
required: [
|
|
278
|
+
{ name: "AmityChannelRepository", kind: "type" },
|
|
279
|
+
{ name: "MessageRepository", kind: "type" },
|
|
280
|
+
{ name: "getChannels", kind: "member", owner: "AmityChannelRepository" },
|
|
281
|
+
{ name: "getMessages", kind: "member", owner: "MessageRepository" },
|
|
282
|
+
{ name: "createMessage", kind: "member", owner: "MessageRepository" },
|
|
283
|
+
],
|
|
284
|
+
optional: [
|
|
285
|
+
{ name: "getChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
286
|
+
{ name: "createChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
287
|
+
{ name: "joinChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
288
|
+
{ name: "leaveChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
289
|
+
{ name: "archiveChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
290
|
+
{ name: "unarchiveChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
291
|
+
{ name: "muteChannel", kind: "member", owner: "AmityChannelRepository" },
|
|
292
|
+
{ name: "searchChannels", kind: "member", owner: "AmityChannelRepository" },
|
|
293
|
+
{ name: "getMessage", kind: "member", owner: "MessageRepository" },
|
|
294
|
+
{ name: "deleteMessage", kind: "member", owner: "MessageRepository" },
|
|
295
|
+
{ name: "flagMessage", kind: "member", owner: "MessageRepository" },
|
|
296
|
+
{ name: "updateMessage", kind: "member", owner: "MessageRepository" },
|
|
297
|
+
{ name: "searchMessage", kind: "member", owner: "MessageRepository" },
|
|
298
|
+
{ name: "getSubChannels", kind: "member", owner: "AmitySubChannelRepository" },
|
|
299
|
+
],
|
|
300
|
+
models: [
|
|
301
|
+
{ name: "AmityChannel", kind: "model" },
|
|
302
|
+
{ name: "AmityMessage", kind: "model" },
|
|
303
|
+
{ name: "AmitySubChannel", kind: "model" },
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
const CAPABILITY_ANCHORS = {
|
|
308
|
+
typescript: CAPABILITIES,
|
|
309
|
+
flutter: FLUTTER_CAPABILITIES,
|
|
310
|
+
};
|
|
91
311
|
export const getSdkFactsTool = {
|
|
92
312
|
name: "get_sdk_facts",
|
|
93
313
|
description: "Read bundled social.plus SDK surface facts for block packaging. Projectless and read-only; answers symbol existence, not semantic correctness.",
|
|
@@ -181,7 +401,12 @@ export async function getSdkFacts(options) {
|
|
|
181
401
|
"Remote SP_SDK_SURFACE_URL resolution is recorded in the manifest for a future online snapshot source; this MVP uses local override/env/bundled directories only.",
|
|
182
402
|
...(platform !== "typescript"
|
|
183
403
|
? [
|
|
184
|
-
|
|
404
|
+
(() => {
|
|
405
|
+
const anchored = Object.keys(CAPABILITY_ANCHORS[platform] ?? {});
|
|
406
|
+
return anchored.length > 0
|
|
407
|
+
? `Capability anchors on ${platform} cover: ${anchored.join(", ")}. Families not listed are not yet anchored on ${platform} — capability mapping is unavailable for them, which does NOT mean the SDK lacks the feature. The symbol and model facts above are extracted from the ${platform} surface and remain authoritative.`
|
|
408
|
+
: `Capability anchors are authored TypeScript-first at this MVP, so capabilities is empty for ${platform}. This means capability mapping is not yet available on ${platform} — it does NOT mean the SDK lacks the feature. The symbol and model facts above are extracted from the ${platform} surface and remain authoritative.`;
|
|
409
|
+
})(),
|
|
185
410
|
]
|
|
186
411
|
: []),
|
|
187
412
|
],
|
|
@@ -264,20 +489,21 @@ async function readJson(filePath) {
|
|
|
264
489
|
}
|
|
265
490
|
function requestedCapabilityIds(platform, capability) {
|
|
266
491
|
const available = Object.keys(CAPABILITIES);
|
|
492
|
+
const anchored = Object.keys(CAPABILITY_ANCHORS[platform] ?? {});
|
|
267
493
|
if (!capability) {
|
|
268
|
-
return
|
|
494
|
+
return anchored;
|
|
269
495
|
}
|
|
270
496
|
const normalized = capability.trim().toLowerCase();
|
|
271
497
|
if (!available.includes(normalized)) {
|
|
272
498
|
throw new Error(`Unsupported SDK capability: ${capability}. Supported capabilities: ${available.join(", ")}.`);
|
|
273
499
|
}
|
|
274
|
-
|
|
275
|
-
return [];
|
|
276
|
-
}
|
|
277
|
-
return [normalized];
|
|
500
|
+
return anchored.includes(normalized) ? [normalized] : [];
|
|
278
501
|
}
|
|
279
502
|
function buildCapabilityFact(id, platform, symbols, modelFacts) {
|
|
280
|
-
const definition =
|
|
503
|
+
const definition = CAPABILITY_ANCHORS[platform]?.[id];
|
|
504
|
+
if (!definition) {
|
|
505
|
+
throw new Error(`No ${platform} anchor set for capability ${id}; requestedCapabilityIds must filter unanchored families.`);
|
|
506
|
+
}
|
|
281
507
|
const requiredSymbols = definition.required.map((symbol) => checkSymbol(symbols, { ...symbol, required: true }));
|
|
282
508
|
const optionalSymbols = definition.optional.map((symbol) => checkSymbol(symbols, { ...symbol, required: false }));
|
|
283
509
|
const models = definition.models.map((symbol) => {
|