@amityco/social-plus-vise 0.14.4 → 0.14.5
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 +10 -0
- package/README.md +6 -5
- package/dist/capabilities.js +126 -12
- package/dist/server.js +4 -3
- package/dist/tools/compliance.js +48 -11
- package/dist/tools/integration.js +24 -1
- package/package.json +1 -1
- package/skills/social-plus-vise/SKILL.md +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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.5 — 2026-06-04
|
|
8
|
+
|
|
9
|
+
**Theme:** Optional feed capabilities become explicit opt-in sensors.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **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.
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
7
17
|
## 0.14.4 — 2026-06-04
|
|
8
18
|
|
|
9
19
|
**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,9 +77,9 @@ 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
|
|
|
@@ -409,12 +409,13 @@ jobs:
|
|
|
409
409
|
|
|
410
410
|
| Code | Meaning |
|
|
411
411
|
|---|---|
|
|
412
|
-
| `0` | All rules pass
|
|
412
|
+
| `0` | All rules pass, all baseline capabilities are present or opted-out, and any selected optional capabilities pass |
|
|
413
413
|
| `1` | One or more rules need attestation |
|
|
414
414
|
| `2` | One or more rules have deterministic failures |
|
|
415
415
|
| `3` | One or more blockers fired (missing prerequisite, e.g. `google-services.json`) |
|
|
416
416
|
| `4` | Contract drift — rules in `sp-vise/compliance.json` no longer match the current ruleset |
|
|
417
|
-
| `5` | One or more
|
|
417
|
+
| `5` | One or more baseline capabilities are neither implemented nor validly opted-out — add the capability or place `// vise: scope-omit <id> — <reason>` |
|
|
418
|
+
| `6` | One or more explicitly selected optional capabilities failed source sensors |
|
|
418
419
|
|
|
419
420
|
`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
421
|
|
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
|
@@ -218,8 +218,8 @@ async function handleCli(args) {
|
|
|
218
218
|
return "exit";
|
|
219
219
|
}
|
|
220
220
|
if (command === "init") {
|
|
221
|
-
assertOnlyKnownFlags(args, ["request", "surface", "surface-path"], "init");
|
|
222
|
-
console.log(JSON.stringify(await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path")), null, 2));
|
|
221
|
+
assertOnlyKnownFlags(args, ["request", "surface", "surface-path", "answer"], "init");
|
|
222
|
+
console.log(JSON.stringify(await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path"), keyValueFlag(args, "answer")), null, 2));
|
|
223
223
|
return "exit";
|
|
224
224
|
}
|
|
225
225
|
if (command === "check") {
|
|
@@ -501,7 +501,8 @@ Usage:
|
|
|
501
501
|
Initialize the local sp-vise compliance sidecar for one integration request.
|
|
502
502
|
|
|
503
503
|
Usage:
|
|
504
|
-
vise init [repoPath] --request "Add a social feed" [--surface apps/web]
|
|
504
|
+
vise init [repoPath] --request "Add a social feed" [--surface apps/web]
|
|
505
|
+
vise init [repoPath] --request "Add a social feed" --answer feed_optional_capabilities=post-poll-creation`;
|
|
505
506
|
}
|
|
506
507
|
if (command === "check") {
|
|
507
508
|
return `${packageName} check
|
package/dist/tools/compliance.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { assessProjectCompleteness } from "../capabilities.js";
|
|
5
|
+
import { assessProjectCompleteness, assessProjectSelectedOptionalCapabilities, selectedOptionalCapabilityIds, } from "../capabilities.js";
|
|
6
6
|
import { classifyOutcome } from "../outcomes.js";
|
|
7
7
|
import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
|
|
8
8
|
import { packageVersion } from "../version.js";
|
|
@@ -21,13 +21,18 @@ export const initComplianceTool = {
|
|
|
21
21
|
repoPath: { type: "string" },
|
|
22
22
|
request: { type: "string" },
|
|
23
23
|
surfacePath: { type: "string" },
|
|
24
|
+
answers: {
|
|
25
|
+
type: "object",
|
|
26
|
+
description: "Optional intake answers from vise plan, including feed_optional_capabilities.",
|
|
27
|
+
additionalProperties: { type: "string" },
|
|
28
|
+
},
|
|
24
29
|
},
|
|
25
30
|
required: ["repoPath", "request"],
|
|
26
31
|
additionalProperties: false,
|
|
27
32
|
},
|
|
28
33
|
async call(input) {
|
|
29
34
|
const args = objectInput(input);
|
|
30
|
-
return textResult(await initCompliance(stringField(args, "repoPath"), stringField(args, "request"), optionalStringField(args, "surfacePath")));
|
|
35
|
+
return textResult(await initCompliance(stringField(args, "repoPath"), stringField(args, "request"), optionalStringField(args, "surfacePath"), answersFromInput(args.answers)));
|
|
31
36
|
},
|
|
32
37
|
};
|
|
33
38
|
export const checkComplianceTool = {
|
|
@@ -46,6 +51,18 @@ export const checkComplianceTool = {
|
|
|
46
51
|
return textResult(await checkCompliance(stringField(args, "repoPath")));
|
|
47
52
|
},
|
|
48
53
|
};
|
|
54
|
+
function answersFromInput(raw) {
|
|
55
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
const answers = {};
|
|
59
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
60
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
61
|
+
answers[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return answers;
|
|
65
|
+
}
|
|
49
66
|
export const syncComplianceTool = {
|
|
50
67
|
name: "sync_compliance",
|
|
51
68
|
description: "Persist current deterministic-pass compliance results into the sp-vise sidecar.",
|
|
@@ -225,10 +242,11 @@ async function readEngagement(repoRoot) {
|
|
|
225
242
|
function engagementPath(repoRoot) {
|
|
226
243
|
return path.join(sidecarDir(repoRoot), "engagement.json");
|
|
227
244
|
}
|
|
228
|
-
export async function initCompliance(repoPath, request, surfacePath) {
|
|
245
|
+
export async function initCompliance(repoPath, request, surfacePath, answers = {}) {
|
|
229
246
|
const repoRoot = path.resolve(repoPath);
|
|
230
247
|
const inspection = await inspectProject(repoRoot, surfacePath);
|
|
231
248
|
const outcome = classifyOutcome(request);
|
|
249
|
+
const selectedOptionalCapabilities = selectedOptionalCapabilityIds(outcome, answers, request);
|
|
232
250
|
const rules = await applicableRules(outcome, inspection.platforms);
|
|
233
251
|
const refs = rules.map(ruleRef); // minimal shape — stable digest input
|
|
234
252
|
const fileRefs = rules.map(ruleRefForFile); // adds title for human/agent readers
|
|
@@ -254,6 +272,7 @@ export async function initCompliance(repoPath, request, surfacePath) {
|
|
|
254
272
|
inferred_tokens: designContract.stats.inferred_tokens,
|
|
255
273
|
}
|
|
256
274
|
: undefined,
|
|
275
|
+
selected_optional_capabilities: selectedOptionalCapabilities.length > 0 ? selectedOptionalCapabilities : undefined,
|
|
257
276
|
};
|
|
258
277
|
await mkdir(attestationsDir(repoRoot), { recursive: true });
|
|
259
278
|
await writeJson(compliancePath(repoRoot), compliance);
|
|
@@ -282,6 +301,7 @@ export async function initCompliance(repoPath, request, surfacePath) {
|
|
|
282
301
|
rules: refs.length,
|
|
283
302
|
engagement_id: engagement?.engagement_id,
|
|
284
303
|
...(compliance.design_contract && { design_contract: compliance.design_contract }),
|
|
304
|
+
...(selectedOptionalCapabilities.length > 0 && { selected_optional_capabilities: selectedOptionalCapabilities }),
|
|
285
305
|
...(warnings.length > 0 && { warnings }),
|
|
286
306
|
nextStep: "Run vise check, then implement until rules pass deterministically or are attested.",
|
|
287
307
|
};
|
|
@@ -454,31 +474,48 @@ export async function checkCompliance(repoPath) {
|
|
|
454
474
|
const hasDeterministicFailure = results.some((result) => result.status === "deterministic-fail");
|
|
455
475
|
// "advisory" status is intentionally excluded — advisory rules surface but never block.
|
|
456
476
|
const needsAttestation = results.some((result) => result.status === "attestation-needed" || result.status === "stale");
|
|
457
|
-
// Precedence: blocked (3) > deterministic-failures (2) > needs-attestation (1) >
|
|
477
|
+
// Precedence: blocked (3) > deterministic-failures (2) > needs-attestation (1) >
|
|
478
|
+
// completeness-gap (5) > selected-capability-failures (6) > green (0).
|
|
458
479
|
// Contract drift (exit 4) is handled earlier and short-circuits the loop.
|
|
459
480
|
// Completeness-gap: capabilities that are neither present nor validly opted-out require an explicit decision
|
|
460
481
|
// (build it, or place // vise: scope-omit <id> — <reason>). The scope-omit escape hatch keeps this
|
|
461
482
|
// FP-free because any capability can be excluded with a recorded reason. Failure to assess is silently ignored.
|
|
462
483
|
const completeness = (await assessProjectCompleteness(inspection.effectiveRoot, compliance.outcome).catch(() => null)) ?? undefined;
|
|
463
484
|
const hasCompletenessGap = (completeness?.missing.length ?? 0) > 0;
|
|
485
|
+
const selectedOptionalCapabilities = (await assessProjectSelectedOptionalCapabilities(inspection.effectiveRoot, compliance.outcome, compliance.selected_optional_capabilities ?? []).catch(() => null)) ?? undefined;
|
|
486
|
+
const hasSelectedOptionalFailures = ((selectedOptionalCapabilities?.failed.length ?? 0) > 0) || ((selectedOptionalCapabilities?.unknown.length ?? 0) > 0);
|
|
464
487
|
// Blocked wins because the agent cannot proceed without customer input;
|
|
465
488
|
// surfacing a smaller failure first would distract from the real blocker.
|
|
489
|
+
const status = hasBlocked
|
|
490
|
+
? "blocked"
|
|
491
|
+
: hasDeterministicFailure
|
|
492
|
+
? "deterministic-failures"
|
|
493
|
+
: needsAttestation
|
|
494
|
+
? "needs-attestation"
|
|
495
|
+
: hasCompletenessGap
|
|
496
|
+
? "completeness-gap"
|
|
497
|
+
: hasSelectedOptionalFailures
|
|
498
|
+
? "selected-capability-failures"
|
|
499
|
+
: "green";
|
|
466
500
|
return {
|
|
467
|
-
status
|
|
468
|
-
|
|
501
|
+
status,
|
|
502
|
+
exitCode: hasBlocked
|
|
503
|
+
? 3
|
|
469
504
|
: hasDeterministicFailure
|
|
470
|
-
?
|
|
505
|
+
? 2
|
|
471
506
|
: needsAttestation
|
|
472
|
-
?
|
|
507
|
+
? 1
|
|
473
508
|
: hasCompletenessGap
|
|
474
|
-
?
|
|
475
|
-
:
|
|
476
|
-
|
|
509
|
+
? 5
|
|
510
|
+
: hasSelectedOptionalFailures
|
|
511
|
+
? 6
|
|
512
|
+
: 0,
|
|
477
513
|
outcome: compliance.outcome,
|
|
478
514
|
surfacePath: compliance.surface?.path,
|
|
479
515
|
summary,
|
|
480
516
|
rules: results,
|
|
481
517
|
...(completeness && (completeness.missing.length > 0 || completeness.optedOut.length > 0 || completeness.present.length > 0) ? { completeness } : {}),
|
|
518
|
+
...(selectedOptionalCapabilities ? { selectedOptionalCapabilities } : {}),
|
|
482
519
|
};
|
|
483
520
|
}
|
|
484
521
|
export async function syncCompliance(repoPath) {
|
|
@@ -2,7 +2,7 @@ import { access, readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { BROAD_SOCIAL_REGEX, DESIGN_REGEX, classifyOutcome, getOutcomeDefinition, hasAnswer, planContextFor, } from "../outcomes.js";
|
|
4
4
|
import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
|
|
5
|
-
import { capabilityChecklist } from "../capabilities.js";
|
|
5
|
+
import { capabilityChecklist, optionalCapabilityChecklist, selectedOptionalCapabilityIds } from "../capabilities.js";
|
|
6
6
|
import { applicableComplianceRuleSummaries } from "./compliance.js";
|
|
7
7
|
import { buildDesignBrief, readDesignContract } from "./design.js";
|
|
8
8
|
import { sdkVersionGuidance } from "./sdkVersion.js";
|
|
@@ -122,6 +122,7 @@ async function buildIntegrationPlan(repoPath, request, surfacePath, answers = {}
|
|
|
122
122
|
evidencePolicy: "Every implementation step must cite at least one detected file, docs page, validator rule, or required user input. If evidence is missing, stop and ask the user instead of inventing details.",
|
|
123
123
|
designContract: designContract ? designContractGuidance(designContract) : undefined,
|
|
124
124
|
completenessChecklist: completenessChecklistFor(outcome),
|
|
125
|
+
optionalCapabilities: optionalCapabilitiesFor(outcome, ctx.answers, request),
|
|
125
126
|
sdkVersion,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
@@ -139,6 +140,18 @@ function completenessChecklistFor(outcome) {
|
|
|
139
140
|
capabilities: items,
|
|
140
141
|
};
|
|
141
142
|
}
|
|
143
|
+
function optionalCapabilitiesFor(outcome, answers, request) {
|
|
144
|
+
const choices = optionalCapabilityChecklist(outcome);
|
|
145
|
+
if (choices.length === 0) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
answerId: "feed_optional_capabilities",
|
|
150
|
+
note: "Optional feed capabilities are feed-forward choices, not baseline compliance. If the user opts in, pass the selected ids through `--answer feed_optional_capabilities=<id[,id]>` on `vise plan` and `vise init`; `vise check` will then run source sensors for those selected capabilities.",
|
|
151
|
+
choices,
|
|
152
|
+
selected: selectedOptionalCapabilityIds(outcome, answers, request),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
142
155
|
// Build advisory UI-generation guidance from an extracted design contract.
|
|
143
156
|
// Declared tokens are surfaced with their custom-property name (so the agent
|
|
144
157
|
// references `var(--x)` / maps it per platform); inferred tokens carry their
|
|
@@ -216,6 +229,16 @@ function intakeFor(ctx, outcomeQuestions, outcome, brief) {
|
|
|
216
229
|
blocksImplementationWhenMissing: false,
|
|
217
230
|
});
|
|
218
231
|
}
|
|
232
|
+
if (outcome === "add-feed" && optionalCapabilityChecklist(outcome).length > 0 && !hasAnswer(ctx.answers, "feed_optional_capabilities")) {
|
|
233
|
+
questions.push({
|
|
234
|
+
id: "feed_optional_capabilities",
|
|
235
|
+
question: "Which optional feed capabilities should be in scope: post-image-upload, post-poll-creation, post-edit, or none?",
|
|
236
|
+
why: "These capabilities are useful for full feeds, but should become enforceable only after the customer explicitly opts in.",
|
|
237
|
+
required: false,
|
|
238
|
+
blocksImplementationWhenMissing: false,
|
|
239
|
+
options: ["none", ...optionalCapabilityChecklist(outcome).map((capability) => capability.id)],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
219
242
|
const remainingBlocking = questions.filter((question) => question.blocksImplementationWhenMissing).length;
|
|
220
243
|
return {
|
|
221
244
|
status: remainingBlocking > 0 ? "needs-clarification" : "ready",
|
package/package.json
CHANGED
|
@@ -37,7 +37,7 @@ vise run-sensors .
|
|
|
37
37
|
|
|
38
38
|
**`vise check .` is mandatory, not optional. You are not done until it passes or every finding is explicitly attested.** Running `vise plan` and `vise inspect` but skipping `vise check` is the most common failure mode — it means the deterministic catch-net never ran and known-bad patterns ship. When you read a `vise plan`, do not truncate it (`| head` drops the implementation steps); read the full `implementationSteps` array.
|
|
39
39
|
|
|
40
|
-
**
|
|
40
|
+
**Baseline completeness is also a stop condition.** When `vise check .` exits with status `completeness-gap` (exit code 5), one or more baseline capabilities are neither implemented nor opted-out. For add-feed, the baseline capability is pagination. For each missing item: either implement it, or place `// vise: scope-omit <id> — <reason>` in the relevant source file and re-run `vise check .` to confirm it exits 0. A missing baseline capability that is neither built nor opted-out is a silent drop — the check will not pass green until every baseline capability is resolved.
|
|
41
41
|
|
|
42
42
|
Treat Vise runtime smoke sensors as real validation. For TypeScript/React Native projects, `vise run-sensors` may include `TypeScript SDK import smoke`; if it fails, the SDK package does not resolve from the host project runtime and the integration is not done.
|
|
43
43
|
|
|
@@ -244,13 +244,15 @@ vise plan . --request "<feed or post request>"
|
|
|
244
244
|
|
|
245
245
|
Require a concrete target from the app: current user feed, selected community, selected channel, or another user-provided domain object. Do not hardcode random target IDs.
|
|
246
246
|
|
|
247
|
-
**Decide engagement scope explicitly — Vise authors the checklist,
|
|
247
|
+
**Decide engagement scope explicitly — Vise authors the baseline checklist, and optional feed capabilities require user opt-in.** `vise plan` returns a `completenessChecklist` for baseline capabilities (for add-feed today: pagination) and an `optionalCapabilities` block for feed-forward choices such as image upload, poll creation, and edit post. `vise check` reports baseline capabilities as present / missing / opted-out. Missing baseline items that are neither built nor validly opted-out produce `completeness-gap` (exit code 5), because they are silent drops. For each baseline capability: build it, or explicitly opt out with a recorded marker in the code so the omission is reviewable, not accidental:
|
|
248
248
|
|
|
249
249
|
```
|
|
250
|
-
// vise: scope-omit
|
|
250
|
+
// vise: scope-omit pagination — single-screen feed; no load-more affordance in this integration
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
Do not report the integration complete while any capability is `missing`. A scope-omit marker must include a reason after the capability id; otherwise it remains invalid and the capability still counts as missing. Re-run `vise check .` after placing a scope-omit marker to confirm it moves from `missing` to `opted-out`.
|
|
253
|
+
Do not report the integration complete while any baseline capability is `missing`. A scope-omit marker must include a reason after the capability id; otherwise it remains invalid and the capability still counts as missing. Re-run `vise check .` after placing a scope-omit marker to confirm it moves from `missing` to `opted-out`.
|
|
254
|
+
|
|
255
|
+
Optional feed choices are not baseline compliance. If the user explicitly chooses one during planning, carry that decision into init, for example `vise init . --request "Add a social feed" --answer feed_optional_capabilities=post-poll-creation`. Once recorded, `vise check .` runs the selected source sensors and exits `selected-capability-failures` (exit code 6) until the selected capability is implemented.
|
|
254
256
|
|
|
255
257
|
**Demo wiring (when the app needs to compile before the real target source exists):** even at the top-level `App` / `MaterialApp` / `_app.tsx` / `AppDelegate` wiring site, do not pass a literal string. Use the host platform's compile-time env channel so the rule sees no string literal at the call site:
|
|
256
258
|
|