@amityco/social-plus-vise 0.14.26 → 0.14.28
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 +59 -0
- package/README.md +25 -7
- package/dist/capabilities.js +45 -1
- package/dist/intelligence/grounding.js +107 -0
- package/dist/intelligence/placement.js +71 -0
- package/dist/outcomes.js +2 -2
- package/dist/server.js +189 -10
- package/dist/tools/ast.js +161 -9
- package/dist/tools/blocks.js +95 -2
- package/dist/tools/compliance.js +176 -15
- package/dist/tools/creative.js +542 -106
- package/dist/tools/experienceCompiler.js +439 -0
- package/dist/tools/experienceSensors.js +406 -0
- package/dist/tools/harness.js +141 -8
- package/dist/tools/integration.js +43 -2
- package/dist/tools/learning.js +486 -0
- package/dist/tools/project.js +147 -17
- package/dist/tools/sdkFacts.js +83 -6
- package/dist/tools/sensors.js +38 -0
- package/dist/tools/uxHarness.js +546 -0
- package/package.json +45 -9
- package/packages/intelligence/README.md +1 -1
- package/packages/intelligence/catalog/archetypes.json +10 -0
- package/packages/intelligence/catalog/business-goals.json +8 -0
- package/packages/intelligence/catalog/catalog.schema.json +224 -0
- package/packages/intelligence/catalog/experience-objects.json +18 -18
- package/packages/intelligence/catalog/variants.json +56 -7
- package/scripts/catalog-coverage-html.mjs +325 -0
- package/scripts/catalog-relationships-html.mjs +686 -0
- package/scripts/catalog-sheets.mjs +286 -0
- package/scripts/extract-sdk-models.mjs +408 -0
- package/scripts/import-sdk-surface.mjs +43 -3
- package/scripts/pilot-feedback.mjs +107 -0
- package/scripts/workshop-board-html.mjs +1018 -0
- package/scripts/workshop-kit.mjs +252 -0
- package/sdk-surface/manifest.json +48 -2
- package/sdk-surface/models.android.json +990 -0
- package/sdk-surface/models.flutter.json +980 -0
- package/sdk-surface/models.ios.json +980 -0
- package/sdk-surface/models.typescript.json +1304 -0
- package/skills/social-plus-vise/SKILL.md +1 -1
- package/skills/vise-harness-engineer/SKILL.md +1 -1
- package/social.plus-vise.png +0 -0
package/dist/server.js
CHANGED
|
@@ -6,11 +6,14 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
|
|
9
|
+
import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, experienceReportTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
|
|
10
10
|
import { designCheckTool, designExtractTool, designInitTokensTool, designPreviewTool, designReferenceTool } from "./tools/design.js";
|
|
11
11
|
import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
|
|
12
|
+
import { compileExperienceTool } from "./tools/experienceCompiler.js";
|
|
13
|
+
import { experienceSensorsTool } from "./tools/experienceSensors.js";
|
|
12
14
|
import { planHarnessTool } from "./tools/harness.js";
|
|
13
15
|
import { planIntegrationTool } from "./tools/integration.js";
|
|
16
|
+
import { recordLearningTool, showLearningTool } from "./tools/learning.js";
|
|
14
17
|
import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
|
|
15
18
|
import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
|
|
16
19
|
import { runSensorsTool } from "./tools/sensors.js";
|
|
@@ -18,6 +21,7 @@ import { getSdkFactsTool } from "./tools/sdkFacts.js";
|
|
|
18
21
|
import { addBlockInstall, listRegistryBlocks, planBlockInstall, validateBlockInstall } from "./tools/blocks.js";
|
|
19
22
|
import { debugIssueTool, debugIssue } from "./tools/debug.js";
|
|
20
23
|
import { creativeAcceptTool, creativeBriefTool } from "./tools/creative.js";
|
|
24
|
+
import { uxHarnessTool } from "./tools/uxHarness.js";
|
|
21
25
|
import { packageName, packageVersion } from "./version.js";
|
|
22
26
|
const tools = new Map([
|
|
23
27
|
searchDocsTool,
|
|
@@ -25,10 +29,16 @@ const tools = new Map([
|
|
|
25
29
|
inspectProjectTool,
|
|
26
30
|
creativeBriefTool,
|
|
27
31
|
creativeAcceptTool,
|
|
32
|
+
uxHarnessTool,
|
|
33
|
+
compileExperienceTool,
|
|
34
|
+
experienceSensorsTool,
|
|
28
35
|
planHarnessTool,
|
|
29
36
|
planIntegrationTool,
|
|
30
37
|
initComplianceTool,
|
|
31
38
|
checkComplianceTool,
|
|
39
|
+
experienceReportTool,
|
|
40
|
+
recordLearningTool,
|
|
41
|
+
showLearningTool,
|
|
32
42
|
syncComplianceTool,
|
|
33
43
|
attestRuleTool,
|
|
34
44
|
explainRuleTool,
|
|
@@ -140,15 +150,18 @@ async function handleCli(args) {
|
|
|
140
150
|
if (command === "creative") {
|
|
141
151
|
if (args[1] === "accept") {
|
|
142
152
|
const subArgs = args.slice(2);
|
|
143
|
-
assertOnlyKnownFlags(subArgs, ["variant", "brief", "brief-path"], "creative accept");
|
|
153
|
+
assertOnlyKnownFlags(subArgs, ["variant", "brief", "brief-path", "rationale", "confidence", "closest"], "creative accept");
|
|
144
154
|
await printToolResult(creativeAcceptTool, {
|
|
145
155
|
repoPath: positionalRepoPath(subArgs),
|
|
146
|
-
variantId: requiredFlagValue(subArgs, "variant", "creative accept requires --variant <id
|
|
156
|
+
variantId: requiredFlagValue(subArgs, "variant", "creative accept requires --variant <id> (or --variant none to record a catalog gap)."),
|
|
147
157
|
briefPath: flagValue(subArgs, "brief") ?? flagValue(subArgs, "brief-path"),
|
|
158
|
+
rationale: flagValue(subArgs, "rationale"),
|
|
159
|
+
confidence: flagValue(subArgs, "confidence"),
|
|
160
|
+
closest: flagValue(subArgs, "closest"),
|
|
148
161
|
});
|
|
149
162
|
return "exit";
|
|
150
163
|
}
|
|
151
|
-
assertOnlyKnownFlags(args, ["request", "requirements", "prototype", "surface", "surface-path", "no-requirements", "no-write"], "creative");
|
|
164
|
+
assertOnlyKnownFlags(args, ["request", "requirements", "prototype", "surface", "surface-path", "no-requirements", "no-write", "ranking-preview"], "creative");
|
|
152
165
|
if (hasFlag(args, "no-requirements") && flagValue(args, "requirements")) {
|
|
153
166
|
throw new Error("creative accepts either --requirements <path> or --no-requirements, not both.");
|
|
154
167
|
}
|
|
@@ -158,10 +171,82 @@ async function handleCli(args) {
|
|
|
158
171
|
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
159
172
|
requirementsPath: hasFlag(args, "no-requirements") ? "none" : flagValue(args, "requirements"),
|
|
160
173
|
prototypePath: flagValue(args, "prototype"),
|
|
174
|
+
rankingPreview: hasFlag(args, "ranking-preview"),
|
|
161
175
|
write: !hasFlag(args, "no-write"),
|
|
162
176
|
});
|
|
163
177
|
return "exit";
|
|
164
178
|
}
|
|
179
|
+
if (command === "ux-harness") {
|
|
180
|
+
assertOnlyKnownFlags(args, ["surface", "surface-path", "no-write"], "ux-harness");
|
|
181
|
+
await printToolResult(uxHarnessTool, {
|
|
182
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
183
|
+
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
184
|
+
write: !hasFlag(args, "no-write"),
|
|
185
|
+
});
|
|
186
|
+
return "exit";
|
|
187
|
+
}
|
|
188
|
+
if (command === "experience-report" || command === "experience_report") {
|
|
189
|
+
assertOnlyKnownFlags(args, ["no-write"], "experience-report");
|
|
190
|
+
await printToolResult(experienceReportTool, {
|
|
191
|
+
repoPath: positionalRepoPath(args.slice(1)),
|
|
192
|
+
write: !hasFlag(args, "no-write"),
|
|
193
|
+
});
|
|
194
|
+
return "exit";
|
|
195
|
+
}
|
|
196
|
+
if (command === "learning" || command === "learn") {
|
|
197
|
+
const sub = args[1];
|
|
198
|
+
const subArgs = args.slice(2);
|
|
199
|
+
if (sub === "record") {
|
|
200
|
+
assertOnlyKnownFlags(subArgs, ["kind", "sentiment", "variant", "variant-id", "note", "metric", "no-write"], "learning record");
|
|
201
|
+
await printToolResult(recordLearningTool, {
|
|
202
|
+
repoPath: positionalRepoPath(subArgs),
|
|
203
|
+
kind: flagValue(subArgs, "kind"),
|
|
204
|
+
sentiment: flagValue(subArgs, "sentiment"),
|
|
205
|
+
variantId: flagValue(subArgs, "variant") ?? flagValue(subArgs, "variant-id"),
|
|
206
|
+
note: flagValue(subArgs, "note"),
|
|
207
|
+
metrics: keyValueFlag(subArgs, "metric"),
|
|
208
|
+
write: !hasFlag(subArgs, "no-write"),
|
|
209
|
+
});
|
|
210
|
+
return "exit";
|
|
211
|
+
}
|
|
212
|
+
if (sub === "show" || sub === "summary") {
|
|
213
|
+
assertOnlyKnownFlags(subArgs, [], `learning ${sub}`);
|
|
214
|
+
await printToolResult(showLearningTool, {
|
|
215
|
+
repoPath: positionalRepoPath(subArgs),
|
|
216
|
+
});
|
|
217
|
+
return "exit";
|
|
218
|
+
}
|
|
219
|
+
console.error(`Unknown learning subcommand: ${sub ?? "(none)"}. Expected "record" or "show".`);
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
return "exit";
|
|
222
|
+
}
|
|
223
|
+
if (command === "experience") {
|
|
224
|
+
const sub = args[1];
|
|
225
|
+
const subArgs = args.slice(2);
|
|
226
|
+
if (sub === "compile") {
|
|
227
|
+
assertOnlyKnownFlags(subArgs, ["request", "surface", "surface-path", "registry", "no-write"], "experience compile");
|
|
228
|
+
await printToolResult(compileExperienceTool, {
|
|
229
|
+
repoPath: positionalRepoPath(subArgs),
|
|
230
|
+
request: flagValue(subArgs, "request"),
|
|
231
|
+
surfacePath: flagValue(subArgs, "surface") ?? flagValue(subArgs, "surface-path"),
|
|
232
|
+
registryPath: flagValue(subArgs, "registry"),
|
|
233
|
+
write: !hasFlag(subArgs, "no-write"),
|
|
234
|
+
});
|
|
235
|
+
return "exit";
|
|
236
|
+
}
|
|
237
|
+
if (sub === "sensors") {
|
|
238
|
+
assertOnlyKnownFlags(subArgs, ["surface", "surface-path", "no-write"], "experience sensors");
|
|
239
|
+
await printToolResult(experienceSensorsTool, {
|
|
240
|
+
repoPath: positionalRepoPath(subArgs),
|
|
241
|
+
surfacePath: flagValue(subArgs, "surface") ?? flagValue(subArgs, "surface-path"),
|
|
242
|
+
write: !hasFlag(subArgs, "no-write"),
|
|
243
|
+
});
|
|
244
|
+
return "exit";
|
|
245
|
+
}
|
|
246
|
+
console.error(`Unknown experience subcommand: ${sub ?? "(none)"}. Expected "compile" or "sensors".`);
|
|
247
|
+
process.exitCode = 1;
|
|
248
|
+
return "exit";
|
|
249
|
+
}
|
|
165
250
|
if (command === "debug") {
|
|
166
251
|
assertOnlyKnownFlags(args, ["error", "error-file", "brief"], "debug");
|
|
167
252
|
let errorMessage = flagValue(args, "error");
|
|
@@ -231,7 +316,7 @@ async function handleCli(args) {
|
|
|
231
316
|
return "exit";
|
|
232
317
|
}
|
|
233
318
|
if (command === "run-sensors" || command === "run_sensor" || command === "run-sensor") {
|
|
234
|
-
await printToolResult(runSensorsTool, {
|
|
319
|
+
const printed = await printToolResult(runSensorsTool, {
|
|
235
320
|
repoPath: positionalRepoPath(args.slice(1)),
|
|
236
321
|
request: flagValue(args, "request"),
|
|
237
322
|
include: flagValues(args, "include"),
|
|
@@ -239,6 +324,13 @@ async function handleCli(args) {
|
|
|
239
324
|
dryRun: hasFlag(args, "dry-run"),
|
|
240
325
|
surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
|
|
241
326
|
});
|
|
327
|
+
// Reflect sensor outcome in the exit code so `vise run-sensors` is usable as a CI gate.
|
|
328
|
+
// Previously it always exited 0, so a failed/timed-out build silently read as green.
|
|
329
|
+
// dry-run / passed / no-sensors stay 0; only real failures fail.
|
|
330
|
+
const status = toolResultStatus(printed);
|
|
331
|
+
if (status === "failed" || status === "timed-out") {
|
|
332
|
+
process.exitCode = 1;
|
|
333
|
+
}
|
|
242
334
|
return "exit";
|
|
243
335
|
}
|
|
244
336
|
if (command === "resolve") {
|
|
@@ -250,7 +342,7 @@ async function handleCli(args) {
|
|
|
250
342
|
return "exit";
|
|
251
343
|
}
|
|
252
344
|
if (command === "sdk-facts" || command === "sdk_facts") {
|
|
253
|
-
assertOnlyKnownFlags(args, ["platform", "capability", "surface-dir", "format", "include-symbols"], "sdk-facts");
|
|
345
|
+
assertOnlyKnownFlags(args, ["platform", "capability", "surface-dir", "format", "include-symbols", "include-models"], "sdk-facts");
|
|
254
346
|
const format = flagValue(args, "format") ?? "json";
|
|
255
347
|
if (format !== "json") {
|
|
256
348
|
throw new Error("sdk-facts currently supports --format json only.");
|
|
@@ -260,6 +352,7 @@ async function handleCli(args) {
|
|
|
260
352
|
capability: flagValue(args, "capability"),
|
|
261
353
|
surfaceDir: flagValue(args, "surface-dir"),
|
|
262
354
|
includeSymbols: hasFlag(args, "include-symbols"),
|
|
355
|
+
includeModels: hasFlag(args, "include-models"),
|
|
263
356
|
});
|
|
264
357
|
return "exit";
|
|
265
358
|
}
|
|
@@ -490,13 +583,74 @@ Usage:
|
|
|
490
583
|
vise creative [repoPath] --request "Add engagement" --requirements none
|
|
491
584
|
vise creative [repoPath] --request "Add engagement" --no-requirements
|
|
492
585
|
vise creative [repoPath] --request "Add engagement" --prototype ./prototype.html
|
|
586
|
+
vise creative [repoPath] --request "Add engagement" --ranking-preview
|
|
493
587
|
vise creative [repoPath] --request "Add engagement" --no-write
|
|
494
588
|
vise creative accept [repoPath] --variant community-first
|
|
589
|
+
vise creative accept [repoPath] --variant none --rationale "No variant fits X; closest is Y" --closest discovery-first
|
|
495
590
|
|
|
496
591
|
Output:
|
|
497
592
|
Writes sp-vise/creative-brief.json and sp-vise/creative-brief.md unless --no-write is set.
|
|
593
|
+
--ranking-preview also writes sp-vise/candidate-ranking-preview.json as an opt-in advisory local preview.
|
|
498
594
|
Requirements are optional; absence or explicit none switches creative mode to exploratory.
|
|
499
|
-
creative accept writes sp-vise/creative-selection.json for plan/init/workplan feed-forward
|
|
595
|
+
creative accept writes sp-vise/creative-selection.json for plan/init/workplan feed-forward.
|
|
596
|
+
creative accept --variant none records sp-vise/catalog-gap.json (a local no-fit signal for human catalog review).`;
|
|
597
|
+
}
|
|
598
|
+
if (command === "ux-harness") {
|
|
599
|
+
return `${packageName} ux-harness
|
|
600
|
+
|
|
601
|
+
Generate the advisory UX Harness sidecar from an accepted creative selection.
|
|
602
|
+
|
|
603
|
+
Usage:
|
|
604
|
+
vise ux-harness [repoPath]
|
|
605
|
+
vise ux-harness [repoPath] --surface apps/web
|
|
606
|
+
vise ux-harness [repoPath] --no-write
|
|
607
|
+
|
|
608
|
+
Output:
|
|
609
|
+
Writes sp-vise/ux-harness.json by default. The sidecar contains selected UX pattern expectations, tradeoffs, anti-patterns, and advisory conformance hints. It never changes deterministic compliance rules by itself.`;
|
|
610
|
+
}
|
|
611
|
+
if (command === "experience-report" || command === "experience_report") {
|
|
612
|
+
return `${packageName} experience-report
|
|
613
|
+
|
|
614
|
+
Generate the advisory dimensioned Experience Report from current compliance evidence.
|
|
615
|
+
|
|
616
|
+
Usage:
|
|
617
|
+
vise experience-report [repoPath]
|
|
618
|
+
vise experience-report [repoPath] --no-write
|
|
619
|
+
|
|
620
|
+
Output:
|
|
621
|
+
Writes sp-vise/experience-report.json by default. The report summarizes technical compliance, design contract presence, UX Harness advisory evidence, and business alignment without producing a calibrated single score.`;
|
|
622
|
+
}
|
|
623
|
+
if (command === "learning" || command === "learn") {
|
|
624
|
+
return `${packageName} learning
|
|
625
|
+
|
|
626
|
+
Record and review local-only Engagement Intelligence learning events.
|
|
627
|
+
|
|
628
|
+
Usage:
|
|
629
|
+
vise learning record [repoPath] --sentiment positive --note "Customer preferred Community First"
|
|
630
|
+
vise learning record [repoPath] --kind outcome-snapshot --metric retention_delta=0.12
|
|
631
|
+
vise learning record [repoPath] --kind report-review --sentiment neutral --no-write
|
|
632
|
+
vise learning show [repoPath]
|
|
633
|
+
|
|
634
|
+
Output:
|
|
635
|
+
record writes sp-vise/learning-events.jsonl and sp-vise/learning-summary.json by default. Learning events are local evidence only; Vise does not change recommendation ranking from them yet.`;
|
|
636
|
+
}
|
|
637
|
+
if (command === "experience") {
|
|
638
|
+
return `${packageName} experience
|
|
639
|
+
|
|
640
|
+
Compile an accepted Engagement Intelligence variant and summarize experience sensors.
|
|
641
|
+
|
|
642
|
+
Usage:
|
|
643
|
+
vise experience compile [repoPath]
|
|
644
|
+
vise experience compile [repoPath] --request "Make this app social"
|
|
645
|
+
vise experience compile [repoPath] --registry ./registry/blocks.json
|
|
646
|
+
vise experience compile [repoPath] --surface apps/web --no-write
|
|
647
|
+
vise experience sensors [repoPath]
|
|
648
|
+
vise experience sensors [repoPath] --surface apps/web --no-write
|
|
649
|
+
|
|
650
|
+
Output:
|
|
651
|
+
compile writes sp-vise/experience-compiler.json by default. The artifact summarizes install guidance, navigation wiring, per-surface plan/init commands, UX expectations, optional Block Factory bridge candidates, design adaptation, and validation commands. It does not edit source code.
|
|
652
|
+
|
|
653
|
+
sensors writes sp-vise/experience-sensors.json by default. The artifact summarizes advisory technical, design, UX, accessibility, and business-alignment evidence without changing vise check exit codes or producing a calibrated score.`;
|
|
500
654
|
}
|
|
501
655
|
if (command === "plan-harness") {
|
|
502
656
|
return `${packageName} plan-harness
|
|
@@ -623,10 +777,15 @@ Usage:
|
|
|
623
777
|
return `${packageName} sdk-facts
|
|
624
778
|
|
|
625
779
|
Read bundled SDK surface facts for social.plus Block Factory planning. Internal, projectless, and read-only.
|
|
780
|
+
Symbol facts prove existence; --include-models adds extraction-grounded field-level model schemas
|
|
781
|
+
(modelSchemas, plus capability model entries gain a schema). Platforms without grounded field data
|
|
782
|
+
report modelFacts.status=absent instead of fabricated fields.
|
|
626
783
|
|
|
627
784
|
Usage:
|
|
628
785
|
vise sdk-facts --platform typescript --capability comments --format json
|
|
786
|
+
vise sdk-facts --platform typescript --capability comments --include-models
|
|
629
787
|
vise sdk-facts --platform react-native --capability reactions --include-symbols
|
|
788
|
+
vise sdk-facts --platform android --include-models --format json
|
|
630
789
|
vise sdk-facts --platform android --surface-dir ./sdk-surface --format json`;
|
|
631
790
|
}
|
|
632
791
|
if (command === "blocks") {
|
|
@@ -745,7 +904,13 @@ Usage:
|
|
|
745
904
|
vise print-skill Print bundled skill markdown
|
|
746
905
|
vise inspect [repoPath] Inspect platform and design signals
|
|
747
906
|
vise creative [repoPath] --request "..." Create an Engagement Intelligence brief
|
|
748
|
-
vise creative accept [repoPath] --variant <id> Accept a creative variant
|
|
907
|
+
vise creative accept [repoPath] --variant <id|none> Accept a creative variant, or record a no-fit catalog gap with --variant none
|
|
908
|
+
vise ux-harness [repoPath] Generate UX Harness expectations from creative selection
|
|
909
|
+
vise experience-report [repoPath] Write the advisory dimensioned Experience Report
|
|
910
|
+
vise experience compile [repoPath] Compile selected variant into implementation artifacts
|
|
911
|
+
vise experience sensors [repoPath] Write advisory experience sensor framework
|
|
912
|
+
vise learning record [repoPath] Record a local-only learning event
|
|
913
|
+
vise learning show [repoPath] Show local learning summary
|
|
749
914
|
vise debug [repoPath] --error ... Debug an SDK-specific runtime error and emit a repair brief
|
|
750
915
|
vise plan [repoPath] --request "..." Create an implementation plan
|
|
751
916
|
vise workplan next [repoPath] --request "..." Get the next broad-social surface to implement
|
|
@@ -784,7 +949,19 @@ SOCIAL_PLUS_DOCS_ROOT to test against a local social-plus-docs checkout.`;
|
|
|
784
949
|
}
|
|
785
950
|
async function printToolResult(tool, input) {
|
|
786
951
|
const result = await tool.call(input);
|
|
787
|
-
|
|
952
|
+
const text = result.content.map((item) => item.text).join("\n");
|
|
953
|
+
console.log(text);
|
|
954
|
+
return { result, text };
|
|
955
|
+
}
|
|
956
|
+
// Parse the `status` field from a tool's printed JSON payload (best-effort).
|
|
957
|
+
function toolResultStatus(printed) {
|
|
958
|
+
try {
|
|
959
|
+
const payload = JSON.parse(printed.text);
|
|
960
|
+
return typeof payload.status === "string" ? payload.status : undefined;
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
return undefined;
|
|
964
|
+
}
|
|
788
965
|
}
|
|
789
966
|
async function installSkill(args) {
|
|
790
967
|
const source = skillSourceDir();
|
|
@@ -1021,6 +1198,7 @@ async function workplanStatus(args) {
|
|
|
1021
1198
|
request,
|
|
1022
1199
|
progressPath: workplanProgressPath(repoRoot),
|
|
1023
1200
|
creativeContext: plan.socialWorkplan?.creativeContext ?? plan.creativeContext,
|
|
1201
|
+
uxHarness: plan.socialWorkplan?.uxHarness ?? plan.uxHarness,
|
|
1024
1202
|
completed,
|
|
1025
1203
|
sequence: sequence.map((surface) => ({
|
|
1026
1204
|
id: surface.id,
|
|
@@ -1037,6 +1215,7 @@ async function workplanStatus(args) {
|
|
|
1037
1215
|
label: nextSurface.label,
|
|
1038
1216
|
intake: nextSurface.intake,
|
|
1039
1217
|
validation: nextSurface.validation,
|
|
1218
|
+
uxHarness: nextSurface.uxHarness,
|
|
1040
1219
|
commands: {
|
|
1041
1220
|
plan: nextSurface.planCommand,
|
|
1042
1221
|
init: nextSurface.initCommand,
|
|
@@ -1222,7 +1401,7 @@ function ciCheckResult(result) {
|
|
|
1222
1401
|
};
|
|
1223
1402
|
}
|
|
1224
1403
|
function positionalRepoPath(args) {
|
|
1225
|
-
const flagsWithValues = new Set(["request", "requirements", "prototype", "variant", "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"]);
|
|
1404
|
+
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"]);
|
|
1226
1405
|
for (let index = 0; index < args.length; index += 1) {
|
|
1227
1406
|
const arg = args[index];
|
|
1228
1407
|
if (!arg) {
|
package/dist/tools/ast.js
CHANGED
|
@@ -29,11 +29,21 @@ function loadNativeBindings() {
|
|
|
29
29
|
const ParserCtor = nodeRequire("tree-sitter");
|
|
30
30
|
const tsGrammars = nodeRequire("tree-sitter-typescript");
|
|
31
31
|
const kotlinGrammar = nodeRequire("tree-sitter-kotlin");
|
|
32
|
+
// Swift loads in its own try/catch: a missing tree-sitter-swift prebuild on an
|
|
33
|
+
// exotic platform must degrade ONLY the Swift helpers, not all AST analysis.
|
|
34
|
+
let swiftGrammar = null;
|
|
35
|
+
try {
|
|
36
|
+
swiftGrammar = nodeRequire("tree-sitter-swift");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
swiftGrammar = null;
|
|
40
|
+
}
|
|
32
41
|
nativeBindings = {
|
|
33
42
|
Parser: ParserCtor,
|
|
34
43
|
tsGrammar: tsGrammars.typescript,
|
|
35
44
|
tsxGrammar: tsGrammars.tsx,
|
|
36
45
|
kotlinGrammar,
|
|
46
|
+
swiftGrammar,
|
|
37
47
|
};
|
|
38
48
|
}
|
|
39
49
|
catch {
|
|
@@ -50,6 +60,14 @@ function loadNativeBindings() {
|
|
|
50
60
|
export function astAvailable() {
|
|
51
61
|
return loadNativeBindings() !== null;
|
|
52
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether the Swift grammar specifically is available. tree-sitter-swift is loaded
|
|
65
|
+
* independently of the core bindings (see loadNativeBindings), so Swift-only
|
|
66
|
+
* validators can check this and fall back to regex without disabling ts/tsx/kotlin.
|
|
67
|
+
*/
|
|
68
|
+
export function swiftAstAvailable() {
|
|
69
|
+
return loadNativeBindings()?.swiftGrammar != null;
|
|
70
|
+
}
|
|
53
71
|
/**
|
|
54
72
|
* Strip comments from source code using tree-sitter AST.
|
|
55
73
|
* Replaces comment spans with whitespace (preserving line structure).
|
|
@@ -101,6 +119,12 @@ function getParser(language) {
|
|
|
101
119
|
parser.setLanguage(native.tsxGrammar);
|
|
102
120
|
else if (language === "kotlin")
|
|
103
121
|
parser.setLanguage(native.kotlinGrammar);
|
|
122
|
+
else if (language === "swift") {
|
|
123
|
+
if (native.swiftGrammar == null) {
|
|
124
|
+
throw new Error("tree-sitter-swift unavailable; Swift AST analysis disabled (regex fallback in effect)");
|
|
125
|
+
}
|
|
126
|
+
parser.setLanguage(native.swiftGrammar);
|
|
127
|
+
}
|
|
104
128
|
else
|
|
105
129
|
parser.setLanguage(native.tsGrammar);
|
|
106
130
|
parsers.set(language, parser);
|
|
@@ -166,7 +190,10 @@ export function findCallExpressions(tree, calleePattern) {
|
|
|
166
190
|
results.push({ callee, node, args });
|
|
167
191
|
return;
|
|
168
192
|
}
|
|
169
|
-
// Kotlin: call_expression = navigation_expression + call_suffix
|
|
193
|
+
// Kotlin AND Swift: call_expression = navigation_expression + call_suffix.
|
|
194
|
+
// (tree-sitter-swift descends from the Kotlin grammar, so the node names —
|
|
195
|
+
// navigation_expression / navigation_suffix / call_suffix / value_arguments —
|
|
196
|
+
// are identical; only Swift's labeled arguments add a value_argument_label.)
|
|
170
197
|
const navNode = node.namedChild(0);
|
|
171
198
|
const suffixNode = node.namedChild(1);
|
|
172
199
|
if (!navNode || !suffixNode || suffixNode.type !== "call_suffix")
|
|
@@ -181,8 +208,11 @@ export function findCallExpressions(tree, calleePattern) {
|
|
|
181
208
|
for (let i = 0; i < valArgsNode.namedChildCount; i++) {
|
|
182
209
|
const valArg = valArgsNode.namedChild(i);
|
|
183
210
|
if (valArg && valArg.type === "value_argument") {
|
|
184
|
-
// The actual expression is inside value_argument
|
|
185
|
-
|
|
211
|
+
// The actual expression is inside value_argument. Swift labeled arguments
|
|
212
|
+
// put a value_argument_label first — the value is the last named child.
|
|
213
|
+
// Kotlin behaviour (namedChild(0)) is intentionally unchanged.
|
|
214
|
+
const first = valArg.namedChild(0);
|
|
215
|
+
const expr = first?.type === "value_argument_label" ? valArg.namedChild(valArg.namedChildCount - 1) : first;
|
|
186
216
|
if (expr)
|
|
187
217
|
args.push(expr);
|
|
188
218
|
}
|
|
@@ -233,6 +263,93 @@ export function pickObjectProperty(objectNode, propertyName) {
|
|
|
233
263
|
}
|
|
234
264
|
return undefined;
|
|
235
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Pick the value of a labeled argument from a Swift call expression node.
|
|
268
|
+
* E.g., from `client.login(userId: HARDCODED, sessionHandler: handler)`,
|
|
269
|
+
* pick the value node for label "userId".
|
|
270
|
+
*
|
|
271
|
+
* Swift-shaped trees only (call_suffix → value_arguments → value_argument with a
|
|
272
|
+
* value_argument_label). Returns undefined when the label is absent.
|
|
273
|
+
*/
|
|
274
|
+
export function pickLabeledArgument(callNode, label) {
|
|
275
|
+
for (let i = 0; i < callNode.namedChildCount; i++) {
|
|
276
|
+
const suffix = callNode.namedChild(i);
|
|
277
|
+
if (!suffix || suffix.type !== "call_suffix")
|
|
278
|
+
continue;
|
|
279
|
+
const valArgs = suffix.namedChild(0);
|
|
280
|
+
if (!valArgs || valArgs.type !== "value_arguments")
|
|
281
|
+
continue;
|
|
282
|
+
for (let j = 0; j < valArgs.namedChildCount; j++) {
|
|
283
|
+
const valArg = valArgs.namedChild(j);
|
|
284
|
+
if (!valArg || valArg.type !== "value_argument")
|
|
285
|
+
continue;
|
|
286
|
+
const first = valArg.namedChild(0);
|
|
287
|
+
if (first?.type !== "value_argument_label" || first.text !== label)
|
|
288
|
+
continue;
|
|
289
|
+
const value = valArg.namedChild(valArg.namedChildCount - 1);
|
|
290
|
+
// namedChild(last) === the label itself when the argument has no value
|
|
291
|
+
// (selector-reference form) — treat as absent.
|
|
292
|
+
return value && value !== first ? value : undefined;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Find Swift `let`/`var` declarations whose initializer text matches a pattern AND
|
|
299
|
+
* which are function-local (declared inside a function/initializer/closure/computed-
|
|
300
|
+
* property body rather than at type or file scope). Used for retention rules: a
|
|
301
|
+
* function-local binding dies with the stack frame, while a type-scope property
|
|
302
|
+
* lives with the object — a distinction regex bridges cannot make reliably.
|
|
303
|
+
*
|
|
304
|
+
* Conservative direction: a declaration only counts as local when a KNOWN
|
|
305
|
+
* function-ish ancestor encloses it, so unknown containers degrade toward
|
|
306
|
+
* "retained" (quiet), never toward a false positive.
|
|
307
|
+
*/
|
|
308
|
+
export function findSwiftFunctionLocalDeclarations(tree, initializerPattern) {
|
|
309
|
+
const results = [];
|
|
310
|
+
walkTree(tree.rootNode, (node) => {
|
|
311
|
+
if (node.type !== "property_declaration")
|
|
312
|
+
return;
|
|
313
|
+
const value = swiftPropertyInitializer(node);
|
|
314
|
+
if (!value || !initializerPattern.test(value.text))
|
|
315
|
+
return;
|
|
316
|
+
if (hasFunctionAncestor(node))
|
|
317
|
+
results.push(node);
|
|
318
|
+
});
|
|
319
|
+
return results;
|
|
320
|
+
}
|
|
321
|
+
const SWIFT_FUNCTION_SCOPES = new Set([
|
|
322
|
+
"function_declaration",
|
|
323
|
+
"init_declaration",
|
|
324
|
+
"deinit_declaration",
|
|
325
|
+
"lambda_literal",
|
|
326
|
+
"computed_property",
|
|
327
|
+
"computed_getter",
|
|
328
|
+
"computed_setter",
|
|
329
|
+
]);
|
|
330
|
+
function hasFunctionAncestor(node) {
|
|
331
|
+
let current = node.parent;
|
|
332
|
+
while (current) {
|
|
333
|
+
if (SWIFT_FUNCTION_SCOPES.has(current.type))
|
|
334
|
+
return true;
|
|
335
|
+
current = current.parent;
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
/** The initializer expression of a Swift property_declaration (the value after `=`), if any. */
|
|
340
|
+
function swiftPropertyInitializer(node) {
|
|
341
|
+
// Shape: property_declaration = value_binding_pattern, pattern, [type_annotation], [value expr]
|
|
342
|
+
// The initializer (when present) is the last named child and is none of the structural parts.
|
|
343
|
+
const last = node.namedChild(node.namedChildCount - 1);
|
|
344
|
+
if (!last)
|
|
345
|
+
return undefined;
|
|
346
|
+
if (["value_binding_pattern", "pattern", "type_annotation", "modifiers", "attribute"].includes(last.type))
|
|
347
|
+
return undefined;
|
|
348
|
+
// computed_property is a body, not an initializer.
|
|
349
|
+
if (last.type === "computed_property")
|
|
350
|
+
return undefined;
|
|
351
|
+
return last;
|
|
352
|
+
}
|
|
236
353
|
// ── Internal helpers ──────────────────────────────────────────────────────────
|
|
237
354
|
function walkTree(node, visit) {
|
|
238
355
|
visit(node);
|
|
@@ -322,6 +439,25 @@ function extractStringLiteral(node) {
|
|
|
322
439
|
return text.slice(1, -1);
|
|
323
440
|
}
|
|
324
441
|
}
|
|
442
|
+
// Swift: line_string_literal contains line_str_text chunks; an interpolated
|
|
443
|
+
// string has >1 named child (line_str_text + interpolated_expression) and is
|
|
444
|
+
// NOT statically resolvable — mirror the TS template-literal treatment.
|
|
445
|
+
if (node.type === "line_string_literal") {
|
|
446
|
+
if (node.namedChildCount === 0)
|
|
447
|
+
return ""; // empty string ""
|
|
448
|
+
if (node.namedChildCount === 1 && node.namedChild(0)?.type === "line_str_text") {
|
|
449
|
+
return node.namedChild(0).text;
|
|
450
|
+
}
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
// Swift: multi-line """…""" literal. Swift semantics strip the newline after the
|
|
454
|
+
// opening and before the closing delimiter.
|
|
455
|
+
if (node.type === "multi_line_string_literal") {
|
|
456
|
+
if (node.namedChildCount === 1 && node.namedChild(0)?.type === "multi_line_str_text") {
|
|
457
|
+
return node.namedChild(0).text.replace(/^\n/, "").replace(/\n[ \t]*$/, "");
|
|
458
|
+
}
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
325
461
|
return undefined;
|
|
326
462
|
}
|
|
327
463
|
function resolveIdentifierToLiteral(name, root) {
|
|
@@ -345,15 +481,31 @@ function resolveIdentifierToLiteral(name, root) {
|
|
|
345
481
|
// Kotlin: property_declaration with variable_declaration + string_literal
|
|
346
482
|
if (node.type === "property_declaration") {
|
|
347
483
|
const varDecl = node.namedChildren.find((c) => c.type === "variable_declaration");
|
|
348
|
-
if (
|
|
484
|
+
if (varDecl) {
|
|
485
|
+
const idNode = varDecl.namedChildren.find((c) => c.type === "simple_identifier");
|
|
486
|
+
if (!idNode || idNode.text !== name)
|
|
487
|
+
return;
|
|
488
|
+
const strLit = node.namedChildren.find((c) => c.type === "string_literal");
|
|
489
|
+
if (!strLit)
|
|
490
|
+
return;
|
|
491
|
+
const literal = extractStringLiteral(strLit);
|
|
492
|
+
if (literal !== undefined)
|
|
493
|
+
result = literal;
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// Swift: property_declaration with pattern → simple_identifier; the string
|
|
497
|
+
// literal must be a DIRECT child (the initializer) — a literal nested inside
|
|
498
|
+
// e.g. `env["KEY"] ?? ""` is not a static binding and must not resolve.
|
|
499
|
+
const pattern = node.namedChildren.find((c) => c.type === "pattern");
|
|
500
|
+
if (!pattern)
|
|
349
501
|
return;
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
502
|
+
const swiftId = pattern.namedChildren.find((c) => c.type === "simple_identifier");
|
|
503
|
+
if (!swiftId || swiftId.text !== name)
|
|
352
504
|
return;
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
505
|
+
const swiftLit = node.namedChildren.find((c) => c.type === "line_string_literal" || c.type === "multi_line_string_literal");
|
|
506
|
+
if (!swiftLit)
|
|
355
507
|
return;
|
|
356
|
-
const literal = extractStringLiteral(
|
|
508
|
+
const literal = extractStringLiteral(swiftLit);
|
|
357
509
|
if (literal !== undefined)
|
|
358
510
|
result = literal;
|
|
359
511
|
}
|