@amityco/social-plus-vise 0.12.3 → 0.12.5

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/README.md CHANGED
@@ -251,6 +251,7 @@ The flow above is what the skill teaches your AI agent. You — the human — dr
251
251
  | `vise design check [path]` | Advisory, **non-blocking** report on how closely the UI code matches the contract (token coverage + on/off-contract color literals). Never fails a build and is **not** a `vise check` gate |
252
252
  | `vise design preview [path] [--reference <prototype>]` | Write a self-contained `sp-vise/design-preview.html`: the contract's tokens as visual swatches + the conformance report + the HTML reference embedded for side-by-side review. Vise renders the artifact; a human/VLM judges the visual match. Dependency-free — **not** an automated pixel diff |
253
253
  | `vise design reference [path] [--title <name>]` | Write a self-contained `sp-vise/design-reference.html`: human/VLM-readable design-system spec — token swatches, type samples, component demos, and a growth-layer summary. Pairs with `design-contract.json` (machine-readable). Use `--title` to name the design system (e.g. `--title Streamly`). Advisory — **not** an enforcement gate |
254
+ | `vise design init-tokens [path] [--force]` | Scaffold `src/styles/social-plus-tokens.css` — the dedicated, customer-editable token file for social.plus features. **Greenfield:** neutral defaults (full `--sp-*` token set). **Brownfield:** seeded from your existing concrete tokens. Idempotent — never overwrites an existing file (use `--force` to override). After editing, run `vise design extract --from-project` to refresh the contract. `design_init_tokens` |
254
255
 
255
256
  The extracted contract is **advisory input for generation**, not an enforcement gate: a token-poor prototype yields a weaker — never wrong — contract, and absence of a prototype simply means no contract (the existing `*.design.reuse-detected-tokens` rules still cover reuse of a host project's own design system).
256
257
 
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
10
- import { designCheckTool, designExtractTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
10
+ import { designCheckTool, designExtractTool, designInitTokensTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
11
11
  import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
12
12
  import { planHarnessTool } from "./tools/harness.js";
13
13
  import { planIntegrationTool } from "./tools/integration.js";
@@ -38,6 +38,7 @@ const tools = new Map([
38
38
  designCheckTool,
39
39
  designPreviewTool,
40
40
  designReferenceTool,
41
+ designInitTokensTool,
41
42
  ].map((tool) => [tool.name, tool]));
42
43
  const bundledSkillName = "social-plus-vise";
43
44
  const cliResult = await handleCli(process.argv.slice(2));
@@ -305,7 +306,15 @@ async function handleCli(args) {
305
306
  });
306
307
  return "exit";
307
308
  }
308
- console.error(`Unknown design subcommand: ${sub ?? "(none)"}. Expected "extract", "check", "preview", or "reference".`);
309
+ if (sub === "init-tokens") {
310
+ assertOnlyKnownFlags(subArgs, ["force"], "design init-tokens");
311
+ await printToolResult(designInitTokensTool, {
312
+ repoPath: positionalRepoPath(subArgs),
313
+ force: hasFlag(subArgs, "force"),
314
+ });
315
+ return "exit";
316
+ }
317
+ console.error(`Unknown design subcommand: ${sub ?? "(none)"}. Expected "extract", "check", "preview", "reference", or "init-tokens".`);
309
318
  process.exitCode = 1;
310
319
  return "exit";
311
320
  }
@@ -515,6 +524,7 @@ Usage:
515
524
  vise design check [repoPath]
516
525
  vise design preview [repoPath] [--reference <prototypePath>]
517
526
  vise design reference [repoPath] [--title "Streamly"] [--no-write]
527
+ vise design init-tokens [repoPath] [--force]
518
528
 
519
529
  extract Build a graded design contract and write it to sp-vise/design-contract.json.
520
530
  Declared CSS custom properties become exact tokens; repeated literal values
@@ -535,7 +545,12 @@ preview Write a self-contained sp-vise/design-preview.html: the contract's to
535
545
  reference Write a self-contained sp-vise/design-reference.html: human/VLM-readable
536
546
  design-system spec (token swatches, type samples, component demos, growth-layer
537
547
  summary). Pairs with the machine-readable design-contract.json. Use --title to
538
- set the design system name. Advisory — not an enforcement gate.`;
548
+ set the design system name. Advisory — not an enforcement gate.
549
+ init-tokens Scaffold src/styles/social-plus-tokens.css in the customer's project —
550
+ the dedicated editable token file for social.plus features. Greenfield:
551
+ neutral defaults. Brownfield (existing tokens found): seeded from their
552
+ concrete values. Idempotent (never overwrites); use --force to overwrite.
553
+ After editing, run vise design extract --from-project to refresh the contract.`;
539
554
  }
540
555
  return `${packageName}
541
556
 
@@ -358,14 +358,17 @@ export async function checkCompliance(repoPath) {
358
358
  // If the current source now produces a finding, the old sync record must
359
359
  // not mask code drift; the next `vise sync` will remove it.
360
360
  if (attestation.status === "deterministic-pass") {
361
+ const failStatus = rule.advisory ? "advisory" : rule.enforcement.attestation.allowed ? "attestation-needed" : "deterministic-fail";
361
362
  results.push({
362
363
  ruleId: rule.id,
363
364
  title: rule.title,
364
365
  severity: rule.severity,
365
- status: rule.enforcement.attestation.allowed ? "attestation-needed" : "deterministic-fail",
366
- reason: rule.enforcement.attestation.allowed
367
- ? "Current deterministic check failed; previously synced deterministic-pass evidence is stale."
368
- : "Current deterministic check failed; this rule does not allow attestation.",
366
+ status: failStatus,
367
+ reason: rule.advisory
368
+ ? "Advisory: informational only does not affect compliance status."
369
+ : rule.enforcement.attestation.allowed
370
+ ? "Current deterministic check failed; previously synced deterministic-pass evidence is stale."
371
+ : "Current deterministic check failed; this rule does not allow attestation.",
369
372
  finding,
370
373
  recommendation: finding?.recommendation,
371
374
  rationale: rule.rationale,
@@ -384,8 +387,10 @@ export async function checkCompliance(repoPath) {
384
387
  ruleId: rule.id,
385
388
  title: rule.title,
386
389
  severity: rule.severity,
387
- status: rule.enforcement.attestation.allowed ? "attestation-needed" : "deterministic-fail",
388
- reason: "Recorded attestation source fingerprints changed. Re-check the evidence and record a fresh attestation.",
390
+ status: rule.advisory ? "advisory" : rule.enforcement.attestation.allowed ? "attestation-needed" : "deterministic-fail",
391
+ reason: rule.advisory
392
+ ? "Advisory: informational only — does not affect compliance status."
393
+ : "Recorded attestation source fingerprints changed. Re-check the evidence and record a fresh attestation.",
389
394
  finding,
390
395
  recommendation: finding?.recommendation,
391
396
  rationale: rule.rationale,
@@ -413,9 +418,14 @@ export async function checkCompliance(repoPath) {
413
418
  continue;
414
419
  }
415
420
  }
416
- const baseStatus = (rule.enforcement.attestation.allowed || isInferential) ? "attestation-needed" : "deterministic-fail";
421
+ const baseStatus = rule.advisory
422
+ ? "advisory"
423
+ : (rule.enforcement.attestation.allowed || isInferential) ? "attestation-needed" : "deterministic-fail";
417
424
  let fallbackReason = "This rule does not allow attestation.";
418
- if (isInferential) {
425
+ if (rule.advisory) {
426
+ fallbackReason = "Advisory: informational only — does not affect compliance status.";
427
+ }
428
+ else if (isInferential) {
419
429
  fallbackReason = "Inferential check required. Please provide a host-agent attestation.";
420
430
  }
421
431
  else if (rule.enforcement.attestation.allowed) {
@@ -436,6 +446,7 @@ export async function checkCompliance(repoPath) {
436
446
  const summary = summarize(results);
437
447
  const hasBlocked = results.some((result) => result.status === "blocked");
438
448
  const hasDeterministicFailure = results.some((result) => result.status === "deterministic-fail");
449
+ // "advisory" status is intentionally excluded — advisory rules surface but never block.
439
450
  const needsAttestation = results.some((result) => result.status === "attestation-needed" || result.status === "stale");
440
451
  // Precedence: blocked (exit 3) > deterministic-failures (2) > needs-attestation (1) > green (0).
441
452
  // Contract drift (exit 4) is handled earlier and short-circuits the loop.
@@ -157,7 +157,16 @@ export async function extractDesignContractFromProject(repoPath) {
157
157
  }
158
158
  }
159
159
  inputs.sort();
160
- return buildDesignContract({ css, html: [], inputs }, { kind: "host-project", inputs, file_count: inputs.length }, moduleTokens);
160
+ // Hash each source file so design check can detect staleness without re-extracting.
161
+ const input_digests = {};
162
+ for (const rel of inputs) {
163
+ try {
164
+ const content = await readFile(path.join(root, rel), "utf8");
165
+ input_digests[rel] = `sha256:${createHash("sha256").update(content).digest("hex")}`;
166
+ }
167
+ catch { /* file read already succeeded above; defensive only */ }
168
+ }
169
+ return buildDesignContract({ css, html: [], inputs }, { kind: "host-project", inputs, input_digests, file_count: inputs.length }, moduleTokens);
161
170
  }
162
171
  export async function writeDesignContract(repoPath, contract) {
163
172
  const sidecarDir = path.join(path.resolve(repoPath), "sp-vise");
@@ -381,6 +390,215 @@ function safeCss(value) {
381
390
  return value.replace(/[<>"]/g, "").slice(0, 200);
382
391
  }
383
392
  // ---------------------------------------------------------------------------
393
+ // Social-plus token scaffold (vise design init-tokens)
394
+ // ---------------------------------------------------------------------------
395
+ //
396
+ // Creates a dedicated `src/styles/social-plus-tokens.css` in the customer's
397
+ // project — the single editable source for social.plus feature styling.
398
+ // The contract always points at this file; customers edit it freely without
399
+ // needing an AI agent. Design check detects changes and prompts re-extract.
400
+ //
401
+ // Two cases:
402
+ // Greenfield (no existing design system): scaffold Option-B neutral defaults.
403
+ // Brownfield (existing tokens found): seed from their concrete values.
404
+ // Already exists: leave it untouched (idempotent — never clobbers edits).
405
+ /** Relative path within the customer's project where sp tokens live. */
406
+ export const SP_TOKENS_PATH = "src/styles/social-plus-tokens.css";
407
+ /** Option-B neutral default — a clean, adaptive light-mode system using system
408
+ * fonts. All token names use `--sp-` prefix to avoid collision with the
409
+ * customer's own design system. Will be replaced with the official social.plus
410
+ * palette (Option A) once that palette is finalised. */
411
+ export const NEUTRAL_SP_TOKENS_DEFAULT = `/* social-plus-tokens.css — social.plus feature design system.
412
+ * This file controls the look of all social.plus features in your app.
413
+ * Edit freely. Run: vise design extract --from-project . to refresh the contract.
414
+ * NOTE: design check scans the whole project; the main-app palette appearing as
415
+ * "off-contract" is expected and advisory only — not a failure. */
416
+ :root {
417
+ /* ── Brand / interactive ───────────────────────────────── */
418
+ --sp-color-brand: #1054DE;
419
+ --sp-color-brand-hover: #0D47C5;
420
+ --sp-color-brand-subtle: #EEF3FF;
421
+ --sp-color-brand-text: #FFFFFF;
422
+
423
+ /* ── Backgrounds ───────────────────────────────────────── */
424
+ --sp-color-bg: #FFFFFF;
425
+ --sp-color-surface: #F8F9FA;
426
+ --sp-color-surface-raised: #FFFFFF;
427
+ --sp-color-surface-hover: #F0F2F4;
428
+ --sp-color-overlay: rgba(0, 0, 0, 0.5);
429
+
430
+ /* ── Text ──────────────────────────────────────────────── */
431
+ --sp-color-text: #0D1017;
432
+ --sp-color-text-muted: #5C6370;
433
+ --sp-color-text-faint: #9AA0AD;
434
+ --sp-color-text-disabled: #C1C7CE;
435
+ --sp-color-text-on-brand: #FFFFFF;
436
+
437
+ /* ── Semantic ──────────────────────────────────────────── */
438
+ --sp-color-success: #1FAF64;
439
+ --sp-color-warning: #F59E0B;
440
+ --sp-color-error: #EF4444;
441
+ --sp-color-info: #3B82F6;
442
+
443
+ /* ── Border ────────────────────────────────────────────── */
444
+ --sp-color-border: #E5E7EB;
445
+ --sp-color-border-strong: #D1D5DB;
446
+
447
+ /* ── Typography ────────────────────────────────────────── */
448
+ --sp-font-body: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
449
+ --sp-font-display: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
450
+ --sp-fs-xs: 12px;
451
+ --sp-fs-sm: 14px;
452
+ --sp-fs-md: 16px;
453
+ --sp-fs-lg: 20px;
454
+ --sp-fs-xl: 24px;
455
+ --sp-fs-2xl: 32px;
456
+ --sp-fw-regular: 400;
457
+ --sp-fw-medium: 500;
458
+ --sp-fw-bold: 700;
459
+ --sp-lh-tight: 1.2;
460
+ --sp-lh-normal: 1.5;
461
+ --sp-lh-relaxed: 1.7;
462
+ --sp-ls-tight: -0.01em;
463
+ --sp-ls-wide: 0.04em;
464
+
465
+ /* ── Spacing ───────────────────────────────────────────── */
466
+ --sp-space-1: 4px;
467
+ --sp-space-2: 8px;
468
+ --sp-space-3: 12px;
469
+ --sp-space-4: 16px;
470
+ --sp-space-5: 24px;
471
+ --sp-space-6: 32px;
472
+ --sp-space-8: 48px;
473
+ --sp-space-10: 64px;
474
+
475
+ /* ── Radius ────────────────────────────────────────────── */
476
+ --sp-radius-sm: 6px;
477
+ --sp-radius-md: 10px;
478
+ --sp-radius-lg: 16px;
479
+ --sp-radius-xl: 24px;
480
+ --sp-radius-pill: 999px;
481
+
482
+ /* ── Border width ──────────────────────────────────────── */
483
+ --sp-border-width-thin: 1px;
484
+ --sp-border-width-base: 2px;
485
+ --sp-border-width-thick: 4px;
486
+
487
+ /* ── Elevation ─────────────────────────────────────────── */
488
+ --sp-shadow-1: 0 1px 3px rgba(0, 0, 0, 0.08);
489
+ --sp-shadow-2: 0 4px 16px rgba(0, 0, 0, 0.10);
490
+ --sp-shadow-3: 0 16px 40px rgba(0, 0, 0, 0.14);
491
+
492
+ /* ── Opacity ───────────────────────────────────────────── */
493
+ --sp-opacity-disabled: 0.4;
494
+ --sp-opacity-muted: 0.7;
495
+
496
+ /* ── Motion ────────────────────────────────────────────── */
497
+ --sp-duration-fast: 120ms;
498
+ --sp-duration-base: 200ms;
499
+ --sp-duration-slow: 400ms;
500
+ --sp-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
501
+ --sp-ease-emphasized:cubic-bezier(0.2, 0, 0, 1);
502
+
503
+ /* ── Sizing ────────────────────────────────────────────── */
504
+ --sp-size-icon-sm: 16px;
505
+ --sp-size-icon-md: 24px;
506
+ --sp-size-icon-lg: 32px;
507
+ --sp-control-height-sm: 32px;
508
+ --sp-control-height-md: 40px;
509
+ --sp-control-height-lg: 48px;
510
+
511
+ /* ── Z-index ───────────────────────────────────────────── */
512
+ --sp-z-nav: 10;
513
+ --sp-z-dropdown: 100;
514
+ --sp-z-modal: 1000;
515
+ --sp-z-toast: 2000;
516
+
517
+ /* ── Breakpoints ───────────────────────────────────────── */
518
+ --sp-bp-sm: 640px;
519
+ --sp-bp-md: 768px;
520
+ --sp-bp-lg: 1024px;
521
+ --sp-bp-xl: 1280px;
522
+ }
523
+ `;
524
+ export const designInitTokensTool = {
525
+ name: "design_init_tokens",
526
+ description: "Scaffold src/styles/social-plus-tokens.css in the customer's project — the dedicated, customer-editable token file for social.plus features. Greenfield: neutral defaults. Brownfield (existing tokens found): seed from their concrete values. Idempotent: never overwrites an existing file.",
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {
530
+ repoPath: { type: "string", description: "Project root. Defaults to the current directory." },
531
+ force: { type: "boolean", description: "Overwrite existing social-plus-tokens.css (default false — never clobbers)." },
532
+ },
533
+ additionalProperties: false,
534
+ },
535
+ async call(input) {
536
+ const args = objectInput(input);
537
+ const repoPath = optionalStringField(args, "repoPath") ?? ".";
538
+ const force = args.force === true;
539
+ return textResult(await initSpTokens(repoPath, force));
540
+ },
541
+ };
542
+ export async function initSpTokens(repoPath, force = false) {
543
+ const root = path.resolve(repoPath);
544
+ const target = path.join(root, SP_TOKENS_PATH);
545
+ // Idempotent: don't overwrite unless forced.
546
+ try {
547
+ await stat(target);
548
+ if (!force) {
549
+ return {
550
+ status: "exists",
551
+ file: target,
552
+ message: `${SP_TOKENS_PATH} already exists — skipping. Use --force to overwrite.`,
553
+ };
554
+ }
555
+ }
556
+ catch { /* file doesn't exist — proceed to scaffold */ }
557
+ // Try brownfield seeding: extract concrete token values from the existing project.
558
+ const existingContract = await extractDesignContractFromProject(root);
559
+ const hasBrownfieldTokens = existingContract.tokens.length > 0;
560
+ let css;
561
+ let seededFrom;
562
+ if (hasBrownfieldTokens) {
563
+ // Seed from their existing concrete values, namespaced as --sp-*.
564
+ const lines = [
565
+ `/* social-plus-tokens.css — social.plus feature design system.`,
566
+ ` * Seeded from your existing design tokens on ${new Date().toISOString().slice(0, 10)}.`,
567
+ ` * Edit freely to customize social.plus features independently from your main app.`,
568
+ ` * Run: vise design extract --from-project . to refresh the contract. */`,
569
+ `:root {`,
570
+ ];
571
+ const categories = [...new Set(existingContract.tokens.map((t) => t.category))];
572
+ for (const cat of categories) {
573
+ const tokensInCat = existingContract.tokens.filter((t) => t.category === cat && t.name);
574
+ if (tokensInCat.length === 0)
575
+ continue;
576
+ lines.push(``, ` /* ── ${cat} ──────────────────────────────────────────── */`);
577
+ for (const t of tokensInCat) {
578
+ const spName = `--sp-${t.name.replace(/^--/, "")}`;
579
+ lines.push(` ${spName}: ${t.value}; /* seeded from ${t.name} */`);
580
+ }
581
+ }
582
+ lines.push(`}`);
583
+ css = lines.join("\n") + "\n";
584
+ seededFrom = existingContract.source.inputs;
585
+ }
586
+ else {
587
+ // Greenfield: neutral Option-B defaults.
588
+ css = NEUTRAL_SP_TOKENS_DEFAULT;
589
+ }
590
+ await mkdir(path.dirname(target), { recursive: true });
591
+ await writeFile(target, css, "utf8");
592
+ return {
593
+ status: hasBrownfieldTokens ? "seeded" : "scaffolded",
594
+ file: target,
595
+ ...(seededFrom ? { seeded_from: seededFrom } : {}),
596
+ message: hasBrownfieldTokens
597
+ ? `Seeded ${SP_TOKENS_PATH} from your existing design tokens. Edit it to customize social.plus features, then run vise design extract --from-project .`
598
+ : `Scaffolded ${SP_TOKENS_PATH} with neutral defaults. Fill in your colors, then run vise design extract --from-project .`,
599
+ };
600
+ }
601
+ // ---------------------------------------------------------------------------
384
602
  // Design-system reference document (human/VLM-readable, advisory)
385
603
  // ---------------------------------------------------------------------------
386
604
  //
@@ -927,6 +1145,9 @@ export async function runDesignCheck(repoPath) {
927
1145
  note: ADVISORY_NOTE,
928
1146
  };
929
1147
  }
1148
+ // Freshness check: compare source file content hashes to those recorded at extract time.
1149
+ // Advisory only — never blocks, just surfaces a nudge to re-extract.
1150
+ const staleContract = await checkContractFreshness(repoRoot, contract);
930
1151
  const files = (await collectFiles(repoRoot, MAX_SCAN_FILES)).filter((file) => SCAN_EXTS.has(path.extname(file).toLowerCase()));
931
1152
  if (files.length === 0) {
932
1153
  return { status: "no-sources", message: "No UI source files found to check against the contract.", contract: contractSummary(contract), note: ADVISORY_NOTE };
@@ -1018,9 +1239,35 @@ export async function runDesignCheck(repoPath) {
1018
1239
  count: undefinedRefs.length,
1019
1240
  sample: undefinedRefs.slice(0, OFF_CONTRACT_SAMPLE),
1020
1241
  },
1242
+ ...(staleContract ? { staleContract } : {}),
1021
1243
  note: ADVISORY_NOTE,
1022
1244
  };
1023
1245
  }
1246
+ /** Compare source.inputs file content against hashes recorded at extract time.
1247
+ * Returns null if the contract is fresh or has no recorded digests. */
1248
+ async function checkContractFreshness(repoRoot, contract) {
1249
+ const recorded = contract.source?.input_digests;
1250
+ if (!recorded || Object.keys(recorded).length === 0)
1251
+ return undefined;
1252
+ const changed = [];
1253
+ for (const [rel, storedDigest] of Object.entries(recorded)) {
1254
+ try {
1255
+ const content = await readFile(path.join(repoRoot, rel), "utf8");
1256
+ const currentDigest = `sha256:${createHash("sha256").update(content).digest("hex")}`;
1257
+ if (currentDigest !== storedDigest)
1258
+ changed.push(rel);
1259
+ }
1260
+ catch {
1261
+ changed.push(rel); // file deleted or unreadable — also stale
1262
+ }
1263
+ }
1264
+ if (changed.length === 0)
1265
+ return undefined;
1266
+ return {
1267
+ changedFiles: changed,
1268
+ hint: `Run \`vise design extract --from-project\` to refresh the contract against the updated file(s).`,
1269
+ };
1270
+ }
1024
1271
  function dedupeByToken(refs) {
1025
1272
  const seen = new Set();
1026
1273
  const out = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "0.12.3",
3
+ "version": "0.12.5",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
package/rules/feed.yaml CHANGED
@@ -1469,10 +1469,11 @@
1469
1469
  },
1470
1470
  {
1471
1471
  "id": "typescript.reactions.configured-name-used",
1472
- "version": 1,
1472
+ "version": 2,
1473
1473
  "title": "TypeScript reaction name matches console config",
1474
1474
  "severity": "warning",
1475
- "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors.",
1475
+ "advisory": true,
1476
+ "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors. Advisory: Vise cannot verify tenant console config, so this surfaces as informational — it never blocks vise check.",
1476
1477
  "applies_when": {
1477
1478
  "platforms": [
1478
1479
  "typescript"
@@ -1506,7 +1507,8 @@
1506
1507
  },
1507
1508
  {
1508
1509
  "id": "react-native.reactions.configured-name-used",
1509
- "version": 1,
1510
+ "version": 2,
1511
+ "advisory": true,
1510
1512
  "title": "React Native reaction name matches console config",
1511
1513
  "severity": "warning",
1512
1514
  "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors.",
@@ -1543,7 +1545,8 @@
1543
1545
  },
1544
1546
  {
1545
1547
  "id": "android.reactions.configured-name-used",
1546
- "version": 1,
1548
+ "version": 2,
1549
+ "advisory": true,
1547
1550
  "title": "Android reaction name matches console config",
1548
1551
  "severity": "warning",
1549
1552
  "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors.",
@@ -1580,7 +1583,8 @@
1580
1583
  },
1581
1584
  {
1582
1585
  "id": "flutter.reactions.configured-name-used",
1583
- "version": 1,
1586
+ "version": 2,
1587
+ "advisory": true,
1584
1588
  "title": "Flutter reaction name matches console config",
1585
1589
  "severity": "warning",
1586
1590
  "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors.",
@@ -1617,7 +1621,8 @@
1617
1621
  },
1618
1622
  {
1619
1623
  "id": "ios.reactions.configured-name-used",
1620
- "version": 1,
1624
+ "version": 2,
1625
+ "advisory": true,
1621
1626
  "title": "iOS reaction name matches console config",
1622
1627
  "severity": "warning",
1623
1628
  "rationale": "Reaction names are configurable per-tenant. Hardcoding 'like' or another specific name prevents apps from dynamically matching the tenant's actual configuration, leading to silent failures or API errors.",