@amityco/social-plus-vise 0.14.2 → 0.14.4
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 +21 -0
- package/README.md +28 -4
- package/dist/capabilities.js +27 -8
- package/dist/outcomes.js +28 -0
- package/dist/tools/compliance.js +40 -9
- package/dist/tools/integration.js +1 -1
- package/dist/tools/project.js +1 -1
- package/package.json +2 -2
- package/rules/feed.yaml +20 -0
- package/skills/social-plus-vise/SKILL.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to `@amityco/social-plus-vise` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.14.4 — 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
|
+
- **Scope-omit markers now require a reason:** `// vise: scope-omit <id>` no longer clears a completeness gap by itself. The marker must include a reason (for example `// vise: scope-omit post-poll — polls disabled for this tenant`) or the capability remains `missing`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **`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.
|
|
24
|
+
- **`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."
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
7
28
|
## 0.14.2 — 2026-06-03
|
|
8
29
|
|
|
9
30
|
**Theme:** SDK facts bridge for social.plus Block Factory.
|
package/README.md
CHANGED
|
@@ -77,9 +77,9 @@ Vise validates on three layers, and the layer is set by the *kind of claim* —
|
|
|
77
77
|
|---|---|---|---|
|
|
78
78
|
| **SDK compliance** | "this is **wrong**" | 300 deterministic rules (session renewal, live-collection vs one-shot, no secret in logs, parent-child rendering, ban-state gating…) | **Hard gate** — `vise check` blocks until green or attested. A small advisory subset surfaces as informational only and never blocks. |
|
|
79
79
|
| **Design conformance** | "this **looks off**" | extract the customer's design system into a contract, then check token usage | **Advisory** — `vise design check`/`preview`; never fails a build |
|
|
80
|
-
| **Feature completeness** | "this is **missing**" | Vise proposes the full SDK feature surface per outcome; the agent opts out of anything out of scope with a recorded reason | **
|
|
80
|
+
| **Feature completeness** | "this is **missing**" | Vise proposes the full SDK feature surface per outcome; the agent opts out of anything out of scope with a recorded reason | **Decision gate** — `vise check` exits `completeness-gap` until each missing capability is built or validly opted out |
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Correctness is gated by deterministic rules or attestations. Completeness is gated by explicit scope decisions: if a capability is legitimately out of scope, record `// vise: scope-omit <id> — <reason>` and it no longer blocks. Conformance remains advisory because "matches the brand" is legitimately subjective. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
83
83
|
|
|
84
84
|
### Relationship to social.plus Block Factory
|
|
85
85
|
|
|
@@ -158,7 +158,8 @@ A failing feature without Vise is *invisible* until a user hits it: the code com
|
|
|
158
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.)
|
|
159
159
|
- **Three arms, separate tooling.** The Rules-as-Markdown arm has no Vise checker available — it cannot run `vise check`.
|
|
160
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.
|
|
161
|
-
- **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.
|
|
162
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.
|
|
163
164
|
|
|
164
165
|
### Which mode should I use?
|
|
@@ -171,6 +172,28 @@ A failing feature without Vise is *invisible* until a user hits it: the code com
|
|
|
171
172
|
|
|
172
173
|
---
|
|
173
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
|
+
|
|
174
197
|
## Supported Platforms
|
|
175
198
|
|
|
176
199
|
| Platform | Coverage | Sensors |
|
|
@@ -386,11 +409,12 @@ jobs:
|
|
|
386
409
|
|
|
387
410
|
| Code | Meaning |
|
|
388
411
|
|---|---|
|
|
389
|
-
| `0` | All rules pass
|
|
412
|
+
| `0` | All rules pass and all expected capabilities are either present or opted-out |
|
|
390
413
|
| `1` | One or more rules need attestation |
|
|
391
414
|
| `2` | One or more rules have deterministic failures |
|
|
392
415
|
| `3` | One or more blockers fired (missing prerequisite, e.g. `google-services.json`) |
|
|
393
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 validly opted-out — add the capability or place `// vise: scope-omit <id> — <reason>` |
|
|
394
418
|
|
|
395
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.
|
|
396
420
|
|
package/dist/capabilities.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Feature-completeness assessment
|
|
2
|
+
* Feature-completeness assessment.
|
|
3
3
|
*
|
|
4
4
|
* Boundary (see the validation-boundaries principle): completeness is a "this is
|
|
5
|
-
* missing" claim — a universal-negative over open-ended correct implementations
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* missing" claim — a universal-negative over open-ended correct implementations
|
|
6
|
+
* unless the customer/agent explicitly removes it from scope. Missing capabilities
|
|
7
|
+
* now produce `completeness-gap` in `vise check`; a recorded scope-omit reason is
|
|
8
|
+
* the false-positive escape hatch.
|
|
8
9
|
*
|
|
9
10
|
* Memory-independence comes from inversion: VISE authors the canonical capability
|
|
10
11
|
* set per outcome; the agent must OPT OUT of a capability with a recorded reason
|
|
@@ -367,7 +368,7 @@ export const CAPABILITIES = [
|
|
|
367
368
|
hint: "respect Amity's server-side notification settings/preferences (getSettings)",
|
|
368
369
|
},
|
|
369
370
|
];
|
|
370
|
-
const ADVISORY_NOTE = "
|
|
371
|
+
const ADVISORY_NOTE = "Build each missing capability, or opt out with a recorded reason: `// vise: scope-omit <id> — <reason>`. A scope-omit marker without a reason is invalid and still counts as missing. Missing capabilities that are neither built nor validly opted-out cause `vise check` to exit with status `completeness-gap` (exit code 5).";
|
|
371
372
|
/** The Vise-authored capability checklist for an outcome (for `vise plan` feed-forward). */
|
|
372
373
|
export function capabilityChecklist(outcome) {
|
|
373
374
|
return CAPABILITIES.filter((c) => c.outcomes.includes(outcome)).map((c) => ({ id: c.id, label: c.label, hint: c.hint }));
|
|
@@ -376,14 +377,24 @@ export function capabilityChecklist(outcome) {
|
|
|
376
377
|
export function assessCompleteness(source, outcome) {
|
|
377
378
|
const caps = CAPABILITIES.filter((c) => c.outcomes.includes(outcome));
|
|
378
379
|
const optOuts = new Map();
|
|
380
|
+
const invalidOptOuts = new Map();
|
|
379
381
|
const omitPattern = /vise:\s*scope-omit\s+([a-z][\w-]*)\s*(?:[—:|-]+\s*(.*))?/gi;
|
|
380
382
|
let match;
|
|
381
383
|
while ((match = omitPattern.exec(source)) !== null) {
|
|
382
|
-
|
|
384
|
+
const id = match[1].toLowerCase();
|
|
385
|
+
const reason = (match[2] ?? "").trim();
|
|
386
|
+
if (reason) {
|
|
387
|
+
optOuts.set(id, reason);
|
|
388
|
+
invalidOptOuts.delete(id);
|
|
389
|
+
}
|
|
390
|
+
else if (!optOuts.has(id)) {
|
|
391
|
+
invalidOptOuts.set(id, "scope-omit marker must include a reason");
|
|
392
|
+
}
|
|
383
393
|
}
|
|
384
394
|
const present = [];
|
|
385
395
|
const missing = [];
|
|
386
396
|
const optedOut = [];
|
|
397
|
+
const invalid = [];
|
|
387
398
|
for (const cap of caps) {
|
|
388
399
|
if (optOuts.has(cap.id)) {
|
|
389
400
|
optedOut.push({ id: cap.id, reason: optOuts.get(cap.id) });
|
|
@@ -392,10 +403,18 @@ export function assessCompleteness(source, outcome) {
|
|
|
392
403
|
present.push({ id: cap.id, label: cap.label });
|
|
393
404
|
}
|
|
394
405
|
else {
|
|
395
|
-
|
|
406
|
+
const invalidReason = invalidOptOuts.get(cap.id);
|
|
407
|
+
if (invalidReason) {
|
|
408
|
+
invalid.push({ id: cap.id, reason: invalidReason });
|
|
409
|
+
}
|
|
410
|
+
missing.push({
|
|
411
|
+
id: cap.id,
|
|
412
|
+
label: cap.label,
|
|
413
|
+
hint: invalidReason ? `${cap.hint}; found scope-omit marker without a reason, so add a reason or implement it` : cap.hint,
|
|
414
|
+
});
|
|
396
415
|
}
|
|
397
416
|
}
|
|
398
|
-
return { outcome, present, missing, optedOut, note: ADVISORY_NOTE };
|
|
417
|
+
return { outcome, present, missing, optedOut, invalidOptOuts: invalid, note: ADVISORY_NOTE };
|
|
399
418
|
}
|
|
400
419
|
// ── Bounded source read (advisory; perf-bounded like the design check scan) ──
|
|
401
420
|
const SCAN_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".dart", ".kt", ".java", ".swift", ".vue"]);
|
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/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 validly 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 because 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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amityco/social-plus-vise",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"test:sdk-facts": "npm run build && node test/run-sdk-facts.mjs",
|
|
68
68
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
69
69
|
"test:e2e-package": "npm run build && node test/run-e2e-package.mjs",
|
|
70
|
-
"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: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: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 pack:check",
|
|
70
|
+
"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: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: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
71
|
"test:ast": "node test/run-ast-helpers.mjs",
|
|
72
72
|
"test:design-extract": "npm run build && node test/run-design-extract.mjs",
|
|
73
73
|
"test:design-brief": "npm run build && node test/run-design-brief.mjs",
|
package/rules/feed.yaml
CHANGED
|
@@ -1110,6 +1110,7 @@
|
|
|
1110
1110
|
{
|
|
1111
1111
|
"id": "typescript.posts.parent-child-rendered",
|
|
1112
1112
|
"version": 1,
|
|
1113
|
+
"symbol_anchored": true,
|
|
1113
1114
|
"title": "Post rendering for TypeScript must account for child posts (images/videos)",
|
|
1114
1115
|
"severity": "warning",
|
|
1115
1116
|
"rationale": "TypeScript UI components for Posts must recursively render or inspect post.children. If a component only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1146,6 +1147,7 @@
|
|
|
1146
1147
|
{
|
|
1147
1148
|
"id": "react-native.posts.parent-child-rendered",
|
|
1148
1149
|
"version": 1,
|
|
1150
|
+
"symbol_anchored": true,
|
|
1149
1151
|
"title": "Post rendering for React Native must account for child posts (images/videos)",
|
|
1150
1152
|
"severity": "warning",
|
|
1151
1153
|
"rationale": "React Native components for Posts must recursively render or inspect post.children. If a component only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1182,6 +1184,7 @@
|
|
|
1182
1184
|
{
|
|
1183
1185
|
"id": "android.posts.parent-child-rendered",
|
|
1184
1186
|
"version": 1,
|
|
1187
|
+
"symbol_anchored": true,
|
|
1185
1188
|
"title": "Post rendering for Android must account for child posts (images/videos)",
|
|
1186
1189
|
"severity": "warning",
|
|
1187
1190
|
"rationale": "Android Compose/RecyclerView components for Posts must render or inspect the child items (post.children). If a component only renders the parent post's text, it will silently drop attached images and videos.",
|
|
@@ -1218,6 +1221,7 @@
|
|
|
1218
1221
|
{
|
|
1219
1222
|
"id": "flutter.posts.parent-child-rendered",
|
|
1220
1223
|
"version": 1,
|
|
1224
|
+
"symbol_anchored": true,
|
|
1221
1225
|
"title": "Post rendering for Flutter must account for child posts (images/videos)",
|
|
1222
1226
|
"severity": "warning",
|
|
1223
1227
|
"rationale": "Flutter Widgets for Posts must render or inspect the child posts (post.children). If a widget only renders the parent post's data.text, it will silently drop attached images and videos.",
|
|
@@ -1254,6 +1258,7 @@
|
|
|
1254
1258
|
{
|
|
1255
1259
|
"id": "ios.posts.parent-child-rendered",
|
|
1256
1260
|
"version": 1,
|
|
1261
|
+
"symbol_anchored": true,
|
|
1257
1262
|
"title": "Post rendering for iOS must account for child posts (images/videos)",
|
|
1258
1263
|
"severity": "warning",
|
|
1259
1264
|
"rationale": "iOS SwiftUI/UIKit views for Posts must inspect the child posts (post.children). If a view only renders the parent post's text, it will silently drop attached images and videos.",
|
|
@@ -1290,6 +1295,7 @@
|
|
|
1290
1295
|
{
|
|
1291
1296
|
"id": "typescript.feed.target-type-explicit",
|
|
1292
1297
|
"version": 1,
|
|
1298
|
+
"symbol_anchored": true,
|
|
1293
1299
|
"title": "Explicit targetType for TypeScript createPost calls must not be hardcoded to COMMUNITY",
|
|
1294
1300
|
"severity": "warning",
|
|
1295
1301
|
"rationale": "TypeScript feed targets must be passed dynamically (e.g. from props or state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1326,6 +1332,7 @@
|
|
|
1326
1332
|
{
|
|
1327
1333
|
"id": "react-native.feed.target-type-explicit",
|
|
1328
1334
|
"version": 1,
|
|
1335
|
+
"symbol_anchored": true,
|
|
1329
1336
|
"title": "Explicit targetType for React Native createPost calls must not be hardcoded to COMMUNITY",
|
|
1330
1337
|
"severity": "warning",
|
|
1331
1338
|
"rationale": "React Native feed targets must be passed dynamically (e.g. from props or state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1362,6 +1369,7 @@
|
|
|
1362
1369
|
{
|
|
1363
1370
|
"id": "android.feed.target-type-explicit",
|
|
1364
1371
|
"version": 1,
|
|
1372
|
+
"symbol_anchored": true,
|
|
1365
1373
|
"title": "Explicit targetType for Android createPost calls must not be hardcoded to COMMUNITY",
|
|
1366
1374
|
"severity": "warning",
|
|
1367
1375
|
"rationale": "Android feed targets must be passed dynamically (e.g. from Intent extras or ViewModel state), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1398,6 +1406,7 @@
|
|
|
1398
1406
|
{
|
|
1399
1407
|
"id": "flutter.feed.target-type-explicit",
|
|
1400
1408
|
"version": 1,
|
|
1409
|
+
"symbol_anchored": true,
|
|
1401
1410
|
"title": "Explicit targetType for Flutter createPost calls must not be hardcoded to COMMUNITY",
|
|
1402
1411
|
"severity": "warning",
|
|
1403
1412
|
"rationale": "Flutter feed targets must be passed dynamically (e.g. from Widget properties), not hardcoded as AmityPostTargetType.COMMUNITY.",
|
|
@@ -1434,6 +1443,7 @@
|
|
|
1434
1443
|
{
|
|
1435
1444
|
"id": "ios.feed.target-type-explicit",
|
|
1436
1445
|
"version": 1,
|
|
1446
|
+
"symbol_anchored": true,
|
|
1437
1447
|
"title": "Explicit targetType for iOS createPost calls must not be hardcoded to COMMUNITY",
|
|
1438
1448
|
"severity": "warning",
|
|
1439
1449
|
"rationale": "iOS feed targets must be passed dynamically (e.g. from View state or initializer), not hardcoded as AmityPostTargetType.community.",
|
|
@@ -1470,6 +1480,7 @@
|
|
|
1470
1480
|
{
|
|
1471
1481
|
"id": "typescript.reactions.configured-name-used",
|
|
1472
1482
|
"version": 2,
|
|
1483
|
+
"symbol_anchored": true,
|
|
1473
1484
|
"title": "TypeScript reaction name matches console config",
|
|
1474
1485
|
"severity": "warning",
|
|
1475
1486
|
"advisory": true,
|
|
@@ -1508,6 +1519,7 @@
|
|
|
1508
1519
|
{
|
|
1509
1520
|
"id": "react-native.reactions.configured-name-used",
|
|
1510
1521
|
"version": 2,
|
|
1522
|
+
"symbol_anchored": true,
|
|
1511
1523
|
"advisory": true,
|
|
1512
1524
|
"title": "React Native reaction name matches console config",
|
|
1513
1525
|
"severity": "warning",
|
|
@@ -1546,6 +1558,7 @@
|
|
|
1546
1558
|
{
|
|
1547
1559
|
"id": "android.reactions.configured-name-used",
|
|
1548
1560
|
"version": 2,
|
|
1561
|
+
"symbol_anchored": true,
|
|
1549
1562
|
"advisory": true,
|
|
1550
1563
|
"title": "Android reaction name matches console config",
|
|
1551
1564
|
"severity": "warning",
|
|
@@ -1584,6 +1597,7 @@
|
|
|
1584
1597
|
{
|
|
1585
1598
|
"id": "flutter.reactions.configured-name-used",
|
|
1586
1599
|
"version": 2,
|
|
1600
|
+
"symbol_anchored": true,
|
|
1587
1601
|
"advisory": true,
|
|
1588
1602
|
"title": "Flutter reaction name matches console config",
|
|
1589
1603
|
"severity": "warning",
|
|
@@ -1622,6 +1636,7 @@
|
|
|
1622
1636
|
{
|
|
1623
1637
|
"id": "ios.reactions.configured-name-used",
|
|
1624
1638
|
"version": 2,
|
|
1639
|
+
"symbol_anchored": true,
|
|
1625
1640
|
"advisory": true,
|
|
1626
1641
|
"title": "iOS reaction name matches console config",
|
|
1627
1642
|
"severity": "warning",
|
|
@@ -1660,6 +1675,7 @@
|
|
|
1660
1675
|
{
|
|
1661
1676
|
"id": "typescript.custom-post-type.dataType-declared",
|
|
1662
1677
|
"version": 1,
|
|
1678
|
+
"symbol_anchored": true,
|
|
1663
1679
|
"title": "TypeScript custom post must declare dataType",
|
|
1664
1680
|
"severity": "warning",
|
|
1665
1681
|
"rationale": "When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.",
|
|
@@ -1696,6 +1712,7 @@
|
|
|
1696
1712
|
{
|
|
1697
1713
|
"id": "react-native.custom-post-type.dataType-declared",
|
|
1698
1714
|
"version": 1,
|
|
1715
|
+
"symbol_anchored": true,
|
|
1699
1716
|
"title": "React Native custom post must declare dataType",
|
|
1700
1717
|
"severity": "warning",
|
|
1701
1718
|
"rationale": "When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.",
|
|
@@ -1732,6 +1749,7 @@
|
|
|
1732
1749
|
{
|
|
1733
1750
|
"id": "android.custom-post-type.dataType-declared",
|
|
1734
1751
|
"version": 1,
|
|
1752
|
+
"symbol_anchored": true,
|
|
1735
1753
|
"title": "Android custom post must declare dataType",
|
|
1736
1754
|
"severity": "warning",
|
|
1737
1755
|
"rationale": "When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.",
|
|
@@ -1768,6 +1786,7 @@
|
|
|
1768
1786
|
{
|
|
1769
1787
|
"id": "flutter.custom-post-type.dataType-declared",
|
|
1770
1788
|
"version": 1,
|
|
1789
|
+
"symbol_anchored": true,
|
|
1771
1790
|
"title": "Flutter custom post must declare dataType",
|
|
1772
1791
|
"severity": "warning",
|
|
1773
1792
|
"rationale": "When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.",
|
|
@@ -1804,6 +1823,7 @@
|
|
|
1804
1823
|
{
|
|
1805
1824
|
"id": "ios.custom-post-type.dataType-declared",
|
|
1806
1825
|
"version": 1,
|
|
1826
|
+
"symbol_anchored": true,
|
|
1807
1827
|
"title": "iOS custom post must declare dataType",
|
|
1808
1828
|
"severity": "warning",
|
|
1809
1829
|
"rationale": "When creating a post with a custom data payload, the 'dataType' tag must be explicitly provided so the SDK can route it correctly. If missing, it defaults to a text post, which stringifies the payload.",
|
|
@@ -37,6 +37,8 @@ vise run-sensors .
|
|
|
37
37
|
|
|
38
38
|
**`vise check .` is mandatory, not optional. You are not done until it passes or every finding is explicitly attested.** Running `vise plan` and `vise inspect` but skipping `vise check` is the most common failure mode — it means the deterministic catch-net never ran and known-bad patterns ship. When you read a `vise plan`, do not truncate it (`| head` drops the implementation steps); read the full `implementationSteps` array.
|
|
39
39
|
|
|
40
|
+
**Completeness is also a stop condition.** When `vise check .` exits with status `completeness-gap` (exit code 5), one or more expected capabilities are neither implemented nor opted-out. For each missing item: either implement it, or place `// vise: scope-omit <id> — <reason>` in the relevant source file and re-run `vise check .` to confirm it exits 0. A missing capability that is neither built nor opted-out is a silent drop — the check will not pass green until every capability is resolved.
|
|
41
|
+
|
|
40
42
|
Treat Vise runtime smoke sensors as real validation. For TypeScript/React Native projects, `vise run-sensors` may include `TypeScript SDK import smoke`; if it fails, the SDK package does not resolve from the host project runtime and the integration is not done.
|
|
41
43
|
|
|
42
44
|
In CI or pull-request pipelines, use `vise check . --ci`. It is read-only, exits non-zero unless compliance is green, and never writes deterministic-pass records; use `vise sync` only during the implementation loop after a local green check.
|
|
@@ -242,13 +244,13 @@ vise plan . --request "<feed or post request>"
|
|
|
242
244
|
|
|
243
245
|
Require a concrete target from the app: current user feed, selected community, selected channel, or another user-provided domain object. Do not hardcode random target IDs.
|
|
244
246
|
|
|
245
|
-
**Decide engagement scope explicitly — Vise authors the checklist, you subtract with a reason.** `vise plan` returns a `completenessChecklist` (the canonical capabilities for the outcome, e.g. comments, reactions, pagination, polls, media, moderation) and `vise check` reports each as present / missing / opted-out.
|
|
247
|
+
**Decide engagement scope explicitly — Vise authors the checklist, you subtract with a reason.** `vise plan` returns a `completenessChecklist` (the canonical capabilities for the outcome, e.g. comments, reactions, pagination, polls, media, moderation) and `vise check` reports each as present / missing / opted-out. Missing items that are neither built nor validly opted-out produce `completeness-gap` (exit code 5), because they are silent drops. For each capability: build it, or explicitly opt out with a recorded marker in the code so the omission is reviewable, not accidental:
|
|
246
248
|
|
|
247
249
|
```
|
|
248
250
|
// vise: scope-omit poll — text + image feed only; polls disabled for this tenant
|
|
249
251
|
```
|
|
250
252
|
|
|
251
|
-
Do not
|
|
253
|
+
Do not report the integration complete while any capability is `missing`. A scope-omit marker must include a reason after the capability id; otherwise it remains invalid and the capability still counts as missing. Re-run `vise check .` after placing a scope-omit marker to confirm it moves from `missing` to `opted-out`.
|
|
252
254
|
|
|
253
255
|
**Demo wiring (when the app needs to compile before the real target source exists):** even at the top-level `App` / `MaterialApp` / `_app.tsx` / `AppDelegate` wiring site, do not pass a literal string. Use the host platform's compile-time env channel so the rule sees no string literal at the call site:
|
|
254
256
|
|