@amityco/social-plus-vise 1.6.0 → 1.8.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.
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { objectInput, optionalNumberField, optionalStringField, stringField, textResult } from "../types.js";
4
4
  import { detectCommandSensors } from "./harness.js";
5
5
  import { inspectProject } from "./project.js";
6
+ const maxCapturedOutputChars = 8000;
6
7
  export const runSensorsTool = {
7
8
  name: "run_sensors",
8
9
  description: "Run detected project command sensors, such as build/typecheck/test, with a timeout and structured results.",
@@ -48,8 +49,29 @@ export const runSensorsTool = {
48
49
  const include = stringArrayField(args, "include");
49
50
  const root = path.resolve(repoPath);
50
51
  const inspection = await inspectProject(root, optionalStringField(args, "surfacePath"));
52
+ if (!inspection.scanCoverage.complete) {
53
+ return textResult({
54
+ status: "incomplete-analysis",
55
+ surfacePath: inspection.selectedSurface?.path,
56
+ targetPlatforms: inspection.platforms,
57
+ scanCoverage: inspection.scanCoverage,
58
+ reason: "Vise could not inspect the complete project inventory, so detected sensors may be incomplete. Narrow the run with --surface/--surface-path.",
59
+ });
60
+ }
51
61
  const detectedSensors = await detectCommandSensors(inspection.effectiveRoot, inspection.platforms);
52
62
  const selectedSensors = include.length > 0 ? detectedSensors.filter((sensor) => include.includes(sensor.name)) : detectedSensors;
63
+ const unmatched = include.filter((requestedName) => !detectedSensors.some((sensor) => sensor.name === requestedName));
64
+ if (unmatched.length > 0) {
65
+ return textResult({
66
+ status: "invalid-selection",
67
+ surfacePath: inspection.selectedSurface?.path,
68
+ targetPlatforms: inspection.platforms,
69
+ requested: include,
70
+ unmatched,
71
+ available: detectedSensors.map((sensor) => sensor.name),
72
+ reason: `No detected sensor matched: ${unmatched.join(", ")}. Explicit --include selections are strict.`,
73
+ });
74
+ }
53
75
  if (dryRun) {
54
76
  return textResult({
55
77
  status: "dry-run",
@@ -91,35 +113,56 @@ async function runSensor(cwd, sensor, timeoutMs) {
91
113
  };
92
114
  }
93
115
  return new Promise((resolve) => {
94
- let stdout = "";
95
- let stderr = "";
116
+ const stdout = { value: "", omitted: 0 };
117
+ const stderr = { value: "", omitted: 0 };
118
+ let environmentProbeTail = "";
119
+ let matchedEnvironmentSkip;
96
120
  let settled = false;
97
121
  const child = spawn(command, args, {
98
122
  cwd,
99
123
  shell: false,
100
124
  env: process.env,
125
+ detached: process.platform !== "win32",
101
126
  });
127
+ const observeEnvironmentOutput = (chunk) => {
128
+ if (!matchedEnvironmentSkip && sensor.environmentSkips?.length) {
129
+ const probe = `${environmentProbeTail}${chunk}`;
130
+ matchedEnvironmentSkip = sensor.environmentSkips.find(({ pattern }) => {
131
+ try {
132
+ return new RegExp(pattern, "i").test(probe);
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ });
138
+ environmentProbeTail = probe.slice(-4096);
139
+ }
140
+ };
102
141
  const timeout = setTimeout(() => {
103
142
  if (settled) {
104
143
  return;
105
144
  }
106
145
  settled = true;
107
- child.kill("SIGTERM");
146
+ terminateProcessTree(child);
108
147
  resolve({
109
148
  name: sensor.name,
110
149
  command: sensor.command,
111
150
  status: "timed-out",
112
151
  durationMs: Date.now() - startedAt,
113
- stdout: truncate(stdout),
114
- stderr: truncate(stderr),
152
+ stdout: capturedOutput(stdout),
153
+ stderr: capturedOutput(stderr),
115
154
  reason: sensor.timeoutReason ?? `Timed out after ${timeoutMs}ms.`,
116
155
  });
117
156
  }, timeoutMs);
118
157
  child.stdout?.on("data", (chunk) => {
119
- stdout += chunk.toString("utf8");
158
+ const text = chunk.toString("utf8");
159
+ captureOutput(stdout, text);
160
+ observeEnvironmentOutput(text);
120
161
  });
121
162
  child.stderr?.on("data", (chunk) => {
122
- stderr += chunk.toString("utf8");
163
+ const text = chunk.toString("utf8");
164
+ captureOutput(stderr, text);
165
+ observeEnvironmentOutput(text);
123
166
  });
124
167
  child.on("error", (error) => {
125
168
  if (settled) {
@@ -127,12 +170,13 @@ async function runSensor(cwd, sensor, timeoutMs) {
127
170
  }
128
171
  settled = true;
129
172
  clearTimeout(timeout);
173
+ captureOutput(stderr, error.message);
130
174
  resolve({
131
175
  name: sensor.name,
132
176
  command: sensor.command,
133
177
  status: "failed",
134
178
  durationMs: Date.now() - startedAt,
135
- stderr: truncate(error.message),
179
+ stderr: capturedOutput(stderr),
136
180
  reason: "Failed to start sensor command.",
137
181
  });
138
182
  });
@@ -142,29 +186,18 @@ async function runSensor(cwd, sensor, timeoutMs) {
142
186
  }
143
187
  settled = true;
144
188
  clearTimeout(timeout);
145
- if (exitCode !== 0 && sensor.environmentSkips?.length) {
146
- const combinedOutput = `${stdout}\n${stderr}`;
147
- const environmentSkip = sensor.environmentSkips.find(({ pattern }) => {
148
- try {
149
- return new RegExp(pattern, "i").test(combinedOutput);
150
- }
151
- catch {
152
- return false;
153
- }
189
+ if (exitCode !== 0 && matchedEnvironmentSkip) {
190
+ resolve({
191
+ name: sensor.name,
192
+ command: sensor.command,
193
+ status: "skipped",
194
+ exitCode,
195
+ durationMs: Date.now() - startedAt,
196
+ stdout: capturedOutput(stdout),
197
+ stderr: capturedOutput(stderr),
198
+ reason: matchedEnvironmentSkip.reason,
154
199
  });
155
- if (environmentSkip) {
156
- resolve({
157
- name: sensor.name,
158
- command: sensor.command,
159
- status: "skipped",
160
- exitCode,
161
- durationMs: Date.now() - startedAt,
162
- stdout: truncate(stdout),
163
- stderr: truncate(stderr),
164
- reason: environmentSkip.reason,
165
- });
166
- return;
167
- }
200
+ return;
168
201
  }
169
202
  resolve({
170
203
  name: sensor.name,
@@ -172,8 +205,8 @@ async function runSensor(cwd, sensor, timeoutMs) {
172
205
  status: exitCode === 0 ? "passed" : "failed",
173
206
  exitCode,
174
207
  durationMs: Date.now() - startedAt,
175
- stdout: truncate(stdout),
176
- stderr: truncate(stderr),
208
+ stdout: capturedOutput(stdout),
209
+ stderr: capturedOutput(stderr),
177
210
  });
178
211
  });
179
212
  });
@@ -209,9 +242,42 @@ function stringArrayField(input, field) {
209
242
  }
210
243
  return value.filter((item) => typeof item === "string" && item.trim() !== "");
211
244
  }
212
- function truncate(value, maxLength = 8000) {
213
- if (value.length <= maxLength) {
214
- return value;
245
+ function captureOutput(output, chunk) {
246
+ const remaining = Math.max(0, maxCapturedOutputChars - output.value.length);
247
+ output.value += chunk.slice(0, remaining);
248
+ output.omitted += Math.max(0, chunk.length - remaining);
249
+ }
250
+ function capturedOutput(output) {
251
+ if (output.omitted === 0) {
252
+ return output.value;
215
253
  }
216
- return `${value.slice(0, maxLength)}\n[truncated ${value.length - maxLength} chars]`;
254
+ return `${output.value}\n[truncated ${output.omitted} chars]`;
255
+ }
256
+ function terminateProcessTree(child) {
257
+ if (!child.pid) {
258
+ child.kill("SIGTERM");
259
+ return;
260
+ }
261
+ if (process.platform === "win32") {
262
+ const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
263
+ stdio: "ignore",
264
+ windowsHide: true,
265
+ });
266
+ killer.unref();
267
+ return;
268
+ }
269
+ try {
270
+ process.kill(-child.pid, "SIGTERM");
271
+ }
272
+ catch {
273
+ child.kill("SIGTERM");
274
+ }
275
+ const forceKill = setTimeout(() => {
276
+ try {
277
+ process.kill(-child.pid, "SIGKILL");
278
+ }
279
+ catch {
280
+ }
281
+ }, 1000);
282
+ forceKill.unref();
217
283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "ai-coding"
23
23
  ],
24
24
  "engines": {
25
- "node": ">=18"
25
+ "node": ">=20"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -103,6 +103,12 @@
103
103
  "minLength": 1,
104
104
  "description": "Installable social.plus block id for this object. Source of truth for blockIdForObject. Omitted when the object has a surface but no dedicated block (e.g. forum, story). Absent on app-layer/in-development objects."
105
105
  },
106
+ "sdkCapabilities": {
107
+ "type": "array",
108
+ "minItems": 1,
109
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
110
+ "description": "SDK capability ids from Vise's sdk-facts table (vise/src/tools/sdkFacts.ts CAPABILITIES) that ground this object. Declared only when the id exists and `vise sdk-facts` proves it on the TypeScript surface — never guessed; omit when no proven mapping exists (e.g. story, app-layer objects). Consumed by the social.plus blocks catalog gap-report so draft block requests carry proven capability ids instead of a TODO(human) marker."
111
+ },
106
112
  "availability": { "$ref": "#/$defs/availability" }
107
113
  }
108
114
  },
@@ -2,17 +2,17 @@
2
2
  "schemaVersion": "0.1.0",
3
3
  "status": "seeded",
4
4
  "items": [
5
- { "id": "feed", "name": "Feed", "description": "A stream of social posts or updates.", "category": "content", "surface": "feed", "block": "feed" },
6
- { "id": "comment", "name": "Comment", "description": "A response thread attached to content or an object.", "category": "participation", "surface": "comments", "block": "comments" },
7
- { "id": "forum", "name": "Forum", "description": "A structured discussion space optimized for topics and knowledge.", "category": "knowledge", "surface": "feed" },
8
- { "id": "community", "name": "Community", "description": "A persistent group identity around people, content, or purpose.", "category": "identity", "surface": "community", "block": "community" },
9
- { "id": "creator", "name": "Creator", "description": "A person or entity that owns an audience relationship.", "category": "identity", "surface": "profile", "block": "profile" },
10
- { "id": "profile", "name": "Profile", "description": "A user or creator identity surface with social relationship signals.", "category": "identity", "surface": "profile", "block": "profile" },
11
- { "id": "chat", "name": "Chat", "description": "Direct or group conversation.", "category": "conversation", "surface": "chat", "block": "chat" },
5
+ { "id": "feed", "name": "Feed", "description": "A stream of social posts or updates.", "category": "content", "surface": "feed", "block": "feed", "sdkCapabilities": ["posts"] },
6
+ { "id": "comment", "name": "Comment", "description": "A response thread attached to content or an object.", "category": "participation", "surface": "comments", "block": "comments", "sdkCapabilities": ["comments"] },
7
+ { "id": "forum", "name": "Forum", "description": "A structured discussion space optimized for topics and knowledge.", "category": "knowledge", "surface": "feed", "sdkCapabilities": ["posts"] },
8
+ { "id": "community", "name": "Community", "description": "A persistent group identity around people, content, or purpose.", "category": "identity", "surface": "community", "block": "community", "sdkCapabilities": ["community"] },
9
+ { "id": "creator", "name": "Creator", "description": "A person or entity that owns an audience relationship.", "category": "identity", "surface": "profile", "block": "profile", "sdkCapabilities": ["users"] },
10
+ { "id": "profile", "name": "Profile", "description": "A user or creator identity surface with social relationship signals.", "category": "identity", "surface": "profile", "block": "profile", "sdkCapabilities": ["users"] },
11
+ { "id": "chat", "name": "Chat", "description": "Direct or group conversation.", "category": "conversation", "surface": "chat", "block": "chat", "sdkCapabilities": ["chat"] },
12
12
  { "id": "story", "name": "Story", "description": "Ephemeral or sequenced media updates.", "category": "content", "surface": "feed" },
13
- { "id": "livestream", "name": "Livestream", "description": "Real-time video or audio participation.", "category": "live", "surface": "livestream", "block": "livestream" },
14
- { "id": "event", "name": "Event", "description": "A scheduled social moment.", "category": "live", "surface": "notifications", "block": "events" },
15
- { "id": "rsvp", "name": "RSVP", "description": "A user's intent to attend or participate in an event.", "category": "commitment", "surface": "notifications", "block": "events" },
13
+ { "id": "livestream", "name": "Livestream", "description": "Real-time video or audio participation.", "category": "live", "surface": "livestream", "block": "livestream", "sdkCapabilities": ["livestream"] },
14
+ { "id": "event", "name": "Event", "description": "A scheduled social moment.", "category": "live", "surface": "notifications", "block": "events", "sdkCapabilities": ["events"] },
15
+ { "id": "rsvp", "name": "RSVP", "description": "A user's intent to attend or participate in an event.", "category": "commitment", "surface": "notifications", "block": "events", "sdkCapabilities": ["events"] },
16
16
  { "id": "challenge", "name": "Challenge", "description": "A time-bounded task or contest.", "category": "participation", "availability": "in-development" },
17
17
  { "id": "mission", "name": "Mission", "description": "A guided objective that encourages progression.", "category": "progression", "availability": "in-development" },
18
18
  { "id": "reward", "name": "Reward", "description": "A benefit or acknowledgement earned through behavior.", "category": "incentive", "availability": "in-development" },
@@ -21,6 +21,6 @@
21
21
  { "id": "leaderboard", "name": "Leaderboard", "description": "Ranked comparison between participants.", "category": "competition", "availability": "in-development" },
22
22
  { "id": "knowledge", "name": "Knowledge", "description": "Reusable question, answer, guide, or expertise content.", "category": "knowledge" },
23
23
  { "id": "prediction", "name": "Prediction", "description": "A user's forecast or choice about a future outcome.", "category": "participation" },
24
- { "id": "notification", "name": "Notification", "description": "A timely signal that brings users back to an engagement moment.", "category": "activation", "surface": "notifications", "block": "notifications" }
24
+ { "id": "notification", "name": "Notification", "description": "A timely signal that brings users back to an engagement moment.", "category": "activation", "surface": "notifications", "block": "notifications", "sdkCapabilities": ["notification-tray"] }
25
25
  ]
26
26
  }
@@ -63,13 +63,13 @@
63
63
  "typescript": {
64
64
  "status": "extracted",
65
65
  "file": "models.typescript.json",
66
- "bytes": 35400,
67
- "sha256": "cdd531fe0904d1815f72760450b1c6610be3f72b4c2e1ba80011456c17ab1a6b",
66
+ "bytes": 102455,
67
+ "sha256": "8ba407b1d3f0228e6af4a0c5e99e8031950e106295bc64bf5dd67c600a9681e5",
68
68
  "fieldsGrounding": "names-and-types",
69
- "modelCount": 6,
69
+ "modelCount": 14,
70
70
  "extractor": "vise-ts-model-extractor",
71
71
  "extractorVersion": "0.1.0",
72
- "extractedAt": "2026-07-06T05:09:06.557Z"
72
+ "extractedAt": "2026-07-08T06:54:23.130Z"
73
73
  },
74
74
  "android": {
75
75
  "status": "extracted",