@swmansion/argent 0.6.0-next.2 → 0.6.1
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/bin/ax-service +0 -0
- package/bin/simulator-server +0 -0
- package/dist/installer.mjs +55 -28
- package/dist/tool-server.cjs +373 -83
- package/dylibs/libArgentInjectionBootstrap.dylib +0 -0
- package/dylibs/libKeyboardPatch.dylib +0 -0
- package/dylibs/libNativeDevtoolsIos.dylib +0 -0
- package/package.json +1 -1
- package/rules/argent.md +1 -1
- package/skills/argent-react-native-optimization/SKILL.md +6 -6
package/bin/ax-service
CHANGED
|
Binary file
|
package/bin/simulator-server
CHANGED
|
Binary file
|
package/dist/installer.mjs
CHANGED
|
@@ -10392,7 +10392,7 @@ ${l}
|
|
|
10392
10392
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
10393
10393
|
import { existsSync as existsSync3 } from "node:fs";
|
|
10394
10394
|
import { resolve as resolve2 } from "node:path";
|
|
10395
|
-
import {
|
|
10395
|
+
import { spawn } from "node:child_process";
|
|
10396
10396
|
|
|
10397
10397
|
// ../argent-installer/src/mcp-configs.ts
|
|
10398
10398
|
import * as fs2 from "node:fs";
|
|
@@ -12813,6 +12813,18 @@ function getInstalledVersion() {
|
|
|
12813
12813
|
return null;
|
|
12814
12814
|
}
|
|
12815
12815
|
}
|
|
12816
|
+
function getGloballyInstalledVersion() {
|
|
12817
|
+
const binaryPath = getGlobalBinaryPath();
|
|
12818
|
+
if (!binaryPath) return null;
|
|
12819
|
+
try {
|
|
12820
|
+
const realPath = fs.realpathSync(binaryPath);
|
|
12821
|
+
const pkgRoot = resolvePackageRoot(path.dirname(realPath));
|
|
12822
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"));
|
|
12823
|
+
return pkg.version ?? null;
|
|
12824
|
+
} catch {
|
|
12825
|
+
return null;
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12816
12828
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
12817
12829
|
function getLatestVersion() {
|
|
12818
12830
|
const result = execSync(`npm view ${PACKAGE_NAME} version --registry ${NPM_REGISTRY}`, {
|
|
@@ -12825,6 +12837,31 @@ function isNewerVersion(candidate, current) {
|
|
|
12825
12837
|
if (!import_semver.default.valid(candidate) || !import_semver.default.valid(current)) return false;
|
|
12826
12838
|
return import_semver.default.gt(candidate, current);
|
|
12827
12839
|
}
|
|
12840
|
+
var TEMP_RUNNER_MARKERS = [
|
|
12841
|
+
"_npx",
|
|
12842
|
+
"/dlx-",
|
|
12843
|
+
"\\dlx-",
|
|
12844
|
+
"bun/install/cache",
|
|
12845
|
+
".bun\\install\\cache"
|
|
12846
|
+
];
|
|
12847
|
+
function isTempRunnerPath(binaryPath) {
|
|
12848
|
+
return TEMP_RUNNER_MARKERS.some((marker) => binaryPath.includes(marker));
|
|
12849
|
+
}
|
|
12850
|
+
function getGlobalBinaryPath() {
|
|
12851
|
+
try {
|
|
12852
|
+
const cmd = process.platform === "win32" ? "where" : "which -a";
|
|
12853
|
+
const output = execSync(`${cmd} ${MCP_BINARY_NAME}`, {
|
|
12854
|
+
encoding: "utf8",
|
|
12855
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
12856
|
+
});
|
|
12857
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).find((line) => !isTempRunnerPath(line)) ?? null;
|
|
12858
|
+
} catch {
|
|
12859
|
+
return null;
|
|
12860
|
+
}
|
|
12861
|
+
}
|
|
12862
|
+
function isGloballyInstalled() {
|
|
12863
|
+
return getGlobalBinaryPath() !== null;
|
|
12864
|
+
}
|
|
12828
12865
|
function isSkillsCliAvailable() {
|
|
12829
12866
|
try {
|
|
12830
12867
|
execSync("npx --no-install skills --version", {
|
|
@@ -13700,25 +13737,6 @@ function formatSkillRefreshSummary(results) {
|
|
|
13700
13737
|
}
|
|
13701
13738
|
|
|
13702
13739
|
// ../argent-installer/src/init.ts
|
|
13703
|
-
var TEMP_RUNNER_MARKERS = [
|
|
13704
|
-
"_npx",
|
|
13705
|
-
"/dlx-",
|
|
13706
|
-
"\\dlx-",
|
|
13707
|
-
"bun/install/cache",
|
|
13708
|
-
".bun\\install\\cache"
|
|
13709
|
-
];
|
|
13710
|
-
function isGloballyInstalled() {
|
|
13711
|
-
try {
|
|
13712
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
13713
|
-
const binaryPath = execSync2(`${cmd} ${MCP_BINARY_NAME}`, {
|
|
13714
|
-
encoding: "utf8",
|
|
13715
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
13716
|
-
}).trim();
|
|
13717
|
-
return !TEMP_RUNNER_MARKERS.some((marker) => binaryPath.includes(marker));
|
|
13718
|
-
} catch {
|
|
13719
|
-
return false;
|
|
13720
|
-
}
|
|
13721
|
-
}
|
|
13722
13740
|
function runShellCommand(cmd) {
|
|
13723
13741
|
return new Promise((resolve3, reject) => {
|
|
13724
13742
|
const isWin = process.platform === "win32";
|
|
@@ -14246,8 +14264,9 @@ async function killToolServer() {
|
|
|
14246
14264
|
async function update(args) {
|
|
14247
14265
|
const nonInteractive = args.includes("--yes") || args.includes("-y");
|
|
14248
14266
|
Wt2(import_picocolors3.default.bgCyan(import_picocolors3.default.black(" argent update ")));
|
|
14249
|
-
const
|
|
14250
|
-
|
|
14267
|
+
const globallyInstalled = isGloballyInstalled();
|
|
14268
|
+
const installed = globallyInstalled ? getGloballyInstalledVersion() : null;
|
|
14269
|
+
if (globallyInstalled && !installed) {
|
|
14251
14270
|
R2.error("Could not determine installed version.");
|
|
14252
14271
|
process.exit(1);
|
|
14253
14272
|
}
|
|
@@ -14262,21 +14281,28 @@ async function update(args) {
|
|
|
14262
14281
|
process.exit(1);
|
|
14263
14282
|
}
|
|
14264
14283
|
spinner.stop("Version check complete.");
|
|
14265
|
-
|
|
14284
|
+
if (installed) {
|
|
14285
|
+
R2.info(`Installed: ${import_picocolors3.default.cyan(`v${installed}`)}`);
|
|
14286
|
+
} else {
|
|
14287
|
+
R2.warn(`${PACKAGE_NAME} is not installed globally.`);
|
|
14288
|
+
}
|
|
14266
14289
|
R2.info(`Latest: ${import_picocolors3.default.cyan(`v${latest}`)}`);
|
|
14267
|
-
|
|
14268
|
-
|
|
14290
|
+
const needsInstall = !installed || isNewerVersion(latest, installed);
|
|
14291
|
+
if (needsInstall) {
|
|
14292
|
+
if (installed) {
|
|
14293
|
+
R2.warn(`Update available: ${import_picocolors3.default.yellow(`v${installed}`)} -> ${import_picocolors3.default.green(`v${latest}`)}`);
|
|
14294
|
+
}
|
|
14269
14295
|
const pm = detectPackageManager();
|
|
14270
14296
|
const cmd = globalInstallCommand(pm, `${PACKAGE_NAME}@${latest}`);
|
|
14271
14297
|
const cmdStr = formatShellCommand(cmd);
|
|
14272
14298
|
if (!nonInteractive) {
|
|
14273
14299
|
R2.message(import_picocolors3.default.dim(" Press y for yes, n for no, enter to confirm."));
|
|
14274
14300
|
const proceed = await Rt({
|
|
14275
|
-
message: `Update to v${latest}?`,
|
|
14301
|
+
message: installed ? `Update to v${latest}?` : `Install ${PACKAGE_NAME}@${latest} globally?`,
|
|
14276
14302
|
initialValue: true
|
|
14277
14303
|
});
|
|
14278
14304
|
if (Ct(proceed) || !proceed) {
|
|
14279
|
-
Nt("Update cancelled.");
|
|
14305
|
+
Nt(installed ? "Update cancelled." : "Install cancelled.");
|
|
14280
14306
|
process.exit(0);
|
|
14281
14307
|
}
|
|
14282
14308
|
}
|
|
@@ -14288,7 +14314,7 @@ async function update(args) {
|
|
|
14288
14314
|
env: { ...process.env, ARGENT_SKIP_POSTINSTALL: "1" }
|
|
14289
14315
|
});
|
|
14290
14316
|
} catch (err) {
|
|
14291
|
-
R2.error(
|
|
14317
|
+
R2.error(`${installed ? "Update" : "Install"} failed: ${err}`);
|
|
14292
14318
|
process.exit(1);
|
|
14293
14319
|
}
|
|
14294
14320
|
} else {
|
|
@@ -14684,6 +14710,7 @@ export {
|
|
|
14684
14710
|
MCP_BINARY_NAME,
|
|
14685
14711
|
MCP_SERVER_KEY,
|
|
14686
14712
|
PACKAGE_NAME,
|
|
14713
|
+
getGloballyInstalledVersion,
|
|
14687
14714
|
getInstalledVersion,
|
|
14688
14715
|
init,
|
|
14689
14716
|
uninstall,
|
package/dist/tool-server.cjs
CHANGED
|
@@ -41850,7 +41850,7 @@ var import_node_https = __toESM(require("node:https"));
|
|
|
41850
41850
|
var import_semver = __toESM(require_semver2());
|
|
41851
41851
|
|
|
41852
41852
|
// ../tool-server/package.json
|
|
41853
|
-
var version = "0.6.
|
|
41853
|
+
var version = "0.6.1";
|
|
41854
41854
|
|
|
41855
41855
|
// ../tool-server/src/utils/update-checker.ts
|
|
41856
41856
|
var PACKAGE_NAME = "@swmansion/argent";
|
|
@@ -45373,7 +45373,7 @@ var gestureTapTool = {
|
|
|
45373
45373
|
Sends a Down event followed by an Up event at the same point.
|
|
45374
45374
|
Use when you need to tap a button, link, or any tappable element on the simulator screen.
|
|
45375
45375
|
Returns { tapped: true, timestampMs }. Fails if the simulator server is not running for the given UDID.
|
|
45376
|
-
Before tapping, determine the correct coordinates by using discovery tools: describe, native-describe-screen, debugger-component-tree. More information in \`simulator-interact\` skill`,
|
|
45376
|
+
Before tapping, determine the correct coordinates by using discovery tools: describe, native-describe-screen, debugger-component-tree. More information in \`argent-simulator-interact\` skill`,
|
|
45377
45377
|
alwaysLoad: true,
|
|
45378
45378
|
searchHint: "tap press button element simulator touch down up",
|
|
45379
45379
|
zodSchema: zodSchema15,
|
|
@@ -47005,7 +47005,7 @@ full-screen transparent wrappers, and implementation-detail components are prune
|
|
|
47005
47005
|
Each visible component is listed with its name, text content, and normalized
|
|
47006
47006
|
tap coordinates in [0,1] space (fractions of the screen, not pixels\u2014same space as tap/swipe/gesture and simulator-server touch).
|
|
47007
47007
|
|
|
47008
|
-
This is the preferred element discovery tool for React Native apps. More information in react-native-app-workflow skill.
|
|
47008
|
+
This is the preferred element discovery tool for React Native apps. More information in argent-react-native-app-workflow skill.
|
|
47009
47009
|
|
|
47010
47010
|
Workflow:
|
|
47011
47011
|
1. Call this tool to get the component tree.
|
|
@@ -50440,11 +50440,55 @@ Fails if the React DevTools hook is not present or no fiber roots have been comm
|
|
|
50440
50440
|
};
|
|
50441
50441
|
|
|
50442
50442
|
// ../tool-server/src/tools/profiler/ios-profiler/ios-profiler-start.ts
|
|
50443
|
-
var
|
|
50443
|
+
var import_child_process2 = require("child_process");
|
|
50444
50444
|
var path6 = __toESM(require("path"));
|
|
50445
50445
|
|
|
50446
|
+
// ../tool-server/src/utils/ios-profiler/lifecycle.ts
|
|
50447
|
+
function waitForChildExit(child, ms) {
|
|
50448
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
50449
|
+
return Promise.resolve(true);
|
|
50450
|
+
}
|
|
50451
|
+
return new Promise((resolve2) => {
|
|
50452
|
+
const onExit = () => {
|
|
50453
|
+
clearTimeout(timer);
|
|
50454
|
+
resolve2(true);
|
|
50455
|
+
};
|
|
50456
|
+
const timer = setTimeout(() => {
|
|
50457
|
+
child.removeListener("exit", onExit);
|
|
50458
|
+
resolve2(false);
|
|
50459
|
+
}, ms);
|
|
50460
|
+
child.once("exit", onExit);
|
|
50461
|
+
});
|
|
50462
|
+
}
|
|
50463
|
+
async function shutdownChild(child, t) {
|
|
50464
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
50465
|
+
return { clean: true, signalUsed: "SIGINT" };
|
|
50466
|
+
}
|
|
50467
|
+
try {
|
|
50468
|
+
child.kill("SIGINT");
|
|
50469
|
+
} catch {
|
|
50470
|
+
}
|
|
50471
|
+
if (await waitForChildExit(child, t.graceMs)) {
|
|
50472
|
+
return { clean: true, signalUsed: "SIGINT" };
|
|
50473
|
+
}
|
|
50474
|
+
try {
|
|
50475
|
+
child.kill("SIGTERM");
|
|
50476
|
+
} catch {
|
|
50477
|
+
}
|
|
50478
|
+
if (await waitForChildExit(child, t.termMs)) {
|
|
50479
|
+
return { clean: false, signalUsed: "SIGTERM" };
|
|
50480
|
+
}
|
|
50481
|
+
try {
|
|
50482
|
+
child.kill("SIGKILL");
|
|
50483
|
+
} catch {
|
|
50484
|
+
}
|
|
50485
|
+
await waitForChildExit(child, t.killMs);
|
|
50486
|
+
return { clean: false, signalUsed: "SIGKILL" };
|
|
50487
|
+
}
|
|
50488
|
+
|
|
50446
50489
|
// ../tool-server/src/blueprints/ios-profiler-session.ts
|
|
50447
50490
|
var IOS_PROFILER_SESSION_NAMESPACE = "IosProfilerSession";
|
|
50491
|
+
var DISPOSE_REAP_MS = 1e3;
|
|
50448
50492
|
var iosInstrumentsSessionBlueprint = {
|
|
50449
50493
|
namespace: IOS_PROFILER_SESSION_NAMESPACE,
|
|
50450
50494
|
getURN(deviceId) {
|
|
@@ -50455,12 +50499,16 @@ var iosInstrumentsSessionBlueprint = {
|
|
|
50455
50499
|
deviceId: _payload,
|
|
50456
50500
|
appProcess: null,
|
|
50457
50501
|
xctracePid: null,
|
|
50502
|
+
xctraceProcess: null,
|
|
50458
50503
|
traceFile: null,
|
|
50459
50504
|
exportedFiles: null,
|
|
50460
50505
|
profilingActive: false,
|
|
50461
50506
|
wallClockStartMs: null,
|
|
50462
50507
|
parsedData: null,
|
|
50463
|
-
recordingTimeout: null
|
|
50508
|
+
recordingTimeout: null,
|
|
50509
|
+
recordingTimedOut: false,
|
|
50510
|
+
recordingExitedUnexpectedly: false,
|
|
50511
|
+
lastExitInfo: null
|
|
50464
50512
|
};
|
|
50465
50513
|
const events = new TypedEventEmitter();
|
|
50466
50514
|
return {
|
|
@@ -50470,12 +50518,16 @@ var iosInstrumentsSessionBlueprint = {
|
|
|
50470
50518
|
clearTimeout(state2.recordingTimeout);
|
|
50471
50519
|
state2.recordingTimeout = null;
|
|
50472
50520
|
}
|
|
50473
|
-
|
|
50521
|
+
const child = state2.xctraceProcess;
|
|
50522
|
+
if (state2.profilingActive && child) {
|
|
50474
50523
|
try {
|
|
50475
|
-
|
|
50524
|
+
child.kill("SIGKILL");
|
|
50476
50525
|
} catch {
|
|
50477
50526
|
}
|
|
50527
|
+
await waitForChildExit(child, DISPOSE_REAP_MS);
|
|
50478
50528
|
state2.profilingActive = false;
|
|
50529
|
+
state2.xctracePid = null;
|
|
50530
|
+
state2.xctraceProcess = null;
|
|
50479
50531
|
}
|
|
50480
50532
|
},
|
|
50481
50533
|
events
|
|
@@ -50483,8 +50535,121 @@ var iosInstrumentsSessionBlueprint = {
|
|
|
50483
50535
|
}
|
|
50484
50536
|
};
|
|
50485
50537
|
|
|
50538
|
+
// ../tool-server/src/utils/ios-profiler/notify.ts
|
|
50539
|
+
var import_child_process = require("child_process");
|
|
50540
|
+
var NOTIFYUTIL_PATH = "/usr/bin/notifyutil";
|
|
50541
|
+
var REGISTRATION_DELAY_MS = 300;
|
|
50542
|
+
function listenForDarwinNotification(name) {
|
|
50543
|
+
const proc = (0, import_child_process.spawn)(NOTIFYUTIL_PATH, ["-v", "-1", name]);
|
|
50544
|
+
let firedResolve = () => {
|
|
50545
|
+
};
|
|
50546
|
+
let fired = false;
|
|
50547
|
+
const fired$ = new Promise((r) => {
|
|
50548
|
+
firedResolve = r;
|
|
50549
|
+
});
|
|
50550
|
+
let readyResolve = () => {
|
|
50551
|
+
};
|
|
50552
|
+
let readyReject = () => {
|
|
50553
|
+
};
|
|
50554
|
+
let readySettled = false;
|
|
50555
|
+
const ready = new Promise((res, rej) => {
|
|
50556
|
+
readyResolve = () => {
|
|
50557
|
+
if (readySettled) return;
|
|
50558
|
+
readySettled = true;
|
|
50559
|
+
res();
|
|
50560
|
+
};
|
|
50561
|
+
readyReject = (e) => {
|
|
50562
|
+
if (readySettled) return;
|
|
50563
|
+
readySettled = true;
|
|
50564
|
+
rej(e);
|
|
50565
|
+
};
|
|
50566
|
+
});
|
|
50567
|
+
const registrationTimer = setTimeout(readyResolve, REGISTRATION_DELAY_MS);
|
|
50568
|
+
proc.on("exit", (code) => {
|
|
50569
|
+
if (!fired && code === 0) {
|
|
50570
|
+
fired = true;
|
|
50571
|
+
firedResolve();
|
|
50572
|
+
}
|
|
50573
|
+
});
|
|
50574
|
+
proc.on("error", (err) => {
|
|
50575
|
+
clearTimeout(registrationTimer);
|
|
50576
|
+
readyReject(err);
|
|
50577
|
+
});
|
|
50578
|
+
return {
|
|
50579
|
+
ready,
|
|
50580
|
+
fired: fired$,
|
|
50581
|
+
cancel: () => {
|
|
50582
|
+
clearTimeout(registrationTimer);
|
|
50583
|
+
try {
|
|
50584
|
+
proc.kill("SIGTERM");
|
|
50585
|
+
} catch {
|
|
50586
|
+
}
|
|
50587
|
+
}
|
|
50588
|
+
};
|
|
50589
|
+
}
|
|
50590
|
+
|
|
50591
|
+
// ../tool-server/src/utils/ios-profiler/startup.ts
|
|
50592
|
+
function waitForXctraceReady(child, { notify, timeoutMs }) {
|
|
50593
|
+
return new Promise((resolve2, reject) => {
|
|
50594
|
+
let settled = false;
|
|
50595
|
+
let stderrBuffer = "";
|
|
50596
|
+
const settle = (run) => {
|
|
50597
|
+
if (settled) return;
|
|
50598
|
+
settled = true;
|
|
50599
|
+
clearTimeout(startupTimer);
|
|
50600
|
+
if (notify) notify.cancel();
|
|
50601
|
+
run();
|
|
50602
|
+
};
|
|
50603
|
+
child.stdout?.on("data", (data) => {
|
|
50604
|
+
const text = data.toString();
|
|
50605
|
+
if (text.includes("Ctrl-C to stop") || text.includes("Starting recording")) {
|
|
50606
|
+
settle(() => resolve2({ stderrBuffer }));
|
|
50607
|
+
}
|
|
50608
|
+
});
|
|
50609
|
+
child.stderr?.on("data", (data) => {
|
|
50610
|
+
stderrBuffer += data.toString();
|
|
50611
|
+
});
|
|
50612
|
+
child.on("exit", (code, signal) => {
|
|
50613
|
+
settle(
|
|
50614
|
+
() => reject(
|
|
50615
|
+
new Error(
|
|
50616
|
+
`xctrace record exited before recording started (code=${code}, signal=${signal}). stderr: ${stderrBuffer.trim() || "<empty>"}`
|
|
50617
|
+
)
|
|
50618
|
+
)
|
|
50619
|
+
);
|
|
50620
|
+
});
|
|
50621
|
+
child.on("error", (err) => {
|
|
50622
|
+
settle(() => reject(new Error(`Failed to start xctrace: ${err.message}`)));
|
|
50623
|
+
});
|
|
50624
|
+
const startupTimer = setTimeout(() => {
|
|
50625
|
+
settle(() => {
|
|
50626
|
+
try {
|
|
50627
|
+
child.kill("SIGKILL");
|
|
50628
|
+
} catch {
|
|
50629
|
+
}
|
|
50630
|
+
reject(
|
|
50631
|
+
new Error(
|
|
50632
|
+
`xctrace record did not start within ${timeoutMs} ms. Last stderr: ${stderrBuffer.trim() || "<empty>"}`
|
|
50633
|
+
)
|
|
50634
|
+
);
|
|
50635
|
+
});
|
|
50636
|
+
}, timeoutMs);
|
|
50637
|
+
if (notify) {
|
|
50638
|
+
notify.fired.then(() => settle(() => resolve2({ stderrBuffer }))).catch(() => {
|
|
50639
|
+
});
|
|
50640
|
+
}
|
|
50641
|
+
});
|
|
50642
|
+
}
|
|
50643
|
+
|
|
50486
50644
|
// ../tool-server/src/tools/profiler/ios-profiler/ios-profiler-start.ts
|
|
50487
50645
|
var DEFAULT_TEMPLATE_PATH = path6.resolve(__dirname, "Argent.tracetemplate");
|
|
50646
|
+
var STARTUP_TIMEOUT_MS = 1e4;
|
|
50647
|
+
var DETECT_RUNNING_APP_TIMEOUT_MS = 1e4;
|
|
50648
|
+
var NOTIFY_REGISTER_TIMEOUT_MS = 2e3;
|
|
50649
|
+
var RECORDING_CAP_MS = 10 * 60 * 1e3;
|
|
50650
|
+
var MAX_START_ATTEMPTS = 2;
|
|
50651
|
+
var RETRY_DELAY_MS = 1200;
|
|
50652
|
+
var COLD_START_SIGNATURE = "Cannot find process matching name:";
|
|
50488
50653
|
var zodSchema42 = external_exports.object({
|
|
50489
50654
|
device_id: external_exports.string().describe("iOS Simulator or device UDID"),
|
|
50490
50655
|
app_process: external_exports.string().optional().describe(
|
|
@@ -50493,9 +50658,18 @@ var zodSchema42 = external_exports.object({
|
|
|
50493
50658
|
template_path: external_exports.string().optional().describe("Path to an Instruments .tracetemplate file (defaults to bundled Argent template)")
|
|
50494
50659
|
});
|
|
50495
50660
|
function detectRunningApp(udid) {
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50661
|
+
let launchctlOutput;
|
|
50662
|
+
try {
|
|
50663
|
+
launchctlOutput = (0, import_child_process2.execSync)(`xcrun simctl spawn ${udid} launchctl list`, {
|
|
50664
|
+
encoding: "utf-8",
|
|
50665
|
+
timeout: DETECT_RUNNING_APP_TIMEOUT_MS
|
|
50666
|
+
});
|
|
50667
|
+
} catch (err) {
|
|
50668
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50669
|
+
throw new Error(
|
|
50670
|
+
`Failed to enumerate running processes on simulator ${udid} within ${DETECT_RUNNING_APP_TIMEOUT_MS} ms. Verify the simulator is booted and responsive, then retry. Underlying error: ${msg}`
|
|
50671
|
+
);
|
|
50672
|
+
}
|
|
50499
50673
|
const runningBundleIds = /* @__PURE__ */ new Set();
|
|
50500
50674
|
for (const line of launchctlOutput.split("\n")) {
|
|
50501
50675
|
const match = line.match(/UIKitApplication:([^\[]+)/);
|
|
@@ -50508,9 +50682,18 @@ function detectRunningApp(udid) {
|
|
|
50508
50682
|
"No running apps detected on the simulator. Launch the app first using `launch-app`, then retry."
|
|
50509
50683
|
);
|
|
50510
50684
|
}
|
|
50511
|
-
|
|
50512
|
-
|
|
50513
|
-
|
|
50685
|
+
let listAppsOutput;
|
|
50686
|
+
try {
|
|
50687
|
+
listAppsOutput = (0, import_child_process2.execSync)(`xcrun simctl listapps ${udid} | plutil -convert json -o - -`, {
|
|
50688
|
+
encoding: "utf-8",
|
|
50689
|
+
timeout: DETECT_RUNNING_APP_TIMEOUT_MS
|
|
50690
|
+
});
|
|
50691
|
+
} catch (err) {
|
|
50692
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50693
|
+
throw new Error(
|
|
50694
|
+
`Failed to list installed apps on simulator ${udid} within ${DETECT_RUNNING_APP_TIMEOUT_MS} ms. Verify the simulator is booted and responsive, then retry. Underlying error: ${msg}`
|
|
50695
|
+
);
|
|
50696
|
+
}
|
|
50514
50697
|
const installedApps = JSON.parse(listAppsOutput);
|
|
50515
50698
|
const runningUserApps = [];
|
|
50516
50699
|
for (const [, appInfo] of Object.entries(installedApps)) {
|
|
@@ -50535,6 +50718,50 @@ Specify \`app_process\` with the CFBundleExecutable of the app you want to profi
|
|
|
50535
50718
|
}
|
|
50536
50719
|
return runningUserApps[0].CFBundleExecutable;
|
|
50537
50720
|
}
|
|
50721
|
+
async function registerStartupNotify(name) {
|
|
50722
|
+
let handle;
|
|
50723
|
+
try {
|
|
50724
|
+
handle = listenForDarwinNotification(name);
|
|
50725
|
+
} catch (err) {
|
|
50726
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50727
|
+
process.stderr.write(
|
|
50728
|
+
`[ios-profiler] failed to spawn notifyutil (${msg}); falling back to stdout substring match.
|
|
50729
|
+
`
|
|
50730
|
+
);
|
|
50731
|
+
return null;
|
|
50732
|
+
}
|
|
50733
|
+
const ready = await Promise.race([
|
|
50734
|
+
handle.ready.then(() => true),
|
|
50735
|
+
new Promise((r) => setTimeout(() => r(false), NOTIFY_REGISTER_TIMEOUT_MS))
|
|
50736
|
+
]);
|
|
50737
|
+
if (ready) return handle;
|
|
50738
|
+
handle.cancel();
|
|
50739
|
+
process.stderr.write(
|
|
50740
|
+
`[ios-profiler] notifyutil did not register within ${NOTIFY_REGISTER_TIMEOUT_MS} ms; falling back to stdout substring match.
|
|
50741
|
+
`
|
|
50742
|
+
);
|
|
50743
|
+
return null;
|
|
50744
|
+
}
|
|
50745
|
+
function resetStartState(api) {
|
|
50746
|
+
api.xctracePid = null;
|
|
50747
|
+
api.xctraceProcess = null;
|
|
50748
|
+
api.traceFile = null;
|
|
50749
|
+
api.appProcess = null;
|
|
50750
|
+
}
|
|
50751
|
+
function handleXctraceExit(api, code, signal) {
|
|
50752
|
+
if (!api.profilingActive) return;
|
|
50753
|
+
if (api.recordingTimeout) {
|
|
50754
|
+
clearTimeout(api.recordingTimeout);
|
|
50755
|
+
api.recordingTimeout = null;
|
|
50756
|
+
}
|
|
50757
|
+
api.xctracePid = null;
|
|
50758
|
+
api.xctraceProcess = null;
|
|
50759
|
+
api.profilingActive = false;
|
|
50760
|
+
if (!api.recordingTimedOut) {
|
|
50761
|
+
api.recordingExitedUnexpectedly = true;
|
|
50762
|
+
}
|
|
50763
|
+
api.lastExitInfo = { code, signal };
|
|
50764
|
+
}
|
|
50538
50765
|
var iosInstrumentsStartTool = {
|
|
50539
50766
|
id: "ios-profiler-start",
|
|
50540
50767
|
description: `Start iOS Instruments profiling via xctrace on a booted simulator or connected device.
|
|
@@ -50557,10 +50784,15 @@ Fails if no app is running on the simulator or xctrace cannot attach to the proc
|
|
|
50557
50784
|
const debugDir = await getDebugDir();
|
|
50558
50785
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, (m) => m === "T" ? "-" : "").slice(0, 15);
|
|
50559
50786
|
const outputFile = path6.join(debugDir, `ios-profiler-${timestamp}.trace`);
|
|
50560
|
-
api.
|
|
50561
|
-
api.
|
|
50562
|
-
|
|
50563
|
-
|
|
50787
|
+
api.recordingTimedOut = false;
|
|
50788
|
+
api.recordingExitedUnexpectedly = false;
|
|
50789
|
+
api.lastExitInfo = null;
|
|
50790
|
+
const attemptStart = async () => {
|
|
50791
|
+
api.appProcess = appProcess;
|
|
50792
|
+
api.traceFile = outputFile;
|
|
50793
|
+
const notifyName = `com.argent.ios-profiler.started.${process.pid}.${Date.now()}`;
|
|
50794
|
+
const notify = await registerStartupNotify(notifyName);
|
|
50795
|
+
const xctraceArgs = [
|
|
50564
50796
|
"record",
|
|
50565
50797
|
"--template",
|
|
50566
50798
|
templatePath,
|
|
@@ -50569,58 +50801,92 @@ Fails if no app is running on the simulator or xctrace cannot attach to the proc
|
|
|
50569
50801
|
"--attach",
|
|
50570
50802
|
appProcess,
|
|
50571
50803
|
"--output",
|
|
50572
|
-
outputFile
|
|
50573
|
-
|
|
50574
|
-
|
|
50575
|
-
|
|
50576
|
-
|
|
50577
|
-
|
|
50578
|
-
|
|
50579
|
-
|
|
50580
|
-
if (api.xctracePid) {
|
|
50581
|
-
api.recordingTimeout = setTimeout(
|
|
50582
|
-
() => {
|
|
50583
|
-
xctraceProcess.kill("SIGINT");
|
|
50584
|
-
api.profilingActive = false;
|
|
50585
|
-
api.xctracePid = null;
|
|
50586
|
-
api.recordingTimeout = null;
|
|
50587
|
-
},
|
|
50588
|
-
10 * 60 * 1e3
|
|
50589
|
-
);
|
|
50590
|
-
resolve2({
|
|
50591
|
-
status: "recording",
|
|
50592
|
-
pid: api.xctracePid,
|
|
50593
|
-
traceFile: outputFile
|
|
50594
|
-
});
|
|
50595
|
-
}
|
|
50596
|
-
}
|
|
50804
|
+
outputFile,
|
|
50805
|
+
"--no-prompt"
|
|
50806
|
+
];
|
|
50807
|
+
if (notify) {
|
|
50808
|
+
xctraceArgs.push("--notify-tracing-started", notifyName);
|
|
50809
|
+
}
|
|
50810
|
+
const xctraceProcess2 = (0, import_child_process2.spawn)("xctrace", xctraceArgs, {
|
|
50811
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
50597
50812
|
});
|
|
50598
|
-
|
|
50599
|
-
|
|
50600
|
-
|
|
50601
|
-
|
|
50602
|
-
|
|
50603
|
-
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50813
|
+
api.xctracePid = xctraceProcess2.pid ?? null;
|
|
50814
|
+
api.xctraceProcess = xctraceProcess2;
|
|
50815
|
+
try {
|
|
50816
|
+
await waitForXctraceReady(xctraceProcess2, { notify, timeoutMs: STARTUP_TIMEOUT_MS });
|
|
50817
|
+
} catch (err) {
|
|
50818
|
+
resetStartState(api);
|
|
50819
|
+
throw err;
|
|
50820
|
+
}
|
|
50821
|
+
if (!xctraceProcess2.pid) {
|
|
50822
|
+
try {
|
|
50823
|
+
xctraceProcess2.kill("SIGKILL");
|
|
50824
|
+
} catch {
|
|
50607
50825
|
}
|
|
50608
|
-
|
|
50609
|
-
|
|
50610
|
-
|
|
50611
|
-
|
|
50612
|
-
|
|
50613
|
-
|
|
50826
|
+
resetStartState(api);
|
|
50827
|
+
throw new Error("xctrace process has no pid; cannot resolve start.");
|
|
50828
|
+
}
|
|
50829
|
+
return { child: xctraceProcess2, pid: xctraceProcess2.pid };
|
|
50830
|
+
};
|
|
50831
|
+
const startMs = Date.now();
|
|
50832
|
+
const startWithRetry = async () => {
|
|
50833
|
+
for (let attempt = 1; attempt <= MAX_START_ATTEMPTS; attempt++) {
|
|
50834
|
+
try {
|
|
50835
|
+
return await attemptStart();
|
|
50836
|
+
} catch (err) {
|
|
50837
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50838
|
+
const isColdStart = msg.includes(COLD_START_SIGNATURE);
|
|
50839
|
+
if (!isColdStart) throw err;
|
|
50840
|
+
if (attempt >= MAX_START_ATTEMPTS) break;
|
|
50841
|
+
process.stderr.write(
|
|
50842
|
+
`[ios-profiler] xctrace could not find "${appProcess}" on attempt ${attempt}/${MAX_START_ATTEMPTS}; waiting ${RETRY_DELAY_MS} ms for cold-start to settle, then retrying.
|
|
50843
|
+
`
|
|
50844
|
+
);
|
|
50845
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
50614
50846
|
}
|
|
50615
|
-
|
|
50616
|
-
|
|
50617
|
-
|
|
50847
|
+
}
|
|
50848
|
+
const totalMs = Date.now() - startMs;
|
|
50849
|
+
throw new Error(
|
|
50850
|
+
`xctrace could not find process "${appProcess}" after ${MAX_START_ATTEMPTS} attempts within ${totalMs} ms. The app appears to be cold-launching \u2014 its bundle is registered with launchd, but xctrace's process resolver hasn't seen it yet. Wait 1\u20132 seconds for the app to finish launching and retry. If the wrong app is being detected, pass app_process explicitly with the CFBundleExecutable.`
|
|
50851
|
+
);
|
|
50852
|
+
};
|
|
50853
|
+
const { child: xctraceProcess, pid: xctracePid } = await startWithRetry();
|
|
50854
|
+
api.profilingActive = true;
|
|
50855
|
+
api.wallClockStartMs = Date.now();
|
|
50856
|
+
api.recordingTimeout = setTimeout(() => {
|
|
50857
|
+
try {
|
|
50858
|
+
xctraceProcess.kill("SIGINT");
|
|
50859
|
+
} catch {
|
|
50860
|
+
}
|
|
50861
|
+
api.profilingActive = false;
|
|
50862
|
+
api.xctracePid = null;
|
|
50863
|
+
api.xctraceProcess = null;
|
|
50864
|
+
api.recordingTimeout = null;
|
|
50865
|
+
api.recordingTimedOut = true;
|
|
50866
|
+
}, RECORDING_CAP_MS);
|
|
50867
|
+
xctraceProcess.on("exit", (code, signal) => handleXctraceExit(api, code, signal));
|
|
50868
|
+
return {
|
|
50869
|
+
status: "recording",
|
|
50870
|
+
pid: xctracePid,
|
|
50871
|
+
traceFile: outputFile
|
|
50872
|
+
};
|
|
50618
50873
|
}
|
|
50619
50874
|
};
|
|
50620
50875
|
|
|
50621
50876
|
// ../tool-server/src/utils/ios-profiler/export.ts
|
|
50622
|
-
var import_child_process2 = require("child_process");
|
|
50623
50877
|
var path7 = __toESM(require("path"));
|
|
50878
|
+
|
|
50879
|
+
// ../tool-server/src/utils/ios-profiler/run-with-timeout.ts
|
|
50880
|
+
var import_child_process3 = require("child_process");
|
|
50881
|
+
var DEFAULT_EXEC_TIMEOUT_MS = 6e4;
|
|
50882
|
+
function execSyncWithTimeout(command, options = {}) {
|
|
50883
|
+
return (0, import_child_process3.execSync)(command, {
|
|
50884
|
+
timeout: DEFAULT_EXEC_TIMEOUT_MS,
|
|
50885
|
+
...options
|
|
50886
|
+
});
|
|
50887
|
+
}
|
|
50888
|
+
|
|
50889
|
+
// ../tool-server/src/utils/ios-profiler/export.ts
|
|
50624
50890
|
var CPU_SCHEMA_CANDIDATES = ["time-profile", "cpu-profile", "time-sample"];
|
|
50625
50891
|
var EXPORTS = {
|
|
50626
50892
|
cpu: {
|
|
@@ -50638,7 +50904,7 @@ var EXPORTS = {
|
|
|
50638
50904
|
};
|
|
50639
50905
|
function getXctraceVersion() {
|
|
50640
50906
|
try {
|
|
50641
|
-
const output = (
|
|
50907
|
+
const output = execSyncWithTimeout("xctrace version 2>&1 || true", {
|
|
50642
50908
|
encoding: "utf-8"
|
|
50643
50909
|
});
|
|
50644
50910
|
const match = output.match(/(\d+)\./);
|
|
@@ -50649,7 +50915,7 @@ function getXctraceVersion() {
|
|
|
50649
50915
|
}
|
|
50650
50916
|
function discoverTraceSchemas(traceFile) {
|
|
50651
50917
|
try {
|
|
50652
|
-
const toc = (
|
|
50918
|
+
const toc = execSyncWithTimeout(`xctrace export --input "${traceFile}" --toc`, {
|
|
50653
50919
|
encoding: "utf-8",
|
|
50654
50920
|
stdio: ["pipe", "pipe", "pipe"]
|
|
50655
50921
|
});
|
|
@@ -50684,9 +50950,12 @@ function tryCpuExportFallback(traceFile, outPath, diagnostics) {
|
|
|
50684
50950
|
for (const candidate of CPU_SCHEMA_CANDIDATES) {
|
|
50685
50951
|
const xpath = `/trace-toc/run[@number="1"]/data/table[@schema="${candidate}"]`;
|
|
50686
50952
|
try {
|
|
50687
|
-
(
|
|
50688
|
-
|
|
50689
|
-
|
|
50953
|
+
execSyncWithTimeout(
|
|
50954
|
+
`xctrace export --input "${traceFile}" --output "${outPath}" --xpath '${xpath}'`,
|
|
50955
|
+
{
|
|
50956
|
+
stdio: "pipe"
|
|
50957
|
+
}
|
|
50958
|
+
);
|
|
50690
50959
|
diagnostics.cpuSchemaUsed = candidate;
|
|
50691
50960
|
return true;
|
|
50692
50961
|
} catch {
|
|
@@ -50711,7 +50980,7 @@ function exportIosTraceData(traceFile) {
|
|
|
50711
50980
|
const resolvedXpath = resolveCpuXpath(traceFile, diagnostics);
|
|
50712
50981
|
if (resolvedXpath) {
|
|
50713
50982
|
try {
|
|
50714
|
-
(
|
|
50983
|
+
execSyncWithTimeout(
|
|
50715
50984
|
`xctrace export --input "${traceFile}" --output "${outPath}" --xpath '${resolvedXpath}'`,
|
|
50716
50985
|
{ stdio: "pipe" }
|
|
50717
50986
|
);
|
|
@@ -50734,7 +51003,7 @@ function exportIosTraceData(traceFile) {
|
|
|
50734
51003
|
const xcVersion = getXctraceVersion();
|
|
50735
51004
|
const halFlag = xcVersion >= 15 ? " --hal" : "";
|
|
50736
51005
|
try {
|
|
50737
|
-
(
|
|
51006
|
+
execSyncWithTimeout(
|
|
50738
51007
|
`xctrace export --input "${traceFile}" --output "${outPath}" --xpath '${config.xpath}'${halFlag}`,
|
|
50739
51008
|
{ stdio: "pipe" }
|
|
50740
51009
|
);
|
|
@@ -50742,7 +51011,7 @@ function exportIosTraceData(traceFile) {
|
|
|
50742
51011
|
} catch {
|
|
50743
51012
|
if (halFlag) {
|
|
50744
51013
|
try {
|
|
50745
|
-
(
|
|
51014
|
+
execSyncWithTimeout(
|
|
50746
51015
|
`xctrace export --input "${traceFile}" --output "${outPath}" --xpath '${config.xpath}'`,
|
|
50747
51016
|
{ stdio: "pipe" }
|
|
50748
51017
|
);
|
|
@@ -50759,7 +51028,7 @@ function exportIosTraceData(traceFile) {
|
|
|
50759
51028
|
continue;
|
|
50760
51029
|
}
|
|
50761
51030
|
try {
|
|
50762
|
-
(
|
|
51031
|
+
execSyncWithTimeout(
|
|
50763
51032
|
`xctrace export --input "${traceFile}" --output "${outPath}" --xpath '${config.xpath}'`,
|
|
50764
51033
|
{ stdio: "pipe" }
|
|
50765
51034
|
);
|
|
@@ -50774,6 +51043,9 @@ function exportIosTraceData(traceFile) {
|
|
|
50774
51043
|
}
|
|
50775
51044
|
|
|
50776
51045
|
// ../tool-server/src/tools/profiler/ios-profiler/ios-profiler-stop.ts
|
|
51046
|
+
var STOP_GRACE_MS = 3e4;
|
|
51047
|
+
var STOP_TERM_MS = 5e3;
|
|
51048
|
+
var STOP_KILL_MS = 5e3;
|
|
50777
51049
|
var zodSchema43 = external_exports.object({
|
|
50778
51050
|
device_id: external_exports.string().describe("iOS Simulator or device UDID")
|
|
50779
51051
|
});
|
|
@@ -50791,34 +51063,52 @@ Fails if no active ios-profiler-start session exists for the given device_id.`,
|
|
|
50791
51063
|
}),
|
|
50792
51064
|
async execute(services) {
|
|
50793
51065
|
const api = services.session;
|
|
50794
|
-
if (
|
|
51066
|
+
if ((api.recordingTimedOut || api.recordingExitedUnexpectedly) && api.traceFile) {
|
|
51067
|
+
const traceFile = api.traceFile;
|
|
51068
|
+
const wasTimeout = api.recordingTimedOut;
|
|
51069
|
+
const exitInfo = api.lastExitInfo;
|
|
51070
|
+
api.recordingTimedOut = false;
|
|
51071
|
+
api.recordingExitedUnexpectedly = false;
|
|
51072
|
+
api.lastExitInfo = null;
|
|
51073
|
+
const { files: exportedFiles2, diagnostics: diagnostics2 } = exportIosTraceData(traceFile);
|
|
51074
|
+
api.exportedFiles = exportedFiles2;
|
|
51075
|
+
const warning2 = wasTimeout ? "Recording timed out at 10 min cap; exported the partial trace. Call ios-profiler-start again for a fresh recording." : `xctrace exited before stop was called (code=${exitInfo?.code ?? "?"}, signal=${exitInfo?.signal ?? "?"}); exported the partial trace. Common causes: attached app terminated, simulator daemon restart. Call ios-profiler-start again for a fresh recording.`;
|
|
51076
|
+
process.stderr.write(`[ios-profiler] ${warning2}
|
|
51077
|
+
`);
|
|
51078
|
+
return { traceFile, exportedFiles: exportedFiles2, exportDiagnostics: diagnostics2, warning: warning2 };
|
|
51079
|
+
}
|
|
51080
|
+
if (!api.profilingActive || !api.xctraceProcess || !api.traceFile) {
|
|
50795
51081
|
throw new Error("No active iOS profiling session found. Call ios-profiler-start first.");
|
|
50796
51082
|
}
|
|
50797
51083
|
if (api.recordingTimeout) {
|
|
50798
51084
|
clearTimeout(api.recordingTimeout);
|
|
50799
51085
|
api.recordingTimeout = null;
|
|
50800
51086
|
}
|
|
50801
|
-
const
|
|
50802
|
-
|
|
50803
|
-
|
|
50804
|
-
|
|
50805
|
-
try {
|
|
50806
|
-
process.kill(pidToKill, 0);
|
|
50807
|
-
} catch {
|
|
50808
|
-
clearInterval(checkInterval);
|
|
50809
|
-
resolve2();
|
|
50810
|
-
}
|
|
50811
|
-
}, 1e3);
|
|
51087
|
+
const result = await shutdownChild(api.xctraceProcess, {
|
|
51088
|
+
graceMs: STOP_GRACE_MS,
|
|
51089
|
+
termMs: STOP_TERM_MS,
|
|
51090
|
+
killMs: STOP_KILL_MS
|
|
50812
51091
|
});
|
|
51092
|
+
let warning;
|
|
51093
|
+
if (!result.clean) {
|
|
51094
|
+
warning = `xctrace did not respond to SIGINT${result.signalUsed === "SIGKILL" ? "/SIGTERM" : ""}; ${result.signalUsed} was used. Trace bundle may be incomplete.`;
|
|
51095
|
+
process.stderr.write(`[ios-profiler] ${warning}
|
|
51096
|
+
`);
|
|
51097
|
+
}
|
|
50813
51098
|
api.profilingActive = false;
|
|
50814
51099
|
api.xctracePid = null;
|
|
51100
|
+
api.xctraceProcess = null;
|
|
51101
|
+
api.recordingExitedUnexpectedly = false;
|
|
51102
|
+
api.lastExitInfo = null;
|
|
50815
51103
|
const { files: exportedFiles, diagnostics } = exportIosTraceData(api.traceFile);
|
|
50816
51104
|
api.exportedFiles = exportedFiles;
|
|
50817
|
-
|
|
51105
|
+
const stopResult = {
|
|
50818
51106
|
traceFile: api.traceFile,
|
|
50819
51107
|
exportedFiles,
|
|
50820
51108
|
exportDiagnostics: diagnostics
|
|
50821
51109
|
};
|
|
51110
|
+
if (warning) stopResult.warning = warning;
|
|
51111
|
+
return stopResult;
|
|
50822
51112
|
}
|
|
50823
51113
|
};
|
|
50824
51114
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/rules/argent.md
CHANGED
|
@@ -48,7 +48,7 @@ Before starting to interact with the app, read the `argent-simulator-interact` s
|
|
|
48
48
|
- Interaction tools (`gesture-tap`, `gesture-swipe`, `gesture-pinch`, `gesture-rotate`, `gesture-custom`, `launch-app`, etc.) return a screenshot automatically.
|
|
49
49
|
Call `screenshot` separately only for a baseline before any action or after a delay.
|
|
50
50
|
- Always open apps with `launch-app` or `open-url` — never tap home screen icons.
|
|
51
|
-
- Always use `run-sequence` when performing multiple sequential simulator actions where you don't need to observe the screen between steps. More in `simulator-interact` skill.
|
|
51
|
+
- Always use `run-sequence` when performing multiple sequential simulator actions where you don't need to observe the screen between steps. More in `argent-simulator-interact` skill.
|
|
52
52
|
- When the session ends or the user says they are done: call `stop-all-simulator-servers`.
|
|
53
53
|
If the user started Metro separately, ask whether to call `stop-metro` (specify the port if not 8081).
|
|
54
54
|
- If tools provided by mcp-server are not sufficient and action can be done using `xcrun` or other commands, use the command. Examples: changing simulator options, performing simulator action such as lock, shake, etc.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: argent-react-native-optimization
|
|
3
|
-
description: Optimizes a React Native app by profiling first to find real bottlenecks, then sweeping for mechanical issues. Entry-point for all performance work. Use when the app feels slow, user asks to optimize, fix re-renders, reduce jank, or improve startup. Delegates to react-native-profiler for measurement.
|
|
3
|
+
description: Optimizes a React Native app by profiling first to find real bottlenecks, then sweeping for mechanical issues. Entry-point for all performance work. Use when the app feels slow, user asks to optimize, fix re-renders, reduce jank, or improve startup. Delegates to argent-react-native-profiler for measurement.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Rules
|
|
7
7
|
|
|
8
8
|
- Do not apply shotgun optimizations. Measure first, define what "good enough" looks like (target metric + threshold), fix the top offender, re-measure honestly.
|
|
9
9
|
- **Quick scan** — `react-profiler-renders` for a live render count table. Identifies hot components instantly.
|
|
10
|
-
- **Deep measure** — load `react-native-profiler` skill. `react-profiler-start` → interact → `react-profiler-stop` → `react-profiler-analyze`.
|
|
10
|
+
- **Deep measure** — load `argent-react-native-profiler` skill. `react-profiler-start` → interact → `react-profiler-stop` → `react-profiler-analyze`.
|
|
11
11
|
- **Inspect** — `react-profiler-component-source` per finding. `react-profiler-fiber-tree` to trace component ancestry and render cost.
|
|
12
12
|
- **Verify correctness** - before fixing, recollect information from steps above and make a logical conclusion whether the approach is worth undertaking.
|
|
13
13
|
- **Fix** — apply one fix. Validate with `debugger-evaluate` before committing.
|
|
14
14
|
- **Re-measure** — report whether the target metric improved, regressed, or stayed flat. Check for regressions in other areas. If no net benefit or unacceptable tradeoffs, revert.
|
|
15
15
|
- **Profile for discovery, not only verification.** Use the profiler to find issues static analysis missed, not only to confirm fixes.
|
|
16
|
-
- **One fix per cycle for architectural changes.** Mechanical batch fixes (inline styles, index keys) can be grouped — re-profile once after the batch. When the measurement involves simulator interaction, record it as a flow (`create-flow` skill) before the first run so all subsequent cycles replay identical steps.
|
|
16
|
+
- **One fix per cycle for architectural changes.** Mechanical batch fixes (inline styles, index keys) can be grouped — re-profile once after the batch. When the measurement involves simulator interaction, record it as a flow (`argent-create-flow` skill) before the first run so all subsequent cycles replay identical steps.
|
|
17
17
|
- **React Compiler**: if `react-profiler-analyze` reports `reactCompilerEnabled: true`, do NOT propose `useCallback`/`useMemo`/`React.memo` unless you confirmed compiler bail-out via `react-profiler-fiber-tree` (absent `useMemoCache`).
|
|
18
18
|
- **Sub-agents**: Phases 1–2 dispatch sub-agents — one per file for lint results, one per checklist item for semantic. Sub-agents CANNOT touch the simulator - all profiling and E2E verification must happen in the main agent.
|
|
19
19
|
|
|
@@ -43,15 +43,15 @@ See [references/semantic-checklist.md](references/semantic-checklist.md) for ful
|
|
|
43
43
|
|
|
44
44
|
### Phase 3: Visual profiling
|
|
45
45
|
|
|
46
|
-
1. Load `react-native-profiler` skill, start dual profiling
|
|
46
|
+
1. Load `argent-react-native-profiler` skill, start dual profiling
|
|
47
47
|
2. Exercise key user flows (navigate screens the user specified, or all major flows)
|
|
48
48
|
3. Analyze with `react-profiler-analyze` + `ios-profiler-analyze` + `profiler-combined-report`
|
|
49
49
|
4. Cross-reference profiling results with Phase 1–2 findings
|
|
50
|
-
5. Fix highest-impact issues. Re-profile after architectural changes; batch mechanical fixes. If a recorded flow breaks after a fix (e.g., UI layout changed), follow `create-flow` skill to repair the flow rather than silently discarding it.
|
|
50
|
+
5. Fix highest-impact issues. Re-profile after architectural changes; batch mechanical fixes. If a recorded flow breaks after a fix (e.g., UI layout changed), follow `argent-create-flow` skill to repair the flow rather than silently discarding it.
|
|
51
51
|
|
|
52
52
|
### Phase 4: Verify no regressions
|
|
53
53
|
|
|
54
|
-
Navigate every screen and UI flow within scope, confirm each renders without errors. If no scope was specified, verify the entire app — cover all reachable screens via `simulator-interact`. Use `debugger-log-registry` to check for runtime errors and take screenshots to check for red/yellow error screens. Check for regressions introduced by fixes (e.g., fewer re-renders but higher CPU, or new jank in a different screen). Main agent only.
|
|
54
|
+
Navigate every screen and UI flow within scope, confirm each renders without errors. If no scope was specified, verify the entire app — cover all reachable screens via `argent-simulator-interact`. Use `debugger-log-registry` to check for runtime errors and take screenshots to check for red/yellow error screens. Check for regressions introduced by fixes (e.g., fewer re-renders but higher CPU, or new jank in a different screen). Main agent only.
|
|
55
55
|
|
|
56
56
|
## App-wide optimization
|
|
57
57
|
|