@deeeed/metamask-harness 0.56.0 → 0.58.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.
@@ -2,6 +2,8 @@ import { spawnSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "node:path";
5
+ import { HANDOFF_INPUT } from "@farmslot/agent-runtime";
6
+ import { enumerateChecklistCheckboxes, isTerminalWorkerSignalStatus } from "@farmslot/protocol";
5
7
  import { EXIT } from "./shared.js";
6
8
  const require2 = createRequire(import.meta.url);
7
9
  const MARK_SCRIPT = require2.resolve(
@@ -88,7 +90,7 @@ function runCloseout(taskDir, args, stdio) {
88
90
  }
89
91
  }
90
92
  function stageLearningPackage(taskDir, step, markArgs) {
91
- if (!["complete", "no-change"].includes(step ?? "") || markArgs.includes("--skip-learnings") || !fs.existsSync(path.join(taskDir, "inputs", "handoff.json"))) {
93
+ if (!["complete", "no-change"].includes(step ?? "") || markArgs.includes("--skip-learnings") || !fs.existsSync(path.join(taskDir, HANDOFF_INPUT))) {
92
94
  return;
93
95
  }
94
96
  try {
@@ -108,7 +110,7 @@ function terminalSignalStatus(taskDir) {
108
110
  } catch {
109
111
  return null;
110
112
  }
111
- return signal.status === "complete" || signal.status === "blocked" ? signal.status : null;
113
+ return isTerminalWorkerSignalStatus(signal.status) ? signal.status ?? null : null;
112
114
  }
113
115
  function preserveTerminalSignal(taskDir, step) {
114
116
  if (step !== "start" && numericMarkStep(step) === null) return false;
@@ -124,20 +126,8 @@ function numericMarkStep(step) {
124
126
  return Number.isInteger(stepNumber) && stepNumber >= 1 ? stepNumber : null;
125
127
  }
126
128
  function checklistStepRequiresDiffPass(checklist, stepNumber) {
127
- let inFence = false;
128
- let seen = 0;
129
- for (const line of checklist.split(/\n/u)) {
130
- if (/^\s*```/u.test(line)) {
131
- inFence = !inFence;
132
- continue;
133
- }
134
- if (inFence) continue;
135
- const match = line.match(/^\s*[-*]\s+\[(?: |x|X)\]\s+(.*)$/u);
136
- if (!match) continue;
137
- seen += 1;
138
- if (seen === stepNumber) return /mm-harness check diff/u.test(match[1] ?? "");
139
- }
140
- return false;
129
+ const step = enumerateChecklistCheckboxes(checklist).find((item) => item.stepNumber === stepNumber);
130
+ return step !== void 0 && /mm-harness check diff/u.test(step.rawLabel);
141
131
  }
142
132
  function checkDiffGateReady(taskDir, step) {
143
133
  const stepNumber = numericMarkStep(step);
@@ -89,7 +89,7 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
89
89
  preExecute
90
90
  );
91
91
  if (preExecuteDrift.length > 0) {
92
- const provenancePath2 = writeExecutionProvenance(
92
+ const provenancePath2 = await writeExecutionProvenance(
93
93
  absoluteArtifactsDir,
94
94
  provenanceSnapshots,
95
95
  preExecuteDrift
@@ -134,7 +134,7 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
134
134
  const end = await captureExecutionProvenance(provenanceInput, "end");
135
135
  provenanceSnapshots.push(end);
136
136
  const endDrift = executionProvenanceDrift(provenanceSnapshots[0], end);
137
- const provenancePath = writeExecutionProvenance(
137
+ const provenancePath = await writeExecutionProvenance(
138
138
  absoluteArtifactsDir,
139
139
  provenanceSnapshots,
140
140
  endDrift,
@@ -1,11 +1,11 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import { isTerminalWorkerSignalStatus } from "@farmslot/protocol";
3
4
  import { color } from "../cli-color.js";
4
5
  import {
5
6
  CHECKLIST_LABEL_LIMIT,
6
7
  collectTaskView,
7
8
  findTaskDir,
8
- isTerminalStatus,
9
9
  TASK_SILENT_AFTER_MS
10
10
  } from "../task-view.js";
11
11
  import { EXIT } from "./shared.js";
@@ -59,7 +59,7 @@ async function runStatusWatch(options, deps) {
59
59
  `);
60
60
  }
61
61
  previous = view;
62
- const terminal = isTerminalStatus(view.signal?.status);
62
+ const terminal = isTerminalWorkerSignalStatus(view.signal?.status);
63
63
  if (view.activity.ongoing) sawInProgress = true;
64
64
  if (!options.watch || terminal && (sawInProgress || options.taskDir !== void 0)) return EXIT.ok;
65
65
  await deps.wait(REFRESH_MS);
@@ -106,6 +106,14 @@ function renderTaskView(view, title, explicitTask = false) {
106
106
  showTask && view.sandbox?.ready !== void 0 && `sandbox ${view.sandbox.ready ? out("ok", "ready") : out("warn", "not ready")}`
107
107
  ]);
108
108
  if (contextLine) lines.push(contextLine);
109
+ const last = view.lastCommand;
110
+ if (showTask && last) {
111
+ lines.push(joinParts([
112
+ `last command ${out("info", joinParts([last.command, last.subject], " "))}`,
113
+ last.verdict && statusWord(out, last.verdict),
114
+ last.ageMs !== void 0 && `${formatDuration(last.ageMs)} ago`
115
+ ]));
116
+ }
109
117
  lines.push("");
110
118
  if (!view.activity.ongoing) {
111
119
  lines.push(out("warn", "no ongoing task detected"));
@@ -169,6 +177,9 @@ function changedLines(previous, next) {
169
177
  if (previous.signal?.status !== next.signal?.status) {
170
178
  lines.push(`status ${next.signal?.status ?? "unknown"}`);
171
179
  }
180
+ if (next.lastCommand && previous.lastCommand?.at !== next.lastCommand.at) {
181
+ lines.push(`command ${joinParts([next.lastCommand.command, next.lastCommand.subject], " ")}${next.lastCommand.verdict ? ` ${next.lastCommand.verdict}` : ""}`);
182
+ }
172
183
  return lines;
173
184
  }
174
185
  function formatDuration(ms) {
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import fs, { constants as fsConstants } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { mobileSourceFingerprint } from "./adapters/mobile/source-freshness.js";
6
+ import { indexArtifactManifest } from "./artifact-files.js";
6
7
  import { importRecipeProtocol, runnerDir } from "./paths.js";
7
8
  const gitContexts = /* @__PURE__ */ new Map();
8
9
  let protocolPromise;
@@ -102,7 +103,7 @@ function executionProvenanceDrift(start, current) {
102
103
  }
103
104
  return drift;
104
105
  }
105
- function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManifestPath) {
106
+ async function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManifestPath) {
106
107
  const provenancePath = path.join(path.resolve(artifactsDir), "execution-provenance.json");
107
108
  const record = {
108
109
  schemaVersion: 1,
@@ -113,7 +114,20 @@ function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManife
113
114
  fs.mkdirSync(path.dirname(provenancePath), { recursive: true });
114
115
  fs.writeFileSync(provenancePath, `${JSON.stringify(record, null, 2)}
115
116
  `);
116
- if (artifactManifestPath) indexProvenanceArtifact(artifactManifestPath, record);
117
+ if (artifactManifestPath) {
118
+ await indexArtifactManifest(artifactManifestPath, [
119
+ {
120
+ path: "execution-provenance.json",
121
+ type: "json",
122
+ label: "Execution provenance",
123
+ category: "system",
124
+ metadata: {
125
+ valid: record.valid,
126
+ errorCode: record.valid ? null : "PROVENANCE_DRIFT"
127
+ }
128
+ }
129
+ ]);
130
+ }
117
131
  return provenancePath;
118
132
  }
119
133
  function recipeFileDigest(protocol, recipePath) {
@@ -309,32 +323,6 @@ function compareSource(drift, phase, prefix, start, current) {
309
323
  function compare(drift, phase, field, start, current) {
310
324
  if (start !== current) drift.push({ phase, field, start, current });
311
325
  }
312
- function indexProvenanceArtifact(artifactManifestPath, record) {
313
- let manifest;
314
- try {
315
- manifest = JSON.parse(fs.readFileSync(artifactManifestPath, "utf8"));
316
- } catch {
317
- return;
318
- }
319
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return;
320
- const document = manifest;
321
- const artifacts = Array.isArray(document.artifacts) ? document.artifacts : [];
322
- document.artifacts = [
323
- ...artifacts.filter((artifact) => !artifact || typeof artifact !== "object" || Array.isArray(artifact) || artifact.path !== "execution-provenance.json"),
324
- {
325
- path: "execution-provenance.json",
326
- type: "json",
327
- label: "Execution provenance",
328
- category: "system",
329
- metadata: {
330
- valid: record.valid,
331
- errorCode: record.valid ? null : "PROVENANCE_DRIFT"
332
- }
333
- }
334
- ];
335
- fs.writeFileSync(artifactManifestPath, `${JSON.stringify(document, null, 2)}
336
- `);
337
- }
338
326
  export {
339
327
  ProvenanceDriftError,
340
328
  captureExecutionProvenance,
package/dist/harness.js CHANGED
@@ -259,6 +259,9 @@ Next: mm-harness install --platform core --target ${target}`
259
259
  }
260
260
  return { command: delegate, prefixArgs: ["doctor", "--adapter", "core"] };
261
261
  }
262
+ if (adapter === "mobile" && action === "verify") {
263
+ return { inProcess: "mobile-verify" };
264
+ }
262
265
  const installedScript = path.join(recipeHarnessPath(target, adapter), "scripts", `${action}.sh`);
263
266
  if (fs.existsSync(installedScript)) {
264
267
  return { command: installedScript, prefixArgs: [] };
@@ -336,6 +339,29 @@ async function handleHarness(argv) {
336
339
  console.error(colorHumanMessage(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`));
337
340
  }
338
341
  const start = Date.now();
342
+ if ("inProcess" in dispatch) {
343
+ const { runMobileVerify } = await import("./adapters/mobile/verify.js");
344
+ let exitCode;
345
+ try {
346
+ exitCode = await runMobileVerify(forwardArgs, {
347
+ out: quiet ? () => {
348
+ } : (line) => {
349
+ (json ? process.stderr : process.stdout).write(`${line}
350
+ `);
351
+ },
352
+ err: quiet ? () => {
353
+ } : (line) => {
354
+ process.stderr.write(`${line}
355
+ `);
356
+ }
357
+ });
358
+ } catch (error) {
359
+ if (!quiet) process.stderr.write(`${error instanceof Error ? error.message : String(error)}
360
+ `);
361
+ exitCode = 1;
362
+ }
363
+ return reportHarnessOutcome(harnessAction, adapter, target, exitCode, autoDetected, json, quiet, start);
364
+ }
339
365
  if (shellLeafMissing(dispatch.command)) {
340
366
  const message = missingShellLeafMessage(dispatch.command);
341
367
  if (quiet) {
@@ -363,7 +389,6 @@ async function handleHarness(argv) {
363
389
  stdio: quiet ? "ignore" : json ? ["inherit", 2, "inherit"] : "inherit",
364
390
  env: process.env
365
391
  });
366
- const seconds = ((Date.now() - start) / 1e3).toFixed(1);
367
392
  if (result.error) {
368
393
  if (quiet) {
369
394
  return 1;
@@ -381,27 +406,30 @@ async function handleHarness(argv) {
381
406
  }
382
407
  return 1;
383
408
  }
384
- const exitCode = result.status ?? 1;
409
+ return reportHarnessOutcome(harnessAction, adapter, target, result.status ?? 1, autoDetected, json, quiet, start);
410
+ }
411
+ function reportHarnessOutcome(action, adapter, target, exitCode, autoDetected, json, quiet, start) {
412
+ const seconds = ((Date.now() - start) / 1e3).toFixed(1);
385
413
  if (quiet) {
386
414
  return exitCode;
387
415
  } else if (json) {
388
416
  console.log(
389
417
  harnessSummary(
390
- harnessAction,
418
+ action,
391
419
  adapter,
392
420
  target,
393
421
  exitCode === 0 ? "pass" : "fail",
394
422
  exitCode,
395
423
  autoDetected,
396
- exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${harnessAction} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(adapter, harnessAction) }
424
+ exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${action} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(adapter, action) }
397
425
  )
398
426
  );
399
427
  } else if (exitCode === 0) {
400
- console.error(colorHumanMessage(`\u2713 ${harnessAction} ${adapter} passed (${seconds}s)
401
- Next: ${successNext(adapter, harnessAction, target)}`));
428
+ console.error(colorHumanMessage(`\u2713 ${action} ${adapter} passed (${seconds}s)
429
+ Next: ${successNext(adapter, action, target)}`));
402
430
  } else {
403
- console.error(colorHumanMessage(`\u2717 ${harnessAction} ${adapter} failed (exit ${exitCode}, ${seconds}s)
404
- ${failureHint(adapter, harnessAction)}`));
431
+ console.error(colorHumanMessage(`\u2717 ${action} ${adapter} failed (exit ${exitCode}, ${seconds}s)
432
+ ${failureHint(adapter, action)}`));
405
433
  }
406
434
  return exitCode;
407
435
  }
package/dist/task-view.js CHANGED
@@ -1,34 +1,24 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { HANDOFF_INPUT, latestTaskDir } from "@farmslot/agent-runtime";
5
+ import {
6
+ checklistStepName,
7
+ deriveChecklistStepDurations,
8
+ enumerateChecklistCheckboxes,
9
+ INTERACTIVE_CHECKLIST_MARKDOWN,
10
+ isTerminalWorkerSignalStatus,
11
+ READINESS_RECORD,
12
+ WORKER_SIGNAL_FILE
13
+ } from "@farmslot/protocol";
14
+ import { readCommandJournal } from "./command-journal.js";
4
15
  import { resolveRuntimeContextPath } from "./harness.js";
16
+ import { recipeRuntimeDir } from "./paths.js";
5
17
  import { parseLibraryPathEnv } from "./review/knowledge.js";
6
- const TASK_SEARCH_DEPTH = 3;
7
18
  const CHECKLIST_LABEL_LIMIT = 70;
8
- const CHECKLIST_ROW = /^\s*-\s\[([ xX])\]\s*(\d+)\.\s*(.*)$/u;
9
- const CHECKLIST_PHASE = /^##\s+(.+?)\s*$/u;
10
19
  const TASK_SILENT_AFTER_MS = 10 * 60 * 1e3;
11
20
  function findTaskDir(target) {
12
- const root = path.join(target, "temp", "tasks");
13
- let bySignal;
14
- let byChecklist;
15
- const consider = (best, dir, file) => {
16
- const stat = fs.statSync(path.join(dir, file), { throwIfNoEntry: false });
17
- if (!stat?.isFile()) return best;
18
- return best && best.mtimeMs >= stat.mtimeMs ? best : { dir, mtimeMs: stat.mtimeMs };
19
- };
20
- const visit = (dir, depth) => {
21
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
22
- if (!entry.isDirectory()) continue;
23
- const child = path.join(dir, entry.name);
24
- bySignal = consider(bySignal, child, "SIGNAL.json");
25
- byChecklist = consider(byChecklist, child, "CHECKLIST.md");
26
- if (depth < TASK_SEARCH_DEPTH) visit(child, depth + 1);
27
- }
28
- };
29
- if (!fs.statSync(root, { throwIfNoEntry: false })?.isDirectory()) return void 0;
30
- visit(root, 1);
31
- return (bySignal ?? byChecklist)?.dir;
21
+ return latestTaskDir(path.join(target, "temp", "tasks"));
32
22
  }
33
23
  function collectTaskView(target, taskDir, now) {
34
24
  const base = {
@@ -39,20 +29,29 @@ function collectTaskView(target, taskDir, now) {
39
29
  )
40
30
  };
41
31
  if (!taskDir) return { ...base, checklist: [], activity: { ongoing: false, reason: "no-task" } };
42
- const handoff = readJsonObject(path.join(taskDir, "inputs", "handoff.json"));
43
- const provenance = readJsonObject(path.join(taskDir, "artifacts", "harness-provenance.json"));
32
+ const handoff = readJsonObject(path.join(taskDir, HANDOFF_INPUT));
44
33
  const fixtures = readJsonObject(path.join(taskDir, "artifacts", "prepare", "fixtures.json"));
45
- const sandbox = readJsonObject(path.join(taskDir, "artifacts", "sandbox.json"));
46
- const signal = readJsonObject(path.join(taskDir, "SIGNAL.json"));
47
- const checklist = readChecklistRows(path.join(taskDir, "CHECKLIST.md"));
34
+ const sandbox = readJsonObject(path.join(taskDir, "artifacts", READINESS_RECORD)) ?? readJsonObject(path.join(target, recipeRuntimeDir(), READINESS_RECORD));
35
+ const signal = readJsonObject(path.join(taskDir, WORKER_SIGNAL_FILE));
36
+ const checklist = readChecklistRows(path.join(taskDir, INTERACTIVE_CHECKLIST_MARKDOWN));
48
37
  const template = asObject(handoff?.executionTemplate);
49
38
  const task = asObject(handoff?.task);
50
- const startedAt = parseTime(pickString(handoff, "startedAt"));
51
- const timing = applyTiming(checklist, signal, startedAt, now);
39
+ const startedAtIso = pickString(handoff, "startedAt");
40
+ const startedAt = parseTime(startedAtIso);
41
+ const timing = applyTiming(checklist, signal, startedAtIso, now);
52
42
  const ticket = pickString(task, "ticket");
43
+ const journal = readLastCommand(target, now);
44
+ const commandAt = parseTime(journal?.at);
45
+ const commandCounts = commandAt !== void 0 && (startedAt === void 0 || commandAt >= startedAt);
46
+ const lastCommand = commandCounts ? journal : void 0;
53
47
  const activity = taskActivity({
54
48
  status: pickString(signal, "status"),
55
- lastWrites: [startedAt, parseTime(pickString(signal, "timestamp")), ...readMarkTimes(signal).values()],
49
+ lastWrites: [
50
+ startedAt,
51
+ parseTime(pickString(signal, "timestamp")),
52
+ ...readMarkEvents(signal).map((event) => Date.parse(event.checkedAt)),
53
+ commandCounts ? commandAt : void 0
54
+ ],
56
55
  branch: base.checkout.branch,
57
56
  ticket,
58
57
  now
@@ -60,9 +59,10 @@ function collectTaskView(target, taskDir, now) {
60
59
  return {
61
60
  ...base,
62
61
  taskDir,
62
+ // The readiness record is the only place a harness records itself for a task.
63
63
  harness: section({
64
- version: pickString(asObject(provenance?.package), "version"),
65
- sourceKind: pickString(asObject(provenance?.source), "kind")
64
+ version: pickString(asObject(sandbox?.harness), "version"),
65
+ sourceKind: pickString(asObject(sandbox?.harness), "source")
66
66
  }),
67
67
  template: section({
68
68
  id: pickString(template, "id"),
@@ -87,10 +87,26 @@ function collectTaskView(target, taskDir, now) {
87
87
  timestamp: pickString(signal, "timestamp"),
88
88
  attemptId: pickString(signal, "attemptId")
89
89
  }),
90
+ ...lastCommand ? { lastCommand } : {},
90
91
  ...timing,
91
92
  activity
92
93
  };
93
94
  }
95
+ function readLastCommand(target, now) {
96
+ const { record } = readCommandJournal(target);
97
+ if (!record) return void 0;
98
+ const first = record.args[0];
99
+ const subject = first !== void 0 && !first.startsWith("-") ? first : void 0;
100
+ const at = record.finishedAt || record.startedAt;
101
+ const atMs = parseTime(at);
102
+ return {
103
+ command: record.command,
104
+ ...subject ? { subject } : {},
105
+ verdict: record.verdict,
106
+ ...at ? { at } : {},
107
+ ...atMs !== void 0 ? { ageMs: Math.max(0, now - atMs) } : {}
108
+ };
109
+ }
94
110
  function taskActivity(input) {
95
111
  const writes = input.lastWrites.filter((value) => value !== void 0);
96
112
  const idleMs = writes.length > 0 ? input.now - Math.max(...writes) : void 0;
@@ -100,71 +116,59 @@ function taskActivity(input) {
100
116
  ...branchCarriesTicket !== void 0 ? { branchCarriesTicket } : {}
101
117
  };
102
118
  if (input.status === void 0 && writes.length === 0) return { ongoing: false, reason: "no-task", ...extra };
103
- if (isTerminalStatus(input.status)) return { ongoing: false, reason: "finished", ...extra };
119
+ if (isTerminalWorkerSignalStatus(input.status)) return { ongoing: false, reason: "finished", ...extra };
104
120
  if (idleMs !== void 0 && idleMs > TASK_SILENT_AFTER_MS) return { ongoing: false, reason: "silent", ...extra };
105
121
  return { ongoing: true, ...extra };
106
122
  }
107
- function isTerminalStatus(status) {
108
- return status !== void 0 && status !== "running" && status !== "started";
109
- }
110
- function applyTiming(rows, signal, startedAt, now) {
111
- const marks = readMarkTimes(signal);
112
- let previousAt = startedAt;
113
- let lastAt;
114
- let firstAt;
123
+ function applyTiming(rows, signal, startedAtIso, now) {
124
+ const events = readMarkEvents(signal);
125
+ const durations = new Map(
126
+ deriveChecklistStepDurations({ schemaVersion: 1, events }, startedAtIso).map((step) => [
127
+ step.stepNumber,
128
+ step.durationMs
129
+ ])
130
+ );
131
+ const marks = new Map(events.map((event) => [event.stepNumber, Date.parse(event.checkedAt)]));
115
132
  for (const row of rows) {
116
- const checkedAt = marks.get(row.number);
117
- if (checkedAt === void 0) continue;
118
- if (previousAt !== void 0) row.durationMs = checkedAt - previousAt;
119
- if (firstAt === void 0) firstAt = checkedAt;
120
- previousAt = checkedAt;
121
- lastAt = checkedAt;
133
+ const durationMs = durations.get(row.number);
134
+ if (durationMs !== void 0) row.durationMs = durationMs;
122
135
  }
136
+ const instants = [...marks.values()];
137
+ const lastAt = instants.length > 0 ? Math.max(...instants) : void 0;
138
+ const firstAt = instants.length > 0 ? Math.min(...instants) : void 0;
123
139
  const current = rows.find((row) => !row.checked);
124
140
  if (current && lastAt !== void 0) current.runningMs = now - lastAt;
125
- const origin = startedAt ?? firstAt;
141
+ const origin = parseTime(startedAtIso) ?? firstAt;
126
142
  return {
127
143
  ...current ? { currentStep: current.number } : {},
128
144
  ...origin !== void 0 ? { elapsedMs: now - origin } : {}
129
145
  };
130
146
  }
131
- function readMarkTimes(signal) {
132
- const marks = /* @__PURE__ */ new Map();
147
+ function readMarkEvents(signal) {
133
148
  const events = asObject(signal?.checklistTiming)?.events;
134
- if (!Array.isArray(events)) return marks;
149
+ if (!Array.isArray(events)) return [];
150
+ const marks = [];
135
151
  for (const entry of events) {
136
152
  const event = asObject(entry);
137
153
  const stepNumber = pickNumber(event, "stepNumber");
138
- const checkedAt = parseTime(pickString(event, "checkedAt"));
139
- if (stepNumber === void 0 || checkedAt === void 0) continue;
140
- marks.set(stepNumber, checkedAt);
154
+ const checkedAt = pickString(event, "checkedAt");
155
+ if (stepNumber === void 0 || parseTime(checkedAt) === void 0) continue;
156
+ marks.push({ stepNumber, label: "", checkedAt });
141
157
  }
142
158
  return marks;
143
159
  }
144
160
  function readChecklistRows(file) {
145
161
  const text = readTextFile(file);
146
162
  if (text === void 0) return [];
147
- const rows = [];
148
- let phase;
149
- for (const line of text.split("\n")) {
150
- const heading = CHECKLIST_PHASE.exec(line);
151
- if (heading) {
152
- phase = heading[1];
153
- continue;
154
- }
155
- const match = CHECKLIST_ROW.exec(line);
156
- if (!match) continue;
157
- rows.push({
158
- ...phase ? { phase } : {},
159
- number: Number(match[2]),
160
- label: trimLabel(match[3]),
161
- checked: match[1].toLowerCase() === "x"
162
- });
163
- }
164
- return rows;
163
+ return enumerateChecklistCheckboxes(text).map((item) => ({
164
+ ...item.phase ? { phase: item.phase } : {},
165
+ number: item.stepNumber,
166
+ label: trimLabel(item.rawLabel),
167
+ checked: item.checked
168
+ }));
165
169
  }
166
170
  function trimLabel(text) {
167
- const plain = text.replace(/\*\*/gu, "").replace(/\s+/gu, " ").trim();
171
+ const plain = checklistStepName(text).replace(/\*\*/gu, "").replace(/^\d+\.\s*/u, "").replace(/\s+/gu, " ").trim();
168
172
  if (plain.length <= CHECKLIST_LABEL_LIMIT) return plain;
169
173
  return `${plain.slice(0, CHECKLIST_LABEL_LIMIT).trimEnd()}\u2026`;
170
174
  }
@@ -242,6 +246,5 @@ export {
242
246
  CHECKLIST_LABEL_LIMIT,
243
247
  TASK_SILENT_AFTER_MS,
244
248
  collectTaskView,
245
- findTaskDir,
246
- isTerminalStatus
249
+ findTaskDir
247
250
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.56.0",
3
+ "version": "0.58.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -20,11 +20,11 @@
20
20
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
21
21
  },
22
22
  "dependencies": {
23
- "@farmslot/agent-runtime": "0.10.0",
24
- "@farmslot/expo-recipe": "0.9.2",
23
+ "@farmslot/agent-runtime": "0.12.0",
24
+ "@farmslot/expo-recipe": "0.10.0",
25
25
  "@farmslot/handoff": "^0.3.1",
26
- "@farmslot/protocol": "0.28.0",
27
- "@farmslot/recipe-harness": "0.16.3",
26
+ "@farmslot/protocol": "0.29.0",
27
+ "@farmslot/recipe-harness": "0.17.0",
28
28
  "agent-device": "0.19.3",
29
29
  "commander": "^12.0.0",
30
30
  "es-module-lexer": "2.3.1",