@amityco/social-plus-vise 0.14.1 → 0.14.3
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 +31 -0
- package/README.md +37 -4
- package/dist/capabilities.js +1 -1
- package/dist/outcomes.js +28 -0
- package/dist/server.js +29 -2
- package/dist/tools/compliance.js +40 -9
- package/dist/tools/integration.js +1 -1
- package/dist/tools/project.js +1 -1
- package/dist/tools/sdkFacts.js +364 -0
- package/package.json +8 -3
- package/rules/feed.yaml +20 -0
- package/scripts/import-sdk-surface.mjs +130 -0
- package/sdk-surface/android.json +30310 -0
- package/sdk-surface/flutter.json +13036 -0
- package/sdk-surface/ios.json +36722 -0
- package/sdk-surface/manifest.json +62 -0
- package/sdk-surface/typescript.json +9994 -0
- package/skills/social-plus-vise/SKILL.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ 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.3 — 2026-06-04
|
|
8
|
+
|
|
9
|
+
**Theme:** Completeness-gap enforcement and add-feed guidance hardening (image upload + poll creation + pagination build step).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`completeness-gap` exit code 5:** `vise check` now exits 5 when capabilities from the completeness checklist are neither built nor explicitly opted-out. Precedence: `blocked(3) > deterministic-failures(2) > needs-attestation(1) > completeness-gap(5) > green(0)`. Agents that silently skip capabilities now see exit 5 rather than 0, making the gap visible in the loop's stop condition. The existing `// vise: scope-omit <id> — <reason>` marker remains the correct opt-out path; a recorded reason moves the capability to `opted-out` and does not trigger the exit.
|
|
13
|
+
- **Image upload build step in `add-feed`:** implementation guidance now covers the two-step upload chain — `FileRepository.uploadImage(file)` followed by `PostRepository.createPost({ …, attachments: [{ fileId, fileType: 'image' }] })` — and explicitly flags the common failure mode (calling `uploadImage` but discarding the `fileId` or omitting `attachments`).
|
|
14
|
+
- **Poll creation build step in `add-feed`:** guidance now covers the `PollRepository.createPoll` → `PostRepository.createPost({ data: { pollId } })` creation chain, distinct from the existing poll-rendering (voting) step. Both halves must be present for a working poll composer.
|
|
15
|
+
- **Pagination build step in `add-feed`:** explicit greenfield build step for wiring `onNextPage` / `hasNextPage`, including the ref-capture pattern (`loadMoreRef.current = onNextPage`). Separate from the existing repair/refactor guard, which continues to protect brownfield edits.
|
|
16
|
+
- **`bench:symbols-drift` wired into `npm run validate`:** the SDK symbol drift check now runs as part of the standard gate, surfacing silent SDK renames before they darken rules.
|
|
17
|
+
### Fixed
|
|
18
|
+
- **`targetType` rule recommendation tightened:** the `validateFeedTargetTypeExplicit` validator recommendation now explicitly calls for binding `targetType` to the intake-resolved feed target (e.g. `communityId` from route params, `userId` from auth context) rather than simply avoiding a literal. Prevents agents from introducing an unbound variable that satisfies the literal check while leaving the same intent gap.
|
|
19
|
+
- **Completeness assessment note language corrected:** `vise plan` completeness note and `assessCompleteness` output no longer say "advisory — never fails the check". They now accurately state that missing items cause `vise check` to exit `completeness-gap` (exit code 5).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **`SKILL.md` scope-omit enforcement:** the Required Loop section now treats missing completeness items as a stop condition, not a suggestion. Agents must either implement or `// vise: scope-omit <id> — <reason>` each missing capability before reporting done.
|
|
23
|
+
- **`rules/feed.yaml` — `symbol_anchored` annotations:** 4 rules whose validators key on specific SDK symbol or parameter names (`targetType`, `dataType`, `addReaction`/`removeReaction`, `.dataType` field access) are annotated with `symbol_anchored: true` to mark them for review after SDK major releases. Described in `docs/ARCHITECTURE.md` under "SDK-Version Coupling."
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 0.14.2 — 2026-06-03
|
|
28
|
+
|
|
29
|
+
**Theme:** SDK facts bridge for social.plus Block Factory.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- **Bundled SDK surface snapshot** in `sdk-surface/` for offline/npm use across TypeScript, Android, iOS, and Flutter.
|
|
33
|
+
- **Internal `vise sdk-facts` CLI command and `get_sdk_facts` MCP tool** for Block Factory. The tool is projectless and read-only: it proves SDK symbol/capability/model-symbol facts from the normalized SDK surface without inspecting customer code.
|
|
34
|
+
- **TypeScript Comments/Reactions capability facts** for the first Block Factory validation slice, including React Native aliasing to the TypeScript SDK surface.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
7
38
|
## 0.14.1 — 2026-06-03
|
|
8
39
|
|
|
9
40
|
**Theme:** Retract the enumerative DesignBuildBrief from plan output — our own benchmark said so.
|
package/README.md
CHANGED
|
@@ -43,9 +43,9 @@ See [Usage Flow](#usage-flow) for the full step-by-step diagram.
|
|
|
43
43
|
|
|
44
44
|
## What Vise Does: Agentic Workflow Governance
|
|
45
45
|
|
|
46
|
-
Instead of just providing a CLI or AI skills, Vise implements a technique called **Agentic Workflow Governance**. Think of it as
|
|
46
|
+
Instead of just providing a CLI or AI skills, Vise implements a technique called **Agentic Workflow Governance**. Think of it as a customer-project integration harness: the governed build loop runs inside the target repo, grounded in the real project, real docs, and real validation signals.
|
|
47
47
|
|
|
48
|
-
Vise
|
|
48
|
+
Vise wraps your local coding agents in compliance guardrails when they integrate social.plus SDKs. It inspects your project, grounds the agent in hosted docs, enforces 300 platform-specific compliance rules, checks the generated UI against the customer's design system, surfaces the full SDK feature surface so nothing is silently dropped, and runs your project's own build/lint/typecheck sensors. **Your source code never leaves your machine.**
|
|
49
49
|
|
|
50
50
|
At a glance, Vise sits between the user's prompt and the agent's code changes. The agent still edits the app; Vise turns the request into a grounded plan, records the local contract, and keeps checking until the integration is ready to ship.
|
|
51
51
|
|
|
@@ -81,6 +81,15 @@ Vise validates on three layers, and the layer is set by the *kind of claim* —
|
|
|
81
81
|
|
|
82
82
|
Only correctness is gated (it can be made FP-free); conformance and completeness are surfaced, because "all post types" and "matches the brand" are legitimately scope-dependent. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
83
83
|
|
|
84
|
+
### Relationship to social.plus Block Factory
|
|
85
|
+
|
|
86
|
+
Vise has two deliberately separate roles:
|
|
87
|
+
|
|
88
|
+
- **Customer integration helper:** runs inside customer projects to inspect, plan, validate, and sensor-check social.plus SDK integrations.
|
|
89
|
+
- **Block Factory SDK facts provider:** planned internal mode for social.plus Block Factory to verify SDK capabilities, symbols, and model schemas before reusable blocks are generated or released.
|
|
90
|
+
|
|
91
|
+
Vise owns SDK truth and customer-project governance. social.plus Block Factory owns block contracts, package adapters, previews, conformance tests, and release readiness. See [docs/SDK_FACTS_FOR_BLOCK_FACTORY.md](docs/SDK_FACTS_FOR_BLOCK_FACTORY.md) for the internal provider-side plan.
|
|
92
|
+
|
|
84
93
|
### Design-conformant UI
|
|
85
94
|
|
|
86
95
|
Vise can ingest the customer's aesthetic into a **design contract** and guide generation to match it — from an HTML/CSS prototype (`vise design extract`) or from the host app's own design system across web + Android + Flutter + iOS (`vise design extract --from-project`: CSS vars/Tailwind/token modules, `colors.xml`, Flutter `Color(0x…)`, iOS `.colorset`/Swift). `vise design check` reports token conformance; `vise design preview` writes a visual review; `vise design reference` generates a full visual design-system spec (swatches, type samples, component demos). All advisory.
|
|
@@ -149,7 +158,8 @@ A failing feature without Vise is *invisible* until a user hits it: the code com
|
|
|
149
158
|
- **Vise-arm passes were deterministic-pass**, not attestation exceptions — agents fixed the code. (The grader applies a narrow, *symmetric* auto-attestation for absence / type-stub findings across **all** arms including the controls; it cannot satisfy the acceptance patterns, so it does not tilt the result toward Vise.)
|
|
150
159
|
- **Three arms, separate tooling.** The Rules-as-Markdown arm has no Vise checker available — it cannot run `vise check`.
|
|
151
160
|
- **Built from scratch** (greenfield seed), capable models with prior SDK familiarity. A complementary **bug-fix** benchmark showed **no Vise advantage** — the loop helps on greenfield integration, not local bug hunts.
|
|
152
|
-
- **N=1 per cell.** A strong directional signal (the Chat/Moderation/Push mechanism reproduces across both models), **not** a statistically settled finding
|
|
161
|
+
- **N=1 per cell.** A strong directional signal (the Chat/Moderation/Push mechanism reproduces across both models), **not** a statistically settled finding.
|
|
162
|
+
- **Follow-up evidence cuts both ways.** The pre-registered [Capability Matrix](#benchmark-capability-matrix-v1-pre-registered) (n=3, arm-independent grading, different briefs) found **no Row 1 claim** on chat/moderation/push — chat and moderation tied, push +1, below the registered margin — and a new, registered win on feature completeness instead. Read the two together, not Phase 1 alone.
|
|
153
163
|
- **Full methodology, per-cell analysis, and threats to validity:** [the Commune paper](docs/commune-paper-2026-05-30.md). The [`benchmarks/FINDINGS.html`](benchmarks/FINDINGS.html) and [`benchmarks/RULES_AS_MARKDOWN.html`](benchmarks/RULES_AS_MARKDOWN.html) files are **summary report tables**, not raw transcripts or workspace diffs.
|
|
154
164
|
|
|
155
165
|
### Which mode should I use?
|
|
@@ -162,6 +172,28 @@ A failing feature without Vise is *invisible* until a user hits it: the code com
|
|
|
162
172
|
|
|
163
173
|
---
|
|
164
174
|
|
|
175
|
+
## Benchmark: Capability Matrix v1 (pre-registered)
|
|
176
|
+
|
|
177
|
+
> One row won, one tied, one was withheld on a technicality. The registered protocol requires all three to be reported with equal prominence — so here they are.
|
|
178
|
+
|
|
179
|
+
Phase 1 measured one thing (secondary-compliance gaps) under a harness that overlaps Vise's own ruleset. The **Capability Matrix** is the stricter follow-up: a [pre-registered protocol](benchmarks/capability-matrix/PROTOCOL.md) frozen before any cell ran (7 dated amendments, each committed before the data it governs), **firewalled grading instruments** (authors barred from Vise's rules and config — [authorship record](benchmarks/capability-matrix/AUTHORSHIP.md)), **blind structural judges**, and 27 fresh isolated cells: n=3 seeds per cell, Cursor / Composer 2.5, docs-only control vs Vise loop, identical offline docs bundle in both arms. Full numbers, per-behavior provenance, and the rig-integrity record: [RESULTS.md](benchmarks/capability-matrix/RESULTS.md).
|
|
180
|
+
|
|
181
|
+
| Capability claim | Registered outcome | What the data says |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| **Feature completeness** — open feed request scored against an 11-item firewalled ground truth | ✅ **Claim ships** (won 3/3 seed pairs and the mean) | The Vise loop roughly **halved silently-dropped SDK capabilities: 4.0 vs 7.67 of 11** — by *building more of the SDK surface*, not by surfacing trade-offs. Mechanism not isolated: plan-time capability enumeration, persistence against the check gate, and ~1.8× time-on-task are all live candidates. |
|
|
184
|
+
| **SDK compliance** — chat / moderation / push slices | ➖ **No claim** (7/9 vs 6/9; push +1, below the registered ≥+2 margin) | Brief-explicit behaviors tie, as pre-registered. One level down the defects are *symmetric*: 2/3 control cells shipped ban gates reading a field that doesn't exist on the real SDK (compiles, never fires; 0/3 Vise cells) — while 2/3 Vise cells over-parameterized `targetType` and never bound the brief's value, plausibly steered by Vise's own rule. |
|
|
185
|
+
| **Design conformance** — ambiguous brief, brand-token usage | ⚠️ **Withheld on a technicality** | By-name token usage **+18.1 points** for the contract+check loop (90.3 vs 72.2 mean) ✓ — but the second registered condition (hex literals strictly *lower*) tied at 0–0, so the claim is withheld and reported descriptively. Vise controls are reused from an earlier round (cross-temporal). |
|
|
186
|
+
|
|
187
|
+
**Registered negative results** (the protocol requires these in any publication of the matrix):
|
|
188
|
+
|
|
189
|
+
- **Push stop-condition non-convergence.** Where docs and SDK disagree (push registration — the docs themselves carry a gap warning), "iterate until `vise check` is green" did not converge within the 30-minute cap in *any* Vise cell: agents looped on attestation dialect. Shipped behavior still passed 3/3 — the cost is wall-clock, not correctness. (2/3 control cells also hit the cap doing SDK archaeology.)
|
|
190
|
+
- **Scope-omit affordance unused.** No agent in any cell used the `// vise: scope-omit` surfacing marker or wrote a qualifying scope note. The advisory surfacing mechanism, as shipped in 0.14.1, influenced zero cells.
|
|
191
|
+
- Unchanged from prior rounds: **no Vise advantage on day-2 bug fixes**; **enumerative plan-time design guidance** measured negative twice and was retracted in 0.14.1.
|
|
192
|
+
|
|
193
|
+
All Capability Matrix findings are directional at n=3 under one model and one executor — not settled statistics.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
165
197
|
## Supported Platforms
|
|
166
198
|
|
|
167
199
|
| Platform | Coverage | Sensors |
|
|
@@ -377,11 +409,12 @@ jobs:
|
|
|
377
409
|
|
|
378
410
|
| Code | Meaning |
|
|
379
411
|
|---|---|
|
|
380
|
-
| `0` | All rules pass
|
|
412
|
+
| `0` | All rules pass and all expected capabilities are either present or opted-out |
|
|
381
413
|
| `1` | One or more rules need attestation |
|
|
382
414
|
| `2` | One or more rules have deterministic failures |
|
|
383
415
|
| `3` | One or more blockers fired (missing prerequisite, e.g. `google-services.json`) |
|
|
384
416
|
| `4` | Contract drift — rules in `sp-vise/compliance.json` no longer match the current ruleset |
|
|
417
|
+
| `5` | One or more expected capabilities are neither implemented nor opted-out — add the capability or place `// vise: scope-omit <id> — <reason>` |
|
|
385
418
|
|
|
386
419
|
`vise check --ci` is read-only. It never updates `sp-vise/`. The JSON output includes a `ci` block with structured details for pipeline logs.
|
|
387
420
|
|
package/dist/capabilities.js
CHANGED
|
@@ -367,7 +367,7 @@ export const CAPABILITIES = [
|
|
|
367
367
|
hint: "respect Amity's server-side notification settings/preferences (getSettings)",
|
|
368
368
|
},
|
|
369
369
|
];
|
|
370
|
-
const ADVISORY_NOTE = "
|
|
370
|
+
const ADVISORY_NOTE = "Build each missing capability, or opt out with a recorded reason: `// vise: scope-omit <id> <reason>`. Missing capabilities that are neither built nor opted-out cause `vise check` to exit with status `completeness-gap` (exit code 5).";
|
|
371
371
|
/** The Vise-authored capability checklist for an outcome (for `vise plan` feed-forward). */
|
|
372
372
|
export function capabilityChecklist(outcome) {
|
|
373
373
|
return CAPABILITIES.filter((c) => c.outcomes.includes(outcome)).map((c) => ({ id: c.id, label: c.label, hint: c.hint }));
|
package/dist/outcomes.js
CHANGED
|
@@ -517,6 +517,13 @@ const addFeed = {
|
|
|
517
517
|
"requiredInputs.target screen or route",
|
|
518
518
|
],
|
|
519
519
|
},
|
|
520
|
+
{
|
|
521
|
+
step: "Bind targetType and targetId in createPost (and query) calls to the intake-resolved feed target — e.g. communityId from route params, userId from auth context, or a prop passed from the screen's caller. A variable or prop that is declared but never wired to the actual target is the same intent gap as a hardcoded literal: the validator fires on literals; the correctness gap fires on unbound variables.",
|
|
522
|
+
evidence: [
|
|
523
|
+
"requiredInputs.concrete feed target",
|
|
524
|
+
"requiredInputs.feed scope",
|
|
525
|
+
],
|
|
526
|
+
},
|
|
520
527
|
{
|
|
521
528
|
step: "Fetch the canonical social/feed docs and use platform-appropriate live collection patterns.",
|
|
522
529
|
evidence: [
|
|
@@ -524,6 +531,13 @@ const addFeed = {
|
|
|
524
531
|
"social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/overview",
|
|
525
532
|
],
|
|
526
533
|
},
|
|
534
|
+
{
|
|
535
|
+
step: "Wire feed pagination: after the initial page loads, expose a 'Load more' action (button, scroll trigger, or infinite-query) that calls the collection's next-page method (loadMore() / nextPage() / onNextPage()). Check hasMore / hasNextPage before showing the control so it disappears when the list is exhausted. Use only opaque cursor tokens returned by the SDK — never construct numeric page offsets. On React/TypeScript, useAmityElement with an infinite-query hook is the idiomatic pattern; on Flutter, a ScrollController + loadMore callback; on Android, PagingData with a LazyColumn scroll trigger.",
|
|
536
|
+
evidence: [
|
|
537
|
+
"social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/overview",
|
|
538
|
+
"requiredInputs.feed scope",
|
|
539
|
+
],
|
|
540
|
+
},
|
|
527
541
|
{
|
|
528
542
|
step: "When repairing or refactoring a feed query, preserve existing pagination inputs and state (for example pageToken, nextPage, hasMore/loadMore, or infinite-query wiring) unless the customer explicitly changes feed behavior.",
|
|
529
543
|
evidence: [
|
|
@@ -531,6 +545,13 @@ const addFeed = {
|
|
|
531
545
|
"implementationRules.file-specific edits",
|
|
532
546
|
],
|
|
533
547
|
},
|
|
548
|
+
{
|
|
549
|
+
step: "If the feed includes a post composer, support image/file attachments: (1) let the user pick a file, (2) upload it with FileRepository.uploadImage(file) — returns a File object with a fileId, (3) pass the fileId in the attachments array of createPost: `PostRepository.createPost({ ..., attachments: [{ fileId, fileType: 'image' }] })`. Display the uploaded image in the new post using the returned fileUrl. A composer that shows an image picker but never calls uploadImage, or calls uploadImage but discards the fileId and omits attachments, produces a broken upload flow — both halves must be present.",
|
|
550
|
+
evidence: [
|
|
551
|
+
"social-plus-sdk/social/content-management/posts/creation/image-post",
|
|
552
|
+
"social-plus-sdk/core-concepts/file-handling/upload",
|
|
553
|
+
],
|
|
554
|
+
},
|
|
534
555
|
{ step: "Reuse the host app's existing visual system for the social surface.", evidence: designEvidence },
|
|
535
556
|
{ step: "Implement loading, empty, error, and data states.", evidence: ["implementationRules.file-specific edits"] },
|
|
536
557
|
{
|
|
@@ -569,6 +590,13 @@ const addFeed = {
|
|
|
569
590
|
step: "If the feed includes poll posts, render each answer by branching on answer.dataType: for 'text' use answer.data directly as a string (PollAnswer.data is the text, not an object); for 'image' render answer.image?.fileUrl. Support voting via PollRepository.votePoll(pollId, [answerId]) and PollRepository.unvotePoll(pollId). When poll.status === 'closed' or poll.isVoted is true, display voteCount percentage bars instead of vote buttons. Show the poll's time status from poll.closedAt (when it ended) or poll.closedIn (when it will end) so users know if voting is open.",
|
|
570
591
|
evidence: ["social-plus-sdk/social/content-management/posts/creation/poll-post"],
|
|
571
592
|
},
|
|
593
|
+
{
|
|
594
|
+
step: "If the post composer supports poll creation, implement the two-step creation chain: (1) `PollRepository.createPoll({ question, answers: [{ data: text }], answerType: 'single'|'multiple', closedIn? })` — returns a Poll with a pollId; (2) `PostRepository.createPost({ targetType, targetId, data: { text: '', pollId } })` — links the poll to the feed post. 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.",
|
|
595
|
+
evidence: [
|
|
596
|
+
"social-plus-sdk/social/content-management/posts/creation/poll-post",
|
|
597
|
+
"social-plus-sdk/social/posts",
|
|
598
|
+
],
|
|
599
|
+
},
|
|
572
600
|
{
|
|
573
601
|
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.",
|
|
574
602
|
evidence: ["social-plus-sdk/social/communities", "social-plus-sdk/social/posts"],
|
package/dist/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import { planIntegrationTool } from "./tools/integration.js";
|
|
|
14
14
|
import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
|
|
15
15
|
import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
|
|
16
16
|
import { runSensorsTool } from "./tools/sensors.js";
|
|
17
|
+
import { getSdkFactsTool } from "./tools/sdkFacts.js";
|
|
17
18
|
import { debugIssueTool, debugIssue } from "./tools/debug.js";
|
|
18
19
|
import { packageName, packageVersion } from "./version.js";
|
|
19
20
|
const tools = new Map([
|
|
@@ -39,6 +40,7 @@ const tools = new Map([
|
|
|
39
40
|
designPreviewTool,
|
|
40
41
|
designReferenceTool,
|
|
41
42
|
designInitTokensTool,
|
|
43
|
+
getSdkFactsTool,
|
|
42
44
|
].map((tool) => [tool.name, tool]));
|
|
43
45
|
const bundledSkillName = "social-plus-vise";
|
|
44
46
|
// Pre-rebrand `install-skill` runs created skill dirs/files under this name. We
|
|
@@ -201,6 +203,20 @@ async function handleCli(args) {
|
|
|
201
203
|
});
|
|
202
204
|
return "exit";
|
|
203
205
|
}
|
|
206
|
+
if (command === "sdk-facts" || command === "sdk_facts") {
|
|
207
|
+
assertOnlyKnownFlags(args, ["platform", "capability", "surface-dir", "format", "include-symbols"], "sdk-facts");
|
|
208
|
+
const format = flagValue(args, "format") ?? "json";
|
|
209
|
+
if (format !== "json") {
|
|
210
|
+
throw new Error("sdk-facts currently supports --format json only.");
|
|
211
|
+
}
|
|
212
|
+
await printToolResult(getSdkFactsTool, {
|
|
213
|
+
platform: requiredFlagValue(args, "platform", "sdk-facts requires --platform."),
|
|
214
|
+
capability: flagValue(args, "capability"),
|
|
215
|
+
surfaceDir: flagValue(args, "surface-dir"),
|
|
216
|
+
includeSymbols: hasFlag(args, "include-symbols"),
|
|
217
|
+
});
|
|
218
|
+
return "exit";
|
|
219
|
+
}
|
|
204
220
|
if (command === "init") {
|
|
205
221
|
assertOnlyKnownFlags(args, ["request", "surface", "surface-path"], "init");
|
|
206
222
|
console.log(JSON.stringify(await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path")), null, 2));
|
|
@@ -468,6 +484,16 @@ Resolve a natural-language request into the closest supported Vise outcome.
|
|
|
468
484
|
|
|
469
485
|
Usage:
|
|
470
486
|
vise resolve [repoPath] --request "Add a social feed"`;
|
|
487
|
+
}
|
|
488
|
+
if (command === "sdk-facts" || command === "sdk_facts") {
|
|
489
|
+
return `${packageName} sdk-facts
|
|
490
|
+
|
|
491
|
+
Read bundled SDK surface facts for social.plus Block Factory planning. Internal, projectless, and read-only.
|
|
492
|
+
|
|
493
|
+
Usage:
|
|
494
|
+
vise sdk-facts --platform typescript --capability comments --format json
|
|
495
|
+
vise sdk-facts --platform react-native --capability reactions --include-symbols
|
|
496
|
+
vise sdk-facts --platform android --surface-dir ./sdk-surface --format json`;
|
|
471
497
|
}
|
|
472
498
|
if (command === "init") {
|
|
473
499
|
return `${packageName} init
|
|
@@ -577,6 +603,7 @@ Usage:
|
|
|
577
603
|
vise status [repoPath] Print compliance summary
|
|
578
604
|
vise validate [repoPath] Validate setup and common risks
|
|
579
605
|
vise run-sensors [repoPath] Run detected project sensors
|
|
606
|
+
vise sdk-facts --platform ... Internal SDK surface facts for Block Factory
|
|
580
607
|
vise design extract <prototype> Extract a design contract from an HTML/CSS prototype
|
|
581
608
|
vise design check [repoPath] Advisory (non-blocking) UI-vs-contract conformance report
|
|
582
609
|
vise design preview [repoPath] Write an HTML visual review of the contract + conformance
|
|
@@ -830,7 +857,7 @@ function ciCheckResult(result) {
|
|
|
830
857
|
};
|
|
831
858
|
}
|
|
832
859
|
function positionalRepoPath(args) {
|
|
833
|
-
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference"]);
|
|
860
|
+
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "capability", "surface-dir", "format", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference"]);
|
|
834
861
|
for (let index = 0; index < args.length; index += 1) {
|
|
835
862
|
const arg = args[index];
|
|
836
863
|
if (!arg) {
|
|
@@ -852,7 +879,7 @@ function positionalRepoPath(args) {
|
|
|
852
879
|
}
|
|
853
880
|
function requiredPositionalText(args, message) {
|
|
854
881
|
const values = [];
|
|
855
|
-
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference"]);
|
|
882
|
+
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "capability", "surface-dir", "format", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference"]);
|
|
856
883
|
for (let index = 0; index < args.length; index += 1) {
|
|
857
884
|
const arg = args[index];
|
|
858
885
|
if (!arg) {
|
package/dist/tools/compliance.js
CHANGED
|
@@ -375,6 +375,7 @@ export async function checkCompliance(repoPath) {
|
|
|
375
375
|
recommendation: finding?.recommendation,
|
|
376
376
|
rationale: rule.rationale,
|
|
377
377
|
current_rule: ruleSummary(rule),
|
|
378
|
+
...(failStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
378
379
|
});
|
|
379
380
|
continue;
|
|
380
381
|
}
|
|
@@ -385,11 +386,12 @@ export async function checkCompliance(repoPath) {
|
|
|
385
386
|
const sourceFingerprintStatus = await checkSourceFingerprints(repoRoot, inspection.effectiveRoot, attestation.source_fingerprints ?? []);
|
|
386
387
|
const staleFingerprints = sourceFingerprintStatus.filter((item) => item.status !== "match");
|
|
387
388
|
if (staleFingerprints.length > 0) {
|
|
389
|
+
const fingerprintStatus = rule.advisory ? "advisory" : rule.enforcement.attestation.allowed ? "attestation-needed" : "deterministic-fail";
|
|
388
390
|
results.push({
|
|
389
391
|
ruleId: rule.id,
|
|
390
392
|
title: rule.title,
|
|
391
393
|
severity: rule.severity,
|
|
392
|
-
status:
|
|
394
|
+
status: fingerprintStatus,
|
|
393
395
|
reason: rule.advisory
|
|
394
396
|
? "Advisory: informational only — does not affect compliance status."
|
|
395
397
|
: "Recorded attestation source fingerprints changed. Re-check the evidence and record a fresh attestation.",
|
|
@@ -398,6 +400,7 @@ export async function checkCompliance(repoPath) {
|
|
|
398
400
|
rationale: rule.rationale,
|
|
399
401
|
current_rule: ruleSummary(rule),
|
|
400
402
|
source_fingerprint_status: sourceFingerprintStatus,
|
|
403
|
+
...(fingerprintStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
401
404
|
});
|
|
402
405
|
continue;
|
|
403
406
|
}
|
|
@@ -442,7 +445,8 @@ export async function checkCompliance(repoPath) {
|
|
|
442
445
|
finding,
|
|
443
446
|
recommendation: finding?.recommendation,
|
|
444
447
|
current_rule: ruleSummary(rule),
|
|
445
|
-
...(isInferential && { inferential_prompt: rule.enforcement.inferential?.prompt })
|
|
448
|
+
...(isInferential && { inferential_prompt: rule.enforcement.inferential?.prompt }),
|
|
449
|
+
...(baseStatus === "attestation-needed" && rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
446
450
|
});
|
|
447
451
|
}
|
|
448
452
|
const summary = summarize(results);
|
|
@@ -450,12 +454,13 @@ export async function checkCompliance(repoPath) {
|
|
|
450
454
|
const hasDeterministicFailure = results.some((result) => result.status === "deterministic-fail");
|
|
451
455
|
// "advisory" status is intentionally excluded — advisory rules surface but never block.
|
|
452
456
|
const needsAttestation = results.some((result) => result.status === "attestation-needed" || result.status === "stale");
|
|
453
|
-
// Precedence: blocked (
|
|
457
|
+
// Precedence: blocked (3) > deterministic-failures (2) > needs-attestation (1) > completeness-gap (5) > green (0).
|
|
454
458
|
// Contract drift (exit 4) is handled earlier and short-circuits the loop.
|
|
455
|
-
//
|
|
456
|
-
// (
|
|
457
|
-
//
|
|
459
|
+
// Completeness-gap: capabilities that are neither present nor opted-out require an explicit decision
|
|
460
|
+
// (build it, or place // vise: scope-omit <id> — <reason>). The scope-omit escape hatch keeps this
|
|
461
|
+
// FP-free — any capability can be excluded with a recorded reason. Failure to assess is silently ignored.
|
|
458
462
|
const completeness = (await assessProjectCompleteness(inspection.effectiveRoot, compliance.outcome).catch(() => null)) ?? undefined;
|
|
463
|
+
const hasCompletenessGap = (completeness?.missing.length ?? 0) > 0;
|
|
459
464
|
// Blocked wins because the agent cannot proceed without customer input;
|
|
460
465
|
// surfacing a smaller failure first would distract from the real blocker.
|
|
461
466
|
return {
|
|
@@ -465,8 +470,10 @@ export async function checkCompliance(repoPath) {
|
|
|
465
470
|
? "deterministic-failures"
|
|
466
471
|
: needsAttestation
|
|
467
472
|
? "needs-attestation"
|
|
468
|
-
:
|
|
469
|
-
|
|
473
|
+
: hasCompletenessGap
|
|
474
|
+
? "completeness-gap"
|
|
475
|
+
: "green",
|
|
476
|
+
exitCode: hasBlocked ? 3 : hasDeterministicFailure ? 2 : needsAttestation ? 1 : hasCompletenessGap ? 5 : 0,
|
|
470
477
|
outcome: compliance.outcome,
|
|
471
478
|
surfacePath: compliance.surface?.path,
|
|
472
479
|
summary,
|
|
@@ -519,7 +526,20 @@ export async function attestRule(args) {
|
|
|
519
526
|
const rules = await rulesById();
|
|
520
527
|
const rule = rules.get(args.ruleId);
|
|
521
528
|
if (!rule || !compliance.rules.some((ref) => ref.rule_id === args.ruleId)) {
|
|
522
|
-
|
|
529
|
+
// Collect up to 8 applicable attestable rule ids from this contract for the error hint. Prefer
|
|
530
|
+
// ids that share the bad id's non-wildcard prefix so the agent can narrow down quickly.
|
|
531
|
+
const attestableIds = compliance.rules
|
|
532
|
+
.map((ref) => rules.get(ref.rule_id))
|
|
533
|
+
.filter((r) => r !== undefined && r.enforcement.attestation.allowed);
|
|
534
|
+
const prefix = args.ruleId.replace(/\.\*$|\*$/, "");
|
|
535
|
+
const prefixed = prefix !== args.ruleId ? attestableIds.filter((r) => r.id.startsWith(prefix) || r.id.includes(prefix)) : [];
|
|
536
|
+
const candidates = prefixed.length > 0 ? prefixed : attestableIds;
|
|
537
|
+
const hintIds = candidates.slice(0, 8).map((r) => r.id);
|
|
538
|
+
const hintSuffix = hintIds.length > 0 ? ` Applicable attestable rules: ${hintIds.join(", ")}.` : " Applicable attestable rules: none.";
|
|
539
|
+
const preamble = args.ruleId.includes("*")
|
|
540
|
+
? `Wildcards are not supported — attest one rule at a time.`
|
|
541
|
+
: `Rule is not applicable in this compliance contract: ${args.ruleId}.`;
|
|
542
|
+
throw new Error(`${preamble}${hintSuffix}`);
|
|
523
543
|
}
|
|
524
544
|
if (!rule.enforcement.attestation.allowed) {
|
|
525
545
|
throw new Error(`Rule does not allow attestation: ${args.ruleId}`);
|
|
@@ -626,6 +646,16 @@ function ruleRef(rule) {
|
|
|
626
646
|
function ruleRefForFile(rule) {
|
|
627
647
|
return { ...ruleRef(rule), title: rule.title };
|
|
628
648
|
}
|
|
649
|
+
// Benchmark-measured friction: agents looped on attest dialect for ~25 min/cell when docs and SDK
|
|
650
|
+
// disagreed on exact invocation syntax (capability-matrix 2026-06, Row 5). Hand them the exact incantation.
|
|
651
|
+
function attestHint(rule) {
|
|
652
|
+
const minConfidence = rule.enforcement.attestation.host_agent_min_confidence ?? "high";
|
|
653
|
+
const fields = rule.enforcement.attestation.evidence_required ?? [];
|
|
654
|
+
return {
|
|
655
|
+
attest_command: `vise attest --rule ${rule.id} --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>"`,
|
|
656
|
+
evidence_template: Object.fromEntries(fields.map((f) => [f.field, `<${f.description}>`])),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
629
659
|
function contractDrift(compliance, rules) {
|
|
630
660
|
const results = [];
|
|
631
661
|
const refs = compliance.rules.map((ref) => {
|
|
@@ -661,6 +691,7 @@ function contractDrift(compliance, rules) {
|
|
|
661
691
|
status: "stale",
|
|
662
692
|
reason,
|
|
663
693
|
current_rule: ruleSummary(rule),
|
|
694
|
+
...(rule.enforcement.attestation.allowed && attestHint(rule)),
|
|
664
695
|
});
|
|
665
696
|
}
|
|
666
697
|
}
|
|
@@ -135,7 +135,7 @@ function completenessChecklistFor(outcome) {
|
|
|
135
135
|
return undefined;
|
|
136
136
|
}
|
|
137
137
|
return {
|
|
138
|
-
note: "Build each capability, or opt out with `// vise: scope-omit <id> <reason>`. `vise check`
|
|
138
|
+
note: "Build each capability, or opt out with `// vise: scope-omit <id> <reason>`. Missing items that are neither built nor opted-out cause `vise check` to exit `completeness-gap` (exit code 5) — treat them as a stop condition.",
|
|
139
139
|
capabilities: items,
|
|
140
140
|
};
|
|
141
141
|
}
|
package/dist/tools/project.js
CHANGED
|
@@ -2993,7 +2993,7 @@ function validateFeedTargetTypeExplicit(root, platform, sourceContent) {
|
|
|
2993
2993
|
}
|
|
2994
2994
|
}
|
|
2995
2995
|
if (flagged) {
|
|
2996
|
-
findings.push(finding(ruleId, "warning", `A createPost call appears to hardcode targetType to a literal community or user.`, rel, "Feed targets
|
|
2996
|
+
findings.push(finding(ruleId, "warning", `A createPost call appears to hardcode targetType to a literal community or user.`, rel, "Feed targets must be bound to the intake-resolved target — the community ID from route params, the user ID from auth context, or a prop passed from the caller. Do not leave targetType as a free variable or default enum value: the intent is to wire it to 'requiredInputs.concrete feed target', not simply to avoid a literal. If this is intentional, add comment // vise: target-type rationale — <reason>."));
|
|
2997
2997
|
break;
|
|
2998
2998
|
}
|
|
2999
2999
|
}
|