@deeeed/metamask-harness 0.92.17 → 0.92.19
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 +22 -2
- package/dist/cli-commands.js +0 -1
- package/dist/command-contract.js +0 -30
- package/dist/mm-harness-cli.js +1 -3
- package/docs/cli-contract.md +1 -1
- package/library/actions/mobile/wallet/import-password.mjs +17 -0
- package/library/actions/mobile/wallet/import.mjs +5 -4
- package/library/actions/mobile/wallet/native-ui.mjs +9 -4
- package/package.json +9 -9
- package/dist/commands/pr-body.js +0 -31
- package/dist/pr-body/render.js +0 -144
package/CHANGELOG.md
CHANGED
|
@@ -2,19 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.92.19 - 2026-10-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Mobile `metamask.wallet.import` confirms the 8.17 password warning sheet after Create password, and still accepts the password checkbox on earlier releases.
|
|
10
|
+
|
|
11
|
+
## 0.92.18 - 2026-10-16
|
|
12
|
+
|
|
5
13
|
### Changed
|
|
6
14
|
|
|
7
|
-
-
|
|
8
|
-
|
|
15
|
+
- Consume the published parent acceptance inheritance worker packages so hosted task tooling uses the matching Protocol 0.53.0 contracts.
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- Remove the retired `pr-body` route and duplicate renderer after callers moved to `farmslot recipe artifacts --format markdown`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Recognize shared host environment lookups in the feature inventory and record the retired private Extension recorder PID handshake.
|
|
9
24
|
|
|
10
25
|
## 0.92.17 - 2026-10-16
|
|
11
26
|
|
|
12
27
|
### Changed
|
|
13
28
|
|
|
14
29
|
- Consume the published pipeline-review contracts, native review results and aligned worker dependency set.
|
|
30
|
+
- Point task initialization, closeout and PR-body documentation at the shared Farmslot commands.
|
|
15
31
|
|
|
16
32
|
## 0.92.16 - 2026-10-16
|
|
17
33
|
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Refresh Terminal contract goldens for the shipped console-error window, capture-provider metadata and engine evidence inventories.
|
|
37
|
+
|
|
18
38
|
### Fixed
|
|
19
39
|
|
|
20
40
|
- Consume the published hosted-task runtime and aligned dependency set so task tooling retains owning workspaces, declared dependencies and hoisted peers.
|
package/dist/cli-commands.js
CHANGED
|
@@ -11,7 +11,6 @@ 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"] },
|
|
15
14
|
{ name: "config", desc: "Per-engineer locations and optional advisor settings", args: ["list", "path", "get", "set", "unset"], flags: ["--json"] },
|
|
16
15
|
{ name: "tutorial", desc: "Open the visual recipe tutorial", flags: ["--json", "--no-open"] },
|
|
17
16
|
{ name: "setup-base", desc: "Bootstrap numbered product checkouts", flags: ["--dir", "--counts", "--only", "--dry-run", "--force", "--json", "--show-config", "--reset-config", "--skip-harness-update"] },
|
package/dist/command-contract.js
CHANGED
|
@@ -175,36 +175,6 @@ 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
|
-
--command <text> Recipe invocation to show above the run log
|
|
194
|
-
--out <file> Write elsewhere than artifacts/pr-body.md
|
|
195
|
-
--json Machine-readable result
|
|
196
|
-
|
|
197
|
-
Example:
|
|
198
|
-
mm-harness pr-body render temp/tasks/dev/TAT-1 --json`,
|
|
199
|
-
contract: {
|
|
200
|
-
options: options(HELP, JSON, {
|
|
201
|
-
"--command": value(),
|
|
202
|
-
"--out": value()
|
|
203
|
-
}),
|
|
204
|
-
positionals: [{ label: "action", choices: ["render"] }, { label: "task-dir" }],
|
|
205
|
-
minimumPositionals: 2
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
178
|
{
|
|
209
179
|
name: "config",
|
|
210
180
|
summary: "Per-engineer locations and optional recipe advisor settings.",
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -48,7 +48,6 @@ 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";
|
|
52
51
|
import { handlePrepare } from "./commands/prepare.js";
|
|
53
52
|
import { handleProvision } from "./commands/provision.js";
|
|
54
53
|
import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
@@ -140,7 +139,6 @@ const PUBLIC_RUN = {
|
|
|
140
139
|
help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
|
|
141
140
|
review: { run: handleReview },
|
|
142
141
|
domain: { run: handleDomain },
|
|
143
|
-
"pr-body": { run: handlePrBody },
|
|
144
142
|
config: { run: handleConfig },
|
|
145
143
|
tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
|
|
146
144
|
"setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
|
|
@@ -200,7 +198,7 @@ const HELP_GROUPS = [
|
|
|
200
198
|
{
|
|
201
199
|
title: "PROVE",
|
|
202
200
|
blurb: "run recipes and inspect readiness",
|
|
203
|
-
commands: ["run", "last", "doctor", "check", "checklist", "review", "
|
|
201
|
+
commands: ["run", "last", "doctor", "check", "checklist", "review", "recipe-quality"]
|
|
204
202
|
},
|
|
205
203
|
{
|
|
206
204
|
title: "RUNTIME OVERLAY",
|
package/docs/cli-contract.md
CHANGED
|
@@ -62,7 +62,7 @@ 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
|
-
| `
|
|
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 |
|
|
66
66
|
| `review checklist --out <file> [--domain <d>] [--since <sha>]` | domain fixtures, review templates | the written file | `task/review-checklist` |
|
|
67
67
|
| `check diff …`, `recipe-quality build …` | templates | exit code | not covered: run repo lint/test tooling |
|
|
68
68
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Submit the Mobile recovery-phrase import password on both onboarding variants.
|
|
2
|
+
// Releases before 8.17 gate Create password behind a checkbox; 8.17 (mobile #37398) shows the
|
|
3
|
+
// same warning as a bottom sheet after Create password and imports only once it is confirmed.
|
|
4
|
+
const IMPORT_PASSWORD_TEST_IDS = {
|
|
5
|
+
passwordTerms: 'password-understand-box',
|
|
6
|
+
createPassword: 'submit-button',
|
|
7
|
+
passwordWarningConfirm: 'password-reset-warning-sheet-confirm-button',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// `driver.pressIfPresent(testId)` presses a visible control and resolves false when it is absent.
|
|
11
|
+
export async function submitImportPassword(driver, timeoutMs) {
|
|
12
|
+
const acceptedCheckbox = await driver.pressIfPresent(IMPORT_PASSWORD_TEST_IDS.passwordTerms);
|
|
13
|
+
await driver.press(IMPORT_PASSWORD_TEST_IDS.createPassword, timeoutMs);
|
|
14
|
+
if (!acceptedCheckbox) {
|
|
15
|
+
await driver.press(IMPORT_PASSWORD_TEST_IDS.passwordWarningConfirm, timeoutMs);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '../platform/bridge.mjs';
|
|
14
14
|
import { setMobileConsent } from '../analytics/consent-settings.mjs';
|
|
15
15
|
import { ensureUnlocked } from './ensure_unlocked.mjs';
|
|
16
|
+
import { submitImportPassword } from './import-password.mjs';
|
|
16
17
|
import { importWalletThroughNativeUi, isOpaqueMobileRuntime } from './native-ui.mjs';
|
|
17
18
|
|
|
18
19
|
const TEST_IDS = {
|
|
@@ -22,8 +23,6 @@ const TEST_IDS = {
|
|
|
22
23
|
continue: 'import-from-seed-screen-continue-button-id',
|
|
23
24
|
password: 'create-password-first-input-field',
|
|
24
25
|
confirmPassword: 'create-password-second-input-field',
|
|
25
|
-
passwordTerms: 'password-understand-box',
|
|
26
|
-
createPassword: 'submit-button',
|
|
27
26
|
metricsCheckbox: 'optin-metrics-metrics-checkbox',
|
|
28
27
|
metricsContinue: 'optin-metrics-continue-button-id',
|
|
29
28
|
interestSkip: 'onboarding-interest-skip-button',
|
|
@@ -173,8 +172,10 @@ async function importThroughUi(input, timeoutMs, options) {
|
|
|
173
172
|
await press(input, TEST_IDS.continue, timeoutMs);
|
|
174
173
|
await setSecretInput(input, TEST_IDS.password, credentials.password, timeoutMs);
|
|
175
174
|
await setSecretInput(input, TEST_IDS.confirmPassword, credentials.password, timeoutMs);
|
|
176
|
-
await
|
|
177
|
-
|
|
175
|
+
await submitImportPassword({
|
|
176
|
+
pressIfPresent: (testId) => pressIfPresent(input, testId),
|
|
177
|
+
press: (testId, ms) => press(input, testId, ms),
|
|
178
|
+
}, timeoutMs);
|
|
178
179
|
|
|
179
180
|
const postPassword = await waitForPostPasswordScreen(input, timeoutMs);
|
|
180
181
|
if (postPassword.route === 'OptinMetrics') {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
nativeAppId,
|
|
8
8
|
nativeSessionName,
|
|
9
9
|
} from '../platform/native-session-name.mjs';
|
|
10
|
+
import { submitImportPassword } from './import-password.mjs';
|
|
10
11
|
|
|
11
12
|
const TEST_IDS = {
|
|
12
13
|
existingWallet: 'wallet-setup-screen-have-an-existing-wallet-button-id',
|
|
@@ -15,8 +16,6 @@ const TEST_IDS = {
|
|
|
15
16
|
continue: 'import-from-seed-screen-continue-button-id',
|
|
16
17
|
password: 'create-password-first-input-field',
|
|
17
18
|
confirmPassword: 'create-password-second-input-field',
|
|
18
|
-
passwordTerms: 'password-understand-box',
|
|
19
|
-
createPassword: 'submit-button',
|
|
20
19
|
metricsCheckbox: 'optin-metrics-metrics-checkbox',
|
|
21
20
|
metricsContinue: 'optin-metrics-continue-button-id',
|
|
22
21
|
interestSkip: 'onboarding-interest-skip-button',
|
|
@@ -139,8 +138,14 @@ export async function importWalletThroughNativeUi(input, credentials, options) {
|
|
|
139
138
|
await native.fill(TEST_IDS.password, credentials.password, options.timeoutMs);
|
|
140
139
|
await native.fill(TEST_IDS.confirmPassword, credentials.password, options.timeoutMs);
|
|
141
140
|
await native.dismissKeyboard();
|
|
142
|
-
await
|
|
143
|
-
|
|
141
|
+
await submitImportPassword({
|
|
142
|
+
async pressIfPresent(testId) {
|
|
143
|
+
if (!await native.exists(testId, 3000)) return false;
|
|
144
|
+
await native.press(testId, options.timeoutMs);
|
|
145
|
+
return true;
|
|
146
|
+
},
|
|
147
|
+
press: (testId, timeoutMs) => native.press(testId, timeoutMs),
|
|
148
|
+
}, options.timeoutMs);
|
|
144
149
|
}
|
|
145
150
|
const onboarding = await native.completeOnboarding(options);
|
|
146
151
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.19",
|
|
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.
|
|
25
|
-
"@farmslot/adapter-rn": "0.22.
|
|
26
|
-
"@farmslot/adapter-sdk": "0.17.
|
|
27
|
-
"@farmslot/adapter-web": "0.10.
|
|
28
|
-
"@farmslot/agent-runtime": "0.
|
|
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",
|
|
29
29
|
"@farmslot/handoff": "^0.3.1",
|
|
30
|
-
"@farmslot/protocol": "0.
|
|
31
|
-
"@farmslot/recipe-cli": "0.22.
|
|
32
|
-
"@farmslot/recipe-runner": "0.38.
|
|
30
|
+
"@farmslot/protocol": "0.53.0",
|
|
31
|
+
"@farmslot/recipe-cli": "0.22.5",
|
|
32
|
+
"@farmslot/recipe-runner": "0.38.4",
|
|
33
33
|
"agent-device": "0.19.3",
|
|
34
34
|
"commander": "^12.0.0",
|
|
35
35
|
"esbuild": "0.28.1",
|
package/dist/commands/pr-body.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { optionFlag, optionString, parseArgs, usageError } from "@farmslot/recipe-cli/harness";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { BODY_FILE, renderPrBody } from "../pr-body/render.js";
|
|
5
|
-
const USAGE = "Usage: mm-harness pr-body render <task-dir> [--command <text>] [--out <file>] [--json]";
|
|
6
|
-
async function handlePrBody(argv) {
|
|
7
|
-
const parsed = parseArgs(argv);
|
|
8
|
-
const [action, taskArg] = parsed.positional;
|
|
9
|
-
if (action !== "render" || !taskArg) throw usageError(USAGE);
|
|
10
|
-
const taskDir = path.resolve(taskArg);
|
|
11
|
-
if (!fs.existsSync(path.join(taskDir, "artifacts"))) {
|
|
12
|
-
throw usageError(`${taskDir} has no artifacts/ directory; pass the task directory mm-harness task init wrote.`);
|
|
13
|
-
}
|
|
14
|
-
const command = optionString(parsed.options, "command");
|
|
15
|
-
const result = renderPrBody({ taskDir, ...command ? { command } : {} });
|
|
16
|
-
const out = path.resolve(optionString(parsed.options, "out") ?? path.join(taskDir, "artifacts", BODY_FILE));
|
|
17
|
-
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
18
|
-
fs.writeFileSync(out, result.body);
|
|
19
|
-
if (optionFlag(parsed.options, "json")) {
|
|
20
|
-
process.stdout.write(`${JSON.stringify({ command: "pr-body", subcommand: "render", status: "ok", taskDir, out, ...result, body: void 0 }, null, 2)}
|
|
21
|
-
`);
|
|
22
|
-
} else {
|
|
23
|
-
process.stdout.write(`pr body: ${out}
|
|
24
|
-
${result.sections.join("\n ")}
|
|
25
|
-
`);
|
|
26
|
-
}
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
export {
|
|
30
|
-
handlePrBody
|
|
31
|
-
};
|
package/dist/pr-body/render.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
const { readRecipeAttemptSelection } = createRequire(import.meta.url)(
|
|
5
|
-
"@farmslot/agent-runtime/scripts/retained-recipe-attempt.cjs"
|
|
6
|
-
);
|
|
7
|
-
const PROSE_FILE = "pr-description.md";
|
|
8
|
-
const BODY_FILE = "pr-body.md";
|
|
9
|
-
const RECIPE_HEADING = "## **Validation Recipe**";
|
|
10
|
-
const LOGS_HEADING = "## **Validation Logs**";
|
|
11
|
-
const WORKFLOW_HEADING = "## **Recipe Workflow**";
|
|
12
|
-
function headingKey(heading) {
|
|
13
|
-
return heading.replace(/^#+\s*/, "").replace(/[*_`]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
14
|
-
}
|
|
15
|
-
const MACHINE_KEYS = new Set([RECIPE_HEADING, LOGS_HEADING, WORKFLOW_HEADING].map(headingKey));
|
|
16
|
-
function levelTwoHeadingLines(lines) {
|
|
17
|
-
const found = [];
|
|
18
|
-
lines.forEach((line, index) => {
|
|
19
|
-
if (/^ {0,3}##(?!#)\s+\S/.test(line)) found.push(index);
|
|
20
|
-
});
|
|
21
|
-
return found;
|
|
22
|
-
}
|
|
23
|
-
function fenceFor(content) {
|
|
24
|
-
let longest = 0;
|
|
25
|
-
for (const run of content.match(/`+/g) ?? []) longest = Math.max(longest, run.length);
|
|
26
|
-
return "`".repeat(Math.max(3, longest + 1));
|
|
27
|
-
}
|
|
28
|
-
function fenced(content, info = "") {
|
|
29
|
-
const fence = fenceFor(content);
|
|
30
|
-
return `${fence}${info}
|
|
31
|
-
${content.replace(/\n+$/, "")}
|
|
32
|
-
${fence}`;
|
|
33
|
-
}
|
|
34
|
-
function escapeHtml(text) {
|
|
35
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
36
|
-
}
|
|
37
|
-
function countRecipeNodes(recipe) {
|
|
38
|
-
if (!recipe || typeof recipe !== "object") return 0;
|
|
39
|
-
const record = recipe;
|
|
40
|
-
if (Array.isArray(record.nodes)) return record.nodes.length;
|
|
41
|
-
if (Array.isArray(record.steps)) return record.steps.length;
|
|
42
|
-
const workflow = record.workflow;
|
|
43
|
-
if (!workflow || typeof workflow !== "object") return 0;
|
|
44
|
-
const graph = workflow.nodes;
|
|
45
|
-
if (graph && typeof graph === "object" && !Array.isArray(graph)) return Object.keys(graph).length;
|
|
46
|
-
return Object.values(workflow).reduce(
|
|
47
|
-
(total, value) => total + (Array.isArray(value) ? value.length : 0),
|
|
48
|
-
0
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
function recipeSection(file) {
|
|
52
|
-
const recipe = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
53
|
-
const nodes = countRecipeNodes(recipe);
|
|
54
|
-
const record = recipe && typeof recipe === "object" ? recipe : {};
|
|
55
|
-
const title = typeof record.title === "string" ? record.title : typeof record.description === "string" ? record.description : "";
|
|
56
|
-
const summary = escapeHtml(`recipe.json (${nodes} nodes${title ? ` \u2014 ${title.slice(0, 120)}` : ""})`);
|
|
57
|
-
return {
|
|
58
|
-
nodes,
|
|
59
|
-
body: [`<details><summary>${summary}</summary>`, "", fenced(JSON.stringify(recipe, null, 2), "json"), "</details>"].join("\n")
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function logsSection(file, command) {
|
|
63
|
-
const report = fs.readFileSync(file, "utf8");
|
|
64
|
-
const status = /^Status:\s*(\S+)/m.exec(report)?.[1];
|
|
65
|
-
const nodes = /^Nodes:\s*(.+?)\s*$/m.exec(report)?.[1];
|
|
66
|
-
const summary = escapeHtml(status || nodes ? `Full output (${[nodes, status].filter(Boolean).join(", ")})` : "Full output");
|
|
67
|
-
const lines = [];
|
|
68
|
-
if (command) lines.push("Command:", "", fenced(command, "bash"), "");
|
|
69
|
-
lines.push(`<details><summary>${summary}</summary>`, "", fenced(report), "</details>");
|
|
70
|
-
return lines.join("\n");
|
|
71
|
-
}
|
|
72
|
-
function trimEnd(lines) {
|
|
73
|
-
let end = lines.length;
|
|
74
|
-
while (end > 0 && !lines[end - 1].trim()) end -= 1;
|
|
75
|
-
return lines.slice(0, end);
|
|
76
|
-
}
|
|
77
|
-
function renderPrBody(input) {
|
|
78
|
-
const artifacts = path.join(input.taskDir, "artifacts");
|
|
79
|
-
const prosePath = path.join(artifacts, PROSE_FILE);
|
|
80
|
-
if (!fs.existsSync(prosePath)) {
|
|
81
|
-
throw new Error(`Missing ${path.relative(input.taskDir, prosePath)}: write the PR description there first.`);
|
|
82
|
-
}
|
|
83
|
-
const recipePath = path.join(artifacts, "recipe.json");
|
|
84
|
-
const selected = readRecipeAttemptSelection(input.taskDir);
|
|
85
|
-
const reportRoot = selected?.relativeArtifactRoot ?? "recipe-run";
|
|
86
|
-
const reportPath = path.join(artifacts, reportRoot, "report.md");
|
|
87
|
-
const workflowPath = path.join(artifacts, "workflow.mmd");
|
|
88
|
-
const machine = [];
|
|
89
|
-
let recipe = null;
|
|
90
|
-
if (fs.existsSync(recipePath)) {
|
|
91
|
-
const rendered = recipeSection(recipePath);
|
|
92
|
-
recipe = { nodes: rendered.nodes, path: path.relative(input.taskDir, recipePath) };
|
|
93
|
-
machine.push([RECIPE_HEADING, rendered.body]);
|
|
94
|
-
} else {
|
|
95
|
-
machine.push([RECIPE_HEADING, "Not applicable: this task has no `artifacts/recipe.json`."]);
|
|
96
|
-
}
|
|
97
|
-
const hasReport = fs.existsSync(reportPath);
|
|
98
|
-
machine.push([
|
|
99
|
-
LOGS_HEADING,
|
|
100
|
-
hasReport ? logsSection(reportPath, input.command) : `Not applicable: no recipe run report under \`artifacts/${reportRoot}/\`.`
|
|
101
|
-
]);
|
|
102
|
-
const hasWorkflow = fs.existsSync(workflowPath);
|
|
103
|
-
if (hasWorkflow) {
|
|
104
|
-
machine.push([
|
|
105
|
-
WORKFLOW_HEADING,
|
|
106
|
-
["<details><summary>workflow.mmd</summary>", "", fenced(fs.readFileSync(workflowPath, "utf8"), "mermaid"), "</details>"].join("\n")
|
|
107
|
-
]);
|
|
108
|
-
}
|
|
109
|
-
const lines = fs.readFileSync(prosePath, "utf8").replace(/\r\n?/g, "\n").split("\n");
|
|
110
|
-
const headings = levelTwoHeadingLines(lines);
|
|
111
|
-
const keep = new Array(lines.length).fill(true);
|
|
112
|
-
let insertAt = lines.length;
|
|
113
|
-
headings.forEach((start, at) => {
|
|
114
|
-
const end = headings[at + 1] ?? lines.length;
|
|
115
|
-
const key = headingKey(lines[start]);
|
|
116
|
-
if (MACHINE_KEYS.has(key)) keep.fill(false, start, end);
|
|
117
|
-
else if (insertAt === lines.length && /checklist/.test(key)) insertAt = start;
|
|
118
|
-
});
|
|
119
|
-
const before = lines.slice(0, insertAt).filter((_, index) => keep[index]);
|
|
120
|
-
const after = lines.slice(insertAt).filter((_, index) => keep[insertAt + index]);
|
|
121
|
-
const generated = machine.flatMap(([heading, body2]) => [heading, "", body2, ""]);
|
|
122
|
-
const body = [...trimEnd(before), "", ...generated, ...after].join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "\n");
|
|
123
|
-
return {
|
|
124
|
-
body,
|
|
125
|
-
sections: machine.map(([heading]) => heading),
|
|
126
|
-
recipe,
|
|
127
|
-
run: hasReport ? { path: path.relative(input.taskDir, reportPath) } : null,
|
|
128
|
-
workflow: hasWorkflow ? { path: path.relative(input.taskDir, workflowPath) } : null
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
export {
|
|
132
|
-
BODY_FILE,
|
|
133
|
-
LOGS_HEADING,
|
|
134
|
-
PROSE_FILE,
|
|
135
|
-
RECIPE_HEADING,
|
|
136
|
-
WORKFLOW_HEADING,
|
|
137
|
-
countRecipeNodes,
|
|
138
|
-
escapeHtml,
|
|
139
|
-
fenceFor,
|
|
140
|
-
fenced,
|
|
141
|
-
headingKey,
|
|
142
|
-
levelTwoHeadingLines,
|
|
143
|
-
renderPrBody
|
|
144
|
-
};
|