@amityco/social-plus-vise 1.5.0 → 1.7.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 CHANGED
@@ -4,6 +4,36 @@ 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.7.0 — 2026-07-09
8
+
9
+ ### Added
10
+ - **Day-2 governance: adding features to an app that already uses social.plus is now a first-class, governed flow.**
11
+ - **Brownfield adoption honesty.** `vise init` on a repo with pre-existing non-green findings surfaces a `brownfield` hint: record `vise baseline .` before writing code, then gate with `vise check --new-only`. A baseline excludes pre-existing *rule findings only* — the requested outcome's completeness checklist and selected capabilities are never baselined, so a baselined repo can't go green without the feature actually built. Baseline files recorded by older versions that carried deliverable entries are ignored and disclosed (`ignored_legacy_deliverable_entries`).
12
+ - **Engagement ledger.** Initializing a different outcome records the outgoing engagement's final check, contract, and intake to `sp-vise/engagements/<outcome>/` (latest per outcome) and discloses the switch in the init result. `vise status` lists superseded engagements under `previousEngagements`. Replacing a **green** engagement asks for an explicit `--supersede-engagement`.
13
+ - **Re-verification of finished work.** `vise check --engagement <outcome>` re-verdicts one recorded engagement against the current rules and code (exit `11` on drift, `0` when it holds; `ruleset_upgraded` disclosed). `vise check --all-engagements` runs the normal gate plus re-verdicts of every recorded engagement — active gate non-green keeps its own exit code; active green with a drifted recorded engagement exits `11` (`engagement-drift`). Findings already gating the active engagement are counted once there (`shared_with_active`).
14
+ - **Attestations are bound to their engagement.** `vise attest` stamps the active outcome; after an outcome switch, a rule-global attestation re-gates (`stale_engagement_attestation`) until re-verified — file-scoped attestations survive the switch because new files re-gate them anyway. Attestations recorded by older versions keep their historical behavior.
15
+ - **Completed journeys don't dead-end new requests.** After a finished multi-surface journey, `vise init` for a genuinely new outcome now proceeds (recording the completed journey to the ledger) instead of stopping at `workplan-complete`; re-initializing a surface the journey already completed still gets the honest stop.
16
+ - New skill section — "Adding Features To An App That Already Uses social.plus" — teaches agents both variants (hand-rolled integration and prior governed engagement).
17
+ - **SDK facts: broader capability coverage and deeper model grounding.** `vise sdk-facts` (now listed in the public `vise --help`) gains capability anchors for `community`, `chat`, `livestream` (Room API only), `notification-tray`, and `events` on TypeScript, plus per-platform anchor sets with Flutter coverage for six families (`comments`, `reactions`, `posts`, `users`, `community`, `chat`) — a partially anchored platform's result names its coverage explicitly. Field-level model schemas grew from 6 to 14 TypeScript models (`Amity.Community`, `Amity.Channel`, `Amity.Message`, `Amity.SubChannel`, `Amity.Room`, `Amity.NotificationTrayItem`, `Amity.NotificationTraySeen`, `Amity.Event`), all extraction-grounded with real field names and types.
18
+ - **Development-workspace guard.** Customer-integration commands now recognize the Vise / social.plus blocks development workspace itself: `vise init` refuses with `status: "internal-workspace"` (exit `10`, `--allow-internal-workspace` to override for harness runs), and `inspect`/`plan` attach an advisory warning — so the projectless `vise sdk-facts` persona and the customer persona can't be mixed up silently.
19
+
20
+ ### Fixed
21
+ - **Blocks installer on native projects.** `vise blocks plan`/`add`/`validate` pointed at a project without a package manifest (an iOS or Android app) now return the graceful `needs-review` plan with unsupported-platform stop conditions instead of crashing on a missing `package.json`.
22
+
23
+ ### Compatibility
24
+ - **Additive exit codes.** `10` (`internal-workspace`, init-only) and `11` (`engagement-drift`, only under the new `--engagement`/`--all-engagements` flags). The existing 0–9 exit surface and precedence are unchanged; default `vise check` behavior is unchanged, so pipelines gating on `--ci` or `exitCode === 0` are unaffected.
25
+ - **One intentional init behavior change.** `vise init` replacing a **green** engagement with a different outcome now requires `--supersede-engagement` (exit `7` otherwise); red or in-progress engagements switch as before, and the outgoing engagement's final state is recorded to the ledger either way.
26
+ - **Sidecar additions are additive.** `sp-vise/engagements/` and the new result fields (`brownfield`, `engagement_switch`, `previousEngagements`, `stale_engagement_attestation`) are new; no existing file changes shape. Legacy baselines and attestations continue to parse — with the honesty adjustments disclosed above.
27
+
28
+ ## 1.6.0 — 2026-07-06
29
+
30
+ ### Added
31
+ - **Reactivity rules for the latest SDK features.** Three new live-collection rules guard features added in the recent SDK release: in-chat **message search** (`message-search.live-collection`, all platforms), **archived channels** (`channel-archive.live-collection`, all platforms), and the personalized **For-You feed** (`for-you-feed.live-collection`, TypeScript/React Native/Android/iOS). Each flags a results list rendered from a one-shot query instead of the reactive, paginated Live Collection, and stays silent on a correct reactive integration. For-You is not part of the Flutter public API, so a For-You request on Flutter now surfaces a platform-availability notice pointing to a community- or user-scoped feed instead.
32
+
33
+ ### Changed
34
+ - **Refreshed the bundled SDK surface facts** for TypeScript, Android, iOS, and Flutter. `vise sdk-facts` and the symbol-anchored rules now reflect the current SDK symbols and models — including channel archiving, message search, For-You feed, and per-scope notification modes. The refresh is additive: no previously grounded symbol was removed, so existing rules keep resolving.
35
+ - **Portable provenance in `vise sdk-surface:import`.** The importer normalizes the source paths recorded in the shipped surface facts, so they stay machine-independent regardless of local checkout layout.
36
+
7
37
  ## 1.5.0 — 2026-07-05
8
38
 
9
39
  ### Added
package/README.md CHANGED
@@ -142,65 +142,41 @@ Vise can ingest your aesthetic from an HTML/CSS prototype (`vise design extract`
142
142
 
143
143
  ## Evidence
144
144
 
145
- We put Vise through the same brownfield social.plus integration twice per platform — once through the **governed Vise workflow**, once from a structured checklist with **Vise switched off** (the control) — and ran the whole thing with two coding agents: first **OpenAI Codex GPT-5.4**, then **Claude Sonnet 5** on a hardened protocol. Vise led on all five platform families with both agents. But the two headline gaps are **not** equally solid, and **neither raw average is the number to stand behind**.
145
+ We put Vise through the same brownfield social.plus integration twice per platform — once through the **governed Vise workflow**, once from a structured checklist with **Vise switched off** (the control) — across five platform families, on a live tenant with no mocking. The precise, uniform benchmark is **Claude Sonnet 5**; we cross-checked the direction with a second agent, **OpenAI Codex GPT-5.4**. Read the direction, not the decimals.
146
146
 
147
- > **Three different numbers appear below. They measure different things:**
148
- > - **+29.1** — Sonnet 5 *full weighted composite*. Includes the +90 definitional capability axis, so it is **not** the earned lift.
149
- > - **+12.1** — Sonnet 5 *credible-only* (capability axis removed, four fair dimensions re-weighted to 100%). **This is the number we stand behind.**
150
- > - **+23 to +31** — cross-model convergence deltas. **Still raw composites** (capability-inflated). They prove the lift points the **same direction** across agents — not its magnitude.
147
+ ### Anchor: Claude Sonnet 5, per platform
151
148
 
152
- > All deltas are computed on **unrounded** composites, so hand-subtracting the rounded scores in a table can differ by 0.1 (e.g. 83.0 70.8 reads as ~12.2, but the exact delta is +12.1).
149
+ Composite score, 0–100 shared scale; every control built and launched.
153
150
 
154
- ### Per-platform head-to-head
151
+ | Platform | Fixture | Vise | no-Vise | Δ |
152
+ |---|---|--:|--:|--:|
153
+ | Web | PULSE / pulse-studio | 88.3 | 56.8 | **+31.5** |
154
+ | React Native | Arena Fit | 89.7 | 62.1 | **+27.6** |
155
+ | iOS | Luminary Club | 77.5 | 48.0 | **+29.5** |
156
+ | Android | MarketPulse | 77.5 | 50.1 | **+27.4** |
157
+ | Flutter | Fieldhouse | 78.0 | 48.5 | **+29.5** |
158
+ | **Average** | 5 fixtures | **82.2** | **53.1** | **+29.1** |
155
159
 
156
- Composite score, 0–100 shared scale. Failed Codex controls (watchdog-killed before launch) are marked; those cells deflate the Codex control and inflate its gap.
157
-
158
- | Platform | Fixture | Sonnet 5 Vise | Sonnet 5 no-Vise | Sonnet Δ | Codex Vise | Codex no-Vise | Codex Δ |
159
- |---|---|--:|--:|--:|--:|--:|--:|
160
- | Web | PULSE / pulse-studio | 88.3 | 56.8 | **+31.5** | 91.4 | 65.6 | **+25.8** |
161
- | React Native | Arena Fit | 89.7 | 62.1 | **+27.6** | 90.3 | 67.3 | **+23.0** |
162
- | iOS | Luminary Club | 77.5 | 48.0 | +29.5 | 90.1 | 43.0 ⚠︎ | +47.1 ⚠︎ |
163
- | Android | MarketPulse / market-pulse | 77.5 | 50.1 | +27.4 | 94.3 | 31.2 ❌ | +63.1 ❌ |
164
- | Flutter | Fieldhouse | 78.0 | 48.5 | +29.5 | 92.8 | 33.0 ❌ | +59.8 ❌ |
165
- | **Average** | 5 fixtures | **82.2** | **53.1** | **+29.1** | 91.8 | 48.0 | ~~+43.8~~ |
166
-
167
- - ❌ **Control failed before launch.** Flutter's no-Vise control deleted `lib/main.dart` and was watchdog-terminated (33.0); Android's was depressed by an instrument failure (31.2). Both deflate the control and inflate the Codex gap.
168
- - ⚠︎ **Control ran low but launched.** Codex iOS no-Vise reached only 43.0 — the lowest launched control in either set — on the earlier harness.
169
-
170
- ### Averages: anchor on the clean run
171
-
172
- - **Claude Sonnet 5 — anchor (+29.1).** Hardened protocol; **all 5 controls built and launched**. This is the clean, conservative headline. It still carries the +90 definitional axis, so the earned lift is +12.1 credible-only.
173
- - **OpenAI Codex GPT-5.4 — caveated (~~+43.8~~).** Earlier, less-hardened harness; 2 of 5 controls failed before launch and iOS ran low. That larger gap is **inflated** and is not comparable to the Sonnet number.
174
-
175
- ### The cross-model signal: where both controls launched, the agents agree
176
-
177
- On the only two platforms where the Codex control actually built and launched — **Web** and **React Native** — the two agents' deltas land within ~5 points of each other:
178
-
179
- - **Web:** Codex **+25.8** ≈ Sonnet 5 **+31.5** (agree within 5.7 pts)
180
- - **React Native:** Codex **+23.0** ≈ Sonnet 5 **+27.6** (agree within 4.6 pts)
181
-
182
- **Read these as raw composites, not the real-world lift.** Each Vise delta here still embeds the +90 definitional capability axis. So the convergence proves the lift points the **same direction** across two independent agents — it does **not** say the earned magnitude is +23–31. For the earned magnitude, use the credible-only number below. (iOS is left out of this window because the Codex iOS control ran low — the same reason its average is caveated.)
183
-
184
- ### The credible-only lift: +12.1
185
-
186
- Drop the Vise-definitional capability axis (the control scores 0 there **by construction**) and re-weight the remaining four dimensions to 100%:
160
+ That **+29.1** full composite still carries the **+90 definitional capability axis** (the control scores 0 there **by construction**). Drop it and re-weight the four fair dimensions to 100%:
187
161
 
188
162
  | | Vise | no-Vise | Δ |
189
163
  |---|--:|--:|--:|
190
164
  | Full weighted composite (incl. capability axis) | 82.2 | 53.1 | +29.1 |
191
- | **Credible-only composite (capability axis removed)** | **83.0** | **70.8** | **+12.1** |
165
+ | **Credible-only (capability axis removed)** | **83.0** | **70.8** | **+12.1** |
166
+
167
+ **+12.1 is the number to stand behind.** It comes from **Product outcome +17.6** and **Design integration +15.8** (the credible wins); **Runtime hygiene is a near-tie (+1.9** — build/launch cleanliness, which a capable agent clears either way; on Android the checklist control actually built cleaner — this is *not* SDK-rule compliance, see below); **Efficiency +11.7 is a scorer *budget* metric** (the governed arm is **slower** in real time, ~2.7× on web); **Capability activation +90 is definitional**, kept out of the headline. (Deltas use unrounded composites, so hand-subtracting rounded cells can differ by 0.1.)
168
+
169
+ > **Why the hygiene dimension is near-parity — and why that isn't the whole story.** The scorer's `correctness` dimension measures *runtime hygiene* (build, launch, no crashes/secrets/hardcoded IDs), which strong agents clear with or without Vise. It is **not** SDK-rule compliance — the thing the 400+ deterministic rules govern. That is measured directly by a separate **compliance-findings** metric (`vise validate` on both arms' produced source): an ungoverned control carries real rule violations a smoke test never surfaces, and its runtime symptoms already show up as the product-outcome gap above.
192
170
 
193
- (The +12.1 is the delta of the unrounded composites; hand-subtracting the rounded 83.0 − 70.8 reads as ~12.2.)
171
+ ### Cross-check: OpenAI Codex GPT-5.4 (directional)
194
172
 
195
- Where the +12.1 comes from (Sonnet 5): **Product outcome +17.6** and **Design integration +15.8** are the credible wins; **Correctness is a near-tie (+1.9** — on Android the checklist control actually built cleaner); **Efficiency +11.7 is a scorer *budget* metric, not wall-clock** the governed arm is **slower** in real time (~2. the control on web); **Capability activation +90 is definitional**, kept out of the headline.
173
+ The same A/B run with Codex points the same way **Vise ahead on every platform**. Codex's exact figures are **directional, not precise**: its no-Vise controls are high-variance — on Android a single control has read anywhere from **+63** (an unlucky, instrument-affected run on the earlier harness) down to **+27** (clean). Run cleanly on the hardened harness (Android and iOS N=3; Flutter one clean launch, the rest watchdog-blocked), Codex's controls land ~65 and its lift settles around **+25–30**, converging with Sonnet's. Honest cross-model read: **both agents show Vise winning by ~+25–30 once controls are run cleanly.** We anchor on Sonnet because it is the uniform, current-harness run; Codex is cross-model confirmation of direction.
196
174
 
197
175
  ### Methodology & caveats
198
176
 
199
177
  - **Vise is the deterministic CLI under test, not an AI model.** Each arm runs the same coding agent on the same brownfield fixture, live tenant, no mocking.
200
- - **Scoring weights:** capability activation 25% (definitional), product outcome 25%, design integration 20%, correctness 20%, efficiency 10%. The credible-only composite drops the capability axis and re-weights the other four to 100%.
201
- - **Deltas use unrounded scores.** Displayed scores are rounded to one decimal, so a hand-subtraction of two table cells can differ from the stated delta by 0.1.
202
- - **Two runs, protocol hardened between them.** Codex GPT-5.4 (Medium) ran late June 2026 on the first, less-hardened harness; Claude Sonnet 5 ran early July 2026 on the current hardened protocol. Because the harness changed, the two agents' controls are **not directly comparable** — which is exactly why the cross-model view is limited to the two platforms where both controls launched.
203
- - **n = 1 per arm, directional.** July 2026 product-evidence runs on representative fixtures — **not a third-party audit**. For a formal comparison, keep each Vise / no-Vise pair on the same model and reasoning setting.
178
+ - **Scoring weights:** capability activation 25% (definitional), product outcome 25%, design integration 20%, runtime hygiene 20% (the scorer's `correctness` key — build/launch cleanliness, not SDK-rule compliance), efficiency 10%. A separate **compliance-findings** metric (`vise validate` on both arms) measures SDK-rule adherence directly. The credible-only composite drops the capability axis and re-weights the other four to 100%.
179
+ - **Small-N, directional.** Controls are n = 1–3 per arm, so neither model is a precise second measurement, and the two are not directly comparable (Codex ran on an earlier harness). The runtime-signal scorer that could over-credit a rendered-but-broken shell is now gated. July 2026 product-evidence runs on representative fixtures — **not a third-party audit**. For a formal comparison, keep each Vise / no-Vise pair on the same model and reasoning setting.
204
180
 
205
181
  <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.</sub>
206
182
 
@@ -230,7 +206,7 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
230
206
  | `vise plan [path] --request "..." [--summary]` | Grounded implementation plan with intake questions and docs citations; `--summary` prints a compact route/intake view |
231
207
  | `vise plan --summary "..."` | Shortcut for quick routing or discovery when the current directory is the repo |
232
208
  | `vise plan-harness [path] --request "..."` | Pre-planning step: build the harness around the request |
233
- | `vise init [path] --request "..." [--answer key=value]` | Write the `sp-vise/` compliance contract once blocking intake is answered; exits 7 (`needs-clarification`) otherwise |
209
+ | `vise init [path] --request "..." [--answer key=value]` | Write the `sp-vise/` compliance contract once blocking intake is answered; exits 7 (`needs-clarification`) otherwise. Refuses to initialize the Vise / social.plus blocks development workspace itself (`status: "internal-workspace"`, exit 10 — use `vise sdk-facts` there, or `--allow-internal-workspace` for an intentional harness run). Replacing a **green** engagement with a different outcome requires `--supersede-engagement` (exit 7 otherwise); the outgoing engagement's final state is recorded to `sp-vise/engagements/` either way |
234
210
  | `vise workplan next [path] --request "..."` | For broad social requests: print the next uncompleted surface and its focused commands |
235
211
  | `vise workplan status [path] --request "..."` | Show the workplan sequence and completed surfaces; ignores unverifiable or drifted snapshot evidence. If the in-scope surface's live `vise check` is already green, auto-records that one surface (same evidence bar as `workplan complete`) so a working, check-green surface isn't left uncounted |
236
212
  | `vise workplan complete [path] --request "..." --surface <id>` | Record a green-checked surface; snapshots evidence plus an integrity manifest under `sp-vise/workplan-snapshots/<surface>/` |
@@ -279,11 +255,13 @@ Everything in this group is local and advisory: no uploads, no `vise check` exit
279
255
  | `vise check [path]` | Re-validate against the recorded contract: `green`, `needs-attestation`, `deterministic-failures`, `blocked`, `contract-drift`, `completeness-gap`, `selected-capability-failures`, `no-platform` (no SDK platform detected — reported as a failure, not a vacuous green), or `runtime-proof-waived` |
280
256
  | `vise check [path] --ci` | Read-only variant that exits non-zero unless green (for CI) |
281
257
  | `vise check [path] --allow-proof-waiver` | Accept an honest `runtime-proof-waived` result (from `vise smoke waive`) as passing (exit 0) instead of blocking; without it a waiver blocks CI by default |
282
- | `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 |
283
- | `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 |
258
+ | `vise check [path] --new-only` | **Brownfield gate.** With a recorded baseline, gate only on rule findings introduced *since* the baseline (pre-existing ones are reported but excluded). The outcome's completeness checklist and selected optional capabilities are **never** excluded — they are the engagement's deliverables and gate like a default check. Default `check` always gates on everything |
259
+ | `vise check [path] --engagement <outcome>` | **Re-verdict one recorded engagement** (from `sp-vise/engagements/`) against the current ruleset and code. Exit `11` on drift, `0` when it still holds; never the active engagement's codes. `ruleset_upgraded` discloses when the installed ruleset moved since it was recorded |
260
+ | `vise check [path] --all-engagements` | Run the normal active gate **plus** re-verdicts of every recorded engagement. Active gate non-green → its normal exit code; active green but a recorded engagement drifted → exit `11` (`engagement-drift`). Findings already gating the active engagement are counted once there (`shared_with_active`) |
261
+ | `vise baseline [path]` | Snapshot the current pre-existing rule findings to `sp-vise/baseline.json` so `check --new-only` can separate legacy debt from new gaps. Deliverables (completeness/selected capabilities) are never baselined. Record it right after `vise init`, before writing code — a mid-build baseline would swallow your own findings. `vise init --baseline` records it at init time |
284
262
  | `vise validate [path]` | Run the deterministic validators only (no attestation comparison) |
285
263
  | `vise sync [path]` | Persist deterministic-pass evidence to `sp-vise/attestations/` |
286
- | `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 |
264
+ | `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. Attestations are bound to the engagement outcome they were reviewed under: after `vise init` switches the sidecar to a different outcome, a rule-global attestation re-gates (`stale_engagement_attestation`) until re-attested — file-scoped attestations survive the switch because new files re-gate them anyway |
287
265
  | `vise explain <ruleId>` | Print a rule's rationale, evidence requirements, and remediation |
288
266
  | `vise status [path]` | Summarize the current compliance state |
289
267
 
@@ -354,6 +332,9 @@ jobs:
354
332
  | `4` | Contract drift — recorded rules no longer match the current ruleset |
355
333
  | `5` | Baseline capability neither implemented nor opted-out (add it or place `// vise: scope-omit <id> — <reason>`) |
356
334
  | `6` | An explicitly selected optional capability failed its source sensors |
335
+ | `8` | No SDK platform detected (`no-platform`) — reported as a failure, never a vacuous green |
336
+ | `9` | Runtime proof waived (`runtime-proof-waived`) — accept explicitly with `--allow-proof-waiver` |
337
+ | `11` | Engagement drift (`--engagement` / `--all-engagements` only): the active gate is green but a recorded engagement re-verdicts non-green — a previously finished surface no longer holds |
357
338
 
358
339
  ## Compliance Contract
359
340
 
@@ -365,6 +346,7 @@ Vise writes local planning, compliance, design, and evidence artifacts under `sp
365
346
  | `sp-vise/intake.json` | `vise init` | Request, outcome, intake answers, design-review status |
366
347
  | `sp-vise/inspection.json` | `vise init` | Platform, surface, and design signals detected at init |
367
348
  | `sp-vise/attestations/*.json` | `vise sync` / `vise attest` | Per-rule evidence: signer, confidence, rationale, source fingerprints for drift detection |
349
+ | `sp-vise/engagements/<outcome>/*.json` | `vise init` (on an outcome switch) | Engagement ledger: the superseded engagement's final check, contract, and intake (latest per outcome) — surfaced by `vise status` as `previousEngagements` |
368
350
  | `sp-vise/creative-brief.json` + `creative-brief.md` | `vise creative` | Advisory brief: goals, archetypes, candidate variants (JSON + human-readable) |
369
351
  | `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 |
370
352
  | `sp-vise/creative-selection.json` | `vise creative accept` | Accepted variant and plan/workplan feed-forward context |
package/dist/flow.js CHANGED
@@ -2,6 +2,12 @@ import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { sidecarDir } from "./sidecar.js";
5
+ export function completedWorkplanBlocksOutcome(stages, requestedOutcome) {
6
+ if (!requestedOutcome || requestedOutcome === "unknown") {
7
+ return true;
8
+ }
9
+ return stages.some((stage) => stage.outcome === requestedOutcome);
10
+ }
5
11
  export function blueprintDigest(input) {
6
12
  const canonical = JSON.stringify({
7
13
  path: input.path ?? null,
package/dist/outcomes.js CHANGED
@@ -63,6 +63,16 @@ export const PLATFORM_FEATURE_GAPS = [
63
63
  sdkSource: "use-cases/social/livestream/go-live-and-room-management",
64
64
  guidance: "Room-based livestreaming — broadcasting, going live, and room management (RoomRepository.createRoom / getRoom / getRooms / stopRoom) — is NOT in the social.plus Flutter SDK; it is available on the iOS, Android, and TypeScript/web SDKs. The Flutter SDK exposes only the DEPRECATED legacy Stream API (StreamRepository.getStream) for VIEWING existing stream posts — there is no Room API in Dart, so do not invent RoomRepository / createRoom. Options: (a) build the broadcast/host livestream experience on a supported platform (iOS, Android, or TypeScript/web), (b) on Flutter, render an existing legacy stream post read-only via StreamRepository.getStream(streamId) (deprecated — exposes title/status/isLive only), or (c) embed a web livestream view.",
65
65
  },
66
+ {
67
+ feature: "For-You (personalized) feed",
68
+ match: /\bfor[\s-]?you\b|personali[sz]ed\s+feed|curated\s+feed|recommend\w*\s+feed/i,
69
+ exclude: /\brecommend\w*\s+(?:communit|user|friend|people|member)\w*/i,
70
+ unavailableOn: ["flutter"],
71
+ availableOn: ["typescript", "ios", "android"],
72
+ objectIds: ["for-you"],
73
+ sdkSource: "social-plus-sdk/social/content-management/posts/retrieve/for-you-feed",
74
+ guidance: "The social.plus Flutter SDK does not expose the For-You (personalized) feed — getForYouFeed / getForYouFeedSetting are available on the iOS, Android, and TypeScript/web SDKs only. Do not invent a getForYouFeed in Dart. Options: (a) build the For-You surface on a supported platform (iOS, Android, or TypeScript/web), (b) on Flutter, render a community-scoped or user-scoped feed (getCommunityFeed / getUserFeed) or the global feed instead, or (c) call a server-side recommendation API from your backend and surface results through your own endpoint.",
75
+ },
66
76
  ];
67
77
  export function platformFeatureGapsFor(request, platform) {
68
78
  return PLATFORM_FEATURE_GAPS.filter((gap) => gap.unavailableOn.includes(platform) &&
package/dist/server.js CHANGED
@@ -5,18 +5,19 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { sidecarDir as sidecarPath } from "./sidecar.js";
8
- import { blueprintDigest, readFlowState, readOmittedSurfaces, renderFlowBlueprint, runtimeReadinessForStages, writeFlowBlueprintHtml, writeFlowState, } from "./flow.js";
8
+ import { blueprintDigest, completedWorkplanBlocksOutcome, readFlowState, readOmittedSurfaces, renderFlowBlueprint, runtimeReadinessForStages, writeFlowBlueprintHtml, writeFlowState, } from "./flow.js";
9
9
  import { normalizeSolutionPathAnswer } from "./solutionPath.js";
10
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
12
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
13
- import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, experienceReportTool, initCompliance, initComplianceTool, recordBaseline, initEngagement, initEngagementTool, listRules, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
13
+ import { attestRule, attestRuleTool, checkAllEngagements, checkCompliance, checkEngagementReVerdict, checkComplianceTool, explainRule, explainRuleTool, experienceReportTool, initCompliance, initComplianceTool, recordBaseline, initEngagement, initEngagementTool, listRules, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
14
14
  import { designCheckTool, designContrastTool, designExtractTool, designInitTokensTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
15
15
  import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
16
16
  import { compileExperienceTool } from "./tools/experienceCompiler.js";
17
17
  import { experienceSensorsTool } from "./tools/experienceSensors.js";
18
18
  import { planHarnessTool } from "./tools/harness.js";
19
19
  import { planIntegrationTool } from "./tools/integration.js";
20
+ import { detectInternalWorkspace, internalWorkspaceWarning } from "./tools/internalWorkspace.js";
20
21
  import { recordLearningTool, showLearningTool } from "./tools/learning.js";
21
22
  import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
22
23
  import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
@@ -153,10 +154,17 @@ async function handleCli(args) {
153
154
  return "exit";
154
155
  }
155
156
  if (command === "inspect") {
156
- await printToolResult(inspectProjectTool, {
157
- repoPath: positionalRepoPath(args.slice(1)),
157
+ const inspectRepo = positionalRepoPath(args.slice(1));
158
+ const inspectResult = await inspectProjectTool.call({
159
+ repoPath: inspectRepo,
158
160
  surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
159
161
  });
162
+ const inspectPayload = JSON.parse(inspectResult.content.map((item) => item.text).join("\n"));
163
+ const inspectWorkspace = await detectInternalWorkspace(inspectRepo);
164
+ if (inspectWorkspace.isInternalWorkspace) {
165
+ inspectPayload.internalWorkspaceWarning = internalWorkspaceWarning(inspectWorkspace);
166
+ }
167
+ console.log(JSON.stringify(inspectPayload, null, 2));
160
168
  return "exit";
161
169
  }
162
170
  if (command === "creative") {
@@ -300,6 +308,10 @@ async function handleCli(args) {
300
308
  const result = await planIntegrationTool.call(input);
301
309
  const text = result.content.map((item) => item.text).join("\n");
302
310
  const payload = JSON.parse(text);
311
+ const planWorkspace = await detectInternalWorkspace(positionalRepoPath(args.slice(1)));
312
+ if (planWorkspace.isInternalWorkspace) {
313
+ payload.internalWorkspaceWarning = internalWorkspaceWarning(planWorkspace);
314
+ }
303
315
  emitResult("plan", hasFlag(args, "summary") ? planSummary(payload) : payload, args);
304
316
  return "exit";
305
317
  }
@@ -462,9 +474,19 @@ async function handleCli(args) {
462
474
  return "exit";
463
475
  }
464
476
  if (command === "init") {
465
- assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer", "allow-unresolved-intake", "baseline"], "init");
477
+ assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer", "allow-unresolved-intake", "baseline", "allow-internal-workspace", "supersede-engagement"], "init");
466
478
  const initRepo = positionalRepoPath(args.slice(1));
467
479
  const initRequest = requiredFlagValue(args, "request", "init requires --request.");
480
+ const initWorkspace = await detectInternalWorkspace(initRepo);
481
+ if (initWorkspace.isInternalWorkspace && !hasFlag(args, "allow-internal-workspace")) {
482
+ console.log(JSON.stringify({
483
+ status: "internal-workspace",
484
+ ...internalWorkspaceWarning(initWorkspace),
485
+ instruction: "Refusing to initialize the compliance sidecar here. If this really is intentional (e.g. a harness run at the workspace root), re-run with --allow-internal-workspace.",
486
+ }, null, 2));
487
+ process.exitCode = 10;
488
+ return "exit";
489
+ }
468
490
  const initWorkplanPlan = await workplanPlan(initRepo, initRequest, args);
469
491
  const { sequence: initSequence, omitted: initOmitted, canonicalRequest: initCanonicalRequest } = await activeBlueprint(initRepo, initRequest, initWorkplanPlan);
470
492
  if (initSequence.length >= 2 && initWorkplanPlan.platform !== "unknown") {
@@ -474,15 +496,16 @@ async function handleCli(args) {
474
496
  process.exitCode = 7;
475
497
  return "exit";
476
498
  }
477
- const workplanInitGate = await requireNextWorkplanSurfaceInit(initRepo, initCanonicalRequest, initSequence, keyValueFlag(args, "answer"), initOmitted, initRequest);
499
+ const workplanInitGate = await requireNextWorkplanSurfaceInit(initRepo, initCanonicalRequest, initSequence, keyValueFlag(args, "answer"), initOmitted, initRequest, initWorkplanPlan.outcome);
478
500
  if (workplanInitGate) {
479
501
  console.log(JSON.stringify(workplanInitGate, null, 2));
480
502
  process.exitCode = 7;
481
503
  return "exit";
482
504
  }
483
505
  }
484
- const result = await initCompliance(initRepo, initRequest, flagValue(args, "surface") ?? flagValue(args, "surface-path"), keyValueFlag(args, "answer"), { allowUnresolvedIntake: hasFlag(args, "allow-unresolved-intake") });
485
- if (result.status === "needs-clarification" && typeof result.exitCode === "number") {
506
+ const result = await initCompliance(initRepo, initRequest, flagValue(args, "surface") ?? flagValue(args, "surface-path"), keyValueFlag(args, "answer"), { allowUnresolvedIntake: hasFlag(args, "allow-unresolved-intake"), baselinePlanned: hasFlag(args, "baseline"), supersedeEngagement: hasFlag(args, "supersede-engagement") });
507
+ if ((result.status === "needs-clarification" || result.status === "needs-engagement-supersede-confirmation") &&
508
+ typeof result.exitCode === "number") {
486
509
  process.exitCode = result.exitCode;
487
510
  }
488
511
  if (hasFlag(args, "baseline") && result.status === "initialized") {
@@ -492,7 +515,26 @@ async function handleCli(args) {
492
515
  return "exit";
493
516
  }
494
517
  if (command === "check") {
495
- assertOnlyKnownFlags(args, ["ci", "format", "new-only", "allow-proof-waiver"], "check");
518
+ assertOnlyKnownFlags(args, ["ci", "format", "new-only", "allow-proof-waiver", "engagement", "all-engagements"], "check");
519
+ const engagementOutcome = flagValue(args, "engagement");
520
+ if (engagementOutcome) {
521
+ if (hasFlag(args, "new-only") || hasFlag(args, "ci") || hasFlag(args, "all-engagements")) {
522
+ throw new Error("--engagement re-verdicts one recorded engagement and cannot combine with --new-only, --ci, or --all-engagements.");
523
+ }
524
+ const reVerdict = await checkEngagementReVerdict(positionalRepoPath(args.slice(1)), engagementOutcome);
525
+ emitResult("check", reVerdict, args);
526
+ process.exitCode = typeof reVerdict.exitCode === "number" ? reVerdict.exitCode : 0;
527
+ return "exit";
528
+ }
529
+ if (hasFlag(args, "all-engagements")) {
530
+ if (hasFlag(args, "ci")) {
531
+ throw new Error("--all-engagements cannot combine with --ci (the CI shape covers the active engagement only).");
532
+ }
533
+ const combined = await checkAllEngagements(positionalRepoPath(args.slice(1)), { newOnly: hasFlag(args, "new-only"), allowProofWaiver: hasFlag(args, "allow-proof-waiver") });
534
+ emitResult("check", combined, args);
535
+ process.exitCode = typeof combined.exitCode === "number" ? combined.exitCode : 0;
536
+ return "exit";
537
+ }
496
538
  const result = await checkCompliance(positionalRepoPath(args.slice(1)), { newOnly: hasFlag(args, "new-only"), allowProofWaiver: hasFlag(args, "allow-proof-waiver") });
497
539
  emitResult("check", hasFlag(args, "ci") ? ciCheckResult(result) : result, args);
498
540
  process.exitCode = result.exitCode;
@@ -657,7 +699,6 @@ function internalCommandsSection() {
657
699
  return `
658
700
 
659
701
  Internal commands (not part of the public surface — see docs/TOOLS.md):
660
- vise sdk-facts --platform <p> [--capability <c>] Read bundled SDK surface facts. Projectless, read-only.
661
702
  vise blocks <list|plan|add> ... Install and validate social.plus blocks in customer projects.
662
703
  vise resolve --request "..." Resolve a request into the closest supported Vise outcome.
663
704
  vise skill-path Print the bundled skill source path and install targets.
@@ -1105,6 +1146,7 @@ Usage:
1105
1146
  vise learning show [repoPath] Show local learning summary
1106
1147
  vise debug [repoPath] --error ... Debug an SDK-specific runtime error and emit a repair brief
1107
1148
  vise explore "<request>" Discover what social.plus offers for a request (no project/credentials needed)
1149
+ vise sdk-facts --platform <p> Read proven SDK surface facts (symbols, capabilities, model schemas). Projectless, read-only
1108
1150
  vise plan [repoPath] --request "..." Create an implementation plan
1109
1151
  vise workplan next [repoPath] --request "..." Get the next broad-social surface to implement
1110
1152
  vise init [repoPath] --request "..." Initialize compliance sidecar (add --baseline on a brownfield app)
@@ -2186,13 +2228,16 @@ function socialWorkplanSequence(plan) {
2186
2228
  }
2187
2229
  return workplan.sequence;
2188
2230
  }
2189
- async function requireNextWorkplanSurfaceInit(repoRoot, request, sequence, answers, omitted = [], requestedAs) {
2231
+ async function requireNextWorkplanSurfaceInit(repoRoot, request, sequence, answers, omitted = [], requestedAs, requestedOutcome) {
2190
2232
  const progress = await verifiedWorkplanProgress(repoRoot, request, sequence);
2191
2233
  const completedIds = new Set(progress.completed.map((item) => item.surface));
2192
2234
  const nextSurface = sequence.find((surface) => !completedIds.has(surface.id));
2193
2235
  const selectedSurface = resolveWorkplanSurfaceAnswer(sequence, answers.feature_surface);
2194
2236
  const selectedOmittedSurface = resolveOmittedSurfaceAnswer(omitted, answers.feature_surface);
2195
2237
  if (!nextSurface) {
2238
+ if (!completedWorkplanBlocksOutcome(sequence, requestedOutcome)) {
2239
+ return null;
2240
+ }
2196
2241
  return {
2197
2242
  status: "workplan-complete",
2198
2243
  exitCode: 7,
@@ -242,7 +242,7 @@ async function packageChangeFor(root, platform, packageInfo, packageSource) {
242
242
  };
243
243
  }
244
244
  const file = "package.json";
245
- const packageJson = await readPackageJson(path.join(root, file));
245
+ const packageJson = await readPackageJson(path.join(root, file)).catch(() => ({}));
246
246
  return {
247
247
  file,
248
248
  dependency: packageInfo.dependencyName,