@amityco/social-plus-vise 0.4.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/LICENSE +51 -0
- package/README.md +92 -0
- package/dist/outcomes.js +574 -0
- package/dist/server.js +810 -0
- package/dist/tools/compliance.js +965 -0
- package/dist/tools/docs.js +312 -0
- package/dist/tools/harness.js +229 -0
- package/dist/tools/integration.js +332 -0
- package/dist/tools/patch.js +67 -0
- package/dist/tools/project.js +908 -0
- package/dist/tools/resolve.js +120 -0
- package/dist/tools/sensors.js +185 -0
- package/dist/types.js +31 -0
- package/dist/version.js +19 -0
- package/package.json +64 -0
- package/rules/design.yaml +66 -0
- package/rules/feed.yaml +126 -0
- package/rules/live-data.yaml +66 -0
- package/rules/push.yaml +95 -0
- package/rules/sdk-lifecycle.yaml +422 -0
- package/rules/security.yaml +162 -0
- package/skills/social-plus-vise/SKILL.md +199 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
|
|
10
|
+
import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
|
|
11
|
+
import { planHarnessTool } from "./tools/harness.js";
|
|
12
|
+
import { planIntegrationTool } from "./tools/integration.js";
|
|
13
|
+
import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
|
|
14
|
+
import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
|
|
15
|
+
import { runSensorsTool } from "./tools/sensors.js";
|
|
16
|
+
import { packageName, packageVersion } from "./version.js";
|
|
17
|
+
const tools = new Map([
|
|
18
|
+
searchDocsTool,
|
|
19
|
+
getDocPageTool,
|
|
20
|
+
inspectProjectTool,
|
|
21
|
+
planHarnessTool,
|
|
22
|
+
planIntegrationTool,
|
|
23
|
+
initComplianceTool,
|
|
24
|
+
checkComplianceTool,
|
|
25
|
+
syncComplianceTool,
|
|
26
|
+
attestRuleTool,
|
|
27
|
+
explainRuleTool,
|
|
28
|
+
initEngagementTool,
|
|
29
|
+
showEngagementTool,
|
|
30
|
+
resolveRequestTool,
|
|
31
|
+
runSensorsTool,
|
|
32
|
+
validateSetupTool,
|
|
33
|
+
suggestPatchTool,
|
|
34
|
+
].map((tool) => [tool.name, tool]));
|
|
35
|
+
const bundledSkillName = "social-plus-vise";
|
|
36
|
+
const cliResult = await handleCli(process.argv.slice(2));
|
|
37
|
+
if (cliResult === "exit") {
|
|
38
|
+
process.exitCode = process.exitCode ?? 0;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const server = new Server({
|
|
42
|
+
name: "social-plus-vise",
|
|
43
|
+
version: packageVersion,
|
|
44
|
+
}, {
|
|
45
|
+
capabilities: {
|
|
46
|
+
tools: {},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
50
|
+
tools: Array.from(tools.values()).map((tool) => ({
|
|
51
|
+
name: tool.name,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
inputSchema: tool.inputSchema,
|
|
54
|
+
})),
|
|
55
|
+
}));
|
|
56
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
57
|
+
const tool = tools.get(request.params.name);
|
|
58
|
+
if (!tool) {
|
|
59
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
60
|
+
}
|
|
61
|
+
return tool.call(request.params.arguments);
|
|
62
|
+
});
|
|
63
|
+
await server.connect(new StdioServerTransport());
|
|
64
|
+
}
|
|
65
|
+
async function handleCli(args) {
|
|
66
|
+
const command = args[0];
|
|
67
|
+
if (!command) {
|
|
68
|
+
return "serve";
|
|
69
|
+
}
|
|
70
|
+
if (command === "mcp" || command === "serve" || command === "start") {
|
|
71
|
+
return "serve";
|
|
72
|
+
}
|
|
73
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
74
|
+
console.log(`${packageName} ${packageVersion}`);
|
|
75
|
+
return "exit";
|
|
76
|
+
}
|
|
77
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
78
|
+
console.log(helpText(args[1]));
|
|
79
|
+
return "exit";
|
|
80
|
+
}
|
|
81
|
+
if (command === "doctor" || command === "--doctor") {
|
|
82
|
+
console.log(JSON.stringify(doctorResult(), null, 2));
|
|
83
|
+
return "exit";
|
|
84
|
+
}
|
|
85
|
+
if (isHelpRequest(args)) {
|
|
86
|
+
console.log(helpText(command));
|
|
87
|
+
return "exit";
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
if (command === "install-skill" || command === "install_skill") {
|
|
91
|
+
console.log(JSON.stringify(await installSkill(args.slice(1)), null, 2));
|
|
92
|
+
return "exit";
|
|
93
|
+
}
|
|
94
|
+
if (command === "print-skill" || command === "print_skill") {
|
|
95
|
+
console.log(await readFile(path.join(skillSourceDir(), "SKILL.md"), "utf8"));
|
|
96
|
+
return "exit";
|
|
97
|
+
}
|
|
98
|
+
if (command === "skill-path" || command === "skill_path") {
|
|
99
|
+
console.log(JSON.stringify(skillPathResult(), null, 2));
|
|
100
|
+
return "exit";
|
|
101
|
+
}
|
|
102
|
+
if (command === "search-docs" || command === "search_docs") {
|
|
103
|
+
await printToolResult(searchDocsTool, {
|
|
104
|
+
query: flagValue(args, "query") ?? requiredPositionalText(args.slice(1), "search-docs requires a query."),
|
|
105
|
+
limit: optionalNumberFlag(args, "limit"),
|
|
106
|
+
});
|
|
107
|
+
return "exit";
|
|
108
|
+
}
|
|
109
|
+
if (command === "get-doc-page" || command === "get_doc_page") {
|
|
110
|
+
await printToolResult(getDocPageTool, {
|
|
111
|
+
path: flagValue(args, "path") ?? requiredPositionalText(args.slice(1), "get-doc-page requires a docs path."),
|
|
112
|
+
});
|
|
113
|
+
return "exit";
|
|
114
|
+
}
|
|
115
|
+
if (command === "inspect") {
|
|
116
|
+
await printToolResult(inspectProjectTool, {
|
|
117
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
118
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
119
|
+
});
|
|
120
|
+
return "exit";
|
|
121
|
+
}
|
|
122
|
+
if (command === "plan" || command === "plan-integration") {
|
|
123
|
+
await printToolResult(planIntegrationTool, {
|
|
124
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
125
|
+
request: requiredFlagValue(args, "request", "plan requires --request."),
|
|
126
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
127
|
+
answers: keyValueFlag(args, "answer"),
|
|
128
|
+
});
|
|
129
|
+
return "exit";
|
|
130
|
+
}
|
|
131
|
+
if (command === "plan-harness") {
|
|
132
|
+
await printToolResult(planHarnessTool, {
|
|
133
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
134
|
+
request: requiredFlagValue(args, "request", "plan-harness requires --request."),
|
|
135
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
136
|
+
});
|
|
137
|
+
return "exit";
|
|
138
|
+
}
|
|
139
|
+
if (command === "validate" || command === "validate-setup") {
|
|
140
|
+
await printToolResult(validateSetupTool, {
|
|
141
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
142
|
+
platform: flagValue(args, "platform"),
|
|
143
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
144
|
+
});
|
|
145
|
+
return "exit";
|
|
146
|
+
}
|
|
147
|
+
if (command === "run-sensors" || command === "run_sensor" || command === "run-sensor") {
|
|
148
|
+
await printToolResult(runSensorsTool, {
|
|
149
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
150
|
+
request: flagValue(args, "request"),
|
|
151
|
+
include: flagValues(args, "include"),
|
|
152
|
+
timeoutMs: optionalNumberFlag(args, "timeout-ms"),
|
|
153
|
+
dryRun: hasFlag(args, "dry-run"),
|
|
154
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
155
|
+
});
|
|
156
|
+
return "exit";
|
|
157
|
+
}
|
|
158
|
+
if (command === "resolve") {
|
|
159
|
+
await printToolResult(resolveRequestTool, {
|
|
160
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
161
|
+
request: requiredFlagValue(args, "request", "resolve requires --request."),
|
|
162
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
163
|
+
});
|
|
164
|
+
return "exit";
|
|
165
|
+
}
|
|
166
|
+
if (command === "init") {
|
|
167
|
+
assertOnlyKnownFlags(args, ["request", "surface", "surface-path"], "init");
|
|
168
|
+
console.log(JSON.stringify(await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path")), null, 2));
|
|
169
|
+
return "exit";
|
|
170
|
+
}
|
|
171
|
+
if (command === "check") {
|
|
172
|
+
assertOnlyKnownFlags(args, ["ci"], "check");
|
|
173
|
+
const result = await checkCompliance(positionalRepoPath(args.slice(1)));
|
|
174
|
+
console.log(JSON.stringify(hasFlag(args, "ci") ? ciCheckResult(result) : result, null, 2));
|
|
175
|
+
process.exitCode = result.exitCode;
|
|
176
|
+
return "exit";
|
|
177
|
+
}
|
|
178
|
+
if (command === "sync") {
|
|
179
|
+
assertOnlyKnownFlags(args, [], "sync");
|
|
180
|
+
console.log(JSON.stringify(await syncCompliance(positionalRepoPath(args.slice(1))), null, 2));
|
|
181
|
+
return "exit";
|
|
182
|
+
}
|
|
183
|
+
if (command === "attest") {
|
|
184
|
+
assertOnlyKnownFlags(args, ["rule", "confidence", "signer", "identity", "rationale", "evidence-file"], "attest");
|
|
185
|
+
console.log(JSON.stringify(await attestRule({
|
|
186
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
187
|
+
ruleId: requiredFlagValue(args, "rule", "attest requires --rule."),
|
|
188
|
+
confidence: requiredConfidence(args),
|
|
189
|
+
signer: requiredSigner(args),
|
|
190
|
+
identity: flagValue(args, "identity"),
|
|
191
|
+
rationale: requiredFlagValue(args, "rationale", "attest requires --rationale."),
|
|
192
|
+
evidence: await evidenceFromArgs(args),
|
|
193
|
+
}), null, 2));
|
|
194
|
+
return "exit";
|
|
195
|
+
}
|
|
196
|
+
if (command === "explain") {
|
|
197
|
+
assertOnlyKnownFlags(args, [], "explain");
|
|
198
|
+
console.log(JSON.stringify(await explainRule(requiredPositionalText(args.slice(1), "explain requires a rule id.")), null, 2));
|
|
199
|
+
return "exit";
|
|
200
|
+
}
|
|
201
|
+
if (command === "status") {
|
|
202
|
+
assertOnlyKnownFlags(args, [], "status");
|
|
203
|
+
console.log(JSON.stringify(await statusCompliance(positionalRepoPath(args.slice(1))), null, 2));
|
|
204
|
+
return "exit";
|
|
205
|
+
}
|
|
206
|
+
if (command === "engagement") {
|
|
207
|
+
const sub = args[1];
|
|
208
|
+
const subArgs = args.slice(2);
|
|
209
|
+
if (sub === "init") {
|
|
210
|
+
assertOnlyKnownFlags(subArgs, ["tier", "customer-id", "scope", "target-completion", "reviewer-name", "reviewer-email", "evidence-upload-consent"], "engagement init");
|
|
211
|
+
console.log(JSON.stringify(await initEngagement({
|
|
212
|
+
repoPath: positionalRepoPath(subArgs),
|
|
213
|
+
tier: flagValue(subArgs, "tier"),
|
|
214
|
+
customerId: flagValue(subArgs, "customer-id"),
|
|
215
|
+
scope: flagValues(subArgs, "scope").flatMap((value) => value.split(",").map((entry) => entry.trim()).filter(Boolean)),
|
|
216
|
+
targetCompletionDate: flagValue(subArgs, "target-completion"),
|
|
217
|
+
reviewerName: flagValue(subArgs, "reviewer-name"),
|
|
218
|
+
reviewerEmail: flagValue(subArgs, "reviewer-email"),
|
|
219
|
+
evidenceUploadConsent: hasFlag(subArgs, "evidence-upload-consent"),
|
|
220
|
+
}), null, 2));
|
|
221
|
+
return "exit";
|
|
222
|
+
}
|
|
223
|
+
if (sub === "show") {
|
|
224
|
+
assertOnlyKnownFlags(subArgs, [], "engagement show");
|
|
225
|
+
console.log(JSON.stringify(await showEngagement(positionalRepoPath(subArgs)), null, 2));
|
|
226
|
+
return "exit";
|
|
227
|
+
}
|
|
228
|
+
console.error(`Unknown engagement subcommand: ${sub ?? "(none)"}. Expected "init" or "show".`);
|
|
229
|
+
process.exitCode = 1;
|
|
230
|
+
return "exit";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return "exit";
|
|
237
|
+
}
|
|
238
|
+
if (command.startsWith("-")) {
|
|
239
|
+
console.error(`Unknown option: ${command}`);
|
|
240
|
+
console.error("Run with --help for usage.");
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return "exit";
|
|
243
|
+
}
|
|
244
|
+
console.error(`Unknown command: ${command}`);
|
|
245
|
+
console.error("Run with --help for usage.");
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
return "exit";
|
|
248
|
+
}
|
|
249
|
+
function helpText(command) {
|
|
250
|
+
if (command === "inspect") {
|
|
251
|
+
return `${packageName} inspect
|
|
252
|
+
|
|
253
|
+
Inspect a customer repository and detect platform, app surface, and design-system signals.
|
|
254
|
+
|
|
255
|
+
Usage:
|
|
256
|
+
vise inspect [repoPath] [--surface apps/web]
|
|
257
|
+
vise inspect [repoPath] [--surface apps/web]`;
|
|
258
|
+
}
|
|
259
|
+
if (command === "plan" || command === "plan-integration") {
|
|
260
|
+
return `${packageName} plan
|
|
261
|
+
|
|
262
|
+
Create the evidence-backed implementation packet before editing code.
|
|
263
|
+
|
|
264
|
+
Usage:
|
|
265
|
+
vise plan [repoPath] --request "Add a social feed"
|
|
266
|
+
vise plan apps/web --request "Create posts" --surface apps/web
|
|
267
|
+
|
|
268
|
+
Re-plan with collected answers (repeat --answer for each intake question):
|
|
269
|
+
vise plan . --request "Add a social feed" \\
|
|
270
|
+
--answer feed_scope=community \\
|
|
271
|
+
--answer feed_target=existing\\ communityId \\
|
|
272
|
+
--answer target_screen_or_route=app/feed/page.tsx`;
|
|
273
|
+
}
|
|
274
|
+
if (command === "plan-harness") {
|
|
275
|
+
return `${packageName} plan-harness
|
|
276
|
+
|
|
277
|
+
Create a harness guide/sensor plan for the requested SDK integration.
|
|
278
|
+
|
|
279
|
+
Usage:
|
|
280
|
+
vise plan-harness [repoPath] --request "Add a social feed"
|
|
281
|
+
vise plan-harness apps/web --request "Set up notifications" --surface apps/web`;
|
|
282
|
+
}
|
|
283
|
+
if (command === "search-docs" || command === "search_docs") {
|
|
284
|
+
return `${packageName} search-docs
|
|
285
|
+
|
|
286
|
+
Search social.plus documentation from the hosted llms source, or SOCIAL_PLUS_DOCS_ROOT for maintainers.
|
|
287
|
+
|
|
288
|
+
Usage:
|
|
289
|
+
vise search-docs "create text post"
|
|
290
|
+
vise search-docs --query "Live Collection cleanup" --limit 3`;
|
|
291
|
+
}
|
|
292
|
+
if (command === "install-skill" || command === "install_skill") {
|
|
293
|
+
return `${packageName} install-skill
|
|
294
|
+
|
|
295
|
+
Install the bundled social.plus Vise skill for a host AI coding tool.
|
|
296
|
+
|
|
297
|
+
Usage:
|
|
298
|
+
vise install-skill --target codex
|
|
299
|
+
vise install-skill --target claude
|
|
300
|
+
vise install-skill --target claude-project .
|
|
301
|
+
vise install-skill --target agents .
|
|
302
|
+
vise install-skill --target cursor .
|
|
303
|
+
vise install-skill --target cursor-rules .
|
|
304
|
+
vise install-skill --target vscode .
|
|
305
|
+
vise install-skill --target copilot .
|
|
306
|
+
vise install-skill --dest ~/.codex/skills
|
|
307
|
+
vise install-skill --dest ~/.codex/skills --force
|
|
308
|
+
|
|
309
|
+
Targets:
|
|
310
|
+
codex Installs to ~/.codex/skills/social-plus-vise
|
|
311
|
+
claude Installs to ~/.claude/skills/social-plus-vise
|
|
312
|
+
claude-project Installs to <repoPath>/.claude/skills/social-plus-vise
|
|
313
|
+
agents Installs to <repoPath>/.agents/skills/social-plus-vise
|
|
314
|
+
cursor Installs to <repoPath>/.cursor/skills/social-plus-vise
|
|
315
|
+
vscode Installs to <repoPath>/.github/skills/social-plus-vise
|
|
316
|
+
copilot Installs to <repoPath>/.github/skills/social-plus-vise
|
|
317
|
+
cursor-rules Writes <repoPath>/.cursor/rules/social-plus-vise.mdc
|
|
318
|
+
custom Use --dest <skillsRoot or exact skill directory>`;
|
|
319
|
+
}
|
|
320
|
+
if (command === "print-skill" || command === "print_skill") {
|
|
321
|
+
return `${packageName} print-skill
|
|
322
|
+
|
|
323
|
+
Print the bundled social.plus Vise skill markdown to stdout.
|
|
324
|
+
|
|
325
|
+
Usage:
|
|
326
|
+
vise print-skill`;
|
|
327
|
+
}
|
|
328
|
+
if (command === "skill-path" || command === "skill_path") {
|
|
329
|
+
return `${packageName} skill-path
|
|
330
|
+
|
|
331
|
+
Print the bundled skill source path and supported install targets.
|
|
332
|
+
|
|
333
|
+
Usage:
|
|
334
|
+
vise skill-path`;
|
|
335
|
+
}
|
|
336
|
+
if (command === "get-doc-page" || command === "get_doc_page") {
|
|
337
|
+
return `${packageName} get-doc-page
|
|
338
|
+
|
|
339
|
+
Read one social.plus documentation page by canonical docs path.
|
|
340
|
+
|
|
341
|
+
Usage:
|
|
342
|
+
vise get-doc-page social-plus-sdk/social/content-management/posts/creation/text-post
|
|
343
|
+
vise get-doc-page --path uikit/getting-started/installation`;
|
|
344
|
+
}
|
|
345
|
+
if (command === "validate" || command === "validate-setup") {
|
|
346
|
+
return `${packageName} validate
|
|
347
|
+
|
|
348
|
+
Run deterministic social.plus setup validation for the current project.
|
|
349
|
+
|
|
350
|
+
Usage:
|
|
351
|
+
vise validate [repoPath] [--platform typescript] [--surface apps/web]`;
|
|
352
|
+
}
|
|
353
|
+
if (command === "run-sensors" || command === "run-sensor" || command === "run_sensor") {
|
|
354
|
+
return `${packageName} run-sensors
|
|
355
|
+
|
|
356
|
+
Run detected project command sensors such as typecheck, test, build, Flutter, or Gradle checks.
|
|
357
|
+
|
|
358
|
+
Usage:
|
|
359
|
+
vise run-sensors [repoPath] [--dry-run] [--include "npm test"] [--timeout-ms 120000]`;
|
|
360
|
+
}
|
|
361
|
+
if (command === "resolve") {
|
|
362
|
+
return `${packageName} resolve
|
|
363
|
+
|
|
364
|
+
Resolve a natural-language request into the closest supported Vise outcome.
|
|
365
|
+
|
|
366
|
+
Usage:
|
|
367
|
+
vise resolve [repoPath] --request "Add a social feed"`;
|
|
368
|
+
}
|
|
369
|
+
if (command === "init") {
|
|
370
|
+
return `${packageName} init
|
|
371
|
+
|
|
372
|
+
Initialize the local sp-vise compliance sidecar for one integration request.
|
|
373
|
+
|
|
374
|
+
Usage:
|
|
375
|
+
vise init [repoPath] --request "Add a social feed" [--surface apps/web]`;
|
|
376
|
+
}
|
|
377
|
+
if (command === "check") {
|
|
378
|
+
return `${packageName} check
|
|
379
|
+
|
|
380
|
+
Check the current source and recorded attestations against the compliance contract. Read-only.
|
|
381
|
+
|
|
382
|
+
Usage:
|
|
383
|
+
vise check [repoPath] [--ci]`;
|
|
384
|
+
}
|
|
385
|
+
if (command === "sync") {
|
|
386
|
+
return `${packageName} sync
|
|
387
|
+
|
|
388
|
+
Persist deterministic-pass compliance evidence into sp-vise/attestations.
|
|
389
|
+
|
|
390
|
+
Usage:
|
|
391
|
+
vise sync [repoPath]`;
|
|
392
|
+
}
|
|
393
|
+
if (command === "attest") {
|
|
394
|
+
return `${packageName} attest
|
|
395
|
+
|
|
396
|
+
Record a host-agent or local-human attestation for one compliance rule.
|
|
397
|
+
|
|
398
|
+
Usage:
|
|
399
|
+
vise attest [repoPath] --rule sdk.init.at-startup --confidence high --signer host-agent --evidence-file evidence.json --rationale "Why this rule is satisfied."`;
|
|
400
|
+
}
|
|
401
|
+
if (command === "explain") {
|
|
402
|
+
return `${packageName} explain
|
|
403
|
+
|
|
404
|
+
Explain one compliance rule.
|
|
405
|
+
|
|
406
|
+
Usage:
|
|
407
|
+
vise explain sdk.init.at-startup`;
|
|
408
|
+
}
|
|
409
|
+
if (command === "status") {
|
|
410
|
+
return `${packageName} status
|
|
411
|
+
|
|
412
|
+
Print a compact compliance summary.
|
|
413
|
+
|
|
414
|
+
Usage:
|
|
415
|
+
vise status [repoPath]`;
|
|
416
|
+
}
|
|
417
|
+
return `${packageName}
|
|
418
|
+
|
|
419
|
+
Skill-guided deterministic CLI for social.plus SDK integration assistance.
|
|
420
|
+
|
|
421
|
+
Usage:
|
|
422
|
+
vise mcp Start the stdio MCP server
|
|
423
|
+
vise search-docs "query" Search hosted social.plus docs
|
|
424
|
+
vise get-doc-page "path" Read a canonical docs page
|
|
425
|
+
vise install-skill --target codex Install bundled skill guidance
|
|
426
|
+
vise print-skill Print bundled skill markdown
|
|
427
|
+
vise inspect [repoPath] Inspect platform and design signals
|
|
428
|
+
vise plan [repoPath] --request "..." Create an implementation plan
|
|
429
|
+
vise init [repoPath] --request "..." Initialize compliance sidecar
|
|
430
|
+
vise check [repoPath] Check compliance contract
|
|
431
|
+
vise sync [repoPath] Persist deterministic-pass evidence
|
|
432
|
+
vise attest [repoPath] --rule ... Record a compliance attestation
|
|
433
|
+
vise explain <ruleId> Explain one compliance rule
|
|
434
|
+
vise status [repoPath] Print compliance summary
|
|
435
|
+
vise validate [repoPath] Validate setup and common risks
|
|
436
|
+
vise run-sensors [repoPath] Run detected project sensors
|
|
437
|
+
vise doctor Print install diagnostics
|
|
438
|
+
vise --help Show this help
|
|
439
|
+
vise --version Show package version
|
|
440
|
+
|
|
441
|
+
Compatibility aliases:
|
|
442
|
+
spf, social-plus-vise, foundry, social-plus-foundry, foundry-mcp
|
|
443
|
+
|
|
444
|
+
MCP config:
|
|
445
|
+
{
|
|
446
|
+
"mcpServers": {
|
|
447
|
+
"social-plus": {
|
|
448
|
+
"command": "vise",
|
|
449
|
+
"args": ["mcp"]
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
Customers do not need environment variables. Maintainers may set
|
|
455
|
+
SOCIAL_PLUS_DOCS_ROOT to test against a local social-plus-docs checkout.`;
|
|
456
|
+
}
|
|
457
|
+
async function printToolResult(tool, input) {
|
|
458
|
+
const result = await tool.call(input);
|
|
459
|
+
console.log(result.content.map((item) => item.text).join("\n"));
|
|
460
|
+
}
|
|
461
|
+
async function installSkill(args) {
|
|
462
|
+
const source = skillSourceDir();
|
|
463
|
+
const instructionInstall = instructionInstallDestination(args);
|
|
464
|
+
if (instructionInstall) {
|
|
465
|
+
return installInstructionFile(instructionInstall, args);
|
|
466
|
+
}
|
|
467
|
+
const destination = skillInstallDestination(args);
|
|
468
|
+
const force = hasFlag(args, "force");
|
|
469
|
+
const installedFiles = await copyDirectory(source, destination, force);
|
|
470
|
+
return {
|
|
471
|
+
status: installedFiles.length > 0 ? "installed" : "already-current",
|
|
472
|
+
skill: bundledSkillName,
|
|
473
|
+
source,
|
|
474
|
+
destination,
|
|
475
|
+
force,
|
|
476
|
+
installedFiles,
|
|
477
|
+
nextStep: "Restart or reload the host AI coding tool so it discovers the installed skill.",
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function installInstructionFile(target, args) {
|
|
481
|
+
const force = hasFlag(args, "force");
|
|
482
|
+
const source = path.join(skillSourceDir(), "SKILL.md");
|
|
483
|
+
const content = await readFile(source, "utf8");
|
|
484
|
+
if (await fileExists(target.destination)) {
|
|
485
|
+
const existing = await readFile(target.destination, "utf8");
|
|
486
|
+
if (existing === content) {
|
|
487
|
+
return {
|
|
488
|
+
status: "already-current",
|
|
489
|
+
skill: bundledSkillName,
|
|
490
|
+
host: target.host,
|
|
491
|
+
source,
|
|
492
|
+
destination: target.destination,
|
|
493
|
+
force,
|
|
494
|
+
installedFiles: [],
|
|
495
|
+
nextStep: "Restart or reload the host AI coding tool so it discovers the updated project instructions.",
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (!force) {
|
|
499
|
+
throw new Error(`Instruction file already exists with different content: ${target.destination}. Re-run with --force to overwrite.`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
await mkdir(path.dirname(target.destination), { recursive: true });
|
|
503
|
+
await writeFile(target.destination, content, "utf8");
|
|
504
|
+
return {
|
|
505
|
+
status: "installed",
|
|
506
|
+
skill: bundledSkillName,
|
|
507
|
+
host: target.host,
|
|
508
|
+
source,
|
|
509
|
+
destination: target.destination,
|
|
510
|
+
force,
|
|
511
|
+
installedFiles: [target.destination],
|
|
512
|
+
nextStep: "Restart or reload the host AI coding tool so it discovers the updated project instructions.",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function instructionInstallDestination(args) {
|
|
516
|
+
const target = flagValue(args, "target");
|
|
517
|
+
if (target !== "cursor-rules" && target !== "cursor-rule") {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
const repoRoot = path.resolve(positionalRepoPath(args));
|
|
521
|
+
return {
|
|
522
|
+
host: "cursor-rules",
|
|
523
|
+
destination: path.join(repoRoot, ".cursor", "rules", "social-plus-vise.mdc"),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function skillInstallDestination(args) {
|
|
527
|
+
const destArg = flagValue(args, "dest") ?? flagValue(args, "destination") ?? flagValue(args, "path");
|
|
528
|
+
if (destArg) {
|
|
529
|
+
const resolved = path.resolve(expandHome(destArg));
|
|
530
|
+
return path.basename(resolved) === bundledSkillName ? resolved : path.join(resolved, bundledSkillName);
|
|
531
|
+
}
|
|
532
|
+
const target = flagValue(args, "target") ?? "codex";
|
|
533
|
+
if (target === "codex") {
|
|
534
|
+
return path.join(os.homedir(), ".codex", "skills", bundledSkillName);
|
|
535
|
+
}
|
|
536
|
+
if (target === "claude") {
|
|
537
|
+
return path.join(os.homedir(), ".claude", "skills", bundledSkillName);
|
|
538
|
+
}
|
|
539
|
+
if (target === "claude-project") {
|
|
540
|
+
return path.resolve(positionalRepoPath(args), ".claude", "skills", bundledSkillName);
|
|
541
|
+
}
|
|
542
|
+
if (target === "agents" || target === "project") {
|
|
543
|
+
return path.resolve(positionalRepoPath(args), ".agents", "skills", bundledSkillName);
|
|
544
|
+
}
|
|
545
|
+
if (target === "cursor") {
|
|
546
|
+
return path.resolve(positionalRepoPath(args), ".cursor", "skills", bundledSkillName);
|
|
547
|
+
}
|
|
548
|
+
if (target === "vscode" || target === "copilot") {
|
|
549
|
+
return path.resolve(positionalRepoPath(args), ".github", "skills", bundledSkillName);
|
|
550
|
+
}
|
|
551
|
+
if (target === "custom") {
|
|
552
|
+
throw new Error("install-skill --target custom requires --dest <skillsRoot or exact skill directory>.");
|
|
553
|
+
}
|
|
554
|
+
throw new Error(`Unsupported skill target: ${target}. Supported targets: codex, claude, claude-project, agents, cursor, cursor-rules, vscode, copilot, custom.`);
|
|
555
|
+
}
|
|
556
|
+
function skillPathResult() {
|
|
557
|
+
return {
|
|
558
|
+
skill: bundledSkillName,
|
|
559
|
+
source: skillSourceDir(),
|
|
560
|
+
files: ["SKILL.md"],
|
|
561
|
+
installExamples: [
|
|
562
|
+
"vise install-skill --target codex",
|
|
563
|
+
"vise install-skill --target claude",
|
|
564
|
+
"vise install-skill --target claude-project .",
|
|
565
|
+
"vise install-skill --target agents .",
|
|
566
|
+
"vise install-skill --target cursor .",
|
|
567
|
+
"vise install-skill --target cursor-rules .",
|
|
568
|
+
"vise install-skill --target vscode .",
|
|
569
|
+
"vise install-skill --target copilot .",
|
|
570
|
+
"vise install-skill --dest ~/.codex/skills",
|
|
571
|
+
"vise print-skill",
|
|
572
|
+
],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function skillSourceDir() {
|
|
576
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
577
|
+
return path.resolve(moduleDir, "..", "skills", bundledSkillName);
|
|
578
|
+
}
|
|
579
|
+
async function copyDirectory(source, destination, force) {
|
|
580
|
+
const installedFiles = [];
|
|
581
|
+
await mkdir(destination, { recursive: true });
|
|
582
|
+
const entries = await readdir(source, { withFileTypes: true });
|
|
583
|
+
for (const entry of entries) {
|
|
584
|
+
const sourcePath = path.join(source, entry.name);
|
|
585
|
+
const destinationPath = path.join(destination, entry.name);
|
|
586
|
+
if (entry.isDirectory()) {
|
|
587
|
+
installedFiles.push(...(await copyDirectory(sourcePath, destinationPath, force)));
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (!entry.isFile()) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (await fileExists(destinationPath)) {
|
|
594
|
+
const [sourceContent, destinationContent] = await Promise.all([readFile(sourcePath, "utf8"), readFile(destinationPath, "utf8")]);
|
|
595
|
+
if (sourceContent === destinationContent) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (!force) {
|
|
599
|
+
throw new Error(`Skill file already exists with different content: ${destinationPath}. Re-run with --force to overwrite.`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
await copyFile(sourcePath, destinationPath);
|
|
603
|
+
installedFiles.push(destinationPath);
|
|
604
|
+
}
|
|
605
|
+
return installedFiles;
|
|
606
|
+
}
|
|
607
|
+
async function fileExists(filePath) {
|
|
608
|
+
try {
|
|
609
|
+
return (await stat(filePath)).isFile();
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function expandHome(input) {
|
|
616
|
+
if (input === "~") {
|
|
617
|
+
return os.homedir();
|
|
618
|
+
}
|
|
619
|
+
if (input.startsWith("~/")) {
|
|
620
|
+
return path.join(os.homedir(), input.slice(2));
|
|
621
|
+
}
|
|
622
|
+
return input;
|
|
623
|
+
}
|
|
624
|
+
function isHelpRequest(args) {
|
|
625
|
+
return args.includes("--help") || args.includes("-h");
|
|
626
|
+
}
|
|
627
|
+
function assertOnlyKnownFlags(args, allowed, commandName) {
|
|
628
|
+
const allowedSet = new Set(allowed);
|
|
629
|
+
const unknown = [];
|
|
630
|
+
for (const arg of args) {
|
|
631
|
+
if (!arg.startsWith("--")) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const flagName = arg.slice(2).split("=")[0];
|
|
635
|
+
if (!flagName) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (!allowedSet.has(flagName)) {
|
|
639
|
+
unknown.push(`--${flagName}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (unknown.length === 0) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const allowedList = allowed.length > 0 ? `Allowed flags: ${allowed.map((flag) => `--${flag}`).join(", ")}.` : "This command takes no flags.";
|
|
646
|
+
throw new Error(`${commandName} does not accept ${unknown.join(", ")}. ${allowedList} Run \`vise ${commandName} --help\` for usage.`);
|
|
647
|
+
}
|
|
648
|
+
function ciCheckResult(result) {
|
|
649
|
+
return {
|
|
650
|
+
...result,
|
|
651
|
+
ci: {
|
|
652
|
+
enabled: true,
|
|
653
|
+
passed: result.exitCode === 0,
|
|
654
|
+
exitCode: result.exitCode,
|
|
655
|
+
blockingResultStatuses: ["contract-drift", "blocked", "deterministic-failures", "needs-attestation"],
|
|
656
|
+
message: result.exitCode === 0 ? "Compliance green for CI." : `Compliance failed for CI with status: ${result.status}.`,
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function positionalRepoPath(args) {
|
|
661
|
+
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale"]);
|
|
662
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
663
|
+
const arg = args[index];
|
|
664
|
+
if (!arg) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (arg.startsWith("--")) {
|
|
668
|
+
const flagName = arg.slice(2).split("=")[0];
|
|
669
|
+
if (!arg.includes("=") && flagsWithValues.has(flagName)) {
|
|
670
|
+
index += 1;
|
|
671
|
+
}
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (arg.startsWith("-")) {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
return arg;
|
|
678
|
+
}
|
|
679
|
+
return ".";
|
|
680
|
+
}
|
|
681
|
+
function requiredPositionalText(args, message) {
|
|
682
|
+
const values = [];
|
|
683
|
+
const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale"]);
|
|
684
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
685
|
+
const arg = args[index];
|
|
686
|
+
if (!arg) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (arg.startsWith("--")) {
|
|
690
|
+
const flagName = arg.slice(2).split("=")[0];
|
|
691
|
+
if (!arg.includes("=") && flagsWithValues.has(flagName)) {
|
|
692
|
+
index += 1;
|
|
693
|
+
}
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!arg.startsWith("-")) {
|
|
697
|
+
values.push(arg);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const text = values.join(" ").trim();
|
|
701
|
+
if (!text) {
|
|
702
|
+
throw new Error(message);
|
|
703
|
+
}
|
|
704
|
+
return text;
|
|
705
|
+
}
|
|
706
|
+
function flagValue(args, name) {
|
|
707
|
+
const equalsPrefix = `--${name}=`;
|
|
708
|
+
const equalsArg = args.find((arg) => arg.startsWith(equalsPrefix));
|
|
709
|
+
if (equalsArg) {
|
|
710
|
+
return equalsArg.slice(equalsPrefix.length);
|
|
711
|
+
}
|
|
712
|
+
const index = args.indexOf(`--${name}`);
|
|
713
|
+
const value = index >= 0 ? args[index + 1] : undefined;
|
|
714
|
+
return value && !value.startsWith("-") ? value : undefined;
|
|
715
|
+
}
|
|
716
|
+
function flagValues(args, name) {
|
|
717
|
+
const values = [];
|
|
718
|
+
const equalsPrefix = `--${name}=`;
|
|
719
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
720
|
+
const arg = args[index];
|
|
721
|
+
if (arg?.startsWith(equalsPrefix)) {
|
|
722
|
+
values.push(arg.slice(equalsPrefix.length));
|
|
723
|
+
}
|
|
724
|
+
else if (arg === `--${name}`) {
|
|
725
|
+
const value = args[index + 1];
|
|
726
|
+
if (value && !value.startsWith("-")) {
|
|
727
|
+
values.push(value);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return values;
|
|
732
|
+
}
|
|
733
|
+
function requiredFlagValue(args, name, message) {
|
|
734
|
+
const value = flagValue(args, name);
|
|
735
|
+
if (!value) {
|
|
736
|
+
throw new Error(message);
|
|
737
|
+
}
|
|
738
|
+
return value;
|
|
739
|
+
}
|
|
740
|
+
function optionalNumberFlag(args, name) {
|
|
741
|
+
const value = flagValue(args, name);
|
|
742
|
+
if (!value) {
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
const number = Number(value);
|
|
746
|
+
if (!Number.isFinite(number)) {
|
|
747
|
+
throw new Error(`--${name} must be a number.`);
|
|
748
|
+
}
|
|
749
|
+
return number;
|
|
750
|
+
}
|
|
751
|
+
function hasFlag(args, name) {
|
|
752
|
+
return args.includes(`--${name}`);
|
|
753
|
+
}
|
|
754
|
+
function keyValueFlag(args, name) {
|
|
755
|
+
const pairs = flagValues(args, name);
|
|
756
|
+
const result = {};
|
|
757
|
+
for (const pair of pairs) {
|
|
758
|
+
const equalsIndex = pair.indexOf("=");
|
|
759
|
+
if (equalsIndex <= 0) {
|
|
760
|
+
throw new Error(`--${name} must be key=value (got "${pair}").`);
|
|
761
|
+
}
|
|
762
|
+
const key = pair.slice(0, equalsIndex).trim();
|
|
763
|
+
const value = pair.slice(equalsIndex + 1).trim();
|
|
764
|
+
if (!key || !value) {
|
|
765
|
+
throw new Error(`--${name} must be key=value (got "${pair}").`);
|
|
766
|
+
}
|
|
767
|
+
result[key] = value;
|
|
768
|
+
}
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
function requiredConfidence(args) {
|
|
772
|
+
const value = requiredFlagValue(args, "confidence", "attest requires --confidence high|medium|low.");
|
|
773
|
+
if (value === "high" || value === "medium" || value === "low") {
|
|
774
|
+
return value;
|
|
775
|
+
}
|
|
776
|
+
throw new Error("--confidence must be high, medium, or low.");
|
|
777
|
+
}
|
|
778
|
+
function requiredSigner(args) {
|
|
779
|
+
const value = requiredFlagValue(args, "signer", "attest requires --signer host-agent|human.");
|
|
780
|
+
if (value === "host-agent" || value === "human") {
|
|
781
|
+
return value;
|
|
782
|
+
}
|
|
783
|
+
throw new Error("--signer must be host-agent or human.");
|
|
784
|
+
}
|
|
785
|
+
async function evidenceFromArgs(args) {
|
|
786
|
+
const evidenceFile = flagValue(args, "evidence-file");
|
|
787
|
+
if (!evidenceFile) {
|
|
788
|
+
throw new Error("attest requires --evidence-file.");
|
|
789
|
+
}
|
|
790
|
+
const parsed = JSON.parse(await readFile(path.resolve(evidenceFile), "utf8"));
|
|
791
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
792
|
+
throw new Error("--evidence-file must contain a JSON object.");
|
|
793
|
+
}
|
|
794
|
+
return parsed;
|
|
795
|
+
}
|
|
796
|
+
function doctorResult() {
|
|
797
|
+
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
798
|
+
return {
|
|
799
|
+
package: packageName,
|
|
800
|
+
version: packageVersion,
|
|
801
|
+
status: nodeMajor >= 18 ? "ok" : "unsupported-node",
|
|
802
|
+
node: process.versions.node,
|
|
803
|
+
requiredNodeMajor: ">=18",
|
|
804
|
+
docsSource: process.env.SOCIAL_PLUS_DOCS_ROOT ? "local" : "hosted",
|
|
805
|
+
docsRoot: process.env.SOCIAL_PLUS_DOCS_ROOT,
|
|
806
|
+
docsBaseUrl: process.env.SOCIAL_PLUS_DOCS_BASE_URL ?? "https://learn.social.plus",
|
|
807
|
+
transport: "stdio",
|
|
808
|
+
tools: Array.from(tools.keys()),
|
|
809
|
+
};
|
|
810
|
+
}
|