@amityco/social-plus-vise 0.14.23 → 0.14.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ 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.25 — 2026-06-06
8
+
9
+ ### Added
10
+ - **Engagement Intelligence creative brief:** `vise creative` / MCP `creative_brief` now produces an advisory pre-planning brief from a customer request plus optional requirements document and optional prototype HTML. If requirements are absent or explicitly set to `none`, Vise enters exploratory mode instead of blocking.
11
+ - **Creative sidecars:** `vise creative` writes `sp-vise/creative-brief.json` and `sp-vise/creative-brief.md` with inferred goals, archetypes, candidate variants, feasibility summaries, a blocking `preferred_solution` question, and next `vise plan` / `vise workplan` commands.
12
+ - **Runtime intelligence catalog packaging:** the shipped npm tarball now includes `packages/intelligence` so installed CLIs can load the seeded goals, archetypes, objects, relationships, patterns, UX patterns, and variants catalog.
13
+
14
+ ### Verified
15
+ - Added `test:creative` for requirements-driven mode, exploratory mode, sidecar writes, conflicting flag handling, and MCP smoke coverage.
16
+ - Packed-package E2E now runs `vise creative --no-requirements --no-write` after installing the npm tarball, proving the runtime catalog is present in the package.
17
+
18
+ ## 0.14.24 — 2026-06-06
19
+
20
+ ### Changed
21
+ - **Evidence-backed workplan completion:** `vise workplan complete` now runs the current compliance check, refuses non-green results, records check evidence in `sp-vise/workplan.json`, and snapshots the active compliance sidecar under `sp-vise/workplan-snapshots/<surface>/`.
22
+ - **Scoped comments/chat/profile completeness:** the `add-comments` baseline is now narrowed to the comment composer/write affordance, `add-chat` is narrowed to send plus read/unread state, and `add-follow` is narrowed to follower/following relationship data, so focused surfaces do not fail on unselected replies, mentions, media-message types, edit/delete, typing, follow-request, block-list, or deeper moderation scope.
23
+ - **Bundled host-agent skill guidance:** `social-plus-vise` now teaches agents to drive broad social requests through `vise workplan next/status/complete`, implement one focused surface at a time, and show `sp-vise/design-preview.html` before answering `design_contract_confirmation=yes`.
24
+
25
+ ### Verified
26
+ - CLI regression coverage now initializes a focused Android feed sidecar before marking a workplan surface complete, then verifies the green-check evidence and per-surface snapshot files.
27
+ - Capability regression coverage now locks the narrow `add-comments`, `add-chat`, and `add-follow` baselines.
28
+
7
29
  ## 0.14.23 — 2026-06-06
8
30
 
9
31
  ### Added
package/README.md CHANGED
@@ -77,10 +77,14 @@ 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, render a preview for confirmation, then check token usage | **Advisory** — `vise design check`/`preview`; never fails a build |
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 |
80
+ | **Feature completeness** | "this is **missing**" | Vise proposes a narrow baseline per outcome; for add-feed, pagination is mandatory, for add-comments, the composer/write affordance is mandatory, for add-chat, send plus read/unread state are mandatory, and for add-follow/profile, SDK-backed follower/following data 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
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
+ ### Engagement Intelligence roadmap
85
+
86
+ Vise's next architecture track is the social.plus **Engagement Intelligence System**: an outcome-to-experience layer that maps customer goals, archetypes, solution patterns, experience objects, UX patterns, and variants before normal implementation planning. The first advisory runtime slice is `vise creative`: it consumes a request plus optional requirements/prototype inputs, or runs in exploratory mode when requirements are absent, then writes a local creative brief for user variant selection. See [docs/ENGAGEMENT_INTELLIGENCE_SYSTEM.md](docs/ENGAGEMENT_INTELLIGENCE_SYSTEM.md), [docs/MONOREPO_ARCHITECTURE.md](docs/MONOREPO_ARCHITECTURE.md), and [packages/intelligence](packages/intelligence).
87
+
84
88
  ### Relationship to social.plus Block Factory
85
89
 
86
90
  Vise has two deliberately separate roles:
@@ -161,15 +165,16 @@ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional ca
161
165
 
162
166
  ### Current Release Validation
163
167
 
164
- Version 0.14.23 carries current release proof around the full feed-forward, product-expectation, and validation flow:
168
+ Version 0.14.25 carries current release proof around the full feed-forward, product-expectation, creative pre-planning, and validation flow:
165
169
 
166
170
  | Surface | What was validated |
167
171
  |---|---|
172
+ | **Creative pre-planning** | `vise creative` produces requirements-driven or exploratory Engagement Intelligence briefs, writes `sp-vise/creative-brief.json` / `.md`, asks for `preferred_solution`, and survives packed-package installation with the intelligence catalog bundled. |
168
173
  | **Product flow** | Local end-to-end smoke covers design extraction, plan feed-forward, blocking intake, answered init, capability check, design conformance, and sensor discovery. |
169
- | **Multi-surface planning** | Broad social requests are decomposed into a `socialWorkplan` sequence for feed, comments, chat, and profile work instead of forcing a single top-level surface choice. `vise workplan next` tells the host agent which surface to implement next, and `vise workplan complete` records progress in `sp-vise/workplan.json`. |
174
+ | **Multi-surface planning** | Broad social requests are decomposed into a `socialWorkplan` sequence for feed, comments, chat, and profile work instead of forcing a single top-level surface choice. `vise workplan next` tells the host agent which surface to implement next, and `vise workplan complete` records green-check progress in `sp-vise/workplan.json` with per-surface snapshots under `sp-vise/workplan-snapshots/<surface>/`. |
170
175
  | **Plan questions** | Plans surface blocking questions such as `design_contract_confirmation`, product-scope questions such as `feed_post_type_scope`, `feed_composer_type_scope`, `comment_tray_scope`, `chat_inbox_scope`, and `profile_identity_scope`, plus optional choices such as `feed_optional_capabilities`. Focused plans still accept `feature_surface` answers when the agent is ready to implement one surface. |
171
176
  | **Capability-to-sensor flow** | Vise checks platform support, matches the prompt to available capabilities, offers supported features as questions, records answers, and turns selected answers into sensors in `vise check`. |
172
- | **Android workplan dogfood** | A brownfield Android music-player app refreshed under `0.14.22` reached `vise check` green with **43/43 deterministic passes**, passed `vise validate`, and passed Gradle assemble/unit-test sensors. This is dogfood evidence, not a controlled multi-agent benchmark. |
177
+ | **Android workplan dogfood** | A brownfield Android music-player app refreshed under `0.14.24` reached `vise check` green with **43/43 deterministic passes** on the focused feed surface and recorded a green-check workplan snapshot. This is dogfood evidence, not a controlled multi-agent benchmark. |
173
178
  | **Shared product expectations** | Public IDs such as `feed.target-resolved`, `feed.post-type-scope-explicit`, `comments.creation-affordance`, `chat.channel-list-order-explicit`, `community.avatar-from-sdk`, `moderation.role-gated-action`, `follow.relationship-live`, `profile.identity-from-sdk`, `profile.social-counts`, and `notifications.tray-live` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence when deterministic sensors exist. |
174
179
  | **Rule detection** | TP-track dashboard detects **321/321 seeded rule gaps (100.0%)** in the static corpus. |
175
180
  | **Packed-package smoke** | Packed-package and host-agent smokes exercise the release tarball path, surfaced plan questions, selected optional capability sensors, rejected design confirmation handling, and exact contract-rule evidence for shared product expectations. |
@@ -298,11 +303,12 @@ The flow above is what the skill teaches your AI agent. You — the human — dr
298
303
  |---|---|
299
304
  | `vise doctor` | Verify install; print version, install path, docs source |
300
305
  | `vise inspect [path]` | Detect platform, monorepo surfaces, design signals, available sensors |
306
+ | `vise creative [path] --request "..." [--requirements <path\|none>] [--prototype <html>]` | Produce an advisory Engagement Intelligence brief with 2-3 solution variants before normal implementation planning; writes `sp-vise/creative-brief.json` and `.md` unless `--no-write` is set |
301
307
  | `vise plan [path] --request "..."` | Produce a grounded implementation plan with intake questions and docs citations |
302
308
  | `vise plan-harness [path] --request "..."` | (Pre-planning step) Build the harness around the request |
303
309
  | `vise workplan next [path] --request "..."` | For broad social requests, print the next uncompleted surface plus focused `plan` / `init` / verification commands |
304
310
  | `vise workplan status [path] --request "..."` | Show the broad social workplan sequence and which surfaces are already marked complete |
305
- | `vise workplan complete [path] --request "..." --surface <id>` | Record a completed workplan surface in `sp-vise/workplan.json` after `check`, `sync`, `validate`, and sensors pass |
311
+ | `vise workplan complete [path] --request "..." --surface <id>` | Record a completed workplan surface after green `vise check`; writes progress to `sp-vise/workplan.json` and snapshots the active sidecar under `sp-vise/workplan-snapshots/<surface>/` |
306
312
  | `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 |
307
313
  | `vise blocks list --registry <path>` | Read a social.plus Block Factory registry |
308
314
  | `vise blocks plan [path] --block <id> --registry <path>` | Plan safe block package, source-anchor, sidecar, and sensor changes |
@@ -402,7 +408,7 @@ MCP-capable hosts can call Vise as structured tool calls instead of shell comman
402
408
 
403
409
  ### Tool names (snake_case per MCP convention)
404
410
 
405
- `inspect_project`, `plan_harness`, `plan_integration`, `init_compliance`, `check_compliance`, `sync_compliance`, `attest_rule`, `explain_rule`, `init_engagement`, `show_engagement`, `resolve_request`, `search_docs`, `get_doc_page`, `debug_issue`, `validate_setup`, `run_sensors`, `suggest_patch`, `design_extract`, `design_check`, `design_preview`, `design_reference`, `design_init_tokens`.
411
+ `inspect_project`, `creative_brief`, `plan_harness`, `plan_integration`, `init_compliance`, `check_compliance`, `sync_compliance`, `attest_rule`, `explain_rule`, `init_engagement`, `show_engagement`, `resolve_request`, `search_docs`, `get_doc_page`, `debug_issue`, `validate_setup`, `run_sensors`, `suggest_patch`, `design_extract`, `design_check`, `design_preview`, `design_reference`, `design_init_tokens`.
406
412
 
407
413
  These are the same operations as the CLI commands above, exposed as MCP tools.
408
414
 
@@ -450,15 +456,18 @@ jobs:
450
456
 
451
457
  ## Compliance Contract
452
458
 
453
- 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:
459
+ Vise writes local planning, compliance, design, and evidence artifacts under `sp-vise/`. `vise creative` can create advisory creative-brief files before implementation; after a successful `vise init`, the compliance contract files are added. 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:
454
460
 
455
461
  | File | Created by | What it contains |
456
462
  |---|---|---|
457
463
  | `sp-vise/compliance.json` | `vise init` | The rules selected for this integration, the Vise version, the ruleset digest, the target app surface, selected optional capabilities, optional engagement link, and an accepted design-contract digest when confirmed. |
458
464
  | `sp-vise/intake.json` | `vise init` | The request, outcome, intake answers, remaining blocking count, design-review status (`absent`, `needs-confirmation`, `accepted`, or `rejected`), and any retrospective `--allow-unresolved-intake` acknowledgement. |
465
+ | `sp-vise/creative-brief.json` | `vise creative` | Advisory Engagement Intelligence brief: mode, objective, inferred goals/archetypes, candidate solution variants, feasibility summary, preferred-solution question, and next plan/workplan commands. |
466
+ | `sp-vise/creative-brief.md` | `vise creative` | Human-readable version of the creative brief for review with the user before selecting a variant. |
459
467
  | `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). |
460
468
  | `sp-vise/inspection.json` | `vise init` | The platform, monorepo surface, and design-token signals detected at init time. |
461
- | `sp-vise/workplan.json` | `vise workplan complete` | Local progress for broad social workplans: request, completed surface IDs, outcomes, timestamps, and optional host-agent notes. |
469
+ | `sp-vise/workplan.json` | `vise workplan complete` | Local progress for broad social workplans: request, completed surface IDs, outcomes, timestamps, green-check evidence, snapshot paths, and optional host-agent notes. |
470
+ | `sp-vise/workplan-snapshots/<surface>/` | `vise workplan complete` | Per-surface compliance/check snapshots copied from the active sidecar so later focused surfaces can overwrite `sp-vise/compliance.json` without erasing earlier proof. |
462
471
  | `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. |
463
472
  | `sp-vise/design-preview.html` | `vise design extract` or `vise design preview` | Self-contained visual review of the design contract, embedded prototype when available, token swatches, and design-check conformance summary. Open this before answering `design_contract_confirmation`. |
464
473
  | `sp-vise/design-reference.html` | `vise design reference` | Self-contained HTML design-system spec (token swatches, type samples, components). Human/VLM-readable; open in a browser alongside the app. |
@@ -13,7 +13,11 @@
13
13
  * agent subtracts with justification — it doesn't have to remember the set.
14
14
  *
15
15
  * Baseline capability gates must stay narrow. For add-feed, pagination is the
16
- * mandatory capability; richer composer affordances are selected optional sensors.
16
+ * mandatory capability. For add-comments, the comment composer is mandatory.
17
+ * For add-chat, sending plus read/unread state are mandatory. For add-follow,
18
+ * follower/following relationship data is mandatory. Replies, mentions,
19
+ * edit/delete, typing, media-message types, block lists, and deeper moderation
20
+ * scope are scoped separately. Richer composer affordances are selected optional sensors.
17
21
  */
18
22
  import { readdir, readFile, stat } from "node:fs/promises";
19
23
  import path from "node:path";
@@ -325,10 +329,10 @@ export const CAPABILITIES = [
325
329
  },
326
330
  {
327
331
  id: "followers-following",
328
- label: "Follower / following lists",
332
+ label: "Follower / following counts or lists",
329
333
  outcomes: ["add-follow"],
330
- symbols: [/getFollowers/i, /getFollowings/i, /getFollowerList/i, /followRelationship/i],
331
- hint: "list/observe followers and following as a Live Collection (getFollowers/getFollowings)",
334
+ symbols: [/getFollowers/i, /getFollowings/i, /getFollowerList/i, /followRelationship/i, /getMyFollowInfo/i, /getFollowerCount/i, /getFollowingCount/i],
335
+ hint: "source follower/following counts or lists from SDK relationship APIs, not placeholders",
332
336
  },
333
337
  {
334
338
  id: "follow-status",
@@ -1166,6 +1170,9 @@ function symbolHaystack(symbols) {
1166
1170
  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).";
1167
1171
  const BASELINE_CAPABILITY_IDS_BY_OUTCOME = {
1168
1172
  "add-feed": ["pagination"],
1173
+ "add-comments": ["comment-composer"],
1174
+ "add-chat": ["send-message", "read-state"],
1175
+ "add-follow": ["followers-following"],
1169
1176
  };
1170
1177
  function baselineCapabilities(outcome) {
1171
1178
  const caps = CAPABILITIES.filter((c) => c.outcomes.includes(outcome));
package/dist/server.js CHANGED
@@ -17,11 +17,13 @@ import { runSensorsTool } from "./tools/sensors.js";
17
17
  import { getSdkFactsTool } from "./tools/sdkFacts.js";
18
18
  import { addBlockInstall, listRegistryBlocks, planBlockInstall, validateBlockInstall } from "./tools/blocks.js";
19
19
  import { debugIssueTool, debugIssue } from "./tools/debug.js";
20
+ import { creativeBriefTool } from "./tools/creative.js";
20
21
  import { packageName, packageVersion } from "./version.js";
21
22
  const tools = new Map([
22
23
  searchDocsTool,
23
24
  getDocPageTool,
24
25
  inspectProjectTool,
26
+ creativeBriefTool,
25
27
  planHarnessTool,
26
28
  planIntegrationTool,
27
29
  initComplianceTool,
@@ -134,6 +136,21 @@ async function handleCli(args) {
134
136
  });
135
137
  return "exit";
136
138
  }
139
+ if (command === "creative") {
140
+ assertOnlyKnownFlags(args, ["request", "requirements", "prototype", "surface", "surface-path", "no-requirements", "no-write"], "creative");
141
+ if (hasFlag(args, "no-requirements") && flagValue(args, "requirements")) {
142
+ throw new Error("creative accepts either --requirements <path> or --no-requirements, not both.");
143
+ }
144
+ await printToolResult(creativeBriefTool, {
145
+ repoPath: positionalRepoPath(args.slice(1)),
146
+ request: requiredFlagValue(args, "request", "creative requires --request."),
147
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
148
+ requirementsPath: hasFlag(args, "no-requirements") ? "none" : flagValue(args, "requirements"),
149
+ prototypePath: flagValue(args, "prototype"),
150
+ write: !hasFlag(args, "no-write"),
151
+ });
152
+ return "exit";
153
+ }
137
154
  if (command === "debug") {
138
155
  assertOnlyKnownFlags(args, ["error", "error-file", "brief"], "debug");
139
156
  let errorMessage = flagValue(args, "error");
@@ -450,6 +467,23 @@ Re-plan with collected answers (repeat --answer for each intake question):
450
467
  --answer feed_scope=community \\
451
468
  --answer feed_target=existing\\ communityId \\
452
469
  --answer target_screen_or_route=app/feed/page.tsx`;
470
+ }
471
+ if (command === "creative") {
472
+ return `${packageName} creative
473
+
474
+ Create an advisory Engagement Intelligence brief before normal Vise planning.
475
+
476
+ Usage:
477
+ vise creative [repoPath] --request "Make this app more social"
478
+ vise creative [repoPath] --request "Add retention loops" --requirements ./requirements.md
479
+ vise creative [repoPath] --request "Add engagement" --requirements none
480
+ vise creative [repoPath] --request "Add engagement" --no-requirements
481
+ vise creative [repoPath] --request "Add engagement" --prototype ./prototype.html
482
+ vise creative [repoPath] --request "Add engagement" --no-write
483
+
484
+ Output:
485
+ Writes sp-vise/creative-brief.json and sp-vise/creative-brief.md unless --no-write is set.
486
+ Requirements are optional; absence or explicit none switches creative mode to exploratory.`;
453
487
  }
454
488
  if (command === "plan-harness") {
455
489
  return `${packageName} plan-harness
@@ -697,6 +731,7 @@ Usage:
697
731
  vise install-skill --target codex Install bundled skill guidance
698
732
  vise print-skill Print bundled skill markdown
699
733
  vise inspect [repoPath] Inspect platform and design signals
734
+ vise creative [repoPath] --request "..." Create an Engagement Intelligence brief
700
735
  vise debug [repoPath] --error ... Debug an SDK-specific runtime error and emit a repair brief
701
736
  vise plan [repoPath] --request "..." Create an implementation plan
702
737
  vise workplan next [repoPath] --request "..." Get the next broad-social surface to implement
@@ -1015,11 +1050,13 @@ async function completeWorkplanSurface(args) {
1015
1050
  throw new Error(`Surface "${surfaceId}" is not in this social workplan. Available surfaces: ${sequence.map((item) => item.id).join(", ") || "(none)"}.`);
1016
1051
  }
1017
1052
  const now = new Date().toISOString();
1053
+ const check = await greenWorkplanCheck(repoRoot, surfaceId);
1054
+ const snapshot = await writeWorkplanSurfaceSnapshot(repoRoot, surfaceId, check, now);
1018
1055
  const existing = await readWorkplanProgress(repoRoot);
1019
1056
  const base = existing?.request === request
1020
1057
  ? existing
1021
1058
  : {
1022
- schema_version: 1,
1059
+ schema_version: 2,
1023
1060
  vise_version: packageVersion,
1024
1061
  request,
1025
1062
  generated_at: now,
@@ -1029,6 +1066,7 @@ async function completeWorkplanSurface(args) {
1029
1066
  const withoutSurface = base.completed.filter((item) => item.surface !== surfaceId);
1030
1067
  const progress = {
1031
1068
  ...base,
1069
+ schema_version: 2,
1032
1070
  vise_version: packageVersion,
1033
1071
  updated_at: now,
1034
1072
  completed: [
@@ -1038,6 +1076,17 @@ async function completeWorkplanSurface(args) {
1038
1076
  outcome: surface.outcome,
1039
1077
  completed_at: now,
1040
1078
  ...(note ? { note } : {}),
1079
+ evidence: {
1080
+ check: {
1081
+ checked_at: now,
1082
+ status: check.status,
1083
+ exit_code: check.exitCode,
1084
+ outcome: check.outcome,
1085
+ ...(check.surfacePath ? { surface_path: check.surfacePath } : {}),
1086
+ summary: check.summary,
1087
+ },
1088
+ snapshot,
1089
+ },
1041
1090
  },
1042
1091
  ].sort((a, b) => sequenceIndex(sequence, a.surface) - sequenceIndex(sequence, b.surface)),
1043
1092
  };
@@ -1051,6 +1100,46 @@ async function completeWorkplanSurface(args) {
1051
1100
  nextStep: status.nextStep,
1052
1101
  };
1053
1102
  }
1103
+ async function greenWorkplanCheck(repoRoot, surfaceId) {
1104
+ let check;
1105
+ try {
1106
+ check = await checkCompliance(repoRoot);
1107
+ }
1108
+ catch (error) {
1109
+ const message = error instanceof Error ? error.message : String(error);
1110
+ throw new Error(`Cannot mark "${surfaceId}" complete because the current compliance check could not run. Run the focused \`vise init\` command for this surface first, then \`vise check .\`. ${message}`);
1111
+ }
1112
+ if (check.status !== "green") {
1113
+ throw new Error(`Cannot mark "${surfaceId}" complete because \`vise check\` returned "${check.status}" (exit ${check.exitCode}). Resolve the check result before recording workplan progress.`);
1114
+ }
1115
+ return check;
1116
+ }
1117
+ async function writeWorkplanSurfaceSnapshot(repoRoot, surfaceId, check, snapshotAt) {
1118
+ const snapshotDir = workplanSurfaceSnapshotDir(repoRoot, surfaceId);
1119
+ await mkdir(snapshotDir, { recursive: true });
1120
+ const files = [];
1121
+ const sidecarRoot = sidecarPath(repoRoot);
1122
+ for (const fileName of ["compliance.json", "intake.json", "inspection.json", "findings.json"]) {
1123
+ const sourcePath = path.join(sidecarRoot, fileName);
1124
+ if (!(await fileExists(sourcePath))) {
1125
+ continue;
1126
+ }
1127
+ const targetPath = path.join(snapshotDir, fileName);
1128
+ await copyFile(sourcePath, targetPath);
1129
+ files.push(repoRelativePath(repoRoot, targetPath));
1130
+ }
1131
+ const checkPath = path.join(snapshotDir, "check.json");
1132
+ await writeFile(checkPath, `${JSON.stringify({
1133
+ snapshot_at: snapshotAt,
1134
+ surface: surfaceId,
1135
+ check,
1136
+ }, null, 2)}\n`, "utf8");
1137
+ files.push(repoRelativePath(repoRoot, checkPath));
1138
+ return {
1139
+ directory: repoRelativePath(repoRoot, snapshotDir),
1140
+ files: files.sort(),
1141
+ };
1142
+ }
1054
1143
  async function workplanPlan(repoRoot, request, args) {
1055
1144
  const answers = keyValueFlag(args, "answer");
1056
1145
  delete answers.feature_surface;
@@ -1083,7 +1172,20 @@ async function writeWorkplanProgress(repoRoot, progress) {
1083
1172
  await writeFile(filePath, `${JSON.stringify(progress, null, 2)}\n`, "utf8");
1084
1173
  }
1085
1174
  function workplanProgressPath(repoRoot) {
1086
- return path.join(repoRoot, "sp-vise", "workplan.json");
1175
+ return path.join(sidecarPath(repoRoot), "workplan.json");
1176
+ }
1177
+ function workplanSurfaceSnapshotDir(repoRoot, surfaceId) {
1178
+ const safeSurfaceId = surfaceId.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
1179
+ if (!safeSurfaceId) {
1180
+ throw new Error(`Invalid workplan surface id: "${surfaceId}".`);
1181
+ }
1182
+ return path.join(sidecarPath(repoRoot), "workplan-snapshots", safeSurfaceId);
1183
+ }
1184
+ function sidecarPath(repoRoot) {
1185
+ return path.join(repoRoot, "sp-vise");
1186
+ }
1187
+ function repoRelativePath(repoRoot, filePath) {
1188
+ return path.relative(repoRoot, filePath).split(path.sep).join("/");
1087
1189
  }
1088
1190
  function sequenceIndex(sequence, surfaceId) {
1089
1191
  const index = sequence.findIndex((surface) => surface.id === surfaceId);
@@ -1105,7 +1207,7 @@ function ciCheckResult(result) {
1105
1207
  };
1106
1208
  }
1107
1209
  function positionalRepoPath(args) {
1108
- 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", "note"]);
1210
+ const flagsWithValues = new Set(["request", "requirements", "prototype", "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"]);
1109
1211
  for (let index = 0; index < args.length; index += 1) {
1110
1212
  const arg = args[index];
1111
1213
  if (!arg) {
@@ -1127,7 +1229,7 @@ function positionalRepoPath(args) {
1127
1229
  }
1128
1230
  function requiredPositionalText(args, message) {
1129
1231
  const values = [];
1130
- 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", "note"]);
1232
+ const flagsWithValues = new Set(["request", "requirements", "prototype", "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"]);
1131
1233
  for (let index = 0; index < args.length; index += 1) {
1132
1234
  const arg = args[index];
1133
1235
  if (!arg) {