@llamaventures/cli 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT_BRIEFING.md CHANGED
@@ -168,9 +168,11 @@ means the truth depends on who judges: a value or comparative word with no
168
168
  stated benchmark, or a claim about someone's future behavior or inner state. If
169
169
  yes, the type is `human_subjective_view.people` (founders or team) or
170
170
  `human_subjective_view.business` (company, product, market or deal),
171
- `value.speaker` names that user, and `value.rawText` quotes the user verbatim
172
- from `origin.originalUserUtterance`. CLI and Core reject anything else. If no,
173
- choose any type that fits; founder words are `founder_claim`.
171
+ `value.speaker` names that user, `value.rawText` quotes the user verbatim
172
+ from `origin.originalUserUtterance`, and `value.summary` is your own
173
+ restatement of those words (never a substitute for them). CLI and Core reject
174
+ anything else. If no, choose any type that fits; founder words are
175
+ `founder_claim`.
174
176
 
175
177
  ```json
176
178
  {
package/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ this project adheres to [Semantic Versioning](https://semver.org).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.4.0] - 2026-09-09
10
+
11
+ ### Added
12
+
13
+ - Read Deal artifact text and original bytes through Core with version/hash provenance.
14
+ - Read Wiki uploads and supported page references; paginate text with a pinned source hash.
15
+ - Verify source downloads and refuse to overwrite local files.
16
+
17
+ ## [2.3.0] - 2026-09-04
18
+
19
+ ### Changed
20
+ - `human_subjective_view.people|business` now also requires `value.summary`,
21
+ the agent's own restatement of the user's words, and rejects a summary that
22
+ merely repeats `value.rawText`. Core enforces the same rule; the Deal Page
23
+ shows both the summary and the verbatim text.
24
+
9
25
  ## [2.2.0] - 2026-09-04
10
26
 
11
27
  ### Added
package/README.md CHANGED
@@ -214,3 +214,17 @@ npm run verify:release
214
214
 
215
215
  Release artifacts are source-SHA certified. Publishing to npm and changing the
216
216
  production server's minimum version are separate, explicit release operations.
217
+
218
+ ### Read original source content
219
+
220
+ Use `llama deal read <dealId> --artifact <artifactId>` to read a listed file's text,
221
+ source hash and page/paragraph anchors. Use `llama wiki read <slug> --format text`
222
+ for a Wiki page or its original uploaded document. `--attachment <referenceId>`
223
+ follows a supported reference listed by the Wiki read. Long reads return
224
+ `nextOffset`; continue with `--offset N --sha256 HASH`. `--output <file>` downloads
225
+ and hash-verifies the original bytes without overwriting a local file.
226
+
227
+ These options require a Core version with source-content reads. Scanned PDFs
228
+ may require OCR; no-text, missing files, unsupported formats and denied storage
229
+ are explicit outcomes, not evidence that a memo has been read. MCP exposes the
230
+ same text reads inside `read_deal` and `wiki_read`.
package/README.zh-CN.md CHANGED
@@ -204,3 +204,14 @@ npm run verify:release
204
204
 
205
205
  发布包会绑定准确 source SHA。npm publish 与生产服务端强制最低版本是两个
206
206
  独立、需要明确授权的 release 动作。
207
+
208
+ ### 读取原始材料
209
+
210
+ `llama deal read <dealId> --artifact <artifactId>` 读取文件正文、原文件哈希与页码/段落位置。
211
+ `llama wiki read <slug> --format text` 读取 Wiki 正文或上传的原文件;
212
+ `--attachment <referenceId>` 读取页面返回的受支持附件引用。
213
+ 长文用 `--offset <nextOffset> --sha256 <source.sha256>` 继续,文件变化时会拒绝拼接。
214
+ `--output <文件路径>` 下载并核验原始字节,不覆盖已有文件。
215
+
216
+ 需要服务端支持正文读取。扫描 PDF 不自动 OCR;无文字、缺失文件、不支持的格式、
217
+ 权限不足都会明确报告。MCP 在原有 `read_deal` 和 `wiki_read` 中提供相同正文读取能力。
package/bin/llama-mcp.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { buildArtifactReadPath, buildWikiReadPath } from "../lib/content-read.mjs";
3
4
  import { createRequire } from "node:module";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -123,14 +124,27 @@ server.registerTool(
123
124
  server.registerTool(
124
125
  "read_deal",
125
126
  {
126
- description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Page field semantics come from agent_bootstrap, not every Deal read.",
127
+ description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Use artifactId to read original source text with page/paragraph anchors. Continue with nextOffset and source.sha256. Page field semantics come from agent_bootstrap, not every Deal read.",
127
128
  inputSchema: {
128
129
  dealId: z.string().min(1),
129
130
  detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
131
+ artifactId: z.string().uuid().optional(),
132
+ version: z.number().int().min(1).optional(),
133
+ offset: z.number().int().min(0).optional(),
134
+ limit: z.number().int().min(1).max(50000).optional(),
135
+ sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
130
136
  },
131
137
  },
132
- // @core-api-operation GET /api/occam/deals/{dealId}
133
- async ({ dealId, detail }) => callApi("GET", buildDealReadPath(dealId, detail || "overview")),
138
+ async ({ dealId, detail, artifactId, ...options }) => {
139
+ if (artifactId) {
140
+ if (detail) throw new Error("Use artifactId or detail, not both");
141
+ // @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
142
+ return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
143
+ }
144
+ if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
145
+ // @core-api-operation GET /api/occam/deals/{dealId}
146
+ return callApi("GET", buildDealReadPath(dealId, detail || "overview"));
147
+ },
134
148
  );
135
149
 
136
150
  server.registerTool(
@@ -152,7 +166,7 @@ server.registerTool(
152
166
  server.registerTool(
153
167
  "write_deal",
154
168
  {
155
- description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker plus value.rawText quoted verbatim from origin.originalUserUtterance; it is rejected otherwise.",
169
+ description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker, value.rawText quoted verbatim from origin.originalUserUtterance, and value.summary (the agent's restatement); it is rejected otherwise.",
156
170
  // MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
157
171
  // command preserves the real discriminated union instead of flattening four
158
172
  // incompatible operations into false affordances.
@@ -344,11 +358,16 @@ server.registerTool(
344
358
  server.registerTool(
345
359
  "wiki_read",
346
360
  {
347
- description: "Read one Wiki article by exact slug.",
348
- inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
361
+ description: "Read one Wiki article. format=text extracts its original upload or page body. attachment follows an ID listed in attachments. Continuations require offset and sha256.",
362
+ inputSchema: {
363
+ slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
364
+ format: z.literal("text").optional(), attachment: z.string().optional(),
365
+ offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
366
+ sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
367
+ },
349
368
  },
350
- async ({ slug, lang }) =>
351
- callApi("GET", `/api/wiki/${encodeURIComponent(slug)}?lang=${lang === "zh" ? "zh" : "en"}`),
369
+ // @core-api-operation GET /api/wiki/{slug}
370
+ async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
352
371
  );
353
372
 
354
373
  server.registerTool(
package/bin/llama.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { buildArtifactReadPath, buildWikiReadPath, saveContentSource } from "../lib/content-read.mjs";
3
4
  import { createRequire } from "node:module";
4
5
  import { readFile } from "node:fs/promises";
5
6
  import readline from "node:readline";
@@ -70,6 +71,8 @@ Separate preserved domains:
70
71
  llama skills list|search|show
71
72
  llama pref list|add|approve|retire
72
73
  llama explain <url-or-object>
74
+ llama deal read <dealId> --artifact <artifactId> [--version N] [--offset N --sha256 HASH] [--limit N] [--output <file>]
75
+ llama wiki read <slug> --format text [--attachment <referenceId>] [--offset N --sha256 HASH] [--output <file>]
73
76
  llama wiki search|read|save|delete|restore
74
77
  llama admin auth-events|deal-events|agent-events
75
78
  llama pitch start|say|upload|status|finalize|end
@@ -84,6 +87,15 @@ Exactly four actions:
84
87
  llama deal create --json <file|->
85
88
  llama deal write --json <file|->
86
89
 
90
+ Read original source content:
91
+ llama deal read <dealId> --artifact <artifactId> [--version N] [--offset N --sha256 HASH] [--limit N] [--output <file>]
92
+ llama wiki read <slug> --format text [--attachment <referenceId>] [--offset N --sha256 HASH] [--output <file>]
93
+
94
+ Artifact reads return source hashes, text and page/paragraph anchors. Continue with
95
+ --offset <nextOffset> --sha256 <source.sha256>. --output saves verified original
96
+ bytes without overwriting existing files. Wiki --format text reads the original
97
+ upload when present; --attachment follows a reference listed by the page.
98
+
87
99
  Before any Page write:
88
100
  llama agent bootstrap
89
101
  llama page-schema read <field> [field...]
@@ -103,7 +115,7 @@ create JSON:
103
115
  write JSON (CLI derives idempotencyKey when omitted):
104
116
  input.submit: {"operation":"input.submit","dealId":"<uuid>","content":"<original input>","format":"text","source":{},"origin":{"kind":"user","originalUserUtterance":"..."}}
105
117
  information.put:{"operation":"information.put","dealId":"<uuid>","type":"founder_claim","labels":["founder-stated"],"subject":{"topic":"traction"},"value":{"content":"...","source":{"kind":"first_meeting_transcript"}},"origin":{"kind":"user","originalUserUtterance":"..."}}
106
- human judgment: {"operation":"information.put","dealId":"<uuid>","type":"human_subjective_view.people","subject":{"topic":"founder_execution"},"value":{"speaker":"<Llama user>","rawText":"<the user's verbatim words>","summary":"<optional agent summary>"},"origin":{"kind":"user","originalUserUtterance":"<complete user input; must contain rawText>"}}
118
+ human judgment: {"operation":"information.put","dealId":"<uuid>","type":"human_subjective_view.people","subject":{"topic":"founder_execution"},"value":{"speaker":"<Llama user>","rawText":"<the user's verbatim words>","summary":"<the agent's restatement>"},"origin":{"kind":"user","originalUserUtterance":"<complete user input; must contain rawText>"}}
107
119
  page.patch: {"operation":"page.patch","dealId":"<uuid>","patch":{"description":{"en":"...","zh":"..."}},"origin":{"kind":"agent"}}
108
120
  artifact.put: {"operation":"artifact.put","dealId":"<uuid>","kind":"deck","title":"deck.pdf","mimeType":"application/pdf","contentBase64":"<file bytes, base64>","origin":{"kind":"user","originalUserUtterance":"..."}}
109
121
 
@@ -119,8 +131,9 @@ value.source records where the evidence came from.
119
131
  human_subjective_view.people|business is a Llama user's own judgment about the
120
132
  team or the deal. Subjective = the truth depends on who judges: a value or
121
133
  comparative word with no benchmark, or a claim about someone's future behavior
122
- or inner state. It requires value.speaker and value.rawText quoted verbatim from
123
- origin.originalUserUtterance; CLI and Core reject anything else. Other types are free.
134
+ or inner state. It requires value.speaker, value.rawText quoted verbatim from
135
+ origin.originalUserUtterance, and value.summary, the agent's own restatement of
136
+ those words; CLI and Core reject anything else. Other types are free.
124
137
 
125
138
  Core owns Chat Records, append-only Deal Events, Drive provisioning, audit,
126
139
  and idempotency. User-originated work must preserve exact wording in
@@ -460,7 +473,7 @@ async function handleWiki(action, rest) {
460
473
  if (action === "read") {
461
474
  if (!slug) throw new Error("Usage: llama wiki read <slug> [--lang en|zh]");
462
475
  // @core-api-operation GET /api/wiki/{slug}
463
- print(await request("GET", `/api/wiki/${encodeURIComponent(slug)}?lang=${flags.lang === "zh" ? "zh" : "en"}`));
476
+ print(await saveContentSource(await request("GET", buildWikiReadPath(slug, flags)), flags.output));
464
477
  return;
465
478
  }
466
479
  if (action === "save") {
@@ -656,9 +669,17 @@ async function main() {
656
669
  }
657
670
  if (area === "deal" && action === "read") {
658
671
  const dealId = rest[0];
659
- const { flags } = parseFlags(rest.slice(1), ["detail"]);
660
- // @core-api-operation GET /api/occam/deals/{dealId}
661
- print(await request("GET", buildDealReadPath(dealId, flags.detail === true ? "overview" : flags.detail || "overview")));
672
+ const { flags } = parseFlags(rest.slice(1), ["detail", "artifact", "version", "format", "offset", "limit", "sha256", "output"]);
673
+ if (flags.artifact !== undefined) {
674
+ if (flags.detail !== undefined) throw new Error("Use --artifact or --detail, not both");
675
+ // @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
676
+ const result = await request("GET", buildArtifactReadPath(dealId, flags.artifact, flags));
677
+ print(await saveContentSource(result, flags.output));
678
+ } else {
679
+ if (["version", "format", "offset", "limit", "sha256", "output"].some(key => flags[key] !== undefined)) throw new Error("Content options require --artifact");
680
+ // @core-api-operation GET /api/occam/deals/{dealId}
681
+ print(await request("GET", buildDealReadPath(dealId, flags.detail === true ? "overview" : flags.detail || "overview")));
682
+ }
662
683
  return;
663
684
  }
664
685
  if (area === "deal" && ["create", "write"].includes(action)) {
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "format": "llama.core-api-contract.v1",
3
3
  "name": "llama-core-api",
4
- "apiVersion": "5.3.0",
4
+ "apiVersion": "5.8.0",
5
5
  "openapiVersion": "3.0.3",
6
- "sha256": "b793f67038b48cdfbfcc9141153132689c40e84adfbe087b0dbef3a409f5e5d5",
6
+ "sha256": "ec68e92362c9f60eee94b81d73fb7cff2868c80d7c17c24a9e3122cc2743e7ad",
7
7
  "pathCount": 113,
8
8
  "operationCount": 140
9
9
  }
@@ -18,6 +18,7 @@
18
18
  { "method": "GET", "path": "/api/oauth/authorize" },
19
19
  { "method": "GET", "path": "/api/occam/deals" },
20
20
  { "method": "GET", "path": "/api/occam/deals/{dealId}" },
21
+ { "method": "GET", "path": "/api/occam/deals/{dealId}/artifacts/{artifactId}" },
21
22
  { "method": "GET", "path": "/api/wiki/{slug}" },
22
23
  { "method": "GET", "path": "/api/wiki/search" },
23
24
  { "method": "PATCH", "path": "/api/agent/preferences/{id}" },
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": "llama.cli-build.v1",
3
3
  "packageName": "@llamaventures/cli",
4
- "packageVersion": "2.2.0",
5
- "sourceSha": "2f4c73310ed6c5d36a47a275490ff1082321ca27",
4
+ "packageVersion": "2.4.0",
5
+ "sourceSha": "5f2e08c216a111bc568e7dee925aa198674c6255",
6
6
  "sourceKind": "github",
7
7
  "sourceDirty": false,
8
8
  "coreApiContract": {
9
9
  "format": "llama.core-api-contract.v1",
10
10
  "name": "llama-core-api",
11
- "apiVersion": "5.3.0",
11
+ "apiVersion": "5.8.0",
12
12
  "openapiVersion": "3.0.3",
13
- "sha256": "b793f67038b48cdfbfcc9141153132689c40e84adfbe087b0dbef3a409f5e5d5"
13
+ "sha256": "ec68e92362c9f60eee94b81d73fb7cff2868c80d7c17c24a9e3122cc2743e7ad"
14
14
  }
15
15
  }
package/lib/client.mjs CHANGED
@@ -291,6 +291,7 @@ function inferCommand(method, endpoint) {
291
291
  if (pathname === "/api/wiki/search") return "wiki.search";
292
292
  if (pathname === "/api/wiki/save") return "wiki.save";
293
293
  if (/^\/api\/wiki\/[^/]+$/.test(pathname)) return verb === "GET" ? "wiki.read" : "wiki.write";
294
+ if (/^\/api\/occam\/deals\/[^/]+\/artifacts\/[^/]+$/.test(pathname)) return "deal.read";
294
295
  if (pathname === "/api/occam/deals") return "deal.search";
295
296
  if (pathname === "/api/occam/deals/commands") return "deal.write";
296
297
  if (/^\/api\/occam\/deals\/[^/]+$/.test(pathname)) return "deal.read";
@@ -0,0 +1,49 @@
1
+ import { createHash } from "node:crypto";
2
+ import { writeFile } from "node:fs/promises";
3
+
4
+ function parameters(options) {
5
+ const params = new URLSearchParams();
6
+ const format = options.output ? "source" : options.format || "text";
7
+ if (!["text", "source"].includes(format)) throw new Error("--format must be text or source");
8
+ params.set("format", format);
9
+ for (const [key, min, max] of [["version", 1, Number.MAX_SAFE_INTEGER], ["offset", 0, 2000000], ["limit", 1, 50000]]) {
10
+ if (options[key] === undefined) continue;
11
+ if (!/^\d+$/.test(String(options[key])) || !Number.isSafeInteger(Number(options[key])) || Number(options[key]) < min || Number(options[key]) > max) throw new Error(`Invalid --${key}`);
12
+ params.set(key, String(options[key]));
13
+ }
14
+ if (options.sha256 !== undefined) {
15
+ if (!/^[a-f0-9]{64}$/.test(options.sha256)) throw new Error("Invalid --sha256");
16
+ params.set("sha256", options.sha256);
17
+ }
18
+ if (Number(options.offset) > 0 && !options.sha256) throw new Error("Continuation reads require --sha256 from the first response");
19
+ if (options.output !== undefined && (typeof options.output !== "string" || !options.output)) throw new Error("--output requires a file path");
20
+ return params;
21
+ }
22
+
23
+ export function buildArtifactReadPath(dealId, artifactId, options = {}) {
24
+ if (typeof dealId !== "string" || !dealId || typeof artifactId !== "string" || !artifactId) throw new Error("Usage: llama deal read <dealId> --artifact <artifactId>");
25
+ return `/api/occam/deals/${encodeURIComponent(dealId)}/artifacts/${encodeURIComponent(artifactId)}?${parameters(options)}`;
26
+ }
27
+
28
+ export function buildWikiReadPath(slug, options = {}) {
29
+ if (typeof slug !== "string" || !slug) throw new Error("Usage: llama wiki read <slug>");
30
+ const params = options.format || options.output || options.attachment ? parameters(options) : new URLSearchParams();
31
+ params.set("lang", options.lang === "zh" ? "zh" : "en");
32
+ if (options.attachment !== undefined) {
33
+ if (typeof options.attachment !== "string" || !options.attachment) throw new Error("--attachment requires a reference ID from the wiki read");
34
+ params.set("attachment", options.attachment);
35
+ }
36
+ if (options.version !== undefined) throw new Error("Wiki reads pin --sha256, not --version");
37
+ if (!options.format && !options.output && !options.attachment && [options.offset, options.limit, options.sha256].some(x => x !== undefined)) throw new Error("Use --format text with pagination options");
38
+ return `/api/wiki/${encodeURIComponent(slug)}?${params}`;
39
+ }
40
+
41
+ export async function saveContentSource(result, output) {
42
+ if (!output) return result;
43
+ if (result?.format !== "source" || typeof result.contentBase64 !== "string") throw new Error("Server did not return original file bytes");
44
+ const bytes = Buffer.from(result.contentBase64, "base64");
45
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
46
+ if (sha256 !== result.source?.sha256 || bytes.length !== result.source?.byteSize) throw new Error("Downloaded file hash or size does not match the source");
47
+ await writeFile(output, bytes, { flag: "wx", mode: 0o600 });
48
+ return { saved: output, source: result.source, verified: true };
49
+ }
@@ -12,8 +12,9 @@ export const DEAL_WRITE_OPERATIONS = Object.freeze([
12
12
  /**
13
13
  * A Llama user's own subjective judgment about the team (.people) or the deal
14
14
  * (.business). An agent may record it, but the record must name the user and
15
- * quote them verbatim from their own input, so the type alone stays a reliable
16
- * filter. Core enforces the same rule; this check only fails faster.
15
+ * quote them verbatim from their own input, plus the agent's own restatement in
16
+ * value.summary, so the type alone stays a reliable filter and every record
17
+ * reads the same way. Core enforces the same rule; this check only fails faster.
17
18
  */
18
19
  export const HUMAN_SUBJECTIVE_VIEW_TYPES = Object.freeze([
19
20
  "human_subjective_view.people",
@@ -72,6 +73,13 @@ export function validateHumanSubjectiveView(information, origin) {
72
73
  "human_subjective_view.* value.rawText must appear verbatim inside origin.originalUserUtterance; do not paraphrase",
73
74
  );
74
75
  }
76
+ const summary = stringField(information.value, "summary");
77
+ if (!summary) {
78
+ throw new Error("human_subjective_view.* requires value.summary: the agent's restatement of the user's words");
79
+ }
80
+ if (normalizeWhitespace(summary) === normalizeWhitespace(rawText)) {
81
+ throw new Error("human_subjective_view.* value.summary must restate value.rawText, not repeat it");
82
+ }
75
83
  return information;
76
84
  }
77
85
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "test": "npm run test:agent-routing && npm run test:contract",
8
8
  "test:agent-routing": "node scripts/verify-agent-routing.mjs",
9
- "test:contract": "node --test scripts/build-manifest.test.mjs scripts/core-api-call-sites.test.mjs scripts/deal-actions-cli.test.mjs scripts/deal-actions.test.mjs scripts/deal-memory-actions-cli.test.mjs scripts/deal-memory-actions.test.mjs scripts/mcp-four-action-surface.test.mjs scripts/page-schema-cli.test.mjs scripts/page-schema.test.mjs scripts/server-compatibility.test.mjs scripts/verify-core-api-contract.test.mjs && node scripts/verify-core-api-contract.mjs",
9
+ "test:contract": "node --test scripts/content-read.test.mjs scripts/build-manifest.test.mjs scripts/core-api-call-sites.test.mjs scripts/deal-actions-cli.test.mjs scripts/deal-actions.test.mjs scripts/deal-memory-actions-cli.test.mjs scripts/deal-memory-actions.test.mjs scripts/mcp-four-action-surface.test.mjs scripts/page-schema-cli.test.mjs scripts/page-schema.test.mjs scripts/server-compatibility.test.mjs scripts/verify-core-api-contract.test.mjs && node scripts/verify-core-api-contract.mjs",
10
10
  "verify:artifact": "node scripts/verify-release-artifact.mjs",
11
11
  "verify:release": "npm test && npm run verify:artifact && node scripts/verify-tarball-clean.mjs",
12
12
  "prepack": "node scripts/prepare-build-manifest.mjs",