@amityco/social-plus-vise 1.2.0 → 1.4.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 +75 -2
- package/README.md +23 -10
- package/dist/capabilities.js +35 -4
- package/dist/entryState.js +71 -0
- package/dist/experience.js +70 -0
- package/dist/explore.js +51 -0
- package/dist/flow.js +382 -0
- package/dist/humanFormat.js +251 -0
- package/dist/intake.js +117 -0
- package/dist/intelligence/placement.js +2 -1
- package/dist/outcomes.js +141 -42
- package/dist/productExpectations.js +15 -0
- package/dist/requestReadiness.js +99 -0
- package/dist/server.js +675 -56
- package/dist/sidecar.js +5 -0
- package/dist/solutionPath.js +2 -2
- package/dist/tools/compliance.js +1014 -133
- package/dist/tools/creative.js +14 -12
- package/dist/tools/debug.js +83 -26
- package/dist/tools/design.js +344 -14
- package/dist/tools/experienceCompiler.js +1 -1
- package/dist/tools/experienceSensors.js +1 -1
- package/dist/tools/harness.js +13 -0
- package/dist/tools/integration.js +263 -90
- package/dist/tools/learning.js +1 -3
- package/dist/tools/project.js +987 -72
- package/dist/tools/sdkFacts.js +8 -1
- package/dist/tools/smoke.js +134 -0
- package/dist/tools/uxHarness.js +54 -6
- package/dist/uikitCustomization.js +22 -6
- package/dist/version.js +7 -3
- package/package.json +1 -1
- package/packages/intelligence/catalog/catalog.schema.json +1 -1
- package/packages/intelligence/catalog/experience-objects.json +2 -2
- package/packages/intelligence/catalog/variants.json +24 -0
- package/rules/event.yaml +3 -0
- package/rules/feed.yaml +251 -0
- package/rules/invitation.yaml +4 -0
- package/rules/live-data.yaml +110 -0
- package/rules/notification-tray.yaml +4 -0
- package/rules/poll.yaml +5 -0
- package/rules/sdk-lifecycle.yaml +559 -0
- package/rules/search.yaml +5 -0
- package/rules/security.yaml +12 -0
- package/rules/story.yaml +5 -0
- package/rules/user-blocking.yaml +5 -0
- package/skills/social-plus-vise/SKILL.md +163 -15
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, getOutcomeDefinition, hasAnswer, planContextFor, resolveOutcome, } from "../outcomes.js";
|
|
3
|
+
import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, getOutcomeDefinition, hasAnswer, planContextFor, platformFeatureGapsFor, resolveOutcome, } from "../outcomes.js";
|
|
4
4
|
import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
|
|
5
5
|
import { availableOptionalCapabilityIds, capabilityChecklist, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, slimCapabilityAvailability, slimCapabilityItem, } from "../capabilities.js";
|
|
6
6
|
import { applicableCompliancePlanRuleSummaries } from "./compliance.js";
|
|
7
|
-
import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, } from "./design.js";
|
|
7
|
+
import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, readDesignProfile, buildDesignReviewLoop, } from "./design.js";
|
|
8
8
|
import { sdkVersionGuidance } from "./sdkVersion.js";
|
|
9
9
|
import { detectCommandSensors } from "./harness.js";
|
|
10
10
|
import { inspectProject } from "./project.js";
|
|
@@ -12,6 +12,11 @@ import { creativeSurfaceHints, readCreativeSelection } from "./creative.js";
|
|
|
12
12
|
import { buildUxHarness, uxHarnessPlanContext } from "./uxHarness.js";
|
|
13
13
|
import { recommendSolutionPath } from "../solutionPath.js";
|
|
14
14
|
import { recommendUIKitCustomization } from "../uikitCustomization.js";
|
|
15
|
+
import { classifyEntryState } from "../entryState.js";
|
|
16
|
+
import { readFlowState } from "../flow.js";
|
|
17
|
+
import { clarifyDimensions, clarifyGuidance } from "../requestReadiness.js";
|
|
18
|
+
import { assembleIntake } from "../intake.js";
|
|
19
|
+
import { experienceBundleFor } from "../experience.js";
|
|
15
20
|
export const planIntegrationTool = {
|
|
16
21
|
name: "plan_integration",
|
|
17
22
|
description: "Create a grounded, evidence-backed implementation packet before an AI coding agent edits a customer project.",
|
|
@@ -60,6 +65,13 @@ function answersFromInput(raw) {
|
|
|
60
65
|
}
|
|
61
66
|
async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}) {
|
|
62
67
|
const repoRoot = path.resolve(repoPath);
|
|
68
|
+
if (!hasAnswer(answers, "solution_path")) {
|
|
69
|
+
const flow = await readFlowState(repoRoot);
|
|
70
|
+
const confirmedPath = flow?.request === request && flow.blueprint?.confirmed_digest ? flow.blueprint?.path ?? null : null;
|
|
71
|
+
if (confirmedPath) {
|
|
72
|
+
answers = { ...answers, solution_path: confirmedPath };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
63
75
|
const inspection = await inspectProject(repoRoot, surfacePath);
|
|
64
76
|
const root = inspection.effectiveRoot;
|
|
65
77
|
const storedCreativeSelection = await readCreativeSelection(repoRoot);
|
|
@@ -74,6 +86,14 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
74
86
|
const uxHarnessOutcome = BROAD_SOCIAL_REGEX.test(request) && !hasAnswer(answers, "feature_surface") ? undefined : outcome;
|
|
75
87
|
const uxHarnessContext = uxHarness ? uxHarnessPlanContext(uxHarness, uxHarnessOutcome) : undefined;
|
|
76
88
|
const platform = preferredPlatform(inspection.platforms);
|
|
89
|
+
const platformAvailability = platformFeatureGapsFor(request, platform).map((gap) => ({
|
|
90
|
+
feature: gap.feature,
|
|
91
|
+
status: "unavailable-on-platform",
|
|
92
|
+
platform,
|
|
93
|
+
availableOn: gap.availableOn,
|
|
94
|
+
sdkSource: gap.sdkSource,
|
|
95
|
+
guidance: gap.guidance,
|
|
96
|
+
}));
|
|
77
97
|
const baseSupportLevel = supportFor(outcome, platform);
|
|
78
98
|
const sensors = await detectCommandSensors(root, inspection.platforms);
|
|
79
99
|
const ctx = planContextFor({
|
|
@@ -92,7 +112,18 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
92
112
|
const designContract = await readDesignContract(repoRoot);
|
|
93
113
|
const designReview = designReviewGuidance(repoRoot, designContract, answers);
|
|
94
114
|
const acceptedDesignContract = designReview.status === "accepted" ? designContract : null;
|
|
95
|
-
const
|
|
115
|
+
const designProfile = acceptedDesignContract ? await readDesignProfile(repoRoot) : null;
|
|
116
|
+
const designBrief = acceptedDesignContract ? buildDesignBrief(acceptedDesignContract, designProfile) : undefined;
|
|
117
|
+
const designReviewLoop = designProfile
|
|
118
|
+
? solutionPath.recommendation === "uikit"
|
|
119
|
+
? {
|
|
120
|
+
status: "uikit",
|
|
121
|
+
note: "Your social.plus UI comes from themed prebuilt UIKit components — there's no custom render to critique. Run `vise design check` to verify your UIKit theme config (`uikit.config.json` — its `theme.light`/`theme.dark` palette) matches the brand tokens; off-brand colours in it are reported as off-contract. Tune the UIKit theme rather than hand-building screens.",
|
|
122
|
+
}
|
|
123
|
+
: solutionPath.recommendation === "sdk" || solutionPath.recommendation === "hybrid"
|
|
124
|
+
? buildDesignReviewLoop(designProfile)
|
|
125
|
+
: undefined
|
|
126
|
+
: undefined;
|
|
96
127
|
const socialWorkplan = await socialWorkplanFor({
|
|
97
128
|
request,
|
|
98
129
|
answers,
|
|
@@ -112,7 +143,9 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
112
143
|
const intake = intakeFor(ctx, outcomeQuestions, outcome, designBrief, capabilityAvailability, designReview);
|
|
113
144
|
const packageJsonText = await readFile(path.join(root, "package.json"), "utf8").catch(() => undefined);
|
|
114
145
|
const sdkVersion = await sdkVersionGuidance(platform, packageJsonText);
|
|
146
|
+
const crossPlatformHandoffSteps = crossPlatformImplementationSteps(platform, packageJsonText);
|
|
115
147
|
const decisionsRequired = [
|
|
148
|
+
...platformAvailability.map((notice) => `[platform limitation] ${notice.feature} is not exposed by the ${notice.platform} SDK (supported on ${notice.availableOn.join(", ")}). Decide an alternative — see platformAvailability — and state it; do not implement against a non-existent SDK API.`),
|
|
116
149
|
...solutionPathDecisions(solutionPath, answers),
|
|
117
150
|
...uikitCustomizationDecisions(uikitCustomization, answers),
|
|
118
151
|
...(creativeContext
|
|
@@ -129,13 +162,32 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
129
162
|
? `[resolve before building] ${q.question}`
|
|
130
163
|
: `[decide & state in your summary] ${q.question}`),
|
|
131
164
|
];
|
|
165
|
+
const entryState = classifyEntryState({
|
|
166
|
+
outcome,
|
|
167
|
+
broadSocialRequest: ctx.broadSocialRequest,
|
|
168
|
+
mentionsDesign: ctx.mentionsDesign,
|
|
169
|
+
designSignalCount: ctx.designSignals.length,
|
|
170
|
+
solutionPath,
|
|
171
|
+
hasExplicitPathAnswer: hasAnswer(answers, "solution_path"),
|
|
172
|
+
multiSurface: Boolean(socialWorkplan),
|
|
173
|
+
creativeSelected: Boolean(creativeSelection),
|
|
174
|
+
});
|
|
175
|
+
const platformDetected = inspection.platforms.length > 0;
|
|
176
|
+
const clarify = clarifyGuidance(clarifyDimensions({
|
|
177
|
+
outcome,
|
|
178
|
+
platformDetected,
|
|
179
|
+
uikitGuidedUnknown: platformDetected && outcome === "unknown" && Boolean(uikitCustomization),
|
|
180
|
+
creativeSelected: Boolean(creativeSelection),
|
|
181
|
+
}));
|
|
132
182
|
return {
|
|
133
183
|
outcome,
|
|
134
184
|
platform,
|
|
135
185
|
solutionPath,
|
|
186
|
+
entryState,
|
|
136
187
|
uikitCustomization,
|
|
137
188
|
supportLevel,
|
|
138
189
|
intent: intentFor(request, definition.interpretation, uikitCustomization),
|
|
190
|
+
...(platformAvailability.length > 0 ? { platformAvailability } : {}),
|
|
139
191
|
creativeContext,
|
|
140
192
|
uxHarness: uxHarnessContext,
|
|
141
193
|
socialWorkplan,
|
|
@@ -147,10 +199,12 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
147
199
|
...uxHarnessImplementationSteps(uxHarnessContext),
|
|
148
200
|
...solutionPathImplementationSteps(solutionPath),
|
|
149
201
|
...uikitCustomizationImplementationSteps(uikitCustomization),
|
|
202
|
+
...crossPlatformHandoffSteps,
|
|
150
203
|
...definition.implementationSteps(ctx),
|
|
151
204
|
],
|
|
152
205
|
validation: ["validate_setup", "run_sensors", ...definition.validation(platform)],
|
|
153
|
-
|
|
206
|
+
...(clarify ? { clarify } : {}),
|
|
207
|
+
nextStep: clarify ? clarify.nextStep : nextStepForPlan(outcome, uikitCustomization, solutionPath),
|
|
154
208
|
requiredInputs: composeRequiredInputs(ctx, definition.requiredInputs(ctx)),
|
|
155
209
|
targetFiles: await targetFilesFor(root, outcome, platform, inspection.designSignals),
|
|
156
210
|
implementationRules: [
|
|
@@ -169,11 +223,168 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
169
223
|
stopConditions: composeStopConditions(ctx, definition.stopConditions(ctx), inspection.surfaces, surfacePath, Boolean(socialWorkplan), uikitCustomization),
|
|
170
224
|
evidencePolicy: "Every implementation step must cite at least one detected file, docs page, validator rule, or required user input. If evidence is missing, stop and ask the user instead of inventing details.",
|
|
171
225
|
designContract: acceptedDesignContract ? designContractGuidance(acceptedDesignContract) : undefined,
|
|
226
|
+
brandGrounding: designBrief?.brandGrounding,
|
|
227
|
+
designReviewLoop,
|
|
172
228
|
completenessChecklist: completenessChecklistFor(outcome),
|
|
173
229
|
optionalCapabilities: optionalCapabilitiesFor(outcome, ctx.answers, request, capabilityAvailability),
|
|
230
|
+
engagement: engagementMenuFor(outcome),
|
|
174
231
|
sdkVersion,
|
|
175
232
|
};
|
|
176
233
|
}
|
|
234
|
+
function crossPlatformImplementationSteps(platform, packageJsonText) {
|
|
235
|
+
if (platform === "flutter") {
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
step: "Flutter product-entry pacing handoff: immediately route `lib/main.dart` to a product-native social surface, or create and import a visible product social widget, before broad pub-cache/source-internals research. In bounded dogfood, do not create a shell-first widget; the first social widget should already import `package:amity_sdk/amity_sdk.dart` and contain SDK setup/login plus at least one SDK data API call. Do not stop at static feature-preview cards that merely describe feed/comments/reactions/profile/chat; the surface must render SDK-returned/discovered/created objects, or honestly mark populated proof failed. Do not delete `lib/main.dart` as a standalone operation while preparing a replacement; edit it in place or add a new widget file and import it so interruptions never leave the app without an entry point. Do not spend the implementation budget only on `sp-vise/` artifacts, dependency installs, or reading SDK internals before the running app has a visible social route. If `flutter.sdk.source-used` fires, the app is still a dependency-only/static shell; import/use `package:amity_sdk/amity_sdk.dart` in product source before polishing UI. In dogfood/benchmark work, do not attest `flutter.sdk.source-used`; fix the source, rerun `vise check .`, and remove stale source-used attestations before claiming SDK-backed runtime proof.",
|
|
239
|
+
evidence: [
|
|
240
|
+
"detectedFiles.lib/main.dart",
|
|
241
|
+
"runtimeReadiness.targetDiscovery",
|
|
242
|
+
"validate_setup.flutter.sdk.source-used",
|
|
243
|
+
"implementationSteps",
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
step: "Flutter dependency handoff: when using amity_sdk 7.x, add a direct `dio: 5.9.2` dependency or another bounded `<5.10.0` constraint before Android build proof. amity_sdk 7.x allows `dio ^5.4.0`, and dio 5.10.0 adds `DioExceptionType.transformTimeout`, which can make the SDK switch non-exhaustive during `assembleDebug` even when `flutter analyze` passes.",
|
|
248
|
+
evidence: [
|
|
249
|
+
"validate_setup.flutter.dependency.dio-compatible",
|
|
250
|
+
"pubspec.yaml",
|
|
251
|
+
"pubspec.lock",
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
step: "Flutter runtime-readiness handoff: before any login, feed, message, or live-collection query, call `WidgetsFlutterBinding.ensureInitialized()` and await `AmityCoreClient.setup(option: AmityCoreClientOption.create(apiKey: ..., endpoint: AmityEndpoint.<region>), sycInitialization: true)`. The synchronous flag waits for SDK database adapters such as MessageDbAdapter; without it, builds can pass while runtime fails with `MessageDbAdapter not ready`.",
|
|
256
|
+
evidence: [
|
|
257
|
+
"social-plus-sdk/getting-started/platform-setup/mobile/flutter-quick-start",
|
|
258
|
+
"social-plus-sdk/getting-started/authentication",
|
|
259
|
+
"validate_setup.flutter.setup.sync-initialization",
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
step: "Flutter query handoff: expose a reactive session-ready state from the login owner, seed it from the current SDK/session value, and trigger community/feed/chat queries only when that state becomes ready. Do not rely on `AmityCoreClient.observeSessionState().listen(...)` to emit the initial `NotLoggedIn` state; after subscribing, also kick the idempotent login/session branch immediately from `initState` (for example `Future.microtask(_login)` or an equivalent current-state branch), then let later observer emissions re-run post-login queries. A one-shot `initState` check that returns before login/setup completes, or an observer-only login path that never receives the initial state, leaves the screen stuck on signing-in/empty forever; render setup/login/query errors with a retry affordance.",
|
|
264
|
+
evidence: [
|
|
265
|
+
"social-plus-sdk/getting-started/authentication",
|
|
266
|
+
"social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/flutter",
|
|
267
|
+
"validate_setup.flutter.session.initial-login-kickoff",
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
step: "Flutter populated-proof handoff: do not count a polished empty state as populated runtime proof for community/feed/comments. If discovery returns empty in dogfood, create safe seed data through SDK functions, select the returned community/feed/post in app state, re-query, and render at least one visible community plus one visible post/comment/reaction path. After seed creation, either update UI state from the returned SDK objects or wait for the live collection to emit the created content before handoff; a default screen still showing `0 posts`, `0 comments` on the only visible thread, `syncing`, `waiting on fresh community content`, or a create/prepare failure is not populated proof. If the exact session/coach/squad target cannot be created or populated because of tenant permissions/review mode, fall back to the nearest discovered readable community/feed that already has visible posts only when it passes the product-owned filter below. Do not dump arbitrary readable tenant data into product UI: skip/hide discovered objects whose names or descriptions are raw IDs, numeric-only labels, short random strings such as `rand2`, lorem/test data, `sampleapp` fixtures, or unrelated generated business copy, then keep searching or seed a product-native target. Prefer a product-owned namespace/allowlist such as app-created names, metadata tags, session/program identifiers, or cached IDs returned by the app's own create flow; if the SDK query returns mixed tenant data, render only product-safe matches and never let an unsafe target become the active selected context. Deduplicate discovered/seeded targets by stable SDK ID or normalized product label, and prioritize populated product-owned targets before empty clones; a long rail of repeated zero-post targets is not polished populated proof. Do not rewrite unsafe tenant labels into product copy to make them look acceptable — exclude the object. Apply that filter to every visible rail/list/card and author fallback, not only the selected target. Do not swallow seed/query exceptions silently; record failures in handoff notes and render a product-native retry/error state. If neither seeding nor discovered populated fallback works, record resolved empty-state proof only and do not claim populated proof. Runtime screenshots with yellow/black Flutter overflow banners, red error boxes, clipped action rows, or `BOTTOM OVERFLOWED BY ... PIXELS` are runtime failures; fix layout with bounded cards, Flexible/Expanded/Wrap, maxLines, ellipsis, and responsive spacing before handoff.",
|
|
272
|
+
evidence: [
|
|
273
|
+
"runtimeReadiness.targetDiscovery",
|
|
274
|
+
"runtimeReadiness.surfaces.smokeExpectation",
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
if (platform === "react-native") {
|
|
280
|
+
const rnVersion = reactNativeVersionFromPackage(packageJsonText);
|
|
281
|
+
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
282
|
+
const toolchainNote = rnVersion && compareSemver(rnVersion, "0.82.0") >= 0
|
|
283
|
+
? ` Detected React Native ${rnVersion}; local Node is ${process.version}. Run npm install/build before Social+ work and treat any RN/Gradle/New-Architecture engine warning as benchmark setup friction until the clean baseline also launches.`
|
|
284
|
+
: "";
|
|
285
|
+
const nodeNote = nodeMajor >= 23
|
|
286
|
+
? " Node 23+ is ahead of most React Native LTS guidance; if install/build emits engine warnings, prefer an LTS Node for implementation proof or record the warning separately from Social+ correctness."
|
|
287
|
+
: "";
|
|
288
|
+
return [
|
|
289
|
+
{
|
|
290
|
+
step: "React Native product-entry pacing handoff: immediately route `App.tsx` to a product-native social surface, or create and import a visible product social component, before broad node_modules/source-internals research. In bounded dogfood, do not create a shell-first component; the first social component should already import `@amityco/ts-sdk-react-native` from the package root and contain SDK setup/login plus at least one SDK data API call such as community/feed/comment/reaction retrieval or creation. Do not stop at static feature-preview cards that merely describe feed/comments/reactions/profile/chat; the surface must render SDK-returned/discovered/created objects, or honestly mark populated proof failed. If `react-native.sdk.source-used` fires, the app is still a dependency-only/static shell; import/use the SDK in product source before polishing UI. In dogfood/benchmark work, do not attest `react-native.sdk.source-used`; fix the source, rerun `vise check .`, and remove stale source-used attestations before claiming SDK-backed runtime proof.",
|
|
291
|
+
evidence: [
|
|
292
|
+
"detectedFiles.App.tsx",
|
|
293
|
+
"runtimeReadiness.targetDiscovery",
|
|
294
|
+
"validate_setup.react-native.sdk.source-used",
|
|
295
|
+
"implementationSteps",
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
step: `React Native runtime-readiness handoff: use \`@amityco/ts-sdk-react-native\`, add an SDK-compatible \`@react-native-async-storage/async-storage\` v1.x app dependency (for example \`^1.24.0\`, not latest 2.x/3.x) and \`@react-native-community/netinfo\` as app dependencies so npm dedupes to one native-backed storage package and React Native autolinks the required native modules, initialize with positional region \`Client.createClient(apiKey, API_REGIONS.SG)\` or the app's configured region, retain the session handler for the app lifetime, and call \`renewal.renew()\` from \`sessionWillRenewAccessToken\`. After \`createClient\`, set a stateful \`clientReady\` flag, call \`Client.login(..., sessionHandler)\` from an effect keyed by \`clientReady\` and the runtime user, set an explicit \`authReady\` / \`loggedIn\` state from the login promise resolution, and drive SDK queries from \`authReady\` or a normalized established session helper; do not rely on \`Client.onSessionStateChange\` alone to advance a cold launch. Add bounded retry/error handling when login stays \`notLoggedIn\`, so runtime proof never ends on a permanent spinner. Never deep-import SDK internals like \`@amityco/ts-sdk-react-native/dist/...\`; Metro/release bundling can fail even when TypeScript or Jest passes. Never seed React state by calling \`Client.isConnected()\` before the createClient path has run; return a safe not-ready state until a local \`clientCreated\` / \`clientReady\` flag is true, because \`Client.isConnected()\` can throw \`Amity SDK (800000): There is no active client\` at launch. Do not use the web-only package or an options-object createClient call.${toolchainNote}${nodeNote}`,
|
|
300
|
+
evidence: [
|
|
301
|
+
"social-plus-sdk/getting-started/platform-setup/web/web-quick-start",
|
|
302
|
+
"social-plus-sdk/getting-started/authentication",
|
|
303
|
+
"validate_setup.react-native.dependency.async-storage",
|
|
304
|
+
"validate_setup.react-native.dependency.async-storage-compatible",
|
|
305
|
+
"validate_setup.react-native.dependency.netinfo",
|
|
306
|
+
"validate_setup.react-native.session.renewal",
|
|
307
|
+
"validate_setup.react-native.client.session-state-after-create",
|
|
308
|
+
"validate_setup.react-native.sdk.no-internal-imports",
|
|
309
|
+
"sdkVersion",
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
step: "React Native target-discovery handoff: bridge only local API key/user/region config through ignored native build config; never bridge communityId, postId, channelId, targetId, or feedId. Resolve those from SDK search/list/create results or host navigation/app state, then pass the returned SDK object/id through normal React state. Treat native config bridge and native module output as unproven until a release/debug runtime smoke shows non-empty config, no `NativeModule: AsyncStorage is null` or `NativeModule.RNCNetInfo is null` log, and an established SDK session; a screen that says credentials/config are missing is failed populated proof even if BuildConfig and TypeScript checks pass. Record runtime proof in evidence/notes, not end-user UI: do not render `CLIENT`, `SESSION`, `RUNTIME`, SDK, env, region, setup/readiness-debug labels, visible runtime/probe/status panels, or hidden no-op controls in the product screen.",
|
|
314
|
+
evidence: [
|
|
315
|
+
"requiredInputs.concrete feed target",
|
|
316
|
+
"runtimeReadiness.targetDiscovery",
|
|
317
|
+
"runtimeReadiness.runtimeProof",
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
step: "React Native populated-proof handoff: an empty community/feed screen is not populated proof. If tenant discovery returns empty, create safe dogfood community/post/comment data through SDK calls, keep the returned objects in runtime state, re-query, and make the first runtime screen show at least one selected social target and one visible feed/comment/reaction path. Prefer a product-owned namespace/allowlist such as app-created names, metadata tags, program/session identifiers, or cached IDs returned by the app's own create flow. Filter every visible discovered rail/list/card and author fallback so raw IDs, numeric-only labels, short random strings like `rand2`, lorem/test fixtures, `sampleapp`, or unrelated tenant copy never appear in the product UI; never let an unsafe discovered target become the active selection, and do not rewrite bad labels/descriptions into product copy. Deduplicate discovered/seeded targets by stable SDK ID or normalized product label, and prioritize populated product-owned targets before empty clones. Runtime screenshots with red boxes, clipped action rows, or React Native layout/debug artifacts are runtime failures to repair. If data creation is not possible, label the run as resolved empty-state proof instead of product-proven populated proof.",
|
|
322
|
+
evidence: [
|
|
323
|
+
"runtimeReadiness.targetDiscovery",
|
|
324
|
+
"runtimeReadiness.surfaces.smokeExpectation",
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
if (platform === "ios") {
|
|
330
|
+
return [
|
|
331
|
+
{
|
|
332
|
+
step: "iOS product-entry pacing handoff: immediately route the SwiftUI root view or target UIViewController to a product-native social surface before broad SwiftPM/source-interface research. In bounded dogfood, the first social source edit should import `AmitySDK`, retain the client/session handler, login/register the runtime user, and use at least one real SDK data API such as `AmityCommunityRepository`, `AmityPostRepository`, `AmityUserRepository`, or `AmityChannelRepository`; `AmityClient` initialization plus static social cards is not enough. If `ios.sdk.source-used` fires, the app is still a dependency-only/static social shell; fix product Swift source and rerun `vise check .` before polishing UI or claiming runtime proof.",
|
|
333
|
+
evidence: [
|
|
334
|
+
"runtimeReadiness.targetDiscovery",
|
|
335
|
+
"validate_setup.ios.sdk.source-used",
|
|
336
|
+
"implementationSteps",
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
step: "iOS runtime-readiness handoff: read API key, region, runtime user, and display name from local-only environment or existing host config, never from committed literals. Do not add communityId, postId, channelId, targetId, or feedId to Info.plist/xcconfig/env; discover or create those targets through SDK queries/create flows, store returned objects in app state, and keep VISE_SMOKE markers log-only. For populated proof, the launched social route must show SDK-backed product-safe community/feed/profile/chat data, not static fallback cards or setup/debug labels.",
|
|
341
|
+
evidence: [
|
|
342
|
+
"runtimeReadiness.targetDiscovery",
|
|
343
|
+
"runtimeReadiness.runtimeProof",
|
|
344
|
+
"runtimeReadiness.surfaces.smokeExpectation",
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
function reactNativeVersionFromPackage(packageJsonText) {
|
|
352
|
+
if (!packageJsonText)
|
|
353
|
+
return undefined;
|
|
354
|
+
try {
|
|
355
|
+
const pkg = JSON.parse(packageJsonText);
|
|
356
|
+
const raw = pkg.dependencies?.["react-native"] ?? pkg.devDependencies?.["react-native"];
|
|
357
|
+
if (!raw)
|
|
358
|
+
return undefined;
|
|
359
|
+
const match = String(raw).match(/(\d+\.\d+\.\d+)/);
|
|
360
|
+
return match?.[1];
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function compareSemver(a, b) {
|
|
367
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
368
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
369
|
+
for (let i = 0; i < 3; i += 1) {
|
|
370
|
+
if (pa[i] !== pb[i])
|
|
371
|
+
return pa[i] - pb[i];
|
|
372
|
+
}
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
function engagementMenuFor(outcome) {
|
|
376
|
+
const baselineLevers = capabilityChecklist(outcome).map((capability) => capability.id);
|
|
377
|
+
const optionalLevers = optionalCapabilityChecklist(outcome).map((capability) => capability.id);
|
|
378
|
+
if (baselineLevers.length === 0 && optionalLevers.length === 0) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
note: "Engagement levers for this surface (advisory). Build the baseline for completeness; treat the optional levers as the menu that makes the experience compelling — don't ship the bare minimum (e.g. a text-only feed with no reactions or polls).",
|
|
383
|
+
baselineLevers,
|
|
384
|
+
optionalLevers,
|
|
385
|
+
deepenWith: 'Run `vise creative . --request "<intent>"` for Engagement Intelligence (candidate solutions, engagement options, and tradeoffs for this surface) before committing to scope.',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
177
388
|
function completenessChecklistFor(outcome) {
|
|
178
389
|
const items = capabilityChecklist(outcome);
|
|
179
390
|
if (items.length === 0) {
|
|
@@ -292,13 +503,23 @@ function supportLevelForPlan(outcome, platform, baseSupportLevel, uikitCustomiza
|
|
|
292
503
|
return baseSupportLevel;
|
|
293
504
|
}
|
|
294
505
|
function nextStepForPlan(outcome, uikitCustomization, solutionPath) {
|
|
506
|
+
const ENGAGEMENT_HINT = " For engagement options (post types, polls, reactions, conversation and following patterns, and other ways to deepen the experience), run `vise creative . --request \"<intent>\"` before building — it surfaces levers `vise plan` lists only tersely under `optionalCapabilities`/`engagement`.";
|
|
507
|
+
const engagementHint = outcome === "add-feed" ||
|
|
508
|
+
outcome === "add-community" ||
|
|
509
|
+
outcome === "add-comments" ||
|
|
510
|
+
outcome === "add-chat" ||
|
|
511
|
+
outcome === "add-follow" ||
|
|
512
|
+
outcome === "add-notifications"
|
|
513
|
+
? ENGAGEMENT_HINT
|
|
514
|
+
: "";
|
|
295
515
|
if (outcome === "unknown" && uikitCustomization) {
|
|
296
|
-
|
|
516
|
+
const uikitEngagementHint = solutionPath.decision.requiredBeforeHandRolledUi ? ENGAGEMENT_HINT : "";
|
|
517
|
+
return "Treat this as UIKit customization guidance first: resolve the UIKit route and concrete app/platform target, then implement customer-owned config, localization, behavior, fork, or SDK custom UI changes with local build evidence. Do not claim `vise check` deterministically validates hidden UIKit internals." + uikitEngagementHint;
|
|
297
518
|
}
|
|
298
519
|
if (solutionPath.decision.requiredBeforeHandRolledUi) {
|
|
299
|
-
return "First resolve the solution-path decision (UIKit vs SDK vs hybrid). The SDK implementation steps above apply only if you build this surface directly with the SDK (or the SDK side of a hybrid); if the customer uses social.plus UIKit for the standard surface, follow the UIKit guidance instead of hand-rolling those steps. Once the route is settled, implement the applicable steps, then run `vise check .` and fix findings until green — you are not done until the check passes or each finding is explicitly attested.";
|
|
520
|
+
return "First resolve the solution-path decision (UIKit vs SDK vs hybrid). The SDK implementation steps above apply only if you build this surface directly with the SDK (or the SDK side of a hybrid); if the customer uses social.plus UIKit for the standard surface, follow the UIKit guidance instead of hand-rolling those steps. Once the route is settled, implement the applicable steps, then run `vise check .` and fix findings until green — you are not done until the check passes or each finding is explicitly attested." + engagementHint;
|
|
300
521
|
}
|
|
301
|
-
return "After implementing every step above, run `vise check .` and fix findings until green. You are not done until the check passes or each finding is explicitly attested.";
|
|
522
|
+
return "After implementing every step above, run `vise check .` and fix findings until green. You are not done until the check passes or each finding is explicitly attested." + engagementHint;
|
|
302
523
|
}
|
|
303
524
|
function dedupeDocsByPath(docs) {
|
|
304
525
|
const seen = new Set();
|
|
@@ -434,6 +655,12 @@ const SOCIAL_SURFACE_SEQUENCE = [
|
|
|
434
655
|
aliases: [/\b(feed|timeline|news feed|post list|posts?|create post|post creation|compose post|composer|reactions?)\b/i],
|
|
435
656
|
defaultForGenericBroad: true,
|
|
436
657
|
},
|
|
658
|
+
{
|
|
659
|
+
id: "livestream",
|
|
660
|
+
outcome: "setup-live-data",
|
|
661
|
+
label: "Live room and broadcast",
|
|
662
|
+
aliases: [/\b(live\s?stream(?:ing)?|live\s+video|live\s+room|broadcast(?:ing)?|go(?:ing)?\s+live)\b/i],
|
|
663
|
+
},
|
|
437
664
|
{
|
|
438
665
|
id: "comments",
|
|
439
666
|
outcome: "add-comments",
|
|
@@ -472,20 +699,17 @@ async function socialWorkplanFor(args) {
|
|
|
472
699
|
if (hasAnswer(args.answers, "feature_surface")) {
|
|
473
700
|
return undefined;
|
|
474
701
|
}
|
|
702
|
+
const experience = experienceBundleFor(args.request);
|
|
475
703
|
const creativeHints = args.creativeSelection ? creativeSurfaceHints(args.creativeSelection) : [];
|
|
476
704
|
const useCreativeSequence = creativeHints.length > 1;
|
|
477
|
-
if (!
|
|
705
|
+
if (!useCreativeSequence) {
|
|
478
706
|
return undefined;
|
|
479
707
|
}
|
|
480
708
|
const matched = SOCIAL_SURFACE_SEQUENCE.filter((surface) => surface.aliases.some((pattern) => pattern.test(args.request)));
|
|
481
|
-
const selected =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
})
|
|
486
|
-
: (matched.length > 0
|
|
487
|
-
? matched
|
|
488
|
-
: SOCIAL_SURFACE_SEQUENCE.filter((surface) => surface.defaultForGenericBroad)).map((surface) => ({ surface, creativeHint: undefined }));
|
|
709
|
+
const selected = creativeHints.flatMap((hint) => {
|
|
710
|
+
const surface = SOCIAL_SURFACE_SEQUENCE.find((item) => item.id === hint.id);
|
|
711
|
+
return surface ? [{ surface, creativeHint: hint }] : [];
|
|
712
|
+
});
|
|
489
713
|
if (selected.length <= 1) {
|
|
490
714
|
return undefined;
|
|
491
715
|
}
|
|
@@ -505,8 +729,10 @@ async function socialWorkplanFor(args) {
|
|
|
505
729
|
}),
|
|
506
730
|
broadSocialRequest: false,
|
|
507
731
|
};
|
|
508
|
-
const intake = intakeFor(surfaceCtx, definition.intakeQuestions(surfaceCtx), surface.outcome, args.designBrief, capabilityAvailability, args.designReview);
|
|
732
|
+
const intake = intakeFor(surfaceCtx, definition.intakeQuestions(surfaceCtx), surface.outcome, args.designBrief, capabilityAvailability, args.designReview, [DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID]);
|
|
509
733
|
const matchedPrompt = matched.some((match) => match.id === surface.id);
|
|
734
|
+
const companionEntry = experience && !matchedPrompt ? experience.surfaces.find((entry) => entry.id === surface.id) : undefined;
|
|
735
|
+
const companion = companionEntry ? { tier: companionEntry.tier, viaExperience: experience.id } : undefined;
|
|
510
736
|
const commonArgs = `. --request ${shellQuote(args.request)} --answer feature_surface=${surface.id}`;
|
|
511
737
|
sequence.push({
|
|
512
738
|
id: surface.id,
|
|
@@ -514,9 +740,12 @@ async function socialWorkplanFor(args) {
|
|
|
514
740
|
outcome: surface.outcome,
|
|
515
741
|
label: surface.label,
|
|
516
742
|
matchedPrompt,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
743
|
+
...(companion ? { companion } : {}),
|
|
744
|
+
reason: companion
|
|
745
|
+
? `Included to make the ${companion.viaExperience} experience coherent (${companion.tier}); confirm or trim at the blueprint sign-off.`
|
|
746
|
+
: creativeHint?.reason ?? (matchedPrompt
|
|
747
|
+
? `The request explicitly mentions ${surface.label.toLowerCase()}.`
|
|
748
|
+
: "The request is generic broad social work, so Vise includes this core surface in the default sequence."),
|
|
520
749
|
planCommand: `vise plan ${commonArgs}`,
|
|
521
750
|
initCommand: `vise init ${commonArgs}`,
|
|
522
751
|
intake: {
|
|
@@ -537,8 +766,8 @@ async function socialWorkplanFor(args) {
|
|
|
537
766
|
kind: "social-multi-surface",
|
|
538
767
|
status,
|
|
539
768
|
note: useCreativeSequence
|
|
540
|
-
? "This accepted creative variant is decomposed into ordered per-surface plans from its experience objects. Initialize and check one surface at a time so each sidecar has one deterministic outcome, while using this sequence as the coordinated product workplan."
|
|
541
|
-
: "This broad social request is decomposed into ordered per-surface plans. Initialize and check one surface at a time so each sidecar has one outcome, while using this sequence as the coordinated product workplan.",
|
|
769
|
+
? "This accepted creative variant is decomposed into ordered per-surface plans from its experience objects. Initialize and check one surface at a time so each sidecar has one deterministic outcome, while using this sequence as the coordinated product workplan. Do not skip ahead to a later surface; after blueprint sign-off `vise init` enforces the next pending surface from `vise workplan next/status`."
|
|
770
|
+
: "This broad social request is decomposed into ordered per-surface plans. Initialize and check one surface at a time so each sidecar has one outcome, while using this sequence as the coordinated product workplan. Do not skip ahead to a later surface; after blueprint sign-off `vise init` enforces the next pending surface from `vise workplan next/status`.",
|
|
542
771
|
creativeContext: args.creativeSelection
|
|
543
772
|
? {
|
|
544
773
|
selectedVariant: creativeContextForPlan(args.creativeSelection).selectedVariant,
|
|
@@ -547,7 +776,7 @@ async function socialWorkplanFor(args) {
|
|
|
547
776
|
: undefined,
|
|
548
777
|
uxHarness: args.uxHarness ? uxHarnessPlanContext(args.uxHarness) : undefined,
|
|
549
778
|
sequence,
|
|
550
|
-
nextStep: "Resolve
|
|
779
|
+
nextStep: "Run `vise workplan next/status` and implement only the next pending surface it prints. Resolve that surface's blocking questions, run its listed plan/init command, implement it, then run `vise check .`, `vise sync .`, `vise validate .`, `vise run-sensors .`, and `vise workplan complete` before moving to the next surface.",
|
|
551
780
|
};
|
|
552
781
|
}
|
|
553
782
|
function shellQuote(value) {
|
|
@@ -641,74 +870,18 @@ function intentFor(request, interpretation, uikitCustomization) {
|
|
|
641
870
|
ambiguity: broadSocialRequest || designRequest || uikitCustomization?.status === "needs-decision" || uikitOnlyCustomization ? "high" : "medium",
|
|
642
871
|
};
|
|
643
872
|
}
|
|
644
|
-
function intakeFor(ctx, outcomeQuestions, outcome, brief, availability, designReview) {
|
|
645
|
-
const questions =
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
if (designReview?.status === "rejected" && !hasAnswer(ctx.answers, "design_source")) {
|
|
657
|
-
questions.push({
|
|
658
|
-
id: "design_source",
|
|
659
|
-
question: "What design source should replace the rejected preview/contract?",
|
|
660
|
-
why: "The current design preview was rejected, so Vise must not use it. Provide a prototype, screenshot, theme/token file, or explicitly say to proceed without a design-conformance claim.",
|
|
661
|
-
required: true,
|
|
662
|
-
blocksImplementationWhenMissing: true,
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
if (ctx.mentionsDesign && ctx.designSignals.length === 0 && !hasAnswer(ctx.answers, "design_source")) {
|
|
666
|
-
questions.push({
|
|
667
|
-
id: "design_source",
|
|
668
|
-
question: "Where are the app's design tokens, theme, or reusable UI components defined?",
|
|
669
|
-
why: "The user asked to match the existing design, and no local design source was detected.",
|
|
670
|
-
required: true,
|
|
671
|
-
blocksImplementationWhenMissing: true,
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
if (ctx.mentionsDesign && ctx.designSignals.length > 0 && !hasAnswer(ctx.answers, "confirm_design_source")) {
|
|
675
|
-
questions.push({
|
|
676
|
-
id: "confirm_design_source",
|
|
677
|
-
question: `Should the social UI use the detected design source(s): ${ctx.designSignals.map((signal) => signal.file).join(", ")}?`,
|
|
678
|
-
why: "Vise found likely design evidence, but the user or host agent should confirm it is the right source before UI edits.",
|
|
679
|
-
required: true,
|
|
680
|
-
blocksImplementationWhenMissing: false,
|
|
681
|
-
options: ["yes", "use another source"],
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
if (brief &&
|
|
685
|
-
designReview?.status === "accepted" &&
|
|
686
|
-
(outcome === "add-feed" || outcome === "add-chat") &&
|
|
687
|
-
!brief.roles.some((r) => r.role === "primaryAction") &&
|
|
688
|
-
!hasAnswer(ctx.answers, "primary_action_token")) {
|
|
689
|
-
questions.push({
|
|
690
|
-
id: "primary_action_token",
|
|
691
|
-
question: "Which design token (or color value) should be used as the primary action color? No primary-action token was confidently identified in the design contract.",
|
|
692
|
-
why: "A primary action colour is needed for interactive elements (composer button, own-message bubble). Without a confident token, the agent must guess or omit it.",
|
|
693
|
-
required: false,
|
|
694
|
-
blocksImplementationWhenMissing: false,
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
const availableOptionalIds = availableOptionalCapabilityIds(availability);
|
|
698
|
-
const optionalChoices = outcome === "add-feed" ? optionalCapabilityChecklist(outcome, availableOptionalIds) : [];
|
|
699
|
-
if (outcome === "add-feed" && optionalChoices.length > 0 && !hasAnswer(ctx.answers, "feed_optional_capabilities")) {
|
|
700
|
-
questions.push({
|
|
701
|
-
id: "feed_optional_capabilities",
|
|
702
|
-
question: `Which optional feed capabilities should be in scope on ${ctx.platform}: ${optionalChoices.map((capability) => capability.id).join(", ")}, or none?`,
|
|
703
|
-
why: "These capabilities are useful for full feeds, but should become enforceable only after the customer explicitly opts in. Vise only lists capabilities that appear available in the platform SDK surface; unsupported capabilities must be treated as out of scope or confirmed from docs.",
|
|
704
|
-
required: false,
|
|
705
|
-
blocksImplementationWhenMissing: false,
|
|
706
|
-
options: ["none", ...optionalChoices.map((capability) => capability.id)],
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
const remainingBlocking = questions.filter((question) => question.blocksImplementationWhenMissing).length;
|
|
873
|
+
function intakeFor(ctx, outcomeQuestions, outcome, brief, availability, designReview, suppressGateIds) {
|
|
874
|
+
const { questions, remainingBlocking, status } = assembleIntake({
|
|
875
|
+
ctx,
|
|
876
|
+
outcomeQuestions,
|
|
877
|
+
outcome,
|
|
878
|
+
designBrief: brief,
|
|
879
|
+
capabilityAvailability: availability,
|
|
880
|
+
designReview,
|
|
881
|
+
suppressGateIds,
|
|
882
|
+
});
|
|
710
883
|
return {
|
|
711
|
-
status
|
|
884
|
+
status,
|
|
712
885
|
questions,
|
|
713
886
|
answers: ctx.answers,
|
|
714
887
|
remainingBlocking,
|
package/dist/tools/learning.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { sidecarDir } from "../sidecar.js";
|
|
3
4
|
import { objectInput, optionalBooleanField, optionalStringField, stringField, textResult } from "../types.js";
|
|
4
5
|
import { packageVersion } from "../version.js";
|
|
5
6
|
import { readCreativeSelection } from "./creative.js";
|
|
@@ -475,9 +476,6 @@ async function writeLearningSummary(repoRoot, summary) {
|
|
|
475
476
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
476
477
|
await writeFile(filePath, `${JSON.stringify(summary, null, 2)}\n`, "utf8");
|
|
477
478
|
}
|
|
478
|
-
function sidecarDir(repoRoot) {
|
|
479
|
-
return path.join(repoRoot, "sp-vise");
|
|
480
|
-
}
|
|
481
479
|
function privacyBlock() {
|
|
482
480
|
return {
|
|
483
481
|
mode: "local-only",
|