@deeeed/metamask-harness 0.92.22 → 0.92.24
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 +43 -0
- package/dist/adapters/core/surface.js +2 -3
- package/dist/adapters/extension/surface.js +1 -2
- package/dist/adapters/mobile/devices.js +0 -1
- package/dist/adapters/mobile/overlay.js +3 -2
- package/dist/adapters/mobile/prepare-target.js +13 -0
- package/dist/adapters/mobile/surface.js +1 -2
- package/dist/commands/doctor.js +3 -423
- package/dist/commands/review.js +5 -66
- package/dist/doctor.js +0 -11
- package/dist/mm-harness-cli.js +50 -20
- package/dist/paths.js +0 -2
- package/dist/provider-doctor.js +3 -1
- package/dist/provider-helpers.js +143 -1
- package/dist/provider-lifecycle.js +43 -1
- package/dist/review/knowledge.js +0 -119
- package/docs/CONTRIBUTING.md +3 -2
- package/library/actions/mobile/perps/perps.mjs +33 -3
- package/package.json +3 -3
- package/dist/commands/checklist.js +0 -242
- package/dist/commands/domain.js +0 -57
- package/dist/commands/execution-template.js +0 -28
- package/dist/commands/logs.js +0 -99
- package/dist/commands/prepare.js +0 -433
- package/dist/commands/recipe-quality.js +0 -103
- package/dist/commands/status-watch.js +0 -317
- package/dist/commands/status.js +0 -146
- package/dist/commands/task-init.js +0 -49
- package/dist/task-view.js +0 -332
package/dist/review/knowledge.js
CHANGED
|
@@ -274,74 +274,6 @@ function loadOwnedPaths(root) {
|
|
|
274
274
|
}
|
|
275
275
|
return { domain: record.domain, repos };
|
|
276
276
|
}
|
|
277
|
-
function globToRegExp(glob) {
|
|
278
|
-
let out = "^";
|
|
279
|
-
for (let i = 0; i < glob.length; i += 1) {
|
|
280
|
-
const char = glob[i];
|
|
281
|
-
if (char === "*") {
|
|
282
|
-
if (glob[i + 1] === "*") {
|
|
283
|
-
const slashAfter = glob[i + 2] === "/";
|
|
284
|
-
out += slashAfter ? "(?:.*/)?" : ".*";
|
|
285
|
-
i += slashAfter ? 2 : 1;
|
|
286
|
-
} else {
|
|
287
|
-
out += "[^/]*";
|
|
288
|
-
}
|
|
289
|
-
} else if (char === "?") {
|
|
290
|
-
out += "[^/]";
|
|
291
|
-
} else if (char === "[") {
|
|
292
|
-
const negated = glob[i + 1] === "!";
|
|
293
|
-
const bodyStart = i + 1 + (negated ? 1 : 0);
|
|
294
|
-
const close = glob.indexOf("]", glob[bodyStart] === "]" ? bodyStart + 1 : bodyStart);
|
|
295
|
-
if (close === -1) {
|
|
296
|
-
out += "\\[";
|
|
297
|
-
} else {
|
|
298
|
-
out += `[${negated ? "^" : ""}${glob.slice(bodyStart, close).replace(/[\\\]]/g, "\\$&")}]`;
|
|
299
|
-
i = close;
|
|
300
|
-
}
|
|
301
|
-
} else {
|
|
302
|
-
out += char.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return new RegExp(`${out}$`);
|
|
306
|
-
}
|
|
307
|
-
function matchOwnedFiles(files, globs) {
|
|
308
|
-
const patterns = globs.map(globToRegExp);
|
|
309
|
-
return files.filter((file) => patterns.some((pattern) => pattern.test(file)));
|
|
310
|
-
}
|
|
311
|
-
function classifyDomain(files, adapter, libraries) {
|
|
312
|
-
const matches = [];
|
|
313
|
-
for (const library of libraries) {
|
|
314
|
-
const owned = loadOwnedPaths(library.root);
|
|
315
|
-
if (!owned) continue;
|
|
316
|
-
const matched = matchOwnedFiles(files, owned.repos[adapter] ?? []);
|
|
317
|
-
if (matched.length > 0) matches.push({ domain: owned.domain, library, matched });
|
|
318
|
-
}
|
|
319
|
-
return matches.sort((a, b) => b.matched.length - a.matched.length);
|
|
320
|
-
}
|
|
321
|
-
function git(target, args) {
|
|
322
|
-
return execFileSync("git", ["-C", target, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
323
|
-
}
|
|
324
|
-
function defaultBaseRef(target) {
|
|
325
|
-
for (const candidate of ["origin/main", "main", "origin/develop", "develop"]) {
|
|
326
|
-
try {
|
|
327
|
-
git(target, ["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]);
|
|
328
|
-
return candidate;
|
|
329
|
-
} catch {
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return "HEAD";
|
|
333
|
-
}
|
|
334
|
-
function changedFiles(target, base) {
|
|
335
|
-
const ref = base ?? defaultBaseRef(target);
|
|
336
|
-
const lists = [
|
|
337
|
-
ref === "HEAD" ? "" : git(target, ["diff", "--name-only", `${ref}...HEAD`]),
|
|
338
|
-
git(target, ["diff", "--name-only", "HEAD"]),
|
|
339
|
-
git(target, ["ls-files", "--others", "--exclude-standard"])
|
|
340
|
-
];
|
|
341
|
-
const files = /* @__PURE__ */ new Set();
|
|
342
|
-
for (const list of lists) for (const line of list.split("\n")) if (line.trim()) files.add(line.trim());
|
|
343
|
-
return { base: ref, files: [...files].sort() };
|
|
344
|
-
}
|
|
345
277
|
function parseSections(markdown) {
|
|
346
278
|
const lines = markdown.split("\n");
|
|
347
279
|
const introLines = [];
|
|
@@ -457,51 +389,6 @@ function parityReviewSteps(input) {
|
|
|
457
389
|
}
|
|
458
390
|
return ["Parity: core changes are consumed by both mobile and extension; confirm both consumers still compile against the public surface."];
|
|
459
391
|
}
|
|
460
|
-
function renderReviewChecklist(input) {
|
|
461
|
-
const diffRange = input.since ? `${input.since}..HEAD` : `${input.base}...HEAD`;
|
|
462
|
-
const lines = [];
|
|
463
|
-
const scope = input.domain ? `${input.domain} (${input.adapter})` : input.adapter;
|
|
464
|
-
lines.push(`# Review checklist \u2014 ${scope}`, "");
|
|
465
|
-
lines.push(`Materialized by mm-harness ${input.harnessVersion} for ${input.target}.`);
|
|
466
|
-
if (input.knowledge) {
|
|
467
|
-
const { library } = input.knowledge;
|
|
468
|
-
lines.push(`Domain knowledge: ${library.name}${library.revision ? ` @ ${library.revision}` : ""} (${library.root}, via ${library.source}).`);
|
|
469
|
-
}
|
|
470
|
-
if (input.reference) {
|
|
471
|
-
lines.push(`Parity reference: ${input.reference.adapter} checkout at ${input.reference.root} (via ${input.reference.source}).`);
|
|
472
|
-
} else if (input.referenceMissing) {
|
|
473
|
-
lines.push(`Parity reference: not checked \u2014 ${input.referenceMissing}.`);
|
|
474
|
-
}
|
|
475
|
-
lines.push(`Scope: \`git diff ${diffRange}\`${input.since ? " (incremental re-review since the last verdict)" : ""}.`, "");
|
|
476
|
-
let step = 0;
|
|
477
|
-
const item = (text) => {
|
|
478
|
-
step += 1;
|
|
479
|
-
lines.push(`- [ ] ${step}. ${text}`);
|
|
480
|
-
};
|
|
481
|
-
lines.push("## Setup");
|
|
482
|
-
if (input.since) {
|
|
483
|
-
item(`Re-read the previous findings, then list only the files changed in \`git diff --name-only ${diffRange}\`; confirm each earlier finding is addressed or explicitly declined.`);
|
|
484
|
-
} else {
|
|
485
|
-
item(`Read the task or PR description and the linked ticket, then list the changed files with \`git diff --name-only ${diffRange}\`.`);
|
|
486
|
-
}
|
|
487
|
-
item(
|
|
488
|
-
input.domain ? `Confirm the domain is ${input.domain} (\`mm-harness domain\`) and open ${input.knowledge?.antipatterns ? "review/antipatterns.md" : "the domain library"} once before reading the diff.` : "Run `mm-harness domain` to check whether a team library owns the changed files; if one does, re-run with --domain."
|
|
489
|
-
);
|
|
490
|
-
lines.push("", "## Base review");
|
|
491
|
-
for (const text of BASE_REVIEW_STEPS) item(text);
|
|
492
|
-
if (input.knowledge?.antipatterns) {
|
|
493
|
-
lines.push("", `## Domain patterns (${input.domain})`);
|
|
494
|
-
for (const section of input.knowledge.antipatterns.sections) {
|
|
495
|
-
item(`${section.title}: ${section.summary}`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
lines.push("", "## Parity");
|
|
499
|
-
for (const step2 of parityReviewSteps({ ...input, hasParityGuide: Boolean(input.knowledge?.parity) })) item(step2);
|
|
500
|
-
lines.push("", "## Verdict");
|
|
501
|
-
item(VERDICT_STEP);
|
|
502
|
-
return `${lines.join("\n")}
|
|
503
|
-
`;
|
|
504
|
-
}
|
|
505
392
|
function renderReviewHelp(input) {
|
|
506
393
|
const lines = [];
|
|
507
394
|
lines.push(`mm-harness ${input.harnessVersion} review guide${input.domain ? ` \u2014 ${input.domain}` : ""}`, "");
|
|
@@ -542,21 +429,16 @@ export {
|
|
|
542
429
|
REVIEW_ADAPTERS,
|
|
543
430
|
SKILLS_CACHE_DIR,
|
|
544
431
|
VERDICT_STEP,
|
|
545
|
-
changedFiles,
|
|
546
|
-
classifyDomain,
|
|
547
432
|
configPath,
|
|
548
433
|
declaredLibraryPathEnv,
|
|
549
|
-
defaultBaseRef,
|
|
550
434
|
discoverLibraries,
|
|
551
435
|
excludeCacheDir,
|
|
552
436
|
fetchLibrary,
|
|
553
437
|
gitExcludeFile,
|
|
554
|
-
globToRegExp,
|
|
555
438
|
hydrateLibraryPathEnv,
|
|
556
439
|
libraryCachePath,
|
|
557
440
|
loadDomainKnowledge,
|
|
558
441
|
loadOwnedPaths,
|
|
559
|
-
matchOwnedFiles,
|
|
560
442
|
parityAdapter,
|
|
561
443
|
parityReviewSteps,
|
|
562
444
|
parseAdvisorSettings,
|
|
@@ -565,7 +447,6 @@ export {
|
|
|
565
447
|
readConfig,
|
|
566
448
|
referenceEnvName,
|
|
567
449
|
referenceHint,
|
|
568
|
-
renderReviewChecklist,
|
|
569
450
|
renderReviewHelp,
|
|
570
451
|
resolveLibrary,
|
|
571
452
|
resolveReference,
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -88,8 +88,9 @@ command-local platform ladders for surface-owned behavior:
|
|
|
88
88
|
- the runner reads `actions` (manifest, bundled semantic actions, live adapter
|
|
89
89
|
scripts, `cdp.target`, the `ui.*` transport, adapter wrappers, input checks);
|
|
90
90
|
- `install`/`verify`/`cleanup` read `harness`, `agentic-runtime.json` reads
|
|
91
|
-
`runtimeContext`, `doctor`/`status` read `readiness`,
|
|
92
|
-
`prepare`, `debug`, `reload` and
|
|
91
|
+
`runtimeContext`, `doctor`/`status` read `readiness`, `prepare` reads
|
|
92
|
+
`readiness.prepare`, and `fixtures`, `debug`, `reload` and
|
|
93
|
+
`runtime-decision` read their own members.
|
|
93
94
|
|
|
94
95
|
A few comparisons remain where a command exists for one platform
|
|
95
96
|
(`resolve-extension`, `ensure-ready`, `runtime-launch`) or
|
|
@@ -428,7 +428,16 @@ async function readOpenOrders(input) {
|
|
|
428
428
|
);
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
const TWAP_NOT_ADVERTISED = 'provider-does-not-advertise-twap';
|
|
432
|
+
|
|
431
433
|
async function readStrategyOrders(input) {
|
|
434
|
+
// Where the provider does not advertise TWAP (Lighter on Mobile 8.17),
|
|
435
|
+
// MetaMask cannot place TWAP, so there is none to clean, and getTwapOrders
|
|
436
|
+
// throws. Support is per provider today, so the first selected market speaks
|
|
437
|
+
// for the read.
|
|
438
|
+
const market = configuredSymbols(input, [])[0] ?? null;
|
|
439
|
+
const provider = typeof input.node?.provider === 'string' ? input.node.provider.toLowerCase() : null;
|
|
440
|
+
const capabilityQuery = market ? { symbol: market, ...(provider ? { providerId: provider } : {}) } : null;
|
|
432
441
|
return retryPerpsClientRead(
|
|
433
442
|
async () => {
|
|
434
443
|
const orders = await evalAsync(
|
|
@@ -438,14 +447,27 @@ async function readStrategyOrders(input) {
|
|
|
438
447
|
if (!controller || typeof controller.getTwapOrders !== 'function' || typeof controller.getChaseOrders !== 'function') {
|
|
439
448
|
throw new Error('PerpsController strategy readers are unavailable; cannot clean live TWAP and Chase orders.');
|
|
440
449
|
}
|
|
450
|
+
var capabilityQuery = ${JSON.stringify(capabilityQuery)};
|
|
451
|
+
var notAdvertised = ${JSON.stringify(TWAP_NOT_ADVERTISED)};
|
|
452
|
+
var twap = capabilityQuery && typeof controller.getOrderCapabilities === 'function'
|
|
453
|
+
? Promise.resolve(controller.getOrderCapabilities(capabilityQuery)).then(function(capabilities){
|
|
454
|
+
var advertised = capabilities && capabilities.status === 'ready' && Array.isArray(capabilities.supportedStrategies)
|
|
455
|
+
? capabilities.supportedStrategies
|
|
456
|
+
: null;
|
|
457
|
+
return advertised && advertised.indexOf('twap') < 0 ? notAdvertised : controller.getTwapOrders();
|
|
458
|
+
})
|
|
459
|
+
: controller.getTwapOrders();
|
|
441
460
|
return Promise.all([
|
|
442
|
-
|
|
461
|
+
twap,
|
|
443
462
|
controller.getChaseOrders()
|
|
444
463
|
]).then(function(values){
|
|
445
|
-
|
|
464
|
+
var skipped = values[0] === notAdvertised;
|
|
465
|
+
if (!(skipped || Array.isArray(values[0])) || !Array.isArray(values[1])) {
|
|
446
466
|
throw new Error('PerpsController strategy readers returned a non-array result.');
|
|
447
467
|
}
|
|
448
|
-
return JSON.stringify(
|
|
468
|
+
return JSON.stringify(skipped
|
|
469
|
+
? { twap: [], twapUnavailable: notAdvertised, chase: values[1] }
|
|
470
|
+
: { twap: values[0], chase: values[1] });
|
|
449
471
|
});
|
|
450
472
|
})()`,
|
|
451
473
|
);
|
|
@@ -574,6 +596,7 @@ async function closeStrategyOrders(input) {
|
|
|
574
596
|
chaseCount: 0,
|
|
575
597
|
acceptedCount: 0,
|
|
576
598
|
alreadyAbsentCount: 0,
|
|
599
|
+
...twapEvidence(orders),
|
|
577
600
|
proofPath: 'mobile-perps-controller-cancel-strategies',
|
|
578
601
|
};
|
|
579
602
|
}
|
|
@@ -641,10 +664,17 @@ async function closeStrategyOrders(input) {
|
|
|
641
664
|
chaseCount: chaseOrders.length,
|
|
642
665
|
acceptedCount: result.filter((entry) => entry.success === true).length,
|
|
643
666
|
alreadyAbsentCount: result.filter(isAlreadyAbsentStrategyResult).length,
|
|
667
|
+
...twapEvidence(orders),
|
|
644
668
|
proofPath: 'mobile-perps-controller-cancel-strategies',
|
|
645
669
|
};
|
|
646
670
|
}
|
|
647
671
|
|
|
672
|
+
// A TWAP count of 0 must not read as "checked and absent" when TWAP was not
|
|
673
|
+
// readable at all.
|
|
674
|
+
function twapEvidence(orders) {
|
|
675
|
+
return orders.twapUnavailable ? { twap: { status: 'unavailable', reason: orders.twapUnavailable } } : {};
|
|
676
|
+
}
|
|
677
|
+
|
|
648
678
|
export function clearPerformanceCachesExpression() {
|
|
649
679
|
return `(function(){
|
|
650
680
|
var metroRequire = globalThis.__r;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"@farmslot/adapter-node": "0.1.34",
|
|
25
25
|
"@farmslot/adapter-rn": "0.22.23",
|
|
26
26
|
"@farmslot/adapter-sdk": "0.18.0",
|
|
27
|
-
"@farmslot/adapter-web": "0.10.
|
|
27
|
+
"@farmslot/adapter-web": "0.10.15",
|
|
28
28
|
"@farmslot/agent-runtime": "0.29.0",
|
|
29
29
|
"@farmslot/handoff": "^0.3.1",
|
|
30
30
|
"@farmslot/protocol": "0.54.0",
|
|
31
|
-
"@farmslot/recipe-cli": "0.23.
|
|
31
|
+
"@farmslot/recipe-cli": "0.23.1",
|
|
32
32
|
"@farmslot/recipe-runner": "0.39.0",
|
|
33
33
|
"agent-device": "0.19.3",
|
|
34
34
|
"commander": "^12.0.0",
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { EXIT } from "@farmslot/recipe-cli/harness";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { HANDOFF_INPUT } from "@farmslot/agent-runtime";
|
|
7
|
-
import { enumerateChecklistCheckboxes, isTerminalWorkerSignalStatus } from "@farmslot/protocol";
|
|
8
|
-
const require2 = createRequire(import.meta.url);
|
|
9
|
-
const MARK_SCRIPT = require2.resolve(
|
|
10
|
-
"@farmslot/agent-runtime/scripts/mark-checklist-step.cjs"
|
|
11
|
-
);
|
|
12
|
-
const { resolveChecklistPaths } = require2(
|
|
13
|
-
"@farmslot/agent-runtime/scripts/checklist-target.cjs"
|
|
14
|
-
);
|
|
15
|
-
const { readSubtaskIndex, SubtaskRefusal } = require2("@farmslot/agent-runtime/scripts/subtask-unit.cjs");
|
|
16
|
-
const USAGE = "mm-harness checklist <mark <task-dir> <step> | closeout <task-dir>> [options]";
|
|
17
|
-
const CHECKLIST_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
18
|
-
"--reason",
|
|
19
|
-
"--checklist",
|
|
20
|
-
"--signal",
|
|
21
|
-
"--config",
|
|
22
|
-
"--destination",
|
|
23
|
-
"--metadata"
|
|
24
|
-
]);
|
|
25
|
-
function parseChecklistArgv(argv) {
|
|
26
|
-
const positionals = [];
|
|
27
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
28
|
-
const arg = argv[index] ?? "";
|
|
29
|
-
if (arg.startsWith("-")) {
|
|
30
|
-
const option = arg.split("=", 1)[0] ?? arg;
|
|
31
|
-
if (CHECKLIST_VALUE_OPTIONS.has(option) && !arg.includes("=")) index += 1;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
positionals.push({ index, value: arg });
|
|
35
|
-
}
|
|
36
|
-
const consumed = new Set(positionals.slice(0, 2).map(({ index }) => index));
|
|
37
|
-
return {
|
|
38
|
-
action: positionals[0]?.value,
|
|
39
|
-
taskDir: positionals[1]?.value,
|
|
40
|
-
step: positionals[2]?.value,
|
|
41
|
-
forwarded: argv.filter((_, index) => !consumed.has(index))
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function handoffCommand() {
|
|
45
|
-
if (process.env.HANDOFF_BIN) {
|
|
46
|
-
try {
|
|
47
|
-
fs.accessSync(process.env.HANDOFF_BIN, fs.constants.X_OK);
|
|
48
|
-
return { command: process.env.HANDOFF_BIN, prefix: [] };
|
|
49
|
-
} catch {
|
|
50
|
-
return { command: process.execPath, prefix: [process.env.HANDOFF_BIN] };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
command: process.execPath,
|
|
55
|
-
prefix: [require2.resolve("@farmslot/handoff/cli")]
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function closeoutArgs(taskDir, args) {
|
|
59
|
-
const normalized = args.flatMap((arg) => {
|
|
60
|
-
const option = ["--config", "--destination", "--metadata"].find(
|
|
61
|
-
(name) => arg.startsWith(`${name}=`)
|
|
62
|
-
);
|
|
63
|
-
return option ? [option, arg.slice(option.length + 1)] : [arg];
|
|
64
|
-
});
|
|
65
|
-
if (process.env.METAMASK_AGENTIC_LEARNINGS_DIR && !normalized.includes("--destination")) {
|
|
66
|
-
return [
|
|
67
|
-
"closeout",
|
|
68
|
-
taskDir,
|
|
69
|
-
...normalized,
|
|
70
|
-
"--destination",
|
|
71
|
-
process.env.METAMASK_AGENTIC_LEARNINGS_DIR
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
return ["closeout", taskDir, ...normalized];
|
|
75
|
-
}
|
|
76
|
-
function runCloseout(taskDir, args, stdio) {
|
|
77
|
-
try {
|
|
78
|
-
const { command, prefix } = handoffCommand();
|
|
79
|
-
return spawnSync(command, [...prefix, ...closeoutArgs(taskDir, args)], {
|
|
80
|
-
encoding: "utf8",
|
|
81
|
-
stdio
|
|
82
|
-
});
|
|
83
|
-
} catch (error) {
|
|
84
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
-
return {
|
|
86
|
-
status: 1,
|
|
87
|
-
stdout: "",
|
|
88
|
-
stderr: message,
|
|
89
|
-
error: new Error(message)
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function stageLearningPackage(taskDir, step, markArgs) {
|
|
94
|
-
if (!["complete", "no-change"].includes(step ?? "") || markArgs.includes("--skip-learnings") || !fs.existsSync(path.join(taskDir, HANDOFF_INPUT))) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const result = runCloseout(taskDir, ["--json"], "pipe");
|
|
99
|
-
if (result.status === 0) return;
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
console.error(
|
|
103
|
-
`Warning: learning capture could not be staged; rerun: mm-harness checklist closeout ${taskDir}; task verdict is unchanged.`
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
function terminalSignalStatus(taskDir) {
|
|
107
|
-
const { signalPath } = resolveChecklistPaths(taskDir);
|
|
108
|
-
let signal;
|
|
109
|
-
try {
|
|
110
|
-
signal = JSON.parse(fs.readFileSync(signalPath, "utf8"));
|
|
111
|
-
} catch {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
return isTerminalWorkerSignalStatus(signal.status) ? signal.status ?? null : null;
|
|
115
|
-
}
|
|
116
|
-
function preserveTerminalSignal(taskDir, step) {
|
|
117
|
-
if (step !== "start" && numericMarkStep(step) === null) return false;
|
|
118
|
-
const status = terminalSignalStatus(taskDir);
|
|
119
|
-
if (!status) return false;
|
|
120
|
-
console.log(
|
|
121
|
-
`signal already terminal: status=${status}; ignoring non-terminal mark ${step}`
|
|
122
|
-
);
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
function numericMarkStep(step) {
|
|
126
|
-
const stepNumber = Number(step);
|
|
127
|
-
return Number.isInteger(stepNumber) && stepNumber >= 1 ? stepNumber : null;
|
|
128
|
-
}
|
|
129
|
-
function checklistStepRequiresDiffPass(checklist, stepNumber) {
|
|
130
|
-
const step = enumerateChecklistCheckboxes(checklist).find((item) => item.stepNumber === stepNumber);
|
|
131
|
-
return step !== void 0 && /mm-harness check diff/u.test(step.rawLabel);
|
|
132
|
-
}
|
|
133
|
-
function checkDiffGateReady(taskDir, step, parentChecklist) {
|
|
134
|
-
const stepNumber = numericMarkStep(step);
|
|
135
|
-
if (stepNumber === null) return true;
|
|
136
|
-
const taskPath = parentChecklist ? path.join(taskDir, parentChecklist) : resolveChecklistPaths(taskDir).taskPath;
|
|
137
|
-
let checklist = "";
|
|
138
|
-
try {
|
|
139
|
-
checklist = fs.readFileSync(taskPath, "utf8");
|
|
140
|
-
} catch {
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
if (!checklistStepRequiresDiffPass(checklist, stepNumber)) return true;
|
|
144
|
-
return checkDiffSummaryReady(taskDir, step);
|
|
145
|
-
}
|
|
146
|
-
function checkDiffSummaryReady(taskDir, step) {
|
|
147
|
-
const summaryPath = path.join(
|
|
148
|
-
taskDir,
|
|
149
|
-
"artifacts",
|
|
150
|
-
"validation-summary.json"
|
|
151
|
-
);
|
|
152
|
-
try {
|
|
153
|
-
const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
|
|
154
|
-
if (summary.command === "check" && summary.action === "diff" && summary.status === "pass") {
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
}
|
|
159
|
-
console.error(
|
|
160
|
-
`check-diff gate ${step} requires a task-local PASS at ${summaryPath}`
|
|
161
|
-
);
|
|
162
|
-
console.error(
|
|
163
|
-
`Run: mm-harness check diff [--fix] --artifacts-dir ${path.dirname(summaryPath)}`
|
|
164
|
-
);
|
|
165
|
-
console.error("Then rerun the same checklist mark command.");
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
function teachRecipeCompletion(taskDir, step) {
|
|
169
|
-
if (step !== "complete") return;
|
|
170
|
-
const artifactsDir = path.join(taskDir, "artifacts");
|
|
171
|
-
if (!fs.existsSync(path.join(artifactsDir, "recipe.json"))) return;
|
|
172
|
-
const qualityPath = path.join(artifactsDir, "recipe-quality.json");
|
|
173
|
-
const coveragePath = path.join(artifactsDir, "recipe-coverage.md");
|
|
174
|
-
const missingQuality = !fs.existsSync(qualityPath);
|
|
175
|
-
const missingCoverage = !fs.existsSync(coveragePath);
|
|
176
|
-
if (!missingQuality && !missingCoverage) return;
|
|
177
|
-
console.error("Recipe completion needs two closure artifacts; derive them from the real recipe run and evidence (do not stub them):");
|
|
178
|
-
if (missingCoverage) {
|
|
179
|
-
console.error(` - ${coveragePath}: map each proof target to its recipe node and evidence, or record a reasoned N/A.`);
|
|
180
|
-
}
|
|
181
|
-
if (missingQuality) {
|
|
182
|
-
const compactPath = path.join(artifactsDir, "recipe-quality-input.json");
|
|
183
|
-
console.error(" First run: mm-harness help");
|
|
184
|
-
console.error(` - ${qualityPath}: write ${compactPath} as:`);
|
|
185
|
-
console.error(' {"verdict":"pass|warn|fail","reasons":["evidence-backed reason"]}');
|
|
186
|
-
console.error(` then run: mm-harness recipe-quality build --input ${compactPath} --output ${qualityPath}`);
|
|
187
|
-
}
|
|
188
|
-
console.error("Then rerun the same checklist complete command.");
|
|
189
|
-
}
|
|
190
|
-
async function handleChecklist(argv) {
|
|
191
|
-
const { action, taskDir, step, forwarded } = parseChecklistArgv(argv);
|
|
192
|
-
if (!taskDir) {
|
|
193
|
-
console.error(`usage: ${USAGE}`);
|
|
194
|
-
return EXIT.usage;
|
|
195
|
-
}
|
|
196
|
-
const resolvedTaskDir = path.resolve(taskDir);
|
|
197
|
-
if (action === "closeout") {
|
|
198
|
-
const result2 = runCloseout(resolvedTaskDir, forwarded, "inherit");
|
|
199
|
-
if (result2.error) {
|
|
200
|
-
console.error(`mm-harness checklist closeout: ${result2.error.message}`);
|
|
201
|
-
return EXIT.runtime;
|
|
202
|
-
}
|
|
203
|
-
return result2.status ?? EXIT.runtime;
|
|
204
|
-
}
|
|
205
|
-
if (action !== "mark" || !step) {
|
|
206
|
-
console.error(`usage: ${USAGE}`);
|
|
207
|
-
return EXIT.usage;
|
|
208
|
-
}
|
|
209
|
-
if (preserveTerminalSignal(resolvedTaskDir, step)) return EXIT.ok;
|
|
210
|
-
if (step === "sub" && forwarded[2] === "complete") {
|
|
211
|
-
try {
|
|
212
|
-
const unit = readSubtaskIndex(resolvedTaskDir)?.units.find((unit2) => unit2.id === forwarded[1]);
|
|
213
|
-
if (unit && !checkDiffGateReady(resolvedTaskDir, String(unit.parent.stepNumber), unit.parent.checklist)) return EXIT.runtime;
|
|
214
|
-
} catch (error) {
|
|
215
|
-
if (!(error instanceof SubtaskRefusal)) throw error;
|
|
216
|
-
console.error(error.message);
|
|
217
|
-
return error.code;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!checkDiffGateReady(resolvedTaskDir, step)) return EXIT.runtime;
|
|
221
|
-
teachRecipeCompletion(resolvedTaskDir, step);
|
|
222
|
-
const result = spawnSync(
|
|
223
|
-
process.execPath,
|
|
224
|
-
[MARK_SCRIPT, resolvedTaskDir, ...forwarded],
|
|
225
|
-
{ stdio: "inherit" }
|
|
226
|
-
);
|
|
227
|
-
if (result.error) {
|
|
228
|
-
console.error(`mm-harness checklist mark: ${result.error.message}`);
|
|
229
|
-
return EXIT.runtime;
|
|
230
|
-
}
|
|
231
|
-
const status = result.status ?? EXIT.runtime;
|
|
232
|
-
if (status === EXIT.ok) {
|
|
233
|
-
stageLearningPackage(resolvedTaskDir, step, forwarded);
|
|
234
|
-
}
|
|
235
|
-
return status;
|
|
236
|
-
}
|
|
237
|
-
export {
|
|
238
|
-
checkDiffSummaryReady,
|
|
239
|
-
handleChecklist,
|
|
240
|
-
runCloseout,
|
|
241
|
-
teachRecipeCompletion
|
|
242
|
-
};
|
package/dist/commands/domain.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { optionFlag, optionString, parseArgs, targetPath, usageError } from "@farmslot/recipe-cli/harness";
|
|
2
|
-
import {
|
|
3
|
-
changedFiles,
|
|
4
|
-
classifyDomain,
|
|
5
|
-
configPath,
|
|
6
|
-
discoverLibraries,
|
|
7
|
-
readConfig,
|
|
8
|
-
REVIEW_ADAPTERS
|
|
9
|
-
} from "../review/knowledge.js";
|
|
10
|
-
import { adapterFromOptions } from "./review.js";
|
|
11
|
-
async function handleDomain(argv) {
|
|
12
|
-
const parsed = parseArgs(argv);
|
|
13
|
-
const target = targetPath(parsed.options);
|
|
14
|
-
const json = optionFlag(parsed.options, "json");
|
|
15
|
-
const declared = optionString(parsed.options, "domain") ?? process.env.MM_HARNESS_DOMAIN;
|
|
16
|
-
const detected = adapterFromOptions();
|
|
17
|
-
const adapter = detected === "unscoped" ? void 0 : detected;
|
|
18
|
-
if (declared) {
|
|
19
|
-
emit(json, { domain: declared, source: "declared", adapter: adapter ?? null, target });
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
if (!adapter) {
|
|
23
|
-
throw usageError(`No MetaMask checkout detected: pass --adapter <${REVIEW_ADAPTERS.join("|")}> or --domain <name>.`);
|
|
24
|
-
}
|
|
25
|
-
const config = readConfig(configPath());
|
|
26
|
-
const libraries = discoverLibraries({ target, config });
|
|
27
|
-
const { base, files } = changedFiles(target, optionString(parsed.options, "base"));
|
|
28
|
-
const matches = classifyDomain(files, adapter, libraries);
|
|
29
|
-
const best = matches[0];
|
|
30
|
-
emit(json, {
|
|
31
|
-
domain: best?.domain ?? null,
|
|
32
|
-
source: best ? "owned-paths" : "none",
|
|
33
|
-
adapter,
|
|
34
|
-
target,
|
|
35
|
-
base,
|
|
36
|
-
changedFiles: files.length,
|
|
37
|
-
libraries: libraries.map((library) => ({ name: library.name, root: library.root, source: library.source, revision: library.revision ?? null })),
|
|
38
|
-
matches: matches.map((match) => ({ domain: match.domain, library: match.library.root, matched: match.matched }))
|
|
39
|
-
});
|
|
40
|
-
return 0;
|
|
41
|
-
}
|
|
42
|
-
function emit(json, payload) {
|
|
43
|
-
if (json) {
|
|
44
|
-
process.stdout.write(`${JSON.stringify({ command: "domain", ...payload }, null, 2)}
|
|
45
|
-
`);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
process.stdout.write(`${payload.domain ?? "none"}
|
|
49
|
-
`);
|
|
50
|
-
const best = payload.matches?.[0];
|
|
51
|
-
const detail = payload.source === "declared" ? "declared via --domain or MM_HARNESS_DOMAIN" : best ? `owned-paths match in ${best.library} (${best.matched.length} of ${payload.changedFiles} changed files)` : `no library owns the ${payload.changedFiles} changed files (base ${payload.base}; libraries scanned: ${payload.libraries?.length ?? 0})`;
|
|
52
|
-
process.stderr.write(`domain: ${detail}
|
|
53
|
-
`);
|
|
54
|
-
}
|
|
55
|
-
export {
|
|
56
|
-
handleDomain
|
|
57
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
const require2 = createRequire(import.meta.url);
|
|
4
|
-
async function handleExecutionTemplate(argv) {
|
|
5
|
-
let entrypoint;
|
|
6
|
-
try {
|
|
7
|
-
entrypoint = require2.resolve(
|
|
8
|
-
"@farmslot/agent-runtime/scripts/execution-template-cli.mjs"
|
|
9
|
-
);
|
|
10
|
-
} catch (error) {
|
|
11
|
-
console.error(
|
|
12
|
-
`mm-harness execution-template: installed @farmslot/agent-runtime does not expose the catalog command: ${error instanceof Error ? error.message : String(error)}`
|
|
13
|
-
);
|
|
14
|
-
return 1;
|
|
15
|
-
}
|
|
16
|
-
const result = spawnSync(process.execPath, [entrypoint, ...argv], {
|
|
17
|
-
env: process.env,
|
|
18
|
-
stdio: "inherit"
|
|
19
|
-
});
|
|
20
|
-
if (result.error) {
|
|
21
|
-
console.error(`mm-harness execution-template: ${result.error.message}`);
|
|
22
|
-
return 1;
|
|
23
|
-
}
|
|
24
|
-
return result.status ?? 1;
|
|
25
|
-
}
|
|
26
|
-
export {
|
|
27
|
-
handleExecutionTemplate
|
|
28
|
-
};
|