@amityco/social-plus-vise 1.1.1 → 1.2.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 CHANGED
@@ -4,6 +4,19 @@ All notable changes to `@amityco/social-plus-vise` are documented in this file.
4
4
 
5
5
  The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 1.2.0 — 2026-06-18
8
+
9
+ Adds advisory **solution-path / UIKit routing** and a fail-closed fix to the SwiftPM manifest sensor. No change to `vise check` exit codes, compliance rules, or sidecar **formats**; the one behavior change is the SwiftPM-timeout outcome noted under Changed.
10
+
11
+ ### Added
12
+ - **Solution-path & UIKit routing (advisory).** `vise plan` emits `solutionPath` (`sdk` · `uikit` · `hybrid` · `needs-decision`) so an agent pauses for `--answer solution_path=…` before building the wrong layer, cleanly separating the two integrator postures (SDK/custom-UI vs UIKit/prebuilt-UI). When UIKit is in play it also emits `uikitCustomization`, recommending the lowest-effort tier that meets the goal (Dynamic UI → Component Styling → Localization → Behavior Overrides → Fork and Extend → SDK Custom UI). Both are **advisory only** — they never change outcome classification, compliance rules, the sidecar schema, or `vise check` exit codes. Documented under "Solution path & UIKit routing" in the README; the two postures are described in `docs/PERSONAS.md`.
13
+ - **Real-build readiness checks** surfaced alongside the routing signals.
14
+
15
+ ### Changed
16
+ - **SwiftPM manifest sensor: timeout is now RED (fail-closed), not a skip.** A manifest-parse timeout previously read as a clean green; for a pure-SwiftPM iOS project the manifest sensor is the only sensor, so a stall masqueraded as success. It now reports `timed-out` and the CLI exits non-zero. **Behavior note for consumers:** a network-restricted CI runner that cannot resolve a remote SwiftPM dependency will now go RED on this sensor rather than silently passing — intended fail-closed behavior, not a regression.
17
+ - **Advisory routing engines hardened (two adversarial-verification batches, 28 fixes, every fix mutation-verified).** `recommendSolutionPath` / `recommendUIKitCustomization` precision: UIKit-rejection coverage now spans modal/contracted negators (won't/can't/cannot), intervening verbs ("don't want to use UIKit", "without using UIKit"), and "anything but UIKit" — an explicit rejection routes SDK-first instead of (previously) recommending UIKit; prebuilt idioms are anchored to a UI/social noun; `off-the-shelf` is anchored (so "off-the-shelf analytics" stays SDK-first); localization no longer fires on a programming/design "language"; backend feature capabilities (e.g. livestreaming) surface as a decision rather than a Dynamic-UI default. Advisory-only quality improvements; no gate behavior changes.
18
+ - **Personas split into two integrator postures** (AI-Assisted SDK/Custom-UI Integrator and AI-Assisted UIKit/Prebuilt-UI Adopter), tied to a deterministic behavior contract in the test suite.
19
+
7
20
  ## 1.1.1 — 2026-06-16
8
21
 
9
22
  Docs-only patch; no CLI, MCP, rule, or sidecar behavior changes.
package/README.md CHANGED
@@ -47,7 +47,7 @@ It turns the request into a grounded plan, records a local contract under `sp-vi
47
47
 
48
48
  > 🔒 **Your source code never leaves your machine.** Vise fetches only the public social.plus docs and the SDK's published version on npm — never your code, file contents, or search queries. `VISE_DOCS_OFFLINE=1` runs fully offline.
49
49
 
50
- Vise can also run **ahead** of that loop: an advisory [Engagement Intelligence](#engagement-intelligence) layer turns a product goal into a candidate engagement design — *what to build*, not only *whether you built it right*.
50
+ Vise can also run **ahead** of that loop: an advisory [Engagement Intelligence](#engagement-intelligence) layer turns a product goal into multiple candidate engagement strategies — *what you might build*, not only *whether you built it right*.
51
51
 
52
52
  > **Why "Vise"?** A bench vise holds the workpiece steady so the craftsman's hands are free to shape it. Vise clamps the integration to the real docs, the real project structure, and the real compliance rules so the agent can focus on building instead of guessing.
53
53
 
@@ -98,12 +98,18 @@ Prefer a per-project install? `npm install -D @amityco/social-plus-vise`, then c
98
98
  ## How it works
99
99
 
100
100
  1. **Inspect** — `vise inspect` detects the platform, app surfaces, available sensors, and design signals from the local repo.
101
- 2. **Plan** — `vise plan --request "..."` classifies the outcome, cites docs, and raises blocking intake and design-preview questions. The agent surfaces them; you answer.
101
+ 2. **Plan** — `vise plan --request "..."` classifies the outcome, cites docs, and raises blocking intake and design-preview questions. Use `--summary` when you only need the route/intake readout before implementation. The agent surfaces questions; you answer.
102
102
  3. **Initialize** — `vise init` writes the `sp-vise/` compliance contract. While blocking questions are unanswered it refuses, returns `status: "needs-clarification"`, and exits 7 — the agent must ask instead of guessing.
103
103
  4. **Build** — the agent edits your code, grounded by `vise search-docs` and `vise get-doc-page`.
104
104
  5. **Check & repair** — `vise check` reports deterministic findings, completeness gaps, and attestation needs. The agent fixes findings or records attestations with evidence, looping until green.
105
105
  6. **Sense** — `vise run-sensors` runs your project's own typecheck/build/lint/SDK smokes. Done means the contract and evidence are committed, not just that the agent stopped.
106
106
 
107
+ ### Solution path & UIKit routing (advisory)
108
+
109
+ Some requests are better served by **social.plus UIKit** (prebuilt social surfaces) than by hand-rolling standard UI directly from SDK primitives. `vise plan` emits an advisory **`solutionPath`** — `sdk`, `uikit`, `hybrid`, or `needs-decision` — so an agent can pause for `--answer solution_path=uikit|sdk|hybrid` before it builds the wrong layer. This separates the two integrator postures cleanly: an **SDK / custom-UI** build (differentiated, direct-SDK) stays SDK-first, a **UIKit / prebuilt-UI** adoption (standard surfaces, fast launch) routes to UIKit, and a genuinely mixed request resolves to `needs-decision` rather than silently picking one. A request that *rejects* UIKit ("don't use UIKit", "we can't use UIKit") routes SDK-first, and a request that names a backend feature capability (e.g. livestreaming) is surfaced as a decision rather than a UIKit-customization default.
110
+
111
+ When UIKit is in play, `vise plan` can also emit **`uikitCustomization`**, recommending the lowest-effort customization tier that meets the goal — Dynamic UI → Component Styling → Localization → Behavior Overrides → Fork and Extend → SDK Custom UI. Both signals are **advisory only**: they never change outcome classification, compliance rules, the sidecar schema, or `vise check` exit codes.
112
+
107
113
  ### Three validation layers
108
114
 
109
115
  The layer is set by the *kind of claim*, which is how Vise is designed to avoid false positives where it gates:
@@ -120,9 +126,9 @@ Correctness is gated by deterministic rules or attestations; completeness is gat
120
126
 
121
127
  ### Engagement Intelligence
122
128
 
123
- **Advisory, in preview.** The three layers above answer *whether you built it right*. Ahead of the build, Vise can also help decide *what to build*: an **Engagement Intelligence** layer turns a product goal into a candidate **engagement design** — archetypes, UX patterns, and solution variants drawn from a social.plus experience catalog — then compiles the chosen variant into an implementation plan (`vise creative` → `vise creative accept` → `vise experience compile`), optionally bridging to installable social.plus blocks, plus advisory UX expectations and a multi-dimension experience review.
129
+ **Advisory, in preview.** The three layers above answer *whether you built it right*. Ahead of the build, Vise can also help reason about *what to build*: an **Engagement Intelligence** layer turns a product goal into multiple candidate **engagement strategies** — archetypes, UX patterns, and solution variants drawn from a social.plus experience catalog — with rationale, tradeoffs, no-fit guidance, availability boundaries, and review gaps. The human or driving agent still chooses the direction, then Vise compiles that selected variant into an implementation plan (`vise creative` → `vise creative accept` → `vise experience compile`), optionally bridging to installable social.plus blocks, plus advisory UX expectations and a multi-dimension experience review.
124
130
 
125
- It is **local-only, never uploads, carries no calibrated score** (a calibration program is in progress), and **never changes `vise check`'s exit codes**. Use it to shape the work; the validation layers still decide when it's done.
131
+ It is **local-only, never uploads, carries no calibrated score**, and **never changes `vise check`'s exit codes**. The opt-in ranking preview is review context, not a top-1 confidence claim or autonomous product-strategy selector. Use it to shape the work; the validation layers still decide when it's done.
126
132
 
127
133
  ### Design contracts
128
134
 
@@ -143,6 +149,8 @@ The mechanism is the durable claim: a checked loop beats the same rules in the p
143
149
 
144
150
  **Feed completeness (capabilities selected):** when the optional capabilities — image, poll, edit — are selected up front, the Vise arm completed **97–100%** of an 11-item feed checklist across three agents (Cursor/Composer, Claude/Sonnet, Codex/GPT). This is *absolute* completeness from answering Vise's capability questions — **not** a lift over a baseline.
145
151
 
152
+ **Engagement Intelligence advisory quality:** a 24-case held-out dogfood set supports the advisory positioning: expected available strategies surfaced in **17/17** cases, multiple strategy options were visible in **20/20** strategy-offer cases, rationale/tradeoff evidence was visible in **24/24**, and no-fit plus availability-gated cases preserved their boundaries. The same run retained **4 ranking-calibration concerns**, so this is **not** a claim of top-1 ranking confidence, autonomous strategy selection, business-outcome lift, or a calibrated score.
153
+
146
154
  **Boundaries:** these are social.plus's own measurements on greenfield work — self-reported, no third-party audit, measured on earlier builds, and not universal claims. Negative results travel with them: no measured advantage on day-2 bug fixing, and the design-conformance arms were measured at different times (the figure is the robust by-name-token signal, not pixel perfection).
147
155
 
148
156
  <sub>Cursor, Claude, Codex, GitHub Copilot, VS Code, and other product names are trademarks of their respective owners; social.plus is not affiliated with or endorsed by them. Benchmark figures are from social.plus's own measurements.</sub>
@@ -155,7 +163,7 @@ The mechanism is the durable claim: a checked loop beats the same rules in the p
155
163
  | **React Native** | ✅ Full | `tsc`, `npm lint`, SDK import smoke |
156
164
  | **Flutter / Dart** | ✅ Full | `flutter analyze`, `flutter test` |
157
165
  | **Android (Kotlin)** | ✅ Full | Gradle assemble, unit tests |
158
- | **iOS (Swift)** | ✅ Full | Static rules fully operational (tree-sitter AST for highest-risk rules); build sensor is guarded best-effort — runs only when `xcodebuild` is available, reports environment issues as skipped-with-reason, and still fails on real build errors |
166
+ | **iOS (Swift)** | ✅ Full | Static rules fully operational (tree-sitter AST for highest-risk rules); `Package.swift` enables a SwiftPM manifest sensor, and `.xcodeproj`/`.xcworkspace` enables a guarded `xcodebuild` sensor when available |
159
167
 
160
168
  Each platform has dozens of rules across 10 compliance domains (feed, comments, moderation, chat, secrets, session & auth, notifications, live objects, logging hygiene, design tokens).
161
169
 
@@ -169,7 +177,8 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
169
177
  |---|---|
170
178
  | `vise doctor` | Verify install; print version, install path, docs source |
171
179
  | `vise inspect [path]` | Detect platform, monorepo surfaces, design signals, available sensors |
172
- | `vise plan [path] --request "..."` | Grounded implementation plan with intake questions and docs citations |
180
+ | `vise plan [path] --request "..." [--summary]` | Grounded implementation plan with intake questions and docs citations; `--summary` prints a compact route/intake view |
181
+ | `vise plan --summary "..."` | Shortcut for quick routing dogfood or discovery when the current directory is the repo |
173
182
  | `vise plan-harness [path] --request "..."` | Pre-planning step: build the harness around the request |
174
183
  | `vise init [path] --request "..." [--answer key=value]` | Write the `sp-vise/` compliance contract once blocking intake is answered; exits 7 (`needs-clarification`) otherwise |
175
184
  | `vise workplan next [path] --request "..."` | For broad social requests: print the next uncompleted surface and its focused commands |
@@ -180,7 +189,7 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
180
189
 
181
190
  | Command | Purpose |
182
191
  |---|---|
183
- | `vise creative [path] --request "..." [--requirements <path\|none>] [--prototype <html>] [--ranking-preview]` | Write an advisory Engagement Intelligence brief with candidate solution variants; `--ranking-preview` adds an opt-in, local-only ranking preview that never reorders the default candidates |
192
+ | `vise creative [path] --request "..." [--requirements <path\|none>] [--prototype <html>] [--ranking-preview]` | Write an advisory Engagement Intelligence brief with multiple candidate solution variants, rationale, tradeoffs, and review gaps; `--ranking-preview` adds opt-in, local-only review context that never reorders the default candidates |
184
193
  | `vise creative accept [path] --variant <id>` | Record the selected variant so `plan`/`init`/`workplan` carry it forward; `--variant none --rationale "..."` records a catalog-gap signal instead |
185
194
  | `vise ux-harness [path]` | Generate advisory UX expectations from the accepted selection |
186
195
  | `vise experience compile [path]` | Compile the accepted variant into an implementation artifact plan |
@@ -189,7 +198,7 @@ Run `vise <command> --help` for full flags. JSON output is the default for agent
189
198
  | `vise learning record [path]` | Append a local-only learning event; refreshes the learning summary |
190
199
  | `vise learning show [path]` | Read the local learning summary (never changes recommendations) |
191
200
 
192
- Everything in this group is local and advisory: no uploads, no `vise check` exit-code changes, no auto-accepted variants, and no calibrated score until the calibration program graduates one.
201
+ Everything in this group is local and advisory: no uploads, no `vise check` exit-code changes, no auto-accepted variants, no top-1 confidence claim, and no calibrated score.
193
202
 
194
203
  ### Design contract
195
204
 
@@ -299,7 +308,7 @@ Vise writes local planning, compliance, design, and evidence artifacts under `sp
299
308
  | `sp-vise/inspection.json` | `vise init` | Platform, surface, and design signals detected at init |
300
309
  | `sp-vise/attestations/*.json` | `vise sync` / `vise attest` | Per-rule evidence: signer, confidence, rationale, source fingerprints for drift detection |
301
310
  | `sp-vise/creative-brief.json` + `creative-brief.md` | `vise creative` | Advisory brief: goals, archetypes, candidate variants (JSON + human-readable) |
302
- | `sp-vise/candidate-ranking-preview.json` | `vise creative --ranking-preview` | Opt-in local ranking preview; `experience_score: null`, no uploads, no default-order change |
311
+ | `sp-vise/candidate-ranking-preview.json` | `vise creative --ranking-preview` | Opt-in local ranking preview for review context; `experience_score: null`, no uploads, no default-order change, no top-1 confidence claim |
303
312
  | `sp-vise/creative-selection.json` | `vise creative accept` | Accepted variant and plan/workplan feed-forward context |
304
313
  | `sp-vise/catalog-gap.json` | `vise creative accept --variant none` | Local-only no-fit signal for human catalog review |
305
314
  | `sp-vise/ux-harness.json` | `vise creative accept` or `vise ux-harness` | Advisory UX pattern expectations and anti-patterns |
package/dist/server.js CHANGED
@@ -285,12 +285,16 @@ async function handleCli(args) {
285
285
  return "exit";
286
286
  }
287
287
  if (command === "plan" || command === "plan-integration") {
288
- await printToolResult(planIntegrationTool, {
289
- repoPath: positionalRepoPath(args.slice(1)),
290
- request: requiredFlagValue(args, "request", "plan requires --request."),
291
- surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
292
- answers: keyValueFlag(args, "answer"),
293
- });
288
+ const input = await planCliInput(args);
289
+ if (hasFlag(args, "summary")) {
290
+ const result = await planIntegrationTool.call(input);
291
+ const text = result.content.map((item) => item.text).join("\n");
292
+ const payload = JSON.parse(text);
293
+ console.log(JSON.stringify(planSummary(payload), null, 2));
294
+ }
295
+ else {
296
+ await printToolResult(planIntegrationTool, input);
297
+ }
294
298
  return "exit";
295
299
  }
296
300
  if (command === "plan-harness") {
@@ -583,7 +587,9 @@ Create the evidence-backed implementation packet before editing code.
583
587
 
584
588
  Usage:
585
589
  vise plan [repoPath] --request "Add a social feed"
590
+ vise plan --summary "Use UIKit for a standard feed"
586
591
  vise plan apps/web --request "Create posts" --surface apps/web
592
+ vise plan . --request "Use UIKit for a standard feed" --summary
587
593
 
588
594
  Re-plan with collected answers (repeat --answer for each intake question):
589
595
  vise plan . --request "Add a social feed" \\
@@ -993,6 +999,33 @@ async function printToolResult(tool, input) {
993
999
  console.log(text);
994
1000
  return { result, text };
995
1001
  }
1002
+ async function planCliInput(args) {
1003
+ const subArgs = args.slice(1);
1004
+ const requestFromFlag = flagValue(args, "request");
1005
+ if (requestFromFlag) {
1006
+ return {
1007
+ repoPath: positionalRepoPath(subArgs),
1008
+ request: requestFromFlag,
1009
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
1010
+ answers: keyValueFlag(args, "answer"),
1011
+ };
1012
+ }
1013
+ const positional = positionalValues(subArgs);
1014
+ const [first, ...rest] = positional;
1015
+ if (!first) {
1016
+ throw new Error("plan requires --request or a quoted request argument.");
1017
+ }
1018
+ const firstLooksLikePath = await pathExists(first);
1019
+ if (firstLooksLikePath && rest.length === 0) {
1020
+ throw new Error("plan requires --request when the only positional argument is a repository path.");
1021
+ }
1022
+ return {
1023
+ repoPath: firstLooksLikePath ? first : ".",
1024
+ request: firstLooksLikePath ? rest.join(" ").trim() : positional.join(" ").trim(),
1025
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
1026
+ answers: keyValueFlag(args, "answer"),
1027
+ };
1028
+ }
996
1029
  function toolResultStatus(printed) {
997
1030
  try {
998
1031
  const payload = JSON.parse(printed.text);
@@ -1002,6 +1035,125 @@ function toolResultStatus(printed) {
1002
1035
  return undefined;
1003
1036
  }
1004
1037
  }
1038
+ function planSummary(payload) {
1039
+ const solutionPath = objectProp(payload, "solutionPath");
1040
+ const uikitCustomization = objectProp(payload, "uikitCustomization");
1041
+ const intake = objectProp(payload, "intake");
1042
+ const answers = objectProp(intake, "answers") ?? {};
1043
+ const workplan = objectProp(payload, "socialWorkplan");
1044
+ const questions = arrayProp(intake, "questions").map(questionSummary);
1045
+ const decisionsRequired = stringArrayProp(payload, "decisionsRequired");
1046
+ const solutionPathAnswerId = stringProp(solutionPath, "answerId");
1047
+ const uikitCustomizationAnswerId = stringProp(uikitCustomization, "answerId");
1048
+ return stripUndefined({
1049
+ kind: "plan-summary",
1050
+ outcome: stringProp(payload, "outcome"),
1051
+ platform: stringProp(payload, "platform"),
1052
+ supportLevel: stringProp(payload, "supportLevel"),
1053
+ solutionPath: solutionPath
1054
+ ? {
1055
+ recommendation: stringProp(solutionPath, "recommendation"),
1056
+ confidence: stringProp(solutionPath, "confidence"),
1057
+ summary: stringProp(solutionPath, "summary"),
1058
+ answerId: solutionPathAnswerId,
1059
+ decisionRequired: booleanProp(objectProp(solutionPath, "decision"), "requiredBeforeHandRolledUi") === true
1060
+ && !hasAnswerValue(answers, solutionPathAnswerId),
1061
+ }
1062
+ : undefined,
1063
+ uikitCustomization: uikitCustomization
1064
+ ? {
1065
+ status: stringProp(uikitCustomization, "status"),
1066
+ recommendedLevel: stringProp(uikitCustomization, "recommendedLevel"),
1067
+ confidence: stringProp(uikitCustomization, "confidence"),
1068
+ summary: stringProp(uikitCustomization, "summary"),
1069
+ answerId: uikitCustomizationAnswerId,
1070
+ decisionRequired: booleanProp(objectProp(uikitCustomization, "decision"), "requiredBeforeCustomization") === true
1071
+ && !hasAnswerValue(answers, uikitCustomizationAnswerId),
1072
+ }
1073
+ : undefined,
1074
+ intake: intake
1075
+ ? {
1076
+ status: stringProp(intake, "status"),
1077
+ remainingBlocking: numberProp(intake, "remainingBlocking"),
1078
+ blockingQuestions: questions.filter((question) => question.blocksImplementationWhenMissing),
1079
+ openQuestions: questions,
1080
+ answers,
1081
+ }
1082
+ : undefined,
1083
+ decisionsRequired,
1084
+ workplan: workplan ? workplanSummary(workplan) : undefined,
1085
+ nextStep: stringProp(payload, "nextStep"),
1086
+ });
1087
+ }
1088
+ function hasAnswerValue(answers, answerId) {
1089
+ if (!answerId) {
1090
+ return false;
1091
+ }
1092
+ const answer = answers[answerId];
1093
+ return typeof answer === "string" && answer.trim() !== "";
1094
+ }
1095
+ function workplanSummary(workplan) {
1096
+ return stripUndefined({
1097
+ kind: stringProp(workplan, "kind"),
1098
+ status: stringProp(workplan, "status"),
1099
+ nextStep: stringProp(workplan, "nextStep"),
1100
+ surfaces: arrayProp(workplan, "sequence").map((surface) => {
1101
+ const surfaceObject = asObject(surface);
1102
+ const intake = objectProp(surfaceObject, "intake");
1103
+ return stripUndefined({
1104
+ id: stringProp(surfaceObject, "id"),
1105
+ order: numberProp(surfaceObject, "order"),
1106
+ outcome: stringProp(surfaceObject, "outcome"),
1107
+ label: stringProp(surfaceObject, "label"),
1108
+ remainingBlocking: numberProp(intake, "remainingBlocking"),
1109
+ intakeStatus: stringProp(intake, "status"),
1110
+ });
1111
+ }),
1112
+ });
1113
+ }
1114
+ function questionSummary(value) {
1115
+ const question = asObject(value);
1116
+ return {
1117
+ id: stringProp(question, "id"),
1118
+ question: stringProp(question, "question"),
1119
+ required: booleanProp(question, "required"),
1120
+ blocksImplementationWhenMissing: booleanProp(question, "blocksImplementationWhenMissing"),
1121
+ };
1122
+ }
1123
+ function objectProp(value, key) {
1124
+ if (!value) {
1125
+ return undefined;
1126
+ }
1127
+ return asObject(value[key]);
1128
+ }
1129
+ function asObject(value) {
1130
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
1131
+ }
1132
+ function arrayProp(value, key) {
1133
+ if (!value) {
1134
+ return [];
1135
+ }
1136
+ const item = value[key];
1137
+ return Array.isArray(item) ? item : [];
1138
+ }
1139
+ function stringArrayProp(value, key) {
1140
+ return arrayProp(value, key).filter((item) => typeof item === "string");
1141
+ }
1142
+ function stringProp(value, key) {
1143
+ const item = value?.[key];
1144
+ return typeof item === "string" ? item : undefined;
1145
+ }
1146
+ function numberProp(value, key) {
1147
+ const item = value?.[key];
1148
+ return typeof item === "number" ? item : undefined;
1149
+ }
1150
+ function booleanProp(value, key) {
1151
+ const item = value?.[key];
1152
+ return typeof item === "boolean" ? item : undefined;
1153
+ }
1154
+ function stripUndefined(value) {
1155
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
1156
+ }
1005
1157
  async function installSkill(args) {
1006
1158
  const source = skillSourceDir();
1007
1159
  const instructionInstall = instructionInstallDestination(args);
@@ -1438,7 +1590,12 @@ function ciCheckResult(result) {
1438
1590
  };
1439
1591
  }
1440
1592
  function positionalRepoPath(args) {
1593
+ const values = positionalValues(args);
1594
+ return values[0] ?? ".";
1595
+ }
1596
+ function positionalValues(args) {
1441
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"]);
1598
+ const values = [];
1442
1599
  for (let index = 0; index < args.length; index += 1) {
1443
1600
  const arg = args[index];
1444
1601
  if (!arg) {
@@ -1454,9 +1611,18 @@ function positionalRepoPath(args) {
1454
1611
  if (arg.startsWith("-")) {
1455
1612
  continue;
1456
1613
  }
1457
- return arg;
1614
+ values.push(arg);
1615
+ }
1616
+ return values;
1617
+ }
1618
+ async function pathExists(input) {
1619
+ try {
1620
+ await stat(path.resolve(expandHome(input)));
1621
+ return true;
1622
+ }
1623
+ catch {
1624
+ return false;
1458
1625
  }
1459
- return ".";
1460
1626
  }
1461
1627
  function requiredPositionalText(args, message) {
1462
1628
  const values = [];
@@ -0,0 +1,274 @@
1
+ const UIKIT_SIGNALS = [
2
+ {
3
+ id: "explicit-uikit",
4
+ label: "Explicit social.plus UIKit / UI Kit mention",
5
+ strength: "strong",
6
+ pattern: /\b(?:social\.plus\s+)?(?:ui\s?kit|uikit)\b(?!\s+(?:experience|expertise|knowledge|background|familiarity))/i,
7
+ },
8
+ {
9
+ id: "prebuilt-components",
10
+ label: "Prebuilt or ready-made social UI components",
11
+ strength: "strong",
12
+ pattern: /\b(pre[-\s]?built|ready[-\s]?made)\b|\b(?:ready[-\s]?to[-\s]?use|out[-\s]?of[-\s]?the[-\s]?box|plug[-\s]?and[-\s]?play|drop[-\s]?in|off[-\s]?the[-\s]?shelf)\b(?:\s+(?:social\.plus|social|messaging|message|notifications?|community|communities|chat|feeds?|profiles?|stor(?:y|ies)|comments?|users?|groups?|channels?|posts?|prebuilt|standard|ui\s?kit|uikit))?\s+(?:ui|ui\s?kit|uikit|components?|widgets?|screens?|views?|feed|chat|profile|comments?|communit(?:y|ies)|stor(?:y|ies)|surfaces?)\b/i,
13
+ },
14
+ {
15
+ id: "speed-to-launch",
16
+ label: "Launch speed / MVP pressure",
17
+ strength: "medium",
18
+ pattern: /\b(quick launch|launch quickly|ship quickly|ship fast|mvp|build (?:a |an |the )?prototype|prototype (?:quickly|fast|first)|rapid prototyp\w*|weeks not months|days not months|save (?:development|dev|build|engineering) time|reduce development time)\b/i,
19
+ },
20
+ {
21
+ id: "standard-layout",
22
+ label: "Standard social app layout or white-label surface",
23
+ strength: "medium",
24
+ pattern: /\b(standard social|standard layout|standard feed|standard chat|standard profile|white[-\s]?label|basic styling|minimal customization|moderate customization|brand colors only)\b/i,
25
+ },
26
+ {
27
+ id: "uikit-customization-ladder",
28
+ label: "UIKit customization mode",
29
+ strength: "medium",
30
+ pattern: /\b(dynamic ui|component styling|fork and extend|forked ui\s?kit|fork the ui\s?kit|customi[sz]e the ui\s?kit)\b/i,
31
+ },
32
+ ];
33
+ const REJECT_NEGATOR = "do not|don't|dont|won't|wont|can't|cant|cannot|avoid|skip|no|without|not|never";
34
+ const REJECT_CONNECTOR = "to|use|uses|using|used|want|wants|wanting|adopt|adopts|adopting|leverage|leveraging|rely|relying|touch|build|building|go|going|be|been|with|on";
35
+ const REJECT_UIKIT_NOUN = "(?:the\\s+)?(?:social\\.plus\\s+)?(?:ui\\s?kit|uikit)\\b";
36
+ const REJECT_SKILL_LOOKAHEAD = "(?!\\s+(?:experience|expertise|knowledge|background|familiarity))";
37
+ const REJECT_UIKIT_SOURCE = `\\b(?:${REJECT_NEGATOR})(?:\\s+(?:${REJECT_CONNECTOR}))*\\s+${REJECT_UIKIT_NOUN}${REJECT_SKILL_LOOKAHEAD}` +
38
+ `|\\b(?:instead of|rather than)(?:\\s+(?:${REJECT_CONNECTOR}))*\\s+${REJECT_UIKIT_NOUN}` +
39
+ `|\\b(?:anything|everything)\\s+but\\s+${REJECT_UIKIT_NOUN}`;
40
+ const NEGATED_FROM_SCRATCH = /\b(?:not|never|avoid|don't|dont|do not|rather than|instead of)\b[\s\w,]{0,16}?\bfrom scratch\b/i;
41
+ const NEGATED_STANDARD = /\b(?:not|never|avoid|don't|dont|do not|rather than|instead of)\b[\s\w,]{0,12}?\b(?:standard social|standard layout|standard feed|standard chat|standard profile|white[-\s]?label|basic styling|minimal customization|moderate customization)\b/i;
42
+ const SDK_SIGNALS = [
43
+ {
44
+ id: "direct-sdk",
45
+ label: "Direct SDK implementation",
46
+ strength: "strong",
47
+ pattern: /\b(?:directly|direct|using|use|with|via|embed|embedding|integrate|integrating)\s+(?:the\s+)?(?:social\.plus\s+)?SDK\b|\b(?:the\s+)?(?:social\.plus\s+)?SDK\s+for\b|\bSDK[-\s]?owned\b|\bSDK[-\s]?custom\b/i,
48
+ },
49
+ {
50
+ id: "reject-uikit",
51
+ label: "Explicitly rejects UIKit or chooses SDK instead of UIKit",
52
+ strength: "strong",
53
+ pattern: new RegExp(REJECT_UIKIT_SOURCE, "i"),
54
+ },
55
+ {
56
+ id: "custom-ui",
57
+ label: "Custom UI or non-standard experience",
58
+ strength: "strong",
59
+ pattern: /\b(?:custom|bespoke|unique|fully[-\s]?custom|completely[-\s]?custom|totally[-\s]?(?:different|custom)|brand[-\s]?new|differentiated)\s+(?:(?:social|messaging|notification|community|chat|feed|profile|story|stories|comment|comments|user|group|channel|post|navigation|onboarding|discovery)\s+)?(?:ui|interface|experience)\b|\bnon[-\s]?standard (?:ui|layout|flow|experience)\b|\bpixel[-\s]?perfect\b/i,
60
+ },
61
+ {
62
+ id: "custom-flows",
63
+ label: "Custom user flows or interactions",
64
+ strength: "strong",
65
+ pattern: /\b(custom flows?|custom user flows?|custom interactions?|custom workflows?|unique flows?|bespoke flows?|special interaction)\b/i,
66
+ },
67
+ {
68
+ id: "full-control",
69
+ label: "Full control / design freedom",
70
+ strength: "strong",
71
+ pattern: /\b(full control|complete control|maximum flexibility|complete design freedom|full design freedom|build from scratch|hand[-\s]?rolled|from scratch)\b/i,
72
+ },
73
+ {
74
+ id: "deep-app-integration",
75
+ label: "Deep existing-app integration",
76
+ strength: "medium",
77
+ pattern: /\b(deeply integrate|existing app flow|existing navigation|custom backend|server[-\s]?to[-\s]?server|non[-\s]?standard platform|advanced technical requirements)\b/i,
78
+ },
79
+ ];
80
+ const SOURCE_EVIDENCE = [
81
+ "social.plus UIKit docs: prebuilt UI components, quick launch, and minimal/moderate customization paths.",
82
+ "social.plus SDK docs: custom experiences, unique user flows, complete design freedom, and existing-app integration.",
83
+ ];
84
+ export function recommendSolutionPath(request, answers = {}) {
85
+ const text = typeof request === "string" ? request : "";
86
+ const explicitAnswer = normalizeSolutionPathAnswer(answers?.solution_path);
87
+ const sdkSignals = collectSignals(text, SDK_SIGNALS).filter((signal) => {
88
+ if (signal.id !== "full-control" || !/scratch/i.test(signal.matched)) {
89
+ return true;
90
+ }
91
+ return !NEGATED_FROM_SCRATCH.test(text);
92
+ });
93
+ const negativeUIKitSignals = sdkSignals.filter((signal) => signal.id === "reject-uikit");
94
+ let uikitScanText = text;
95
+ if (negativeUIKitSignals.length > 0) {
96
+ uikitScanText = text.replace(new RegExp(REJECT_UIKIT_SOURCE, "gi"), " ");
97
+ }
98
+ let uikitSignals = collectSignals(uikitScanText, UIKIT_SIGNALS).filter((signal) => {
99
+ if (signal.id !== "standard-layout") {
100
+ return true;
101
+ }
102
+ return !NEGATED_STANDARD.test(text);
103
+ });
104
+ if (negativeUIKitSignals.length > 0 && !uikitSignals.some((signal) => signal.strength === "strong")) {
105
+ uikitSignals = [];
106
+ }
107
+ if (explicitAnswer) {
108
+ return guidanceFor({
109
+ recommendation: explicitAnswer,
110
+ confidence: "high",
111
+ uikitSignals,
112
+ sdkSignals,
113
+ answered: explicitAnswer,
114
+ });
115
+ }
116
+ const hasStrongUIKit = uikitSignals.some((signal) => signal.strength === "strong");
117
+ const hasStrongSdk = sdkSignals.some((signal) => signal.strength === "strong");
118
+ if (uikitSignals.length > 0 && sdkSignals.length > 0) {
119
+ return guidanceFor({
120
+ recommendation: "needs-decision",
121
+ confidence: hasStrongUIKit || hasStrongSdk ? "medium" : "low",
122
+ uikitSignals,
123
+ sdkSignals,
124
+ });
125
+ }
126
+ if (uikitSignals.length > 0) {
127
+ return guidanceFor({
128
+ recommendation: "uikit",
129
+ confidence: hasStrongUIKit ? "high" : "medium",
130
+ uikitSignals,
131
+ sdkSignals,
132
+ });
133
+ }
134
+ if (sdkSignals.length > 0) {
135
+ return guidanceFor({
136
+ recommendation: "sdk",
137
+ confidence: hasStrongSdk ? "high" : "medium",
138
+ uikitSignals,
139
+ sdkSignals,
140
+ });
141
+ }
142
+ return guidanceFor({
143
+ recommendation: "sdk",
144
+ confidence: "medium",
145
+ uikitSignals,
146
+ sdkSignals,
147
+ });
148
+ }
149
+ function guidanceFor(args) {
150
+ const decision = {
151
+ requiredBeforeHandRolledUi: !args.answered && (args.recommendation === "uikit" || args.recommendation === "hybrid" || args.recommendation === "needs-decision"),
152
+ question: "Should this build use social.plus UIKit components, a direct SDK implementation, or a hybrid path?",
153
+ options: [
154
+ "uikit: use social.plus UIKit for standard social surfaces and theme/customize it",
155
+ "sdk: build the experience directly with the social.plus SDK",
156
+ "hybrid: use UIKit for standard surfaces and SDK/custom code for differentiated app-layer behavior",
157
+ ],
158
+ };
159
+ if (args.recommendation === "uikit") {
160
+ return {
161
+ recommendation: "uikit",
162
+ confidence: args.confidence,
163
+ summary: args.answered === "uikit"
164
+ ? "The customer explicitly selected the UIKit path. Confirm scope, then use social.plus UIKit as the primary implementation artifact instead of hand-rolling standard social UI."
165
+ : "The request sounds UIKit-shaped: favor social.plus UIKit for the standard social surface, then customize within the UIKit theming/component model before considering direct SDK UI.",
166
+ answerId: "solution_path",
167
+ signals: { uikit: args.uikitSignals, sdk: args.sdkSignals },
168
+ decision,
169
+ implementationGuidance: [
170
+ "Confirm `solution_path=uikit` before the host agent hand-rolls standard feed, chat, profile, comments, community, or story UI.",
171
+ "Use UIKit installation, authentication, customization, and platform build guidance as the implementation source of truth.",
172
+ "Keep Vise's existing SDK setup, secrets, region, auth, build, and project-sensor guidance around the UIKit integration.",
173
+ "Do not claim deterministic compliance for UIKit internals hidden outside the customer repo; validate customer wiring, configuration, custom code, and local build evidence.",
174
+ "Keep design conformance advisory: use Dynamic UI, component styling, or fork-and-extend based on the customer's customization need.",
175
+ ],
176
+ evidence: SOURCE_EVIDENCE,
177
+ advisoryOnly: "This is an advisory solution-path recommendation. It does not change outcome classification, compliance rules, sidecar schema, or `vise check` exit codes.",
178
+ };
179
+ }
180
+ if (args.recommendation === "needs-decision") {
181
+ return {
182
+ recommendation: "needs-decision",
183
+ confidence: args.confidence,
184
+ summary: args.answered === "hybrid"
185
+ ? "The customer selected a hybrid path. Treat UIKit as the default for standard surfaces and use direct SDK/custom code only for the differentiated behavior."
186
+ : "The request contains both UIKit-shaped speed/prebuilt signals and SDK-shaped custom-control signals. Ask for the solution path before the agent starts UI implementation.",
187
+ answerId: "solution_path",
188
+ signals: { uikit: args.uikitSignals, sdk: args.sdkSignals },
189
+ decision,
190
+ implementationGuidance: [
191
+ "Resolve `solution_path` before hand-rolling standard social UI.",
192
+ "Choose UIKit when the standard social surface can be themed or lightly customized.",
193
+ "Choose SDK when the requested workflow, layout, or interaction model is materially different from UIKit's component model.",
194
+ "Choose hybrid when UIKit can cover baseline social surfaces and SDK/custom code is needed for app-specific extensions.",
195
+ "Do not claim deterministic compliance for UIKit internals hidden outside the customer repo; validate customer wiring, configuration, custom code, and local build evidence.",
196
+ ],
197
+ evidence: SOURCE_EVIDENCE,
198
+ advisoryOnly: "This is an advisory solution-path recommendation. It does not change outcome classification, compliance rules, sidecar schema, or `vise check` exit codes.",
199
+ };
200
+ }
201
+ if (args.recommendation === "hybrid") {
202
+ return {
203
+ recommendation: "hybrid",
204
+ confidence: args.confidence,
205
+ summary: "The customer selected a hybrid path. Treat UIKit as the default for standard surfaces and use direct SDK/custom code only for the differentiated behavior.",
206
+ answerId: "solution_path",
207
+ signals: { uikit: args.uikitSignals, sdk: args.sdkSignals },
208
+ decision,
209
+ implementationGuidance: [
210
+ "Use UIKit for standard social surfaces that fit its component and customization model.",
211
+ "Use direct SDK/custom code only for differentiated app-layer behavior, custom profile/discovery surfaces, or non-standard flows.",
212
+ "Keep the handoff explicit in the implementation summary: name which surfaces are UIKit-owned and which are SDK/customer-owned.",
213
+ "Do not claim deterministic compliance for UIKit internals hidden outside the customer repo; validate customer wiring, configuration, custom code, and local build evidence.",
214
+ ],
215
+ evidence: SOURCE_EVIDENCE,
216
+ advisoryOnly: "This is an advisory solution-path recommendation. It does not change outcome classification, compliance rules, sidecar schema, or `vise check` exit codes.",
217
+ };
218
+ }
219
+ return {
220
+ recommendation: "sdk",
221
+ confidence: args.confidence,
222
+ summary: args.answered === "sdk"
223
+ ? "The customer explicitly selected the direct SDK path. Continue with the normal Vise SDK integration harness."
224
+ : args.sdkSignals.some((signal) => signal.id === "reject-uikit")
225
+ ? "The request explicitly rejects UIKit or chooses the SDK instead of UIKit, so the normal SDK-first Vise harness is the safer fit."
226
+ : args.sdkSignals.length > 0
227
+ ? "The request asks for custom control, unique flows, or deep app integration, so the normal SDK-first Vise harness is the safer fit."
228
+ : "No strong UIKit fast-launch/prebuilt-surface signal was detected, so Vise keeps the existing SDK-first integration flow.",
229
+ answerId: "solution_path",
230
+ signals: { uikit: args.uikitSignals, sdk: args.sdkSignals },
231
+ decision,
232
+ implementationGuidance: [
233
+ "Use the normal Vise SDK integration flow: plan, answer intake, initialize the compliance contract, implement, check, and run sensors.",
234
+ "If the customer later asks for a standard prebuilt social surface or quick-launch MVP, re-plan with `--answer solution_path=uikit` or a UIKit-specific request.",
235
+ ],
236
+ evidence: SOURCE_EVIDENCE,
237
+ advisoryOnly: "This is an advisory solution-path recommendation. It does not change outcome classification, compliance rules, sidecar schema, or `vise check` exit codes.",
238
+ };
239
+ }
240
+ function collectSignals(request, signals) {
241
+ return signals.flatMap((signal) => {
242
+ const match = request.match(signal.pattern);
243
+ if (!match?.[0]) {
244
+ return [];
245
+ }
246
+ return [{
247
+ id: signal.id,
248
+ label: signal.label,
249
+ strength: signal.strength,
250
+ matched: match[0],
251
+ }];
252
+ });
253
+ }
254
+ const REJECT_UIKIT_IN_ANSWER = new RegExp(REJECT_UIKIT_SOURCE, "i");
255
+ const REJECT_HYBRID_IN_ANSWER = /\b(?:not|no|without|avoid|don't|dont|do not|never|rather than|instead of)\b[\s\w,]{0,12}?\bhybrid\b/;
256
+ function normalizeSolutionPathAnswer(answer) {
257
+ const normalized = (typeof answer === "string" ? answer : "").trim().toLowerCase();
258
+ if (!normalized) {
259
+ return undefined;
260
+ }
261
+ if (/\bhybrid\b/.test(normalized) && !REJECT_HYBRID_IN_ANSWER.test(normalized)) {
262
+ return "hybrid";
263
+ }
264
+ if (REJECT_UIKIT_IN_ANSWER.test(normalized)) {
265
+ return "sdk";
266
+ }
267
+ if (/\b(ui\s?kit|uikit)\b/.test(normalized)) {
268
+ return "uikit";
269
+ }
270
+ if (/\bsdk\b/.test(normalized)) {
271
+ return "sdk";
272
+ }
273
+ return undefined;
274
+ }