@amityco/social-plus-vise 0.14.0 → 0.14.2

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,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.2 — 2026-06-03
8
+
9
+ **Theme:** SDK facts bridge for social.plus Block Factory.
10
+
11
+ ### Added
12
+ - **Bundled SDK surface snapshot** in `sdk-surface/` for offline/npm use across TypeScript, Android, iOS, and Flutter.
13
+ - **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.
14
+ - **TypeScript Comments/Reactions capability facts** for the first Block Factory validation slice, including React Native aliasing to the TypeScript SDK surface.
15
+
16
+ ---
17
+
18
+ ## 0.14.1 — 2026-06-03
19
+
20
+ **Theme:** Retract the enumerative DesignBuildBrief from plan output — our own benchmark said so.
21
+
22
+ ### Changed
23
+ - **`vise plan` no longer emits `designContract.brief`** (the 0.14.0 roles / component hints / outcome recipes). The pre-registered ablation we shipped 0.14.0 with came back negative — twice: agents given the enumerative brief produced LESS on-contract UI than agents given the design contract alone (by-name token usage ~75% vs ~89–92%; two runs, n=6 per arm, Cursor/Composer 2.5; full evidence in `benchmarks/brief-ablation/`). The mechanism: enumerative guidance narrows what capable agents build and style, while the contract + `design check` loop drives wholesale token adoption. We measure before we claim — and this one measured against us, so it's out.
24
+ - **Kept:** the non-blocking `primary_action_token` intake question (the brief is still computed internally to detect a missing primary-action token — it asks instead of steering). The brief generator and its 17-scenario test suite remain in-tree for future redesign.
25
+
26
+ ---
27
+
7
28
  ## 0.14.0 — 2026-06-03
8
29
 
9
30
  **Theme:** DesignBuildBrief — plan-time, grounded UI-building guidance for coding agents (advisory).
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 building a software factory directly on top of the customer's project.
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 acts as the foreman of this factory, wrapping 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.**
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.
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) {
@@ -2391,23 +2391,28 @@ const ROLE_RULES = [
2391
2391
  reason: "name contains a border keyword with a primary/secondary modifier — the noun keyword sets the role family",
2392
2392
  },
2393
2393
  // Primary: plain "primary" → high; "brand"/"accent" → medium
2394
+ // EXCLUSION: action roles bind interactive-family names only.
2395
+ // A token whose name contains a text-family noun (text/foreground/fg) MUST
2396
+ // NEVER bind primaryAction or secondaryAction — it represents a text colour,
2397
+ // not an interactive accent (e.g. --text-bright-accent is a text accent, while
2398
+ // --essential-bright-accent is the real interactive accent).
2394
2399
  {
2395
- test: (n) => /\bprimary\b/.test(n) && !/brand|accent/.test(n),
2400
+ test: (n) => /\bprimary\b/.test(n) && !/brand|accent/.test(n) && !/\btext\b|foreground|\bfg\b/.test(n),
2396
2401
  role: "primaryAction",
2397
2402
  confidence: "high",
2398
- reason: "name contains 'primary'",
2403
+ reason: "name contains 'primary' (and is not a text-family name)",
2399
2404
  },
2400
2405
  {
2401
- test: (n) => /\bbrand\b|\baccent\b/.test(n),
2406
+ test: (n) => /\bbrand\b|\baccent\b/.test(n) && !/\btext\b|foreground|\bfg\b/.test(n),
2402
2407
  role: "primaryAction",
2403
2408
  confidence: "medium",
2404
- reason: "name contains 'brand' or 'accent'",
2409
+ reason: "name contains 'brand' or 'accent' (and is not a text-family name)",
2405
2410
  },
2406
2411
  {
2407
- test: (n) => /\bsecondary\b/.test(n),
2412
+ test: (n) => /\bsecondary\b/.test(n) && !/\btext\b|foreground|\bfg\b/.test(n),
2408
2413
  role: "secondaryAction",
2409
2414
  confidence: "high",
2410
- reason: "name contains 'secondary'",
2415
+ reason: "name contains 'secondary' (and is not a text-family name)",
2411
2416
  },
2412
2417
  {
2413
2418
  test: (n) => /\bdanger\b|\berror\b|\bdestructive\b/.test(n),
@@ -2510,15 +2515,51 @@ export function buildDesignBrief(contract) {
2510
2515
  const roles = [...roleMap.values()];
2511
2516
  // Helper: is a role name in this brief?
2512
2517
  const roleNames = new Set(roles.map((r) => r.role));
2518
+ // String-typed version for use in contexts where we compare arbitrary strings against role names.
2519
+ const roleNameStrings = new Set(roles.map((r) => r.role));
2513
2520
  // ── Component hints ─────────────────────────────────────────────────────────
2514
2521
  // Reference ONLY tokens that actually exist in the contract.
2515
- const firstToken = (cat) => contract.tokens.find((t) => t.category === cat && t.name !== null);
2516
- const radiusToken = firstToken("radius");
2517
- const spaceToken = firstToken("space");
2522
+ /**
2523
+ * Representative token selector: among a category's declared tokens, prefer
2524
+ * (in order) a name containing "base", then "default", then "md"/"medium",
2525
+ * then the SHORTEST name, then first-in-order.
2526
+ *
2527
+ * Using first-in-contract-order (old behaviour) selected unrepresentative tokens
2528
+ * like --encore-corner-radius-smaller over --encore-corner-radius-base, causing
2529
+ * agents to anchor on non-base variants and miss the authoritative base token.
2530
+ */
2531
+ function representativeToken(cat) {
2532
+ const candidates = contract.tokens.filter((t) => t.category === cat && t.name !== null && t.provenance === "declared");
2533
+ if (candidates.length === 0)
2534
+ return undefined;
2535
+ // Scoring: lower is better. 0 = base, 1 = default, 2 = md/medium, 3 = shortest, 4 = first.
2536
+ const score = (name) => {
2537
+ const n = name.toLowerCase();
2538
+ if (/\bbase\b/.test(n))
2539
+ return 0;
2540
+ if (/\bdefault\b/.test(n))
2541
+ return 1;
2542
+ if (/\bmd\b|\bmedium\b/.test(n))
2543
+ return 2;
2544
+ return 3;
2545
+ };
2546
+ return candidates.reduce((best, t) => {
2547
+ const bs = score(best.name);
2548
+ const ts = score(t.name);
2549
+ if (ts < bs)
2550
+ return t;
2551
+ if (ts > bs)
2552
+ return best;
2553
+ // Same bucket: prefer shorter name, then first-in-order (best wins ties).
2554
+ return t.name.length < best.name.length ? t : best;
2555
+ });
2556
+ }
2557
+ const radiusToken = representativeToken("radius");
2558
+ const spaceToken = representativeToken("space");
2518
2559
  // Border colour: sourced from the inferred border role (a color token named *border*/*outline*/*divider*).
2519
2560
  // There is no "border" TokenCategory — border colours live in the "color" category.
2520
2561
  const borderRoleColor = roleMap.get("border");
2521
- const shadowToken = firstToken("shadow");
2562
+ const shadowToken = representativeToken("shadow");
2522
2563
  const primaryRole = roleMap.get("primaryAction");
2523
2564
  // card hint
2524
2565
  const cardGuidanceLines = [];
@@ -2607,6 +2648,31 @@ export function buildDesignBrief(contract) {
2607
2648
  if (primaryRole) {
2608
2649
  avoidLines.push(line(`Do not override the primary colour token (${primaryRole.token}) with ad-hoc colours on interactive elements.`, [primaryRole.role]));
2609
2650
  }
2651
+ // ── Breadth instruction (Fix 3 — anti-anchoring) ─────────────────────────────
2652
+ //
2653
+ // The roles and hints above are a starting lens — a minimal named anchor set.
2654
+ // Without an explicit instruction, agents anchor on the named tokens and stop
2655
+ // reading the full token file. This breadth line counters that by directing the
2656
+ // agent to the FULL declared token set, grounded in one representative token
2657
+ // per category so the grounding itself spans the system's breadth.
2658
+ //
2659
+ // Grounding rule: up to one representative declared token per category present,
2660
+ // using the representativeToken() selector (Fix 2). Only emitted when ≥1
2661
+ // declared token exists — weak/empty contracts do NOT get an invented breadth line.
2662
+ const ALL_TOKEN_CATEGORIES = ["color", "space", "radius", "shadow", "fontFamily", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "borderWidth", "breakpoint", "motion", "opacity", "zIndex"];
2663
+ // Collect one representative declared token per category that has any declared tokens.
2664
+ const breadthGroundingTokens = [];
2665
+ for (const cat of ALL_TOKEN_CATEGORIES) {
2666
+ const rep = representativeToken(cat);
2667
+ if (rep)
2668
+ breadthGroundingTokens.push(rep);
2669
+ }
2670
+ const totalDeclaredTokens = contract.tokens.filter((t) => t.provenance === "declared" && t.name !== null).length;
2671
+ if (breadthGroundingTokens.length > 0) {
2672
+ // Build the grounding set (names of the representative tokens).
2673
+ const breadthGrounding = breadthGroundingTokens.map((t) => t.name);
2674
+ doLines.push(line(`The roles and hints above are a starting lens, not the full design system — reference the FULL declared token set (${totalDeclaredTokens} declared tokens) and prefer an existing token over any new value.`, breadthGrounding));
2675
+ }
2610
2676
  // ── Review notes ─────────────────────────────────────────────────────────────
2611
2677
  const reviewNotes = [];
2612
2678
  if (strength === "weak") {
@@ -2626,12 +2692,38 @@ export function buildDesignBrief(contract) {
2626
2692
  if (!roleNames.has("border") && contract.stats.declared_tokens > 0) {
2627
2693
  reviewNotes.push("No border colour found — consider naming a token --color-border or --color-outline.");
2628
2694
  }
2695
+ // Conservative-inference reviewNote (Fix 3):
2696
+ // When roles bind fewer than half the declared COLOR tokens, the role inference
2697
+ // was conservative on this vocabulary (e.g. Encore's essential-/decorative-
2698
+ // prefixed names are correct restraint but result in thin role coverage).
2699
+ // Alert the agent that it MUST read the full token file rather than relying on roles alone.
2700
+ const declaredColorCount = declaredColorTokens.length;
2701
+ const colorTokensBoundToRoles = roles.filter((r) => declaredColorTokens.some((t) => t.name === r.token)).length;
2702
+ if (declaredColorCount > 0 && colorTokensBoundToRoles < declaredColorCount / 2) {
2703
+ reviewNotes.push(`Role inference was conservative on this vocabulary — only ${colorTokensBoundToRoles} of ${declaredColorCount} declared colour token(s) are bound to roles. The full token file must be read to discover the complete colour system.`);
2704
+ }
2629
2705
  // ── Summary ──────────────────────────────────────────────────────────────────
2630
2706
  const tokenCount = contract.tokens.filter((t) => t.name !== null).length;
2707
+ // Count how many distinct declared token names are referenced by roles + hints.
2708
+ const briefReferencedTokenNames = new Set();
2709
+ for (const r of roles)
2710
+ briefReferencedTokenNames.add(r.token);
2711
+ for (const h of componentHints) {
2712
+ if (!("absent" in h)) {
2713
+ for (const l of h.guidance) {
2714
+ for (const g of l.groundedIn) {
2715
+ // Only add entries that are actual declared token names (not role names).
2716
+ if (!roleNameStrings.has(g))
2717
+ briefReferencedTokenNames.add(g);
2718
+ }
2719
+ }
2720
+ }
2721
+ }
2722
+ const briefCoveredCount = [...briefReferencedTokenNames].filter((n) => contract.tokens.some((t) => t.name === n && t.provenance === "declared")).length;
2631
2723
  const summary = roles.length > 0
2632
- ? `Brief grounded in ${tokenCount} named token(s) and ${roles.length} inferred role(s). Contract strength: ${strength}.`
2724
+ ? `Brief grounded in ${tokenCount} named token(s) and ${roles.length} inferred role(s). Contract strength: ${strength}. roles/hints cover ${briefCoveredCount} of ${totalDeclaredTokens} declared tokens — consult the full set.`
2633
2725
  : tokenCount > 0
2634
- ? `Brief grounded in ${tokenCount} named token(s); no colour roles could be inferred from token names. Contract strength: ${strength}.`
2726
+ ? `Brief grounded in ${tokenCount} named token(s); no colour roles could be inferred from token names. Contract strength: ${strength}. roles/hints cover ${briefCoveredCount} of ${totalDeclaredTokens} declared tokens — consult the full set.`
2635
2727
  : `Contract has no named tokens — guidance is unavailable. Contract strength: ${strength}. Run \`vise design extract --from-project\` to derive tokens from the host project.`;
2636
2728
  return {
2637
2729
  summary,
@@ -2728,6 +2820,16 @@ export function buildOutcomeDesignRecipe(brief, outcome) {
2728
2820
  items.push(line(`Moderation actions (report, block, mute) use the danger colour token (${danger.token}).`, ["danger"]));
2729
2821
  }
2730
2822
  }
2823
+ // Breadth audit item (Fix 3 — anti-anchoring):
2824
+ // Append a final item instructing the agent to audit against the FULL token set.
2825
+ // Grounded the same way as the brief's breadth do-line: reuse the groundedIn of
2826
+ // the "starting lens" do-line if it exists in the brief (spans the breadth of
2827
+ // the system's categories). Only emitted when the brief has declared tokens
2828
+ // (the breadth do-line only exists when there are declared tokens).
2829
+ const breadthDoLine = brief.do.find((l) => l.text.includes("starting lens"));
2830
+ if (breadthDoLine) {
2831
+ items.push(line("Before finishing, audit the UI against the full declared token set and replace any near-miss values with the matching token.", breadthDoLine.groundedIn));
2832
+ }
2731
2833
  if (items.length === 0)
2732
2834
  return undefined;
2733
2835
  return { outcome, items };
@@ -4,7 +4,7 @@ import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, classifyOutcome, getOutcomeDefinition
4
4
  import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
5
5
  import { capabilityChecklist } from "../capabilities.js";
6
6
  import { applicableComplianceRuleSummaries } from "./compliance.js";
7
- import { buildDesignBrief, buildOutcomeDesignRecipe, readDesignContract } from "./design.js";
7
+ import { buildDesignBrief, readDesignContract } from "./design.js";
8
8
  import { sdkVersionGuidance } from "./sdkVersion.js";
9
9
  import { detectCommandSensors } from "./harness.js";
10
10
  import { inspectProject } from "./project.js";
@@ -71,13 +71,14 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
71
71
  answers,
72
72
  });
73
73
  const definition = getOutcomeDefinition(outcome);
74
- // Design contract is loaded before intake so the brief can inform the fallback
75
- // intake question (missing primary-action token) at assembly time.
74
+ // The design brief is computed INTERNALLY only, to drive the missing-primary-action
75
+ // intake fallback. It is deliberately NOT emitted in plan output: a pre-registered
76
+ // ablation (benchmarks/brief-ablation/RESULTS.md, two runs, n=6/arm) showed that
77
+ // enumerative plan-time design guidance NARROWS what capable agents build and style —
78
+ // the contract + design-check loop alone scored higher on by-name token conformance.
79
+ // Retracted in 0.14.1; the generator stays in design.ts for future redesign.
76
80
  const designContract = await readDesignContract(repoRoot);
77
81
  const designBrief = designContract ? buildDesignBrief(designContract) : undefined;
78
- if (designBrief && (outcome === "add-feed" || outcome === "add-chat")) {
79
- designBrief.outcomeRecipe = buildOutcomeDesignRecipe(designBrief, outcome) ?? undefined;
80
- }
81
82
  const intake = intakeFor(ctx, definition.intakeQuestions(ctx), outcome, designBrief);
82
83
  // Advisory SDK-version currency guidance (npm registry for TS/RN; version-agnostic
83
84
  // for native). Best-effort — degrades to greenfield "install latest + pin" if the
@@ -119,7 +120,7 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
119
120
  sensors: sensors.map((sensor) => ({ name: sensor.name, command: sensor.command, source: sensor.source })),
120
121
  stopConditions: composeStopConditions(ctx, definition.stopConditions(ctx), inspection.surfaces, surfacePath),
121
122
  evidencePolicy: "Every implementation step must cite at least one detected file, docs page, validator rule, or required user input. If evidence is missing, stop and ask the user instead of inventing details.",
122
- designContract: designContract && designBrief ? designContractGuidance(designContract, designBrief) : undefined,
123
+ designContract: designContract ? designContractGuidance(designContract) : undefined,
123
124
  completenessChecklist: completenessChecklistFor(outcome),
124
125
  sdkVersion,
125
126
  };
@@ -143,7 +144,7 @@ function completenessChecklistFor(outcome) {
143
144
  // references `var(--x)` / maps it per platform); inferred tokens carry their
144
145
  // raw value plus a usage count and an explicit "inferred" marker so they are
145
146
  // never mistaken for authoritative brand values.
146
- function designContractGuidance(contract, brief) {
147
+ function designContractGuidance(contract) {
147
148
  const byCategory = (category) => contract.tokens
148
149
  .filter((token) => token.category === category)
149
150
  .map((token) => token.provenance === "declared" && token.name
@@ -168,7 +169,6 @@ function designContractGuidance(contract, brief) {
168
169
  breakpoints: contract.breakpoints.map((breakpoint) => breakpoint.raw),
169
170
  attestation: `When you record a design attestation, cite this contract digest (${contract.digest}) so the generated feed can be claimed conformant to the customer's prototype.`,
170
171
  advisoryOnly: "This contract is advisory generation guidance — it adds no deterministic enforcement and never fails `vise check`.",
171
- brief,
172
172
  };
173
173
  }
174
174
  function intentFor(request, interpretation) {