@amityco/social-plus-vise 1.0.0 → 1.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +71 -24
  2. package/LICENSE +8 -6
  3. package/README.md +168 -358
  4. package/dist/capabilities.js +19 -62
  5. package/dist/intelligence/grounding.js +0 -23
  6. package/dist/intelligence/placement.js +0 -9
  7. package/dist/outcomes.js +44 -22
  8. package/dist/server.js +75 -38
  9. package/dist/tools/ast.js +3 -209
  10. package/dist/tools/blocks.js +6 -20
  11. package/dist/tools/compliance.js +168 -43
  12. package/dist/tools/creative.js +15 -41
  13. package/dist/tools/debug.js +0 -16
  14. package/dist/tools/design.js +18 -364
  15. package/dist/tools/docs.js +53 -24
  16. package/dist/tools/experienceCompiler.js +7 -10
  17. package/dist/tools/experienceSensors.js +1 -1
  18. package/dist/tools/harness.js +2 -27
  19. package/dist/tools/integration.js +6 -38
  20. package/dist/tools/learning.js +1 -1
  21. package/dist/tools/project.js +763 -546
  22. package/dist/tools/sdkFacts.js +2 -15
  23. package/dist/tools/sdkVersion.js +3 -36
  24. package/dist/tools/sensors.js +0 -6
  25. package/dist/tools/uxHarness.js +12 -9
  26. package/package.json +8 -97
  27. package/rules/chat.yaml +225 -0
  28. package/rules/event.yaml +45 -0
  29. package/rules/feed.yaml +24 -24
  30. package/rules/invitation.yaml +58 -0
  31. package/rules/live-data.yaml +104 -2
  32. package/rules/notification-tray.yaml +106 -0
  33. package/rules/poll.yaml +71 -0
  34. package/rules/sdk-lifecycle.yaml +112 -6
  35. package/rules/search.yaml +131 -0
  36. package/rules/story.yaml +221 -0
  37. package/rules/user-blocking.yaml +71 -0
  38. package/sdk-surface/flutter.json +1 -1
  39. package/sdk-surface/ios.json +1 -1
  40. package/sdk-surface/manifest.json +12 -12
  41. package/sdk-surface/models.flutter.json +96 -96
  42. package/sdk-surface/models.ios.json +1 -1
  43. package/sdk-surface/typescript.json +4 -4
  44. package/skills/social-plus-vise/SKILL.md +25 -5
  45. package/scripts/catalog-coverage-html.mjs +0 -325
  46. package/scripts/catalog-relationships-html.mjs +0 -686
  47. package/scripts/catalog-sheets.mjs +0 -286
  48. package/scripts/dart-model-extractor/bin/extract_models.dart +0 -169
  49. package/scripts/dart-model-extractor/pubspec.lock +0 -149
  50. package/scripts/dart-model-extractor/pubspec.yaml +0 -16
  51. package/scripts/extract-sdk-models.mjs +0 -749
  52. package/scripts/import-sdk-surface.mjs +0 -161
  53. package/scripts/pilot-feedback.mjs +0 -107
  54. package/scripts/workshop-board-html.mjs +0 -1018
  55. package/scripts/workshop-kit.mjs +0 -252
  56. package/skills/vise-harness-engineer/SKILL.md +0 -35
package/dist/tools/ast.js CHANGED
@@ -1,26 +1,5 @@
1
- /**
2
- * AST-based deterministic analysis helpers using tree-sitter.
3
- *
4
- * This module provides syntactic (not type-resolving) analysis for source files.
5
- * It is an additive layer alongside the existing regex-based validators.
6
- *
7
- * Policy: When AST and regex disagree (e.g., regex matches a comment), the regex
8
- * result wins for now. AST cleanup of comment false-matches is Phase 4 work.
9
- *
10
- * Scope: Single-file, single-step identifier resolution only.
11
- * No cross-file imports, no type inference, no function boundary traversal.
12
- */
13
1
  import { createRequire } from "node:module";
14
2
  const nodeRequire = createRequire(import.meta.url);
15
- // Lazily and defensively load the tree-sitter native bindings. tree-sitter ships
16
- // prebuilt binaries for common platforms (darwin, linux-x64, win32-x64); on others
17
- // (linux-arm64, Alpine/musl, win32-arm64) the binding can fail to load when no C++
18
- // toolchain is present. AST is an ADDITIVE layer over the regex validators, so a
19
- // load failure must degrade to regex-only — NOT take down the entire CLI (including
20
- // doc-search/compliance commands that never touch a parser) at import time. Static
21
- // top-level imports would throw at module load and brick every command; this loader
22
- // confines the failure to the AST path. `undefined` = not yet attempted; `null` =
23
- // attempted and unavailable.
24
3
  let nativeBindings;
25
4
  function loadNativeBindings() {
26
5
  if (nativeBindings !== undefined)
@@ -29,8 +8,6 @@ function loadNativeBindings() {
29
8
  const ParserCtor = nodeRequire("tree-sitter");
30
9
  const tsGrammars = nodeRequire("tree-sitter-typescript");
31
10
  const kotlinGrammar = nodeRequire("tree-sitter-kotlin");
32
- // Swift loads in its own try/catch: a missing tree-sitter-swift prebuild on an
33
- // exotic platform must degrade ONLY the Swift helpers, not all AST analysis.
34
11
  let swiftGrammar = null;
35
12
  try {
36
13
  swiftGrammar = nodeRequire("tree-sitter-swift");
@@ -51,28 +28,12 @@ function loadNativeBindings() {
51
28
  }
52
29
  return nativeBindings;
53
30
  }
54
- /**
55
- * Whether tree-sitter native bindings are available in this environment. When
56
- * false, every AST helper degrades gracefully: parse() throws (so tryParse()
57
- * returns null and stripComments() returns the source unchanged), and validators
58
- * fall back to their regex paths.
59
- */
60
31
  export function astAvailable() {
61
32
  return loadNativeBindings() !== null;
62
33
  }
63
- /**
64
- * Whether the Swift grammar specifically is available. tree-sitter-swift is loaded
65
- * independently of the core bindings (see loadNativeBindings), so Swift-only
66
- * validators can check this and fall back to regex without disabling ts/tsx/kotlin.
67
- */
68
34
  export function swiftAstAvailable() {
69
35
  return loadNativeBindings()?.swiftGrammar != null;
70
36
  }
71
- /**
72
- * Strip comments from source code using tree-sitter AST.
73
- * Replaces comment spans with whitespace (preserving line structure).
74
- * Returns original source unchanged if parsing fails.
75
- */
76
37
  export function stripComments(language, source) {
77
38
  try {
78
39
  const tree = parse(language, source);
@@ -80,7 +41,6 @@ export function stripComments(language, source) {
80
41
  collectComments(tree.rootNode, commentRanges);
81
42
  if (commentRanges.length === 0)
82
43
  return source;
83
- // Replace comment ranges with spaces (preserve newlines for line numbers)
84
44
  const chars = source.split("");
85
45
  for (const { start, end } of commentRanges) {
86
46
  for (let i = start; i < end && i < chars.length; i++) {
@@ -105,7 +65,6 @@ function collectComments(node, out) {
105
65
  collectComments(child, out);
106
66
  }
107
67
  }
108
- // Parser instances are reusable — one per language.
109
68
  const parsers = new Map();
110
69
  function getParser(language) {
111
70
  let parser = parsers.get(language);
@@ -131,18 +90,7 @@ function getParser(language) {
131
90
  }
132
91
  return parser;
133
92
  }
134
- // node-tree-sitter's native parser rejects sufficiently large inputs with a
135
- // native "Invalid argument" error (~32KB string limit). It IS catchable, but an
136
- // unguarded caller would otherwise abort the whole validate run. We short-circuit
137
- // before the native call so callers get a clean, documented, catchable error and
138
- // can degrade to regex-only for that file. Real codebases routinely have files
139
- // past this size (1000+ line activities/view controllers).
140
93
  export const MAX_PARSE_BYTES = 30000;
141
- /**
142
- * Parse source content into a tree-sitter syntax tree.
143
- * Throws on oversized input (see MAX_PARSE_BYTES) — callers that want graceful
144
- * degradation should use tryParse or wrap this in try/catch.
145
- */
146
94
  export function parse(language, source) {
147
95
  if (Buffer.byteLength(source, "utf8") > MAX_PARSE_BYTES) {
148
96
  throw new Error(`source exceeds tree-sitter parse limit (${Buffer.byteLength(source, "utf8")} bytes > ${MAX_PARSE_BYTES})`);
@@ -150,11 +98,6 @@ export function parse(language, source) {
150
98
  const parser = getParser(language);
151
99
  return parser.parse(source);
152
100
  }
153
- /**
154
- * Parse, returning null instead of throwing when the source can't be parsed
155
- * (oversized input, native parser error). Lets validators skip AST analysis for
156
- * one file and fall back to regex without aborting the run.
157
- */
158
101
  export function tryParse(language, source) {
159
102
  try {
160
103
  return parse(language, source);
@@ -163,16 +106,11 @@ export function tryParse(language, source) {
163
106
  return null;
164
107
  }
165
108
  }
166
- /**
167
- * Find all call expressions in the tree whose callee matches a pattern.
168
- * Returns normalised callee strings and argument nodes.
169
- */
170
109
  export function findCallExpressions(tree, calleePattern) {
171
110
  const results = [];
172
111
  walkTree(tree.rootNode, (node) => {
173
112
  if (node.type !== "call_expression")
174
113
  return;
175
- // TypeScript: uses field names "function" and "arguments"
176
114
  const calleeNode = node.childForFieldName("function");
177
115
  if (calleeNode) {
178
116
  const callee = normaliseCallee(calleeNode);
@@ -190,10 +128,6 @@ export function findCallExpressions(tree, calleePattern) {
190
128
  results.push({ callee, node, args });
191
129
  return;
192
130
  }
193
- // Kotlin AND Swift: call_expression = navigation_expression + call_suffix.
194
- // (tree-sitter-swift descends from the Kotlin grammar, so the node names —
195
- // navigation_expression / navigation_suffix / call_suffix / value_arguments —
196
- // are identical; only Swift's labeled arguments add a value_argument_label.)
197
131
  const navNode = node.namedChild(0);
198
132
  const suffixNode = node.namedChild(1);
199
133
  if (!navNode || !suffixNode || suffixNode.type !== "call_suffix")
@@ -201,16 +135,12 @@ export function findCallExpressions(tree, calleePattern) {
201
135
  const callee = normaliseKotlinCallee(navNode);
202
136
  if (!callee || !calleePattern.test(callee))
203
137
  return;
204
- // Extract args from call_suffix → value_arguments → value_argument nodes
205
138
  const args = [];
206
139
  const valArgsNode = suffixNode.namedChild(0);
207
140
  if (valArgsNode && valArgsNode.type === "value_arguments") {
208
141
  for (let i = 0; i < valArgsNode.namedChildCount; i++) {
209
142
  const valArg = valArgsNode.namedChild(i);
210
143
  if (valArg && valArg.type === "value_argument") {
211
- // The actual expression is inside value_argument. Swift labeled arguments
212
- // put a value_argument_label first — the value is the last named child.
213
- // Kotlin behaviour (namedChild(0)) is intentionally unchanged.
214
144
  const first = valArg.namedChild(0);
215
145
  const expr = first?.type === "value_argument_label" ? valArg.namedChild(valArg.namedChildCount - 1) : first;
216
146
  if (expr)
@@ -222,56 +152,25 @@ export function findCallExpressions(tree, calleePattern) {
222
152
  });
223
153
  return results;
224
154
  }
225
- // Bound on identifier→identifier resolution chains. Five hops covers realistic
226
- // re-binding (`const RAW = "…"; const T = RAW; const X = T;`) while keeping the
227
- // walk cheap; a `visited` set guards against cyclic self-reference (which would
228
- // be a type error anyway, but the parser still produces a tree for it).
229
155
  const MAX_RESOLUTION_HOPS = 5;
230
- /**
231
- * Resolve an AST node to its literal string value within the same file.
232
- *
233
- * Handles:
234
- * - String literals directly → returns the string value
235
- * - A pure-passthrough template literal — `` `${X}` `` with exactly one
236
- * substitution and no cooked text — which is value-equivalent to `X`; the
237
- * substitution is resolved through this same resolver. Templates with any
238
- * literal text (`` `pre${X}` ``) or a dynamic substitution (`` `${a.b}` ``)
239
- * are NOT pure passthrough and stay unresolved. (Arbitrary string
240
- * concatenation is a separate, larger blind spot — intentionally not handled.)
241
- * - Identifiers that reference a const/let/var whose initializer resolves to a
242
- * literal, following identifier→identifier re-bind chains up to
243
- * MAX_RESOLUTION_HOPS deep (multi-hop), with a cycle guard. A binding to any
244
- * dynamic expression (member access, call, parameter, env fallback, …)
245
- * terminates the chain and stays unresolved — multi-hop never makes a runtime
246
- * value look literal.
247
- *
248
- * Returns undefined if the value cannot be statically resolved.
249
- */
250
156
  export function resolveLiteralValue(node, tree) {
251
157
  return resolveLiteralValueBounded(node, tree, MAX_RESOLUTION_HOPS, new Set());
252
158
  }
253
159
  function resolveLiteralValueBounded(node, tree, hopsLeft, visited) {
254
- // Direct string literal
255
160
  const directValue = extractStringLiteral(node);
256
161
  if (directValue !== undefined)
257
162
  return directValue;
258
- // Pure-passthrough template literal: `${X}` (exactly one substitution, no text).
259
- // Equivalent to resolving X itself. Bounded to this one shape — not concat.
260
163
  if (node.type === "template_string") {
261
164
  const passthrough = templatePassthroughSubstitution(node);
262
165
  if (passthrough)
263
166
  return resolveLiteralValueBounded(passthrough, tree, hopsLeft, visited);
264
167
  }
265
- // Identifier — resolve to its declaration in the same file, then recurse into
266
- // the initializer so identifier→identifier re-binds and template re-wraps
267
- // chain through to the underlying literal.
268
- // TypeScript uses "identifier", Kotlin uses "simple_identifier".
269
168
  if (node.type === "identifier" || node.type === "simple_identifier") {
270
169
  if (hopsLeft <= 0)
271
170
  return undefined;
272
171
  const name = node.text;
273
172
  if (visited.has(name))
274
- return undefined; // cycle guard
173
+ return undefined;
275
174
  visited.add(name);
276
175
  const valueNode = resolveIdentifierToValueNode(name, tree.rootNode);
277
176
  if (!valueNode)
@@ -280,12 +179,6 @@ function resolveLiteralValueBounded(node, tree, hopsLeft, visited) {
280
179
  }
281
180
  return undefined;
282
181
  }
283
- /**
284
- * If `node` is a template literal that is a pure passthrough of a single
285
- * substitution — `` `${X}` `` with exactly one `template_substitution` child,
286
- * no `string_fragment` (cooked text) — return that substitution's inner
287
- * expression node. Otherwise undefined.
288
- */
289
182
  function templatePassthroughSubstitution(node) {
290
183
  if (node.type !== "template_string")
291
184
  return undefined;
@@ -296,35 +189,17 @@ function templatePassthroughSubstitution(node) {
296
189
  continue;
297
190
  if (child.type === "template_substitution") {
298
191
  if (substitution)
299
- return undefined; // more than one substitution → not passthrough
192
+ return undefined;
300
193
  substitution = child;
301
194
  }
302
195
  else {
303
- // Any other named child (e.g. string_fragment cooked text) → has literal
304
- // content, so not a pure passthrough.
305
196
  return undefined;
306
197
  }
307
198
  }
308
199
  if (!substitution)
309
200
  return undefined;
310
- // The substitution wraps a single expression: `${ expr }`.
311
201
  return substitution.namedChild(0) ?? undefined;
312
202
  }
313
- /**
314
- * Unwrap TypeScript expression wrappers that are transparent for static value
315
- * extraction — type casts and grouping that don't change the runtime value:
316
- * - `expr as T` (as_expression)
317
- * - `expr satisfies T` (satisfies_expression)
318
- * - `(expr)` (parenthesized_expression)
319
- * - `expr!` (non_null_expression)
320
- * - `<T>expr` (type_assertion, angle-bracket cast)
321
- * Nested wrappers are peeled fully (`({ … } as any)!`). The inner expression is
322
- * always the wrapper's FIRST named child across these node types. Non-wrapper
323
- * nodes are returned unchanged, so callers can apply this unconditionally.
324
- *
325
- * High-frequency motivation: agents emit `as any` constantly to silence TS
326
- * errors, which otherwise hides the payload object from property extraction.
327
- */
328
203
  export function unwrapExpression(node) {
329
204
  let current = node;
330
205
  const wrappers = new Set([
@@ -334,8 +209,6 @@ export function unwrapExpression(node) {
334
209
  "non_null_expression",
335
210
  "type_assertion",
336
211
  ]);
337
- // Bounded peel: a handful of stacked casts at most; the guard prevents any
338
- // pathological loop if a grammar ever yields a self-referential first child.
339
212
  for (let i = 0; i < 16 && wrappers.has(current.type); i++) {
340
213
  const inner = current.namedChild(0);
341
214
  if (!inner)
@@ -344,14 +217,6 @@ export function unwrapExpression(node) {
344
217
  }
345
218
  return current;
346
219
  }
347
- /**
348
- * Pick a specific named property from an object argument node.
349
- * E.g., from `{ userId: HARDCODED }`, pick the value node for "userId".
350
- *
351
- * The argument is unwrapped first, so a cast/parenthesized/non-null-wrapped
352
- * payload (`{ … } as any`, `({ … })`, `{ … }!`) is still treated as the inner
353
- * object — otherwise the wrapper hides every property from extraction.
354
- */
355
220
  export function pickObjectProperty(objectNode, propertyName) {
356
221
  const unwrapped = unwrapExpression(objectNode);
357
222
  if (unwrapped.type !== "object")
@@ -368,14 +233,6 @@ export function pickObjectProperty(objectNode, propertyName) {
368
233
  }
369
234
  return undefined;
370
235
  }
371
- /**
372
- * Pick the value of a labeled argument from a Swift call expression node.
373
- * E.g., from `client.login(userId: HARDCODED, sessionHandler: handler)`,
374
- * pick the value node for label "userId".
375
- *
376
- * Swift-shaped trees only (call_suffix → value_arguments → value_argument with a
377
- * value_argument_label). Returns undefined when the label is absent.
378
- */
379
236
  export function pickLabeledArgument(callNode, label) {
380
237
  for (let i = 0; i < callNode.namedChildCount; i++) {
381
238
  const suffix = callNode.namedChild(i);
@@ -392,24 +249,11 @@ export function pickLabeledArgument(callNode, label) {
392
249
  if (first?.type !== "value_argument_label" || first.text !== label)
393
250
  continue;
394
251
  const value = valArg.namedChild(valArg.namedChildCount - 1);
395
- // namedChild(last) === the label itself when the argument has no value
396
- // (selector-reference form) — treat as absent.
397
252
  return value && value !== first ? value : undefined;
398
253
  }
399
254
  }
400
255
  return undefined;
401
256
  }
402
- /**
403
- * Find Swift `let`/`var` declarations whose initializer text matches a pattern AND
404
- * which are function-local (declared inside a function/initializer/closure/computed-
405
- * property body rather than at type or file scope). Used for retention rules: a
406
- * function-local binding dies with the stack frame, while a type-scope property
407
- * lives with the object — a distinction regex bridges cannot make reliably.
408
- *
409
- * Conservative direction: a declaration only counts as local when a KNOWN
410
- * function-ish ancestor encloses it, so unknown containers degrade toward
411
- * "retained" (quiet), never toward a false positive.
412
- */
413
257
  export function findSwiftFunctionLocalDeclarations(tree, initializerPattern) {
414
258
  const results = [];
415
259
  walkTree(tree.rootNode, (node) => {
@@ -441,21 +285,16 @@ function hasFunctionAncestor(node) {
441
285
  }
442
286
  return false;
443
287
  }
444
- /** The initializer expression of a Swift property_declaration (the value after `=`), if any. */
445
288
  function swiftPropertyInitializer(node) {
446
- // Shape: property_declaration = value_binding_pattern, pattern, [type_annotation], [value expr]
447
- // The initializer (when present) is the last named child and is none of the structural parts.
448
289
  const last = node.namedChild(node.namedChildCount - 1);
449
290
  if (!last)
450
291
  return undefined;
451
292
  if (["value_binding_pattern", "pattern", "type_annotation", "modifiers", "attribute"].includes(last.type))
452
293
  return undefined;
453
- // computed_property is a body, not an initializer.
454
294
  if (last.type === "computed_property")
455
295
  return undefined;
456
296
  return last;
457
297
  }
458
- // ── Internal helpers ──────────────────────────────────────────────────────────
459
298
  function walkTree(node, visit) {
460
299
  visit(node);
461
300
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -481,8 +320,6 @@ function normaliseKotlinCallee(node) {
481
320
  if (node.type === "simple_identifier")
482
321
  return node.text;
483
322
  if (node.type === "navigation_expression") {
484
- // navigation_expression has children: expression + navigation_suffix
485
- // e.g., AmityCoreClient.login → nav_expr(simple_id("AmityCoreClient"), nav_suffix(".login"))
486
323
  const parts = [];
487
324
  for (let i = 0; i < node.namedChildCount; i++) {
488
325
  const child = node.namedChild(i);
@@ -492,13 +329,11 @@ function normaliseKotlinCallee(node) {
492
329
  parts.push(child.text);
493
330
  }
494
331
  else if (child.type === "navigation_suffix") {
495
- // navigation_suffix contains a simple_identifier after the dot
496
332
  const id = child.namedChild(0);
497
333
  if (id)
498
334
  parts.push(id.text);
499
335
  }
500
336
  else if (child.type === "call_expression") {
501
- // Chained: obj.method1().method2 — extract the last part from the chain
502
337
  const innerCallee = normaliseKotlinCallee(child.namedChild(0));
503
338
  if (innerCallee)
504
339
  parts.push(innerCallee);
@@ -515,48 +350,38 @@ function normaliseKotlinCallee(node) {
515
350
  }
516
351
  function extractStringLiteral(node) {
517
352
  if (node.type === "string") {
518
- // tree-sitter-typescript: string nodes include the quotes — strip them
519
353
  const text = node.text;
520
354
  if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
521
355
  return text.slice(1, -1);
522
356
  }
523
- // Template literal with no interpolations
524
357
  if (text.startsWith("`") && text.endsWith("`") && !text.includes("${")) {
525
358
  return text.slice(1, -1);
526
359
  }
527
360
  }
528
- // template_string without substitutions
529
361
  if (node.type === "template_string" && node.namedChildCount === 0) {
530
362
  const text = node.text;
531
363
  if (text.startsWith("`") && text.endsWith("`")) {
532
364
  return text.slice(1, -1);
533
365
  }
534
366
  }
535
- // Kotlin: string_literal contains string_content child
536
367
  if (node.type === "string_literal") {
537
368
  const contentNode = node.namedChild(0);
538
369
  if (contentNode && contentNode.type === "string_content") {
539
370
  return contentNode.text;
540
371
  }
541
- // Simple case — strip quotes from text
542
372
  const text = node.text;
543
373
  if (text.startsWith('"') && text.endsWith('"')) {
544
374
  return text.slice(1, -1);
545
375
  }
546
376
  }
547
- // Swift: line_string_literal contains line_str_text chunks; an interpolated
548
- // string has >1 named child (line_str_text + interpolated_expression) and is
549
- // NOT statically resolvable — mirror the TS template-literal treatment.
550
377
  if (node.type === "line_string_literal") {
551
378
  if (node.namedChildCount === 0)
552
- return ""; // empty string ""
379
+ return "";
553
380
  if (node.namedChildCount === 1 && node.namedChild(0)?.type === "line_str_text") {
554
381
  return node.namedChild(0).text;
555
382
  }
556
383
  return undefined;
557
384
  }
558
- // Swift: multi-line """…""" literal. Swift semantics strip the newline after the
559
- // opening and before the closing delimiter.
560
385
  if (node.type === "multi_line_string_literal") {
561
386
  if (node.namedChildCount === 1 && node.namedChild(0)?.type === "multi_line_str_text") {
562
387
  return node.namedChild(0).text.replace(/^\n/, "").replace(/\n[ \t]*$/, "");
@@ -565,31 +390,11 @@ function extractStringLiteral(node) {
565
390
  }
566
391
  return undefined;
567
392
  }
568
- /**
569
- * Find the binding for `name` in the same file and return the AST node that is
570
- * its initializer VALUE — not the resolved string. The caller
571
- * (resolveLiteralValueBounded) then recurses into that node, so an initializer
572
- * that is itself an identifier (re-bind) or a pure-passthrough template literal
573
- * chains through to the underlying literal. Returns undefined when the binding
574
- * is absent or is a parameter / dynamic expression rather than a value
575
- * initializer.
576
- *
577
- * Per-platform value node:
578
- * - TypeScript/JS: `variable_declarator` → its `value` field.
579
- * - Kotlin: `property_declaration` with a `variable_declaration` → the initializer
580
- * sibling (the node after `=`).
581
- * - Swift: `property_declaration` with `pattern` → the DIRECT string-literal
582
- * initializer only. A literal nested inside another expression
583
- * (`env["KEY"] ?? ""`) is not a static binding and stays unresolved — so for
584
- * Swift we deliberately surface only a line/multi-line string literal node,
585
- * preserving the prior conservative behavior.
586
- */
587
393
  function resolveIdentifierToValueNode(name, root) {
588
394
  let result;
589
395
  walkTree(root, (node) => {
590
396
  if (result !== undefined)
591
397
  return;
592
- // TypeScript/JS: variable_declarator with name + value fields
593
398
  if (node.type === "variable_declarator") {
594
399
  const nameNode = node.childForFieldName("name");
595
400
  if (!nameNode || nameNode.text !== name)
@@ -597,33 +402,22 @@ function resolveIdentifierToValueNode(name, root) {
597
402
  const valueNode = node.childForFieldName("value");
598
403
  if (!valueNode)
599
404
  return;
600
- // Casts/grouping around the initializer are transparent (e.g.
601
- // `const T = RAW as string;`).
602
405
  result = unwrapExpression(valueNode);
603
406
  return;
604
407
  }
605
- // Kotlin: property_declaration with variable_declaration + initializer
606
408
  if (node.type === "property_declaration") {
607
409
  const varDecl = node.namedChildren.find((c) => c.type === "variable_declaration");
608
410
  if (varDecl) {
609
411
  const idNode = varDecl.namedChildren.find((c) => c.type === "simple_identifier");
610
412
  if (!idNode || idNode.text !== name)
611
413
  return;
612
- // The initializer is the named child after the variable_declaration.
613
414
  const declIdx = node.namedChildren.indexOf(varDecl);
614
415
  const initializer = node.namedChildren[declIdx + 1];
615
- // Only surface a string literal or a re-bind identifier as the value node;
616
- // anything else (call, when-expression, etc.) is dynamic and stays
617
- // unresolved, matching the prior literal-only behavior.
618
416
  if (initializer && (initializer.type === "string_literal" || initializer.type === "simple_identifier")) {
619
417
  result = initializer;
620
418
  }
621
419
  return;
622
420
  }
623
- // Swift: property_declaration with pattern → simple_identifier; the string
624
- // literal (or a re-bind identifier) must be a DIRECT child (the
625
- // initializer) — a literal nested inside e.g. `env["KEY"] ?? ""` is not a
626
- // static binding and must not resolve.
627
421
  const pattern = node.namedChildren.find((c) => c.type === "pattern");
628
422
  if (!pattern)
629
423
  return;
@@ -14,7 +14,7 @@ const registryPlatformByVisePlatform = {
14
14
  export async function listRegistryBlocks(registryPath) {
15
15
  const registry = await loadRegistry(registryPath);
16
16
  return {
17
- source: "social-plus-block-factory",
17
+ source: "social-plus-blocks",
18
18
  mode: "block-registry",
19
19
  schemaVersion: registry.schemaVersion,
20
20
  blocks: registry.blocks.map((block) => ({
@@ -53,6 +53,7 @@ export async function addBlockInstall(options) {
53
53
  ...plan,
54
54
  applied: true,
55
55
  filesTouched,
56
+ nextStep: "Run `vise blocks validate <repoPath> --block <id> --registry <path>` to re-check for install drift after further edits.",
56
57
  };
57
58
  }
58
59
  export async function validateBlockInstall(options) {
@@ -68,14 +69,14 @@ export async function validateBlockInstall(options) {
68
69
  ruleId: "blocks.registry.known",
69
70
  severity: "warning",
70
71
  message: `Installed sidecar entry references unknown registry block ${entry.blockId}.`,
71
- recommendation: "Update the Block Factory registry or remove stale block sidecar state.",
72
+ recommendation: "Update the block registry or remove stale block sidecar state.",
72
73
  }));
73
74
  if (installed.length === 0) {
74
75
  findings.push({
75
76
  ruleId: "blocks.sidecar.installed",
76
77
  severity: "warning",
77
78
  message: "No installed block sidecar entries exist.",
78
- recommendation: "Run `vise blocks add <repoPath> --block <id> --apply` after reviewing the dry-run plan.",
79
+ recommendation: "Run `vise blocks add <repoPath> --block <id> --registry <path> --apply` after reviewing the dry-run plan.",
79
80
  });
80
81
  }
81
82
  return {
@@ -91,14 +92,13 @@ export async function validateBlockInstall(options) {
91
92
  const repoPath = requiredRepoPath(options);
92
93
  const sidecar = await readSidecar(repoPath);
93
94
  const installed = (sidecar?.installed ?? []).filter((entry) => !options.blockId || entry.blockId === options.blockId);
94
- // Seed with plan findings so the providesCapabilities seam guard also surfaces in validate mode.
95
95
  const findings = [...plan.findings];
96
96
  if (installed.length === 0) {
97
97
  findings.push({
98
98
  ruleId: "blocks.sidecar.installed",
99
99
  severity: "warning",
100
100
  message: options.blockId ? `No sidecar entry exists for block ${options.blockId}.` : "No installed block sidecar entries exist.",
101
- recommendation: "Run `vise blocks add <repoPath> --block <id> --apply` after reviewing the dry-run plan.",
101
+ recommendation: "Run `vise blocks add <repoPath> --block <id> --registry <path> --apply` after reviewing the dry-run plan.",
102
102
  });
103
103
  }
104
104
  if (plan.packageChange.alreadyPresent === false) {
@@ -158,9 +158,6 @@ async function buildInstallPlan(options, mode) {
158
158
  stopConditions.push(`Missing safe install anchor ${targetFile.anchor} in ${targetFile.path}.`);
159
159
  }
160
160
  }
161
- // Seam guard: the factory declares providesCapabilities, but Vise owns the
162
- // completeness id vocabulary. Unknown ids warn (never block) — they simply
163
- // can't satisfy a completeness gap in `vise check`.
164
161
  const providesCapabilities = providesCapabilitiesFor(block);
165
162
  const knownCapabilityIds = completenessCapabilityIds();
166
163
  const unknownCapabilityIds = providesCapabilities.filter((id) => !knownCapabilityIds.has(id));
@@ -168,7 +165,7 @@ async function buildInstallPlan(options, mode) {
168
165
  ruleId: "blocks.providesCapabilities.known",
169
166
  severity: "warning",
170
167
  message: `Block ${block.blockId} declares providesCapabilities id "${id}", which is not in this Vise's completeness checklist vocabulary.`,
171
- recommendation: "Unknown ids never satisfy completeness gaps. Align the Block Factory contract with Vise's capability catalog (vise owns the id space) or upgrade Vise.",
168
+ recommendation: "Unknown ids never satisfy completeness gaps. Align the block contract with Vise's capability catalog (vise owns the id space) or upgrade Vise.",
172
169
  }));
173
170
  return {
174
171
  status: stopConditions.length > 0 ? "needs-review" : "ready",
@@ -344,16 +341,6 @@ async function readSidecar(repoPath) {
344
341
  return null;
345
342
  }
346
343
  }
347
- /**
348
- * Registry-free evidence that installed blocks deliver completeness capabilities.
349
- *
350
- * `vise check` cannot assume registry access, so a sidecar entry's
351
- * `providesCapabilities` only counts when the install is still locally valid:
352
- * the block's manifest dependency is declared in the project's package manifest
353
- * AND every recorded `filesTouched` path still exists. On drift (file removed,
354
- * package gone, pre-providesCapabilities sidecar) the entry contributes nothing
355
- * and the capability reverts to the normal completeness gap.
356
- */
357
344
  export async function installedBlockProvidedCapabilities(repoPath) {
358
345
  const repoRoot = path.resolve(repoPath);
359
346
  const sidecar = await readSidecar(repoRoot);
@@ -394,7 +381,6 @@ async function installedEntryLocallyValid(repoRoot, entry) {
394
381
  async function blockDependencyDeclared(repoRoot, entry) {
395
382
  const dependencyName = entry.dependencyName;
396
383
  if (!dependencyName) {
397
- // Pre-2026-06-10 sidecars carry no package evidence; fail closed so the gap stays.
398
384
  return false;
399
385
  }
400
386
  if (entry.platform === "flutter") {