@amityco/social-plus-vise 1.1.1 → 1.3.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 +43 -0
- package/README.md +22 -10
- package/dist/capabilities.js +6 -3
- package/dist/explore.js +51 -0
- package/dist/humanFormat.js +226 -0
- package/dist/outcomes.js +15 -14
- package/dist/server.js +275 -37
- package/dist/solutionPath.js +274 -0
- package/dist/tools/compliance.js +289 -35
- package/dist/tools/debug.js +83 -26
- package/dist/tools/design.js +24 -4
- package/dist/tools/harness.js +30 -2
- package/dist/tools/integration.js +161 -10
- package/dist/tools/project.js +39 -13
- package/dist/tools/sdkFacts.js +8 -1
- package/dist/tools/sensors.js +1 -1
- package/dist/uikitCustomization.js +384 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,49 @@ 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
|
+
## 1.3.0 — 2026-06-19
|
|
8
|
+
|
|
9
|
+
Discovery + readability surface, CLI/report honesty, and a batch of detector-precision fixes from two real-agent persona-dogfood sweeps (Sonnet 4.6), each fix mutation-verified and the whole batch adversarially reviewed. **No change to `vise check` exit codes, compliance rules, or sidecar *formats*** — the sidecar gains only **additive** fields (see Compatibility). The rule corpus is unchanged, so the contract digest is stable and no re-attestation is triggered.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`vise explore "<request>"`** — a pre-credentials, read-only discovery command. It maps a free-text request to a candidate feature/setup outcome (and lists the full menu when it can't, instead of dead-ending on `outcome:unknown`); each route carries the capabilities involved, the canonical docs, and the `vise plan` command to start. No project or API key required.
|
|
13
|
+
- **`--format human`** on the read commands (`check` · `status` · `plan` · `doctor` · `explain` · `explore`). **JSON stays the default** — agents, CI, and sidecar-consuming flows parse stdout, so the default is unchanged; `--format human` is opt-in.
|
|
14
|
+
- **`vise explain`** with no id now **enumerates the valid rule ids** (grouped by public id → contract ids) instead of erroring.
|
|
15
|
+
- **`completeness` / selected-capability detail** is now surfaced by **`vise status`** and the init-time `findings.json` snapshot, mirroring `vise check`. Previously a `needs-attestation` / `deterministic-failures` headline (status precedence) hid a co-existing completeness-gap from anything reading those two surfaces.
|
|
16
|
+
- **Brownfield baseline gate.** `vise baseline` (and `vise init --baseline`) snapshots the current pre-existing findings to `sp-vise/baseline.json`, and **`vise check --new-only`** (also `status --new-only`) gates only on findings introduced *since* the baseline — legacy findings are reported (`baselined: true`) but excluded. This makes `check` usable as a per-PR merge gate on an existing codebase. **Gate integrity:** subtraction is an explicit per-invocation flag, *never* the default — a bare `vise check` always gates on everything, so a green exit can never silently mean "no *new* gaps." A stale baseline (ruleset digest moved since it was recorded) is **not** applied (fail-safe: gate on everything and disclose `baseline.stale`). The artifact is additive — absent ⇒ unchanged behavior; the frozen sidecar-compat baseline is unaffected. Known v1 limitation, surfaced in the check output as `baseline.residual_caveat`: the rule+file key can mask a new violation of an already-baselined rule in the same file.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **Honesty of reports.** `attest` re-runs the deterministic sensor and **discloses** a live violation (without refusing — attestation is the override path); `check`/`status` carry an `evidence_basis_note` (plain-language "passed by absence" caveat); `vise status` propagates the body exit code to the process exit; `--ci` `blockingResultStatuses` enumerates every non-green status (incl. completeness-gap, selected-capability-failures); `explain` surfaces the feedforward/symptoms/inferential corpus fields; `doctor` surfaces the support channel.
|
|
20
|
+
- **`vise debug`** correlates runtime-only symptom matches regardless of compliance status (a passing rule is framed as a runtime/out-of-sensor-reach signal, never a compliance failure), and maps `Cannot find module` / `MODULE_NOT_FOUND` for a **bare package** to a concrete `npm install` remediation — a failing correlated rule still wins the headline (the module hint rides as a secondary note).
|
|
21
|
+
- **`vise init` intake validation.** Out-of-enum values (on closed-enum questions) and unrecognized `--answer` keys now produce advisory **warnings** instead of being silently accepted/dropped. Warnings only — never a rejection — so multi-select and free-form answers are unaffected.
|
|
22
|
+
- **`engagement --scope`** accepts the full planner outcome vocabulary (derived from the classifier order), including `add-follow` / `add-community` / `add-notifications`, which were previously rejected.
|
|
23
|
+
- **Classifier routing.** "community profile" → `add-community`, "user/member profile" (with a social qualifier) → `add-follow`, "notifications tray" → `add-notifications`; a non-social "company/performance/memory profile" stays `unknown`.
|
|
24
|
+
- **`design check` coverage** splits `referenced_in_app` vs `seeded_only_tokens` — tokens that appear only in the Vise-scaffolded token file no longer inflate a false 100% (the raw `referenced` count is unchanged for back-compat).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Advisory routing precision** (advisory-only; no gate change): an explicit "build it with our own component" / "replace the UIKit surface" ask now routes SDK/custom-UI instead of Dynamic-UI; Console/theming phrasing corrected.
|
|
28
|
+
- **Detector false positives.** `posts.status-filtered` no longer mis-attributes to `.d.ts` type stubs; `chat.send-error-handling` requires a real message *send* call (a read-only `markRead` view no longer fires); Android `comments.observer-cleanup` recognizes structured-concurrency cleanup (`stateIn(viewModelScope)` / `collectAsStateWithLifecycle` / `repeatOnLifecycle` / `DisposableEffect`), anchored to the observer's own chain; a negated "no custom code" no longer trips behavior-overrides; `scope-omit` reasons must be auditable (≥ 8 chars).
|
|
29
|
+
- **`*.sdk.version.pinned` messages** now name each platform's real uncontrolled tokens (Android `+`/`:latest`, Flutter `any`, iOS moving branch / unspecified CocoaPods, TypeScript `latest`/`*`/`x` with `^`/`~` accepted). The rule **rationale is unchanged** (it is part of the contract digest; rewording it would force re-attestation — see the digest note in maintainer docs).
|
|
30
|
+
- **`sdk-facts`**: the "names-only grounding" caveat now prints only when grounding is actually names-only; native platforms note that an empty `capabilities` list reflects TypeScript-first capability authoring, not lack of SDK support.
|
|
31
|
+
- **Design contract** confirmation re-routes to `needs-confirmation` when the preview is absent; the design reference inlines only sanitized `:root` token declarations, never raw source.
|
|
32
|
+
- Concrete CLI bugs from the first sweep (broken docs path, `engagement` show/init path mismatch, `workplan.json` persistence, and related discovery/intake edges).
|
|
33
|
+
|
|
34
|
+
### Compatibility
|
|
35
|
+
- **No exit-code or sidecar-*format* breaking changes.** The `completeness` / `selectedOptionalCapabilities` fields added to `vise status` / the init `findings.json` snapshot, and the new `sp-vise/baseline.json` artifact, are all **additive**; the `test:sidecar-compat` baseline (frozen 1.1.0 sidecar, no baseline.json) stays green. The rule corpus and contract digest are unchanged, so existing attestations are not invalidated. `vise check` with no flags is unchanged — the brownfield baseline only applies under the explicit `--new-only` flag.
|
|
36
|
+
|
|
37
|
+
## 1.2.0 — 2026-06-18
|
|
38
|
+
|
|
39
|
+
Adds advisory **solution-path / UIKit routing** and a fail-closed fix to the SwiftPM manifest sensor. No change to `vise check` exit codes, compliance rules, or sidecar **formats**; the one behavior change is the SwiftPM-timeout outcome noted under Changed.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **Solution-path & UIKit routing (advisory).** `vise plan` emits `solutionPath` (`sdk` · `uikit` · `hybrid` · `needs-decision`) so an agent pauses for `--answer solution_path=…` before building the wrong layer, cleanly separating the two integrator postures (SDK/custom-UI vs UIKit/prebuilt-UI). When UIKit is in play it also emits `uikitCustomization`, recommending the lowest-effort tier that meets the goal (Dynamic UI → Component Styling → Localization → Behavior Overrides → Fork and Extend → SDK Custom UI). Both are **advisory only** — they never change outcome classification, compliance rules, the sidecar schema, or `vise check` exit codes. Documented under "Solution path & UIKit routing" in the README; the two postures are described in `docs/PERSONAS.md`.
|
|
43
|
+
- **Real-build readiness checks** surfaced alongside the routing signals.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- **SwiftPM manifest sensor: timeout is now RED (fail-closed), not a skip.** A manifest-parse timeout previously read as a clean green; for a pure-SwiftPM iOS project the manifest sensor is the only sensor, so a stall masqueraded as success. It now reports `timed-out` and the CLI exits non-zero. **Behavior note for consumers:** a network-restricted CI runner that cannot resolve a remote SwiftPM dependency will now go RED on this sensor rather than silently passing — intended fail-closed behavior, not a regression.
|
|
47
|
+
- **Advisory routing engines hardened (two adversarial-verification batches, 28 fixes, every fix mutation-verified).** `recommendSolutionPath` / `recommendUIKitCustomization` precision: UIKit-rejection coverage now spans modal/contracted negators (won't/can't/cannot), intervening verbs ("don't want to use UIKit", "without using UIKit"), and "anything but UIKit" — an explicit rejection routes SDK-first instead of (previously) recommending UIKit; prebuilt idioms are anchored to a UI/social noun; `off-the-shelf` is anchored (so "off-the-shelf analytics" stays SDK-first); localization no longer fires on a programming/design "language"; backend feature capabilities (e.g. livestreaming) surface as a decision rather than a Dynamic-UI default. Advisory-only quality improvements; no gate behavior changes.
|
|
48
|
+
- **Personas split into two integrator postures** (AI-Assisted SDK/Custom-UI Integrator and AI-Assisted UIKit/Prebuilt-UI Adopter), tied to a deterministic behavior contract in the test suite.
|
|
49
|
+
|
|
7
50
|
## 1.1.1 — 2026-06-16
|
|
8
51
|
|
|
9
52
|
Docs-only patch; no CLI, MCP, rule, or sidecar behavior changes.
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ It turns the request into a grounded plan, records a local contract under `sp-vi
|
|
|
47
47
|
|
|
48
48
|
> 🔒 **Your source code never leaves your machine.** Vise fetches only the public social.plus docs and the SDK's published version on npm — never your code, file contents, or search queries. `VISE_DOCS_OFFLINE=1` runs fully offline.
|
|
49
49
|
|
|
50
|
-
Vise can also run **ahead** of that loop: an advisory [Engagement Intelligence](#engagement-intelligence) layer turns a product goal into
|
|
50
|
+
Vise can also run **ahead** of that loop: an advisory [Engagement Intelligence](#engagement-intelligence) layer turns a product goal into multiple candidate engagement strategies — *what you might build*, not only *whether you built it right*.
|
|
51
51
|
|
|
52
52
|
> **Why "Vise"?** A bench vise holds the workpiece steady so the craftsman's hands are free to shape it. Vise clamps the integration to the real docs, the real project structure, and the real compliance rules so the agent can focus on building instead of guessing.
|
|
53
53
|
|
|
@@ -98,12 +98,18 @@ Prefer a per-project install? `npm install -D @amityco/social-plus-vise`, then c
|
|
|
98
98
|
## How it works
|
|
99
99
|
|
|
100
100
|
1. **Inspect** — `vise inspect` detects the platform, app surfaces, available sensors, and design signals from the local repo.
|
|
101
|
-
2. **Plan** — `vise plan --request "..."` classifies the outcome, cites docs, and raises blocking intake and design-preview questions. The agent surfaces
|
|
101
|
+
2. **Plan** — `vise plan --request "..."` classifies the outcome, cites docs, and raises blocking intake and design-preview questions. Use `--summary` when you only need the route/intake readout before implementation. The agent surfaces questions; you answer.
|
|
102
102
|
3. **Initialize** — `vise init` writes the `sp-vise/` compliance contract. While blocking questions are unanswered it refuses, returns `status: "needs-clarification"`, and exits 7 — the agent must ask instead of guessing.
|
|
103
103
|
4. **Build** — the agent edits your code, grounded by `vise search-docs` and `vise get-doc-page`.
|
|
104
104
|
5. **Check & repair** — `vise check` reports deterministic findings, completeness gaps, and attestation needs. The agent fixes findings or records attestations with evidence, looping until green.
|
|
105
105
|
6. **Sense** — `vise run-sensors` runs your project's own typecheck/build/lint/SDK smokes. Done means the contract and evidence are committed, not just that the agent stopped.
|
|
106
106
|
|
|
107
|
+
### Solution path & UIKit routing (advisory)
|
|
108
|
+
|
|
109
|
+
Some requests are better served by **social.plus UIKit** (prebuilt social surfaces) than by hand-rolling standard UI directly from SDK primitives. `vise plan` emits an advisory **`solutionPath`** — `sdk`, `uikit`, `hybrid`, or `needs-decision` — so an agent can pause for `--answer solution_path=uikit|sdk|hybrid` before it builds the wrong layer. This separates the two integrator postures cleanly: an **SDK / custom-UI** build (differentiated, direct-SDK) stays SDK-first, a **UIKit / prebuilt-UI** adoption (standard surfaces, fast launch) routes to UIKit, and a genuinely mixed request resolves to `needs-decision` rather than silently picking one. A request that *rejects* UIKit ("don't use UIKit", "we can't use UIKit") routes SDK-first, and a request that names a backend feature capability (e.g. livestreaming) is surfaced as a decision rather than a UIKit-customization default.
|
|
110
|
+
|
|
111
|
+
When UIKit is in play, `vise plan` can also emit **`uikitCustomization`**, recommending the lowest-effort customization tier that meets the goal — Dynamic UI → Component Styling → Localization → Behavior Overrides → Fork and Extend → SDK Custom UI. Both signals are **advisory only**: they never change outcome classification, compliance rules, the sidecar schema, or `vise check` exit codes.
|
|
112
|
+
|
|
107
113
|
### Three validation layers
|
|
108
114
|
|
|
109
115
|
The layer is set by the *kind of claim*, which is how Vise is designed to avoid false positives where it gates:
|
|
@@ -120,9 +126,9 @@ Correctness is gated by deterministic rules or attestations; completeness is gat
|
|
|
120
126
|
|
|
121
127
|
### Engagement Intelligence
|
|
122
128
|
|
|
123
|
-
**Advisory, in preview.** The three layers above answer *whether you built it right*. Ahead of the build, Vise can also help
|
|
129
|
+
**Advisory, in preview.** The three layers above answer *whether you built it right*. Ahead of the build, Vise can also help reason about *what to build*: an **Engagement Intelligence** layer turns a product goal into multiple candidate **engagement strategies** — archetypes, UX patterns, and solution variants drawn from a social.plus experience catalog — with rationale, tradeoffs, no-fit guidance, availability boundaries, and review gaps. The human or driving agent still chooses the direction, then Vise compiles that selected variant into an implementation plan (`vise creative` → `vise creative accept` → `vise experience compile`), optionally bridging to installable social.plus blocks, plus advisory UX expectations and a multi-dimension experience review.
|
|
124
130
|
|
|
125
|
-
It is **local-only, never uploads, carries no calibrated score
|
|
131
|
+
It is **local-only, never uploads, carries no calibrated score**, and **never changes `vise check`'s exit codes**. The opt-in ranking preview is review context, not a top-1 confidence claim or autonomous product-strategy selector. Use it to shape the work; the validation layers still decide when it's done.
|
|
126
132
|
|
|
127
133
|
### Design contracts
|
|
128
134
|
|
|
@@ -143,6 +149,8 @@ The mechanism is the durable claim: a checked loop beats the same rules in the p
|
|
|
143
149
|
|
|
144
150
|
**Feed completeness (capabilities selected):** when the optional capabilities — image, poll, edit — are selected up front, the Vise arm completed **97–100%** of an 11-item feed checklist across three agents (Cursor/Composer, Claude/Sonnet, Codex/GPT). This is *absolute* completeness from answering Vise's capability questions — **not** a lift over a baseline.
|
|
145
151
|
|
|
152
|
+
**Engagement Intelligence advisory quality:** a 24-case held-out dogfood set supports the advisory positioning: expected available strategies surfaced in **17/17** cases, multiple strategy options were visible in **20/20** strategy-offer cases, rationale/tradeoff evidence was visible in **24/24**, and no-fit plus availability-gated cases preserved their boundaries. The same run retained **4 ranking-calibration concerns**, so this is **not** a claim of top-1 ranking confidence, autonomous strategy selection, business-outcome lift, or a calibrated score.
|
|
153
|
+
|
|
146
154
|
**Boundaries:** these are social.plus's own measurements on greenfield work — self-reported, no third-party audit, measured on earlier builds, and not universal claims. Negative results travel with them: no measured advantage on day-2 bug fixing, and the design-conformance arms were measured at different times (the figure is the robust by-name-token signal, not pixel perfection).
|
|
147
155
|
|
|
148
156
|
<sub>Cursor, Claude, Codex, GitHub Copilot, VS Code, and other product names are trademarks of their respective owners; social.plus is not affiliated with or endorsed by them. Benchmark figures are from social.plus's own measurements.</sub>
|
|
@@ -155,21 +163,23 @@ The mechanism is the durable claim: a checked loop beats the same rules in the p
|
|
|
155
163
|
| **React Native** | ✅ Full | `tsc`, `npm lint`, SDK import smoke |
|
|
156
164
|
| **Flutter / Dart** | ✅ Full | `flutter analyze`, `flutter test` |
|
|
157
165
|
| **Android (Kotlin)** | ✅ Full | Gradle assemble, unit tests |
|
|
158
|
-
| **iOS (Swift)** | ✅ Full | Static rules fully operational (tree-sitter AST for highest-risk rules);
|
|
166
|
+
| **iOS (Swift)** | ✅ Full | Static rules fully operational (tree-sitter AST for highest-risk rules); `Package.swift` enables a SwiftPM manifest sensor, and `.xcodeproj`/`.xcworkspace` enables a guarded `xcodebuild` sensor when available |
|
|
159
167
|
|
|
160
168
|
Each platform has dozens of rules across 10 compliance domains (feed, comments, moderation, chat, secrets, session & auth, notifications, live objects, logging hygiene, design tokens).
|
|
161
169
|
|
|
162
170
|
## CLI Reference
|
|
163
171
|
|
|
164
|
-
Run `vise <command> --help` for full flags. JSON output is the default for agent-facing commands.
|
|
172
|
+
Run `vise <command> --help` for full flags. JSON output is the default for agent-facing commands; the read commands (`check`, `status`, `plan`, `doctor`, `explain`, `explore`) also accept `--format human` for a readable summary.
|
|
165
173
|
|
|
166
174
|
### Inspect, plan, initialize
|
|
167
175
|
|
|
168
176
|
| Command | Purpose |
|
|
169
177
|
|---|---|
|
|
178
|
+
| `vise explore "<request>"` | Pre-credentials discovery: map a request to what social.plus offers (candidate outcome, capabilities, docs, next command). No project or API key needed |
|
|
170
179
|
| `vise doctor` | Verify install; print version, install path, docs source |
|
|
171
180
|
| `vise inspect [path]` | Detect platform, monorepo surfaces, design signals, available sensors |
|
|
172
|
-
| `vise plan [path] --request "..."` | Grounded implementation plan with intake questions and docs citations |
|
|
181
|
+
| `vise plan [path] --request "..." [--summary]` | Grounded implementation plan with intake questions and docs citations; `--summary` prints a compact route/intake view |
|
|
182
|
+
| `vise plan --summary "..."` | Shortcut for quick routing dogfood or discovery when the current directory is the repo |
|
|
173
183
|
| `vise plan-harness [path] --request "..."` | Pre-planning step: build the harness around the request |
|
|
174
184
|
| `vise init [path] --request "..." [--answer key=value]` | Write the `sp-vise/` compliance contract once blocking intake is answered; exits 7 (`needs-clarification`) otherwise |
|
|
175
185
|
| `vise workplan next [path] --request "..."` | For broad social requests: print the next uncompleted surface and its focused commands |
|
|
@@ -180,7 +190,7 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
|
|
|
180
190
|
|
|
181
191
|
| Command | Purpose |
|
|
182
192
|
|---|---|
|
|
183
|
-
| `vise creative [path] --request "..." [--requirements <path\|none>] [--prototype <html>] [--ranking-preview]` | Write an advisory Engagement Intelligence brief with candidate solution variants; `--ranking-preview` adds
|
|
193
|
+
| `vise creative [path] --request "..." [--requirements <path\|none>] [--prototype <html>] [--ranking-preview]` | Write an advisory Engagement Intelligence brief with multiple candidate solution variants, rationale, tradeoffs, and review gaps; `--ranking-preview` adds opt-in, local-only review context that never reorders the default candidates |
|
|
184
194
|
| `vise creative accept [path] --variant <id>` | Record the selected variant so `plan`/`init`/`workplan` carry it forward; `--variant none --rationale "..."` records a catalog-gap signal instead |
|
|
185
195
|
| `vise ux-harness [path]` | Generate advisory UX expectations from the accepted selection |
|
|
186
196
|
| `vise experience compile [path]` | Compile the accepted variant into an implementation artifact plan |
|
|
@@ -189,7 +199,7 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
|
|
|
189
199
|
| `vise learning record [path]` | Append a local-only learning event; refreshes the learning summary |
|
|
190
200
|
| `vise learning show [path]` | Read the local learning summary (never changes recommendations) |
|
|
191
201
|
|
|
192
|
-
Everything in this group is local and advisory: no uploads, no `vise check` exit-code changes, no auto-accepted variants,
|
|
202
|
+
Everything in this group is local and advisory: no uploads, no `vise check` exit-code changes, no auto-accepted variants, no top-1 confidence claim, and no calibrated score.
|
|
193
203
|
|
|
194
204
|
### Design contract
|
|
195
205
|
|
|
@@ -216,6 +226,8 @@ Everything in this group is local and advisory: no uploads, no `vise check` exit
|
|
|
216
226
|
|---|---|
|
|
217
227
|
| `vise check [path]` | Re-validate against the recorded contract: `green`, `needs-attestation`, `deterministic-failures`, `blocked`, or `contract-drift` |
|
|
218
228
|
| `vise check [path] --ci` | Read-only variant that exits non-zero unless green (for CI) |
|
|
229
|
+
| `vise check [path] --new-only` | **Brownfield gate.** With a recorded baseline, gate only on findings introduced *since* the baseline (pre-existing ones are reported but excluded). Default `check` always gates on everything |
|
|
230
|
+
| `vise baseline [path]` | Snapshot the current pre-existing findings to `sp-vise/baseline.json` so `check --new-only` can separate legacy debt from new gaps. `vise init --baseline` records it at init time |
|
|
219
231
|
| `vise validate [path]` | Run the deterministic validators only (no attestation comparison) |
|
|
220
232
|
| `vise sync [path]` | Persist deterministic-pass evidence to `sp-vise/attestations/` |
|
|
221
233
|
| `vise attest [path] --rule <id> --signer host-agent --confidence high --evidence-file evidence.json --rationale "..."` | Record an attestation when a rule passes through architecture the deterministic check can't see |
|
|
@@ -299,7 +311,7 @@ Vise writes local planning, compliance, design, and evidence artifacts under `sp
|
|
|
299
311
|
| `sp-vise/inspection.json` | `vise init` | Platform, surface, and design signals detected at init |
|
|
300
312
|
| `sp-vise/attestations/*.json` | `vise sync` / `vise attest` | Per-rule evidence: signer, confidence, rationale, source fingerprints for drift detection |
|
|
301
313
|
| `sp-vise/creative-brief.json` + `creative-brief.md` | `vise creative` | Advisory brief: goals, archetypes, candidate variants (JSON + human-readable) |
|
|
302
|
-
| `sp-vise/candidate-ranking-preview.json` | `vise creative --ranking-preview` | Opt-in local ranking preview; `experience_score: null`, no uploads, no default-order change |
|
|
314
|
+
| `sp-vise/candidate-ranking-preview.json` | `vise creative --ranking-preview` | Opt-in local ranking preview for review context; `experience_score: null`, no uploads, no default-order change, no top-1 confidence claim |
|
|
303
315
|
| `sp-vise/creative-selection.json` | `vise creative accept` | Accepted variant and plan/workplan feed-forward context |
|
|
304
316
|
| `sp-vise/catalog-gap.json` | `vise creative accept --variant none` | Local-only no-fit signal for human catalog review |
|
|
305
317
|
| `sp-vise/ux-harness.json` | `vise creative accept` or `vise ux-harness` | Advisory UX pattern expectations and anti-patterns |
|
package/dist/capabilities.js
CHANGED
|
@@ -1256,6 +1256,7 @@ export function assessSelectedOptionalCapabilities(source, outcome, selectedIds
|
|
|
1256
1256
|
note: "Selected optional capabilities are enforced only after the user or host agent opts in through `feed_optional_capabilities`. They are source sensors, not baseline compliance rules.",
|
|
1257
1257
|
};
|
|
1258
1258
|
}
|
|
1259
|
+
const MIN_SCOPE_OMIT_REASON_CHARS = 8;
|
|
1259
1260
|
export function assessCompleteness(source, outcome) {
|
|
1260
1261
|
const caps = baselineCapabilities(outcome);
|
|
1261
1262
|
const optOuts = new Map();
|
|
@@ -1265,12 +1266,14 @@ export function assessCompleteness(source, outcome) {
|
|
|
1265
1266
|
while ((match = omitPattern.exec(source)) !== null) {
|
|
1266
1267
|
const id = match[1].toLowerCase();
|
|
1267
1268
|
const reason = (match[2] ?? "").trim();
|
|
1268
|
-
if (reason) {
|
|
1269
|
+
if (reason.length >= MIN_SCOPE_OMIT_REASON_CHARS) {
|
|
1269
1270
|
optOuts.set(id, reason);
|
|
1270
1271
|
invalidOptOuts.delete(id);
|
|
1271
1272
|
}
|
|
1272
1273
|
else if (!optOuts.has(id)) {
|
|
1273
|
-
invalidOptOuts.set(id,
|
|
1274
|
+
invalidOptOuts.set(id, reason.length === 0
|
|
1275
|
+
? "scope-omit marker must include a reason"
|
|
1276
|
+
: `scope-omit reason is too short ("${reason}") — give an auditable reason of at least ${MIN_SCOPE_OMIT_REASON_CHARS} characters`);
|
|
1274
1277
|
}
|
|
1275
1278
|
}
|
|
1276
1279
|
const present = [];
|
|
@@ -1292,7 +1295,7 @@ export function assessCompleteness(source, outcome) {
|
|
|
1292
1295
|
missing.push({
|
|
1293
1296
|
id: cap.id,
|
|
1294
1297
|
label: cap.label,
|
|
1295
|
-
hint: invalidReason ? `${cap.hint};
|
|
1298
|
+
hint: invalidReason ? `${cap.hint}; ${invalidReason}` : cap.hint,
|
|
1296
1299
|
});
|
|
1297
1300
|
}
|
|
1298
1301
|
}
|
package/dist/explore.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { capabilityChecklist } from "./capabilities.js";
|
|
2
|
+
import { classifyOutcome, getOutcomeDefinition } from "./outcomes.js";
|
|
3
|
+
const DISCOVERY_MENU = [
|
|
4
|
+
"setup-sdk",
|
|
5
|
+
"add-feed",
|
|
6
|
+
"add-comments",
|
|
7
|
+
"add-chat",
|
|
8
|
+
"add-community",
|
|
9
|
+
"add-follow",
|
|
10
|
+
"add-moderation",
|
|
11
|
+
"add-notifications",
|
|
12
|
+
"setup-push",
|
|
13
|
+
"setup-live-data",
|
|
14
|
+
];
|
|
15
|
+
function outcomeBrief(outcome, platform, request) {
|
|
16
|
+
const def = getOutcomeDefinition(outcome);
|
|
17
|
+
return {
|
|
18
|
+
outcome,
|
|
19
|
+
summary: def.interpretation,
|
|
20
|
+
capabilities: capabilityChecklist(outcome).map((capability) => ({ id: capability.id, label: capability.label })),
|
|
21
|
+
docs: def.docs(platform).slice(0, 4).map((doc) => ({ path: doc.path, reason: doc.reason })),
|
|
22
|
+
start: `vise plan . --request ${JSON.stringify(request)}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function exploreRequest(request, platform = "typescript") {
|
|
26
|
+
const matched = classifyOutcome(request);
|
|
27
|
+
const isKnown = matched !== "unknown" && DISCOVERY_MENU.includes(matched);
|
|
28
|
+
if (isKnown) {
|
|
29
|
+
return {
|
|
30
|
+
kind: "exploration",
|
|
31
|
+
request,
|
|
32
|
+
platform,
|
|
33
|
+
matched_outcome: matched,
|
|
34
|
+
routes: [outcomeBrief(matched, platform, request)],
|
|
35
|
+
also_available: DISCOVERY_MENU.filter((outcome) => outcome !== matched).map((outcome) => ({
|
|
36
|
+
outcome,
|
|
37
|
+
summary: getOutcomeDefinition(outcome).interpretation,
|
|
38
|
+
})),
|
|
39
|
+
note: "Discovery is pre-credentials and read-only — no project or API key needed. When you're ready to build, `vise plan`/`vise init` starts a tracked integration.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
kind: "exploration",
|
|
44
|
+
request,
|
|
45
|
+
platform,
|
|
46
|
+
matched_outcome: null,
|
|
47
|
+
unmatched_note: "This request didn't map to a single social.plus outcome. Below is everything Vise can guide you through — pick one and run its `start` command.",
|
|
48
|
+
routes: DISCOVERY_MENU.map((outcome) => outcomeBrief(outcome, platform, request)),
|
|
49
|
+
note: "Discovery is pre-credentials and read-only — no project or API key needed. When you're ready to build, `vise plan`/`vise init` starts a tracked integration.",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const STATUS_GLOSS = {
|
|
2
|
+
green: "All applicable rules pass.",
|
|
3
|
+
"needs-attestation": "Some rules need a host-agent or human attestation before they can pass.",
|
|
4
|
+
"deterministic-failures": "A deterministic sensor found a violation in the source.",
|
|
5
|
+
blocked: "An external prerequisite the customer must provide is missing.",
|
|
6
|
+
"contract-drift": "The recorded ruleset no longer matches the installed Vise — re-run init.",
|
|
7
|
+
"completeness-gap": "Required capabilities are neither built nor opted out (scope-omit) yet.",
|
|
8
|
+
"selected-capability-failures": "A selected optional capability's sensor failed.",
|
|
9
|
+
"needs-clarification": "Blocking intake questions are unresolved.",
|
|
10
|
+
};
|
|
11
|
+
function asString(value) {
|
|
12
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function asArray(value) {
|
|
15
|
+
return Array.isArray(value) ? value : [];
|
|
16
|
+
}
|
|
17
|
+
function bullet(lines, indent = " ") {
|
|
18
|
+
return lines.map((line) => `${indent}- ${line}`).join("\n");
|
|
19
|
+
}
|
|
20
|
+
function renderCompliance(payload) {
|
|
21
|
+
const status = asString(payload.status) ?? "unknown";
|
|
22
|
+
const exitCode = typeof payload.exitCode === "number" ? payload.exitCode : undefined;
|
|
23
|
+
const outcome = asString(payload.outcome);
|
|
24
|
+
const surface = asString(payload.surfacePath);
|
|
25
|
+
const out = [];
|
|
26
|
+
const head = `social.plus compliance: ${status.toUpperCase()}${exitCode !== undefined ? ` (exit ${exitCode})` : ""}`;
|
|
27
|
+
const scope = [outcome ? `outcome: ${outcome}` : null, surface ? `surface: ${surface}` : null].filter(Boolean).join(", ");
|
|
28
|
+
out.push(scope ? `${head} — ${scope}` : head);
|
|
29
|
+
if (STATUS_GLOSS[status])
|
|
30
|
+
out.push(STATUS_GLOSS[status]);
|
|
31
|
+
const summary = payload.summary;
|
|
32
|
+
if (summary && Object.keys(summary).length > 0) {
|
|
33
|
+
const parts = Object.entries(summary).map(([key, count]) => `${count} ${key}`);
|
|
34
|
+
out.push("", `Rules: ${parts.join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
const basisNote = asString(payload.evidence_basis_note);
|
|
37
|
+
if (basisNote)
|
|
38
|
+
out.push(`Evidence: ${basisNote}`);
|
|
39
|
+
const blocking = asArray(payload.rules)
|
|
40
|
+
.filter((r) => typeof r === "object" && r !== null)
|
|
41
|
+
.filter((r) => {
|
|
42
|
+
const s = asString(r.status);
|
|
43
|
+
return s !== undefined && s !== "deterministic-pass" && s !== "attested" && s !== "advisory";
|
|
44
|
+
});
|
|
45
|
+
if (blocking.length > 0) {
|
|
46
|
+
out.push("", "Needs action:");
|
|
47
|
+
out.push(bullet(blocking.slice(0, 20).map((r) => {
|
|
48
|
+
const id = asString(r.ruleId) ?? asString(r.contractRuleId) ?? "(rule)";
|
|
49
|
+
const reason = asString(r.reason) ?? asString(r.status) ?? "";
|
|
50
|
+
return reason ? `${id} [${asString(r.status)}]: ${reason}` : `${id} [${asString(r.status)}]`;
|
|
51
|
+
})));
|
|
52
|
+
if (blocking.length > 20)
|
|
53
|
+
out.push(` …and ${blocking.length - 20} more`);
|
|
54
|
+
}
|
|
55
|
+
const completeness = payload.completeness;
|
|
56
|
+
const missing = asArray(completeness?.missing).filter((m) => typeof m === "object" && m !== null);
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
out.push("", "Missing capabilities (build, or `// vise: scope-omit <id> — <reason>`):");
|
|
59
|
+
out.push(bullet(missing.map((m) => `${asString(m.id) ?? "(capability)"}: ${asString(m.hint) ?? asString(m.label) ?? ""}`.trim())));
|
|
60
|
+
}
|
|
61
|
+
const next = asString(payload.nextStep);
|
|
62
|
+
if (next)
|
|
63
|
+
out.push("", `Next: ${next}`);
|
|
64
|
+
return out.join("\n");
|
|
65
|
+
}
|
|
66
|
+
function renderPlan(payload) {
|
|
67
|
+
const out = [];
|
|
68
|
+
const outcome = asString(payload.outcome) ?? "(unrouted)";
|
|
69
|
+
const platform = asString(payload.platform);
|
|
70
|
+
out.push(`Plan: ${outcome}${platform ? ` — ${platform}` : ""}`);
|
|
71
|
+
const sp = payload.solutionPath;
|
|
72
|
+
if (sp) {
|
|
73
|
+
const rec = asString(sp.recommendation);
|
|
74
|
+
const conf = asString(sp.confidence);
|
|
75
|
+
const summary = asString(sp.summary);
|
|
76
|
+
out.push(`Solution path: ${rec ?? "(n/a)"}${conf ? ` (${conf})` : ""}${summary ? ` — ${summary}` : ""}`);
|
|
77
|
+
}
|
|
78
|
+
const uikit = payload.uikitCustomization;
|
|
79
|
+
if (uikit) {
|
|
80
|
+
out.push(`UIKit customization: ${asString(uikit.recommendedLevel) ?? "(n/a)"} (${asString(uikit.status) ?? "n/a"})`);
|
|
81
|
+
}
|
|
82
|
+
const decisions = asArray(payload.decisionsRequired);
|
|
83
|
+
if (decisions.length > 0) {
|
|
84
|
+
out.push("", "Decisions required:");
|
|
85
|
+
out.push(bullet(decisions.map((d) => (typeof d === "string" ? d : asString(d.question) ?? JSON.stringify(d)))));
|
|
86
|
+
}
|
|
87
|
+
const steps = asArray(payload.implementationSteps);
|
|
88
|
+
if (steps.length > 0) {
|
|
89
|
+
out.push("", "Build steps:");
|
|
90
|
+
out.push(steps
|
|
91
|
+
.slice(0, 30)
|
|
92
|
+
.map((s, i) => ` ${i + 1}. ${typeof s === "string" ? s : asString(s.step) ?? asString(s.description) ?? JSON.stringify(s)}`)
|
|
93
|
+
.join("\n"));
|
|
94
|
+
}
|
|
95
|
+
const inputs = asArray(payload.requiredInputs).filter((i) => typeof i === "string");
|
|
96
|
+
if (inputs.length > 0) {
|
|
97
|
+
out.push("", "Required inputs:");
|
|
98
|
+
out.push(bullet(inputs));
|
|
99
|
+
}
|
|
100
|
+
const intake = payload.intake;
|
|
101
|
+
const questions = asArray(intake?.questions).filter((q) => typeof q === "object" && q !== null);
|
|
102
|
+
const blockingQs = questions.filter((q) => q.blocksImplementationWhenMissing === true);
|
|
103
|
+
if (blockingQs.length > 0) {
|
|
104
|
+
out.push("", "Open intake (blocking):");
|
|
105
|
+
out.push(bullet(blockingQs.map((q) => `${asString(q.id) ?? "?"}: ${asString(q.prompt) ?? asString(q.question) ?? ""}`.trim())));
|
|
106
|
+
}
|
|
107
|
+
const next = asString(payload.nextStep);
|
|
108
|
+
if (next)
|
|
109
|
+
out.push("", `Next: ${next}`);
|
|
110
|
+
return out.join("\n");
|
|
111
|
+
}
|
|
112
|
+
function renderDoctor(payload) {
|
|
113
|
+
const out = [];
|
|
114
|
+
out.push(`${asString(payload.package) ?? "vise"} ${asString(payload.version) ?? ""} — ${asString(payload.status) ?? "?"}`.trim());
|
|
115
|
+
if (payload.node)
|
|
116
|
+
out.push(`node: ${asString(payload.node)} (requires ${asString(payload.requiredNodeMajor) ?? "?"})`);
|
|
117
|
+
out.push(`docs: ${asString(payload.docsSource) ?? "?"}${payload.docsRoot ? ` (${asString(payload.docsRoot)})` : ""}`);
|
|
118
|
+
if (payload.transport)
|
|
119
|
+
out.push(`transport: ${asString(payload.transport)}`);
|
|
120
|
+
const tools = asArray(payload.tools);
|
|
121
|
+
if (tools.length > 0)
|
|
122
|
+
out.push(`tools: ${tools.length} registered`);
|
|
123
|
+
const support = asString(payload.support);
|
|
124
|
+
if (support)
|
|
125
|
+
out.push(`support: ${support}`);
|
|
126
|
+
return out.join("\n");
|
|
127
|
+
}
|
|
128
|
+
function renderExplain(payload) {
|
|
129
|
+
if (payload.kind === "rule-index") {
|
|
130
|
+
const out = [`${payload.count} compliance rules. Run \`vise explain <id>\` for the full rule.`, ""];
|
|
131
|
+
for (const entry of asArray(payload.rules).filter((e) => typeof e === "object" && e !== null)) {
|
|
132
|
+
const id = asString(entry.id) ?? "(rule)";
|
|
133
|
+
const sev = asString(entry.severity);
|
|
134
|
+
const outcomes = asArray(entry.outcomes).join(", ");
|
|
135
|
+
out.push(` ${id}${sev ? ` (${sev})` : ""}${outcomes ? ` — ${outcomes}` : ""}`);
|
|
136
|
+
}
|
|
137
|
+
return out.join("\n");
|
|
138
|
+
}
|
|
139
|
+
if (payload.kind === "product_expectation") {
|
|
140
|
+
const out = [`${asString(payload.id) ?? "(rule)"} — validated by ${asArray(payload.contract_rules).length} contract rule(s):`];
|
|
141
|
+
for (const c of asArray(payload.contract_rules).filter((e) => typeof e === "object" && e !== null)) {
|
|
142
|
+
out.push(` ${asString(c.contract_rule_id) ?? "?"} — ${asString(c.title) ?? ""}`.trimEnd());
|
|
143
|
+
}
|
|
144
|
+
return out.join("\n");
|
|
145
|
+
}
|
|
146
|
+
const out = [];
|
|
147
|
+
const id = asString(payload.id) ?? "(rule)";
|
|
148
|
+
const contractId = asString(payload.contract_rule_id);
|
|
149
|
+
const title = asString(payload.title);
|
|
150
|
+
const sev = asString(payload.severity);
|
|
151
|
+
const version = payload.version;
|
|
152
|
+
out.push(`${id}${contractId ? ` (${contractId})` : ""}${title ? ` — ${title}` : ""}${sev ? ` [${sev}${version !== undefined ? `, v${version}` : ""}]` : ""}`);
|
|
153
|
+
const rationale = asString(payload.rationale);
|
|
154
|
+
if (rationale)
|
|
155
|
+
out.push(rationale);
|
|
156
|
+
const applies = payload.applies_when;
|
|
157
|
+
if (applies) {
|
|
158
|
+
const o = asArray(applies.outcomes).join(", ");
|
|
159
|
+
const p = asArray(applies.platforms).join(", ");
|
|
160
|
+
const parts = [o ? `outcomes: ${o}` : null, p ? `platforms: ${p}` : null].filter(Boolean).join("; ");
|
|
161
|
+
if (parts)
|
|
162
|
+
out.push(`Applies — ${parts}`);
|
|
163
|
+
}
|
|
164
|
+
const feedforward = asString(payload.feedforward);
|
|
165
|
+
if (feedforward)
|
|
166
|
+
out.push(`Feedforward: ${feedforward}`);
|
|
167
|
+
const symptoms = asArray(payload.symptoms).filter((s) => typeof s === "string");
|
|
168
|
+
if (symptoms.length > 0)
|
|
169
|
+
out.push(`Symptoms: ${symptoms.join(" | ")}`);
|
|
170
|
+
const attestation = payload.attestation;
|
|
171
|
+
if (attestation)
|
|
172
|
+
out.push(`Attestation: ${attestation.allowed ? "allowed" : "not allowed"}${attestation.host_agent_min_confidence ? ` (host-agent min ${asString(attestation.host_agent_min_confidence)})` : ""}`);
|
|
173
|
+
const digest = asString(payload.rule_digest);
|
|
174
|
+
if (digest)
|
|
175
|
+
out.push(`digest: ${digest}`);
|
|
176
|
+
return out.join("\n");
|
|
177
|
+
}
|
|
178
|
+
function renderExplore(payload) {
|
|
179
|
+
const out = [];
|
|
180
|
+
const request = asString(payload.request) ?? "";
|
|
181
|
+
const matched = asString(payload.matched_outcome);
|
|
182
|
+
out.push(matched
|
|
183
|
+
? `social.plus — best match for "${request}": ${matched}`
|
|
184
|
+
: `social.plus — what you can build (no single match for "${request}"):`);
|
|
185
|
+
for (const route of asArray(payload.routes).filter((r) => typeof r === "object" && r !== null)) {
|
|
186
|
+
out.push("", `▸ ${asString(route.outcome) ?? "(outcome)"} — ${asString(route.summary) ?? ""}`.trimEnd());
|
|
187
|
+
const caps = asArray(route.capabilities).filter((c) => typeof c === "object" && c !== null);
|
|
188
|
+
if (caps.length > 0)
|
|
189
|
+
out.push(` capabilities: ${caps.map((c) => asString(c.id)).filter(Boolean).join(", ")}`);
|
|
190
|
+
const start = asString(route.start);
|
|
191
|
+
if (start)
|
|
192
|
+
out.push(` start: ${start}`);
|
|
193
|
+
}
|
|
194
|
+
const also = asArray(payload.also_available).filter((a) => typeof a === "object" && a !== null);
|
|
195
|
+
if (also.length > 0) {
|
|
196
|
+
out.push("", "Also available:");
|
|
197
|
+
out.push(bullet(also.map((a) => `${asString(a.outcome) ?? "?"} — ${asString(a.summary) ?? ""}`.trimEnd())));
|
|
198
|
+
}
|
|
199
|
+
const note = asString(payload.note);
|
|
200
|
+
if (note)
|
|
201
|
+
out.push("", note);
|
|
202
|
+
return out.join("\n");
|
|
203
|
+
}
|
|
204
|
+
const RENDERERS = {
|
|
205
|
+
check: renderCompliance,
|
|
206
|
+
status: renderCompliance,
|
|
207
|
+
plan: renderPlan,
|
|
208
|
+
doctor: renderDoctor,
|
|
209
|
+
explain: renderExplain,
|
|
210
|
+
explore: renderExplore,
|
|
211
|
+
};
|
|
212
|
+
export function wantsHumanFormat(format) {
|
|
213
|
+
return format === "human";
|
|
214
|
+
}
|
|
215
|
+
export function renderHuman(command, payload) {
|
|
216
|
+
const renderer = RENDERERS[command];
|
|
217
|
+
if (!renderer || typeof payload !== "object" || payload === null) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
return renderer(payload);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dist/outcomes.js
CHANGED
|
@@ -42,20 +42,21 @@ const CHAT_PATTERNS = [
|
|
|
42
42
|
];
|
|
43
43
|
const COMMUNITY_PATTERNS = [
|
|
44
44
|
/\b(create|creating|build|building|manage|managing|join|joining|leave|leaving|set ?up)\s+(a\s+|the\s+)?communit/,
|
|
45
|
-
/\bcommunit\w*\s+(member|membership|role|invit|categor|setting|management|directory|discovery)/,
|
|
45
|
+
/\bcommunit\w*\s+(member|membership|role|invit|categor|setting|management|directory|discovery|profile|page)/,
|
|
46
46
|
/\bcommunity\s+(creation|management|members?|roles?|invitations?|categories|settings|moderation)\b/,
|
|
47
47
|
];
|
|
48
48
|
const FOLLOW_PATTERNS = [
|
|
49
49
|
/\b(follow|unfollow|follower|followers|following)\b/,
|
|
50
50
|
/\b(social graph|user relationship|follow request|followers? list|following list)\b/,
|
|
51
|
+
/\b(?:user'?s?|member|account|my|your|their)\s+profile\b|\buser_profile_page\b/,
|
|
51
52
|
/\bblocked\s+users?\b/,
|
|
52
53
|
/\bmanage\s+blocked\b/,
|
|
53
54
|
/\bblock\s*list\b/,
|
|
54
55
|
/\bunblock\b/,
|
|
55
56
|
];
|
|
56
57
|
const NOTIFICATION_PATTERNS = [
|
|
57
|
-
/\b(
|
|
58
|
-
/\
|
|
58
|
+
/\b(notifications?\s+tray|notifications?\s+cent(?:er|re)|in-?app notifications?)/,
|
|
59
|
+
/\bnotifications?\s+(?:setting|preference)/,
|
|
59
60
|
];
|
|
60
61
|
const TROUBLESHOOT_PATTERNS = [/\b(error|broken|crash|not working|fail|timeout|401|403)\b/];
|
|
61
62
|
const VALIDATE_PATTERNS = [/\b(validate|check|correct|setup right|initiali[sz])\b/];
|
|
@@ -212,7 +213,7 @@ export function liveDataPlatformPath(platform) {
|
|
|
212
213
|
const setupSdk = {
|
|
213
214
|
id: "setup-sdk",
|
|
214
215
|
patterns: SETUP_PATTERNS,
|
|
215
|
-
interpretation: "
|
|
216
|
+
interpretation: "Set up the social.plus SDK — client creation with the correct region/endpoint, secure session login, and access-token renewal — the foundation every other social.plus feature builds on.",
|
|
216
217
|
docsQuery: (platform) => `${platform} quick start setup`,
|
|
217
218
|
docs: (platform) => [
|
|
218
219
|
platformQuickStart(platform),
|
|
@@ -489,8 +490,8 @@ const addFeed = {
|
|
|
489
490
|
docsQuery: (platform) => `${platform} social feed posts`,
|
|
490
491
|
docs: (platform) => [
|
|
491
492
|
{
|
|
492
|
-
path: "social-plus-sdk/social/posts",
|
|
493
|
-
reason: "Canonical
|
|
493
|
+
path: "social-plus-sdk/social/content-management/posts/creation/text-post",
|
|
494
|
+
reason: "Canonical post model and creation API entrypoint; pair with the live-objects/collections docs below for feed querying.",
|
|
494
495
|
},
|
|
495
496
|
{
|
|
496
497
|
path: "social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/overview",
|
|
@@ -601,7 +602,7 @@ const addFeed = {
|
|
|
601
602
|
{
|
|
602
603
|
step: "Fetch the canonical social/feed docs and use platform-appropriate live collection patterns.",
|
|
603
604
|
evidence: [
|
|
604
|
-
"social-plus-sdk/social/posts",
|
|
605
|
+
"social-plus-sdk/social/content-management/posts/creation/text-post",
|
|
605
606
|
"social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/overview",
|
|
606
607
|
],
|
|
607
608
|
},
|
|
@@ -633,11 +634,11 @@ const addFeed = {
|
|
|
633
634
|
{ step: "Implement loading, empty, error, and data states.", evidence: ["implementationRules.file-specific edits"] },
|
|
634
635
|
{
|
|
635
636
|
step: "In post card renderers, resolve each media type from the parent post OR its childrenPosts — in a feed the parent is usually dataType 'text' and the image/video/poll/clip/room rides on a child post. Handle at minimum: text, image, video, file, poll, clip, room. Do not render placeholder labels like '[Image post]' or '[Poll post]'; read the SDK data/accessors and render the actual content. Do NOT gate media on the parent's dataType alone (e.g. post.dataType === 'poll') — that never matches a text parent and the content silently never renders. When rendering the text body itself (post or comment), apply @mention highlights if metadata carries mention entries ({ type: 'user', index, length, userId }, length excluding the '@'): wrap each [index, index + length + 1] span in a styled element and resolve userId to a display name, rather than printing raw text. Pass mentionees on create so mentioned users are notified.",
|
|
636
|
-
evidence: ["social-plus-sdk/social/posts", "intake.feed_post_type_scope", "capabilityAvailability.available"],
|
|
637
|
+
evidence: ["social-plus-sdk/social/content-management/posts/creation/text-post", "intake.feed_post_type_scope", "capabilityAvailability.available"],
|
|
637
638
|
},
|
|
638
639
|
{
|
|
639
640
|
step: "Read SDK objects through their own typed accessors and types (e.g. post.getImageInfo(), the Amity.* types) instead of casting return values to hand-written shapes like (post.data as { text?: string }). A hand-written cast silences the type-checker, so when an SDK upgrade renames a field your build still compiles and the bug only surfaces at runtime — reading through SDK types lets tsc/analyze flag the breakage.",
|
|
640
|
-
evidence: ["social-plus-sdk/social/posts"],
|
|
641
|
+
evidence: ["social-plus-sdk/social/content-management/posts/creation/text-post"],
|
|
641
642
|
},
|
|
642
643
|
{
|
|
643
644
|
step: "For community and user avatars, use the SDK-provided URL (community.avatar?.fileUrl / community.avatarImage?.getUrl / community.getAvatar()?.getUrl) and fall back to an initial only when the field is absent. Do not derive an initial from the display name as the sole identifier.",
|
|
@@ -671,12 +672,12 @@ const addFeed = {
|
|
|
671
672
|
step: "If the post composer supports poll creation, implement the two-step creation chain: (1) create the poll with the platform SDK poll repository — returns a Poll/pollId; (2) link that poll into a post with the platform post builder, for example `PostRepository.createPost({ targetType, targetId, data: { text: '', pollId } })` on TypeScript/React Native or Android's dedicated `createPollPost(targetType, targetId, pollId, text, ...)` API. Rendering poll answers (votePoll/unvotePoll) is the read-side; without both creation steps the poll composer silently does nothing. Offer a dedicated poll-builder UI (question input + dynamic answer list) so users can author polls inline.",
|
|
672
673
|
evidence: [
|
|
673
674
|
"social-plus-sdk/social/content-management/posts/creation/poll-post",
|
|
674
|
-
"social-plus-sdk/social/posts",
|
|
675
|
+
"social-plus-sdk/social/content-management/posts/creation/text-post",
|
|
675
676
|
],
|
|
676
677
|
},
|
|
677
678
|
{
|
|
678
679
|
step: "In post card headers, show the post's target context when post.targetType === 'community': display 'Author.displayName › Community.displayName' by subscribing to CommunityRepository.getCommunity(post.targetId, cb) for the live community name.",
|
|
679
|
-
evidence: ["social-plus-sdk/social/communities", "social-plus-sdk/social/posts"],
|
|
680
|
+
evidence: ["social-plus-sdk/social/communities", "social-plus-sdk/social/content-management/posts/creation/text-post"],
|
|
680
681
|
},
|
|
681
682
|
{
|
|
682
683
|
step: "For room-type posts, subscribe to RoomRepository.getRoom(roomId, cb) to get the Room object and display room.title (not room.roomId). Show room.status (live/idle/ended) as a badge.",
|
|
@@ -684,7 +685,7 @@ const addFeed = {
|
|
|
684
685
|
},
|
|
685
686
|
{
|
|
686
687
|
step: "Give each post card an actions menu gated by the viewer's relationship to the post. If the viewer is the author (post.postedUserId === currentUserId), show edit (PostRepository.editPost) and delete (PostRepository.deletePost). For posts the viewer does not own, show report/flag (PostRepository.flagPost). Author-ownership actions and moderator-role actions are distinct — do not show edit/delete to non-authors.",
|
|
687
|
-
evidence: ["social-plus-sdk/social/posts", "social-plus-sdk/social/moderation"],
|
|
688
|
+
evidence: ["social-plus-sdk/social/content-management/posts/creation/text-post", "social-plus-sdk/social/moderation"],
|
|
688
689
|
},
|
|
689
690
|
{ step: "Run validate_setup and detected command sensors after edits.", evidence: ["validate_setup", "run_sensors"] },
|
|
690
691
|
];
|
|
@@ -891,7 +892,7 @@ const addModeration = {
|
|
|
891
892
|
const questions = [
|
|
892
893
|
{
|
|
893
894
|
id: "moderation_target",
|
|
894
|
-
question: "What content types need moderation (
|
|
895
|
+
question: "What content types need moderation (posts, comments, messages, users, or all)?",
|
|
895
896
|
why: "Each content type has different moderation APIs and UI patterns.",
|
|
896
897
|
required: true,
|
|
897
898
|
blocksImplementationWhenMissing: true,
|
|
@@ -922,7 +923,7 @@ const addModeration = {
|
|
|
922
923
|
return filterAnswered(ctx.answers, questions);
|
|
923
924
|
},
|
|
924
925
|
requiredInputs: () => [
|
|
925
|
-
"moderation target types:
|
|
926
|
+
"moderation target types: posts, comments, messages, users, or all",
|
|
926
927
|
"moderation action set: report, block, mute, hide, admin queue",
|
|
927
928
|
"post-action rendering policy for blocked/hidden content",
|
|
928
929
|
],
|