@deeeed/metamask-harness 0.17.5 → 0.18.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +872 -0
  2. package/README.md +94 -140
  3. package/adapters/extension/console-tail.mjs +55 -24
  4. package/adapters/extension/ensure-browser.sh +6 -1
  5. package/adapters/extension/inject.mjs +6 -9
  6. package/adapters/extension/launch-browser.cjs +7 -1
  7. package/adapters/extension/launch-webpack.cjs +83 -0
  8. package/adapters/extension/launch.sh +8 -11
  9. package/adapters/extension/lib/chrome-args.cjs +8 -1
  10. package/adapters/extension/lib/macos-focus.cjs +32 -0
  11. package/adapters/extension/live.sh +12 -21
  12. package/adapters/extension/reattach.sh +1 -0
  13. package/adapters/extension/seed-fixture.sh +4 -12
  14. package/adapters/extension/sidepanel-toggle.sh +4 -1
  15. package/adapters/extension/stamp-runtime-title.cjs +58 -0
  16. package/adapters/extension/start-watch.sh +17 -4
  17. package/adapters/extension/stop-viewers.sh +1 -1
  18. package/adapters/extension/sync-webpack-dist.cjs +107 -0
  19. package/adapters/extension/verify.sh +1 -1
  20. package/adapters/manifest.json +53 -37
  21. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
  22. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  23. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
  24. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  25. package/adapters/mobile/cleanup.sh +16 -176
  26. package/adapters/mobile/inject.sh +90 -671
  27. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  28. package/adapters/mobile/open-device.sh +45 -7
  29. package/adapters/mobile/start-console-forwarder.sh +70 -0
  30. package/adapters/mobile/start-metro.sh +0 -41
  31. package/adapters/mobile/verify.sh +19 -64
  32. package/adapters/mobile/wait-for-bridge.sh +22 -8
  33. package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
  34. package/adapters/shared/resolve-slot-ports.sh +17 -5
  35. package/adapters/shared/update-check-worker.mjs +43 -0
  36. package/bin/mm-harness +21 -2
  37. package/dist/adapters/core/surface.js +4 -1
  38. package/dist/adapters/extension/console-capture.js +95 -0
  39. package/dist/adapters/extension/product-config.js +110 -0
  40. package/dist/adapters/extension/runtime-decision.js +20 -2
  41. package/dist/adapters/extension/surface.js +22 -3
  42. package/dist/adapters/mobile/perps-env.js +43 -3
  43. package/dist/adapters/mobile/prepare.js +39 -7
  44. package/dist/adapters/mobile/surface.js +5 -2
  45. package/dist/adapters/resolve-slot-ports.js +2 -2
  46. package/dist/adapters/slot-ports.js +13 -16
  47. package/dist/adapters.js +56 -16
  48. package/dist/checkout-lock.js +27 -2
  49. package/dist/cli-color.js +19 -0
  50. package/dist/cli-commands.js +1 -1
  51. package/dist/cli.js +7 -14
  52. package/dist/command-contract.js +451 -0
  53. package/dist/command-journal.js +225 -0
  54. package/dist/commands/call.js +170 -50
  55. package/dist/commands/check.js +9 -3
  56. package/dist/commands/completion-candidates.js +20 -13
  57. package/dist/commands/device-target.js +27 -12
  58. package/dist/commands/doctor.js +106 -25
  59. package/dist/commands/fixtures.js +92 -34
  60. package/dist/commands/flows.js +39 -10
  61. package/dist/commands/last.js +52 -0
  62. package/dist/commands/launch/extension.js +38 -15
  63. package/dist/commands/launch/index.js +207 -63
  64. package/dist/commands/list-executables.js +151 -29
  65. package/dist/commands/logs.js +8 -6
  66. package/dist/commands/manifest.js +270 -35
  67. package/dist/commands/parse-args.js +13 -1
  68. package/dist/commands/provision.js +10 -3
  69. package/dist/commands/run-engine.js +435 -105
  70. package/dist/commands/run-report.js +12 -3
  71. package/dist/commands/run.js +355 -50
  72. package/dist/commands/shared.js +75 -3
  73. package/dist/commands/status-probe.js +4 -1
  74. package/dist/commands/status.js +2 -1
  75. package/dist/commands/stop.js +7 -2
  76. package/dist/commands/update.js +16 -28
  77. package/dist/completions-cache.js +1 -1
  78. package/dist/doctor.js +57 -10
  79. package/dist/harness.js +47 -137
  80. package/dist/heal-bounds.js +2 -2
  81. package/dist/json-stream.js +57 -0
  82. package/dist/live-adapter-contract.js +138 -32
  83. package/dist/manifest.js +161 -1
  84. package/dist/mm-harness-cli.js +135 -54
  85. package/dist/paths.js +2 -5
  86. package/dist/recipe-security.js +178 -0
  87. package/dist/run-diagnostics.js +261 -0
  88. package/dist/runner.js +117 -8
  89. package/docs/CONTRIBUTING.md +137 -0
  90. package/docs/QA.md +185 -0
  91. package/docs/RECIPES.md +161 -0
  92. package/docs/SECURITY.md +88 -0
  93. package/library/README.md +4 -0
  94. package/library/actions/core/perps/_controller.mjs +10 -55
  95. package/library/actions/core/perps/read_account.mjs +2 -2
  96. package/library/actions/core/perps/read_orders.mjs +2 -1
  97. package/library/actions/core/perps/read_positions.mjs +2 -1
  98. package/library/actions/core/wallet/list_accounts.mjs +95 -0
  99. package/library/actions/extension/platform/cdp.mjs +1 -0
  100. package/library/actions/extension/wallet/list_accounts.mjs +41 -0
  101. package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
  102. package/library/manifests/core.action-manifest.json +68 -7
  103. package/library/manifests/extension.action-manifest.json +53 -0
  104. package/library/manifests/mobile.action-manifest.json +81 -3
  105. package/library/recipes/runner/action-validation.extension.recipe.json +8 -1
  106. package/library/recipes/runner/action-validation.mobile.recipe.json +8 -1
  107. package/library/recipes/runner/smoke.core.recipe.json +27 -0
  108. package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
  109. package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
  110. package/package.json +12 -5
  111. package/scripts/completions.sh +7 -7
  112. package/scripts/validate-human-outcomes.mjs +169 -0
  113. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  114. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  115. package/adapters/mobile/overlay/compat/README.md +0 -28
  116. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  117. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  118. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  119. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  120. package/docs/ADAPTER-SURFACE.md +0 -119
  121. package/docs/CHEATSHEET.md +0 -61
  122. package/docs/CLI-SPEC.md +0 -1098
  123. package/docs/CODE-MAP.md +0 -62
  124. package/docs/DEBUG-HANDOVER.md +0 -36
  125. package/docs/MENTAL-MODEL.md +0 -295
  126. package/docs/UX-PRINCIPLES.md +0 -64
  127. package/docs/architecture.md +0 -398
  128. package/docs/live-adapter-contract.md +0 -188
  129. package/docs/package-boundaries.md +0 -47
  130. package/docs/perps-flow-catalog.md +0 -235
  131. package/docs/recipe-libraries.md +0 -301
  132. package/docs/runtime-file-conventions.md +0 -36
@@ -18,6 +18,22 @@
18
18
  "inputs": "--target --port --clear; env WATCHER_PORT, METRO_PORT, MOBILE_METRO_REQUIRED_ENV, METRO_MAX_WORKERS",
19
19
  "outputs": "Metro PID file + log; exit 0 ready / 1 timeout / 2 bad args"
20
20
  },
21
+ {
22
+ "id": "mobile/start-console-forwarder",
23
+ "entry": "adapters/mobile/start-console-forwarder.sh",
24
+ "kind": "bash",
25
+ "purpose": "Ensure one runner-owned Mobile application console collector is alive after bridge readiness.",
26
+ "inputs": "--target --port --quiet; env WATCHER_PORT, METRO_PORT, METAMASK_RECIPE_CONSOLE_FORWARD",
27
+ "outputs": "app-console.log + collector PID file; exit 0 ready / 1 startup failed / 2 bad args"
28
+ },
29
+ {
30
+ "id": "mobile/launch-console-forwarder",
31
+ "entry": "adapters/mobile/launch-console-forwarder.cjs",
32
+ "kind": "node",
33
+ "purpose": "Detach the Mobile application console collector from command-runner process-group cleanup.",
34
+ "inputs": "--port --out --err",
35
+ "outputs": "collector PID on stdout; exit 0 launched / 1 spawn failed / 2 bad args"
36
+ },
21
37
  {
22
38
  "id": "mobile/launch-metro",
23
39
  "entry": "adapters/mobile/launch-metro.cjs",
@@ -62,17 +78,17 @@
62
78
  "id": "mobile/inject",
63
79
  "entry": "adapters/mobile/inject.sh",
64
80
  "kind": "bash",
65
- "purpose": "Install the mobile harness overlay (delegate + snapshots; full mode adds the app bridge with backups).",
66
- "inputs": "--target --allow-dirty-harness-paths --force-overlay --no-git-exclude; env RECIPE_HARNESS_ROOT, METAMASK_RUNNER_PROTOCOL_ROOT>FARMSLOT_ROOT",
67
- "outputs": "<harness>/manifest.json + runner/ + scripts/ + backup store; exit 0/1/2"
81
+ "purpose": "Install Mobile runner metadata, delegates, and snapshots without modifying product source.",
82
+ "inputs": "--target --no-git-exclude; env RECIPE_HARNESS_ROOT, METAMASK_RUNNER_PROTOCOL_ROOT",
83
+ "outputs": "<harness>/manifest.json + runner/ + scripts/; exit 0/1/2"
68
84
  },
69
85
  {
70
86
  "id": "mobile/cleanup",
71
87
  "entry": "adapters/mobile/cleanup.sh",
72
88
  "kind": "bash",
73
- "purpose": "Remove the mobile overlay; restore backed-up product files; remove recorded git excludes.",
74
- "inputs": "--target --allow-managed-changes; env RECIPE_HARNESS_ROOT",
75
- "outputs": "removes <harness>/ + backups; exit 0/1/2"
89
+ "purpose": "Remove Mobile harness metadata and recorded git excludes without touching product source.",
90
+ "inputs": "--target; env RECIPE_HARNESS_ROOT",
91
+ "outputs": "removes <harness>/; exit 0/1/2"
76
92
  },
77
93
  {
78
94
  "id": "mobile/verify",
@@ -114,6 +130,30 @@
114
130
  "inputs": "--target --runtime-dir --runner-bin --summary",
115
131
  "outputs": "<runtime-dir>/recipe-harness-webpack.{pid,log}; optional summary; exit 0/1/2"
116
132
  },
133
+ {
134
+ "id": "extension/launch-webpack",
135
+ "entry": "adapters/extension/launch-webpack.cjs",
136
+ "kind": "node",
137
+ "purpose": "Detach the normal yarn start watcher from the launching CLI process while preserving its target, environment, and log.",
138
+ "inputs": "--target --log --pid-file --sync-script --sync-pid-file --sync-log --dist --runtime-dist; optional --port --path-prefix",
139
+ "outputs": "watcher and runtime-sync pids; webpack and sync logs; exit 0/1"
140
+ },
141
+ {
142
+ "id": "extension/sync-webpack-dist",
143
+ "entry": "adapters/extension/sync-webpack-dist.cjs",
144
+ "kind": "node",
145
+ "purpose": "After each successful webpack compile, update the loaded isolated runtime in place so an ordinary browser refresh sees the edit.",
146
+ "inputs": "--watcher-pid --watch-log --target --dist --runtime-dist --pid-file",
147
+ "outputs": "runtime-dist updated in place after successful compile markers; exits with its watcher"
148
+ },
149
+ {
150
+ "id": "extension/stamp-runtime-title",
151
+ "entry": "adapters/extension/stamp-runtime-title.cjs",
152
+ "kind": "node",
153
+ "purpose": "Stamp the slot id into isolated Extension page titles so window identity survives browser refreshes.",
154
+ "inputs": "--target --runtime-dist; optional --runtime-dir",
155
+ "outputs": "home.html and sidepanel.html titles updated in the isolated runtime only; exit 0/1"
156
+ },
117
157
  {
118
158
  "id": "extension/stop-viewers",
119
159
  "entry": "adapters/extension/stop-viewers.sh",
@@ -378,22 +418,6 @@
378
418
  "inputs": "ESM import from resolve-slot-ports.mjs or resolve-slot-ports.ts",
379
419
  "outputs": "resolveSlotPortsByRepo and related functions"
380
420
  },
381
- {
382
- "id": "lib/resolve-farmslot-ports",
383
- "entry": "adapters/shared/resolve-farmslot-ports.mjs",
384
- "kind": "node",
385
- "purpose": "Back-compat shim (one release): delegates to resolve-slot-ports.mjs.",
386
- "inputs": "resolve-farmslot-ports.mjs <fn> <repo>",
387
- "outputs": "same as resolve-slot-ports.mjs"
388
- },
389
- {
390
- "id": "lib/resolve-farmslot-ports-core",
391
- "entry": "adapters/shared/resolve-farmslot-ports-core.mjs",
392
- "kind": "lib",
393
- "purpose": "Back-compat shim (one release): re-exports resolve-slot-ports-core.mjs.",
394
- "inputs": "ESM import of the old path",
395
- "outputs": "re-exports resolveSlotPortsByRepo (and the resolveFarmslotPortsByRepo alias)"
396
- },
397
421
  {
398
422
  "id": "lib/recipe-harness-root",
399
423
  "entry": "adapters/shared/recipe-harness-root.mjs",
@@ -406,18 +430,10 @@
406
430
  "id": "lib/resolve-slot-ports-sh",
407
431
  "entry": "adapters/shared/resolve-slot-ports.sh",
408
432
  "kind": "lib",
409
- "purpose": "Bash-compat sourced wrapper over resolve-slot-ports.mjs for stop-metro and legacy callers.",
433
+ "purpose": "Sourced wrapper over resolve-slot-ports.mjs for shell runtime leaves.",
410
434
  "inputs": "source + resolve_* / apply_resolved_* function calls",
411
435
  "outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
412
436
  },
413
- {
414
- "id": "lib/resolve-farmslot-ports-sh",
415
- "entry": "adapters/shared/resolve-farmslot-ports.sh",
416
- "kind": "lib",
417
- "purpose": "Back-compat shim (one release): sources resolve-slot-ports.sh (keeps the resolve_farmslot_ports_by_repo alias).",
418
- "inputs": "source",
419
- "outputs": "same sourced functions as resolve-slot-ports.sh"
420
- },
421
437
  {
422
438
  "id": "lib/reap-checkout-metros",
423
439
  "entry": "adapters/shared/reap-checkout-metros.sh",
@@ -480,12 +496,12 @@
480
496
  "outputs": "stable SHA-256 fingerprint"
481
497
  },
482
498
  {
483
- "id": "compat-overlays/mobile",
484
- "entry": "adapters/mobile/overlay/compat",
485
- "kind": "compat-overlay",
486
- "purpose": "Historical-commit replay support: reversible source overlays (e.g. rn81-message-event-source.patch) applied manually/via the recipe-harness skill so pre-RN-0.81 mobile checkouts boot and bridge under the current toolchain.",
487
- "inputs": "manual `git apply` in the historical product checkout (no in-repo apply logic)",
488
- "outputs": "patched historical checkout (reversible via git apply -R); overlay path recorded in eval evidence"
499
+ "id": "lib/update-check-worker",
500
+ "entry": "adapters/shared/update-check-worker.mjs",
501
+ "kind": "node",
502
+ "purpose": "Detached, silent registry probe used by the daily passive update notice.",
503
+ "inputs": "<cache-file> <timeout-ms> [registry-endpoint]",
504
+ "outputs": "atomic update-cache refresh; always silent and best-effort"
489
505
  }
490
506
  ]
491
507
  }
@@ -254,6 +254,21 @@ const COMMANDS = {
254
254
  return { ...result, testId, deviceName };
255
255
  },
256
256
 
257
+ async 'press-text'(client, args, { deviceName } = {}) {
258
+ const text = args[0];
259
+ if (!text) {
260
+ throw new Error('Usage: press-text <text>');
261
+ }
262
+ const expr = `(function() {
263
+ if (typeof globalThis.__AGENTIC__?.pressText !== 'function') {
264
+ return { ok: false, error: 'The app bridge does not expose pressText' };
265
+ }
266
+ return globalThis.__AGENTIC__.pressText(${JSON.stringify(text)});
267
+ })()`;
268
+ const result = await cdpEval(client, expr);
269
+ return { ...result, text, deviceName };
270
+ },
271
+
257
272
  async 'scroll-view'(client, args, { deviceName } = {}) {
258
273
  let testId;
259
274
  let offset = 300;
@@ -678,6 +693,7 @@ Commands:
678
693
  get-selected-account Get the currently selected account
679
694
  switch-account <address> Switch to account by address
680
695
  press-test-id <testId> Press a component by its testID prop
696
+ press-text <text> Press a component containing visible text
681
697
  scroll-view [--test-id <id>] [--offset <n>] [--animated]
682
698
  Scroll a ScrollView/FlatList
683
699
  set-input <testId> <value> Set text input value by testID (calls onChangeText)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // console-forwarder — stream device console lines into metro.log.
3
3
  //
4
- // React Native (Bridgeless) gates its legacy console→Metro forwarding on
4
+ // React Native (Bridgeless) gates its built-in console→Metro forwarding on
5
5
  // `console._isPolyfilled` (setUpDeveloperTools.js, T214991636); with the Hermes
6
6
  // native console that gate is false, so app logs (incl. DevLogger) never reach
7
7
  // Metro's log. The supported contract for log consumption in modern RN is CDP —
@@ -50,7 +50,9 @@ function matchesBridgeTarget(target, env) {
50
50
  // (an in-app agentic bridge is live), not merely a registered debug target.
51
51
  function hasMatchingRoute(value, env) {
52
52
  const targets = Array.isArray(value) ? value : [value];
53
- return targets.some((t) => matchesBridgeTarget(t, env) && t && t.route);
53
+ return targets.some(
54
+ (t) => matchesBridgeTarget(t, env) && t && t.agenticPresent === true && Boolean(t.route),
55
+ );
54
56
  }
55
57
 
56
58
  // Human summary of the targets that answered (platform / deviceName), for teaching on
@@ -136,7 +136,7 @@ async function discoverTarget(port) {
136
136
  // ANDROID_TARGET_DEVICE_NAME — Metro-compatible model prefix resolved by bridge.mjs
137
137
  // from the adb serial via `adb -s <serial> shell getprop ro.product.model`.
138
138
  // Example: "Pixel 6" matches Metro deviceName "Pixel 6 - 16 - API 36".
139
- // ANDROID_DEVICE — exact Metro deviceName (backward compat / user-specified).
139
+ // ANDROID_DEVICE — exact user-specified Metro deviceName.
140
140
  const androidTargetName = loadAndroidTargetDeviceName();
141
141
  const androidDevice = loadAndroidDevice();
142
142
  const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
@@ -205,8 +205,7 @@ async function discoverTarget(port) {
205
205
  }
206
206
  candidates = androidFiltered;
207
207
  } else if (androidDevice) {
208
- // Exact Metro deviceName match (user set ANDROID_DEVICE to the Metro name, or
209
- // legacy path where ANDROID_DEVICE was not resolved from serial).
208
+ // Exact Metro deviceName match when the user set ANDROID_DEVICE directly.
210
209
  androidFiltered = candidates.filter((t) => t.deviceName === androidDevice);
211
210
  if (androidFiltered.length === 0) {
212
211
  // Pinned by exact Metro name but no candidate matched — fail fast.
@@ -1,196 +1,36 @@
1
1
  #!/usr/bin/env bash
2
- # cleanup.sh remove the mobile harness overlay from a checkout
3
- #
4
- # Purpose:
5
- # Restores backed-up product files (full installs), removes exactly the
6
- # .git/info/exclude entries the install recorded, then deletes the harness
7
- # dir and backup store. Metadata-only installs remove metadata alone.
8
- #
9
- # Inputs (flags / env):
10
- # --target <metamask-mobile> (default $PWD)
11
- # --allow-managed-changes skip the managed-hash refusal
12
- # env RECIPE_HARNESS_ROOT
13
- #
14
- # Outputs:
15
- # Removes <harness>/ and backup dir; restores managed product paths.
16
- # Exit 0 — cleaned; 1 — no backup state / changed managed paths refusal /
17
- # missing backup; 2 — bad args.
18
- #
19
- # Never touches: product files other than the recorded managed paths;
20
- # .git/info/exclude lines it did not record; product-local cache dirs.
2
+ # Remove checkout-local Mobile harness metadata without touching product source.
21
3
  set -euo pipefail
22
4
 
23
5
  TARGET="$PWD"
24
- ALLOW_MANAGED_CHANGES=false
25
6
  while [ "$#" -gt 0 ]; do
26
7
  case "$1" in
27
- --target) TARGET="$2"; shift 2 ;;
28
- --allow-managed-changes) ALLOW_MANAGED_CHANGES=true; shift ;;
29
- -h|--help) echo "Usage: cleanup.sh [--target <metamask-mobile>] [--allow-managed-changes]"; exit 0 ;;
8
+ --target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
9
+ -h|--help) echo "Usage: cleanup.sh [--target <metamask-mobile>]"; exit 0 ;;
30
10
  *) echo "Unknown arg: $1" >&2; exit 2 ;;
31
11
  esac
32
12
  done
33
13
 
34
14
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
- TARGET="$(cd "$TARGET" && pwd)"
36
- # shellcheck disable=SC1091
37
- . "$SCRIPT_DIR/../shared/hash-helpers.sh"
38
- # shellcheck disable=SC1091
39
- for _hp in "$SCRIPT_DIR/../shared/harness-path.sh"; do
40
- [ -f "$_hp" ] && { . "$_hp"; break; }
41
- done
42
- unset _hp
43
- if ! command -v harness_root >/dev/null 2>&1; then
44
- echo "mm-harness: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
45
- exit 1
46
- fi
15
+ . "$SCRIPT_DIR/../shared/harness-path.sh"
16
+ TARGET="$(cd "$TARGET" && pwd -P)"
47
17
  HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
48
18
 
49
- # A console-forwarder left running would keep polling for this checkout's Metro
50
- # after cleanup orphans it; stop any forwarder scoped to this checkout first.
51
19
  pkill -f "console-forwarder.cjs --port .* --out $TARGET/" 2>/dev/null || true
52
- if GIT_BACKUP_PATH="$(git -C "$TARGET" rev-parse --git-path recipe-harness/mobile/backup 2>/dev/null)"; then
53
- case "$GIT_BACKUP_PATH" in
54
- /*) BACKUP_DIR="$GIT_BACKUP_PATH" ;;
55
- *) BACKUP_DIR="$TARGET/$GIT_BACKUP_PATH" ;;
56
- esac
57
- else
58
- BACKUP_DIR="$HARNESS_DIR/backup-store"
59
- fi
60
- if [ ! -f "$BACKUP_DIR/state.env" ] && [ -f "$HARNESS_DIR/backup/state.env" ]; then
61
- BACKUP_DIR="$HARNESS_DIR/backup"
62
- fi
63
- STATE_FILE="$BACKUP_DIR/state.env"
64
-
65
- remove_recorded_git_exclude_entries() {
66
- local tracking_file="$1"
67
- [ -s "$tracking_file" ] || return 0
68
- local git_dir exclude_file tmp_file
69
- if ! git_dir="$(git -C "$TARGET" rev-parse --git-dir 2>/dev/null)"; then
70
- return 0
71
- fi
72
- case "$git_dir" in
73
- /*) ;;
74
- *) git_dir="$TARGET/$git_dir" ;;
75
- esac
76
- exclude_file="$git_dir/info/exclude"
77
- [ -f "$exclude_file" ] || return 0
78
- # Remove only the lines THIS install recorded, one occurrence per distinct
79
- # ledger entry (the appended copy is the last match), so a pre-existing
80
- # duplicate copy or a stale/duplicate ledger entry never drops a line we did
81
- # not add this run.
82
- tmp_file="$(mktemp)"
83
- awk '
84
- NR==FNR { if (length($0)) want[$0]=1; next }
85
- { lines[++n]=$0; if ($0 in want) last[$0]=n }
86
- END { for (k in last) drop[last[k]]=1; for (i=1;i<=n;i++) if (!(i in drop)) print lines[i] }
87
- ' "$tracking_file" "$exclude_file" > "$tmp_file"
88
- mv "$tmp_file" "$exclude_file"
89
- }
90
-
91
- if [ -f "$HARNESS_DIR/manifest.json" ] && node -e '
92
- const fs = require("fs");
93
- const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
94
- process.exit(manifest.installMode === "product-owned" ? 0 : 1);
95
- ' "$HARNESS_DIR/manifest.json" 2>/dev/null; then
96
- remove_recorded_git_exclude_entries "$HARNESS_DIR/added-git-exclude"
97
- rm -rf "$HARNESS_DIR"
98
- echo "Cleaned mobile recipe harness metadata from $TARGET (product-owned in-app bridge files left untouched)"
99
- exit 0
100
- fi
101
-
102
- if [ ! -f "$STATE_FILE" ]; then
103
- if [ -f "$HARNESS_DIR/manifest.json" ] && node -e '
104
- const fs = require("fs");
105
- const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
106
- process.exit(manifest.installMode === "product-owned" ? 0 : 1);
107
- ' "$HARNESS_DIR/manifest.json" 2>/dev/null; then
108
- remove_recorded_git_exclude_entries "$HARNESS_DIR/added-git-exclude"
109
- rm -rf "$HARNESS_DIR"
110
- echo "Cleaned mobile recipe harness metadata from $TARGET (product-owned in-app bridge files left untouched)"
111
- exit 0
112
- fi
113
- echo "No mobile harness backup found at $STATE_FILE" >&2
114
- exit 1
115
- fi
116
-
117
- while IFS= read -r _line || [ -n "$_line" ]; do
118
- [[ "$_line" =~ ^[[:space:]]*(#|$) ]] && continue
119
- _key="${_line%%=*}"
120
- _val="${_line#*=}"
121
- case "$_key" in
122
- AGENTIC_SERVICE_EXISTED|PACKAGE_JSON_EXISTED|NAVIGATION_SERVICE_EXISTED|APP_TSX_EXISTED) ;;
123
- *) continue ;;
124
- esac
125
- export "$_key=$_val"
126
- done < "$STATE_FILE"
127
- unset _line _key _val
128
-
129
- HASH_FILE="$BACKUP_DIR/managed-hashes.tsv"
130
-
131
- verify_managed_paths_unchanged() {
132
- if [ ! -f "$HASH_FILE" ]; then
133
- cat >&2 <<EOF
134
- Refusing cleanup: no managed hash file found at $HASH_FILE.
135
- Re-run mobile harness install from the current runner to refresh safety metadata, or restore manually.
136
- EOF
137
- exit 1
138
- fi
139
- local rel expected actual conflicts=0
140
- while IFS=$'\t' read -r rel expected; do
141
- [ -n "$rel" ] || continue
142
- actual="$(hash_path "$rel")"
143
- if [ "$actual" != "$expected" ]; then
144
- echo "Refusing cleanup: managed harness path changed after install: $rel" >&2
145
- echo " expected: $expected" >&2
146
- echo " actual: $actual" >&2
147
- conflicts=1
148
- fi
149
- done < "$HASH_FILE"
150
- if [ "$conflicts" != "0" ]; then
151
- cat >&2 <<EOF
152
- Cleanup would restore backups over files that changed after harness install.
153
- Save/stash those changes, rerun harness install to refresh managed hashes, or restore manually.
154
- EOF
155
- exit 1
156
- fi
157
- }
158
-
159
- if [ "$ALLOW_MANAGED_CHANGES" != "true" ]; then
160
- verify_managed_paths_unchanged
161
- else
162
- echo "Allowing cleanup over changed managed harness paths."
163
- fi
164
20
 
165
- restore_path() {
166
- local rel="$1"
167
- local existed="$2"
168
- local target_path="$TARGET/$rel"
169
- local backup_path="$BACKUP_DIR/$rel"
170
- if [ "$existed" = "1" ]; then
171
- if [ ! -e "$backup_path" ]; then
172
- echo "Missing backup for $rel at $backup_path" >&2
173
- exit 1
21
+ tracking_file="$HARNESS_DIR/added-git-exclude"
22
+ if [ -s "$tracking_file" ]; then
23
+ git_dir="$(git -C "$TARGET" rev-parse --git-dir 2>/dev/null || true)"
24
+ if [ -n "$git_dir" ]; then
25
+ case "$git_dir" in /*) ;; *) git_dir="$TARGET/$git_dir" ;; esac
26
+ exclude_file="$git_dir/info/exclude"
27
+ if [ -f "$exclude_file" ]; then
28
+ tmp_file="$(mktemp)"
29
+ awk 'NR==FNR { if (length($0)) drop[$0]=1; next } !($0 in drop)' "$tracking_file" "$exclude_file" > "$tmp_file"
30
+ mv "$tmp_file" "$exclude_file"
174
31
  fi
175
- rm -rf "$target_path"
176
- mkdir -p "$(dirname "$target_path")"
177
- cp -a "$backup_path" "$target_path"
178
- else
179
- rm -rf "$target_path"
180
32
  fi
181
- }
182
-
183
- restore_path "app/dev-tools/AgenticService" "${AGENTIC_SERVICE_EXISTED:-0}"
184
- if [ "${PACKAGE_JSON_EXISTED+x}" = "x" ]; then
185
- restore_path "package.json" "$PACKAGE_JSON_EXISTED"
186
33
  fi
187
- restore_path "app/core/NavigationService/NavigationService.ts" "${NAVIGATION_SERVICE_EXISTED:-0}"
188
- restore_path "app/components/Nav/App/App.tsx" "${APP_TSX_EXISTED:-0}"
189
-
190
- remove_recorded_git_exclude_entries "$BACKUP_DIR/added-git-exclude"
191
34
 
192
- # Leave any product-local cache dirs alone: they are owned by the product
193
- # checkout, not the harness.
194
35
  rm -rf "$HARNESS_DIR"
195
- rm -rf "$BACKUP_DIR"
196
- echo "Cleaned mobile recipe harness from $TARGET"
36
+ echo "Cleaned mobile harness metadata from $TARGET; product files were not touched"