@amityco/social-plus-vise 0.14.1 → 0.14.3
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 +31 -0
- package/README.md +37 -4
- package/dist/capabilities.js +1 -1
- package/dist/outcomes.js +28 -0
- package/dist/server.js +29 -2
- package/dist/tools/compliance.js +40 -9
- package/dist/tools/integration.js +1 -1
- package/dist/tools/project.js +1 -1
- package/dist/tools/sdkFacts.js +364 -0
- package/package.json +8 -3
- package/rules/feed.yaml +20 -0
- package/scripts/import-sdk-surface.mjs +130 -0
- package/sdk-surface/android.json +30310 -0
- package/sdk-surface/flutter.json +13036 -0
- package/sdk-surface/ios.json +36722 -0
- package/sdk-surface/manifest.json +62 -0
- package/sdk-surface/typescript.json +9994 -0
- package/skills/social-plus-vise/SKILL.md +4 -2
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { objectInput, optionalBooleanField, optionalStringField, stringField, textResult } from "../types.js";
|
|
5
|
+
const PLATFORM_ALIASES = {
|
|
6
|
+
ios: "ios",
|
|
7
|
+
swift: "ios",
|
|
8
|
+
android: "android",
|
|
9
|
+
kotlin: "android",
|
|
10
|
+
typescript: "typescript",
|
|
11
|
+
ts: "typescript",
|
|
12
|
+
javascript: "typescript",
|
|
13
|
+
"react-native": "typescript",
|
|
14
|
+
reactnative: "typescript",
|
|
15
|
+
flutter: "flutter",
|
|
16
|
+
dart: "flutter",
|
|
17
|
+
};
|
|
18
|
+
const CAPABILITIES = {
|
|
19
|
+
comments: {
|
|
20
|
+
required: [
|
|
21
|
+
{ name: "CommentRepository", kind: "type" },
|
|
22
|
+
{ name: "getComments", kind: "member", owner: "CommentRepository" },
|
|
23
|
+
{ name: "createComment", kind: "member", owner: "CommentRepository" },
|
|
24
|
+
],
|
|
25
|
+
optional: [
|
|
26
|
+
{ name: "updateComment", kind: "member", owner: "CommentRepository" },
|
|
27
|
+
{ name: "deleteComment", kind: "member", owner: "CommentRepository" },
|
|
28
|
+
{ name: "flagComment", kind: "member", owner: "CommentRepository" },
|
|
29
|
+
{ name: "unflagComment", kind: "member", owner: "CommentRepository" },
|
|
30
|
+
{ name: "isCommentFlaggedByMe", kind: "member", owner: "CommentRepository" },
|
|
31
|
+
],
|
|
32
|
+
models: [{ name: "Comment", kind: "model", owner: "Amity" }],
|
|
33
|
+
},
|
|
34
|
+
reactions: {
|
|
35
|
+
required: [
|
|
36
|
+
{ name: "ReactionRepository", kind: "type" },
|
|
37
|
+
{ name: "addReaction", kind: "member", owner: "ReactionRepository" },
|
|
38
|
+
{ name: "removeReaction", kind: "member", owner: "ReactionRepository" },
|
|
39
|
+
],
|
|
40
|
+
optional: [
|
|
41
|
+
{ name: "getReactions", kind: "member", owner: "ReactionRepository" },
|
|
42
|
+
{ name: "onReactionAdded", kind: "member", owner: "ReactionRepository" },
|
|
43
|
+
{ name: "onReactionRemoved", kind: "member", owner: "ReactionRepository" },
|
|
44
|
+
{ name: "myReactions", kind: "member", owner: "ReactionRepository" },
|
|
45
|
+
],
|
|
46
|
+
models: [{ name: "Reaction", kind: "model", owner: "Amity" }],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export const getSdkFactsTool = {
|
|
50
|
+
name: "get_sdk_facts",
|
|
51
|
+
description: "Read bundled social.plus SDK surface facts for Block Factory planning. Projectless and read-only; answers symbol existence, not semantic correctness.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
platform: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "SDK platform or alias: typescript, react-native, android, ios, flutter.",
|
|
58
|
+
},
|
|
59
|
+
capability: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Optional capability filter. Current MVP supports comments and reactions for the TypeScript/React Native surface.",
|
|
62
|
+
},
|
|
63
|
+
surfaceDir: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Optional directory containing manifest.json and platform JSON files. Defaults to SP_SDK_SURFACE_DIR, then bundled sdk-surface/.",
|
|
66
|
+
},
|
|
67
|
+
includeSymbols: {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
description: "Include compact type/member symbol arrays. Defaults to false.",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ["platform"],
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
},
|
|
75
|
+
async call(input) {
|
|
76
|
+
const args = objectInput(input);
|
|
77
|
+
return textResult(await getSdkFacts({
|
|
78
|
+
platform: stringField(args, "platform"),
|
|
79
|
+
capability: optionalStringField(args, "capability"),
|
|
80
|
+
surfaceDir: optionalStringField(args, "surfaceDir"),
|
|
81
|
+
includeSymbols: optionalBooleanField(args, "includeSymbols"),
|
|
82
|
+
}));
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export function canonicalPlatform(platform) {
|
|
86
|
+
return PLATFORM_ALIASES[platform.trim().toLowerCase()] ?? null;
|
|
87
|
+
}
|
|
88
|
+
export async function getSdkFacts(options) {
|
|
89
|
+
const platform = canonicalPlatform(options.platform);
|
|
90
|
+
if (!platform) {
|
|
91
|
+
throw new Error(`Unsupported SDK platform: ${options.platform}. Supported platforms: typescript, react-native, android, ios, flutter.`);
|
|
92
|
+
}
|
|
93
|
+
const surfaceLocation = resolveSurfaceDirectory(options.surfaceDir);
|
|
94
|
+
const manifest = await readJson(path.join(surfaceLocation.directory, "manifest.json"));
|
|
95
|
+
const entry = manifest.surfaces?.[platform];
|
|
96
|
+
const surfaceFile = entry?.file ?? `${platform}.json`;
|
|
97
|
+
const surface = await readJson(path.join(surfaceLocation.directory, surfaceFile));
|
|
98
|
+
const symbols = collectSymbols(platform, surface);
|
|
99
|
+
const capabilityIds = requestedCapabilityIds(platform, options.capability);
|
|
100
|
+
const capabilities = capabilityIds.map((id) => buildCapabilityFact(id, platform, symbols));
|
|
101
|
+
const result = {
|
|
102
|
+
source: "social-plus-vise",
|
|
103
|
+
mode: "block-factory-sdk-facts",
|
|
104
|
+
requestedPlatform: options.platform,
|
|
105
|
+
platform,
|
|
106
|
+
surfaceSource: {
|
|
107
|
+
kind: surfaceLocation.kind,
|
|
108
|
+
directory: surfaceLocation.directory,
|
|
109
|
+
manifestSource: manifest.source,
|
|
110
|
+
manifestSchemaVersion: manifest.schemaVersion,
|
|
111
|
+
upstreamGit: manifest.upstreamGit,
|
|
112
|
+
importedAt: manifest.importedAt,
|
|
113
|
+
remoteUrlStatus: "not-implemented",
|
|
114
|
+
},
|
|
115
|
+
sdkProduct: entry?.sdkProduct ?? stringProperty(surface, "sdk_product") ?? stringProperty(surface, "sdk_package"),
|
|
116
|
+
sdkVersion: entry?.sdkVersion ?? null,
|
|
117
|
+
extractor: entry?.extractor ?? stringProperty(surface, "extractor"),
|
|
118
|
+
extractorVersion: entry?.extractorVersion ?? stringProperty(surface, "extractor_version"),
|
|
119
|
+
extractedAt: entry?.extractedAt ?? stringProperty(surface, "extracted_at"),
|
|
120
|
+
symbolSummary: {
|
|
121
|
+
typeCount: symbols.types.length,
|
|
122
|
+
memberCount: symbols.members.length,
|
|
123
|
+
},
|
|
124
|
+
capabilities,
|
|
125
|
+
notes: [
|
|
126
|
+
"Existence-only facts: Vise confirms public symbols in the normalized SDK surface, but semantic correctness and idiomatic usage still require rules, docs, and sensors.",
|
|
127
|
+
"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.",
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
if (options.includeSymbols) {
|
|
131
|
+
result.symbols = {
|
|
132
|
+
types: symbols.types,
|
|
133
|
+
members: symbols.members,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
function resolveSurfaceDirectory(surfaceDir) {
|
|
139
|
+
if (surfaceDir) {
|
|
140
|
+
return { kind: "override", directory: path.resolve(surfaceDir) };
|
|
141
|
+
}
|
|
142
|
+
if (process.env.SP_SDK_SURFACE_DIR) {
|
|
143
|
+
return { kind: "env", directory: path.resolve(process.env.SP_SDK_SURFACE_DIR) };
|
|
144
|
+
}
|
|
145
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
146
|
+
return { kind: "bundled", directory: path.resolve(moduleDir, "..", "..", "sdk-surface") };
|
|
147
|
+
}
|
|
148
|
+
async function readJson(filePath) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new Error(`Unable to read SDK surface file ${filePath}: ${detail}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function requestedCapabilityIds(platform, capability) {
|
|
158
|
+
const available = Object.keys(CAPABILITIES);
|
|
159
|
+
if (!capability) {
|
|
160
|
+
return platform === "typescript" ? available : [];
|
|
161
|
+
}
|
|
162
|
+
const normalized = capability.trim().toLowerCase();
|
|
163
|
+
if (!available.includes(normalized)) {
|
|
164
|
+
throw new Error(`Unsupported SDK capability: ${capability}. Supported capabilities: ${available.join(", ")}.`);
|
|
165
|
+
}
|
|
166
|
+
if (platform !== "typescript") {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return [normalized];
|
|
170
|
+
}
|
|
171
|
+
function buildCapabilityFact(id, platform, symbols) {
|
|
172
|
+
const definition = CAPABILITIES[id];
|
|
173
|
+
const requiredSymbols = definition.required.map((symbol) => checkSymbol(symbols, { ...symbol, required: true }));
|
|
174
|
+
const optionalSymbols = definition.optional.map((symbol) => checkSymbol(symbols, { ...symbol, required: false }));
|
|
175
|
+
const models = definition.models.map((symbol) => checkSymbol(symbols, { ...symbol, required: false }));
|
|
176
|
+
const missingRequired = requiredSymbols.filter((symbol) => !symbol.exists);
|
|
177
|
+
return {
|
|
178
|
+
id,
|
|
179
|
+
support: missingRequired.length > 0 ? "missing-symbols" : "supported",
|
|
180
|
+
platform,
|
|
181
|
+
requiredSymbols,
|
|
182
|
+
optionalSymbols,
|
|
183
|
+
models,
|
|
184
|
+
notes: [
|
|
185
|
+
"Model facts are symbol-only in this MVP; field-level schemas need richer extractor output before they become contract-grade.",
|
|
186
|
+
...(missingRequired.length > 0 ? [`Missing required symbol(s): ${missingRequired.map((symbol) => symbol.owner ? `${symbol.owner}.${symbol.name}` : symbol.name).join(", ")}.`] : []),
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function checkSymbol(symbols, spec) {
|
|
191
|
+
const haystack = spec.kind === "type" ? symbols.types : spec.kind === "member" ? symbols.members : symbols.types;
|
|
192
|
+
const match = haystack.find((symbol) => symbol.name === spec.name && (!spec.owner || symbol.owner === spec.owner || symbol.namespace === spec.owner));
|
|
193
|
+
return {
|
|
194
|
+
name: spec.name,
|
|
195
|
+
kind: spec.kind,
|
|
196
|
+
owner: spec.owner,
|
|
197
|
+
required: spec.required,
|
|
198
|
+
exists: Boolean(match),
|
|
199
|
+
source: match ? { file: match.file, line: match.line } : undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function collectSymbols(platform, surface) {
|
|
203
|
+
if (platform === "typescript") {
|
|
204
|
+
return collectTypeScriptSymbols(surface);
|
|
205
|
+
}
|
|
206
|
+
return collectNativeSymbols(surface);
|
|
207
|
+
}
|
|
208
|
+
function collectTypeScriptSymbols(surface) {
|
|
209
|
+
const root = objectRecord(surface);
|
|
210
|
+
const types = [];
|
|
211
|
+
const members = [];
|
|
212
|
+
const namespaces = objectRecord(root.namespaces);
|
|
213
|
+
for (const [name, namespace] of Object.entries(namespaces)) {
|
|
214
|
+
collectTypeScriptNamespace(name, objectRecord(namespace), types, members);
|
|
215
|
+
}
|
|
216
|
+
for (const exportEntry of arrayRecords(root.root_exports)) {
|
|
217
|
+
if (typeof exportEntry.name === "string") {
|
|
218
|
+
types.push({
|
|
219
|
+
name: exportEntry.name,
|
|
220
|
+
kind: typeof exportEntry.kind === "string" ? exportEntry.kind : "export",
|
|
221
|
+
file: typeof exportEntry.file === "string" ? exportEntry.file : undefined,
|
|
222
|
+
line: typeof exportEntry.line === "number" ? exportEntry.line : null,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return sortSymbols(dedupeSymbols({ types, members }));
|
|
227
|
+
}
|
|
228
|
+
function collectTypeScriptNamespace(name, namespace, types, members) {
|
|
229
|
+
const sourceModule = typeof namespace.source_module === "string" ? namespace.source_module : undefined;
|
|
230
|
+
types.push({ name, kind: "namespace", file: sourceModule });
|
|
231
|
+
for (const member of arrayRecords(namespace.members)) {
|
|
232
|
+
const memberName = typeof member.name === "string" ? member.name : undefined;
|
|
233
|
+
if (!memberName) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const symbol = {
|
|
237
|
+
name: memberName,
|
|
238
|
+
kind: typeof member.kind === "string" ? member.kind : "member",
|
|
239
|
+
owner: name,
|
|
240
|
+
namespace: typeof member.namespace === "string" ? member.namespace : name,
|
|
241
|
+
file: typeof member.file === "string" ? member.file : undefined,
|
|
242
|
+
line: typeof member.line === "number" ? member.line : null,
|
|
243
|
+
};
|
|
244
|
+
if (symbol.kind === "type" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "class") {
|
|
245
|
+
types.push(symbol);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
members.push(symbol);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const subNamespaces = objectRecord(namespace.sub_namespaces);
|
|
252
|
+
for (const [subName, subNamespace] of Object.entries(subNamespaces)) {
|
|
253
|
+
collectTypeScriptNamespace(`${name}.${subName}`, objectRecord(subNamespace), types, members);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function collectNativeSymbols(surface) {
|
|
257
|
+
const root = objectRecord(surface);
|
|
258
|
+
const types = [];
|
|
259
|
+
const members = [];
|
|
260
|
+
for (const collectionName of ["types", "interfaces", "protocols", "mixins", "extensions", "typedefs", "typealiases", "global_typealiases"]) {
|
|
261
|
+
collectTypeMap(objectRecord(root[collectionName]), types, members);
|
|
262
|
+
}
|
|
263
|
+
for (const collectionName of ["global_funcs", "global_consts"]) {
|
|
264
|
+
for (const symbol of arrayRecords(root[collectionName])) {
|
|
265
|
+
const name = typeof symbol.name === "string" ? symbol.name : undefined;
|
|
266
|
+
if (name) {
|
|
267
|
+
members.push({
|
|
268
|
+
name,
|
|
269
|
+
kind: typeof symbol.kind === "string" ? symbol.kind : collectionName,
|
|
270
|
+
file: sourceFile(symbol),
|
|
271
|
+
line: sourceLine(symbol),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return sortSymbols(dedupeSymbols({ types, members }));
|
|
277
|
+
}
|
|
278
|
+
function collectTypeMap(typeMap, types, members) {
|
|
279
|
+
for (const [name, rawType] of Object.entries(typeMap)) {
|
|
280
|
+
const typeInfo = objectRecord(rawType);
|
|
281
|
+
types.push({
|
|
282
|
+
name,
|
|
283
|
+
kind: typeof typeInfo.kind === "string" ? typeInfo.kind : "type",
|
|
284
|
+
file: sourceFile(typeInfo),
|
|
285
|
+
line: sourceLine(typeInfo),
|
|
286
|
+
});
|
|
287
|
+
for (const member of arrayRecords(typeInfo.members)) {
|
|
288
|
+
const memberName = typeof member.name === "string" ? member.name : undefined;
|
|
289
|
+
if (!memberName) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
members.push({
|
|
293
|
+
name: memberName,
|
|
294
|
+
kind: typeof member.kind === "string" ? member.kind : "member",
|
|
295
|
+
owner: name,
|
|
296
|
+
file: sourceFile(member),
|
|
297
|
+
line: sourceLine(member),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
for (const nestedType of arrayRecords(typeInfo.nested_types)) {
|
|
301
|
+
const nestedName = typeof nestedType.name === "string" ? nestedType.name : undefined;
|
|
302
|
+
if (!nestedName) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const qualifiedName = `${name}.${nestedName}`;
|
|
306
|
+
types.push({
|
|
307
|
+
name: qualifiedName,
|
|
308
|
+
kind: typeof nestedType.kind === "string" ? nestedType.kind : "nested_type",
|
|
309
|
+
owner: name,
|
|
310
|
+
file: sourceFile(nestedType),
|
|
311
|
+
line: sourceLine(nestedType),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function sourceFile(value) {
|
|
317
|
+
if (typeof value.file === "string") {
|
|
318
|
+
return value.file;
|
|
319
|
+
}
|
|
320
|
+
const primaryDecl = objectRecord(value.primary_decl);
|
|
321
|
+
return typeof primaryDecl.file === "string" ? primaryDecl.file : undefined;
|
|
322
|
+
}
|
|
323
|
+
function sourceLine(value) {
|
|
324
|
+
if (typeof value.line === "number") {
|
|
325
|
+
return value.line;
|
|
326
|
+
}
|
|
327
|
+
const primaryDecl = objectRecord(value.primary_decl);
|
|
328
|
+
return typeof primaryDecl.line === "number" ? primaryDecl.line : null;
|
|
329
|
+
}
|
|
330
|
+
function stringProperty(value, property) {
|
|
331
|
+
const record = objectRecord(value);
|
|
332
|
+
return typeof record[property] === "string" ? record[property] : undefined;
|
|
333
|
+
}
|
|
334
|
+
function objectRecord(value) {
|
|
335
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
336
|
+
}
|
|
337
|
+
function arrayRecords(value) {
|
|
338
|
+
return Array.isArray(value) ? value.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
339
|
+
}
|
|
340
|
+
function dedupeSymbols(symbols) {
|
|
341
|
+
return {
|
|
342
|
+
types: dedupeSymbolList(symbols.types),
|
|
343
|
+
members: dedupeSymbolList(symbols.members),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function dedupeSymbolList(symbols) {
|
|
347
|
+
const byKey = new Map();
|
|
348
|
+
for (const symbol of symbols) {
|
|
349
|
+
const key = `${symbol.owner ?? ""}:${symbol.namespace ?? ""}:${symbol.kind ?? ""}:${symbol.name}`;
|
|
350
|
+
if (!byKey.has(key)) {
|
|
351
|
+
byKey.set(key, symbol);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return [...byKey.values()];
|
|
355
|
+
}
|
|
356
|
+
function sortSymbols(symbols) {
|
|
357
|
+
return {
|
|
358
|
+
types: symbols.types.sort(compareSymbols),
|
|
359
|
+
members: symbols.members.sort(compareSymbols),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function compareSymbols(a, b) {
|
|
363
|
+
return `${a.owner ?? ""}.${a.name}`.localeCompare(`${b.owner ?? ""}.${b.name}`);
|
|
364
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amityco/social-plus-vise",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -40,11 +40,14 @@
|
|
|
40
40
|
"CHANGELOG.md",
|
|
41
41
|
"social.plus-vise.png",
|
|
42
42
|
"rules",
|
|
43
|
-
"skills"
|
|
43
|
+
"skills",
|
|
44
|
+
"scripts",
|
|
45
|
+
"sdk-surface"
|
|
44
46
|
],
|
|
45
47
|
"scripts": {
|
|
46
48
|
"build": "tsc -p tsconfig.json",
|
|
47
49
|
"postbuild": "chmod +x dist/server.js",
|
|
50
|
+
"sdk-surface:import": "node scripts/import-sdk-surface.mjs",
|
|
48
51
|
"pack:check": "npm pack --dry-run --cache /tmp/social-plus-foundry-npm-cache",
|
|
49
52
|
"publish:check": "npm run validate && npm publish --dry-run --access public --cache /tmp/social-plus-foundry-npm-cache",
|
|
50
53
|
"start": "node dist/server.js",
|
|
@@ -60,9 +63,11 @@
|
|
|
60
63
|
"test:fixture-symmetry": "npm run build && node test/run-fixture-symmetry.mjs",
|
|
61
64
|
"test:nonui-skip": "npm run build && node test/run-nonui-layer-skip.mjs",
|
|
62
65
|
"test:sdk-version": "npm run build && node test/run-sdk-version.mjs",
|
|
66
|
+
"test:sdk-surface": "node test/run-sdk-surface-snapshot.mjs",
|
|
67
|
+
"test:sdk-facts": "npm run build && node test/run-sdk-facts.mjs",
|
|
63
68
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
64
69
|
"test:e2e-package": "npm run build && node test/run-e2e-package.mjs",
|
|
65
|
-
"validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:design-extract && npm run test:design-brief && npm run test:capabilities && npm run test:classify && npm run test:compliance && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:nonui-skip && npm run test:sdk-version && npm run test:native-idioms && npm run test:grader-facts && npm run test:ground-truth && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run pack:check",
|
|
70
|
+
"validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:design-extract && npm run test:design-brief && npm run test:capabilities && npm run test:classify && npm run test:compliance && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:nonui-skip && npm run test:sdk-version && npm run test:sdk-surface && npm run test:sdk-facts && npm run test:native-idioms && npm run test:grader-facts && npm run test:ground-truth && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run bench:symbols-drift && npm run pack:check",
|
|
66
71
|
"test:ast": "node test/run-ast-helpers.mjs",
|
|
67
72
|
"test:design-extract": "npm run build && node test/run-design-extract.mjs",
|
|
68
73
|
"test:design-brief": "npm run build && node test/run-design-brief.mjs",
|
package/rules/feed.yaml
CHANGED
|
@@ -1110,6 +1110,7 @@
|
|
|
1110
1110
|
{
|
|
1111
1111
|
"id": "typescript.posts.parent-child-rendered",
|
|
1112
1112
|
"version": 1,
|
|
1113
|
+
"symbol_anchored": true,
|
|
1113
1114
|
"title": "Post rendering for TypeScript must account for child posts (images/videos)",
|
|
1114
1115
|
"severity": "warning",
|
|
1115
1116
|
"rationale": "TypeScript UI components for Posts must recursively render or inspect post.children. If a component only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1146,6 +1147,7 @@
|
|
|
1146
1147
|
{
|
|
1147
1148
|
"id": "react-native.posts.parent-child-rendered",
|
|
1148
1149
|
"version": 1,
|
|
1150
|
+
"symbol_anchored": true,
|
|
1149
1151
|
"title": "Post rendering for React Native must account for child posts (images/videos)",
|
|
1150
1152
|
"severity": "warning",
|
|
1151
1153
|
"rationale": "React Native components for Posts must recursively render or inspect post.children. If a component only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1182,6 +1184,7 @@
|
|
|
1182
1184
|
{
|
|
1183
1185
|
"id": "android.posts.parent-child-rendered",
|
|
1184
1186
|
"version": 1,
|
|
1187
|
+
"symbol_anchored": true,
|
|
1185
1188
|
"title": "Post rendering for Android must account for child posts (images/videos)",
|
|
1186
1189
|
"severity": "warning",
|
|
1187
1190
|
"rationale": "Android Compose/RecyclerView components for Posts must render or inspect the child items (post.children). If a component only renders the parent post's text, it will silently drop attached images and videos.",
|
|
@@ -1218,6 +1221,7 @@
|
|
|
1218
1221
|
{
|
|
1219
1222
|
"id": "flutter.posts.parent-child-rendered",
|
|
1220
1223
|
"version": 1,
|
|
1224
|
+
"symbol_anchored": true,
|
|
1221
1225
|
"title": "Post rendering for Flutter must account for child posts (images/videos)",
|
|
1222
1226
|
"severity": "warning",
|
|
1223
1227
|
"rationale": "Flutter Widgets for Posts must render or inspect the child posts (post.children). If a widget only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1254,6 +1258,7 @@
|
|
|
1254
1258
|
{
|
|
1255
1259
|
"id": "ios.posts.parent-child-rendered",
|
|
1256
1260
|
"version": 1,
|
|
1261
|
+
"symbol_anchored": true,
|
|
1257
1262
|
"title": "Post rendering for iOS must account for child posts (images/videos)",
|
|
1258
1263
|
"severity": "warning",
|
|
1259
1264
|
"rationale": "iOS SwiftUI/UIKit views for Posts must inspect the child posts (post.children). If a view only renders the parent post's text, it will silently drop attached images and videos.",
|
|
@@ -1290,6 +1295,7 @@
|
|
|
1290
1295
|
{
|
|
1291
1296
|
"id": "typescript.feed.target-type-explicit",
|
|
1292
1297
|
"version": 1,
|
|
1298
|
+
"symbol_anchored": true,
|
|
1293
1299
|
"title": "Explicit targetType for TypeScript createPost calls must not be hardcoded to COMMUNITY",
|
|
1294
1300
|
"severity": "warning",
|
|
1295
1301
|
"rationale": "TypeScript feed targets must be passed dynamically (e.g. from props or state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1326,6 +1332,7 @@
|
|
|
1326
1332
|
{
|
|
1327
1333
|
"id": "react-native.feed.target-type-explicit",
|
|
1328
1334
|
"version": 1,
|
|
1335
|
+
"symbol_anchored": true,
|
|
1329
1336
|
"title": "Explicit targetType for React Native createPost calls must not be hardcoded to COMMUNITY",
|
|
1330
1337
|
"severity": "warning",
|
|
1331
1338
|
"rationale": "React Native feed targets must be passed dynamically (e.g. from props or state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1362,6 +1369,7 @@
|
|
|
1362
1369
|
{
|
|
1363
1370
|
"id": "android.feed.target-type-explicit",
|
|
1364
1371
|
"version": 1,
|
|
1372
|
+
"symbol_anchored": true,
|
|
1365
1373
|
"title": "Explicit targetType for Android createPost calls must not be hardcoded to COMMUNITY",
|
|
1366
1374
|
"severity": "warning",
|
|
1367
1375
|
"rationale": "Android feed targets must be passed dynamically (e.g. from Intent extras or ViewModel state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1398,6 +1406,7 @@
|
|
|
1398
1406
|
{
|
|
1399
1407
|
"id": "flutter.feed.target-type-explicit",
|
|
1400
1408
|
"version": 1,
|
|
1409
|
+
"symbol_anchored": true,
|
|
1401
1410
|
"title": "Explicit targetType for Flutter createPost calls must not be hardcoded to COMMUNITY",
|
|
1402
1411
|
"severity": "warning",
|
|
1403
1412
|
"rationale": "Flutter feed targets must be passed dynamically (e.g. from Widget properties), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1434,6 +1443,7 @@
|
|
|
1434
1443
|
{
|
|
1435
1444
|
"id": "ios.feed.target-type-explicit",
|
|
1436
1445
|
"version": 1,
|
|
1446
|
+
"symbol_anchored": true,
|
|
1437
1447
|
"title": "Explicit targetType for iOS createPost calls must not be hardcoded to COMMUNITY",
|
|
1438
1448
|
"severity": "warning",
|
|
1439
1449
|
"rationale": "iOS feed targets must be passed dynamically (e.g. from View state or initializer), not hardcoded as AmityPostTargetType.community.",
|
|
@@ -1470,6 +1480,7 @@
|
|
|
1470
1480
|
{
|
|
1471
1481
|
"id": "typescript.reactions.configured-name-used",
|
|
1472
1482
|
"version": 2,
|
|
1483
|
+
"symbol_anchored": true,
|
|
1473
1484
|
"title": "TypeScript reaction name matches console config",
|
|
1474
1485
|
"severity": "warning",
|
|
1475
1486
|
"advisory": true,
|
|
@@ -1508,6 +1519,7 @@
|
|
|
1508
1519
|
{
|
|
1509
1520
|
"id": "react-native.reactions.configured-name-used",
|
|
1510
1521
|
"version": 2,
|
|
1522
|
+
"symbol_anchored": true,
|
|
1511
1523
|
"advisory": true,
|
|
1512
1524
|
"title": "React Native reaction name matches console config",
|
|
1513
1525
|
"severity": "warning",
|
|
@@ -1546,6 +1558,7 @@
|
|
|
1546
1558
|
{
|
|
1547
1559
|
"id": "android.reactions.configured-name-used",
|
|
1548
1560
|
"version": 2,
|
|
1561
|
+
"symbol_anchored": true,
|
|
1549
1562
|
"advisory": true,
|
|
1550
1563
|
"title": "Android reaction name matches console config",
|
|
1551
1564
|
"severity": "warning",
|
|
@@ -1584,6 +1597,7 @@
|
|
|
1584
1597
|
{
|
|
1585
1598
|
"id": "flutter.reactions.configured-name-used",
|
|
1586
1599
|
"version": 2,
|
|
1600
|
+
"symbol_anchored": true,
|
|
1587
1601
|
"advisory": true,
|
|
1588
1602
|
"title": "Flutter reaction name matches console config",
|
|
1589
1603
|
"severity": "warning",
|
|
@@ -1622,6 +1636,7 @@
|
|
|
1622
1636
|
{
|
|
1623
1637
|
"id": "ios.reactions.configured-name-used",
|
|
1624
1638
|
"version": 2,
|
|
1639
|
+
"symbol_anchored": true,
|
|
1625
1640
|
"advisory": true,
|
|
1626
1641
|
"title": "iOS reaction name matches console config",
|
|
1627
1642
|
"severity": "warning",
|
|
@@ -1660,6 +1675,7 @@
|
|
|
1660
1675
|
{
|
|
1661
1676
|
"id": "typescript.custom-post-type.dataType-declared",
|
|
1662
1677
|
"version": 1,
|
|
1678
|
+
"symbol_anchored": true,
|
|
1663
1679
|
"title": "TypeScript custom post must declare dataType",
|
|
1664
1680
|
"severity": "warning",
|
|
1665
1681
|
"rationale": "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.",
|
|
@@ -1696,6 +1712,7 @@
|
|
|
1696
1712
|
{
|
|
1697
1713
|
"id": "react-native.custom-post-type.dataType-declared",
|
|
1698
1714
|
"version": 1,
|
|
1715
|
+
"symbol_anchored": true,
|
|
1699
1716
|
"title": "React Native custom post must declare dataType",
|
|
1700
1717
|
"severity": "warning",
|
|
1701
1718
|
"rationale": "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.",
|
|
@@ -1732,6 +1749,7 @@
|
|
|
1732
1749
|
{
|
|
1733
1750
|
"id": "android.custom-post-type.dataType-declared",
|
|
1734
1751
|
"version": 1,
|
|
1752
|
+
"symbol_anchored": true,
|
|
1735
1753
|
"title": "Android custom post must declare dataType",
|
|
1736
1754
|
"severity": "warning",
|
|
1737
1755
|
"rationale": "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.",
|
|
@@ -1768,6 +1786,7 @@
|
|
|
1768
1786
|
{
|
|
1769
1787
|
"id": "flutter.custom-post-type.dataType-declared",
|
|
1770
1788
|
"version": 1,
|
|
1789
|
+
"symbol_anchored": true,
|
|
1771
1790
|
"title": "Flutter custom post must declare dataType",
|
|
1772
1791
|
"severity": "warning",
|
|
1773
1792
|
"rationale": "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.",
|
|
@@ -1804,6 +1823,7 @@
|
|
|
1804
1823
|
{
|
|
1805
1824
|
"id": "ios.custom-post-type.dataType-declared",
|
|
1806
1825
|
"version": 1,
|
|
1826
|
+
"symbol_anchored": true,
|
|
1807
1827
|
"title": "iOS custom post must declare dataType",
|
|
1808
1828
|
"severity": "warning",
|
|
1809
1829
|
"rationale": "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.",
|