@amityco/social-plus-vise 0.14.4 → 0.14.6
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 +28 -0
- package/README.md +52 -11
- package/dist/capabilities.js +126 -12
- package/dist/server.js +83 -5
- package/dist/tools/blocks.js +385 -0
- package/dist/tools/compliance.js +151 -14
- package/dist/tools/design.js +19 -3
- package/dist/tools/integration.js +36 -4
- package/package.json +3 -2
- package/skills/social-plus-vise/SKILL.md +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ 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
|
+
## 0.14.6 — 2026-06-05
|
|
8
|
+
|
|
9
|
+
**Theme:** Intake questions must reach the human before implementation.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`vise init` now enforces unresolved blocking intake:** normal init returns `status: "needs-clarification"` and exits 7 when required planning questions are still unanswered. This prevents host agents from skipping surfaced questions and writing a compliance sidecar as if scope were resolved.
|
|
13
|
+
- **`sp-vise/intake.json`:** successful init records the request, outcome, answers, remaining blocking count, and whether unresolved intake was explicitly acknowledged.
|
|
14
|
+
- **`--allow-unresolved-intake`:** explicit bypass for retrospective benchmark/harness initialization only. The acknowledgement is recorded in `sp-vise/intake.json`; customer implementations should answer the blocking questions instead.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Host-project `style.css` design extraction:** `vise design extract --from-project` now detects non-theme-named CSS files that declare custom properties, so common roots like `style.css` produce strong host-project design contracts instead of empty weak contracts.
|
|
18
|
+
- **Design-source wording:** `vise plan` now labels host-project design contracts as the host app's design system, not the customer's prototype, and empty contracts tell the agent to ask for the correct design source instead of implying tokens exist.
|
|
19
|
+
|
|
20
|
+
### Docs
|
|
21
|
+
- **Skill and tool docs now require surfacing blocking intake questions** before implementation, and release smoke guidance uses `--allow-unresolved-intake` only where a disposable retrospective init is intended.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 0.14.5 — 2026-06-04
|
|
26
|
+
|
|
27
|
+
**Theme:** Optional feed capabilities become explicit opt-in sensors.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- **Add-feed completeness baseline narrowed to pagination:** image upload, poll creation, and edit post are no longer baseline completeness requirements. The add-feed `completenessChecklist` now gates pagination / load-more only.
|
|
31
|
+
- **Optional feed capabilities require explicit opt-in:** `vise plan` now offers image upload, poll creation, and edit post as `optionalCapabilities`. When the user selects one and the agent carries it into `vise init --answer feed_optional_capabilities=...`, `vise check` runs selected source sensors and exits `selected-capability-failures` (exit code 6) until the selected capability is implemented.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
7
35
|
## 0.14.4 — 2026-06-04
|
|
8
36
|
|
|
9
37
|
**Theme:** Completeness-gap enforcement and add-feed guidance hardening (image upload + poll creation + pagination build step).
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ See [Usage Flow](#usage-flow) for the full step-by-step diagram.
|
|
|
45
45
|
|
|
46
46
|
Instead of just providing a CLI or AI skills, Vise implements a technique called **Agentic Workflow Governance**. Think of it as a customer-project integration harness: the governed build loop runs inside the target repo, grounded in the real project, real docs, and real validation signals.
|
|
47
47
|
|
|
48
|
-
Vise wraps your local coding agents in compliance guardrails when they integrate social.plus SDKs. It inspects your project, grounds the agent in hosted docs, enforces 300 platform-specific compliance rules, checks the generated UI against the customer's design system,
|
|
48
|
+
Vise wraps your local coding agents in compliance guardrails when they integrate social.plus SDKs. It inspects your project, grounds the agent in hosted docs, enforces 300 platform-specific compliance rules, checks the generated UI against the customer's design system, gates narrow baseline capabilities so nothing mandatory is silently dropped, offers richer feed capabilities as explicit opt-ins, and runs your project's own build/lint/typecheck sensors. **Your source code never leaves your machine.**
|
|
49
49
|
|
|
50
50
|
At a glance, Vise sits between the user's prompt and the agent's code changes. The agent still edits the app; Vise turns the request into a grounded plan, records the local contract, and keeps checking until the integration is ready to ship.
|
|
51
51
|
|
|
@@ -77,18 +77,53 @@ Vise validates on three layers, and the layer is set by the *kind of claim* —
|
|
|
77
77
|
|---|---|---|---|
|
|
78
78
|
| **SDK compliance** | "this is **wrong**" | 300 deterministic rules (session renewal, live-collection vs one-shot, no secret in logs, parent-child rendering, ban-state gating…) | **Hard gate** — `vise check` blocks until green or attested. A small advisory subset surfaces as informational only and never blocks. |
|
|
79
79
|
| **Design conformance** | "this **looks off**" | extract the customer's design system into a contract, then check token usage | **Advisory** — `vise design check`/`preview`; never fails a build |
|
|
80
|
-
| **Feature completeness** | "this is **missing**" | Vise proposes
|
|
80
|
+
| **Feature completeness** | "this is **missing**" | Vise proposes a narrow baseline per outcome; for add-feed, pagination is mandatory, while richer feed capabilities are opt-in choices from `vise plan` | **Decision gate** — `vise check` exits `completeness-gap` until each baseline capability is built or validly opted out; selected optional capabilities run separate sensors |
|
|
81
81
|
|
|
82
|
-
Correctness is gated by deterministic rules or attestations.
|
|
82
|
+
Correctness is gated by deterministic rules or attestations. Baseline completeness is gated by explicit scope decisions: if a baseline capability is legitimately out of scope, record `// vise: scope-omit <id> — <reason>` and it no longer blocks. Optional feed capabilities such as image upload, poll creation, and edit post are offered during planning and become checked only after the user opts in. Conformance remains advisory because "matches the brand" is legitimately subjective. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
83
83
|
|
|
84
84
|
### Relationship to social.plus Block Factory
|
|
85
85
|
|
|
86
86
|
Vise has two deliberately separate roles:
|
|
87
87
|
|
|
88
88
|
- **Customer integration helper:** runs inside customer projects to inspect, plan, validate, and sensor-check social.plus SDK integrations.
|
|
89
|
-
- **Block Factory SDK facts provider:**
|
|
89
|
+
- **Block Factory SDK facts provider:** internal mode for social.plus Block Factory to verify SDK capabilities, symbols, and model schemas before reusable blocks are generated or released.
|
|
90
|
+
- **Block installer governance:** customer-project workflow that consumes a Block Factory registry, plans safe package/source changes, writes `sp-vise/blocks.json`, and validates installed block state.
|
|
90
91
|
|
|
91
|
-
Vise owns SDK truth and customer-project governance. social.plus Block Factory owns block contracts, package adapters, previews, conformance tests, and release readiness. See [docs/SDK_FACTS_FOR_BLOCK_FACTORY.md](docs/SDK_FACTS_FOR_BLOCK_FACTORY.md) for the internal provider-side plan.
|
|
92
|
+
Vise owns SDK truth and customer-project governance. social.plus Block Factory owns block contracts, package adapters, registry metadata, previews, conformance tests, and release readiness. See [docs/SDK_FACTS_FOR_BLOCK_FACTORY.md](docs/SDK_FACTS_FOR_BLOCK_FACTORY.md) for the internal provider-side plan.
|
|
93
|
+
|
|
94
|
+
### Block Factory user experience
|
|
95
|
+
|
|
96
|
+
When Vise is used with social.plus Block Factory, the customer experience should feel like asking an AI agent for a social capability rather than manually assembling SDK calls and UI states:
|
|
97
|
+
|
|
98
|
+
> "Add social.plus comments to this app and match my existing design system."
|
|
99
|
+
|
|
100
|
+
The agent uses Vise to turn that request into a governed install workflow:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
vise blocks list --registry ./registry/blocks.json --format json
|
|
104
|
+
vise blocks plan . --block comments --registry ./registry/blocks.json --format json
|
|
105
|
+
vise blocks add . --block comments --registry ./registry/blocks.json --package-source npm --dry-run
|
|
106
|
+
vise blocks add . --block comments --registry ./registry/blocks.json --package-source npm --apply
|
|
107
|
+
vise blocks validate . --block comments --registry ./registry/blocks.json --format json
|
|
108
|
+
vise run-sensors --dry-run
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
What Vise does:
|
|
112
|
+
|
|
113
|
+
- Inspects the customer project and detects the target platform.
|
|
114
|
+
- Reads Block Factory registry metadata without owning block product data.
|
|
115
|
+
- Plans package, source-anchor, sidecar, design-contract, and sensor requirements.
|
|
116
|
+
- Applies changes only to package manifests, `sp-vise/blocks.json`, and source files with explicit install anchors.
|
|
117
|
+
- Returns `needs-review` when a brownfield project has no safe install anchor.
|
|
118
|
+
- Validates installed block state and detects drift after future code changes.
|
|
119
|
+
|
|
120
|
+
Required customer or agent actions:
|
|
121
|
+
|
|
122
|
+
- Choose the block id and package source.
|
|
123
|
+
- Review the dry-run plan before using `--apply`.
|
|
124
|
+
- Add explicit install anchors when Vise cannot safely edit the project.
|
|
125
|
+
- Keep `sp-vise/blocks.json` committed so future validation has state to compare.
|
|
126
|
+
- Run the target project's normal build, lint, test, and Vise sensors before shipping.
|
|
92
127
|
|
|
93
128
|
### Design-conformant UI
|
|
94
129
|
|
|
@@ -253,7 +288,7 @@ flowchart LR
|
|
|
253
288
|
C --> D{Intake<br/>questions?}
|
|
254
289
|
D -->|Yes| E[Agent asks user<br/>for feed target,<br/>design source, etc.]
|
|
255
290
|
D -->|No| F
|
|
256
|
-
E --> F[Agent runs<br/>vise init]
|
|
291
|
+
E --> F[Agent runs<br/>vise init<br/>with answers]
|
|
257
292
|
F --> G[Agent runs<br/>vise search-docs<br/>vise get-doc-page]
|
|
258
293
|
G --> H[Agent edits<br/>your code]
|
|
259
294
|
H --> I[Agent runs<br/>vise check]
|
|
@@ -266,7 +301,7 @@ flowchart LR
|
|
|
266
301
|
M --> N[Done.<br/>sp-vise/ contract<br/>committed]
|
|
267
302
|
```
|
|
268
303
|
|
|
269
|
-
The flow above is what the skill teaches your AI agent. You — the human — drive intent and approve edits; the agent runs Vise commands deterministically; Vise grounds the agent in real docs and real compliance rules.
|
|
304
|
+
The flow above is what the skill teaches your AI agent. You — the human — drive intent and approve edits; the agent runs Vise commands deterministically; Vise grounds the agent in real docs and real compliance rules. If blocking intake is still unresolved, `vise init` refuses to initialize, returns `status: "needs-clarification"`, and exits 7 so the agent must surface the questions instead of guessing.
|
|
270
305
|
|
|
271
306
|
---
|
|
272
307
|
|
|
@@ -280,7 +315,11 @@ The flow above is what the skill teaches your AI agent. You — the human — dr
|
|
|
280
315
|
| `vise inspect [path]` | Detect platform, monorepo surfaces, design signals, available sensors |
|
|
281
316
|
| `vise plan [path] --request "..."` | Produce a grounded implementation plan with intake questions and docs citations |
|
|
282
317
|
| `vise plan-harness [path] --request "..."` | (Pre-planning step) Build the harness around the request |
|
|
283
|
-
| `vise init [path] --request "..."` | Write the `sp-vise/` compliance contract
|
|
318
|
+
| `vise init [path] --request "..." [--answer key=value]` | Write the `sp-vise/` compliance contract after blocking intake is answered; returns `needs-clarification` and exits 7 if required answers are missing |
|
|
319
|
+
| `vise blocks list --registry <path>` | Read a social.plus Block Factory registry |
|
|
320
|
+
| `vise blocks plan [path] --block <id> --registry <path>` | Plan safe block package, source-anchor, sidecar, and sensor changes |
|
|
321
|
+
| `vise blocks add [path] --block <id> --registry <path> [--dry-run\|--apply]` | Dry-run or apply safe block installation inside a customer project |
|
|
322
|
+
| `vise blocks validate [path] [--block <id>] --registry <path>` | Validate installed block sidecar state, package presence, and source anchors |
|
|
284
323
|
|
|
285
324
|
### Design contract (UI generation)
|
|
286
325
|
|
|
@@ -409,12 +448,13 @@ jobs:
|
|
|
409
448
|
|
|
410
449
|
| Code | Meaning |
|
|
411
450
|
|---|---|
|
|
412
|
-
| `0` | All rules pass
|
|
451
|
+
| `0` | All rules pass, all baseline capabilities are present or opted-out, and any selected optional capabilities pass |
|
|
413
452
|
| `1` | One or more rules need attestation |
|
|
414
453
|
| `2` | One or more rules have deterministic failures |
|
|
415
454
|
| `3` | One or more blockers fired (missing prerequisite, e.g. `google-services.json`) |
|
|
416
455
|
| `4` | Contract drift — rules in `sp-vise/compliance.json` no longer match the current ruleset |
|
|
417
|
-
| `5` | One or more
|
|
456
|
+
| `5` | One or more baseline capabilities are neither implemented nor validly opted-out — add the capability or place `// vise: scope-omit <id> — <reason>` |
|
|
457
|
+
| `6` | One or more explicitly selected optional capabilities failed source sensors |
|
|
418
458
|
|
|
419
459
|
`vise check --ci` is read-only. It never updates `sp-vise/`. The JSON output includes a `ci` block with structured details for pipeline logs.
|
|
420
460
|
|
|
@@ -422,11 +462,12 @@ jobs:
|
|
|
422
462
|
|
|
423
463
|
## Compliance Contract
|
|
424
464
|
|
|
425
|
-
After `vise init`, your project gets a `sp-vise/` directory. These files become part of your repo and travel through code review:
|
|
465
|
+
After a successful `vise init`, your project gets a `sp-vise/` directory. If init returns `needs-clarification`, no compliance sidecar is written; answer the blocking questions and run init again. These files become part of your repo and travel through code review:
|
|
426
466
|
|
|
427
467
|
| File | Created by | What it contains |
|
|
428
468
|
|---|---|---|
|
|
429
469
|
| `sp-vise/compliance.json` | `vise init` | The rules selected for this integration, the Vise version, the ruleset digest, the target app surface, and an optional engagement link. |
|
|
470
|
+
| `sp-vise/intake.json` | `vise init` | The request, outcome, intake answers, remaining blocking count, and any retrospective `--allow-unresolved-intake` acknowledgement. |
|
|
430
471
|
| `sp-vise/attestations/*.json` | `vise sync` (deterministic) or `vise attest` (host-agent / human) | Per-rule evidence: signer, confidence, rationale, cited files (with source fingerprints for drift detection). |
|
|
431
472
|
| `sp-vise/inspection.json` | `vise init` | The platform, monorepo surface, and design-token signals detected at init time. |
|
|
432
473
|
| `sp-vise/design-contract.json` | `vise design extract` | The extracted design contract: declared tokens, breakpoints, advisory components, source file digests (for freshness detection), and a stable digest over design facts. |
|
package/dist/capabilities.js
CHANGED
|
@@ -12,16 +12,14 @@
|
|
|
12
12
|
* (`// vise: scope-omit <id> <reason>`), which `check` reads and reports. The
|
|
13
13
|
* agent subtracts with justification — it doesn't have to remember the set.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Every capability below is anchored on SDK symbols for exactly that reason, but
|
|
18
|
-
* the output is advisory regardless.
|
|
15
|
+
* Baseline capability gates must stay narrow. For add-feed, pagination is the
|
|
16
|
+
* mandatory capability; richer composer affordances are selected optional sensors.
|
|
19
17
|
*/
|
|
20
18
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
21
19
|
import path from "node:path";
|
|
22
|
-
// Canonical, Vise-authored capability
|
|
23
|
-
// rules/*.yaml + SKILL.md, not guessed).
|
|
24
|
-
//
|
|
20
|
+
// Canonical, Vise-authored capability catalog — the SDK feature surface
|
|
21
|
+
// (grounded in rules/*.yaml + SKILL.md, not guessed). Baseline gating is applied
|
|
22
|
+
// by baselineCapabilities(); not every catalog item is mandatory for every outcome.
|
|
25
23
|
export const CAPABILITIES = [
|
|
26
24
|
// ── add-feed: post dataTypes (a feed parent is usually 'text'; rich content
|
|
27
25
|
// rides on child posts — render each type the feed can return) ──────────
|
|
@@ -368,14 +366,124 @@ export const CAPABILITIES = [
|
|
|
368
366
|
hint: "respect Amity's server-side notification settings/preferences (getSettings)",
|
|
369
367
|
},
|
|
370
368
|
];
|
|
369
|
+
// Optional capabilities are not baseline compliance. `vise plan` offers them as
|
|
370
|
+
// explicit feed-forward choices; `vise init --answer feed_optional_capabilities=...`
|
|
371
|
+
// records selected choices; `vise check` then evaluates these source sensors.
|
|
372
|
+
export const OPTIONAL_CAPABILITIES = [
|
|
373
|
+
{
|
|
374
|
+
id: "post-image-upload",
|
|
375
|
+
label: "Image/file upload composer",
|
|
376
|
+
outcomes: ["add-feed"],
|
|
377
|
+
aliases: [/image(?:\/file)? upload/i, /image post/i, /file post/i, /attachments?/i, /media upload/i],
|
|
378
|
+
sensors: [
|
|
379
|
+
{ label: "FileRepository.uploadImage or uploadImage", regex: /FileRepository\.uploadImage\s*\(|(?<![.\w])uploadImage\s*\(/ },
|
|
380
|
+
{ label: "createPost attachments array", regex: /createPost\s*\(\s*\{[\s\S]*?attachments\s*:/ },
|
|
381
|
+
],
|
|
382
|
+
hint: "If the user opts into media posting, implement file picker -> FileRepository.uploadImage(file) -> createPost({ ..., attachments: [{ fileId, fileType: 'image' }] }).",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: "post-poll-creation",
|
|
386
|
+
label: "Poll composer and voting",
|
|
387
|
+
outcomes: ["add-feed"],
|
|
388
|
+
aliases: [/poll creation/i, /poll composer/i, /create poll/i, /\bpolls?\b/i],
|
|
389
|
+
sensors: [
|
|
390
|
+
{ label: "PollRepository.createPoll", regex: /PollRepository\.createPoll\s*\(|(?<![.\w])createPoll\s*\(/ },
|
|
391
|
+
{
|
|
392
|
+
label: "createPost links pollId",
|
|
393
|
+
regex: /createPost\s*\(\s*\{[\s\S]*?data\s*:\s*\{[\s\S]*?\bpollId\b\s*(?::|[,}])/,
|
|
394
|
+
},
|
|
395
|
+
{ label: "PollRepository.votePoll", regex: /PollRepository\.votePoll\s*\(|(?<![.\w])votePoll\s*\(/ },
|
|
396
|
+
],
|
|
397
|
+
hint: "If the user opts into polls, implement the createPoll -> createPost({ data: { pollId } }) chain and the votePoll read-side interaction.",
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "post-edit",
|
|
401
|
+
label: "Edit own post",
|
|
402
|
+
outcomes: ["add-feed"],
|
|
403
|
+
aliases: [/edit post/i, /edit own post/i, /author edit/i, /\beditPost\b/i],
|
|
404
|
+
sensors: [
|
|
405
|
+
{ label: "PostRepository.editPost", regex: /PostRepository\.editPost\s*\(|(?<![.\w])editPost\s*\(/ },
|
|
406
|
+
],
|
|
407
|
+
hint: "If the user opts into author management, show edit only for post.postedUserId === currentUserId and call PostRepository.editPost with updated text data.",
|
|
408
|
+
},
|
|
409
|
+
];
|
|
371
410
|
const ADVISORY_NOTE = "Build each missing capability, or opt out with a recorded reason: `// vise: scope-omit <id> — <reason>`. A scope-omit marker without a reason is invalid and still counts as missing. Missing capabilities that are neither built nor validly opted-out cause `vise check` to exit with status `completeness-gap` (exit code 5).";
|
|
372
|
-
|
|
411
|
+
const BASELINE_CAPABILITY_IDS_BY_OUTCOME = {
|
|
412
|
+
"add-feed": ["pagination"],
|
|
413
|
+
};
|
|
414
|
+
function baselineCapabilities(outcome) {
|
|
415
|
+
const caps = CAPABILITIES.filter((c) => c.outcomes.includes(outcome));
|
|
416
|
+
const baselineIds = BASELINE_CAPABILITY_IDS_BY_OUTCOME[outcome];
|
|
417
|
+
if (!baselineIds) {
|
|
418
|
+
return caps;
|
|
419
|
+
}
|
|
420
|
+
const baseline = new Set(baselineIds);
|
|
421
|
+
return caps.filter((capability) => baseline.has(capability.id));
|
|
422
|
+
}
|
|
423
|
+
/** The Vise-authored baseline capability checklist for an outcome. */
|
|
373
424
|
export function capabilityChecklist(outcome) {
|
|
374
|
-
return
|
|
425
|
+
return baselineCapabilities(outcome).map((c) => ({ id: c.id, label: c.label, hint: c.hint }));
|
|
426
|
+
}
|
|
427
|
+
/** Optional feed-forward choices. These are not baseline completeness requirements. */
|
|
428
|
+
export function optionalCapabilityChecklist(outcome) {
|
|
429
|
+
return OPTIONAL_CAPABILITIES.filter((c) => c.outcomes.includes(outcome)).map((c) => ({ id: c.id, label: c.label, hint: c.hint }));
|
|
430
|
+
}
|
|
431
|
+
export function selectedOptionalCapabilityIds(outcome, answers = {}, _request = "") {
|
|
432
|
+
const candidates = OPTIONAL_CAPABILITIES.filter((capability) => capability.outcomes.includes(outcome));
|
|
433
|
+
if (candidates.length === 0) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
const answerText = [answers.feed_optional_capabilities, answers.optional_capabilities, answers.selected_capabilities]
|
|
437
|
+
.filter(Boolean)
|
|
438
|
+
.join("\n");
|
|
439
|
+
const normalized = answerText.toLowerCase();
|
|
440
|
+
if (!normalized.trim() || /\b(none|no optional|default only)\b/.test(normalized)) {
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
const selected = new Set();
|
|
444
|
+
for (const capability of candidates) {
|
|
445
|
+
if (normalized.includes(capability.id.toLowerCase()) || capability.aliases.some((alias) => alias.test(answerText))) {
|
|
446
|
+
selected.add(capability.id);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return [...selected].sort();
|
|
450
|
+
}
|
|
451
|
+
export function assessSelectedOptionalCapabilities(source, outcome, selectedIds = []) {
|
|
452
|
+
const selected = [...new Set(selectedIds.map((id) => id.trim()).filter(Boolean))].sort();
|
|
453
|
+
if (selected.length === 0) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const applicable = new Map(OPTIONAL_CAPABILITIES.filter((capability) => capability.outcomes.includes(outcome)).map((capability) => [capability.id, capability]));
|
|
457
|
+
const passed = [];
|
|
458
|
+
const failed = [];
|
|
459
|
+
const unknown = [];
|
|
460
|
+
for (const id of selected) {
|
|
461
|
+
const capability = applicable.get(id);
|
|
462
|
+
if (!capability) {
|
|
463
|
+
unknown.push(id);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const present = capability.sensors.filter((sensor) => sensor.regex.test(source)).map((sensor) => sensor.label);
|
|
467
|
+
const missing = capability.sensors.filter((sensor) => !sensor.regex.test(source)).map((sensor) => sensor.label);
|
|
468
|
+
if (missing.length === 0) {
|
|
469
|
+
passed.push({ id, label: capability.label, present });
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
failed.push({ id, label: capability.label, missing, hint: capability.hint });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
outcome,
|
|
477
|
+
selected,
|
|
478
|
+
passed,
|
|
479
|
+
failed,
|
|
480
|
+
unknown,
|
|
481
|
+
note: "Selected optional capabilities are enforced only after the user or host agent opts in through `feed_optional_capabilities`. They are source sensors, not baseline compliance rules.",
|
|
482
|
+
};
|
|
375
483
|
}
|
|
376
484
|
/** Pure assessment over already-read source text. */
|
|
377
485
|
export function assessCompleteness(source, outcome) {
|
|
378
|
-
const caps =
|
|
486
|
+
const caps = baselineCapabilities(outcome);
|
|
379
487
|
const optOuts = new Map();
|
|
380
488
|
const invalidOptOuts = new Map();
|
|
381
489
|
const omitPattern = /vise:\s*scope-omit\s+([a-z][\w-]*)\s*(?:[—:|-]+\s*(.*))?/gi;
|
|
@@ -422,9 +530,15 @@ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "ou
|
|
|
422
530
|
const MAX_FILES = 1500;
|
|
423
531
|
const MAX_FILE_BYTES = 1_000_000;
|
|
424
532
|
export async function assessProjectCompleteness(root, outcome) {
|
|
425
|
-
if (
|
|
533
|
+
if (baselineCapabilities(outcome).length === 0) {
|
|
426
534
|
return null; // outcome has no completeness checklist
|
|
427
535
|
}
|
|
536
|
+
return assessCompleteness(await readProjectSource(root), outcome);
|
|
537
|
+
}
|
|
538
|
+
export async function assessProjectSelectedOptionalCapabilities(root, outcome, selectedIds = []) {
|
|
539
|
+
return assessSelectedOptionalCapabilities(await readProjectSource(root), outcome, selectedIds);
|
|
540
|
+
}
|
|
541
|
+
async function readProjectSource(root) {
|
|
428
542
|
const resolved = path.resolve(root);
|
|
429
543
|
const parts = [];
|
|
430
544
|
const stack = [resolved];
|
|
@@ -462,5 +576,5 @@ export async function assessProjectCompleteness(root, outcome) {
|
|
|
462
576
|
}
|
|
463
577
|
}
|
|
464
578
|
}
|
|
465
|
-
return
|
|
579
|
+
return parts.join("\n");
|
|
466
580
|
}
|
package/dist/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
|
|
|
15
15
|
import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
|
|
16
16
|
import { runSensorsTool } from "./tools/sensors.js";
|
|
17
17
|
import { getSdkFactsTool } from "./tools/sdkFacts.js";
|
|
18
|
+
import { addBlockInstall, listRegistryBlocks, planBlockInstall, validateBlockInstall } from "./tools/blocks.js";
|
|
18
19
|
import { debugIssueTool, debugIssue } from "./tools/debug.js";
|
|
19
20
|
import { packageName, packageVersion } from "./version.js";
|
|
20
21
|
const tools = new Map([
|
|
@@ -217,9 +218,62 @@ async function handleCli(args) {
|
|
|
217
218
|
});
|
|
218
219
|
return "exit";
|
|
219
220
|
}
|
|
221
|
+
if (command === "blocks") {
|
|
222
|
+
const sub = args[1];
|
|
223
|
+
const subArgs = args.slice(2);
|
|
224
|
+
if (sub === "list") {
|
|
225
|
+
assertOnlyKnownFlags(subArgs, ["registry", "format"], "blocks list");
|
|
226
|
+
assertJsonFormat(subArgs, "blocks list");
|
|
227
|
+
console.log(JSON.stringify(await listRegistryBlocks(requiredFlagValue(subArgs, "registry", "blocks list requires --registry.")), null, 2));
|
|
228
|
+
return "exit";
|
|
229
|
+
}
|
|
230
|
+
if (sub === "plan") {
|
|
231
|
+
assertOnlyKnownFlags(subArgs, ["block", "surface", "surface-path", "registry", "format"], "blocks plan");
|
|
232
|
+
assertJsonFormat(subArgs, "blocks plan");
|
|
233
|
+
console.log(JSON.stringify(await planBlockInstall({
|
|
234
|
+
repoPath: positionalRepoPath(subArgs),
|
|
235
|
+
blockId: requiredFlagValue(subArgs, "block", "blocks plan requires --block."),
|
|
236
|
+
registryPath: requiredFlagValue(subArgs, "registry", "blocks plan requires --registry."),
|
|
237
|
+
surfacePath: flagValue(subArgs, "surface") ?? flagValue(subArgs, "surface-path"),
|
|
238
|
+
}), null, 2));
|
|
239
|
+
return "exit";
|
|
240
|
+
}
|
|
241
|
+
if (sub === "add") {
|
|
242
|
+
assertOnlyKnownFlags(subArgs, ["block", "surface", "surface-path", "registry", "package-source", "dry-run", "apply", "format"], "blocks add");
|
|
243
|
+
assertJsonFormat(subArgs, "blocks add");
|
|
244
|
+
console.log(JSON.stringify(await addBlockInstall({
|
|
245
|
+
repoPath: positionalRepoPath(subArgs),
|
|
246
|
+
blockId: requiredFlagValue(subArgs, "block", "blocks add requires --block."),
|
|
247
|
+
registryPath: requiredFlagValue(subArgs, "registry", "blocks add requires --registry."),
|
|
248
|
+
surfacePath: flagValue(subArgs, "surface") ?? flagValue(subArgs, "surface-path"),
|
|
249
|
+
packageSource: flagValue(subArgs, "package-source"),
|
|
250
|
+
dryRun: hasFlag(subArgs, "dry-run"),
|
|
251
|
+
apply: hasFlag(subArgs, "apply"),
|
|
252
|
+
}), null, 2));
|
|
253
|
+
return "exit";
|
|
254
|
+
}
|
|
255
|
+
if (sub === "validate") {
|
|
256
|
+
assertOnlyKnownFlags(subArgs, ["block", "surface", "surface-path", "registry", "format"], "blocks validate");
|
|
257
|
+
assertJsonFormat(subArgs, "blocks validate");
|
|
258
|
+
console.log(JSON.stringify(await validateBlockInstall({
|
|
259
|
+
repoPath: positionalRepoPath(subArgs),
|
|
260
|
+
blockId: flagValue(subArgs, "block"),
|
|
261
|
+
registryPath: requiredFlagValue(subArgs, "registry", "blocks validate requires --registry."),
|
|
262
|
+
surfacePath: flagValue(subArgs, "surface") ?? flagValue(subArgs, "surface-path"),
|
|
263
|
+
}), null, 2));
|
|
264
|
+
return "exit";
|
|
265
|
+
}
|
|
266
|
+
console.error(`Unknown blocks subcommand: ${sub ?? "(none)"}. Expected "list", "plan", "add", or "validate".`);
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
return "exit";
|
|
269
|
+
}
|
|
220
270
|
if (command === "init") {
|
|
221
|
-
assertOnlyKnownFlags(args, ["request", "surface", "surface-path"], "init");
|
|
222
|
-
|
|
271
|
+
assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer", "allow-unresolved-intake"], "init");
|
|
272
|
+
const result = await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path"), keyValueFlag(args, "answer"), { allowUnresolvedIntake: hasFlag(args, "allow-unresolved-intake") });
|
|
273
|
+
if (result.status === "needs-clarification" && typeof result.exitCode === "number") {
|
|
274
|
+
process.exitCode = result.exitCode;
|
|
275
|
+
}
|
|
276
|
+
console.log(JSON.stringify(result, null, 2));
|
|
223
277
|
return "exit";
|
|
224
278
|
}
|
|
225
279
|
if (command === "check") {
|
|
@@ -494,6 +548,20 @@ Usage:
|
|
|
494
548
|
vise sdk-facts --platform typescript --capability comments --format json
|
|
495
549
|
vise sdk-facts --platform react-native --capability reactions --include-symbols
|
|
496
550
|
vise sdk-facts --platform android --surface-dir ./sdk-surface --format json`;
|
|
551
|
+
}
|
|
552
|
+
if (command === "blocks") {
|
|
553
|
+
return `${packageName} blocks
|
|
554
|
+
|
|
555
|
+
Install and validate social.plus Block Factory blocks inside customer projects.
|
|
556
|
+
|
|
557
|
+
Usage:
|
|
558
|
+
vise blocks list --registry <path> --format json
|
|
559
|
+
vise blocks plan <repoPath> --block comments --registry <path> --format json
|
|
560
|
+
vise blocks add <repoPath> --block comments --registry <path> --package-source <npm|path|tarball> [--dry-run|--apply]
|
|
561
|
+
vise blocks validate <repoPath> [--block comments] --registry <path> --format json
|
|
562
|
+
|
|
563
|
+
Safety:
|
|
564
|
+
Dry-run is the default. Apply mode only edits package manifests, explicit source anchors, and sp-vise/blocks.json.`;
|
|
497
565
|
}
|
|
498
566
|
if (command === "init") {
|
|
499
567
|
return `${packageName} init
|
|
@@ -501,7 +569,10 @@ Usage:
|
|
|
501
569
|
Initialize the local sp-vise compliance sidecar for one integration request.
|
|
502
570
|
|
|
503
571
|
Usage:
|
|
504
|
-
vise init [repoPath] --request "Add a social feed" [--surface apps/web]
|
|
572
|
+
vise init [repoPath] --request "Add a social feed" [--surface apps/web]
|
|
573
|
+
vise init [repoPath] --request "Add a social feed" --answer feed_target="existing app community picker"
|
|
574
|
+
vise init [repoPath] --request "Add a social feed" --answer feed_optional_capabilities=post-poll-creation
|
|
575
|
+
vise init [repoPath] --request "Add a social feed" --allow-unresolved-intake`;
|
|
505
576
|
}
|
|
506
577
|
if (command === "check") {
|
|
507
578
|
return `${packageName} check
|
|
@@ -604,6 +675,7 @@ Usage:
|
|
|
604
675
|
vise validate [repoPath] Validate setup and common risks
|
|
605
676
|
vise run-sensors [repoPath] Run detected project sensors
|
|
606
677
|
vise sdk-facts --platform ... Internal SDK surface facts for Block Factory
|
|
678
|
+
vise blocks ... Install and validate Block Factory blocks
|
|
607
679
|
vise design extract <prototype> Extract a design contract from an HTML/CSS prototype
|
|
608
680
|
vise design check [repoPath] Advisory (non-blocking) UI-vs-contract conformance report
|
|
609
681
|
vise design preview [repoPath] Write an HTML visual review of the contract + conformance
|
|
@@ -857,7 +929,7 @@ function ciCheckResult(result) {
|
|
|
857
929
|
};
|
|
858
930
|
}
|
|
859
931
|
function positionalRepoPath(args) {
|
|
860
|
-
const flagsWithValues = new Set(["request", "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"]);
|
|
932
|
+
const flagsWithValues = new Set(["request", "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"]);
|
|
861
933
|
for (let index = 0; index < args.length; index += 1) {
|
|
862
934
|
const arg = args[index];
|
|
863
935
|
if (!arg) {
|
|
@@ -879,7 +951,7 @@ function positionalRepoPath(args) {
|
|
|
879
951
|
}
|
|
880
952
|
function requiredPositionalText(args, message) {
|
|
881
953
|
const values = [];
|
|
882
|
-
const flagsWithValues = new Set(["request", "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"]);
|
|
954
|
+
const flagsWithValues = new Set(["request", "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"]);
|
|
883
955
|
for (let index = 0; index < args.length; index += 1) {
|
|
884
956
|
const arg = args[index];
|
|
885
957
|
if (!arg) {
|
|
@@ -936,6 +1008,12 @@ function requiredFlagValue(args, name, message) {
|
|
|
936
1008
|
}
|
|
937
1009
|
return value;
|
|
938
1010
|
}
|
|
1011
|
+
function assertJsonFormat(args, commandName) {
|
|
1012
|
+
const format = flagValue(args, "format") ?? "json";
|
|
1013
|
+
if (format !== "json") {
|
|
1014
|
+
throw new Error(`${commandName} currently supports --format json only.`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
939
1017
|
function optionalNumberFlag(args, name) {
|
|
940
1018
|
const value = flagValue(args, name);
|
|
941
1019
|
if (!value) {
|