@amityco/social-plus-vise 1.4.6 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -10
- package/README.md +35 -31
- package/dist/capabilities.js +1 -1
- package/dist/flow.js +2 -2
- package/dist/humanFormat.js +2 -0
- package/dist/outcomes.js +34 -10
- package/dist/server.js +421 -62
- package/dist/tools/compliance.js +321 -12
- package/dist/tools/debug.js +16 -1
- package/dist/tools/design.js +47 -16
- package/dist/tools/integration.js +40 -7
- package/dist/tools/project.js +257 -10
- package/dist/tools/smoke.js +169 -1
- package/docs/vise-how-it-helps.html +318 -105
- package/package.json +1 -1
- package/rules/channel-archive.yaml +136 -0
- package/rules/feed.yaml +37 -15
- package/rules/for-you-feed.yaml +110 -0
- package/rules/live-data.yaml +10 -5
- package/rules/message-search.yaml +136 -0
- package/rules/sdk-lifecycle.yaml +23 -0
- package/sdk-surface/android.json +1305 -23
- package/sdk-surface/flutter.json +2542 -2582
- package/sdk-surface/ios.json +1182 -166
- package/sdk-surface/manifest.json +32 -32
- package/sdk-surface/models.android.json +14 -4
- package/sdk-surface/models.flutter.json +2 -2
- package/sdk-surface/models.ios.json +2 -2
- package/sdk-surface/models.typescript.json +145 -145
- package/sdk-surface/typescript.json +8337 -2443
- package/skills/social-plus-vise/SKILL.md +33 -13
package/dist/tools/smoke.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { promises as fs } from "node:fs";
|
|
3
4
|
import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
|
|
4
5
|
function escapeRegExp(s) {
|
|
@@ -66,6 +67,31 @@ async function loadSmokeConfig(repoRoot) {
|
|
|
66
67
|
return null;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
const RUNTIME_PROOF_WAIVER_PATH = ["sp-vise", "evidence", "runtime-proof-waiver.json"];
|
|
71
|
+
export const MIN_WAIVER_REASON_CHARS = 8;
|
|
72
|
+
export async function readRuntimeProofWaiver(repoRoot) {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(await fs.readFile(path.join(repoRoot, ...RUNTIME_PROOF_WAIVER_PATH), "utf8"));
|
|
75
|
+
if ((parsed.mode !== "declined" && parsed.mode !== "deviceless") || typeof parsed.reason !== "string" || parsed.reason.trim().length < MIN_WAIVER_REASON_CHARS) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return { mode: parsed.mode, reason: parsed.reason.trim(), recorded_at: typeof parsed.recorded_at === "string" ? parsed.recorded_at : "" };
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function writeRuntimeProofWaiver(repoRoot, mode, reason, recordedAt) {
|
|
85
|
+
const trimmed = reason.trim();
|
|
86
|
+
if (trimmed.length < MIN_WAIVER_REASON_CHARS) {
|
|
87
|
+
throw new Error(`A runtime-proof waiver requires an auditable reason of at least ${MIN_WAIVER_REASON_CHARS} characters (got ${trimmed.length}).`);
|
|
88
|
+
}
|
|
89
|
+
const waiver = { mode, reason: trimmed, recorded_at: recordedAt };
|
|
90
|
+
const target = path.join(repoRoot, ...RUNTIME_PROOF_WAIVER_PATH);
|
|
91
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
92
|
+
await fs.writeFile(target, `${JSON.stringify(waiver, null, 2)}\n`, "utf8");
|
|
93
|
+
return waiver;
|
|
94
|
+
}
|
|
69
95
|
function smokeCaptureHelp() {
|
|
70
96
|
return [
|
|
71
97
|
"Create `sp-vise/smoke.json` with the mounted collection surfaces, for example `{ \"platform\": \"android\", \"surfaces\": [{ \"id\": \"feed\", \"expect\": \"populated\" }] }`.",
|
|
@@ -75,6 +101,115 @@ function smokeCaptureHelp() {
|
|
|
75
101
|
"Then run `vise smoke . --log sp-vise/evidence/runtime-smoke.log` and rerun `vise check --ci`.",
|
|
76
102
|
].join(" ");
|
|
77
103
|
}
|
|
104
|
+
function digestText(value) {
|
|
105
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
106
|
+
}
|
|
107
|
+
function pathInside(root, candidate) {
|
|
108
|
+
const relative = path.relative(root, candidate);
|
|
109
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
110
|
+
}
|
|
111
|
+
function repoRelativePath(repoRoot, filePath) {
|
|
112
|
+
return path.relative(repoRoot, filePath).split(path.sep).join(path.posix.sep);
|
|
113
|
+
}
|
|
114
|
+
function shouldSkipDir(name) {
|
|
115
|
+
return new Set([
|
|
116
|
+
".git",
|
|
117
|
+
".idea",
|
|
118
|
+
".vscode",
|
|
119
|
+
"node_modules",
|
|
120
|
+
"dist",
|
|
121
|
+
"build",
|
|
122
|
+
".next",
|
|
123
|
+
".nuxt",
|
|
124
|
+
".dart_tool",
|
|
125
|
+
".gradle",
|
|
126
|
+
"Pods",
|
|
127
|
+
"DerivedData",
|
|
128
|
+
"sp-vise",
|
|
129
|
+
]).has(name);
|
|
130
|
+
}
|
|
131
|
+
function isSourceFile(filePath) {
|
|
132
|
+
return /\.(?:ts|tsx|js|jsx|mjs|cjs|swift|kt|kts|java|dart)$/i.test(filePath);
|
|
133
|
+
}
|
|
134
|
+
async function collectSourceFiles(root, limit = 800) {
|
|
135
|
+
const found = [];
|
|
136
|
+
async function walk(dir) {
|
|
137
|
+
if (found.length >= limit)
|
|
138
|
+
return;
|
|
139
|
+
let entries;
|
|
140
|
+
try {
|
|
141
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
if (found.length >= limit)
|
|
148
|
+
break;
|
|
149
|
+
const full = path.join(dir, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
if (!shouldSkipDir(entry.name))
|
|
152
|
+
await walk(full);
|
|
153
|
+
}
|
|
154
|
+
else if (entry.isFile() && isSourceFile(full)) {
|
|
155
|
+
found.push(full);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await walk(root);
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
export function hasExecutableSmokeMarker(content) {
|
|
163
|
+
for (const line of content.split(/\r?\n/)) {
|
|
164
|
+
const idx = line.indexOf("VISE_SMOKE");
|
|
165
|
+
if (idx < 0)
|
|
166
|
+
continue;
|
|
167
|
+
if (/^\s*(\/\/|\*|#|<!--|\/\*)/.test(line))
|
|
168
|
+
continue;
|
|
169
|
+
const before = line.slice(0, idx);
|
|
170
|
+
if ((before.match(/["'`]/g) || []).length % 2 === 1)
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
async function collectSmokeSourceProvenance(repoRoot, surfaces) {
|
|
176
|
+
const files = await collectSourceFiles(repoRoot);
|
|
177
|
+
const markerFiles = [];
|
|
178
|
+
const surfaceFiles = new Map();
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
let content;
|
|
181
|
+
try {
|
|
182
|
+
content = await fs.readFile(file, "utf8");
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (!hasExecutableSmokeMarker(content)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const matchedSurfaces = surfaces
|
|
191
|
+
.map((surface) => surface.id)
|
|
192
|
+
.filter((id) => content.includes(id));
|
|
193
|
+
if (matchedSurfaces.length === 0) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const relative = repoRelativePath(repoRoot, file);
|
|
197
|
+
markerFiles.push({
|
|
198
|
+
path: relative,
|
|
199
|
+
sha256: digestText(content),
|
|
200
|
+
surfaces: matchedSurfaces.sort(),
|
|
201
|
+
});
|
|
202
|
+
for (const surface of matchedSurfaces) {
|
|
203
|
+
surfaceFiles.set(surface, [...(surfaceFiles.get(surface) ?? []), relative]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const surfaceEmitters = surfaces.map((surface) => ({
|
|
207
|
+
surface: surface.id,
|
|
208
|
+
files: (surfaceFiles.get(surface.id) ?? []).sort(),
|
|
209
|
+
}));
|
|
210
|
+
const missingSurfaces = surfaceEmitters.filter((item) => item.files.length === 0).map((item) => item.surface);
|
|
211
|
+
return { fingerprints: markerFiles.sort((a, b) => a.path.localeCompare(b.path)), surfaceEmitters, missingSurfaces };
|
|
212
|
+
}
|
|
78
213
|
export const smokeTool = {
|
|
79
214
|
name: "smoke",
|
|
80
215
|
description: "Assess a captured runtime mount-smoke log (VISE_SMOKE markers) into a pass/fail verdict and record runtime-smoke evidence. Catches the session-establish race that static check cannot.",
|
|
@@ -115,10 +250,43 @@ export const smokeTool = {
|
|
|
115
250
|
return textResult({ status: "no-surfaces", note: only ? `Surface '${only}' is not declared in smoke.json.` : "smoke.json declares no surfaces." });
|
|
116
251
|
}
|
|
117
252
|
const assessment = assessSmokeLog(logText, surfaces);
|
|
253
|
+
const sourceProvenance = await collectSmokeSourceProvenance(repoRoot, surfaces);
|
|
254
|
+
if (sourceProvenance.fingerprints.length === 0 || sourceProvenance.missingSurfaces.length > 0) {
|
|
255
|
+
return textResult({
|
|
256
|
+
status: "no-source-provenance",
|
|
257
|
+
platform: config.platform,
|
|
258
|
+
results: assessment.results,
|
|
259
|
+
missingSurfaces: sourceProvenance.missingSurfaces,
|
|
260
|
+
note: "Runtime smoke log markers were not bound to source emitters. Add `VISE_SMOKE` marker code in product source for each declared surface id, rerun the app, capture logs, and rerun `vise smoke`.",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
118
263
|
try {
|
|
119
264
|
const evidenceDir = path.join(repoRoot, "sp-vise", "evidence");
|
|
120
265
|
await fs.mkdir(evidenceDir, { recursive: true });
|
|
121
|
-
|
|
266
|
+
const canonicalLogPath = path.join(evidenceDir, "runtime-smoke.log");
|
|
267
|
+
if (path.resolve(logPath) !== canonicalLogPath) {
|
|
268
|
+
await fs.writeFile(canonicalLogPath, logText, "utf8");
|
|
269
|
+
}
|
|
270
|
+
const smokeConfigPath = path.join(repoRoot, "sp-vise", "smoke.json");
|
|
271
|
+
const smokeConfigText = await fs.readFile(smokeConfigPath, "utf8");
|
|
272
|
+
const inputLogPath = pathInside(repoRoot, logPath) ? repoRelativePath(repoRoot, logPath) : logPath;
|
|
273
|
+
await fs.writeFile(path.join(evidenceDir, "runtime-smoke.json"), JSON.stringify({
|
|
274
|
+
passed: assessment.passed,
|
|
275
|
+
platform: config.platform,
|
|
276
|
+
generated_at: new Date().toISOString(),
|
|
277
|
+
log: {
|
|
278
|
+
path: "sp-vise/evidence/runtime-smoke.log",
|
|
279
|
+
input_path: inputLogPath,
|
|
280
|
+
sha256: digestText(logText),
|
|
281
|
+
},
|
|
282
|
+
smoke_config: {
|
|
283
|
+
path: "sp-vise/smoke.json",
|
|
284
|
+
sha256: digestText(smokeConfigText),
|
|
285
|
+
},
|
|
286
|
+
source_fingerprints: sourceProvenance.fingerprints,
|
|
287
|
+
surface_emitters: sourceProvenance.surfaceEmitters,
|
|
288
|
+
results: assessment.results,
|
|
289
|
+
}, null, 2));
|
|
122
290
|
}
|
|
123
291
|
catch {
|
|
124
292
|
}
|