@amityco/social-plus-vise 1.2.0 → 1.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/CHANGELOG.md +75 -2
- package/README.md +23 -10
- package/dist/capabilities.js +35 -4
- package/dist/entryState.js +71 -0
- package/dist/experience.js +70 -0
- package/dist/explore.js +51 -0
- package/dist/flow.js +382 -0
- package/dist/humanFormat.js +251 -0
- package/dist/intake.js +117 -0
- package/dist/intelligence/placement.js +2 -1
- package/dist/outcomes.js +141 -42
- package/dist/productExpectations.js +15 -0
- package/dist/requestReadiness.js +99 -0
- package/dist/server.js +675 -56
- package/dist/sidecar.js +5 -0
- package/dist/solutionPath.js +2 -2
- package/dist/tools/compliance.js +1014 -133
- package/dist/tools/creative.js +14 -12
- package/dist/tools/debug.js +83 -26
- package/dist/tools/design.js +344 -14
- package/dist/tools/experienceCompiler.js +1 -1
- package/dist/tools/experienceSensors.js +1 -1
- package/dist/tools/harness.js +13 -0
- package/dist/tools/integration.js +263 -90
- package/dist/tools/learning.js +1 -3
- package/dist/tools/project.js +987 -72
- package/dist/tools/sdkFacts.js +8 -1
- package/dist/tools/smoke.js +134 -0
- package/dist/tools/uxHarness.js +54 -6
- package/dist/uikitCustomization.js +22 -6
- package/dist/version.js +7 -3
- package/package.json +1 -1
- package/packages/intelligence/catalog/catalog.schema.json +1 -1
- package/packages/intelligence/catalog/experience-objects.json +2 -2
- package/packages/intelligence/catalog/variants.json +24 -0
- package/rules/event.yaml +3 -0
- package/rules/feed.yaml +251 -0
- package/rules/invitation.yaml +4 -0
- package/rules/live-data.yaml +110 -0
- package/rules/notification-tray.yaml +4 -0
- package/rules/poll.yaml +5 -0
- package/rules/sdk-lifecycle.yaml +559 -0
- package/rules/search.yaml +5 -0
- package/rules/security.yaml +12 -0
- package/rules/story.yaml +5 -0
- package/rules/user-blocking.yaml +5 -0
- package/skills/social-plus-vise/SKILL.md +163 -15
package/dist/server.js
CHANGED
|
@@ -3,11 +3,14 @@ import { copyFile, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { sidecarDir as sidecarPath } from "./sidecar.js";
|
|
7
|
+
import { blueprintDigest, readFlowState, readOmittedSurfaces, renderFlowBlueprint, runtimeReadinessForStages, writeFlowBlueprintHtml, writeFlowState, } from "./flow.js";
|
|
8
|
+
import { normalizeSolutionPathAnswer } from "./solutionPath.js";
|
|
6
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
11
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, experienceReportTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
|
|
10
|
-
import { designCheckTool, designExtractTool, designInitTokensTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
|
|
12
|
+
import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, experienceReportTool, initCompliance, initComplianceTool, recordBaseline, initEngagement, initEngagementTool, listRules, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
|
|
13
|
+
import { designCheckTool, designContrastTool, designExtractTool, designInitTokensTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
|
|
11
14
|
import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
|
|
12
15
|
import { compileExperienceTool } from "./tools/experienceCompiler.js";
|
|
13
16
|
import { experienceSensorsTool } from "./tools/experienceSensors.js";
|
|
@@ -17,12 +20,16 @@ import { recordLearningTool, showLearningTool } from "./tools/learning.js";
|
|
|
17
20
|
import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
|
|
18
21
|
import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
|
|
19
22
|
import { runSensorsTool } from "./tools/sensors.js";
|
|
23
|
+
import { smokeTool } from "./tools/smoke.js";
|
|
20
24
|
import { getSdkFactsTool } from "./tools/sdkFacts.js";
|
|
21
25
|
import { addBlockInstall, listRegistryBlocks, planBlockInstall, validateBlockInstall } from "./tools/blocks.js";
|
|
22
26
|
import { debugIssueTool, debugIssue } from "./tools/debug.js";
|
|
23
27
|
import { creativeAcceptTool, creativeBriefTool } from "./tools/creative.js";
|
|
24
28
|
import { uxHarnessTool } from "./tools/uxHarness.js";
|
|
25
|
-
import {
|
|
29
|
+
import { exploreRequest } from "./explore.js";
|
|
30
|
+
import { renderHuman, wantsHumanFormat } from "./humanFormat.js";
|
|
31
|
+
import { buildSource, packageName, packageRoot, packageVersion } from "./version.js";
|
|
32
|
+
const SUPPORT_URL = "mailto:support@social.plus";
|
|
26
33
|
const tools = new Map([
|
|
27
34
|
searchDocsTool,
|
|
28
35
|
getDocPageTool,
|
|
@@ -46,11 +53,13 @@ const tools = new Map([
|
|
|
46
53
|
showEngagementTool,
|
|
47
54
|
resolveRequestTool,
|
|
48
55
|
runSensorsTool,
|
|
56
|
+
smokeTool,
|
|
49
57
|
validateSetupTool,
|
|
50
58
|
suggestPatchTool,
|
|
51
59
|
debugIssueTool,
|
|
52
60
|
designExtractTool,
|
|
53
61
|
designCheckTool,
|
|
62
|
+
designContrastTool,
|
|
54
63
|
designPreviewTool,
|
|
55
64
|
designReferenceTool,
|
|
56
65
|
designInitTokensTool,
|
|
@@ -107,14 +116,14 @@ async function handleCli(args) {
|
|
|
107
116
|
console.log(helpText(args[1]));
|
|
108
117
|
return "exit";
|
|
109
118
|
}
|
|
110
|
-
if (command === "doctor" || command === "--doctor") {
|
|
111
|
-
console.log(JSON.stringify(doctorResult(), null, 2));
|
|
112
|
-
return "exit";
|
|
113
|
-
}
|
|
114
119
|
if (isHelpRequest(args)) {
|
|
115
120
|
console.log(helpText(command));
|
|
116
121
|
return "exit";
|
|
117
122
|
}
|
|
123
|
+
if (command === "doctor" || command === "--doctor") {
|
|
124
|
+
emitResult("doctor", doctorResult(), args);
|
|
125
|
+
return "exit";
|
|
126
|
+
}
|
|
118
127
|
try {
|
|
119
128
|
if (command === "install-skill" || command === "install_skill") {
|
|
120
129
|
console.log(JSON.stringify(await installSkill(args.slice(1)), null, 2));
|
|
@@ -286,15 +295,10 @@ async function handleCli(args) {
|
|
|
286
295
|
}
|
|
287
296
|
if (command === "plan" || command === "plan-integration") {
|
|
288
297
|
const input = await planCliInput(args);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
console.log(JSON.stringify(planSummary(payload), null, 2));
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
await printToolResult(planIntegrationTool, input);
|
|
297
|
-
}
|
|
298
|
+
const result = await planIntegrationTool.call(input);
|
|
299
|
+
const text = result.content.map((item) => item.text).join("\n");
|
|
300
|
+
const payload = JSON.parse(text);
|
|
301
|
+
emitResult("plan", hasFlag(args, "summary") ? planSummary(payload) : payload, args);
|
|
298
302
|
return "exit";
|
|
299
303
|
}
|
|
300
304
|
if (command === "plan-harness") {
|
|
@@ -318,7 +322,14 @@ async function handleCli(args) {
|
|
|
318
322
|
console.log(JSON.stringify(await completeWorkplanSurface(subArgs), null, 2));
|
|
319
323
|
return "exit";
|
|
320
324
|
}
|
|
321
|
-
|
|
325
|
+
if (sub === "trim") {
|
|
326
|
+
assertOnlyKnownFlags(subArgs, ["request", "surface", "surface-path", "answer", "reason"], "workplan trim");
|
|
327
|
+
const trim = await trimWorkplanSurface(subArgs);
|
|
328
|
+
console.log(JSON.stringify(trim.payload, null, 2));
|
|
329
|
+
process.exitCode = trim.exitCode;
|
|
330
|
+
return "exit";
|
|
331
|
+
}
|
|
332
|
+
console.error(`Unknown workplan subcommand: ${sub ?? "(none)"}. Expected "next", "status", "complete", or "trim".`);
|
|
322
333
|
process.exitCode = 1;
|
|
323
334
|
return "exit";
|
|
324
335
|
}
|
|
@@ -345,6 +356,21 @@ async function handleCli(args) {
|
|
|
345
356
|
}
|
|
346
357
|
return "exit";
|
|
347
358
|
}
|
|
359
|
+
if (command === "smoke") {
|
|
360
|
+
const printed = await printToolResult(smokeTool, {
|
|
361
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
362
|
+
logPath: flagValue(args, "log") ?? flagValue(args, "log-path"),
|
|
363
|
+
surface: flagValue(args, "surface"),
|
|
364
|
+
});
|
|
365
|
+
const status = toolResultStatus(printed);
|
|
366
|
+
if (status === "failed") {
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
}
|
|
369
|
+
else if (status === "no-config" || status === "no-log" || status === "no-surfaces") {
|
|
370
|
+
process.exitCode = 2;
|
|
371
|
+
}
|
|
372
|
+
return "exit";
|
|
373
|
+
}
|
|
348
374
|
if (command === "resolve") {
|
|
349
375
|
await printToolResult(resolveRequestTool, {
|
|
350
376
|
repoPath: positionalRepoPath(args.slice(1)),
|
|
@@ -418,21 +444,47 @@ async function handleCli(args) {
|
|
|
418
444
|
return "exit";
|
|
419
445
|
}
|
|
420
446
|
if (command === "init") {
|
|
421
|
-
assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer", "allow-unresolved-intake"], "init");
|
|
422
|
-
const
|
|
447
|
+
assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer", "allow-unresolved-intake", "baseline"], "init");
|
|
448
|
+
const initRepo = positionalRepoPath(args.slice(1));
|
|
449
|
+
const initRequest = requiredFlagValue(args, "request", "init requires --request.");
|
|
450
|
+
const initWorkplanPlan = await workplanPlan(initRepo, initRequest, args);
|
|
451
|
+
const { sequence: initSequence, omitted: initOmitted } = await activeBlueprint(initRepo, initRequest, initWorkplanPlan);
|
|
452
|
+
if (initSequence.length >= 2 && initWorkplanPlan.platform !== "unknown") {
|
|
453
|
+
const blueprintGate = await requireBlueprintConfirmation(initRepo, initRequest, initWorkplanPlan, initSequence, args, new Date().toISOString(), initOmitted);
|
|
454
|
+
if (blueprintGate) {
|
|
455
|
+
console.log(JSON.stringify(blueprintGate, null, 2));
|
|
456
|
+
process.exitCode = 7;
|
|
457
|
+
return "exit";
|
|
458
|
+
}
|
|
459
|
+
const workplanInitGate = await requireNextWorkplanSurfaceInit(initRepo, initRequest, initSequence, keyValueFlag(args, "answer"));
|
|
460
|
+
if (workplanInitGate) {
|
|
461
|
+
console.log(JSON.stringify(workplanInitGate, null, 2));
|
|
462
|
+
process.exitCode = 7;
|
|
463
|
+
return "exit";
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const result = await initCompliance(initRepo, initRequest, flagValue(args, "surface") ?? flagValue(args, "surface-path"), keyValueFlag(args, "answer"), { allowUnresolvedIntake: hasFlag(args, "allow-unresolved-intake") });
|
|
423
467
|
if (result.status === "needs-clarification" && typeof result.exitCode === "number") {
|
|
424
468
|
process.exitCode = result.exitCode;
|
|
425
469
|
}
|
|
470
|
+
if (hasFlag(args, "baseline") && result.status === "initialized") {
|
|
471
|
+
result.baseline_recorded = await recordBaseline(initRepo);
|
|
472
|
+
}
|
|
426
473
|
console.log(JSON.stringify(result, null, 2));
|
|
427
474
|
return "exit";
|
|
428
475
|
}
|
|
429
476
|
if (command === "check") {
|
|
430
|
-
assertOnlyKnownFlags(args, ["ci"], "check");
|
|
431
|
-
const result = await checkCompliance(positionalRepoPath(args.slice(1)));
|
|
432
|
-
|
|
477
|
+
assertOnlyKnownFlags(args, ["ci", "format", "new-only"], "check");
|
|
478
|
+
const result = await checkCompliance(positionalRepoPath(args.slice(1)), { newOnly: hasFlag(args, "new-only") });
|
|
479
|
+
emitResult("check", hasFlag(args, "ci") ? ciCheckResult(result) : result, args);
|
|
433
480
|
process.exitCode = result.exitCode;
|
|
434
481
|
return "exit";
|
|
435
482
|
}
|
|
483
|
+
if (command === "baseline") {
|
|
484
|
+
assertOnlyKnownFlags(args, [], "baseline");
|
|
485
|
+
console.log(JSON.stringify(await recordBaseline(positionalRepoPath(args.slice(1))), null, 2));
|
|
486
|
+
return "exit";
|
|
487
|
+
}
|
|
436
488
|
if (command === "sync") {
|
|
437
489
|
assertOnlyKnownFlags(args, [], "sync");
|
|
438
490
|
console.log(JSON.stringify(await syncCompliance(positionalRepoPath(args.slice(1))), null, 2));
|
|
@@ -452,13 +504,26 @@ async function handleCli(args) {
|
|
|
452
504
|
return "exit";
|
|
453
505
|
}
|
|
454
506
|
if (command === "explain") {
|
|
455
|
-
assertOnlyKnownFlags(args, [], "explain");
|
|
456
|
-
|
|
507
|
+
assertOnlyKnownFlags(args, ["format"], "explain");
|
|
508
|
+
const explainRuleId = positionalValues(args.slice(1))[0];
|
|
509
|
+
const payload = explainRuleId ? await explainRule(explainRuleId) : await listRules();
|
|
510
|
+
emitResult("explain", payload, args);
|
|
511
|
+
return "exit";
|
|
512
|
+
}
|
|
513
|
+
if (command === "explore") {
|
|
514
|
+
assertOnlyKnownFlags(args, ["request", "platform", "format"], "explore");
|
|
515
|
+
const request = flagValue(args, "request") ??
|
|
516
|
+
requiredPositionalText(args.slice(1), 'explore requires a request, e.g. `vise explore "add a social feed"`.');
|
|
517
|
+
emitResult("explore", exploreRequest(request, flagValue(args, "platform")), args);
|
|
457
518
|
return "exit";
|
|
458
519
|
}
|
|
459
520
|
if (command === "status") {
|
|
460
|
-
assertOnlyKnownFlags(args, [], "status");
|
|
461
|
-
|
|
521
|
+
assertOnlyKnownFlags(args, ["format", "new-only"], "status");
|
|
522
|
+
const result = await statusCompliance(positionalRepoPath(args.slice(1)), { newOnly: hasFlag(args, "new-only") });
|
|
523
|
+
emitResult("status", result, args);
|
|
524
|
+
if (typeof result.exitCode === "number") {
|
|
525
|
+
process.exitCode = result.exitCode;
|
|
526
|
+
}
|
|
462
527
|
return "exit";
|
|
463
528
|
}
|
|
464
529
|
if (command === "engagement") {
|
|
@@ -512,6 +577,17 @@ async function handleCli(args) {
|
|
|
512
577
|
await printToolResult(designCheckTool, { repoPath: positionalRepoPath(subArgs) });
|
|
513
578
|
return "exit";
|
|
514
579
|
}
|
|
580
|
+
if (sub === "contrast") {
|
|
581
|
+
assertOnlyKnownFlags(subArgs, [], "design contrast");
|
|
582
|
+
const colors = subArgs.filter((arg) => !arg.startsWith("-"));
|
|
583
|
+
if (colors.length < 2) {
|
|
584
|
+
console.error("design contrast requires two colours: vise design contrast <foreground> <background> (e.g. vise design contrast '#5a6477' '#0e1726').");
|
|
585
|
+
process.exitCode = 1;
|
|
586
|
+
return "exit";
|
|
587
|
+
}
|
|
588
|
+
await printToolResult(designContrastTool, { foreground: colors[0], background: colors[1] });
|
|
589
|
+
return "exit";
|
|
590
|
+
}
|
|
515
591
|
if (sub === "preview") {
|
|
516
592
|
assertOnlyKnownFlags(subArgs, ["reference", "no-write"], "design preview");
|
|
517
593
|
await printToolResult(designPreviewTool, {
|
|
@@ -538,7 +614,7 @@ async function handleCli(args) {
|
|
|
538
614
|
});
|
|
539
615
|
return "exit";
|
|
540
616
|
}
|
|
541
|
-
console.error(`Unknown design subcommand: ${sub ?? "(none)"}. Expected "extract", "check", "preview", "reference", or "init-tokens".`);
|
|
617
|
+
console.error(`Unknown design subcommand: ${sub ?? "(none)"}. Expected "extract", "check", "contrast", "preview", "reference", or "init-tokens".`);
|
|
542
618
|
process.exitCode = 1;
|
|
543
619
|
return "exit";
|
|
544
620
|
}
|
|
@@ -595,7 +671,9 @@ Re-plan with collected answers (repeat --answer for each intake question):
|
|
|
595
671
|
vise plan . --request "Add a social feed" \\
|
|
596
672
|
--answer feed_scope=community \\
|
|
597
673
|
--answer feed_target=existing\\ communityId \\
|
|
598
|
-
--answer target_screen_or_route=app/feed/page.tsx
|
|
674
|
+
--answer target_screen_or_route=app/feed/page.tsx
|
|
675
|
+
|
|
676
|
+
Output is JSON by default (for agents/CI). Add --format human for a readable plan summary.`;
|
|
599
677
|
}
|
|
600
678
|
if (command === "creative") {
|
|
601
679
|
return `${packageName} creative
|
|
@@ -610,8 +688,8 @@ Usage:
|
|
|
610
688
|
vise creative [repoPath] --request "Add engagement" --prototype ./prototype.html
|
|
611
689
|
vise creative [repoPath] --request "Add engagement" --ranking-preview
|
|
612
690
|
vise creative [repoPath] --request "Add engagement" --no-write
|
|
613
|
-
vise creative accept [repoPath] --variant community-first
|
|
614
|
-
vise creative accept [repoPath] --variant none --rationale "No variant fits X; closest is Y" --closest discovery-first
|
|
691
|
+
vise creative accept [repoPath] --variant community-first --rationale "Always-on community participation is the primary destination." --confidence high
|
|
692
|
+
vise creative accept [repoPath] --variant none --rationale "No variant fits X; closest is Y" --confidence high --closest discovery-first
|
|
615
693
|
|
|
616
694
|
Output:
|
|
617
695
|
Writes sp-vise/creative-brief.json and sp-vise/creative-brief.md unless --no-write is set.
|
|
@@ -695,12 +773,17 @@ Usage:
|
|
|
695
773
|
vise workplan next [repoPath] --request "Add feed, comments, chat, and profile"
|
|
696
774
|
vise workplan status [repoPath] --request "Add feed, comments, chat, and profile"
|
|
697
775
|
vise workplan complete [repoPath] --request "Add feed, comments, chat, and profile" --surface feed
|
|
776
|
+
vise workplan trim [repoPath] --request "..." --surface chat --reason "goal never asked for messaging"
|
|
698
777
|
|
|
699
778
|
Notes:
|
|
700
779
|
next/status re-run the broad plan, ignore any feature_surface answer, and combine it
|
|
701
780
|
with sp-vise/workplan.json progress. complete records a host-agent progress marker;
|
|
702
781
|
run vise check, vise sync, vise validate, and vise run-sensors before marking a
|
|
703
|
-
surface complete
|
|
782
|
+
surface complete.
|
|
783
|
+
trim deliberately omits a companion surface (surface-level scope-omit): --reason is
|
|
784
|
+
required, the surface must not already be built, and at least two surfaces must remain.
|
|
785
|
+
A trim changes the blueprint, so the sign-off gate re-fires — the human re-confirms the
|
|
786
|
+
trimmed blueprint (which names what was dropped) before any surface proceeds.`;
|
|
704
787
|
}
|
|
705
788
|
if (command === "search-docs" || command === "search_docs") {
|
|
706
789
|
return `${packageName} search-docs
|
|
@@ -789,6 +872,23 @@ Run detected project command sensors such as typecheck, test, build, Flutter, or
|
|
|
789
872
|
|
|
790
873
|
Usage:
|
|
791
874
|
vise run-sensors [repoPath] [--dry-run] [--include "npm test"] [--timeout-ms 120000]`;
|
|
875
|
+
}
|
|
876
|
+
if (command === "smoke") {
|
|
877
|
+
return `${packageName} smoke
|
|
878
|
+
|
|
879
|
+
Assess a captured runtime mount-smoke log into a pass/fail verdict and record runtime-smoke
|
|
880
|
+
evidence. Catches the session-establish race (a live-data query fired before the SDK session is
|
|
881
|
+
established) that static \`vise check\` cannot see. Declare collection surfaces in sp-vise/smoke.json,
|
|
882
|
+
emit VISE_SMOKE markers from the mounted SDK query lifecycle, cold-launch into the social route,
|
|
883
|
+
wait for the collection to resolve, capture the runtime log, then:
|
|
884
|
+
|
|
885
|
+
Usage:
|
|
886
|
+
vise smoke [repoPath] --log <captured-log> [--surface <id>]
|
|
887
|
+
|
|
888
|
+
Android: adb logcat -c before launch, then adb logcat -d -v time | grep VISE_SMOKE > sp-vise/evidence/runtime-smoke.log
|
|
889
|
+
iOS: xcrun simctl spawn <device> log show --style compact --last 3m --predicate 'eventMessage CONTAINS "VISE_SMOKE"' > sp-vise/evidence/runtime-smoke.log
|
|
890
|
+
|
|
891
|
+
See docs/RUNTIME_SMOKE.md.`;
|
|
792
892
|
}
|
|
793
893
|
if (command === "resolve") {
|
|
794
894
|
return `${packageName} resolve
|
|
@@ -844,7 +944,9 @@ Usage:
|
|
|
844
944
|
Check the current source and recorded attestations against the compliance contract. Read-only.
|
|
845
945
|
|
|
846
946
|
Usage:
|
|
847
|
-
vise check [repoPath] [--ci]
|
|
947
|
+
vise check [repoPath] [--ci] [--format human]
|
|
948
|
+
|
|
949
|
+
Output is JSON by default (for agents/CI). Add --format human for a readable verdict summary.`;
|
|
848
950
|
}
|
|
849
951
|
if (command === "sync") {
|
|
850
952
|
return `${packageName} sync
|
|
@@ -860,23 +962,45 @@ Usage:
|
|
|
860
962
|
Record a host-agent or local-human attestation for one compliance rule.
|
|
861
963
|
|
|
862
964
|
Usage:
|
|
863
|
-
vise attest [repoPath] --rule
|
|
965
|
+
vise attest [repoPath] --rule typescript.client.region --confidence high --signer host-agent --evidence-file evidence.json --rationale "Why this rule is satisfied."`;
|
|
864
966
|
}
|
|
865
967
|
if (command === "explain") {
|
|
866
968
|
return `${packageName} explain
|
|
867
969
|
|
|
868
|
-
Explain one compliance rule.
|
|
970
|
+
Explain one compliance rule, or list every valid rule id when run with no id.
|
|
971
|
+
|
|
972
|
+
Usage:
|
|
973
|
+
vise explain [--format human] List all rule ids (public + contract) with where each applies
|
|
974
|
+
vise explain typescript.client.region [--format human]`;
|
|
975
|
+
}
|
|
976
|
+
if (command === "explore") {
|
|
977
|
+
return `${packageName} explore
|
|
978
|
+
|
|
979
|
+
Discover what social.plus offers for a request — before any project or credentials exist. Read-only:
|
|
980
|
+
maps the request to a candidate outcome (or lists the full menu when it can't), and for each shows the
|
|
981
|
+
capabilities involved, the canonical docs, and the command to start. No project, no API key, no writes.
|
|
869
982
|
|
|
870
983
|
Usage:
|
|
871
|
-
vise
|
|
984
|
+
vise explore "add a social feed" [--platform typescript] [--format human]`;
|
|
985
|
+
}
|
|
986
|
+
if (command === "doctor") {
|
|
987
|
+
return `${packageName} doctor
|
|
988
|
+
|
|
989
|
+
Print install diagnostics (Node version, transport, docs source, registered tools).
|
|
990
|
+
Read-only. JSON by default; --format human for a readable summary.
|
|
991
|
+
|
|
992
|
+
Usage:
|
|
993
|
+
vise doctor [--format human]
|
|
994
|
+
|
|
995
|
+
If diagnostics report a problem, see ${SUPPORT_URL}`;
|
|
872
996
|
}
|
|
873
997
|
if (command === "status") {
|
|
874
998
|
return `${packageName} status
|
|
875
999
|
|
|
876
|
-
Print a compact compliance summary.
|
|
1000
|
+
Print a compact compliance summary. JSON by default; --format human for a readable verdict.
|
|
877
1001
|
|
|
878
1002
|
Usage:
|
|
879
|
-
vise status [repoPath]`;
|
|
1003
|
+
vise status [repoPath] [--format human]`;
|
|
880
1004
|
}
|
|
881
1005
|
if (command === "design") {
|
|
882
1006
|
return `${packageName} design
|
|
@@ -897,7 +1021,8 @@ extract Build a graded design contract and write it to sp-vise/design-contrac
|
|
|
897
1021
|
With --from-project (no external prototype), derive the contract from the host
|
|
898
1022
|
project's OWN design system: CSS custom properties (incl. shadcn :root and
|
|
899
1023
|
Tailwind v4 @theme), TS/JS token modules, inline tailwind configs, Android
|
|
900
|
-
colors.xml/dimens.xml,
|
|
1024
|
+
colors.xml/dimens.xml, Jetpack Compose Kotlin themes (Color(0xFF..) in
|
|
1025
|
+
Theme.kt/Color.kt), Flutter Color(0x..), and iOS .xcassets/.colorset +
|
|
901
1026
|
Swift colors. Reference values (var()/theme()/calc()) are skipped, so a
|
|
902
1027
|
var-mapped config contributes nothing rather than wrong tokens.
|
|
903
1028
|
check Advisory, non-blocking report on how closely the project's UI code
|
|
@@ -925,14 +1050,17 @@ contractual record of which broad-social outcomes are in scope for this project.
|
|
|
925
1050
|
|
|
926
1051
|
Usage:
|
|
927
1052
|
vise engagement init [repoPath] --tier <free|pro|partner> --customer-id <id> --scope <outcome,...>
|
|
928
|
-
vise engagement init [repoPath] --scope feed,
|
|
1053
|
+
vise engagement init [repoPath] --scope add-feed,add-community --target-completion 2026-12-31 \\
|
|
929
1054
|
--reviewer-name "Jane Doe" --reviewer-email jane@example.com --evidence-upload-consent
|
|
930
1055
|
vise engagement show [repoPath]
|
|
931
1056
|
|
|
932
1057
|
Flags (init):
|
|
933
1058
|
--tier <free|pro|partner> Engagement tier (validated).
|
|
934
1059
|
--customer-id <id> Customer identifier recorded in the artifact.
|
|
935
|
-
--scope <outcome,...> In-scope
|
|
1060
|
+
--scope <outcome,...> In-scope outcomes, comma-separated. One or more of: setup-sdk,
|
|
1061
|
+
setup-push, setup-live-data, add-feed, add-comments, add-chat,
|
|
1062
|
+
add-community, add-follow, add-moderation, add-notifications,
|
|
1063
|
+
troubleshoot, validate-setup.
|
|
936
1064
|
--target-completion <date> Target completion date, YYYY-MM-DD.
|
|
937
1065
|
--reviewer-name <name> Human reviewer recorded on the engagement.
|
|
938
1066
|
--reviewer-email <email> Human reviewer contact.
|
|
@@ -950,7 +1078,7 @@ Usage:
|
|
|
950
1078
|
vise print-skill Print bundled skill markdown
|
|
951
1079
|
vise inspect [repoPath] Inspect platform and design signals
|
|
952
1080
|
vise creative [repoPath] --request "..." Create an Engagement Intelligence brief
|
|
953
|
-
vise creative accept [repoPath] --variant <id|none>
|
|
1081
|
+
vise creative accept [repoPath] --variant <id|none> --rationale "..." --confidence high
|
|
954
1082
|
vise ux-harness [repoPath] Generate UX Harness expectations from creative selection
|
|
955
1083
|
vise experience-report [repoPath] Write the advisory dimensioned Experience Report
|
|
956
1084
|
vise experience compile [repoPath] Compile selected variant into implementation artifacts
|
|
@@ -958,10 +1086,12 @@ Usage:
|
|
|
958
1086
|
vise learning record [repoPath] Record a local-only learning event
|
|
959
1087
|
vise learning show [repoPath] Show local learning summary
|
|
960
1088
|
vise debug [repoPath] --error ... Debug an SDK-specific runtime error and emit a repair brief
|
|
1089
|
+
vise explore "<request>" Discover what social.plus offers for a request (no project/credentials needed)
|
|
961
1090
|
vise plan [repoPath] --request "..." Create an implementation plan
|
|
962
1091
|
vise workplan next [repoPath] --request "..." Get the next broad-social surface to implement
|
|
963
|
-
vise init [repoPath] --request "..." Initialize compliance sidecar
|
|
964
|
-
vise check [repoPath] Check compliance contract
|
|
1092
|
+
vise init [repoPath] --request "..." Initialize compliance sidecar (add --baseline on a brownfield app)
|
|
1093
|
+
vise check [repoPath] Check compliance contract (add --new-only to gate on findings since the baseline)
|
|
1094
|
+
vise baseline [repoPath] Snapshot pre-existing findings so check --new-only gates only new ones
|
|
965
1095
|
vise sync [repoPath] Persist deterministic-pass evidence
|
|
966
1096
|
vise attest [repoPath] --rule ... Record a compliance attestation
|
|
967
1097
|
vise explain <ruleId> Explain one compliance rule
|
|
@@ -999,6 +1129,16 @@ async function printToolResult(tool, input) {
|
|
|
999
1129
|
console.log(text);
|
|
1000
1130
|
return { result, text };
|
|
1001
1131
|
}
|
|
1132
|
+
function emitResult(command, payload, args) {
|
|
1133
|
+
if (wantsHumanFormat(flagValue(args, "format"))) {
|
|
1134
|
+
const human = renderHuman(command, payload);
|
|
1135
|
+
if (human !== null) {
|
|
1136
|
+
console.log(human);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1141
|
+
}
|
|
1002
1142
|
async function planCliInput(args) {
|
|
1003
1143
|
const subArgs = args.slice(1);
|
|
1004
1144
|
const requestFromFlag = flagValue(args, "request");
|
|
@@ -1365,11 +1505,263 @@ function assertOnlyKnownFlags(args, allowed, commandName) {
|
|
|
1365
1505
|
const allowedList = allowed.length > 0 ? `Allowed flags: ${allowed.map((flag) => `--${flag}`).join(", ")}.` : "This command takes no flags.";
|
|
1366
1506
|
throw new Error(`${commandName} does not accept ${unknown.join(", ")}. ${allowedList} Run \`vise ${commandName} --help\` for usage.`);
|
|
1367
1507
|
}
|
|
1508
|
+
function blueprintStagesForSequence(sequence) {
|
|
1509
|
+
return sequence.map((surface) => ({ id: surface.id, outcome: surface.outcome, label: surface.label }));
|
|
1510
|
+
}
|
|
1511
|
+
async function activeBlueprint(repoRoot, request, plan) {
|
|
1512
|
+
const planSequence = socialWorkplanSequence(plan);
|
|
1513
|
+
const full = planSequence.length
|
|
1514
|
+
? planSequence
|
|
1515
|
+
: await socialWorkplanSequenceFromSignedFlow(repoRoot, request);
|
|
1516
|
+
const omitted = await readOmittedSurfaces(repoRoot, request);
|
|
1517
|
+
const omittedIds = new Set(omitted.map((item) => item.id));
|
|
1518
|
+
return { sequence: full.filter((surface) => !omittedIds.has(surface.id)), omitted };
|
|
1519
|
+
}
|
|
1520
|
+
async function socialWorkplanSequenceFromSignedFlow(repoRoot, request) {
|
|
1521
|
+
const state = await readFlowState(repoRoot);
|
|
1522
|
+
if (state?.request !== request) {
|
|
1523
|
+
return [];
|
|
1524
|
+
}
|
|
1525
|
+
const stages = state.blueprint?.stages ?? [];
|
|
1526
|
+
if (stages.length <= 1) {
|
|
1527
|
+
return [];
|
|
1528
|
+
}
|
|
1529
|
+
return stages.map((stage, index) => {
|
|
1530
|
+
const id = stage.id || stage.outcome;
|
|
1531
|
+
const commonArgs = `. --request ${shellQuoteForCommand(request)} --answer feature_surface=${id}`;
|
|
1532
|
+
return {
|
|
1533
|
+
id,
|
|
1534
|
+
order: index + 1,
|
|
1535
|
+
outcome: stage.outcome,
|
|
1536
|
+
label: stage.label ?? id,
|
|
1537
|
+
planCommand: `vise plan ${commonArgs}`,
|
|
1538
|
+
initCommand: `vise init ${commonArgs}`,
|
|
1539
|
+
intake: {
|
|
1540
|
+
status: "from-signed-blueprint",
|
|
1541
|
+
questions: [],
|
|
1542
|
+
remainingBlocking: 0,
|
|
1543
|
+
},
|
|
1544
|
+
validation: ["validate_setup", "run_sensors"],
|
|
1545
|
+
};
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
async function requireBlueprintConfirmation(repoRoot, request, plan, sequence, args, now, omitted = []) {
|
|
1549
|
+
if (sequence.length === 0) {
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
const stages = blueprintStagesForSequence(sequence);
|
|
1553
|
+
const runtimeReadiness = runtimeReadinessForStages(stages);
|
|
1554
|
+
const existing = await readFlowState(repoRoot);
|
|
1555
|
+
const answers = keyValueFlag(args, "answer");
|
|
1556
|
+
const existingBlueprint = existing?.request === request ? existing.blueprint : undefined;
|
|
1557
|
+
const existingPath = existingBlueprint?.path ?? null;
|
|
1558
|
+
const existingPathDecisionConfirmed = Boolean(existingBlueprint?.path_decision_confirmed === true || existingBlueprint?.confirmed_digest);
|
|
1559
|
+
const persistedPath = existingPathDecisionConfirmed ? existingPath : null;
|
|
1560
|
+
const explicitPathAnswer = normalizeSolutionPathAnswer(answers.solution_path);
|
|
1561
|
+
const pathDecidedness = plan.entryState?.pathDecidedness ?? null;
|
|
1562
|
+
const pathNeedsDecision = (pathDecidedness === "undecided" || pathDecidedness === "conflict") && !persistedPath && !explicitPathAnswer;
|
|
1563
|
+
const pathForDigest = explicitPathAnswer ?? persistedPath ?? plan.solutionPath?.recommendation ?? null;
|
|
1564
|
+
const design = plan.designReview?.digest ?? plan.designContract?.digest ?? null;
|
|
1565
|
+
const digest = blueprintDigest({ path: pathForDigest, stages, design });
|
|
1566
|
+
const confirmedByAnswer = answers.blueprint_confirmation === digest;
|
|
1567
|
+
const alreadyConfirmed = existing?.request === request && existing?.blueprint?.confirmed_digest === digest;
|
|
1568
|
+
const persist = (confirmed) => {
|
|
1569
|
+
const pathDecisionConfirmed = confirmed || Boolean(existingPathDecisionConfirmed && existingPath && existingPath === pathForDigest);
|
|
1570
|
+
return {
|
|
1571
|
+
schema_version: 1,
|
|
1572
|
+
vise_version: packageVersion,
|
|
1573
|
+
generated_at: existing?.generated_at ?? now,
|
|
1574
|
+
updated_at: now,
|
|
1575
|
+
request,
|
|
1576
|
+
blueprint: {
|
|
1577
|
+
digest,
|
|
1578
|
+
confirmed_digest: confirmed ? digest : null,
|
|
1579
|
+
...(confirmed ? { confirmed_at: alreadyConfirmed ? existing?.blueprint?.confirmed_at ?? now : now } : {}),
|
|
1580
|
+
stages,
|
|
1581
|
+
...(pathForDigest ? { path: pathForDigest, path_decision_confirmed: pathDecisionConfirmed } : {}),
|
|
1582
|
+
...(omitted.length ? { omitted } : {}),
|
|
1583
|
+
runtime_readiness: runtimeReadiness,
|
|
1584
|
+
},
|
|
1585
|
+
};
|
|
1586
|
+
};
|
|
1587
|
+
if (alreadyConfirmed) {
|
|
1588
|
+
await writeFlowState(repoRoot, persist(true));
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
const experience = await blueprintExperience(repoRoot);
|
|
1592
|
+
const blueprintRel = (fsPath) => repoRelativePath(repoRoot, fsPath);
|
|
1593
|
+
const approvalChecklist = (pathDecision) => [
|
|
1594
|
+
{
|
|
1595
|
+
id: "solution_path",
|
|
1596
|
+
label: "Solution path",
|
|
1597
|
+
status: pathDecision ? "choose-one-before-signoff" : "confirm",
|
|
1598
|
+
value: pathForDigest ?? null,
|
|
1599
|
+
...(pathDecision ? { question: pathDecision.question, options: pathDecision.options } : {}),
|
|
1600
|
+
},
|
|
1601
|
+
{
|
|
1602
|
+
id: "experience",
|
|
1603
|
+
label: "EI experience",
|
|
1604
|
+
status: experience?.selected ? "confirm-or-switch" : "not-provided",
|
|
1605
|
+
value: experience?.selected ?? null,
|
|
1606
|
+
alternatives: experience?.alternatives ?? [],
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
id: "surfaces",
|
|
1610
|
+
label: "Journey surfaces",
|
|
1611
|
+
status: "confirm-in-scope",
|
|
1612
|
+
value: stages,
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
id: "design",
|
|
1616
|
+
label: "Design source",
|
|
1617
|
+
status: design ? "confirm-contract" : "no-accepted-design-contract",
|
|
1618
|
+
value: design,
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id: "runtime_readiness",
|
|
1622
|
+
label: "Runtime readiness",
|
|
1623
|
+
status: "review-and-ask-runtime-proof-consent",
|
|
1624
|
+
value: runtimeReadiness,
|
|
1625
|
+
},
|
|
1626
|
+
...(omitted.length
|
|
1627
|
+
? [{
|
|
1628
|
+
id: "omitted_surfaces",
|
|
1629
|
+
label: "Trimmed surfaces",
|
|
1630
|
+
status: "confirm-out-of-scope",
|
|
1631
|
+
value: omitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })),
|
|
1632
|
+
}]
|
|
1633
|
+
: []),
|
|
1634
|
+
];
|
|
1635
|
+
const unattendedBlueprintStop = (pathDecision) => ({
|
|
1636
|
+
status: "blocked",
|
|
1637
|
+
mode: "handoff-required",
|
|
1638
|
+
reason: "Blueprint sign-off is an alignment decision about what to build.",
|
|
1639
|
+
instruction: "If the user is not available, stop and return the blueprint path, approvalChecklist, and required answers. Do not choose the solution path, sign the digest, initialize a surface, or record completion on the user's behalf.",
|
|
1640
|
+
allowedActions: [
|
|
1641
|
+
"open or summarize sp-vise/flow-blueprint.html",
|
|
1642
|
+
"collect an explicit solution_path choice when required",
|
|
1643
|
+
"collect explicit approval or requested trims/switches",
|
|
1644
|
+
],
|
|
1645
|
+
resumeWhen: [
|
|
1646
|
+
...(pathDecision ? ["the user chooses solution_path=sdk|uikit|hybrid"] : []),
|
|
1647
|
+
"the user approves the experience and journey, or asks for a trim/switch",
|
|
1648
|
+
"the command is re-run with the required --answer values",
|
|
1649
|
+
],
|
|
1650
|
+
});
|
|
1651
|
+
if (pathNeedsDecision) {
|
|
1652
|
+
const decision = plan.solutionPath?.decision;
|
|
1653
|
+
const pathDecision = {
|
|
1654
|
+
question: decision?.question ?? "Should this build use social.plus UIKit components, a direct SDK implementation, or a hybrid path?",
|
|
1655
|
+
options: decision?.options ?? [
|
|
1656
|
+
"uikit: use social.plus UIKit for standard social surfaces and theme/customize it",
|
|
1657
|
+
"sdk: build the experience directly with the social.plus SDK",
|
|
1658
|
+
"hybrid: use UIKit for standard surfaces and SDK/custom code for differentiated app-layer behavior",
|
|
1659
|
+
],
|
|
1660
|
+
};
|
|
1661
|
+
const fsPath = await writeFlowBlueprintHtml(repoRoot, renderFlowBlueprint({
|
|
1662
|
+
request,
|
|
1663
|
+
path: null,
|
|
1664
|
+
stages,
|
|
1665
|
+
design,
|
|
1666
|
+
digest,
|
|
1667
|
+
omitted,
|
|
1668
|
+
experience: experience ?? undefined,
|
|
1669
|
+
pathDecision,
|
|
1670
|
+
runtimeReadiness,
|
|
1671
|
+
}));
|
|
1672
|
+
await writeFlowState(repoRoot, persist(false));
|
|
1673
|
+
return {
|
|
1674
|
+
status: "needs-blueprint-confirmation",
|
|
1675
|
+
request,
|
|
1676
|
+
blueprintDigest: digest,
|
|
1677
|
+
blueprintPath: blueprintRel(fsPath),
|
|
1678
|
+
blueprint: { path: null, stages },
|
|
1679
|
+
pathDecision: { ...pathDecision, required: true },
|
|
1680
|
+
approvalChecklist: approvalChecklist(pathDecision),
|
|
1681
|
+
unattended: unattendedBlueprintStop(pathDecision),
|
|
1682
|
+
runtimeReadiness,
|
|
1683
|
+
...(experience ? { experience } : {}),
|
|
1684
|
+
...(omitted.length ? { omitted: omitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })) } : {}),
|
|
1685
|
+
nextStep: `A multi-surface build blueprint was produced, but the solution path is undecided. Open ${blueprintRel(fsPath)} and review the journey, scope, design, and runtime readiness with the customer, and present the path options (${pathDecision.options.map((o) => o.split(":")[0]).join(" / ")}). ` +
|
|
1686
|
+
`First re-run with --answer solution_path=<sdk|uikit|hybrid>, then sign off the resulting blueprint. ` +
|
|
1687
|
+
(experience?.selected ? `The recommended experience is "${experience.selected.title}"${experience.alternatives.length ? ` (alternatives: ${experience.alternatives.map((a) => a.title).join(", ")} — switch with \`vise creative accept . --variant <id> --rationale "<why>" --confidence high\`)` : ""}. ` : "") +
|
|
1688
|
+
"Surfaces cannot be started or recorded until the blueprint is signed off; prepare identity, natural target discovery, tenant data, and Console prerequisites before feeding the workplan to a dogfood agent. Ask the user before running runtime proof because browser/simulator/emulator proof can consume local resources and agent tokens.",
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
if (confirmedByAnswer) {
|
|
1692
|
+
await writeFlowState(repoRoot, persist(true));
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
const fsPath = await writeFlowBlueprintHtml(repoRoot, renderFlowBlueprint({
|
|
1696
|
+
request,
|
|
1697
|
+
path: pathForDigest,
|
|
1698
|
+
stages,
|
|
1699
|
+
design,
|
|
1700
|
+
digest,
|
|
1701
|
+
omitted,
|
|
1702
|
+
experience: experience ?? undefined,
|
|
1703
|
+
runtimeReadiness,
|
|
1704
|
+
}));
|
|
1705
|
+
await writeFlowState(repoRoot, persist(false));
|
|
1706
|
+
return {
|
|
1707
|
+
status: "needs-blueprint-confirmation",
|
|
1708
|
+
request,
|
|
1709
|
+
blueprintDigest: digest,
|
|
1710
|
+
blueprintPath: blueprintRel(fsPath),
|
|
1711
|
+
blueprint: { path: pathForDigest, stages },
|
|
1712
|
+
approvalChecklist: approvalChecklist(),
|
|
1713
|
+
unattended: unattendedBlueprintStop(),
|
|
1714
|
+
runtimeReadiness,
|
|
1715
|
+
...(experience ? { experience } : {}),
|
|
1716
|
+
...(omitted.length
|
|
1717
|
+
? { omitted: omitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })) }
|
|
1718
|
+
: {}),
|
|
1719
|
+
nextStep: `A multi-surface build blueprint was produced. Open ${blueprintRel(fsPath)} and review the path, journey, scope, design, and runtime readiness with the customer. ` +
|
|
1720
|
+
(experience?.selected && experience.alternatives.length
|
|
1721
|
+
? `The recommended experience is "${experience.selected.title}" (alternatives: ${experience.alternatives.map((a) => a.title).join(", ")} — to build a different one, re-run \`vise creative accept . --variant <id> --rationale "<why>" --confidence high\` and re-confirm). `
|
|
1722
|
+
: "") +
|
|
1723
|
+
(omitted.length
|
|
1724
|
+
? `This blueprint has ${omitted.length} deliberately trimmed surface(s): ${omitted.map((item) => `${item.id} (${item.reason})`).join("; ")}. Confirm the human approved dropping them. `
|
|
1725
|
+
: "") +
|
|
1726
|
+
`After they approve, re-run this command with --answer blueprint_confirmation=${digest}. ` +
|
|
1727
|
+
"Surfaces cannot be started or recorded until the blueprint is signed off; prepare identity, natural target discovery, tenant data, and Console prerequisites before feeding the workplan to a dogfood agent. Ask the user before running runtime proof because browser/simulator/emulator proof can consume local resources and agent tokens.",
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
async function blueprintExperience(repoRoot) {
|
|
1731
|
+
const dir = sidecarPath(repoRoot);
|
|
1732
|
+
let candidates = [];
|
|
1733
|
+
try {
|
|
1734
|
+
const brief = JSON.parse(await readFile(path.join(dir, "creative-brief.json"), "utf8"));
|
|
1735
|
+
candidates = Array.isArray(brief.candidateSolutions) ? brief.candidateSolutions : [];
|
|
1736
|
+
}
|
|
1737
|
+
catch {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
if (candidates.length === 0)
|
|
1741
|
+
return null;
|
|
1742
|
+
let selected = null;
|
|
1743
|
+
try {
|
|
1744
|
+
const sel = JSON.parse(await readFile(path.join(dir, "creative-selection.json"), "utf8"));
|
|
1745
|
+
const id = sel.selectedVariant?.id;
|
|
1746
|
+
if (typeof id === "string") {
|
|
1747
|
+
selected = { id, title: typeof sel.selectedVariant?.title === "string" ? sel.selectedVariant.title : id };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
catch {
|
|
1751
|
+
}
|
|
1752
|
+
const alternatives = candidates
|
|
1753
|
+
.filter((c) => c != null && typeof c.id === "string" && c.id !== selected?.id)
|
|
1754
|
+
.slice(0, 4)
|
|
1755
|
+
.map((c) => ({ id: c.id, title: typeof c.title === "string" ? c.title : c.id }));
|
|
1756
|
+
if (!selected && alternatives.length === 0)
|
|
1757
|
+
return null;
|
|
1758
|
+
return { selected, alternatives };
|
|
1759
|
+
}
|
|
1368
1760
|
async function workplanStatus(args) {
|
|
1369
1761
|
const repoRoot = path.resolve(positionalRepoPath(args));
|
|
1370
1762
|
const request = requiredFlagValue(args, "request", "workplan next/status requires --request.");
|
|
1371
1763
|
const plan = await workplanPlan(repoRoot, request, args);
|
|
1372
|
-
const sequence =
|
|
1764
|
+
const { sequence, omitted } = await activeBlueprint(repoRoot, request, plan);
|
|
1373
1765
|
const progress = await readWorkplanProgress(repoRoot);
|
|
1374
1766
|
const completed = progress?.request === request ? progress.completed : [];
|
|
1375
1767
|
const completedIds = new Set(completed.map((item) => item.surface));
|
|
@@ -1382,12 +1774,19 @@ async function workplanStatus(args) {
|
|
|
1382
1774
|
progressPath: workplanProgressPath(repoRoot),
|
|
1383
1775
|
};
|
|
1384
1776
|
}
|
|
1777
|
+
const blueprintGate = await requireBlueprintConfirmation(repoRoot, request, plan, sequence, args, new Date().toISOString(), omitted);
|
|
1778
|
+
if (blueprintGate) {
|
|
1779
|
+
return blueprintGate;
|
|
1780
|
+
}
|
|
1781
|
+
const runtimeReadiness = runtimeReadinessForStages(blueprintStagesForSequence(sequence));
|
|
1782
|
+
const nextSurfaceReadiness = nextSurface ? runtimeReadiness.surfaces.find((surface) => surface.id === nextSurface.id) : undefined;
|
|
1385
1783
|
return {
|
|
1386
1784
|
status: nextSurface ? "next-surface" : "complete",
|
|
1387
1785
|
request,
|
|
1388
1786
|
progressPath: workplanProgressPath(repoRoot),
|
|
1389
1787
|
creativeContext: plan.socialWorkplan?.creativeContext ?? plan.creativeContext,
|
|
1390
1788
|
uxHarness: plan.socialWorkplan?.uxHarness ?? plan.uxHarness,
|
|
1789
|
+
runtimeReadiness,
|
|
1391
1790
|
completed,
|
|
1392
1791
|
sequence: sequence.map((surface) => ({
|
|
1393
1792
|
id: surface.id,
|
|
@@ -1396,6 +1795,9 @@ async function workplanStatus(args) {
|
|
|
1396
1795
|
label: surface.label,
|
|
1397
1796
|
completed: completedIds.has(surface.id),
|
|
1398
1797
|
})),
|
|
1798
|
+
...(omitted.length
|
|
1799
|
+
? { omitted: omitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })) }
|
|
1800
|
+
: {}),
|
|
1399
1801
|
nextSurface: nextSurface
|
|
1400
1802
|
? {
|
|
1401
1803
|
id: nextSurface.id,
|
|
@@ -1405,9 +1807,12 @@ async function workplanStatus(args) {
|
|
|
1405
1807
|
intake: nextSurface.intake,
|
|
1406
1808
|
validation: nextSurface.validation,
|
|
1407
1809
|
uxHarness: nextSurface.uxHarness,
|
|
1810
|
+
runtimeReadiness: nextSurfaceReadiness,
|
|
1408
1811
|
commands: {
|
|
1409
1812
|
plan: nextSurface.planCommand,
|
|
1410
|
-
init:
|
|
1813
|
+
init: plan.designReview?.status === "needs-confirmation"
|
|
1814
|
+
? `${nextSurface.initCommand} --answer design_contract_confirmation=yes`
|
|
1815
|
+
: nextSurface.initCommand,
|
|
1411
1816
|
check: "vise check .",
|
|
1412
1817
|
sync: "vise sync .",
|
|
1413
1818
|
validate: "vise validate .",
|
|
@@ -1417,23 +1822,101 @@ async function workplanStatus(args) {
|
|
|
1417
1822
|
}
|
|
1418
1823
|
: undefined,
|
|
1419
1824
|
nextStep: nextSurface
|
|
1420
|
-
? `Resolve the ${nextSurface.id} surface intake, run its focused plan/init command, implement it, then run check/sync/validate/sensors before
|
|
1825
|
+
? `Resolve the ${nextSurface.id} surface intake, confirm runtime readiness and natural target discovery for that next pending surface, run only its focused plan/init command, implement it, then run check/sync/validate/sensors and workplan complete before moving to any later surface. Before runtime proof, ask the user whether to spend browser/simulator/emulator resources; if they decline, record the expected unproven runtime risks instead of claiming product proof.`
|
|
1421
1826
|
: "All workplan surfaces are marked complete. Run one final `vise check .`, `vise validate .`, and `vise run-sensors .` before handoff.",
|
|
1422
1827
|
};
|
|
1423
1828
|
}
|
|
1829
|
+
async function trimWorkplanSurface(args) {
|
|
1830
|
+
const repoRoot = path.resolve(positionalRepoPath(args));
|
|
1831
|
+
const request = requiredFlagValue(args, "request", "workplan trim requires --request.");
|
|
1832
|
+
const surfaceId = requiredFlagValue(args, "surface", "workplan trim requires --surface <surface-id>.");
|
|
1833
|
+
const reason = (flagValue(args, "reason") ?? "").trim();
|
|
1834
|
+
if (!reason) {
|
|
1835
|
+
throw new Error('workplan trim requires --reason "<why this companion is out of scope>". An omission without a recorded reason is invalid.');
|
|
1836
|
+
}
|
|
1837
|
+
const plan = await workplanPlan(repoRoot, request, args);
|
|
1838
|
+
const full = socialWorkplanSequence(plan);
|
|
1839
|
+
if (full.length < 2) {
|
|
1840
|
+
throw new Error("This request did not produce a multi-surface blueprint, so there is nothing to trim. Use `vise plan` for the single-outcome flow.");
|
|
1841
|
+
}
|
|
1842
|
+
const surface = full.find((item) => item.id === surfaceId);
|
|
1843
|
+
if (!surface) {
|
|
1844
|
+
throw new Error(`Surface "${surfaceId}" is not in this blueprint. Trimmable surfaces: ${full.map((item) => item.id).join(", ")}.`);
|
|
1845
|
+
}
|
|
1846
|
+
const existingOmitted = await readOmittedSurfaces(repoRoot, request);
|
|
1847
|
+
if (existingOmitted.some((item) => item.id === surfaceId)) {
|
|
1848
|
+
return {
|
|
1849
|
+
payload: {
|
|
1850
|
+
status: "already-trimmed",
|
|
1851
|
+
request,
|
|
1852
|
+
surface: surfaceId,
|
|
1853
|
+
omitted: existingOmitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })),
|
|
1854
|
+
nextStep: `Surface "${surfaceId}" is already trimmed; no change. Run \`vise workplan status\` to see the active blueprint.`,
|
|
1855
|
+
},
|
|
1856
|
+
exitCode: 0,
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
const progress = await readWorkplanProgress(repoRoot);
|
|
1860
|
+
const completedIds = new Set(progress?.request === request ? progress.completed.map((item) => item.surface) : []);
|
|
1861
|
+
if (completedIds.has(surfaceId)) {
|
|
1862
|
+
throw new Error(`Surface "${surfaceId}" has already been built and recorded; it cannot be trimmed. Trim applies only to not-yet-built companion surfaces.`);
|
|
1863
|
+
}
|
|
1864
|
+
const newOmitted = [
|
|
1865
|
+
...existingOmitted,
|
|
1866
|
+
{ id: surface.id, outcome: surface.outcome, ...(surface.label ? { label: surface.label } : {}), reason, omitted_at: new Date().toISOString() },
|
|
1867
|
+
];
|
|
1868
|
+
const newOmittedIds = new Set(newOmitted.map((item) => item.id));
|
|
1869
|
+
const activeAfter = full.filter((item) => !newOmittedIds.has(item.id));
|
|
1870
|
+
if (activeAfter.length < 2) {
|
|
1871
|
+
throw new Error(`Trimming "${surfaceId}" would leave ${activeAfter.length} active surface(s). The alignment gate covers multi-surface journeys; at least 2 must remain. To build a single surface, re-select that experience via \`vise creative\` instead of trimming a journey down to one.`);
|
|
1872
|
+
}
|
|
1873
|
+
const gateArgs = stripAnswerKey(args, "blueprint_confirmation");
|
|
1874
|
+
const gate = await requireBlueprintConfirmation(repoRoot, request, plan, activeAfter, gateArgs, new Date().toISOString(), newOmitted);
|
|
1875
|
+
if (!gate) {
|
|
1876
|
+
return {
|
|
1877
|
+
payload: {
|
|
1878
|
+
status: "trimmed",
|
|
1879
|
+
request,
|
|
1880
|
+
surface: surfaceId,
|
|
1881
|
+
reason,
|
|
1882
|
+
note: "Omission recorded; the blueprint is already in a confirmed state for the resulting digest.",
|
|
1883
|
+
omitted: newOmitted.map((item) => ({ id: item.id, outcome: item.outcome, reason: item.reason })),
|
|
1884
|
+
},
|
|
1885
|
+
exitCode: 0,
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
return {
|
|
1889
|
+
payload: {
|
|
1890
|
+
status: "trimmed",
|
|
1891
|
+
request,
|
|
1892
|
+
trimmed: { id: surface.id, outcome: surface.outcome, reason },
|
|
1893
|
+
...gate,
|
|
1894
|
+
nextStep: `Recorded the deliberate omission of "${surfaceId}" (${reason}). The blueprint changed and must be re-signed before any surface proceeds. ` +
|
|
1895
|
+
(typeof gate.nextStep === "string" ? gate.nextStep : ""),
|
|
1896
|
+
},
|
|
1897
|
+
exitCode: 7,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1424
1900
|
async function completeWorkplanSurface(args) {
|
|
1425
1901
|
const repoRoot = path.resolve(positionalRepoPath(args));
|
|
1426
1902
|
const request = requiredFlagValue(args, "request", "workplan complete requires --request.");
|
|
1427
1903
|
const surfaceId = requiredFlagValue(args, "surface", "workplan complete requires --surface <surface-id>.");
|
|
1428
1904
|
const note = flagValue(args, "note");
|
|
1429
1905
|
const plan = await workplanPlan(repoRoot, request, args);
|
|
1430
|
-
const sequence =
|
|
1906
|
+
const { sequence, omitted } = await activeBlueprint(repoRoot, request, plan);
|
|
1431
1907
|
const surface = sequence.find((item) => item.id === surfaceId);
|
|
1432
1908
|
if (!surface) {
|
|
1909
|
+
if (omitted.some((item) => item.id === surfaceId)) {
|
|
1910
|
+
throw new Error(`Surface "${surfaceId}" was deliberately trimmed from this blueprint and is not built (recorded omission). Un-trim is not yet supported; to build it, re-select an experience that includes it via \`vise creative\`.`);
|
|
1911
|
+
}
|
|
1433
1912
|
throw new Error(`Surface "${surfaceId}" is not in this social workplan. Available surfaces: ${sequence.map((item) => item.id).join(", ") || "(none)"}.`);
|
|
1434
1913
|
}
|
|
1435
1914
|
const now = new Date().toISOString();
|
|
1436
|
-
const
|
|
1915
|
+
const blueprintGate = await requireBlueprintConfirmation(repoRoot, request, plan, sequence, args, now, omitted);
|
|
1916
|
+
if (blueprintGate) {
|
|
1917
|
+
return blueprintGate;
|
|
1918
|
+
}
|
|
1919
|
+
const check = await greenWorkplanCheck(repoRoot, surfaceId, surface.outcome);
|
|
1437
1920
|
const snapshot = await writeWorkplanSurfaceSnapshot(repoRoot, surfaceId, check, now);
|
|
1438
1921
|
const existing = await readWorkplanProgress(repoRoot);
|
|
1439
1922
|
const base = existing?.request === request
|
|
@@ -1475,15 +1958,45 @@ async function completeWorkplanSurface(args) {
|
|
|
1475
1958
|
};
|
|
1476
1959
|
await writeWorkplanProgress(repoRoot, progress);
|
|
1477
1960
|
const status = await workplanStatus(args);
|
|
1961
|
+
const sequenceStatus = workplanSequenceStatus(status);
|
|
1962
|
+
const completedSurfaces = sequenceStatus.filter((item) => item.completed);
|
|
1963
|
+
const pendingSurfaces = sequenceStatus.filter((item) => !item.completed);
|
|
1964
|
+
const finalHandoffAllowed = pendingSurfaces.length === 0;
|
|
1965
|
+
const nextSurface = status.nextSurface;
|
|
1966
|
+
const pendingSummary = pendingSurfaces.map((item) => item.id).join(", ");
|
|
1478
1967
|
return {
|
|
1479
1968
|
status: "recorded",
|
|
1480
1969
|
progressPath: workplanProgressPath(repoRoot),
|
|
1481
1970
|
recorded: progress.completed.find((item) => item.surface === surfaceId),
|
|
1482
|
-
|
|
1483
|
-
|
|
1971
|
+
completedSurfaces,
|
|
1972
|
+
pendingSurfaces,
|
|
1973
|
+
workplanComplete: finalHandoffAllowed,
|
|
1974
|
+
finalHandoffAllowed,
|
|
1975
|
+
...(pendingSurfaces.length
|
|
1976
|
+
? {
|
|
1977
|
+
blockedHandoffReason: `The broad social request is not complete. Pending surfaces remain: ${pendingSummary}.`,
|
|
1978
|
+
}
|
|
1979
|
+
: {}),
|
|
1980
|
+
nextSurface,
|
|
1981
|
+
nextStep: pendingSurfaces.length
|
|
1982
|
+
? `Recorded "${surfaceId}" only. Do not hand off the broad request as complete: pending surfaces remain (${pendingSummary}). Continue with the next pending surface using the focused init/check/complete commands from \`nextSurface.commands\`, then rerun \`vise workplan status\` until \`workplanComplete\` is true.`
|
|
1983
|
+
: status.nextStep,
|
|
1484
1984
|
};
|
|
1485
1985
|
}
|
|
1486
|
-
|
|
1986
|
+
function workplanSequenceStatus(status) {
|
|
1987
|
+
const sequence = Array.isArray(status.sequence) ? status.sequence : [];
|
|
1988
|
+
return sequence
|
|
1989
|
+
.filter((item) => item != null && typeof item === "object")
|
|
1990
|
+
.map((item) => ({
|
|
1991
|
+
id: typeof item.id === "string" ? item.id : "",
|
|
1992
|
+
outcome: typeof item.outcome === "string" ? item.outcome : undefined,
|
|
1993
|
+
label: typeof item.label === "string" ? item.label : undefined,
|
|
1994
|
+
order: typeof item.order === "number" ? item.order : undefined,
|
|
1995
|
+
completed: item.completed === true,
|
|
1996
|
+
}))
|
|
1997
|
+
.filter((item) => item.id.length > 0);
|
|
1998
|
+
}
|
|
1999
|
+
async function greenWorkplanCheck(repoRoot, surfaceId, expectedOutcome) {
|
|
1487
2000
|
let check;
|
|
1488
2001
|
try {
|
|
1489
2002
|
check = await checkCompliance(repoRoot);
|
|
@@ -1492,6 +2005,9 @@ async function greenWorkplanCheck(repoRoot, surfaceId) {
|
|
|
1492
2005
|
const message = error instanceof Error ? error.message : String(error);
|
|
1493
2006
|
throw new Error(`Cannot mark "${surfaceId}" complete because the current compliance check could not run. Run the focused \`vise init\` command for this surface first, then \`vise check .\`. ${message}`);
|
|
1494
2007
|
}
|
|
2008
|
+
if (expectedOutcome && check.outcome !== expectedOutcome) {
|
|
2009
|
+
throw new Error(`Cannot mark "${surfaceId}" complete: the sp-vise sidecar is currently scoped to outcome "${check.outcome}", not this surface's outcome "${expectedOutcome}". Each workplan surface has its own sidecar scope — run this surface's focused \`vise init\` command (it sets \`--answer feature_surface=${surfaceId}\`) and \`vise check .\` before recording it. (The check reported "${check.status}" only because it ran against the other surface's contract.)`);
|
|
2010
|
+
}
|
|
1495
2011
|
if (check.status !== "green") {
|
|
1496
2012
|
throw new Error(`Cannot mark "${surfaceId}" complete because \`vise check\` returned "${check.status}" (exit ${check.exitCode}). Resolve the check result before recording workplan progress.`);
|
|
1497
2013
|
}
|
|
@@ -1541,6 +2057,86 @@ function socialWorkplanSequence(plan) {
|
|
|
1541
2057
|
}
|
|
1542
2058
|
return workplan.sequence;
|
|
1543
2059
|
}
|
|
2060
|
+
async function requireNextWorkplanSurfaceInit(repoRoot, request, sequence, answers) {
|
|
2061
|
+
const progress = await readWorkplanProgress(repoRoot);
|
|
2062
|
+
const completedIds = new Set(progress?.request === request ? progress.completed.map((item) => item.surface) : []);
|
|
2063
|
+
const nextSurface = sequence.find((surface) => !completedIds.has(surface.id));
|
|
2064
|
+
const selectedSurface = resolveWorkplanSurfaceAnswer(sequence, answers.feature_surface);
|
|
2065
|
+
if (!nextSurface) {
|
|
2066
|
+
return {
|
|
2067
|
+
status: "workplan-complete",
|
|
2068
|
+
exitCode: 7,
|
|
2069
|
+
request,
|
|
2070
|
+
sequence: sequence.map((surface) => ({
|
|
2071
|
+
id: surface.id,
|
|
2072
|
+
order: surface.order,
|
|
2073
|
+
outcome: surface.outcome,
|
|
2074
|
+
label: surface.label,
|
|
2075
|
+
completed: completedIds.has(surface.id),
|
|
2076
|
+
})),
|
|
2077
|
+
nextStep: "All active blueprint surfaces are already recorded complete. Run one final `vise check .`, `vise validate .`, and `vise run-sensors .` instead of initializing another surface.",
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
if (!answers.feature_surface || !selectedSurface) {
|
|
2081
|
+
return {
|
|
2082
|
+
status: "needs-workplan-surface",
|
|
2083
|
+
exitCode: 7,
|
|
2084
|
+
request,
|
|
2085
|
+
expectedSurface: {
|
|
2086
|
+
id: nextSurface.id,
|
|
2087
|
+
order: nextSurface.order,
|
|
2088
|
+
outcome: nextSurface.outcome,
|
|
2089
|
+
label: nextSurface.label,
|
|
2090
|
+
},
|
|
2091
|
+
sequence: sequence.map((surface) => ({
|
|
2092
|
+
id: surface.id,
|
|
2093
|
+
order: surface.order,
|
|
2094
|
+
outcome: surface.outcome,
|
|
2095
|
+
label: surface.label,
|
|
2096
|
+
completed: completedIds.has(surface.id),
|
|
2097
|
+
})),
|
|
2098
|
+
nextStep: `This request has a signed multi-surface blueprint. Initialize the next pending surface with: ${nextSurface.initCommand}. ` +
|
|
2099
|
+
"After implementing it and getting `vise check .` green, run the matching `vise workplan complete` command before moving to the next surface.",
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
if (selectedSurface.id !== nextSurface.id) {
|
|
2103
|
+
return {
|
|
2104
|
+
status: "needs-workplan-order",
|
|
2105
|
+
exitCode: 7,
|
|
2106
|
+
request,
|
|
2107
|
+
attemptedSurface: {
|
|
2108
|
+
id: selectedSurface.id,
|
|
2109
|
+
order: selectedSurface.order,
|
|
2110
|
+
outcome: selectedSurface.outcome,
|
|
2111
|
+
label: selectedSurface.label,
|
|
2112
|
+
},
|
|
2113
|
+
expectedSurface: {
|
|
2114
|
+
id: nextSurface.id,
|
|
2115
|
+
order: nextSurface.order,
|
|
2116
|
+
outcome: nextSurface.outcome,
|
|
2117
|
+
label: nextSurface.label,
|
|
2118
|
+
},
|
|
2119
|
+
sequence: sequence.map((surface) => ({
|
|
2120
|
+
id: surface.id,
|
|
2121
|
+
order: surface.order,
|
|
2122
|
+
outcome: surface.outcome,
|
|
2123
|
+
label: surface.label,
|
|
2124
|
+
completed: completedIds.has(surface.id),
|
|
2125
|
+
})),
|
|
2126
|
+
nextStep: `Do not jump to "${selectedSurface.id}" before "${nextSurface.id}" is complete. Run: ${nextSurface.initCommand}. ` +
|
|
2127
|
+
"Each active blueprint surface must be implemented, checked, synced, and recorded in order so the final app does not collapse to one narrow social surface.",
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
function resolveWorkplanSurfaceAnswer(sequence, answer) {
|
|
2133
|
+
const normalized = (answer ?? "").trim().toLowerCase();
|
|
2134
|
+
if (!normalized)
|
|
2135
|
+
return undefined;
|
|
2136
|
+
return sequence.find((surface) => surface.id.toLowerCase() === normalized)
|
|
2137
|
+
?? sequence.find((surface) => surface.label.toLowerCase() === normalized)
|
|
2138
|
+
?? sequence.find((surface) => surface.label.toLowerCase().includes(normalized));
|
|
2139
|
+
}
|
|
1544
2140
|
async function readWorkplanProgress(repoRoot) {
|
|
1545
2141
|
try {
|
|
1546
2142
|
return JSON.parse(await readFile(workplanProgressPath(repoRoot), "utf8"));
|
|
@@ -1564,9 +2160,6 @@ function workplanSurfaceSnapshotDir(repoRoot, surfaceId) {
|
|
|
1564
2160
|
}
|
|
1565
2161
|
return path.join(sidecarPath(repoRoot), "workplan-snapshots", safeSurfaceId);
|
|
1566
2162
|
}
|
|
1567
|
-
function sidecarPath(repoRoot) {
|
|
1568
|
-
return path.join(repoRoot, "sp-vise");
|
|
1569
|
-
}
|
|
1570
2163
|
function repoRelativePath(repoRoot, filePath) {
|
|
1571
2164
|
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
|
1572
2165
|
}
|
|
@@ -1584,7 +2177,14 @@ function ciCheckResult(result) {
|
|
|
1584
2177
|
enabled: true,
|
|
1585
2178
|
passed: result.exitCode === 0,
|
|
1586
2179
|
exitCode: result.exitCode,
|
|
1587
|
-
blockingResultStatuses: [
|
|
2180
|
+
blockingResultStatuses: [
|
|
2181
|
+
"contract-drift",
|
|
2182
|
+
"blocked",
|
|
2183
|
+
"deterministic-failures",
|
|
2184
|
+
"needs-attestation",
|
|
2185
|
+
"completeness-gap",
|
|
2186
|
+
"selected-capability-failures",
|
|
2187
|
+
],
|
|
1588
2188
|
message: result.exitCode === 0 ? "Compliance green for CI." : `Compliance failed for CI with status: ${result.status}.`,
|
|
1589
2189
|
},
|
|
1590
2190
|
};
|
|
@@ -1594,7 +2194,7 @@ function positionalRepoPath(args) {
|
|
|
1594
2194
|
return values[0] ?? ".";
|
|
1595
2195
|
}
|
|
1596
2196
|
function positionalValues(args) {
|
|
1597
|
-
const flagsWithValues = new Set(["request", "requirements", "prototype", "variant", "variant-id", "brief", "brief-path", "surface", "surface-path", "platform", "capability", "surface-dir", "format", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference", "registry", "block", "package-source", "note", "kind", "sentiment", "metric"]);
|
|
2197
|
+
const flagsWithValues = new Set(["request", "requirements", "prototype", "variant", "variant-id", "brief", "brief-path", "surface", "surface-path", "platform", "capability", "surface-dir", "format", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale", "repo", "reference", "registry", "block", "package-source", "note", "kind", "sentiment", "metric", "tier", "customer-id", "scope", "target-completion", "reviewer-name", "reviewer-email"]);
|
|
1598
2198
|
const values = [];
|
|
1599
2199
|
for (let index = 0; index < args.length; index += 1) {
|
|
1600
2200
|
const arg = args[index];
|
|
@@ -1720,6 +2320,22 @@ function hasFlag(args, name) {
|
|
|
1720
2320
|
}
|
|
1721
2321
|
return false;
|
|
1722
2322
|
}
|
|
2323
|
+
function stripAnswerKey(args, key) {
|
|
2324
|
+
const out = [];
|
|
2325
|
+
const valuePrefix = `${key}=`;
|
|
2326
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2327
|
+
const arg = args[index];
|
|
2328
|
+
if (arg === "--answer" && typeof args[index + 1] === "string" && args[index + 1].startsWith(valuePrefix)) {
|
|
2329
|
+
index += 1;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (arg?.startsWith("--answer=") && arg.slice("--answer=".length).startsWith(valuePrefix)) {
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
out.push(arg);
|
|
2336
|
+
}
|
|
2337
|
+
return out;
|
|
2338
|
+
}
|
|
1723
2339
|
function keyValueFlag(args, name) {
|
|
1724
2340
|
const pairs = flagValues(args, name);
|
|
1725
2341
|
const result = {};
|
|
@@ -1767,6 +2383,8 @@ function doctorResult() {
|
|
|
1767
2383
|
return {
|
|
1768
2384
|
package: packageName,
|
|
1769
2385
|
version: packageVersion,
|
|
2386
|
+
buildSource,
|
|
2387
|
+
packageRoot,
|
|
1770
2388
|
status: nodeMajor >= 18 ? "ok" : "unsupported-node",
|
|
1771
2389
|
node: process.versions.node,
|
|
1772
2390
|
requiredNodeMajor: ">=18",
|
|
@@ -1775,5 +2393,6 @@ function doctorResult() {
|
|
|
1775
2393
|
docsBaseUrl: process.env.SOCIAL_PLUS_DOCS_BASE_URL ?? "https://learn.social.plus",
|
|
1776
2394
|
transport: "stdio",
|
|
1777
2395
|
tools: Array.from(tools.keys()),
|
|
2396
|
+
support: SUPPORT_URL,
|
|
1778
2397
|
};
|
|
1779
2398
|
}
|