@deeeed/metamask-harness 0.92.19 → 0.92.21

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
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.92.21 - 2026-10-17
6
+
7
+ ### Added
8
+
9
+ - Restore `mm-harness pr-body render` as a thin command over the shared Farmslot renderer.
10
+
11
+ ### Changed
12
+
13
+ - Pin the `@farmslot` packages to the release set with `@farmslot/recipe-cli` 0.23.0 (protocol 0.54.0, recipe-runner 0.39.0, adapter-sdk 0.18.0, agent-runtime 0.29.0, adapter-web 0.10.14, adapter-node 0.1.34, adapter-rn 0.22.23).
14
+
15
+ ### Fixed
16
+
17
+ - `pr-body render` takes the Validation Recipe from the selected attempt's captured `recipe.json` (or the task's single retained package), not the editable `artifacts/recipe.json`, and names the run, runner, runner revision and recipe digest above its log.
18
+ - `pr-body render` refuses a selected attempt that fails artifact validation or lacks its recipe or report, and refuses two candidate packages when no attempt is selected; it writes nothing in either case.
19
+ - `pr-body render --out` refuses to overwrite the authored description, recipe, workflow, attempt selection or retained evidence.
20
+ - `pr-body render` refuses symlinked or oversized (over 4 MiB) inputs, converts CRLF in fenced logs to LF, and creates new output files owner-only without following a symlink.
21
+
22
+ ## 0.92.20 - 2026-10-17
23
+
24
+ ### Removed
25
+
26
+ - Retire the unused `runtime-health` hidden command and the `health` and `home` aliases of `status`. Extension health checks keep running inside `ensure-ready`, `runtime-decision` and launch verification.
27
+
28
+ ### Changed
29
+
30
+ - Document that hosted tasks resolve the Recipe Cook and Perps checklist roots from each node's pool `env`.
31
+
5
32
  ## 0.92.19 - 2026-10-17
6
33
 
7
34
  ### Fixed
package/README.md CHANGED
@@ -400,16 +400,16 @@ check for upstream updates. Keep the selected library unchanged during a run.
400
400
  ```bash
401
401
  # Write artifacts/pr-description.md in the repository PR template shape (prose
402
402
  # only), then render the publishable body.
403
- farmslot recipe artifacts temp/tasks/dev/TAT-1 --format markdown \
404
- --out temp/tasks/dev/TAT-1/artifacts/pr-body.md \
405
- --command 'farmslot recipe run artifacts/recipe.json'
403
+ mm-harness pr-body render temp/tasks/dev/TAT-1 --command 'mm-harness run artifacts/recipe.json'
406
404
  ```
407
405
 
408
- The Markdown renderer writes `artifacts/pr-body.md`: the description with the "Validation
406
+ `pr-body render` writes `artifacts/pr-body.md`: the description with the "Validation
409
407
  Recipe" and "Validation Logs" sections rendered from `artifacts/recipe.json` and
410
408
  `artifacts/recipe-run/report.md` (and "Recipe Workflow" from `artifacts/workflow.mmd`
411
409
  when it exists), replacing any pasted by hand. The worker never pastes artifacts
412
- into markdown, so fences cannot break.
410
+ into markdown, so fences cannot break. When a passing run recorded the task's
411
+ selected attempt, both sections come from that attempt's validated package; a package
412
+ that fails validation is refused, and nothing is written.
413
413
 
414
414
  ## Review a change
415
415
 
@@ -473,6 +473,8 @@ Farmslot selects that same package through `recipe.provider.root.env`, configure
473
473
 
474
474
  Explicitly invoke `mms-recipe-dev`, `mms-recipe-fix-bug`, `mms-recipe-qa` or the team's static review skill. `mms-recipe-cook` selects or resumes their shared Markdown checklist. Matching descriptions or changed paths never activates these workflows.
475
475
 
476
+ Hosted tasks select that checklist on the execution node, not the gateway. The Core, Mobile and Extension packs root their Recipe Cook checklists at `CONSENSYS_RECIPE_COOK_ROOT` and their Perps checklists at `PERPS_RECIPE_LIBRARY_ROOT`. Set them in every node's pool `env`, not only the gateway's. A node missing the root for the selected checklist refuses the task as source unavailable.
477
+
476
478
  Static Perps review uses a generated source checklist without installing or launching the app harness. `recipe-qa` freezes a PR, range or daily change window and requires runtime smoke plus selected recipe proof. Official release validation uses its named release profile and proof lane.
477
479
 
478
480
  TASK.md holds the request; CHECKLIST.md is the sole step list. The frozen terminal contract and task-local mark validate completion. Publication, retained sessions and workspace cleanup belong to the caller. Farmslot handles them through its control plane; direct invocation returns the outcome to the engineer. See the [task-directory guide](site/task-dir.html) and [QA tutorial](site/tutorials/v5.html).
@@ -11,10 +11,11 @@ const SPEC = {
11
11
  { name: "help", desc: "Load version-matched recipe guidance (help review composes the review guide)", args: ["review"], flags: ["--json", "--adapter", "--target", "--domain"] },
12
12
  { name: "review", desc: "Materialize a review checklist composed from the base review and a team library", args: ["checklist"], flags: ["--domain", "--since", "--base", "--out", "--adapter", "--target", "--json"] },
13
13
  { name: "domain", desc: "Which team library owns the change (declared value, else owned-paths.json)", flags: ["--domain", "--base", "--adapter", "--target", "--json"] },
14
+ { name: "pr-body", desc: "Render the publishable PR body: pr-description.md plus the recipe and run sections built from artifacts", args: ["render", "<task-dir>"], flags: ["--command", "--out", "--json"] },
14
15
  { name: "config", desc: "Per-engineer locations and optional advisor settings", args: ["list", "path", "get", "set", "unset"], flags: ["--json"] },
15
16
  { name: "tutorial", desc: "Open the visual recipe tutorial", flags: ["--json", "--no-open"] },
16
17
  { name: "setup-base", desc: "Bootstrap numbered product checkouts", flags: ["--dir", "--counts", "--only", "--dry-run", "--force", "--json", "--show-config", "--reset-config", "--skip-harness-update"] },
17
- { name: "status", aliases: ["health", "home"], desc: "Home status + next commands", flags: ["--json", "--fast", "--watch", "--task"] },
18
+ { name: "status", desc: "Home status + next commands", flags: ["--json", "--fast", "--watch", "--task"] },
18
19
  { name: "ports", desc: "Slot ports and runtime paths", flags: ["--json"] },
19
20
  { name: "up", desc: "Decide + run minimum work to reach ready", flags: ["--json", "--dry-run"] },
20
21
  { name: "sync", desc: "Refresh harness + canonicalize wallet fixture", flags: ["--json"] },
@@ -175,6 +175,40 @@ Example:
175
175
  })
176
176
  }
177
177
  },
178
+ {
179
+ name: "pr-body",
180
+ summary: "Render the publishable PR body: the authored description plus the recipe and run log sections built from artifacts.",
181
+ example: "mm-harness pr-body render temp/tasks/dev/TAT-1",
182
+ helpText: `mm-harness pr-body render <task-dir> [flags]
183
+
184
+ Reads <task-dir>/artifacts/pr-description.md (the PR body the worker wrote in
185
+ the repository PR template shape) and inserts the machine sections rendered
186
+ from artifacts: "Validation Recipe" from artifacts/recipe.json, "Validation
187
+ Logs" from artifacts/recipe-run/report.md, and "Recipe Workflow" from
188
+ artifacts/workflow.mmd when it exists. Any such section the worker pasted by
189
+ hand is replaced. The sections go before the first checklist section, else at
190
+ the end. Fences are always longer than any backtick run in the content and
191
+ close on their own line. Writes <task-dir>/artifacts/pr-body.md.
192
+
193
+ When a passing run recorded the task's selected attempt, both sections come
194
+ from that attempt's package, which must pass validation or nothing is written.
195
+
196
+ --command <text> Recipe invocation to show above the run log
197
+ --out <file> Write elsewhere than artifacts/pr-body.md (never over an
198
+ authored input or retained evidence)
199
+ --json Machine-readable result
200
+
201
+ Example:
202
+ mm-harness pr-body render temp/tasks/dev/TAT-1 --json`,
203
+ contract: {
204
+ options: options(HELP, JSON, {
205
+ "--command": value(),
206
+ "--out": value()
207
+ }),
208
+ positionals: [{ label: "action", choices: ["render"] }, { label: "task-dir" }],
209
+ minimumPositionals: 2
210
+ }
211
+ },
178
212
  {
179
213
  name: "config",
180
214
  summary: "Per-engineer locations and optional recipe advisor settings.",
@@ -258,13 +292,12 @@ Example:
258
292
  },
259
293
  {
260
294
  name: "status",
261
- aliases: ["health", "home"],
262
295
  summary: "Home dashboard for a checkout: detected adapter, next command, and (mobile) connected devices with the selected target.",
263
296
  example: "mm-harness status",
264
297
  helpText: `mm-harness status [flags]
265
298
 
266
299
  Home dashboard for the current checkout \u2014 the detected adapter, the next command
267
- to run, and (mobile only) the connected devices with live app state. Aliases: health, home.
300
+ to run, and (mobile only) the connected devices with live app state.
268
301
 
269
302
  --adapter <{adapters}> Target adapter (auto-detected inside a checkout)
270
303
  --target <path> Checkout path (default: cwd)
@@ -1409,7 +1442,6 @@ Example:
1409
1442
  ];
1410
1443
  const HIDDEN_COMMANDS = [
1411
1444
  "completion-candidates",
1412
- "runtime-health",
1413
1445
  "runtime-decision",
1414
1446
  "resolve-extension",
1415
1447
  "ensure-ready",
@@ -0,0 +1,29 @@
1
+ import { optionFlag, optionString, parseArgs, renderRetainedArtifactsMarkdown, usageError } from "@farmslot/recipe-cli/harness";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ const USAGE = "Usage: mm-harness pr-body render <task-dir> [--command <text>] [--out <file>] [--json]";
5
+ async function handlePrBody(argv) {
6
+ const parsed = parseArgs(argv);
7
+ const [action, taskArg] = parsed.positional;
8
+ if (action !== "render" || !taskArg) throw usageError(USAGE);
9
+ const taskDir = path.resolve(taskArg);
10
+ if (!fs.existsSync(path.join(taskDir, "artifacts"))) {
11
+ throw usageError(`${taskDir} has no artifacts/ directory; pass the task directory mm-harness task init wrote.`);
12
+ }
13
+ const command = optionString(parsed.options, "command");
14
+ const out = path.resolve(optionString(parsed.options, "out") ?? path.join(taskDir, "artifacts", "pr-body.md"));
15
+ const { result } = await renderRetainedArtifactsMarkdown({ directory: taskDir, command, out, format: "markdown" });
16
+ const { sections = [], recipe, run, workflow } = result;
17
+ if (optionFlag(parsed.options, "json")) {
18
+ process.stdout.write(`${JSON.stringify({ command: "pr-body", subcommand: "render", status: "ok", taskDir, out, sections, recipe, run, workflow }, null, 2)}
19
+ `);
20
+ } else {
21
+ process.stdout.write(`pr body: ${out}
22
+ ${sections.join("\n ")}
23
+ `);
24
+ }
25
+ return 0;
26
+ }
27
+ export {
28
+ handlePrBody
29
+ };
@@ -48,13 +48,13 @@ import { handleHelp } from "./commands/help.js";
48
48
  import { handleLogs } from "./commands/logs.js";
49
49
  import { overlayCommandOptions } from "./commands/overlay.js";
50
50
  import { handlePin } from "./commands/pin.js";
51
+ import { handlePrBody } from "./commands/pr-body.js";
51
52
  import { handlePrepare } from "./commands/prepare.js";
52
53
  import { handleProvision } from "./commands/provision.js";
53
54
  import { handleRecipeQuality } from "./commands/recipe-quality.js";
54
55
  import { handleResolveExtension } from "./commands/resolve-extension.js";
55
56
  import { handleReview } from "./commands/review.js";
56
57
  import { handleRuntimeDecision } from "./commands/runtime-decision.js";
57
- import { handleRuntimeHealth } from "./commands/runtime-health.js";
58
58
  import { handleRuntimeLaunch } from "./commands/runtime-launch.js";
59
59
  import { handleSelfTest } from "./commands/self-test.js";
60
60
  import { handleSetupBase } from "./commands/setup-base.js";
@@ -139,6 +139,7 @@ const PUBLIC_RUN = {
139
139
  help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
140
140
  review: { run: handleReview },
141
141
  domain: { run: handleDomain },
142
+ "pr-body": { run: handlePrBody },
142
143
  config: { run: handleConfig },
143
144
  tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
144
145
  "setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
@@ -173,7 +174,6 @@ const PUBLIC_RUN = {
173
174
  const HIDDEN_RUN = {
174
175
  // Shell completion answers for the checkout's adapter, so it resolves one.
175
176
  "completion-candidates": { run: handleCompletionCandidates, nudge: false, context: "adapter" },
176
- "runtime-health": { run: (args) => handleRuntimeHealth(parseArgs(args)) },
177
177
  "runtime-decision": { run: (args) => handleRuntimeDecision(parseArgs(args, { recordIsFlag: true })) },
178
178
  "resolve-extension": { run: (args) => handleResolveExtension(parseArgs(args)) },
179
179
  "ensure-ready": { run: (args) => handleEnsureReady(parseArgs(args)) },
@@ -198,7 +198,7 @@ const HELP_GROUPS = [
198
198
  {
199
199
  title: "PROVE",
200
200
  blurb: "run recipes and inspect readiness",
201
- commands: ["run", "last", "doctor", "check", "checklist", "review", "recipe-quality"]
201
+ commands: ["run", "last", "doctor", "check", "checklist", "review", "pr-body", "recipe-quality"]
202
202
  },
203
203
  {
204
204
  title: "RUNTIME OVERLAY",
@@ -92,7 +92,7 @@ command-local platform ladders for surface-owned behavior:
92
92
  `prepare`, `debug`, `reload` and `runtime-decision` read their own members.
93
93
 
94
94
  A few comparisons remain where a command exists for one platform
95
- (`runtime-health`, `resolve-extension`, `ensure-ready`, `runtime-launch`) or
95
+ (`resolve-extension`, `ensure-ready`, `runtime-launch`) or
96
96
  code compares a platform's own data (`review/knowledge.ts`, the Core jest
97
97
  grouping in `check.ts`, the Mobile release-artifact state file).
98
98
 
@@ -62,7 +62,8 @@ Callers scanned (2026-10-02):
62
62
  | Command | Caller | Consumes | G: |
63
63
  |---|---|---|---|
64
64
  | `checklist mark <taskDir> {start\|<n>\|complete [--mark-last]\|blocked --reason <s>\|no-change --reason <s>\|--help}` | `{{TASK_DIR}}/mark` shim (`mark_cmd`) | exit code; `SIGNAL.json`, `CHECKLIST.md` | `task/mark-*` |
65
- | `farmslot recipe artifacts <taskDir> --format markdown --out <file> --json` | migrated pack hooks and PR-description skills | successful exit, structured `error.message`, explicit `out`; no implicit output file | shared recipe-cli artifact contracts; retained Markdown baselines |
65
+ | `pr-body render <taskDir> [--out <file>] --json` (cwd = task dir) | MetaMask engineers and skills; the gateway `pr-body-render.ts` when a pack's `pr_body_cmd` names it | on failure `JSON.parse(stdout).error`, else stderr; then `--out`, else `artifacts/pr-body.md` | `task/pr-body-render-*` |
66
+ | `farmslot recipe artifacts <taskDir> --format markdown --out <file> --json` | farm pack hooks (`vars.pr_body_cmd`) and PR-description skills on the farm | successful exit, structured `error.message`, explicit `out`; no implicit output file | shared recipe-cli artifact contracts; retained Markdown baselines |
66
67
  | `review checklist --out <file> [--domain <d>] [--since <sha>]` | domain fixtures, review templates | the written file | `task/review-checklist` |
67
68
  | `check diff …`, `recipe-quality build …` | templates | exit code | not covered: run repo lint/test tooling |
68
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.92.19",
3
+ "version": "0.92.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -21,15 +21,15 @@
21
21
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
22
22
  },
23
23
  "dependencies": {
24
- "@farmslot/adapter-node": "0.1.33",
25
- "@farmslot/adapter-rn": "0.22.22",
26
- "@farmslot/adapter-sdk": "0.17.1",
27
- "@farmslot/adapter-web": "0.10.13",
28
- "@farmslot/agent-runtime": "0.28.0",
24
+ "@farmslot/adapter-node": "0.1.34",
25
+ "@farmslot/adapter-rn": "0.22.23",
26
+ "@farmslot/adapter-sdk": "0.18.0",
27
+ "@farmslot/adapter-web": "0.10.14",
28
+ "@farmslot/agent-runtime": "0.29.0",
29
29
  "@farmslot/handoff": "^0.3.1",
30
- "@farmslot/protocol": "0.53.0",
31
- "@farmslot/recipe-cli": "0.22.5",
32
- "@farmslot/recipe-runner": "0.38.4",
30
+ "@farmslot/protocol": "0.54.0",
31
+ "@farmslot/recipe-cli": "0.23.0",
32
+ "@farmslot/recipe-runner": "0.39.0",
33
33
  "agent-device": "0.19.3",
34
34
  "commander": "^12.0.0",
35
35
  "esbuild": "0.28.1",
@@ -1,19 +0,0 @@
1
- import { adapterOption, optionFlag, optionString, parsePort, targetPath } from "@farmslot/recipe-cli/harness";
2
- async function handleRuntimeHealth({ options }) {
3
- const adapter = adapterOption(options);
4
- if (adapter !== "extension") throw new Error("runtime-health currently applies to the extension adapter.");
5
- const target = targetPath(options);
6
- const cdpPort = parsePort(
7
- optionString(options, "cdpPort") ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT,
8
- "runtime-health requires --cdp-port <port>."
9
- );
10
- const { checkExtensionRuntimeHealth, formatHealthFailure } = await import("../adapters/extension/runtime.js");
11
- const report = await checkExtensionRuntimeHealth(target, cdpPort);
12
- if (optionFlag(options, "json")) console.log(JSON.stringify(report, null, 2));
13
- else if (report.status === "PASS") console.log(`PASS extension runtime cdp=${cdpPort} target=${report.targetUrl}`);
14
- else console.error(formatHealthFailure(report, target));
15
- return report.status === "PASS" ? 0 : 1;
16
- }
17
- export {
18
- handleRuntimeHealth
19
- };