@amityco/social-plus-vise 1.0.1 → 1.1.1
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 +69 -25
- package/LICENSE +8 -6
- package/README.md +57 -17
- package/dist/capabilities.js +19 -62
- package/dist/intelligence/grounding.js +0 -23
- package/dist/intelligence/placement.js +0 -9
- package/dist/outcomes.js +44 -22
- package/dist/server.js +75 -38
- package/dist/tools/ast.js +3 -209
- package/dist/tools/blocks.js +6 -20
- package/dist/tools/compliance.js +168 -43
- package/dist/tools/creative.js +15 -41
- package/dist/tools/debug.js +0 -16
- package/dist/tools/design.js +18 -364
- package/dist/tools/docs.js +53 -24
- package/dist/tools/experienceCompiler.js +7 -10
- package/dist/tools/experienceSensors.js +1 -1
- package/dist/tools/harness.js +2 -27
- package/dist/tools/integration.js +6 -38
- package/dist/tools/learning.js +1 -1
- package/dist/tools/project.js +763 -546
- package/dist/tools/sdkFacts.js +2 -15
- package/dist/tools/sdkVersion.js +3 -36
- package/dist/tools/sensors.js +0 -6
- package/dist/tools/uxHarness.js +12 -9
- package/package.json +8 -97
- package/rules/chat.yaml +225 -0
- package/rules/event.yaml +45 -0
- package/rules/feed.yaml +24 -24
- package/rules/invitation.yaml +58 -0
- package/rules/live-data.yaml +104 -2
- package/rules/notification-tray.yaml +106 -0
- package/rules/poll.yaml +71 -0
- package/rules/sdk-lifecycle.yaml +112 -6
- package/rules/search.yaml +131 -0
- package/rules/story.yaml +221 -0
- package/rules/user-blocking.yaml +71 -0
- package/sdk-surface/flutter.json +1 -1
- package/sdk-surface/ios.json +1 -1
- package/sdk-surface/manifest.json +12 -12
- package/sdk-surface/models.flutter.json +96 -96
- package/sdk-surface/models.ios.json +1 -1
- package/sdk-surface/typescript.json +4 -4
- package/skills/social-plus-vise/SKILL.md +25 -5
- package/scripts/catalog-coverage-html.mjs +0 -325
- package/scripts/catalog-relationships-html.mjs +0 -686
- package/scripts/catalog-sheets.mjs +0 -286
- package/scripts/dart-model-extractor/bin/extract_models.dart +0 -169
- package/scripts/dart-model-extractor/pubspec.lock +0 -149
- package/scripts/dart-model-extractor/pubspec.yaml +0 -16
- package/scripts/extract-sdk-models.mjs +0 -749
- package/scripts/import-sdk-surface.mjs +0 -161
- package/scripts/pilot-feedback.mjs +0 -107
- package/scripts/workshop-board-html.mjs +0 -1018
- package/scripts/workshop-kit.mjs +0 -252
- package/skills/vise-harness-engineer/SKILL.md +0 -35
package/dist/capabilities.js
CHANGED
|
@@ -1,33 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feature-completeness assessment.
|
|
3
|
-
*
|
|
4
|
-
* Boundary (see the validation-boundaries principle): completeness is a "this is
|
|
5
|
-
* missing" claim — a universal-negative over open-ended correct implementations
|
|
6
|
-
* unless the customer/agent explicitly removes it from scope. Missing capabilities
|
|
7
|
-
* now produce `completeness-gap` in `vise check`; a recorded scope-omit reason is
|
|
8
|
-
* the false-positive escape hatch.
|
|
9
|
-
*
|
|
10
|
-
* Memory-independence comes from inversion: VISE authors the canonical capability
|
|
11
|
-
* set per outcome; the agent must OPT OUT of a capability with a recorded reason
|
|
12
|
-
* (`// vise: scope-omit <id> <reason>`), which `check` reads and reports. The
|
|
13
|
-
* agent subtracts with justification — it doesn't have to remember the set.
|
|
14
|
-
*
|
|
15
|
-
* Baseline capability gates must stay narrow. For add-feed, pagination is the
|
|
16
|
-
* mandatory capability. For add-comments, the comment composer is mandatory.
|
|
17
|
-
* For add-chat, sending plus read/unread state are mandatory. For add-follow,
|
|
18
|
-
* follower/following relationship data is mandatory. Replies, mentions,
|
|
19
|
-
* edit/delete, typing, media-message types, block lists, and deeper moderation
|
|
20
|
-
* scope are scoped separately. Richer composer affordances are selected optional sensors.
|
|
21
|
-
*/
|
|
22
1
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
23
2
|
import path from "node:path";
|
|
24
3
|
import { canonicalPlatform, getSdkFacts } from "./tools/sdkFacts.js";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
4
|
+
export function slimCapabilityItem(i) {
|
|
5
|
+
return { id: i.id, status: i.status, validator: i.validator };
|
|
6
|
+
}
|
|
7
|
+
export function slimCapabilityAvailability(ca) {
|
|
8
|
+
if (!ca)
|
|
9
|
+
return undefined;
|
|
10
|
+
return {
|
|
11
|
+
platform: ca.platform,
|
|
12
|
+
...(ca.canonicalPlatform !== undefined ? { canonicalPlatform: ca.canonicalPlatform } : {}),
|
|
13
|
+
outcome: ca.outcome,
|
|
14
|
+
source: ca.source,
|
|
15
|
+
note: ca.note,
|
|
16
|
+
available: ca.available.map(slimCapabilityItem),
|
|
17
|
+
unavailable: ca.unavailable.map(slimCapabilityItem),
|
|
18
|
+
unknown: ca.unknown.map(slimCapabilityItem),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
28
21
|
export const CAPABILITIES = [
|
|
29
|
-
// ── add-feed: post dataTypes (a feed parent is usually 'text'; rich content
|
|
30
|
-
// rides on child posts — render each type the feed can return) ──────────
|
|
31
22
|
{
|
|
32
23
|
id: "post-image",
|
|
33
24
|
label: "Image posts",
|
|
@@ -39,10 +30,6 @@ export const CAPABILITIES = [
|
|
|
39
30
|
id: "post-video",
|
|
40
31
|
label: "Video posts",
|
|
41
32
|
outcomes: ["add-feed"],
|
|
42
|
-
// Native idioms differ from TS: Android matches a sealed-class case
|
|
43
|
-
// `AmityPost.Data.VIDEO`, Flutter an enum `AmityDataType.VIDEO`, both expose
|
|
44
|
-
// getVideo()/videoData. Without these the catalog false-reports video as missing
|
|
45
|
-
// on Kotlin/Dart even when the agent handles it (found on Netflix native builds).
|
|
46
33
|
symbols: [/\bgetVideo\w*/, /\bgetVideoUrl\b/, /AmityVideo/i, /AmityPost\.Data\.VIDEO\b/, /['"]video['"]/, /Data(?:Type)?\.VIDEO\b/, /\bvideoData\b/],
|
|
47
34
|
hint: "resolve video posts via getVideo / the VIDEO dataType from the parent or a child post",
|
|
48
35
|
},
|
|
@@ -88,8 +75,6 @@ export const CAPABILITIES = [
|
|
|
88
75
|
symbols: [/\bmentionees\b/, /\bmentionedUsers\b/, /metadata.*mention/i, /['"]mention['"]/],
|
|
89
76
|
hint: "wrap @mention spans from metadata offsets and resolve userId→name; pass mentionees on create to notify",
|
|
90
77
|
},
|
|
91
|
-
// ── add-feed: broader social.plus content surfaces (advisory — opt out if a
|
|
92
|
-
// plain post feed; these are distinct social.plus features) ─────────────
|
|
93
78
|
{
|
|
94
79
|
id: "story",
|
|
95
80
|
label: "Stories (ephemeral image/video)",
|
|
@@ -111,7 +96,6 @@ export const CAPABILITIES = [
|
|
|
111
96
|
symbols: [/AmityRoomRepository/i, /\bcreateRoom\b/, /createRoomAndStartStreaming/i, /getBroadcastData/i, /createLiveStreamPost/i, /\bgoLive\b/i, /startPublish/i],
|
|
112
97
|
hint: "live rooms/broadcasting via AmityRoomRepository (createRoom, go-live, live-viewing); post the stream with createLiveStreamPost (distinct from rendering a livestream post in the feed)",
|
|
113
98
|
},
|
|
114
|
-
// ── add-feed: engagement surfaces ─────────────────────────────────────────
|
|
115
99
|
{
|
|
116
100
|
id: "comments",
|
|
117
101
|
label: "Comment thread (list + composer)",
|
|
@@ -161,7 +145,6 @@ export const CAPABILITIES = [
|
|
|
161
145
|
symbols: [/\bflagPost\b/, /\bflagComment\b/, /\bflagMessage\b/, /\.report\(\)/, /\bflaggedByMe\b/, /\bisFlagged/i],
|
|
162
146
|
hint: "show report/flag for non-authors; gate moderator-only actions by role",
|
|
163
147
|
},
|
|
164
|
-
// ── add-comments: depth ───────────────────────────────────────────────────
|
|
165
148
|
{
|
|
166
149
|
id: "comment-composer",
|
|
167
150
|
label: "Comment composer (write)",
|
|
@@ -190,7 +173,6 @@ export const CAPABILITIES = [
|
|
|
190
173
|
symbols: [/\bflagComment\b/, /\.report\(\)/, /\bflaggedByMe\b/],
|
|
191
174
|
hint: "show flag/report on comments for non-authors",
|
|
192
175
|
},
|
|
193
|
-
// ── add-chat: depth ───────────────────────────────────────────────────────
|
|
194
176
|
{
|
|
195
177
|
id: "send-message",
|
|
196
178
|
label: "Send message",
|
|
@@ -223,7 +205,7 @@ export const CAPABILITIES = [
|
|
|
223
205
|
id: "read-state",
|
|
224
206
|
label: "Read state / unread count",
|
|
225
207
|
outcomes: ["add-chat"],
|
|
226
|
-
symbols: [/\bmarkRead\b/, /\bmarkAsRead\b/,
|
|
208
|
+
symbols: [/\bmarkRead\b/, /\bmarkAsRead\b/, /\bunreadCount\b/],
|
|
227
209
|
hint: "mark channel/messages read so the server's unread count decrements",
|
|
228
210
|
},
|
|
229
211
|
{
|
|
@@ -240,7 +222,6 @@ export const CAPABILITIES = [
|
|
|
240
222
|
symbols: [/getMembers/i, /ChannelMember/i, /\bmembership\b/i, /\bgetChannelMembers\b/i],
|
|
241
223
|
hint: "list/observe channel members; handle join/leave",
|
|
242
224
|
},
|
|
243
|
-
// ── add-community ─────────────────────────────────────────────────────────
|
|
244
225
|
{
|
|
245
226
|
id: "community-create",
|
|
246
227
|
label: "Create community",
|
|
@@ -290,7 +271,6 @@ export const CAPABILITIES = [
|
|
|
290
271
|
symbols: [/AmityCommunityCategory/i, /getCategories/i, /categoryIds?/i],
|
|
291
272
|
hint: "organize communities by category (AmityCommunityCategory)",
|
|
292
273
|
},
|
|
293
|
-
// ── add-moderation: depth ─────────────────────────────────────────────────
|
|
294
274
|
{
|
|
295
275
|
id: "report-flow",
|
|
296
276
|
label: "Report / flag flow",
|
|
@@ -319,7 +299,6 @@ export const CAPABILITIES = [
|
|
|
319
299
|
symbols: [/isDeleted/i, /\bisFlagged/i, /hasFlaggedComment/i, /blockedContent/i, /\bhidden\b/i],
|
|
320
300
|
hint: "render a placeholder for deleted/flagged content rather than hiding it silently",
|
|
321
301
|
},
|
|
322
|
-
// ── add-follow (social graph) ─────────────────────────────────────────────
|
|
323
302
|
{
|
|
324
303
|
id: "follow-unfollow",
|
|
325
304
|
label: "Follow / unfollow",
|
|
@@ -355,7 +334,6 @@ export const CAPABILITIES = [
|
|
|
355
334
|
symbols: [/getBlockedUsers/i, /blockedUsers/i],
|
|
356
335
|
hint: "surface a managed blocked-users list (getBlockedUsers)",
|
|
357
336
|
},
|
|
358
|
-
// ── add-notifications (in-app tray) ───────────────────────────────────────
|
|
359
337
|
{
|
|
360
338
|
id: "notification-tray",
|
|
361
339
|
label: "Notification tray / inbox",
|
|
@@ -378,9 +356,6 @@ export const CAPABILITIES = [
|
|
|
378
356
|
hint: "respect Amity's server-side notification settings/preferences (getSettings)",
|
|
379
357
|
},
|
|
380
358
|
];
|
|
381
|
-
// Optional capabilities are not baseline compliance. `vise plan` offers them as
|
|
382
|
-
// explicit feed-forward choices; `vise init --answer feed_optional_capabilities=...`
|
|
383
|
-
// records selected choices; `vise check` then evaluates these source sensors.
|
|
384
359
|
export const OPTIONAL_CAPABILITIES = [
|
|
385
360
|
{
|
|
386
361
|
id: "post-image-upload",
|
|
@@ -1183,26 +1158,12 @@ function baselineCapabilities(outcome) {
|
|
|
1183
1158
|
const baseline = new Set(baselineIds);
|
|
1184
1159
|
return caps.filter((capability) => baseline.has(capability.id));
|
|
1185
1160
|
}
|
|
1186
|
-
/** The Vise-authored baseline capability checklist for an outcome. */
|
|
1187
1161
|
export function capabilityChecklist(outcome) {
|
|
1188
1162
|
return baselineCapabilities(outcome).map((c) => ({ id: c.id, label: c.label, hint: c.hint }));
|
|
1189
1163
|
}
|
|
1190
|
-
/**
|
|
1191
|
-
* The Vise-owned completeness id vocabulary — every id a Block Factory contract
|
|
1192
|
-
* may reference in `providesCapabilities`. Vise owns this id space; the factory
|
|
1193
|
-
* owns the per-block declaration (see block-factory/docs/vise-boundary.md).
|
|
1194
|
-
*/
|
|
1195
1164
|
export function completenessCapabilityIds() {
|
|
1196
1165
|
return new Set(CAPABILITIES.map((capability) => capability.id));
|
|
1197
1166
|
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Overlay installed-block evidence onto a source-scan completeness assessment.
|
|
1200
|
-
* A still-missing checklist capability becomes present with evidence
|
|
1201
|
-
* `source: "block:<blockId>"` when a locally validated installed block declares
|
|
1202
|
-
* it in `providesCapabilities`. Callers are responsible for the local validation
|
|
1203
|
-
* (package declared + filesTouched intact); on drift they must simply not pass
|
|
1204
|
-
* the capability here, so the normal gap returns.
|
|
1205
|
-
*/
|
|
1206
1167
|
export function applyBlockProvidedCompleteness(assessment, provided) {
|
|
1207
1168
|
if (provided.length === 0 || assessment.missing.length === 0) {
|
|
1208
1169
|
return assessment;
|
|
@@ -1231,7 +1192,6 @@ export function applyBlockProvidedCompleteness(assessment, provided) {
|
|
|
1231
1192
|
}
|
|
1232
1193
|
return { ...assessment, present, missing };
|
|
1233
1194
|
}
|
|
1234
|
-
/** Optional feed-forward choices. These are not baseline completeness requirements. */
|
|
1235
1195
|
export function optionalCapabilityChecklist(outcome, availableIds) {
|
|
1236
1196
|
const available = availableIds ? new Set(availableIds) : undefined;
|
|
1237
1197
|
return OPTIONAL_CAPABILITIES
|
|
@@ -1296,7 +1256,6 @@ export function assessSelectedOptionalCapabilities(source, outcome, selectedIds
|
|
|
1296
1256
|
note: "Selected optional capabilities are enforced only after the user or host agent opts in through `feed_optional_capabilities`. They are source sensors, not baseline compliance rules.",
|
|
1297
1257
|
};
|
|
1298
1258
|
}
|
|
1299
|
-
/** Pure assessment over already-read source text. */
|
|
1300
1259
|
export function assessCompleteness(source, outcome) {
|
|
1301
1260
|
const caps = baselineCapabilities(outcome);
|
|
1302
1261
|
const optOuts = new Map();
|
|
@@ -1339,14 +1298,13 @@ export function assessCompleteness(source, outcome) {
|
|
|
1339
1298
|
}
|
|
1340
1299
|
return { outcome, present, missing, optedOut, invalidOptOuts: invalid, note: ADVISORY_NOTE };
|
|
1341
1300
|
}
|
|
1342
|
-
// ── Bounded source read (advisory; perf-bounded like the design check scan) ──
|
|
1343
1301
|
const SCAN_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".dart", ".kt", ".java", ".swift", ".vue"]);
|
|
1344
1302
|
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", "vendor", ".dart_tool", "pods", "macos", "windows", "linux"]);
|
|
1345
1303
|
const MAX_FILES = 1500;
|
|
1346
1304
|
const MAX_FILE_BYTES = 1_000_000;
|
|
1347
1305
|
export async function assessProjectCompleteness(root, outcome) {
|
|
1348
1306
|
if (baselineCapabilities(outcome).length === 0) {
|
|
1349
|
-
return null;
|
|
1307
|
+
return null;
|
|
1350
1308
|
}
|
|
1351
1309
|
return assessCompleteness(await readProjectSource(root), outcome);
|
|
1352
1310
|
}
|
|
@@ -1386,7 +1344,6 @@ async function readProjectSource(root) {
|
|
|
1386
1344
|
}
|
|
1387
1345
|
}
|
|
1388
1346
|
catch {
|
|
1389
|
-
// skip unreadable
|
|
1390
1347
|
}
|
|
1391
1348
|
}
|
|
1392
1349
|
}
|
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
// Deterministic grounding contract for agent-driven variant selection (experience-factory rebuild,
|
|
5
|
-
// Option A). The factory does NOT call a model: the driving coding agent performs the semantic
|
|
6
|
-
// understanding (customer request -> variant) and hands the factory a selection. This module is the
|
|
7
|
-
// factory's deterministic half — it enforces that the agent's selection is GROUNDED in the catalog
|
|
8
|
-
// and surfaces honest review signals. It performs no inference and no IO in its core function, so it
|
|
9
|
-
// is fully reproducible (the determinism lives here; the non-determinism lives in the visible driver).
|
|
10
|
-
//
|
|
11
|
-
// Contract, by evidence from the spike + stress test (benchmarks/experience-calibration/
|
|
12
|
-
// semantic-understanding-spike.mjs and -stresstest.mjs):
|
|
13
|
-
// - variantId MUST be a catalog id, or the literal "none" (the agent's honest no-fit answer).
|
|
14
|
-
// Anything else is a hallucination and is REJECTED.
|
|
15
|
-
// - rationale is REQUIRED (explainability is a core principle of the factory).
|
|
16
|
-
// - confidence is expected (high|medium|low); low confidence and "none" are not failures — they are
|
|
17
|
-
// honest signals that the human/agent should reconsider or that the catalog may need a new variant.
|
|
18
4
|
export const GROUNDING_SCHEMA_VERSION = "2026-06-07.vise-grounding.v1";
|
|
19
5
|
export const NO_FIT = "none";
|
|
20
6
|
const CONFIDENCE_VALUES = ["high", "medium", "low"];
|
|
21
|
-
/**
|
|
22
|
-
* Validate an agent-provided variant selection against the catalog. Pure and deterministic: given the
|
|
23
|
-
* same selection and the same catalog id set it always returns the same result.
|
|
24
|
-
*/
|
|
25
7
|
export function groundSelection(selection, catalogVariantIds) {
|
|
26
8
|
const ids = [...catalogVariantIds];
|
|
27
9
|
const idSet = new Set(ids);
|
|
@@ -31,7 +13,6 @@ export function groundSelection(selection, catalogVariantIds) {
|
|
|
31
13
|
const rawConfidence = typeof selection?.confidence === "string" ? selection.confidence.trim().toLowerCase() : "";
|
|
32
14
|
const isNoFit = rawVariant.toLowerCase() === NO_FIT;
|
|
33
15
|
const isKnownVariant = idSet.has(rawVariant);
|
|
34
|
-
// 1. Grounding: variant must be a catalog id or the explicit no-fit answer.
|
|
35
16
|
if (!isNoFit && !isKnownVariant) {
|
|
36
17
|
signals.push({
|
|
37
18
|
code: "hallucinated-variant",
|
|
@@ -48,7 +29,6 @@ export function groundSelection(selection, catalogVariantIds) {
|
|
|
48
29
|
message: `The agent reported no catalog variant fits this request. Reconsider the request, or treat this as a candidate for a new catalog variant.`,
|
|
49
30
|
});
|
|
50
31
|
}
|
|
51
|
-
// 2. Rationale is required for explainability (applies to a real variant and to "none").
|
|
52
32
|
if (!rationale) {
|
|
53
33
|
signals.push({
|
|
54
34
|
code: "missing-rationale",
|
|
@@ -56,7 +36,6 @@ export function groundSelection(selection, catalogVariantIds) {
|
|
|
56
36
|
message: `A rationale is required: the selection must explain, grounded in the request and catalog, why this variant (or "${NO_FIT}") was chosen.`,
|
|
57
37
|
});
|
|
58
38
|
}
|
|
59
|
-
// 3. Confidence (advisory but expected). Invalid value is a contract violation; absence/low are signals.
|
|
60
39
|
let confidence = null;
|
|
61
40
|
if (!rawConfidence) {
|
|
62
41
|
signals.push({ code: "missing-confidence", severity: "warn", message: `No confidence was provided; expected one of ${CONFIDENCE_VALUES.join(", ")}.` });
|
|
@@ -91,12 +70,10 @@ function summarize(status, variantId, signals) {
|
|
|
91
70
|
return `Selection rejected: ${signals.filter((s) => s.severity === "error").map((s) => s.code).join(", ")}.`;
|
|
92
71
|
return `Selection accepted with review signals: ${signals.map((s) => s.code).join(", ")}.`;
|
|
93
72
|
}
|
|
94
|
-
/** Resolve the catalog directory shipped in the package (packages/intelligence/catalog). */
|
|
95
73
|
export function catalogDir() {
|
|
96
74
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
97
75
|
return path.resolve(moduleDir, "..", "..", "packages", "intelligence", "catalog");
|
|
98
76
|
}
|
|
99
|
-
/** Load the set of valid variant ids from the declarative catalog. */
|
|
100
77
|
export async function loadCatalogVariantIds(dir = catalogDir()) {
|
|
101
78
|
const raw = await readFile(path.join(dir, "variants.json"), "utf8");
|
|
102
79
|
const parsed = JSON.parse(raw);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
// Display order — drives CREATIVE_SURFACE_HINTS and, downstream, surfaceSequence.
|
|
5
4
|
export const SURFACE_REGISTRY = [
|
|
6
5
|
{ id: "feed", outcome: "add-feed", label: "Feed and content surface" },
|
|
7
6
|
{ id: "comments", outcome: "add-comments", label: "Comments and replies" },
|
|
@@ -10,9 +9,6 @@ export const SURFACE_REGISTRY = [
|
|
|
10
9
|
{ id: "community", outcome: "add-community", label: "Community management and identity" },
|
|
11
10
|
{ id: "notifications", outcome: "add-notifications", label: "Notifications and activation" },
|
|
12
11
|
];
|
|
13
|
-
// Scan order for reducing an object list to its surfaces/outcomes. Kept distinct from the display
|
|
14
|
-
// order above to reproduce the pre-derivation if-chain output byte-for-byte (community was scanned
|
|
15
|
-
// before profile). Faithful-reproduction only; could be unified if order is proven immaterial.
|
|
16
12
|
const SURFACE_SCAN_ORDER = ["feed", "comments", "chat", "community", "profile", "notifications"];
|
|
17
13
|
const outcomeBySurface = new Map(SURFACE_REGISTRY.map((s) => [s.id, s.outcome]));
|
|
18
14
|
const VALID_SURFACES = new Set(SURFACE_REGISTRY.map((s) => s.id));
|
|
@@ -20,8 +16,6 @@ function catalogRoot() {
|
|
|
20
16
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
21
17
|
return path.resolve(moduleDir, "..", "..", "packages", "intelligence", "catalog");
|
|
22
18
|
}
|
|
23
|
-
// object id -> surface / block, in catalog declaration order. Read synchronously at module load so
|
|
24
|
-
// the SOCIAL_PLUS_OBJECTS export and the lookup functions stay synchronous (matches prior contract).
|
|
25
19
|
const objectSurface = new Map();
|
|
26
20
|
const objectBlock = new Map();
|
|
27
21
|
{
|
|
@@ -41,8 +35,6 @@ const objectBlock = new Map();
|
|
|
41
35
|
}
|
|
42
36
|
}
|
|
43
37
|
}
|
|
44
|
-
// SDK-backed objects are exactly those placed on a surface; app-layer (knowledge/prediction) and
|
|
45
|
-
// in-development objects declare no surface and are excluded.
|
|
46
38
|
export const SOCIAL_PLUS_OBJECTS = new Set(objectSurface.keys());
|
|
47
39
|
export function surfaceIdsForObjects(objectIds) {
|
|
48
40
|
const present = new Set(objectIds);
|
|
@@ -60,7 +52,6 @@ export function outcomesForObjects(objectIds) {
|
|
|
60
52
|
export function blockIdForObject(objectId) {
|
|
61
53
|
return objectBlock.get(objectId);
|
|
62
54
|
}
|
|
63
|
-
// Object ids bound to a surface, in catalog declaration order (used to build CREATIVE_SURFACE_HINTS).
|
|
64
55
|
export function surfaceObjectIds(surfaceId) {
|
|
65
56
|
const ids = [];
|
|
66
57
|
for (const [id, surface] of objectSurface) {
|
package/dist/outcomes.js
CHANGED
|
@@ -17,8 +17,16 @@ export const CLASSIFY_ORDER = [
|
|
|
17
17
|
"setup-sdk",
|
|
18
18
|
];
|
|
19
19
|
const PUSH_PATTERNS = [/\b(push(?:\s+notification)?|push(?:\s+notifications)?|firebase|fcm|apns)\b/];
|
|
20
|
+
const STORY_REGEX = /\bstor(?:y|ies)\s+(?:tray|ring|bar|reel|carousel|viewer|highlight|feature|collection)\b|\b(?:add|adding|build|building|create|creating|implement|implementing|show|render|rendering|display|displaying)\s+(?:a\s+|an\s+|the\s+)?(?:active\s+|community\s+)?stor(?:y|ies)\b/;
|
|
21
|
+
const SEARCH_REGEX = /\b(?:search|find)\s+(?:for\s+)?(?:users?|people|communit(?:y|ies)|posts?|channels?|messages?|members?|content)\b|\b(?:user|people|communit(?:y|ies)|post|content|global|message)\s+search\b|\bsearch\s+(?:bar|screen|box|field|page|results?|ui|view|feature)\b|\bsemantic search\b|\b(?:add|adding|build|building|implement|implementing|create|creating)\s+(?:a\s+|an\s+|the\s+)?search\b/;
|
|
22
|
+
const EVENT_REGEX = /\bevent\s+(?:calendar|list|feed|page|screen|details?)\b|\battendee(?:s)?\s+list\b|\bRSVP\b|\b(?:add|adding|build|building|create|creating|implement|implementing|show|display)\s+(?:a\s+|an\s+|the\s+)?events?\b/i;
|
|
23
|
+
const LIVESTREAM_REGEX = /\blive\s?stream(?:ing)?\b|\blive\s+video\b|\blive\s+room\b|\b(?:add|adding|build|building|create|creating|implement|implementing|start|starting|watch|watching|play|playing)\s+(?:a\s+|an\s+|the\s+)?(?:live\s+)?(?:livestream|live\s+stream|live\s+room)\b/i;
|
|
20
24
|
const LIVE_PATTERNS = [
|
|
21
25
|
/\b(live object|live objects|live collection|live collections|realtime collection|real-time collection|observe|observer|subscribe|subscription|unsubscribe|live update|live updates)\b/,
|
|
26
|
+
STORY_REGEX,
|
|
27
|
+
SEARCH_REGEX,
|
|
28
|
+
EVENT_REGEX,
|
|
29
|
+
LIVESTREAM_REGEX,
|
|
22
30
|
];
|
|
23
31
|
const FEED_PATTERNS = [
|
|
24
32
|
/\b(social feature|social features|feed|timeline|post list|news feed|create post|create a post|post creation|compose post)\b/,
|
|
@@ -32,25 +40,19 @@ const MODERATION_OUTCOME_PATTERNS = [
|
|
|
32
40
|
const CHAT_PATTERNS = [
|
|
33
41
|
/\b(chat|messaging|dm|direct message|conversation|group chat|channel|inbox)\b/,
|
|
34
42
|
];
|
|
35
|
-
// Community MANAGEMENT/membership — deliberately NOT bare "community" (which
|
|
36
|
-
// co-occurs with feed: "community feed" must route to add-feed, handled by
|
|
37
|
-
// classify order placing add-community AFTER add-feed).
|
|
38
43
|
const COMMUNITY_PATTERNS = [
|
|
39
44
|
/\b(create|creating|build|building|manage|managing|join|joining|leave|leaving|set ?up)\s+(a\s+|the\s+)?communit/,
|
|
40
45
|
/\bcommunit\w*\s+(member|membership|role|invit|categor|setting|management|directory|discovery)/,
|
|
41
46
|
/\bcommunity\s+(creation|management|members?|roles?|invitations?|categories|settings|moderation)\b/,
|
|
42
47
|
];
|
|
43
|
-
// Social graph (follow/followers). "block user" is intentionally left to
|
|
44
|
-
// add-moderation; blocking is still a capability under add-follow.
|
|
45
48
|
const FOLLOW_PATTERNS = [
|
|
46
49
|
/\b(follow|unfollow|follower|followers|following)\b/,
|
|
47
50
|
/\b(social graph|user relationship|follow request|followers? list|following list)\b/,
|
|
51
|
+
/\bblocked\s+users?\b/,
|
|
52
|
+
/\bmanage\s+blocked\b/,
|
|
53
|
+
/\bblock\s*list\b/,
|
|
54
|
+
/\bunblock\b/,
|
|
48
55
|
];
|
|
49
|
-
// In-app notification tray/inbox/settings. Deliberately NOT bare "notification"
|
|
50
|
-
// and NOT "push notification" (which routes to setup-push).
|
|
51
|
-
// "notification feed"/"notification inbox" deliberately excluded — "feed" and
|
|
52
|
-
// "inbox" are claimed by add-feed / add-chat respectively. Tray/center/in-app/
|
|
53
|
-
// settings are non-colliding.
|
|
54
56
|
const NOTIFICATION_PATTERNS = [
|
|
55
57
|
/\b(notification tray|notification cent(?:er|re)|in-?app notification)/,
|
|
56
58
|
/\bnotification (?:setting|preference)/,
|
|
@@ -58,26 +60,46 @@ const NOTIFICATION_PATTERNS = [
|
|
|
58
60
|
const TROUBLESHOOT_PATTERNS = [/\b(error|broken|crash|not working|fail|timeout|401|403)\b/];
|
|
59
61
|
const VALIDATE_PATTERNS = [/\b(validate|check|correct|setup right|initiali[sz])\b/];
|
|
60
62
|
const SETUP_PATTERNS = [/\b(setup|set up|install|integrate|wire|configure|init sdk|sdk setup|session lifecycle)\b|initialise?s?\b/];
|
|
61
|
-
// Multi-surface social requests must not collapse to the first matching single
|
|
62
|
-
// outcome. A request like "feed + chat + profile" needs an explicit surface
|
|
63
|
-
// choice or separate contracts, otherwise chat/profile gaps escape an add-feed
|
|
64
|
-
// compliance sidecar.
|
|
65
63
|
export const BROAD_SOCIAL_REGEX = /\b(nice|social features|social feature|engagement|community experience)\b|\b(?:feed|post|posts|comments?)\b[\s\S]{0,160}\b(?:chat|messaging|profile|followers?|following|social graph)\b|\b(?:chat|messaging|profile|followers?|following|social graph)\b[\s\S]{0,160}\b(?:feed|post|posts|comments?)\b/i;
|
|
66
64
|
export const DESIGN_REGEX = /\bdesign token|design tokens|theme|same design|design system|brand/i;
|
|
67
65
|
export function classifyOutcome(request) {
|
|
68
66
|
const normalized = request.toLowerCase();
|
|
69
|
-
// Precedence: a feed/timeline build that also mentions comments is a feed
|
|
70
|
-
// build, not a standalone "add comments to existing content" task. add-feed
|
|
71
|
-
// carries conditional comment guidance, so a multi-feature feed request must
|
|
72
|
-
// route there — otherwise add-comments (which precedes add-feed in the
|
|
73
|
-
// classify order) wins on the bare word "comment" and silently drops every
|
|
74
|
-
// feed/avatar/poll/notification step. Pure comment requests (no feed signal)
|
|
75
|
-
// still fall through to add-comments below.
|
|
76
67
|
const matchesFeed = outcomeRegistry["add-feed"].patterns.some((p) => p.test(normalized));
|
|
77
68
|
const matchesComments = outcomeRegistry["add-comments"].patterns.some((p) => p.test(normalized));
|
|
78
69
|
if (matchesFeed && matchesComments) {
|
|
79
70
|
return "add-feed";
|
|
80
71
|
}
|
|
72
|
+
if (STORY_REGEX.test(normalized)) {
|
|
73
|
+
if (matchesFeed) {
|
|
74
|
+
return "add-feed";
|
|
75
|
+
}
|
|
76
|
+
if (matchesComments) {
|
|
77
|
+
return "add-comments";
|
|
78
|
+
}
|
|
79
|
+
if (outcomeRegistry["add-chat"].patterns.some((p) => p.test(normalized))) {
|
|
80
|
+
return "add-chat";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (SEARCH_REGEX.test(normalized)) {
|
|
84
|
+
if (matchesFeed) {
|
|
85
|
+
return "add-feed";
|
|
86
|
+
}
|
|
87
|
+
if (matchesComments) {
|
|
88
|
+
return "add-comments";
|
|
89
|
+
}
|
|
90
|
+
if (outcomeRegistry["add-chat"].patterns.some((p) => p.test(normalized))) {
|
|
91
|
+
return "add-chat";
|
|
92
|
+
}
|
|
93
|
+
if (outcomeRegistry["add-community"].patterns.some((p) => p.test(normalized))) {
|
|
94
|
+
return "add-community";
|
|
95
|
+
}
|
|
96
|
+
if (outcomeRegistry["add-follow"].patterns.some((p) => p.test(normalized))) {
|
|
97
|
+
return "add-follow";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (LIVESTREAM_REGEX.test(normalized) && matchesFeed) {
|
|
101
|
+
return "add-feed";
|
|
102
|
+
}
|
|
81
103
|
for (const id of CLASSIFY_ORDER) {
|
|
82
104
|
const def = outcomeRegistry[id];
|
|
83
105
|
if (def.patterns.some((pattern) => pattern.test(normalized))) {
|
|
@@ -1062,7 +1084,7 @@ const addChat = {
|
|
|
1062
1084
|
{ step: "Add message send with error handling and auth gate. Observe each message's syncState and surface failed sends (error state) with a retry/delete affordance instead of rendering optimistic success unconditionally; clean up unrecoverable failures (e.g. deleteFailedMessages) on init.", evidence: ["implementationRules.file-specific edits"] },
|
|
1063
1085
|
{ step: "Render the chat inbox from SDK channel queries/live collections. Show unread counts/badges from the SDK on channel rows or the chat tab (`channel.getUnreadCount()`, `getSubChannelsUnreadCount()`, or `AmityCoreClient.observeUserUnread()` / `getTotalChannelUnread()`), and declare channel-list sorting explicitly (typically last activity descending) so recency cannot be reversed by UI defaults.", evidence: ["social-plus-sdk/chat/channels", "intake.chat_inbox_scope", "capabilityAvailability.available"] },
|
|
1064
1086
|
{ step: "Declare message order explicitly with `AmityMessageQuerySortOption.FIRST_CREATED` or `LAST_CREATED` so the message thread cannot invert by relying on defaults.", evidence: ["social-plus-sdk/chat/messages"] },
|
|
1065
|
-
{ step: "Wire read receipts and typing indicators if required — and mark the channel/messages read on open (channel.markAsRead() / message.markRead()
|
|
1087
|
+
{ step: "Wire read receipts and typing indicators if required — and mark the channel/messages read on open (channel.markAsRead() / message.markRead()) so the server-side unread count actually decrements. Reading the unread count without ever marking read leaves the badge stuck. Note: the MarkerSyncEngine receipt-sync API (startMessageReceiptSync/stopMessageReceiptSync) is deprecated and being sun-set — track read state with the unread count + markRead() instead.", evidence: ["requiredInputs.read receipt and typing indicator requirements"] },
|
|
1066
1088
|
{ step: "Add moderation affordance on messages.", evidence: ["requiredInputs.moderation flow for chat messages"] },
|
|
1067
1089
|
{ step: "Run validate_setup and detected command sensors after edits.", evidence: ["validate_setup", "run_sensors"] },
|
|
1068
1090
|
];
|