@amityco/social-plus-vise 1.0.1 → 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.
- package/CHANGELOG.md +62 -25
- package/LICENSE +8 -6
- package/README.md +51 -18
- package/dist/capabilities.js +19 -62
- package/dist/intelligence/grounding.js +0 -23
- package/dist/intelligence/placement.js +0 -9
- package/dist/outcomes.js +44 -22
- package/dist/server.js +75 -38
- package/dist/tools/ast.js +3 -209
- package/dist/tools/blocks.js +6 -20
- package/dist/tools/compliance.js +168 -43
- package/dist/tools/creative.js +15 -41
- package/dist/tools/debug.js +0 -16
- package/dist/tools/design.js +18 -364
- package/dist/tools/docs.js +53 -24
- package/dist/tools/experienceCompiler.js +7 -10
- package/dist/tools/experienceSensors.js +1 -1
- package/dist/tools/harness.js +2 -27
- package/dist/tools/integration.js +6 -38
- package/dist/tools/learning.js +1 -1
- package/dist/tools/project.js +763 -546
- package/dist/tools/sdkFacts.js +2 -15
- package/dist/tools/sdkVersion.js +3 -36
- package/dist/tools/sensors.js +0 -6
- package/dist/tools/uxHarness.js +12 -9
- package/package.json +8 -97
- package/rules/chat.yaml +225 -0
- package/rules/event.yaml +45 -0
- package/rules/feed.yaml +24 -24
- package/rules/invitation.yaml +58 -0
- package/rules/live-data.yaml +104 -2
- package/rules/notification-tray.yaml +106 -0
- package/rules/poll.yaml +71 -0
- package/rules/sdk-lifecycle.yaml +112 -6
- package/rules/search.yaml +131 -0
- package/rules/story.yaml +221 -0
- package/rules/user-blocking.yaml +71 -0
- package/sdk-surface/flutter.json +1 -1
- package/sdk-surface/ios.json +1 -1
- package/sdk-surface/manifest.json +12 -12
- package/sdk-surface/models.flutter.json +96 -96
- package/sdk-surface/models.ios.json +1 -1
- package/sdk-surface/typescript.json +4 -4
- package/skills/social-plus-vise/SKILL.md +25 -5
- package/scripts/catalog-coverage-html.mjs +0 -325
- package/scripts/catalog-relationships-html.mjs +0 -686
- package/scripts/catalog-sheets.mjs +0 -286
- package/scripts/dart-model-extractor/bin/extract_models.dart +0 -169
- package/scripts/dart-model-extractor/pubspec.lock +0 -149
- package/scripts/dart-model-extractor/pubspec.yaml +0 -16
- package/scripts/extract-sdk-models.mjs +0 -749
- package/scripts/import-sdk-surface.mjs +0 -161
- package/scripts/pilot-feedback.mjs +0 -107
- package/scripts/workshop-board-html.mjs +0 -1018
- package/scripts/workshop-kit.mjs +0 -252
- package/skills/vise-harness-engineer/SKILL.md +0 -35
package/dist/tools/docs.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { objectInput, optionalNumberField, stringField, textResult } from "../types.js";
|
|
@@ -11,18 +13,6 @@ let hostedCachePending;
|
|
|
11
13
|
function localDocsRoot() {
|
|
12
14
|
return process.env.SOCIAL_PLUS_DOCS_ROOT ? path.resolve(process.env.SOCIAL_PLUS_DOCS_ROOT) : undefined;
|
|
13
15
|
}
|
|
14
|
-
// Hermetic ("offline") docs mode. When VISE_DOCS_OFFLINE is set truthy, docs
|
|
15
|
-
// lookups are served from a committed, bundled corpus instead of the hosted
|
|
16
|
-
// learn.social.plus endpoint — so CI (and offline installs) never depend on the
|
|
17
|
-
// docs site being reachable. This kills the learn.social.plus flake class that
|
|
18
|
-
// once hit test:cli. The live-fetch path stays the default when neither
|
|
19
|
-
// VISE_DOCS_OFFLINE nor SOCIAL_PLUS_DOCS_ROOT is set, so real-fetch coverage is
|
|
20
|
-
// preserved (see vise/package.json test:docs-live).
|
|
21
|
-
//
|
|
22
|
-
// Resolution precedence in loadDocPages():
|
|
23
|
-
// 1. SOCIAL_PLUS_DOCS_ROOT (explicit local override) — highest, unchanged.
|
|
24
|
-
// 2. VISE_DOCS_OFFLINE — serve the bundled docs-cache/ corpus.
|
|
25
|
-
// 3. neither — live fetch from the hosted docs (default).
|
|
26
16
|
function docsOfflineMode() {
|
|
27
17
|
const raw = process.env.VISE_DOCS_OFFLINE;
|
|
28
18
|
if (raw === undefined) {
|
|
@@ -31,10 +21,6 @@ function docsOfflineMode() {
|
|
|
31
21
|
const normalized = raw.trim().toLowerCase();
|
|
32
22
|
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "off" && normalized !== "no";
|
|
33
23
|
}
|
|
34
|
-
// The bundled hermetic corpus ships in the package `files` allowlist as
|
|
35
|
-
// `docs-cache/`. At runtime this module is dist/tools/docs.js, so the corpus is
|
|
36
|
-
// two directories up. An explicit SOCIAL_PLUS_DOCS_OFFLINE_ROOT override is
|
|
37
|
-
// honoured for tests that want to point offline mode at a fixture corpus.
|
|
38
24
|
function bundledDocsCacheRoot() {
|
|
39
25
|
const override = process.env.SOCIAL_PLUS_DOCS_OFFLINE_ROOT;
|
|
40
26
|
if (override) {
|
|
@@ -62,6 +48,40 @@ function cacheTtlMs() {
|
|
|
62
48
|
const value = Number(raw);
|
|
63
49
|
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_CACHE_TTL_MS;
|
|
64
50
|
}
|
|
51
|
+
function docsDiskCacheFile() {
|
|
52
|
+
const dir = process.env.VISE_DOCS_CACHE_DIR ?? path.join(os.tmpdir(), "vise-docs-cache");
|
|
53
|
+
const key = createHash("sha256").update(docsBaseUrl()).digest("hex").slice(0, 16);
|
|
54
|
+
return path.join(dir, `llms-full.${key}.txt`);
|
|
55
|
+
}
|
|
56
|
+
async function readDiskCorpus() {
|
|
57
|
+
const ttl = cacheTtlMs();
|
|
58
|
+
if (ttl === 0)
|
|
59
|
+
return undefined;
|
|
60
|
+
const file = docsDiskCacheFile();
|
|
61
|
+
try {
|
|
62
|
+
const info = await stat(file);
|
|
63
|
+
if (Date.now() - info.mtimeMs >= ttl)
|
|
64
|
+
return undefined;
|
|
65
|
+
const pages = parseLlmsFull(await readFile(file, "utf8"), docsBaseUrl());
|
|
66
|
+
return pages.length >= MIN_EXPECTED_PAGES ? pages : undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function writeDiskCorpus(content) {
|
|
73
|
+
if (cacheTtlMs() === 0)
|
|
74
|
+
return;
|
|
75
|
+
const file = docsDiskCacheFile();
|
|
76
|
+
try {
|
|
77
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
78
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
79
|
+
await writeFile(tmp, content, "utf8");
|
|
80
|
+
await rename(tmp, file);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
65
85
|
export const searchDocsTool = {
|
|
66
86
|
name: "search_docs",
|
|
67
87
|
description: "Search social.plus documentation. For implementation requests, call plan_integration first so required inputs and stop conditions are known before docs lookup.",
|
|
@@ -143,7 +163,6 @@ async function loadDocPages() {
|
|
|
143
163
|
if (root) {
|
|
144
164
|
return loadLocalDocPages(root);
|
|
145
165
|
}
|
|
146
|
-
// Hermetic mode: serve the committed bundled corpus, never the network.
|
|
147
166
|
if (docsOfflineMode()) {
|
|
148
167
|
const cacheRoot = bundledDocsCacheRoot();
|
|
149
168
|
const pages = await loadLocalDocPages(cacheRoot);
|
|
@@ -157,7 +176,7 @@ async function loadDocPages() {
|
|
|
157
176
|
if (hostedCacheEntry && now - hostedCacheEntry.fetchedAt < cacheTtlMs()) {
|
|
158
177
|
return hostedCacheEntry.pages;
|
|
159
178
|
}
|
|
160
|
-
hostedCachePending ??=
|
|
179
|
+
hostedCachePending ??= resolveHostedDocPages()
|
|
161
180
|
.then((pages) => {
|
|
162
181
|
hostedCacheEntry = { pages, fetchedAt: Date.now() };
|
|
163
182
|
return pages;
|
|
@@ -167,6 +186,20 @@ async function loadDocPages() {
|
|
|
167
186
|
});
|
|
168
187
|
return hostedCachePending;
|
|
169
188
|
}
|
|
189
|
+
async function resolveHostedDocPages() {
|
|
190
|
+
const cached = await readDiskCorpus();
|
|
191
|
+
if (cached)
|
|
192
|
+
return cached;
|
|
193
|
+
try {
|
|
194
|
+
return await loadHostedDocPages();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const bundled = await loadLocalDocPages(bundledDocsCacheRoot()).catch(() => []);
|
|
198
|
+
if (bundled.length > 0)
|
|
199
|
+
return bundled;
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
170
203
|
async function loadHostedDocPages() {
|
|
171
204
|
const baseUrl = docsBaseUrl();
|
|
172
205
|
const fullUrl = `${baseUrl}/llms-full.txt`;
|
|
@@ -174,6 +207,7 @@ async function loadHostedDocPages() {
|
|
|
174
207
|
const failures = [];
|
|
175
208
|
try {
|
|
176
209
|
const content = await fetchText(fullUrl);
|
|
210
|
+
await writeDiskCorpus(content);
|
|
177
211
|
return parseLlmsFull(content, baseUrl);
|
|
178
212
|
}
|
|
179
213
|
catch (error) {
|
|
@@ -204,9 +238,6 @@ async function loadLocalDocPages(root) {
|
|
|
204
238
|
async function fetchText(url) {
|
|
205
239
|
return fetchTextWithTimeout(url, "text/plain, text/markdown, */*");
|
|
206
240
|
}
|
|
207
|
-
// Shared network primitive — same AbortController + env-configurable timeout used for
|
|
208
|
-
// docs fetches. Exported so other plan-layer lookups (e.g. the npm registry SDK-version
|
|
209
|
-
// check) reuse one network path rather than adding a parallel one.
|
|
210
241
|
export async function fetchTextWithTimeout(url, accept = "*/*", timeoutMs = fetchTimeoutMs()) {
|
|
211
242
|
const controller = new AbortController();
|
|
212
243
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -314,8 +345,6 @@ async function findDocFiles(root) {
|
|
|
314
345
|
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next") {
|
|
315
346
|
continue;
|
|
316
347
|
}
|
|
317
|
-
// A README is corpus metadata (e.g. the docs-cache/ provenance note), not
|
|
318
|
-
// an SDK doc page — don't ingest it as a searchable page.
|
|
319
348
|
if (/^readme\.(md|mdx)$/i.test(entry.name)) {
|
|
320
349
|
continue;
|
|
321
350
|
}
|
|
@@ -29,7 +29,7 @@ export const compileExperienceTool = {
|
|
|
29
29
|
},
|
|
30
30
|
registryPath: {
|
|
31
31
|
type: "string",
|
|
32
|
-
description: "Optional local
|
|
32
|
+
description: "Optional local block registry path used to bridge selected objects to block install plans.",
|
|
33
33
|
},
|
|
34
34
|
write: {
|
|
35
35
|
type: "boolean",
|
|
@@ -65,7 +65,7 @@ export async function compileExperience(options) {
|
|
|
65
65
|
]);
|
|
66
66
|
const surfaceHints = creativeSurfaceHints(selection);
|
|
67
67
|
const surfacePlans = await Promise.all(surfaceHints.map((hint) => compileSurfacePlan(repoRoot, request, options.surfacePath, hint, uxHarness)));
|
|
68
|
-
const
|
|
68
|
+
const blocksBridge = await blocksBridgeFor({
|
|
69
69
|
repoRoot,
|
|
70
70
|
registryPath: options.registryPath,
|
|
71
71
|
surfacePath: options.surfacePath,
|
|
@@ -77,7 +77,7 @@ export async function compileExperience(options) {
|
|
|
77
77
|
: surface.intake.remainingBlocking > 0
|
|
78
78
|
? [`Surface ${surface.surfaceId} has ${surface.intake.remainingBlocking} blocking intake question(s).`]
|
|
79
79
|
: []),
|
|
80
|
-
...(
|
|
80
|
+
...(blocksBridge.status === "needs-review" ? ["One or more block bridge candidates need review before block installation."] : []),
|
|
81
81
|
];
|
|
82
82
|
const compiler = {
|
|
83
83
|
schema_version: EXPERIENCE_COMPILER_SCHEMA_VERSION,
|
|
@@ -109,7 +109,7 @@ export async function compileExperience(options) {
|
|
|
109
109
|
surfaceSequence: surfaceSequenceFor(surfaceHints, selection, uxHarness),
|
|
110
110
|
surfacePlans,
|
|
111
111
|
designAdaptation: designAdaptationFor(designContract),
|
|
112
|
-
|
|
112
|
+
blocksBridge,
|
|
113
113
|
validationPlan: validationPlanFor(request, surfacePlans),
|
|
114
114
|
sensors: sensors.map((sensor) => ({ name: sensor.name, command: sensor.command, source: sensor.source })),
|
|
115
115
|
stopConditions,
|
|
@@ -204,10 +204,7 @@ function questionSummary(raw) {
|
|
|
204
204
|
blocksImplementationWhenMissing: question.blocksImplementationWhenMissing === true,
|
|
205
205
|
};
|
|
206
206
|
}
|
|
207
|
-
async function
|
|
208
|
-
// Gate on the creative feasibility's computed social.plus-vs-custom classification rather than
|
|
209
|
-
// guessing Block Factory's catalog: skip custom objects (no social.plus block exists), keep every
|
|
210
|
-
// social.plus-backed object as a candidate, and let the registry confirm or deny each one.
|
|
207
|
+
async function blocksBridgeFor(args) {
|
|
211
208
|
const customObjectIds = new Set(args.selection.selectedVariant.feasibility?.customObjectIds ?? []);
|
|
212
209
|
const candidates = blockCandidatesFor(args.selection.selectedVariant.experienceObjectIds, customObjectIds);
|
|
213
210
|
const customAppLayerObjects = customAppLayerObjectsFor(args.selection.selectedVariant.experienceObjectIds, customObjectIds);
|
|
@@ -219,7 +216,7 @@ async function blockFactoryBridgeFor(args) {
|
|
|
219
216
|
...candidate,
|
|
220
217
|
status: "no-registry",
|
|
221
218
|
command: `vise blocks plan . --block ${candidate.blockId} --registry <path>${surfaceOption} --format json`,
|
|
222
|
-
reason: "No
|
|
219
|
+
reason: "No block registry was provided; this is a suggested bridge candidate only.",
|
|
223
220
|
})),
|
|
224
221
|
customAppLayerObjects,
|
|
225
222
|
};
|
|
@@ -296,7 +293,7 @@ function installPlanFor(platforms, surfacePlans) {
|
|
|
296
293
|
guidance: [
|
|
297
294
|
`Use the ${platform} social.plus SDK installation path surfaced by focused plans.`,
|
|
298
295
|
"Pin SDK package versions; do not rely on floating latest ranges.",
|
|
299
|
-
"If
|
|
296
|
+
"If block registry candidates are available, review dry-run block plans before applying any package/source changes.",
|
|
300
297
|
],
|
|
301
298
|
sdkVersion,
|
|
302
299
|
packageManagers: packageManagersFor(platforms),
|
|
@@ -93,7 +93,7 @@ export async function buildExperienceSensors(options) {
|
|
|
93
93
|
stopConditions,
|
|
94
94
|
nextStep: stopConditions.length > 0
|
|
95
95
|
? "Resolve needs-setup and needs-review sensor items, then regenerate `vise experience sensors .` and `vise experience-report .`."
|
|
96
|
-
: "Use this sensor framework as the review checklist after implementation. Keep `score` null until benchmark
|
|
96
|
+
: "Use this sensor framework as the review checklist after implementation. Keep `score` null until benchmark evidence calibrates scoring.",
|
|
97
97
|
};
|
|
98
98
|
if (options.write !== false) {
|
|
99
99
|
framework.artifacts = await writeExperienceSensors(repoRoot, framework);
|
package/dist/tools/harness.js
CHANGED
|
@@ -164,19 +164,12 @@ export async function detectCommandSensors(repoPath, platforms) {
|
|
|
164
164
|
}
|
|
165
165
|
return sensors;
|
|
166
166
|
}
|
|
167
|
-
// xcodebuild failure text that means "this ENVIRONMENT cannot build", not "this project is
|
|
168
|
-
// broken". Matched case-insensitively against combined stdout+stderr only when the exit code is
|
|
169
|
-
// non-zero. Deliberately narrow: a real compile/link error ("error: cannot find 'X' in scope",
|
|
170
|
-
// BUILD FAILED with diagnostics) matches none of these and stays a hard failure.
|
|
171
167
|
const XCODEBUILD_ENVIRONMENT_SKIPS = [
|
|
172
168
|
{
|
|
173
|
-
// xcode-select points at the Command Line Tools: xcodebuild exists on PATH but cannot build.
|
|
174
169
|
pattern: "requires Xcode|command line tools instance",
|
|
175
170
|
reason: "xcodebuild is present but the active developer directory is the Command Line Tools, not a full Xcode install. Run `sudo xcode-select -s /Applications/Xcode.app` (or install Xcode) and re-run; this is an environment precondition, not a project failure.",
|
|
176
171
|
},
|
|
177
172
|
{
|
|
178
|
-
// "Agreeing to the Xcode/iOS license requires admin privileges, please run 'sudo xcodebuild -license'…"
|
|
179
|
-
// Anchored to the agreement message — a compile error citing a LicenseView.swift must stay a failure.
|
|
180
173
|
pattern: "agreeing to the .{0,20}license|xcodebuild -license|license agreement",
|
|
181
174
|
reason: "The Xcode license has not been accepted in this environment. Run `sudo xcodebuild -license accept` and re-run; this is an environment precondition, not a project failure.",
|
|
182
175
|
},
|
|
@@ -185,8 +178,6 @@ const XCODEBUILD_ENVIRONMENT_SKIPS = [
|
|
|
185
178
|
reason: "The build stopped on code-signing requirements (certificates/provisioning not available in this environment). Vise already disables signing for this probe; a signing demand that persists is an environment/config precondition, not a project failure.",
|
|
186
179
|
},
|
|
187
180
|
{
|
|
188
|
-
// 'SDK "iphonesimulator" cannot be located' / destination errors. Anchored so a generic
|
|
189
|
-
// "cannot be located" inside project diagnostics does not classify as environmental.
|
|
190
181
|
pattern: "unable to find a destination|no destinations|sdk \\S{0,30}\\s*cannot be located|unable to locate a simulator|CoreSimulator",
|
|
191
182
|
reason: "No usable build destination (simulator runtime/SDK) is installed in this environment. Install an iOS Simulator runtime via Xcode and re-run; this is an environment precondition, not a project failure.",
|
|
192
183
|
},
|
|
@@ -196,11 +187,8 @@ const XCODEBUILD_ENVIRONMENT_SKIPS = [
|
|
|
196
187
|
},
|
|
197
188
|
];
|
|
198
189
|
async function iosBuildSensors(root) {
|
|
199
|
-
// .xcodeproj/.xcworkspace are bundle DIRECTORIES at the app root.
|
|
200
190
|
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
201
191
|
const dirNames = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
202
|
-
// Prefer the workspace (CocoaPods projects must build via the workspace); ignore the
|
|
203
|
-
// .xcodeproj-embedded project.xcworkspace which readdir at root never returns anyway.
|
|
204
192
|
const workspace = dirNames.find((name) => /\.xcworkspace$/i.test(name));
|
|
205
193
|
const project = dirNames.find((name) => /\.xcodeproj$/i.test(name));
|
|
206
194
|
if (!workspace && !project) {
|
|
@@ -208,9 +196,6 @@ async function iosBuildSensors(root) {
|
|
|
208
196
|
}
|
|
209
197
|
const container = workspace ?? project;
|
|
210
198
|
const source = container;
|
|
211
|
-
// Visible precondition (pf-003): the Xcode project enables the sensor, but the sensor only
|
|
212
|
-
// RUNS when xcodebuild exists on PATH. Absent toolchain → skipped-with-reason, never a
|
|
213
|
-
// silent non-detection and never a spawn-ENOENT failure.
|
|
214
199
|
if (!(await commandOnPath("xcodebuild"))) {
|
|
215
200
|
return [
|
|
216
201
|
{
|
|
@@ -225,10 +210,9 @@ async function iosBuildSensors(root) {
|
|
|
225
210
|
}
|
|
226
211
|
const containerFlag = workspace ? "-workspace" : "-project";
|
|
227
212
|
const buildCommand = workspace
|
|
228
|
-
?
|
|
229
|
-
// workspace name. A wrong guess is classified as an environment/config skip below.
|
|
213
|
+
?
|
|
230
214
|
["xcodebuild", containerFlag, container, "-scheme", container.replace(/\.xcworkspace$/i, ""), "build"]
|
|
231
|
-
:
|
|
215
|
+
:
|
|
232
216
|
["xcodebuild", containerFlag, container, "build"];
|
|
233
217
|
return [
|
|
234
218
|
{
|
|
@@ -243,7 +227,6 @@ async function iosBuildSensors(root) {
|
|
|
243
227
|
name: "iOS build (xcodebuild)",
|
|
244
228
|
command: [
|
|
245
229
|
...buildCommand,
|
|
246
|
-
// Guards: never demand signing assets for a compile probe, and keep the build local.
|
|
247
230
|
"CODE_SIGNING_ALLOWED=NO",
|
|
248
231
|
"CODE_SIGNING_REQUIRED=NO",
|
|
249
232
|
"CODE_SIGN_IDENTITY=",
|
|
@@ -256,8 +239,6 @@ async function iosBuildSensors(root) {
|
|
|
256
239
|
},
|
|
257
240
|
];
|
|
258
241
|
}
|
|
259
|
-
// True when an executable with this name exists on the current PATH. Used for sensor
|
|
260
|
-
// preconditions, so the answer must be computed WITHOUT spawning anything.
|
|
261
242
|
async function commandOnPath(command) {
|
|
262
243
|
const pathValue = process.env.PATH ?? "";
|
|
263
244
|
for (const dir of pathValue.split(path.delimiter)) {
|
|
@@ -268,7 +249,6 @@ async function commandOnPath(command) {
|
|
|
268
249
|
return true;
|
|
269
250
|
}
|
|
270
251
|
catch {
|
|
271
|
-
// keep scanning
|
|
272
252
|
}
|
|
273
253
|
}
|
|
274
254
|
return false;
|
|
@@ -318,11 +298,6 @@ function hasPackageDependency(packageJson, dependencyName) {
|
|
|
318
298
|
packageJson.peerDependencies?.[dependencyName] ??
|
|
319
299
|
packageJson.optionalDependencies?.[dependencyName]);
|
|
320
300
|
}
|
|
321
|
-
// A package.json script that runs a watcher/dev server never exits; running it as a sensor just
|
|
322
|
-
// burns the timeout and reports a misleading "timed-out". Detect the unambiguous cases and skip with
|
|
323
|
-
// guidance. Deliberately conservative so a real one-shot sensor is never skipped: an explicitly
|
|
324
|
-
// disabled flag (--watchAll=false) is NOT a watcher, and ambiguous short flags (e.g. `prettier -w`
|
|
325
|
-
// = --write) are not treated as watch.
|
|
326
301
|
function watchScriptSkipReason(scriptName, body) {
|
|
327
302
|
const command = String(body);
|
|
328
303
|
const watchFlag = command.match(/(?:^|\s)--watch(?:all)?(=\S+)?/i);
|
|
@@ -2,7 +2,7 @@ import { access, readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, getOutcomeDefinition, hasAnswer, planContextFor, resolveOutcome, } from "../outcomes.js";
|
|
4
4
|
import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
|
|
5
|
-
import { availableOptionalCapabilityIds, capabilityChecklist, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, } from "../capabilities.js";
|
|
5
|
+
import { availableOptionalCapabilityIds, capabilityChecklist, optionalCapabilityChecklist, platformCapabilityAvailability, selectedOptionalCapabilityIds, slimCapabilityAvailability, slimCapabilityItem, } from "../capabilities.js";
|
|
6
6
|
import { applicableCompliancePlanRuleSummaries } from "./compliance.js";
|
|
7
7
|
import { DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID, buildDesignBrief, designContractConfirmationFromAnswers, designPreviewPath, readDesignContract, } from "./design.js";
|
|
8
8
|
import { sdkVersionGuidance } from "./sdkVersion.js";
|
|
@@ -84,12 +84,6 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
84
84
|
});
|
|
85
85
|
const definition = getOutcomeDefinition(outcome);
|
|
86
86
|
const capabilityAvailability = await platformCapabilityAvailability(outcome, platform);
|
|
87
|
-
// The design brief is computed INTERNALLY only, to drive the missing-primary-action
|
|
88
|
-
// intake fallback. It is deliberately NOT emitted in plan output: a pre-registered
|
|
89
|
-
// ablation (benchmarks/brief-ablation/RESULTS.md, two runs, n=6/arm) showed that
|
|
90
|
-
// enumerative plan-time design guidance NARROWS what capable agents build and style —
|
|
91
|
-
// the contract + design-check loop alone scored higher on by-name token conformance.
|
|
92
|
-
// Retracted in 0.14.1; the generator stays in design.ts for future redesign.
|
|
93
87
|
const designContract = await readDesignContract(repoRoot);
|
|
94
88
|
const designReview = designReviewGuidance(repoRoot, designContract, answers);
|
|
95
89
|
const acceptedDesignContract = designReview.status === "accepted" ? designContract : null;
|
|
@@ -111,18 +105,8 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
111
105
|
? definition.intakeQuestions(ctx).filter((question) => question.id !== "feature_surface")
|
|
112
106
|
: definition.intakeQuestions(ctx);
|
|
113
107
|
const intake = intakeFor(ctx, outcomeQuestions, outcome, designBrief, capabilityAvailability, designReview);
|
|
114
|
-
// Advisory SDK-version currency guidance (npm registry for TS/RN; version-agnostic
|
|
115
|
-
// for native). Best-effort — degrades to greenfield "install latest + pin" if the
|
|
116
|
-
// registry is unreachable. Never gates.
|
|
117
108
|
const packageJsonText = await readFile(path.join(root, "package.json"), "utf8").catch(() => undefined);
|
|
118
109
|
const sdkVersion = await sdkVersionGuidance(platform, packageJsonText);
|
|
119
|
-
// Scope decisions, hoisted to the top of the output so they survive a `head`
|
|
120
|
-
// truncation. Blocking questions must be resolved before building; clarifying
|
|
121
|
-
// questions (e.g. which engagement surfaces are in scope) must be decided and
|
|
122
|
-
// the decision stated — so an omitted feature like comments is an explicit,
|
|
123
|
-
// reviewable choice rather than a silent drop. Mirrors the detailed `intake`
|
|
124
|
-
// block below; placed here because the intake block lands past common `head`
|
|
125
|
-
// cut points.
|
|
126
110
|
const decisionsRequired = [
|
|
127
111
|
...(creativeContext
|
|
128
112
|
? [
|
|
@@ -138,11 +122,6 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
138
122
|
? `[resolve before building] ${q.question}`
|
|
139
123
|
: `[decide & state in your summary] ${q.question}`),
|
|
140
124
|
];
|
|
141
|
-
// Steps-first ordering: implementationSteps + validation lead the object so
|
|
142
|
-
// that an agent piping `vise plan` through `head` still captures the
|
|
143
|
-
// actionable guidance. The verbose scaffolding (intake, docs, surfaces, rule
|
|
144
|
-
// dumps) follows. Do not move implementationSteps back down — feature steps
|
|
145
|
-
// landing past a `head -120` cut is a documented real-world failure mode.
|
|
146
125
|
return {
|
|
147
126
|
outcome,
|
|
148
127
|
platform,
|
|
@@ -151,7 +130,7 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
151
130
|
creativeContext,
|
|
152
131
|
uxHarness: uxHarnessContext,
|
|
153
132
|
socialWorkplan,
|
|
154
|
-
capabilityAvailability,
|
|
133
|
+
capabilityAvailability: slimCapabilityAvailability(capabilityAvailability),
|
|
155
134
|
designReview,
|
|
156
135
|
decisionsRequired,
|
|
157
136
|
implementationSteps: [
|
|
@@ -182,10 +161,6 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
182
161
|
sdkVersion,
|
|
183
162
|
};
|
|
184
163
|
}
|
|
185
|
-
// Vise-authored capability checklist for the outcome (feed-forward). The agent
|
|
186
|
-
// builds each or opts out with a recorded reason (`// vise: scope-omit <id>
|
|
187
|
-
// <reason>`), which `vise check` reads and reports. Advisory — Vise proposes the
|
|
188
|
-
// set so completeness doesn't depend on the agent remembering it.
|
|
189
164
|
function completenessChecklistFor(outcome) {
|
|
190
165
|
const items = capabilityChecklist(outcome);
|
|
191
166
|
if (items.length === 0) {
|
|
@@ -202,8 +177,8 @@ function optionalCapabilitiesFor(outcome, answers, request, availability) {
|
|
|
202
177
|
if (choices.length === 0) {
|
|
203
178
|
return undefined;
|
|
204
179
|
}
|
|
205
|
-
const unavailable = availability?.unavailable.filter((item) => item.kind === "optional-scope") ?? [];
|
|
206
|
-
const unknown = availability?.unknown.filter((item) => item.kind === "optional-scope") ?? [];
|
|
180
|
+
const unavailable = (availability?.unavailable.filter((item) => item.kind === "optional-scope") ?? []).map(slimCapabilityItem);
|
|
181
|
+
const unknown = (availability?.unknown.filter((item) => item.kind === "optional-scope") ?? []).map(slimCapabilityItem);
|
|
207
182
|
return {
|
|
208
183
|
answerId: "feed_optional_capabilities",
|
|
209
184
|
note: "Optional feed capabilities are feed-forward choices, not baseline compliance. Vise filters choices through the platform SDK surface before showing them. Before `vise init`, make an explicit answer: pass selected ids through `--answer feed_optional_capabilities=<id[,id]>` on `vise plan` and `vise init`, or pass `--answer feed_optional_capabilities=none`. `vise check` runs source sensors only for selected supported capabilities; explicitly unselected or unavailable capabilities remain non-mandatory.",
|
|
@@ -405,7 +380,7 @@ async function socialWorkplanFor(args) {
|
|
|
405
380
|
questions: intake.questions,
|
|
406
381
|
remainingBlocking: intake.remainingBlocking,
|
|
407
382
|
},
|
|
408
|
-
capabilityAvailability,
|
|
383
|
+
capabilityAvailability: slimCapabilityAvailability(capabilityAvailability),
|
|
409
384
|
validation: ["validate_setup", "run_sensors", ...definition.validation(args.platform)],
|
|
410
385
|
docs: definition.docs(args.platform).filter((doc) => doc.path !== "unknown"),
|
|
411
386
|
optionalCapabilities: optionalCapabilitiesFor(surface.outcome, surfaceAnswers, args.request, capabilityAvailability),
|
|
@@ -450,6 +425,7 @@ function designReviewGuidance(repoRoot, contract, answers) {
|
|
|
450
425
|
digest: contract.digest,
|
|
451
426
|
previewPath,
|
|
452
427
|
previewCommand: "vise design preview .",
|
|
428
|
+
referenceCommand: "vise design reference .",
|
|
453
429
|
requiredForDesignConformance: true,
|
|
454
430
|
};
|
|
455
431
|
if (confirmation === "accepted") {
|
|
@@ -472,11 +448,6 @@ function designReviewGuidance(repoRoot, contract, answers) {
|
|
|
472
448
|
nextStep: `Open ${previewPath} and show it to the user. Re-run plan/init with --answer ${DESIGN_CONTRACT_CONFIRMATION_ANSWER_ID}=yes only if the user confirms it matches the desired design; use =no to reject and request a new design source.`,
|
|
473
449
|
};
|
|
474
450
|
}
|
|
475
|
-
// Build advisory UI-generation guidance from an extracted design contract.
|
|
476
|
-
// Declared tokens are surfaced with their custom-property name (so the agent
|
|
477
|
-
// references `var(--x)` / maps it per platform); inferred tokens carry their
|
|
478
|
-
// raw value plus a usage count and an explicit "inferred" marker so they are
|
|
479
|
-
// never mistaken for authoritative brand values.
|
|
480
451
|
function designContractGuidance(contract) {
|
|
481
452
|
const byCategory = (category) => contract.tokens
|
|
482
453
|
.filter((token) => token.category === category)
|
|
@@ -562,9 +533,6 @@ function intakeFor(ctx, outcomeQuestions, outcome, brief, availability, designRe
|
|
|
562
533
|
options: ["yes", "use another source"],
|
|
563
534
|
});
|
|
564
535
|
}
|
|
565
|
-
// Graceful-degradation fallback: when a design contract exists for a feed or chat
|
|
566
|
-
// outcome but no primary-action token was confidently inferred, ask the developer
|
|
567
|
-
// to name the correct token. Non-blocking so it doesn't stall implementation.
|
|
568
536
|
if (brief &&
|
|
569
537
|
designReview?.status === "accepted" &&
|
|
570
538
|
(outcome === "add-feed" || outcome === "add-chat") &&
|
package/dist/tools/learning.js
CHANGED
|
@@ -414,7 +414,7 @@ function summarizeLearning(events) {
|
|
|
414
414
|
recommendationOptimization: {
|
|
415
415
|
status: "not-active",
|
|
416
416
|
reason: "Local learning events are evidence inputs only. Vise does not alter variant ranking from them yet.",
|
|
417
|
-
nextStep: "Use repeated
|
|
417
|
+
nextStep: "Use repeated benchmark review to define a calibrated ranking policy before enabling recommendation optimization.",
|
|
418
418
|
},
|
|
419
419
|
privacy: privacyBlock(),
|
|
420
420
|
nextStep: "Review these local events and recurring sensor gaps with Product/SE. Aggregate learning or automatic ranking changes require explicit opt-in and privacy review.",
|