@deeeed/metamask-harness 0.92.21 → 0.92.23
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/adapters/mobile/ui.js +51 -4
- 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/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/mm-harness-cli.js
CHANGED
|
@@ -9,22 +9,36 @@ import {
|
|
|
9
9
|
createHarnessCli,
|
|
10
10
|
handleActions,
|
|
11
11
|
handleCall,
|
|
12
|
+
handleChecklist,
|
|
13
|
+
handleDoctor,
|
|
14
|
+
handleExecutionTemplate,
|
|
12
15
|
handleHarness,
|
|
13
16
|
handleLast,
|
|
14
17
|
handleLaunch,
|
|
18
|
+
handleLogs,
|
|
19
|
+
handlePrepare,
|
|
20
|
+
handleRecipeQuality,
|
|
15
21
|
handleReload,
|
|
22
|
+
handleReview,
|
|
16
23
|
handleRun,
|
|
24
|
+
handleStatus,
|
|
17
25
|
handleStop,
|
|
26
|
+
handleTaskInit,
|
|
18
27
|
harnessHost,
|
|
19
28
|
optionValues,
|
|
20
29
|
parseArgs,
|
|
30
|
+
resolveAdapter,
|
|
21
31
|
targetPath
|
|
22
32
|
} from "@farmslot/recipe-cli/harness";
|
|
23
33
|
import { handleStopViewers } from "./commands/stop.js";
|
|
24
|
-
import {
|
|
34
|
+
import { cliDoctorOptions, configureProviderReadiness } from "./provider-doctor.js";
|
|
35
|
+
import { cliRecipeQualityOptions, cliReviewOptions, helperCommandOptions } from "./provider-helpers.js";
|
|
36
|
+
import { cliStatusOptions, prepareOptions, stopOptions } from "./provider-lifecycle.js";
|
|
37
|
+
import { createLogsOptions } from "./provider-logs.js";
|
|
25
38
|
import { adoptPluginAdapter, afterAdapterLoad, configuredLibrarySources } from "./adapters/plugin-surface.js";
|
|
26
39
|
import { withMainnetOrderAbort } from "./adapters/perps-mainnet-cleanup.js";
|
|
27
40
|
import { adapterRegistry } from "./adapters/registry.js";
|
|
41
|
+
import { getAdapterSurface } from "./adapters/surface.js";
|
|
28
42
|
import {
|
|
29
43
|
explainMissingAdapterLibrary,
|
|
30
44
|
HIDDEN_COMMANDS,
|
|
@@ -34,32 +48,22 @@ import {
|
|
|
34
48
|
withAdapterList
|
|
35
49
|
} from "./command-contract.js";
|
|
36
50
|
import { handleCheck } from "./commands/check.js";
|
|
37
|
-
import { handleChecklist } from "./commands/checklist.js";
|
|
38
51
|
import { handleCompletionCandidates, invalidateCompletionCache } from "./commands/completion-candidates.js";
|
|
39
52
|
import { handleConfig } from "./commands/config.js";
|
|
40
53
|
import { handleDebug } from "./commands/debug.js";
|
|
41
54
|
import { applyDeviceTargeting } from "./commands/device-target.js";
|
|
42
|
-
import { handleDoctor } from "./commands/doctor.js";
|
|
43
|
-
import { handleDomain } from "./commands/domain.js";
|
|
44
55
|
import { handleEnsureReady } from "./commands/ensure-ready.js";
|
|
45
|
-
import { handleExecutionTemplate } from "./commands/execution-template.js";
|
|
46
56
|
import { handleFixtures } from "./commands/fixtures.js";
|
|
47
57
|
import { handleHelp } from "./commands/help.js";
|
|
48
|
-
import { handleLogs } from "./commands/logs.js";
|
|
49
58
|
import { overlayCommandOptions } from "./commands/overlay.js";
|
|
50
59
|
import { handlePin } from "./commands/pin.js";
|
|
51
60
|
import { handlePrBody } from "./commands/pr-body.js";
|
|
52
|
-
import { handlePrepare } from "./commands/prepare.js";
|
|
53
61
|
import { handleProvision } from "./commands/provision.js";
|
|
54
|
-
import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
55
62
|
import { handleResolveExtension } from "./commands/resolve-extension.js";
|
|
56
|
-
import { handleReview } from "./commands/review.js";
|
|
57
63
|
import { handleRuntimeDecision } from "./commands/runtime-decision.js";
|
|
58
64
|
import { handleRuntimeLaunch } from "./commands/runtime-launch.js";
|
|
59
65
|
import { handleSelfTest } from "./commands/self-test.js";
|
|
60
66
|
import { handleSetupBase } from "./commands/setup-base.js";
|
|
61
|
-
import { handleStatus } from "./commands/status.js";
|
|
62
|
-
import { handleTaskInit } from "./commands/task-init.js";
|
|
63
67
|
import { handleTutorial } from "./commands/tutorial.js";
|
|
64
68
|
import { handleUpdate, maybeNudge } from "./commands/update.js";
|
|
65
69
|
import { metaMaskEngine } from "./engine.js";
|
|
@@ -114,6 +118,20 @@ function overlay(command) {
|
|
|
114
118
|
return code;
|
|
115
119
|
};
|
|
116
120
|
}
|
|
121
|
+
const LOGS_OPTIONS = PUBLIC_COMMANDS.find((command) => command.name === "logs").contract.options;
|
|
122
|
+
function logsOptions(options) {
|
|
123
|
+
const surface = () => createLogsOptions(getAdapterSurface(resolveAdapter(options).adapter));
|
|
124
|
+
return {
|
|
125
|
+
compact: (request) => surface().compact(request),
|
|
126
|
+
window: (request) => surface().window(request),
|
|
127
|
+
announce: ({ source, logFile }) => process.stderr.write(
|
|
128
|
+
`${source} log: ${logFile}
|
|
129
|
+
tail it yourself: tail -f ${logFile} \xB7 page it: less +F ${logFile}
|
|
130
|
+
following below (Ctrl-C to stop) \u2026
|
|
131
|
+
`
|
|
132
|
+
)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
117
135
|
function invocationLibraries() {
|
|
118
136
|
return optionValues(process.argv.slice(2), "--library");
|
|
119
137
|
}
|
|
@@ -137,16 +155,16 @@ function withManifestEnvelope(command, run) {
|
|
|
137
155
|
}
|
|
138
156
|
const PUBLIC_RUN = {
|
|
139
157
|
help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
|
|
140
|
-
review: { run: handleReview },
|
|
141
|
-
domain: { run:
|
|
158
|
+
review: { run: (args) => handleReview(args, cliReviewOptions) },
|
|
159
|
+
domain: { run: (args) => handleReview(["domain", ...args], cliReviewOptions) },
|
|
142
160
|
"pr-body": { run: handlePrBody },
|
|
143
161
|
config: { run: handleConfig },
|
|
144
162
|
tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
|
|
145
163
|
"setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
|
|
146
|
-
status: { run: (args) => handleStatus(parseArgs(args)) },
|
|
147
|
-
checklist: { run: handleChecklist },
|
|
148
|
-
prepare: { run: handlePrepare },
|
|
149
|
-
task: { run: handleTaskInit },
|
|
164
|
+
status: { run: (args) => handleStatus(parseArgs(args), cliStatusOptions) },
|
|
165
|
+
checklist: { run: (args) => handleChecklist(args, helperCommandOptions.task) },
|
|
166
|
+
prepare: { run: (args) => handlePrepare(args, prepareOptions) },
|
|
167
|
+
task: { run: (args) => handleTaskInit(args, { surface: "skill" }) },
|
|
150
168
|
"execution-template": { run: handleExecutionTemplate },
|
|
151
169
|
actions: { run: (args) => handleActions(parseArgs(args), { catalog: metaMaskEngine }) },
|
|
152
170
|
stop: { run: (args) => handleStop(args, stopOptions) },
|
|
@@ -155,16 +173,28 @@ const PUBLIC_RUN = {
|
|
|
155
173
|
call: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleCall(args, callOptions)) },
|
|
156
174
|
run: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleRun(args, runOptions)) },
|
|
157
175
|
last: { run: (args) => handleLast(parseArgs(args)) },
|
|
158
|
-
doctor: {
|
|
176
|
+
doctor: {
|
|
177
|
+
run: (args) => {
|
|
178
|
+
const parsed = parseArgs(args);
|
|
179
|
+
const adapter = contextAdapter(targetPath(parsed.options));
|
|
180
|
+
if (adapter && adapterRegistry.has(adapter)) configureProviderReadiness(getAdapterSurface(adapter));
|
|
181
|
+
return handleDoctor(parsed, cliDoctorOptions);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
159
184
|
check: { run: handleCheck },
|
|
160
|
-
"recipe-quality": { run: handleRecipeQuality },
|
|
185
|
+
"recipe-quality": { run: (args) => handleRecipeQuality(args, cliRecipeQualityOptions) },
|
|
161
186
|
provision: { run: (args) => handleProvision(parseArgs(args)) },
|
|
162
187
|
install: { run: overlay("install") },
|
|
163
188
|
verify: { run: overlay("verify") },
|
|
164
189
|
cleanup: { run: overlay("cleanup") },
|
|
165
190
|
launch: { run: handleLaunch },
|
|
166
191
|
"runtime-launch": { run: (args) => handleRuntimeLaunch(parseArgs(args)) },
|
|
167
|
-
logs: {
|
|
192
|
+
logs: {
|
|
193
|
+
run: (args) => {
|
|
194
|
+
const parsed = parseArgs(args, { optionSpecs: LOGS_OPTIONS });
|
|
195
|
+
return handleLogs(parsed, logsOptions(parsed.options));
|
|
196
|
+
}
|
|
197
|
+
},
|
|
168
198
|
debug: { run: handleDebug },
|
|
169
199
|
reload: { run: handleReload },
|
|
170
200
|
update: { run: handleUpdate, exit: "now", nudge: false },
|
package/dist/paths.js
CHANGED
|
@@ -4,7 +4,6 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
4
4
|
import { recipeRuntimeDir, recipeRuntimePath } from "@farmslot/recipe-cli/harness";
|
|
5
5
|
import { adapterRegistry } from "./adapters/registry.js";
|
|
6
6
|
const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
-
const PREPARE_PROGRESS_ARTIFACT = "prepare/progress.json";
|
|
8
7
|
function walletFixturePath(projectRoot) {
|
|
9
8
|
return recipeRuntimePath(projectRoot, "wallet-fixture.json");
|
|
10
9
|
}
|
|
@@ -157,7 +156,6 @@ function isMissingPackageError(error, packageName) {
|
|
|
157
156
|
}
|
|
158
157
|
export {
|
|
159
158
|
LIBRARY_ACTION_NAMESPACE,
|
|
160
|
-
PREPARE_PROGRESS_ARTIFACT,
|
|
161
159
|
assertAdapter,
|
|
162
160
|
extensionIdPath,
|
|
163
161
|
harnessSlotPoolDir,
|
package/dist/provider-doctor.js
CHANGED
|
@@ -3,7 +3,7 @@ import { farmslotReadyIndicator } from "./commands/farmslot-ready.js";
|
|
|
3
3
|
import { updateStatus } from "./commands/update.js";
|
|
4
4
|
import { doctorReportOptions, fixtureSummary } from "./doctor.js";
|
|
5
5
|
import { loadActionManifest, validateManifest } from "./manifest.js";
|
|
6
|
-
import { statusOptions } from "./provider-lifecycle.js";
|
|
6
|
+
import { cliDevicePreview, statusOptions } from "./provider-lifecycle.js";
|
|
7
7
|
import { ensureRuntimeContext } from "./runtime-context.js";
|
|
8
8
|
const doctorOptions = {
|
|
9
9
|
...statusOptions,
|
|
@@ -25,12 +25,14 @@ const doctorOptions = {
|
|
|
25
25
|
userAction: (failed, retry) => failed.includes("wallet-fixture") ? `choose one wallet fixture source from nextActions, run it, then retry: ${retry}` : void 0
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
+
const cliDoctorOptions = { ...doctorOptions, previewDevice: cliDevicePreview };
|
|
28
29
|
function configureProviderReadiness(runtime) {
|
|
29
30
|
Object.assign(runtime.readiness, {
|
|
30
31
|
readyIndicator: (devices) => farmslotReadyIndicator(runtime.id, [...devices])
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
export {
|
|
35
|
+
cliDoctorOptions,
|
|
34
36
|
configureProviderReadiness,
|
|
35
37
|
doctorOptions
|
|
36
38
|
};
|
package/dist/provider-helpers.js
CHANGED
|
@@ -1,14 +1,118 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
usageError
|
|
7
|
+
} from "@farmslot/recipe-cli/harness";
|
|
8
|
+
import { adapterFromOptions, harnessVersion } from "./commands/review.js";
|
|
2
9
|
import {
|
|
3
10
|
BASE_REVIEW_STEPS,
|
|
4
11
|
REVIEW_ADAPTERS,
|
|
12
|
+
VERDICT_STEP,
|
|
5
13
|
configPath,
|
|
14
|
+
discoverLibraries,
|
|
6
15
|
parityAdapter,
|
|
7
16
|
parityReviewSteps,
|
|
8
17
|
readConfig,
|
|
9
18
|
referenceHint,
|
|
19
|
+
resolveLibrary,
|
|
10
20
|
resolveReference
|
|
11
21
|
} from "./review/knowledge.js";
|
|
22
|
+
const require2 = createRequire(import.meta.url);
|
|
23
|
+
function handoffCommand() {
|
|
24
|
+
if (process.env.HANDOFF_BIN) {
|
|
25
|
+
try {
|
|
26
|
+
fs.accessSync(process.env.HANDOFF_BIN, fs.constants.X_OK);
|
|
27
|
+
return { command: process.env.HANDOFF_BIN, prefix: [] };
|
|
28
|
+
} catch {
|
|
29
|
+
return { command: process.execPath, prefix: [process.env.HANDOFF_BIN] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
command: process.execPath,
|
|
34
|
+
prefix: [require2.resolve("@farmslot/handoff/cli")]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function closeoutArgs(taskDir, args) {
|
|
38
|
+
const normalized = args.flatMap((arg) => {
|
|
39
|
+
const option = ["--config", "--destination", "--metadata"].find(
|
|
40
|
+
(name) => arg.startsWith(`${name}=`)
|
|
41
|
+
);
|
|
42
|
+
return option ? [option, arg.slice(option.length + 1)] : [arg];
|
|
43
|
+
});
|
|
44
|
+
if (process.env.METAMASK_AGENTIC_LEARNINGS_DIR && !normalized.includes("--destination")) {
|
|
45
|
+
return [
|
|
46
|
+
"closeout",
|
|
47
|
+
taskDir,
|
|
48
|
+
...normalized,
|
|
49
|
+
"--destination",
|
|
50
|
+
process.env.METAMASK_AGENTIC_LEARNINGS_DIR
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
return ["closeout", taskDir, ...normalized];
|
|
54
|
+
}
|
|
55
|
+
function runCloseout(taskDir, args, stdio) {
|
|
56
|
+
try {
|
|
57
|
+
const { command, prefix } = handoffCommand();
|
|
58
|
+
return spawnSync(command, [...prefix, ...closeoutArgs(taskDir, args)], {
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
stdio
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
return {
|
|
65
|
+
status: 1,
|
|
66
|
+
stdout: "",
|
|
67
|
+
stderr: message,
|
|
68
|
+
error: new Error(message)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function checkDiffSummaryReady(taskDir, step) {
|
|
73
|
+
const summaryPath = path.join(
|
|
74
|
+
taskDir,
|
|
75
|
+
"artifacts",
|
|
76
|
+
"validation-summary.json"
|
|
77
|
+
);
|
|
78
|
+
try {
|
|
79
|
+
const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
|
|
80
|
+
if (summary.command === "check" && summary.action === "diff" && summary.status === "pass") {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
console.error(
|
|
86
|
+
`check-diff gate ${step} requires a task-local PASS at ${summaryPath}`
|
|
87
|
+
);
|
|
88
|
+
console.error(
|
|
89
|
+
`Run: mm-harness check diff [--fix] --artifacts-dir ${path.dirname(summaryPath)}`
|
|
90
|
+
);
|
|
91
|
+
console.error("Then rerun the same checklist mark command.");
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function teachRecipeCompletion(taskDir, step) {
|
|
95
|
+
if (step !== "complete") return;
|
|
96
|
+
const artifactsDir = path.join(taskDir, "artifacts");
|
|
97
|
+
if (!fs.existsSync(path.join(artifactsDir, "recipe.json"))) return;
|
|
98
|
+
const qualityPath = path.join(artifactsDir, "recipe-quality.json");
|
|
99
|
+
const coveragePath = path.join(artifactsDir, "recipe-coverage.md");
|
|
100
|
+
const missingQuality = !fs.existsSync(qualityPath);
|
|
101
|
+
const missingCoverage = !fs.existsSync(coveragePath);
|
|
102
|
+
if (!missingQuality && !missingCoverage) return;
|
|
103
|
+
console.error("Recipe completion needs two closure artifacts; derive them from the real recipe run and evidence (do not stub them):");
|
|
104
|
+
if (missingCoverage) {
|
|
105
|
+
console.error(` - ${coveragePath}: map each proof target to its recipe node and evidence, or record a reasoned N/A.`);
|
|
106
|
+
}
|
|
107
|
+
if (missingQuality) {
|
|
108
|
+
const compactPath = path.join(artifactsDir, "recipe-quality-input.json");
|
|
109
|
+
console.error(" First run: mm-harness help");
|
|
110
|
+
console.error(` - ${qualityPath}: write ${compactPath} as:`);
|
|
111
|
+
console.error(' {"verdict":"pass|warn|fail","reasons":["evidence-backed reason"]}');
|
|
112
|
+
console.error(` then run: mm-harness recipe-quality build --input ${compactPath} --output ${qualityPath}`);
|
|
113
|
+
}
|
|
114
|
+
console.error("Then rerun the same checklist complete command.");
|
|
115
|
+
}
|
|
12
116
|
const task = {
|
|
13
117
|
stepGates: [{ label: /mm-harness check diff/u, ready: checkDiffSummaryReady }],
|
|
14
118
|
closeout: runCloseout,
|
|
@@ -30,6 +134,32 @@ const review = {
|
|
|
30
134
|
};
|
|
31
135
|
}
|
|
32
136
|
};
|
|
137
|
+
const cliReviewOptions = {
|
|
138
|
+
...review,
|
|
139
|
+
usage: "Usage: mm-harness review checklist [--domain <name>] [--since <sha>] [--base <ref>] [--out <file>] [--json]",
|
|
140
|
+
domainCommand: "mm-harness domain",
|
|
141
|
+
defaultDomain: { read: () => process.env.MM_HARNESS_DOMAIN || void 0, label: "MM_HARNESS_DOMAIN" },
|
|
142
|
+
runtime: ({ action, domain }) => {
|
|
143
|
+
const adapter = adapterFromOptions();
|
|
144
|
+
if (action === "domain" && !domain && adapter === "unscoped") {
|
|
145
|
+
throw usageError(`No MetaMask checkout detected: pass --adapter <${REVIEW_ADAPTERS.join("|")}> or --domain <name>.`);
|
|
146
|
+
}
|
|
147
|
+
return adapter;
|
|
148
|
+
},
|
|
149
|
+
libraries: ({ target }) => discoverLibraries({ target, config: readConfig(configPath()) }).map((library) => ({
|
|
150
|
+
name: library.name,
|
|
151
|
+
root: library.root,
|
|
152
|
+
source: library.source,
|
|
153
|
+
revision: library.revision ?? null
|
|
154
|
+
})),
|
|
155
|
+
resolveLibrary: ({ domain, target }) => resolveLibrary(domain, { target, config: readConfig(configPath()), fetch: true }),
|
|
156
|
+
header: ({ target, library, reference, referenceMissing }) => [
|
|
157
|
+
`Materialized by mm-harness ${harnessVersion()} for ${target}.`,
|
|
158
|
+
...library ? [`Domain knowledge: ${library.name}${library.revision ? ` @ ${library.revision}` : ""} (${library.root}, via ${library.source}).`] : [],
|
|
159
|
+
...reference ? [`Parity reference: ${reference.adapter} checkout at ${reference.root} (via ${reference.source}).`] : referenceMissing ? [`Parity reference: not checked \u2014 ${referenceMissing}.`] : []
|
|
160
|
+
],
|
|
161
|
+
verdictRule: VERDICT_STEP
|
|
162
|
+
};
|
|
33
163
|
async function recipeAdvice(options, mode) {
|
|
34
164
|
const { handleRecipeAdvice } = await import("./commands/recipe-advice.js");
|
|
35
165
|
return handleRecipeAdvice(options, mode);
|
|
@@ -48,6 +178,18 @@ const recipeQuality = {
|
|
|
48
178
|
missingAction: "Use build, plan, advise or advisor-status."
|
|
49
179
|
};
|
|
50
180
|
const helperCommandOptions = { task, review, recipeQuality };
|
|
181
|
+
const cliRecipeQualityOptions = {
|
|
182
|
+
subcommands: recipeQuality.subcommands,
|
|
183
|
+
subcommandOptions: {
|
|
184
|
+
names: recipeQuality.subcommandOptions.names,
|
|
185
|
+
message: "advice options require the advise subcommand",
|
|
186
|
+
userAction: "mm-harness recipe-quality advise --recipe <recipe.json> --task <TASK.md> --adapter <adapter> --advisor <provider>"
|
|
187
|
+
},
|
|
188
|
+
missingAction: "missing action: use build, plan, or advise",
|
|
189
|
+
usageNote: "apply the quality bar from mm-harness help, then build its compact verdict here."
|
|
190
|
+
};
|
|
51
191
|
export {
|
|
192
|
+
cliRecipeQualityOptions,
|
|
193
|
+
cliReviewOptions,
|
|
52
194
|
helperCommandOptions
|
|
53
195
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
usageError
|
|
3
|
+
} from "@farmslot/recipe-cli/harness";
|
|
1
4
|
import { stopProjectCollector } from "../library/actions/shared/analytics/collector.mjs";
|
|
2
5
|
import { collectCheckoutView, renderCheckoutView } from "./checkout-view.js";
|
|
3
6
|
import { applyDeviceTargeting } from "./commands/device-target.js";
|
|
4
|
-
import {
|
|
7
|
+
import { doctorReportOptions } from "./doctor.js";
|
|
8
|
+
import { featureFlagReport, FeatureFlagSpecError, parseRemoteFlagSpec } from "./feature-flags.js";
|
|
5
9
|
const statusOptions = {
|
|
6
10
|
checkoutView(target, adapter) {
|
|
7
11
|
const view = collectCheckoutView(target, adapter);
|
|
@@ -14,6 +18,41 @@ const statusOptions = {
|
|
|
14
18
|
persistPin: false
|
|
15
19
|
})
|
|
16
20
|
};
|
|
21
|
+
const cliDevicePreview = (command, adapter, options) => applyDeviceTargeting(command, adapter, options, { gate: false, rerun: "" });
|
|
22
|
+
const cliStatusOptions = { ...statusOptions, previewDevice: cliDevicePreview, journal: false };
|
|
23
|
+
const prepareOptions = {
|
|
24
|
+
usage: "mm-harness prepare [--target <checkout>] [--platform mobile|extension|core] [--mobile-platform ios|android] [--clear-metro] [--artifacts-dir <dir>] [--json]",
|
|
25
|
+
launchHint: "can take minutes on Mobile: Metro bundle and app start",
|
|
26
|
+
steps: [{
|
|
27
|
+
id: "fixtures",
|
|
28
|
+
command: "fixtures set",
|
|
29
|
+
hint: "under a minute",
|
|
30
|
+
argv: ({ device, forwarded, targetArgs }) => [
|
|
31
|
+
"fixtures",
|
|
32
|
+
"set",
|
|
33
|
+
...device.flatMap((platform) => ["--platform", platform]),
|
|
34
|
+
...forwarded,
|
|
35
|
+
"--json",
|
|
36
|
+
...targetArgs
|
|
37
|
+
]
|
|
38
|
+
}],
|
|
39
|
+
deviceTarget: { option: "mobilePlatform", flag: "--mobile-platform", choices: ["ios", "android"] },
|
|
40
|
+
clearMetroOnly: "mobile only",
|
|
41
|
+
forward: {
|
|
42
|
+
option: "remoteFlag",
|
|
43
|
+
flag: "--remote-flag",
|
|
44
|
+
validate(value) {
|
|
45
|
+
try {
|
|
46
|
+
parseRemoteFlagSpec(value);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (!(error instanceof FeatureFlagSpecError)) throw error;
|
|
49
|
+
throw usageError(`${error.message}
|
|
50
|
+
Next: ${error.userAction}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
provenance: doctorReportOptions.provenance
|
|
55
|
+
};
|
|
17
56
|
const stopOptions = {
|
|
18
57
|
companions: async (target) => {
|
|
19
58
|
const collector = await stopProjectCollector(target);
|
|
@@ -21,6 +60,9 @@ const stopOptions = {
|
|
|
21
60
|
}
|
|
22
61
|
};
|
|
23
62
|
export {
|
|
63
|
+
cliDevicePreview,
|
|
64
|
+
cliStatusOptions,
|
|
65
|
+
prepareOptions,
|
|
24
66
|
statusOptions,
|
|
25
67
|
stopOptions
|
|
26
68
|
};
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.23",
|
|
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",
|