@amityco/social-plus-vise 0.14.11 → 0.14.13

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,26 @@ 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.13 — 2026-06-05
8
+
9
+ ### Changed
10
+ - **Comment UI-state precision:** `*.comments.ui-states-present` now requires loading/empty/error handling inside the comment surface itself, so unrelated feed/auth/network state words elsewhere in the app do not satisfy the comment tray expectation.
11
+ - **Rich feed rendering grouping:** `feed.rich-post-rendering` now groups both concrete post datatype handling and parent-child post rendering sensors across TypeScript, React Native, Android, Flutter, and iOS.
12
+ - **Repeatable host-agent smoke:** `npm run validate` now includes a temp-project flow that verifies design extraction, plan question surfacing, answered init, optional capability sensors, grouped product expectations, and ambiguous attestation guidance.
13
+
14
+ ### Verified
15
+ - Focused validator coverage and real-project temp-copy smokes covered Android, TypeScript, and iOS host apps without modifying the original projects.
16
+
17
+ ## 0.14.12 — 2026-06-05
18
+
19
+ ### Changed
20
+ - **Multi-sensor shared expectations:** `comments.thread-read-write` now groups the concrete comment page-size, creation-affordance, observer-cleanup, and UI-state sensors under one public product expectation while preserving `contractRuleId` and `validator.sensorId` evidence.
21
+ - **Ambiguous attestation guidance:** when a shared public expectation maps to multiple contract rules in the same sidecar, `vise check` now emits exact `vise attest --rule <contract-rule-id>` commands and `vise attest --rule <public-id>` rejects with a concrete rule list.
22
+ - **Public benchmark claim:** README benchmark copy now describes the current full workflow, current release validation, and evidence boundaries without relying on local benchmark artifact links.
23
+
24
+ ### Verified
25
+ - Packed-package smoke for `@amityco/social-plus-vise@0.14.12` confirmed plan question surfacing, design confirmation, optional feed capability sensors, shared comment expectation grouping, and ambiguous attestation handling.
26
+
7
27
  ## 0.14.11 — 2026-06-05
8
28
 
9
29
  ### Added
package/README.md CHANGED
@@ -143,27 +143,40 @@ A bench vise holds the workpiece steady so the craftsman's hands are free to sha
143
143
 
144
144
  ## Benchmark Results: Current Claim
145
145
 
146
- > **Vise gives AI coding agents a governed workflow for social.plus integrations, improving feature completeness, SDK compliance, and design consistency in greenfield work.**
146
+ > **Vise gives AI coding agents a governed workflow for social.plus integrations: it makes scope explicit, checks the local code, and turns missing SDK capabilities or compliance gaps into repair work before the agent stops.**
147
147
 
148
- The strongest current claim is not a universal speed or quality promise. It is narrower and more useful: when agents build greenfield social.plus SDK features, the Vise workflow makes scope explicit, checks local code, and turns missing capabilities or SDK violations into concrete repair work before the agent stops.
148
+ The strongest current claim is not a universal speed or quality promise. It is narrower and more useful: for greenfield social.plus SDK work, Vise improves the stopping condition. The agent is not done after reading docs or producing code; it is done when the local contract is green, attested, or blocked on explicit customer input.
149
149
 
150
- ### Latest headline: feed completeness
150
+ ### Latest Quantitative Benchmark
151
151
 
152
- The latest Vise 0.14.5 opt-in comparison is the headline product proof. Same feed request, same SDK docs, no Vise workflow in the baseline; the Vise arm explicitly selected `feed_optional_capabilities=post-image-upload,post-poll-creation,post-edit`, persisted that choice into `sp-vise/compliance.json`, and activated the selected sensors.
152
+ The latest feed-completeness benchmark remains the headline product proof. Same feed request, same SDK docs, no Vise workflow in the baseline; the Vise arm explicitly selected feed optional capabilities, persisted that choice into `sp-vise/compliance.json`, and activated selected source sensors.
153
153
 
154
- | Agent / model | Docs-only baseline | Vise 0.14.5 opt-in arm | Readout |
154
+ | Agent / model | Docs-only baseline | Vise opt-in arm | Readout |
155
155
  |---|---:|---:|---|
156
156
  | Cursor / Composer 2.5 | 30% (3.3/11 avg) | **97% (32/33)** | One seed surfaced the remaining item instead of silently dropping it. |
157
157
  | Claude / Sonnet 4.6 medium | 27% (3.0/11 avg) | **100% (33/33)** | All three Vise seeds reached 11/11. |
158
158
  | Codex / GPT-5.4 medium | 21% (2.3/11 avg) | **100% (33/33)** | All three Vise seeds reached 11/11. |
159
159
 
160
- Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional capabilities** implemented across the latest Vise arm. See the full table and per-seed grader links in [`benchmarks/CURSOR_VISE_0.14.5_RESULTS.html`](benchmarks/CURSOR_VISE_0.14.5_RESULTS.html).
160
+ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional capabilities** implemented across the Vise arm.
161
161
 
162
- ### Supporting proof
162
+ ### Current Release Validation
163
+
164
+ Version 0.14.12 adds current release proof around the full feed-forward and validation flow:
165
+
166
+ | Surface | What was validated |
167
+ |---|---|
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
+ | **Plan questions** | Plans surface blocking questions such as `feature_surface` and `design_contract_confirmation`, plus optional choices such as `feed_optional_capabilities`. |
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. |
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. |
174
+
175
+ ### Supporting Proof
163
176
 
164
177
  | Surface | Safe claim | Evidence |
165
178
  |---|---|---|
166
- | **Feature completeness** | Vise helps agents build more of the expected SDK capability surface. | Latest comparison: **21-30% without Vise vs 97-100% with Vise 0.14.5**. Earlier pre-registered Capability Matrix Row 2 also shipped a feature-completeness win: silently dropped items fell from 7.67/11 to 4.0/11. |
179
+ | **Feature completeness** | Vise helps agents build more of the expected SDK capability surface. | Latest comparison: **21-30% without Vise vs 97-100% with Vise**, with **98/99** expected feed capabilities implemented in aggregate. Earlier Capability Matrix work also showed silently dropped items falling from 7.67/11 to 4.0/11. |
167
180
  | **SDK compliance** | Vise checks catch greenfield SDK compliance gaps that docs or static guidance can miss. | Commune benchmark: Vise averaged **100% greenfield SDK compliance** where docs/RAG-style controls averaged **67%** across the reported slices. |
168
181
  | **Design conformance** | Vise design checks reduce design drift under ambiguous briefs. | Ambiguous Spotify-style design test: Vise design runs produced **0 / 0 / 0 hex literals** across three seeds; without Vise, runs varied **0 / 2 / 15**. This supports variance reduction, not pixel-perfect visual quality. |
169
182
 
@@ -172,10 +185,10 @@ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional ca
172
185
  The benchmark story is the product flow:
173
186
 
174
187
  1. **Inspect** — Vise detects platform, app surface, SDK surface, sensors, and design signals from the local repo.
175
- 2. **Plan** — Vise classifies the outcome, asks blocking intake questions, surfaces capability availability, and offers optional feed capabilities only when the platform SDK surface supports them.
188
+ 2. **Plan** — Vise classifies the outcome, asks blocking intake questions, checks platform capability availability, and offers optional features only when the platform SDK surface supports them.
176
189
  3. **Confirm design** — `vise design extract` writes a preview; `plan`/`init` withhold design feed-forward until the user confirms `design_contract_confirmation=yes`.
177
190
  4. **Initialize** — `vise init` records the resolved compliance contract, intake answers, selected optional capabilities, inspection result, and accepted design digest.
178
- 5. **Build / check / repair** — the agent edits locally, runs `vise check`, fixes deterministic findings, resolves completeness gaps or selected-capability failures, and then runs project sensors.
191
+ 5. **Build / check / repair** — selected answers become sensors. The agent edits locally, runs `vise check`, fixes deterministic findings, resolves completeness gaps or selected-capability failures, and then runs project sensors.
179
192
 
180
193
  Static docs can tell an agent what exists. Vise changes the stopping condition: the agent is not done until the local contract is green, attested, or blocked on explicit customer input.
181
194
 
@@ -183,14 +196,12 @@ Static docs can tell an agent what exists. Vise changes the stopping condition:
183
196
 
184
197
  The benchmark suite is intentionally reported with boundaries:
185
198
 
186
- - **Latest feed-completeness comparison** is the current product claim for the opt-in capability flow in Vise 0.14.5. It is a best-case/opt-in comparison across Cursor, Claude, and Codex, not a universal result for every prompt.
199
+ - **Latest feed-completeness comparison** is the current quantitative product claim for the opt-in capability flow. It is a best-case/opt-in comparison across Cursor, Claude, and Codex, not a universal result for every prompt.
187
200
  - **Capability Matrix v1** remains the pre-registered follow-up. It shipped the Row 2 feature-completeness claim, found **no Row 1 SDK-compliance claim** on chat/moderation/push under its registered margin, and withheld the Row 3 design claim on a technicality despite higher by-name token use.
188
201
  - **Commune Phase 1** remains useful historical evidence for the compliance loop: two agents reached 9/9 with Vise vs 5-7/9 under controls, but it was N=1 per cell and the grader overlaps Vise's own rules.
189
202
  - **Design tests** support design-drift reduction and token cleanup. They do not prove visual taste, pixel perfection, or production-ready UI without human review.
190
203
  - **Negative results must travel with the claim:** no measured Vise advantage on day-2 bug fixing; the push slice exposed a non-converging attestation loop when docs and SDK disagreed; earlier enumerative plan-time design guidance measured negative and was retracted; the original `scope-omit` affordance went unused in the matrix.
191
204
 
192
- Full evidence: [`benchmarks/CURSOR_VISE_0.14.5_RESULTS.html`](benchmarks/CURSOR_VISE_0.14.5_RESULTS.html), [`benchmarks/capability-matrix/RESULTS.md`](benchmarks/capability-matrix/RESULTS.md), [`benchmarks/commune/RESULTS.md`](benchmarks/commune/RESULTS.md), and [`benchmarks/brand/design-test/RESULTS.md`](benchmarks/brand/design-test/RESULTS.md).
193
-
194
205
  ### Which mode should I use?
195
206
 
196
207
  | If you… | Use | Why |
package/dist/outcomes.js CHANGED
@@ -788,8 +788,7 @@ const addComments = {
788
788
  "comment target resolved",
789
789
  "no invented postId/commentId",
790
790
  `${platform}.comments.target-resolved`,
791
- `${platform}.comments.observer-cleanup`,
792
- `${platform}.comments.ui-states-present`,
791
+ "comments.thread-read-write",
793
792
  `${platform}.comments.moderation-affordance-present`,
794
793
  ],
795
794
  stopConditions: (ctx) => filterStops(ctx.answers, [
@@ -4,26 +4,51 @@ export const PRODUCT_EXPECTATION_BINDINGS = [
4
4
  sensorId: "typescript.feed.post-datatype-handled",
5
5
  platform: "typescript",
6
6
  },
7
+ {
8
+ expectationId: "feed.rich-post-rendering",
9
+ sensorId: "typescript.posts.parent-child-rendered",
10
+ platform: "typescript",
11
+ },
7
12
  {
8
13
  expectationId: "feed.rich-post-rendering",
9
14
  sensorId: "react-native.feed.post-datatype-handled",
10
15
  platform: "react-native",
11
16
  },
17
+ {
18
+ expectationId: "feed.rich-post-rendering",
19
+ sensorId: "react-native.posts.parent-child-rendered",
20
+ platform: "react-native",
21
+ },
12
22
  {
13
23
  expectationId: "feed.rich-post-rendering",
14
24
  sensorId: "android.feed.post-datatype-handled",
15
25
  platform: "android",
16
26
  },
27
+ {
28
+ expectationId: "feed.rich-post-rendering",
29
+ sensorId: "android.posts.parent-child-rendered",
30
+ platform: "android",
31
+ },
17
32
  {
18
33
  expectationId: "feed.rich-post-rendering",
19
34
  sensorId: "flutter.feed.post-datatype-handled",
20
35
  platform: "flutter",
21
36
  },
37
+ {
38
+ expectationId: "feed.rich-post-rendering",
39
+ sensorId: "flutter.posts.parent-child-rendered",
40
+ platform: "flutter",
41
+ },
22
42
  {
23
43
  expectationId: "feed.rich-post-rendering",
24
44
  sensorId: "ios.feed.post-datatype-handled",
25
45
  platform: "ios",
26
46
  },
47
+ {
48
+ expectationId: "feed.rich-post-rendering",
49
+ sensorId: "ios.posts.parent-child-rendered",
50
+ platform: "ios",
51
+ },
27
52
  {
28
53
  expectationId: "feed.rich-post-composer-scope",
29
54
  sensorId: "typescript.feed.rich-post-composer-surfaced",
@@ -49,31 +74,111 @@ export const PRODUCT_EXPECTATION_BINDINGS = [
49
74
  sensorId: "ios.feed.rich-post-composer-surfaced",
50
75
  platform: "ios",
51
76
  },
77
+ {
78
+ expectationId: "comments.thread-read-write",
79
+ sensorId: "typescript.comments.query-has-limit",
80
+ platform: "typescript",
81
+ },
52
82
  {
53
83
  expectationId: "comments.thread-read-write",
54
84
  sensorId: "typescript.comments.creation-affordance-present",
55
85
  platform: "typescript",
56
86
  },
87
+ {
88
+ expectationId: "comments.thread-read-write",
89
+ sensorId: "typescript.comments.observer-cleanup",
90
+ platform: "typescript",
91
+ },
92
+ {
93
+ expectationId: "comments.thread-read-write",
94
+ sensorId: "typescript.comments.ui-states-present",
95
+ platform: "typescript",
96
+ },
97
+ {
98
+ expectationId: "comments.thread-read-write",
99
+ sensorId: "react-native.comments.query-has-limit",
100
+ platform: "react-native",
101
+ },
57
102
  {
58
103
  expectationId: "comments.thread-read-write",
59
104
  sensorId: "react-native.comments.creation-affordance-present",
60
105
  platform: "react-native",
61
106
  },
107
+ {
108
+ expectationId: "comments.thread-read-write",
109
+ sensorId: "react-native.comments.observer-cleanup",
110
+ platform: "react-native",
111
+ },
112
+ {
113
+ expectationId: "comments.thread-read-write",
114
+ sensorId: "react-native.comments.ui-states-present",
115
+ platform: "react-native",
116
+ },
117
+ {
118
+ expectationId: "comments.thread-read-write",
119
+ sensorId: "android.comments.query-has-limit",
120
+ platform: "android",
121
+ },
122
+ {
123
+ expectationId: "comments.thread-read-write",
124
+ sensorId: "android.comments.creation-affordance-present",
125
+ platform: "android",
126
+ },
127
+ {
128
+ expectationId: "comments.thread-read-write",
129
+ sensorId: "android.comments.observer-cleanup",
130
+ platform: "android",
131
+ },
132
+ {
133
+ expectationId: "comments.thread-read-write",
134
+ sensorId: "android.comments.ui-states-present",
135
+ platform: "android",
136
+ },
62
137
  {
63
138
  expectationId: "comments.thread-read-write",
64
139
  sensorId: "android.comments.thread-ui-states-present",
65
140
  platform: "android",
66
141
  },
142
+ {
143
+ expectationId: "comments.thread-read-write",
144
+ sensorId: "flutter.comments.query-has-limit",
145
+ platform: "flutter",
146
+ },
67
147
  {
68
148
  expectationId: "comments.thread-read-write",
69
149
  sensorId: "flutter.comments.creation-affordance-present",
70
150
  platform: "flutter",
71
151
  },
152
+ {
153
+ expectationId: "comments.thread-read-write",
154
+ sensorId: "flutter.comments.observer-cleanup",
155
+ platform: "flutter",
156
+ },
157
+ {
158
+ expectationId: "comments.thread-read-write",
159
+ sensorId: "flutter.comments.ui-states-present",
160
+ platform: "flutter",
161
+ },
162
+ {
163
+ expectationId: "comments.thread-read-write",
164
+ sensorId: "ios.comments.query-has-limit",
165
+ platform: "ios",
166
+ },
72
167
  {
73
168
  expectationId: "comments.thread-read-write",
74
169
  sensorId: "ios.comments.creation-affordance-present",
75
170
  platform: "ios",
76
171
  },
172
+ {
173
+ expectationId: "comments.thread-read-write",
174
+ sensorId: "ios.comments.observer-cleanup",
175
+ platform: "ios",
176
+ },
177
+ {
178
+ expectationId: "comments.thread-read-write",
179
+ sensorId: "ios.comments.ui-states-present",
180
+ platform: "ios",
181
+ },
77
182
  {
78
183
  expectationId: "chat.unread-visible",
79
184
  sensorId: "typescript.chat.unread-visible",
@@ -169,3 +274,6 @@ export function contractRuleCandidatesForPublicId(ruleId) {
169
274
  .filter((binding) => binding.expectationId === ruleId)
170
275
  .map((binding) => binding.sensorId);
171
276
  }
277
+ export function hasMultipleContractRuleCandidates(ruleId) {
278
+ return contractRuleCandidatesForPublicId(ruleId).length > 1;
279
+ }
@@ -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, productExpectationBindingForSensor, publicProductRuleId, } from "../productExpectations.js";
7
+ import { contractRuleCandidatesForPublicId, hasMultipleContractRuleCandidates, productExpectationBindingForSensor, 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";
@@ -568,7 +568,7 @@ export async function checkCompliance(repoPath) {
568
568
  recommendation: finding?.recommendation,
569
569
  rationale: rule.rationale,
570
570
  current_rule: ruleSummary(rule),
571
- ...(failStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
571
+ ...(failStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
572
572
  });
573
573
  continue;
574
574
  }
@@ -593,7 +593,7 @@ export async function checkCompliance(repoPath) {
593
593
  rationale: rule.rationale,
594
594
  current_rule: ruleSummary(rule),
595
595
  source_fingerprint_status: sourceFingerprintStatus,
596
- ...(fingerprintStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
596
+ ...(fingerprintStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
597
597
  });
598
598
  continue;
599
599
  }
@@ -639,7 +639,7 @@ export async function checkCompliance(repoPath) {
639
639
  recommendation: finding?.recommendation,
640
640
  current_rule: ruleSummary(rule),
641
641
  ...(isInferential && { inferential_prompt: rule.enforcement.inferential?.prompt }),
642
- ...(baseStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
642
+ ...(baseStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
643
643
  });
644
644
  }
645
645
  const summary = summarize(results);
@@ -738,6 +738,14 @@ export async function attestRule(args) {
738
738
  const contractRuleId = resolveRuleIdForContract(args.ruleId, compliance, rules);
739
739
  const rule = contractRuleId ? rules.get(contractRuleId) : undefined;
740
740
  if (!rule || !contractRuleId) {
741
+ const ambiguousCandidates = contractRuleCandidatesForContract(args.ruleId, compliance, rules);
742
+ if (ambiguousCandidates.length > 1) {
743
+ const candidateIds = ambiguousCandidates
744
+ .filter((candidate) => rules.get(candidate)?.enforcement.attestation.allowed)
745
+ .slice(0, 8);
746
+ const ids = candidateIds.length > 0 ? candidateIds : ambiguousCandidates.slice(0, 8);
747
+ throw new Error(`Rule is ambiguous in this compliance contract: ${args.ruleId}. Attest one contract rule at a time: ${ids.join(", ")}.`);
748
+ }
741
749
  // Collect up to 8 applicable attestable rule ids from this contract for the error hint. Prefer
742
750
  // ids that share the bad id's non-wildcard prefix so the agent can narrow down quickly.
743
751
  const attestableIds = compliance.rules
@@ -746,7 +754,10 @@ export async function attestRule(args) {
746
754
  const prefix = args.ruleId.replace(/\.\*$|\*$/, "");
747
755
  const prefixed = prefix !== args.ruleId ? attestableIds.filter((r) => r.id.startsWith(prefix) || r.id.includes(prefix) || publicProductRuleId(r.id).startsWith(prefix)) : [];
748
756
  const candidates = prefixed.length > 0 ? prefixed : attestableIds;
749
- const hintIds = candidates.slice(0, 8).map((r) => publicProductRuleId(r.id));
757
+ const hintIds = uniqueStrings(candidates.slice(0, 8).map((r) => {
758
+ const publicId = publicProductRuleId(r.id);
759
+ return publicId === r.id ? r.id : `${publicId} (${r.id})`;
760
+ }));
750
761
  const hintSuffix = hintIds.length > 0 ? ` Applicable attestable rules: ${hintIds.join(", ")}.` : " Applicable attestable rules: none.";
751
762
  const preamble = args.ruleId.includes("*")
752
763
  ? `Wildcards are not supported — attest one rule at a time.`
@@ -780,6 +791,31 @@ export async function explainRule(ruleId) {
780
791
  const rules = await rulesById();
781
792
  const contractRuleId = resolveRuleIdForInstalledRules(ruleId, rules);
782
793
  const rule = contractRuleId ? rules.get(contractRuleId) : undefined;
794
+ const publicCandidates = contractRuleCandidatesForPublicId(ruleId)
795
+ .map((candidate) => rules.get(candidate))
796
+ .filter((candidate) => candidate !== undefined);
797
+ if (!rule && publicCandidates.length > 1) {
798
+ return {
799
+ id: ruleId,
800
+ kind: "product_expectation",
801
+ note: "This public product expectation is validated by multiple concrete contract rules. Use the contract_rule_id when attesting one sensor.",
802
+ contract_rules: publicCandidates.map((candidate) => {
803
+ const binding = productExpectationBindingForSensor(candidate.id);
804
+ return {
805
+ contract_rule_id: candidate.id,
806
+ ...(binding && { validator: { platform: binding.platform, sensorId: binding.sensorId } }),
807
+ version: candidate.version,
808
+ title: candidate.title,
809
+ severity: candidate.severity,
810
+ rationale: candidate.rationale,
811
+ applies_when: candidate.applies_when,
812
+ attestation: candidate.enforcement.attestation,
813
+ summary: ruleSummary(candidate),
814
+ rule_digest: digestRule(candidate),
815
+ };
816
+ }),
817
+ };
818
+ }
783
819
  if (!rule || !contractRuleId) {
784
820
  throw new Error(`Unknown compliance rule: ${ruleId}`);
785
821
  }
@@ -865,9 +901,12 @@ function resolveRuleIdForContract(ruleId, compliance, rules) {
865
901
  if (rules.has(ruleId) && compliance.rules.some((ref) => ref.rule_id === ruleId)) {
866
902
  return ruleId;
867
903
  }
868
- const candidates = contractRuleCandidatesForPublicId(ruleId).filter((candidate) => rules.has(candidate) && compliance.rules.some((ref) => ref.rule_id === candidate));
904
+ const candidates = contractRuleCandidatesForContract(ruleId, compliance, rules);
869
905
  return candidates.length === 1 ? candidates[0] : null;
870
906
  }
907
+ function contractRuleCandidatesForContract(ruleId, compliance, rules) {
908
+ return contractRuleCandidatesForPublicId(ruleId).filter((candidate) => rules.has(candidate) && compliance.rules.some((ref) => ref.rule_id === candidate));
909
+ }
871
910
  function resolveRuleIdForInstalledRules(ruleId, rules) {
872
911
  if (rules.has(ruleId)) {
873
912
  return ruleId;
@@ -895,12 +934,16 @@ function ruleRefForFile(rule) {
895
934
  }
896
935
  // Benchmark-measured friction: agents looped on attest dialect for ~25 min/cell when docs and SDK
897
936
  // disagreed on exact invocation syntax (capability-matrix 2026-06, Row 5). Hand them the exact incantation.
898
- function attestHint(rule) {
937
+ function attestHint(rule, compliance) {
899
938
  const minConfidence = rule.enforcement.attestation.host_agent_min_confidence ?? "high";
900
939
  const fields = rule.enforcement.attestation.evidence_required ?? [];
901
940
  const publicRuleId = publicProductRuleId(rule.id);
941
+ const candidatesInContract = compliance
942
+ ? contractRuleCandidatesForPublicId(publicRuleId).filter((candidate) => compliance.rules.some((ref) => ref.rule_id === candidate))
943
+ : contractRuleCandidatesForPublicId(publicRuleId);
944
+ const ruleArg = hasMultipleContractRuleCandidates(publicRuleId) && candidatesInContract.length > 1 ? rule.id : publicRuleId;
902
945
  return {
903
- attest_command: `vise attest --rule ${publicRuleId} --confidence ${minConfidence} --signer host-agent --evidence-file sp-vise/evidence/${rule.id}.json --rationale "<why this rule is satisfied (or cannot apply) in this codebase>"`,
946
+ attest_command: `vise attest --rule ${ruleArg} --confidence ${minConfidence} --signer host-agent --evidence-file sp-vise/evidence/${rule.id}.json --rationale "<why this rule is satisfied (or cannot apply) in this codebase>"`,
904
947
  evidence_template: Object.fromEntries(fields.map((f) => [f.field, `<${f.description}>`])),
905
948
  };
906
949
  }
@@ -939,7 +982,7 @@ function contractDrift(compliance, rules) {
939
982
  status: "stale",
940
983
  reason,
941
984
  current_rule: ruleSummary(rule),
942
- ...(rule.enforcement.attestation.allowed && attestHint(rule)),
985
+ ...(rule.enforcement.attestation.allowed && attestHint(rule, compliance)),
943
986
  });
944
987
  }
945
988
  }
@@ -959,6 +1002,18 @@ function deterministicFindingIds(rule) {
959
1002
  .filter((check) => check.check === "validator-finding-absent")
960
1003
  .map((check) => check.finding_rule_id);
961
1004
  }
1005
+ function uniqueStrings(values) {
1006
+ const seen = new Set();
1007
+ const result = [];
1008
+ for (const value of values) {
1009
+ if (seen.has(value)) {
1010
+ continue;
1011
+ }
1012
+ seen.add(value);
1013
+ result.push(value);
1014
+ }
1015
+ return result;
1016
+ }
962
1017
  function buildAttestation(compliance, rule, signer, confidence, identity, rationale, evidence, sourceFingerprints = []) {
963
1018
  const ref = compliance.rules.find((item) => item.rule_id === rule.id);
964
1019
  if (!ref) {
@@ -1022,8 +1022,9 @@ function validateComments(root, platform, sourceContent) {
1022
1022
  if (commentFiles.length > 0 && hasReactiveCommentObserver && !containsAny(sourceContent, cleanupMarkers)) {
1023
1023
  findings.push(finding(`${platform}.comments.observer-cleanup`, "warning", "Comment observer/subscription was found but no obvious cleanup was detected.", relativeFile(root, commentFiles[0]), "Dispose or unsubscribe comment observers when the lifecycle owner is destroyed."));
1024
1024
  }
1025
- // ui-states-present: require loading/empty/error states
1026
- if (!containsAny(sourceContent, COMMENT_UI_STATE_MARKERS)) {
1025
+ // ui-states-present: require loading/empty/error states in the comment surface itself.
1026
+ // Unrelated feed/auth/network state markers elsewhere in the project do not make a comment UI complete.
1027
+ if (!containsAnyForFiles(sourceContent, commentFiles, COMMENT_UI_STATE_MARKERS)) {
1027
1028
  findings.push(finding(`${platform}.comments.ui-states-present`, "warning", "Comment UI is present but no loading, empty, or error state handling was detected.", relativeFile(root, commentFiles[0]), "Handle loading, empty, and error states in the comment UI for a complete user experience."));
1028
1029
  }
1029
1030
  // moderation-affordance-present: require moderation on comments (scoped to comment files)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "0.14.11",
3
+ "version": "0.14.13",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -68,7 +68,7 @@
68
68
  "test:blocks-installer": "npm run build && node test/run-blocks-installer.mjs",
69
69
  "typecheck": "tsc -p tsconfig.json --noEmit",
70
70
  "test:e2e-package": "npm run build && node test/run-e2e-package.mjs",
71
- "validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:design-extract && npm run test:design-brief && npm run test:capabilities && npm run test:classify && npm run test:compliance && npm run test:product-flow && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:nonui-skip && npm run test:sdk-version && npm run test:sdk-surface && npm run test:sdk-facts && npm run test:blocks-installer && npm run test:native-idioms && npm run test:grader-facts && npm run test:ground-truth && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run bench:symbols-drift && npm run pack:check",
71
+ "validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:design-extract && npm run test:design-brief && npm run test:capabilities && npm run test:classify && npm run test:compliance && npm run test:product-flow && npm run test:agent-flow && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:nonui-skip && npm run test:sdk-version && npm run test:sdk-surface && npm run test:sdk-facts && npm run test:blocks-installer && npm run test:native-idioms && npm run test:grader-facts && npm run test:ground-truth && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run bench:symbols-drift && npm run pack:check",
72
72
  "test:ast": "node test/run-ast-helpers.mjs",
73
73
  "test:design-extract": "npm run build && node test/run-design-extract.mjs",
74
74
  "test:design-brief": "npm run build && node test/run-design-brief.mjs",
@@ -77,6 +77,7 @@
77
77
  "test:debug": "npm run build && node test/run-debug.mjs",
78
78
  "test:preflight": "npm run build && node test/run-preflight.mjs",
79
79
  "test:product-flow": "npm run build && node test/run-product-flow.mjs",
80
+ "test:agent-flow": "npm run build && node test/run-agent-flow-smoke.mjs",
80
81
  "test:native-idioms": "npm run build && node test/run-native-idioms.mjs",
81
82
  "test:grader-facts": "node test/run-grader-facts.mjs",
82
83
  "test:ground-truth": "node test/run-ground-truth.mjs",