@amityco/social-plus-vise 0.14.16 → 0.14.18

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 CHANGED
@@ -4,6 +4,27 @@ All notable changes to `@amityco/social-plus-vise` are documented in this file.
4
4
 
5
5
  The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.14.18 — 2026-06-05
8
+
9
+ ### Changed
10
+ - **Broader product-first rule surface:** feed target resolution, feed UI states, feed pagination, UGC moderation affordances, post status filtering, opaque pagination cursors, and server-synced unread counts now surface through shared product expectation IDs while preserving the exact platform `contractRuleId`/`validator.sensorId` evidence.
11
+ - **Capability feed-forward expansion:** the promoted feed/data expectations now participate in platform capability preflight where the bundled SDK surface proves support, so plans can show product-level expectations before implementation rather than only after `vise check`.
12
+ - **Validation checklist clarity:** add-feed, add-comments, and add-chat plans now use product-level expectation IDs for the promoted shared behaviors instead of exposing platform-specific validator names as the primary checklist items.
13
+
14
+ ### Verified
15
+ - Focused CLI, fixture, MCP, product-flow, agent-flow, capability, native-idiom, and rule-coverage suites passed. A local iOS `whoops` plan smoke confirmed the broader product ids with iOS sensor evidence.
16
+
17
+ ## 0.14.17 — 2026-06-05
18
+
19
+ ### Changed
20
+ - **Product-first plan rules:** `vise plan` now shows shared product expectation IDs as primary `applicableRules[].rule_id` values when a rule is backed by platform-specific sensors, while preserving the exact `contract_rule_id`, `contract_rule_digest`, and `validator.sensorId` evidence needed for debugging and attestations.
21
+
22
+ ### Fixed
23
+ - **Design rejection flow coverage:** the host-agent smoke now locks the behavior where `design_contract_confirmation=no` rejects the preview, withholds the design contract from feed-forward, asks for a replacement design source, and blocks `vise init` until that source is resolved.
24
+
25
+ ### Verified
26
+ - Full `npm run validate` passed. A local iOS `whoops` plan smoke confirmed shared chat ids are public while iOS sensor ids remain as validator evidence.
27
+
7
28
  ## 0.14.16 — 2026-06-05
8
29
 
9
30
  ### Changed
package/README.md CHANGED
@@ -161,16 +161,16 @@ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional ca
161
161
 
162
162
  ### Current Release Validation
163
163
 
164
- Version 0.14.12 adds current release proof around the full feed-forward and validation flow:
164
+ Version 0.14.18 carries current release proof around the full feed-forward, product-expectation, and validation flow:
165
165
 
166
166
  | Surface | What was validated |
167
167
  |---|---|
168
168
  | **Product flow** | Local end-to-end smoke covers design extraction, plan feed-forward, blocking intake, answered init, capability check, design conformance, and sensor discovery. |
169
169
  | **Plan questions** | Plans surface blocking questions such as `feature_surface` and `design_contract_confirmation`, plus optional choices such as `feed_optional_capabilities`. |
170
170
  | **Capability-to-sensor flow** | Vise checks platform support, matches the prompt to available capabilities, offers supported features as questions, records answers, and turns selected answers into sensors in `vise check`. |
171
- | **Shared product expectations** | Public IDs such as `comments.thread-read-write` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence. |
171
+ | **Shared product expectations** | Public IDs such as `feed.target-resolved`, `moderation.affordance-present`, `comments.thread-read-write`, and `chat.unread-visible` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence. |
172
172
  | **Rule detection** | TP-track dashboard detects **311/311 seeded rule gaps (100.0%)** in the static corpus. |
173
- | **Packed-package smoke** | A real Antigravity agent smoke tested the 0.14.12 tarball, opted into surfaced plan questions, repaired selected optional poll capability sensors, and verified ambiguous shared comment attestations require exact contract rule IDs. |
173
+ | **Packed-package smoke** | Packed-package and host-agent smokes exercise the release tarball path, surfaced plan questions, selected optional capability sensors, rejected design confirmation handling, and exact contract-rule evidence for shared product expectations. |
174
174
 
175
175
  ### Supporting Proof
176
176
 
@@ -427,6 +427,104 @@ export const OPTIONAL_CAPABILITIES = [
427
427
  },
428
428
  ];
429
429
  export const SHARED_PRODUCT_EXPECTATIONS = [
430
+ {
431
+ id: "feed.target-resolved",
432
+ label: "Resolved feed target",
433
+ outcomes: ["add-feed"],
434
+ kind: "shared-expectation",
435
+ availability: [
436
+ {
437
+ label: "SDK feed target/query APIs",
438
+ symbols: [/\btargetId\b/i, /\btargetType\b/i, /\bgetUserFeed\b/i, /\bgetCommunityFeed\b/i, /\bgetGlobalFeed\b/i, /\bgetPosts\b/i, /\bqueryPosts\b/i],
439
+ },
440
+ ],
441
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
442
+ hint: "bind targetType and targetId to route params, auth context, or customer-owned selection state; do not invent literals",
443
+ },
444
+ {
445
+ id: "feed.ui-states-present",
446
+ label: "Feed loading/empty/error states",
447
+ outcomes: ["add-feed"],
448
+ kind: "shared-expectation",
449
+ availability: [
450
+ {
451
+ label: "SDK feed collection/query APIs",
452
+ symbols: [/\bgetUserFeed\b/i, /\bgetCommunityFeed\b/i, /\bgetGlobalFeed\b/i, /\bgetPosts\b/i, /\bqueryPosts\b/i],
453
+ },
454
+ ],
455
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
456
+ hint: "render loading, empty, error, and data states around the feed collection instead of only the success path",
457
+ },
458
+ {
459
+ id: "feed.pagination-wired",
460
+ label: "Feed pagination",
461
+ outcomes: ["add-feed"],
462
+ kind: "shared-expectation",
463
+ availability: [
464
+ {
465
+ label: "SDK pagination APIs",
466
+ symbols: [/\bnextPage\b/i, /\bnextPageToken\b/i, /\bloadMore\b/i, /\bonNextPage\b/i, /\bhasNext\b/i],
467
+ },
468
+ ],
469
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
470
+ hint: "wire load-more or automatic paging to the SDK collection; a fixed first page is incomplete",
471
+ },
472
+ {
473
+ id: "moderation.affordance-present",
474
+ label: "UGC moderation affordance",
475
+ outcomes: ["add-feed", "add-comments", "add-chat"],
476
+ kind: "shared-expectation",
477
+ availability: [
478
+ {
479
+ label: "SDK moderation/reporting APIs",
480
+ symbols: [/\bflagPost\b/i, /\bflagComment\b/i, /\bflagMessage\b/i, /\breport\b/i, /\bmoderation\b/i, /\bblockUser\b/i],
481
+ },
482
+ ],
483
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
484
+ hint: "show report/flag/block/hide affordances on user-generated content, scoped to the surface being built",
485
+ },
486
+ {
487
+ id: "posts.status-filtered",
488
+ label: "Post status filtering",
489
+ outcomes: ["add-feed"],
490
+ kind: "shared-expectation",
491
+ availability: [
492
+ {
493
+ label: "SDK post status/filter APIs",
494
+ symbols: [/\bstatus\b/i, /\bstatuses\b/i, /\bfeedType\b/i, /\bpublished\b/i, /\bincludeDeleted\b/i],
495
+ },
496
+ ],
497
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
498
+ hint: "filter post queries to published/non-deleted content so moderated or deleted posts do not leak into normal feeds",
499
+ },
500
+ {
501
+ id: "pagination.cursor-opaque",
502
+ label: "Opaque pagination cursors",
503
+ outcomes: ["add-feed", "add-comments", "add-chat"],
504
+ kind: "shared-expectation",
505
+ availability: [
506
+ {
507
+ label: "SDK cursor pagination APIs",
508
+ symbols: [/\bnextPage\b/i, /\bnextPageToken\b/i, /\bloadMore\b/i, /\bonNextPage\b/i, /\bhasNext\b/i],
509
+ },
510
+ ],
511
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
512
+ hint: "pass SDK-provided cursor tokens or call SDK next-page helpers; never compute numeric page offsets",
513
+ },
514
+ {
515
+ id: "unread.server-synced",
516
+ label: "Server-synced unread counts",
517
+ outcomes: ["add-feed", "add-chat"],
518
+ kind: "shared-expectation",
519
+ availability: [
520
+ {
521
+ label: "SDK unread state",
522
+ symbols: [/\bunreadCount\b/i, /\bChannelUnread\b/i, /\bobserveUserUnread\b/i, /\bgetUnread\w*\b/i, /\bgetTotalChannelUnread\b/i],
523
+ },
524
+ ],
525
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
526
+ hint: "source unread badges from the SDK stream instead of counting stale local arrays",
527
+ },
430
528
  {
431
529
  id: "feed.rich-post-rendering",
432
530
  label: "Rich post rendering",
package/dist/outcomes.js CHANGED
@@ -653,7 +653,13 @@ const addFeed = {
653
653
  validation: (platform) => [
654
654
  "feed target identified",
655
655
  "no invented communityId/targetId/feedId",
656
- `${platform}.feed.target.literal`,
656
+ "feed.target-resolved",
657
+ "feed.ui-states-present",
658
+ "feed.pagination-wired",
659
+ "moderation.affordance-present",
660
+ "posts.status-filtered",
661
+ "pagination.cursor-opaque",
662
+ "unread.server-synced",
657
663
  "feed.rich-post-rendering",
658
664
  "feed.rich-post-composer-scope",
659
665
  "comments.thread-read-write",
@@ -789,7 +795,8 @@ const addComments = {
789
795
  "no invented postId/commentId",
790
796
  `${platform}.comments.target-resolved`,
791
797
  "comments.thread-read-write",
792
- `${platform}.comments.moderation-affordance-present`,
798
+ "moderation.affordance-present",
799
+ "pagination.cursor-opaque",
793
800
  ],
794
801
  stopConditions: (ctx) => filterStops(ctx.answers, [
795
802
  { id: "comment_target", text: "The comment target entity is unknown; do not invent postId, commentId, or parent entity references." },
@@ -1021,7 +1028,9 @@ const addChat = {
1021
1028
  `${platform}.chat.channel-target-resolved`,
1022
1029
  `${platform}.chat.message-observer-cleanup`,
1023
1030
  `${platform}.chat.send-error-handling`,
1024
- `${platform}.chat.moderation-affordance-present`,
1031
+ "moderation.affordance-present",
1032
+ "unread.server-synced",
1033
+ "pagination.cursor-opaque",
1025
1034
  "chat.unread-visible",
1026
1035
  ...(platform === "android" || platform === "flutter" || platform === "ios" ? ["chat.message-order-explicit"] : []),
1027
1036
  ],
@@ -1,4 +1,93 @@
1
+ export const PRODUCT_EXPECTATION_TITLES = {
2
+ "feed.target-resolved": "Feed target comes from app state",
3
+ "feed.ui-states-present": "Feed renders loading, empty, and error states",
4
+ "feed.pagination-wired": "Feed pagination is wired",
5
+ "moderation.affordance-present": "UGC moderation affordance is present",
6
+ "posts.status-filtered": "Post queries filter unsafe statuses",
7
+ "pagination.cursor-opaque": "Pagination cursors stay opaque",
8
+ "unread.server-synced": "Unread counts use the server-synced stream",
9
+ "feed.rich-post-rendering": "Feed renders rich post types",
10
+ "feed.rich-post-composer-scope": "Feed composer surfaces rich post scope",
11
+ "comments.thread-read-write": "Comment threads support reading and creation",
12
+ "chat.unread-visible": "Chat unread counts are visible",
13
+ "chat.message-order-explicit": "Chat message order is explicit",
14
+ "profile.social-counts": "Profile social counts come from the SDK",
15
+ };
16
+ const platformBindings = (expectationId, sensorsByPlatform) => Object.entries(sensorsByPlatform).flatMap(([platform, sensors]) => (Array.isArray(sensors) ? sensors : [sensors]).map((sensorId) => ({
17
+ expectationId,
18
+ sensorId,
19
+ platform,
20
+ })));
1
21
  export const PRODUCT_EXPECTATION_BINDINGS = [
22
+ ...platformBindings("feed.target-resolved", {
23
+ typescript: ["typescript.feed.target.literal", "typescript.feed.target-type-explicit"],
24
+ "react-native": ["react-native.feed.target.literal", "react-native.feed.target-type-explicit"],
25
+ android: ["android.feed.target.literal", "android.feed.target-type-explicit"],
26
+ flutter: ["flutter.feed.target.literal", "flutter.feed.target-type-explicit"],
27
+ ios: ["ios.feed.target.literal", "ios.feed.target-type-explicit"],
28
+ }),
29
+ ...platformBindings("feed.ui-states-present", {
30
+ typescript: "typescript.feed.ui-states-present",
31
+ "react-native": "react-native.feed.ui-states-present",
32
+ android: "android.feed.ui-states-present",
33
+ flutter: "flutter.feed.ui-states-present",
34
+ ios: "ios.feed.ui-states-present",
35
+ }),
36
+ ...platformBindings("feed.pagination-wired", {
37
+ typescript: "typescript.feed.pagination-wired",
38
+ "react-native": "react-native.feed.pagination-wired",
39
+ android: "android.feed.pagination-wired",
40
+ flutter: "flutter.feed.pagination-wired",
41
+ ios: "ios.feed.pagination-wired",
42
+ }),
43
+ ...platformBindings("moderation.affordance-present", {
44
+ typescript: [
45
+ "typescript.feed.moderation-affordance-present",
46
+ "typescript.comments.moderation-affordance-present",
47
+ "typescript.chat.moderation-affordance-present",
48
+ ],
49
+ "react-native": [
50
+ "react-native.feed.moderation-affordance-present",
51
+ "react-native.comments.moderation-affordance-present",
52
+ "react-native.chat.moderation-affordance-present",
53
+ ],
54
+ android: [
55
+ "android.feed.moderation-affordance-present",
56
+ "android.comments.moderation-affordance-present",
57
+ "android.chat.moderation-affordance-present",
58
+ ],
59
+ flutter: [
60
+ "flutter.feed.moderation-affordance-present",
61
+ "flutter.comments.moderation-affordance-present",
62
+ "flutter.chat.moderation-affordance-present",
63
+ ],
64
+ ios: [
65
+ "ios.feed.moderation-affordance-present",
66
+ "ios.comments.moderation-affordance-present",
67
+ "ios.chat.moderation-affordance-present",
68
+ ],
69
+ }),
70
+ ...platformBindings("posts.status-filtered", {
71
+ typescript: "typescript.posts.status-filter-applied",
72
+ "react-native": "react-native.posts.status-filter-applied",
73
+ android: "android.posts.status-filter-applied",
74
+ flutter: "flutter.posts.status-filter-applied",
75
+ ios: "ios.posts.status-filter-applied",
76
+ }),
77
+ ...platformBindings("pagination.cursor-opaque", {
78
+ typescript: "typescript.pagination.cursor-opaque",
79
+ "react-native": "react-native.pagination.cursor-opaque",
80
+ android: "android.pagination.cursor-opaque",
81
+ flutter: "flutter.pagination.cursor-opaque",
82
+ ios: "ios.pagination.cursor-opaque",
83
+ }),
84
+ ...platformBindings("unread.server-synced", {
85
+ typescript: "typescript.unread.subscribed-not-counted",
86
+ "react-native": "react-native.unread.subscribed-not-counted",
87
+ android: "android.unread.subscribed-not-counted",
88
+ flutter: "flutter.unread.subscribed-not-counted",
89
+ ios: "ios.unread.subscribed-not-counted",
90
+ }),
2
91
  {
3
92
  expectationId: "feed.rich-post-rendering",
4
93
  sensorId: "typescript.feed.post-datatype-handled",
@@ -266,6 +355,9 @@ export function productFindingIdentity(sensorId) {
266
355
  export function publicProductRuleId(ruleId) {
267
356
  return productExpectationBindingForSensor(ruleId)?.expectationId ?? ruleId;
268
357
  }
358
+ export function productExpectationTitle(expectationId) {
359
+ return PRODUCT_EXPECTATION_TITLES[expectationId];
360
+ }
269
361
  export function findingMatchesId(finding, id) {
270
362
  return finding.ruleId === id || finding.sensorId === id;
271
363
  }
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { assessProjectCompleteness, assessProjectSelectedOptionalCapabilities, availableOptionalCapabilityIds, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, } from "../capabilities.js";
6
6
  import { getOutcomeDefinition, hasAnswer, planContextFor, resolveOutcome, } from "../outcomes.js";
7
- import { contractRuleCandidatesForPublicId, hasMultipleContractRuleCandidates, productExpectationBindingForSensor, publicProductRuleId, } from "../productExpectations.js";
7
+ import { contractRuleCandidatesForPublicId, hasMultipleContractRuleCandidates, productExpectationBindingForSensor, productExpectationTitle, publicProductRuleId, } from "../productExpectations.js";
8
8
  import { objectInput, optionalBooleanField, optionalStringField, stringField, textResult } from "../types.js";
9
9
  import { packageVersion } from "../version.js";
10
10
  import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, } from "./design.js";
@@ -476,6 +476,9 @@ function preferredPlatform(platforms) {
476
476
  export async function applicableComplianceRuleSummaries(outcome, platforms) {
477
477
  return (await applicableRules(outcome, platforms)).map(ruleRefForFile);
478
478
  }
479
+ export async function applicableCompliancePlanRuleSummaries(outcome, platforms) {
480
+ return (await applicableRules(outcome, platforms)).map(ruleRefForPlan);
481
+ }
479
482
  export async function checkCompliance(repoPath) {
480
483
  const repoRoot = path.resolve(repoPath);
481
484
  const compliance = await readCompliance(repoRoot);
@@ -932,6 +935,28 @@ function ruleRefForFile(rule) {
932
935
  title: rule.title,
933
936
  };
934
937
  }
938
+ function ruleRefForPlan(rule) {
939
+ const publicRuleId = publicProductRuleId(rule.id);
940
+ const base = ruleRefForFile(rule);
941
+ if (publicRuleId === rule.id) {
942
+ return base;
943
+ }
944
+ const binding = productExpectationBindingForSensor(rule.id);
945
+ return {
946
+ ...base,
947
+ rule_id: publicRuleId,
948
+ public_rule_id: publicRuleId,
949
+ title: productExpectationTitle(publicRuleId) ?? rule.title,
950
+ contract_rule_id: rule.id,
951
+ contract_rule_digest: base.rule_digest,
952
+ validator: binding
953
+ ? {
954
+ platform: binding.platform,
955
+ sensorId: binding.sensorId,
956
+ }
957
+ : undefined,
958
+ };
959
+ }
935
960
  // Benchmark-measured friction: agents looped on attest dialect for ~25 min/cell when docs and SDK
936
961
  // disagreed on exact invocation syntax (capability-matrix 2026-06, Row 5). Hand them the exact incantation.
937
962
  function attestHint(rule, compliance) {
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, getOutcomeDefinition, hasAnswer, planContextFor, resolveOutcome, } from "../outcomes.js";
4
4
  import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
5
5
  import { availableOptionalCapabilityIds, capabilityChecklist, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, } from "../capabilities.js";
6
- import { applicableComplianceRuleSummaries } from "./compliance.js";
6
+ import { applicableCompliancePlanRuleSummaries } from "./compliance.js";
7
7
  import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, } from "./design.js";
8
8
  import { sdkVersionGuidance } from "./sdkVersion.js";
9
9
  import { detectCommandSensors } from "./harness.js";
@@ -121,7 +121,7 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
121
121
  docs: definition.docs(platform).filter((doc) => doc.path !== "unknown"),
122
122
  surface: inspection.selectedSurface ? { path: inspection.selectedSurface.path, platforms: inspection.selectedSurface.platforms } : undefined,
123
123
  availableSurfaces: inspection.surfaces,
124
- applicableRules: await applicableComplianceRuleSummaries(outcome, inspection.platforms),
124
+ applicableRules: await applicableCompliancePlanRuleSummaries(outcome, inspection.platforms),
125
125
  sensors: sensors.map((sensor) => ({ name: sensor.name, command: sensor.command, source: sensor.source })),
126
126
  stopConditions: composeStopConditions(ctx, definition.stopConditions(ctx), inspection.surfaces, surfacePath),
127
127
  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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "0.14.16",
3
+ "version": "0.14.18",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",