jorgex-stack 1.9.39 → 1.9.41

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/dist/cli.js CHANGED
@@ -681,9 +681,9 @@ function ensureModelMapFile() {
681
681
  import path7 from "path";
682
682
  import { existsSync as existsSync2, statSync } from "fs";
683
683
  import { execFileSync } from "child_process";
684
- function lookPath(cmd) {
684
+ function lookPath(cmd, env = process.env) {
685
685
  const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
686
- for (const dir of (process.env.PATH ?? "").split(path7.delimiter)) {
686
+ for (const dir of (env.PATH ?? "").split(path7.delimiter)) {
687
687
  if (!dir) continue;
688
688
  for (const ext of exts) {
689
689
  const candidate = path7.join(dir, cmd + ext);
@@ -830,19 +830,6 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
830
830
  });
831
831
  }
832
832
 
833
- // src/lib/git-guard.ts
834
- var DESTRUCTIVE_GIT_DENY = [
835
- "git reset*",
836
- "git clean*",
837
- "git checkout -- *",
838
- "git restore*",
839
- "git push*--force*",
840
- // posición libre: caza "git push origin main --force[-with-lease]"
841
- "git push -f*",
842
- "git switch*--discard-changes*"
843
- ];
844
- var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
845
-
846
833
  // src/lib/quality-capabilities.ts
847
834
  var QUALITY_CAPABILITY_NAMESPACE = "jorgex.quality.capabilities";
848
835
  var QUALITY_CAPABILITY_VERSION = 1;
@@ -917,6 +904,15 @@ function localQualityStatus(profile, status) {
917
904
  }
918
905
 
919
906
  // src/adapters/opencode.ts
907
+ var gitReadPrefix = "git --no-pager -c core.fsmonitor=false -c log.showSignature=false";
908
+ var gitReadCommands = [
909
+ "diff",
910
+ "diff --stat",
911
+ "diff --name-only",
912
+ "diff --cached",
913
+ "log",
914
+ "log --oneline -10"
915
+ ].map((action) => `${gitReadPrefix} ${action} --no-ext-diff --no-textconv --end-of-options`);
920
916
  function yamlString(value) {
921
917
  return JSON.stringify(value);
922
918
  }
@@ -1015,15 +1011,20 @@ var opencodeAdapter = {
1015
1011
  const tierModel = resolveAgentModel(models, agent.name, agent.tier);
1016
1012
  lines.push(`model: ${tierModel.model}`);
1017
1013
  if (tierModel.variant) lines.push(`variant: ${tierModel.variant}`);
1018
- lines.push("permission:");
1019
- lines.push(` edit: ${agent.readonly ? "deny" : "allow"}`);
1014
+ if (agent.readonly || agent.bash !== "full" || !agent.spawn) lines.push("permission:");
1015
+ if (agent.readonly) lines.push(" edit: deny");
1020
1016
  if (agent.bash === "none") lines.push(" bash: deny");
1021
- else if (agent.bash === "git-read") lines.push(' bash:\n "git diff*": allow\n "git log*": allow');
1022
- else {
1023
- const deny = DESTRUCTIVE_GIT_DENY.map((p7) => ` ${yamlString(p7)}: deny`).join("\n");
1024
- lines.push(` bash:
1025
- "*": allow
1026
- ${deny}`);
1017
+ else if (agent.bash === "git-read") {
1018
+ lines.push(' bash:\n "*": deny');
1019
+ for (const command of gitReadCommands) {
1020
+ lines.push(` ${yamlString(command)}: allow`, ` ${yamlString(`${command} *`)}: allow`);
1021
+ }
1022
+ const permission = objectValue(loadCanonicalDefaults(stackRoot())["opencode"]?.permission);
1023
+ const bash = objectValue(permission?.bash);
1024
+ if (bash === null) throw new Error("OpenCode: canonical Bash policy is required for git-read agents.");
1025
+ for (const [pattern, decision] of Object.entries(bash)) {
1026
+ if (decision === "deny") lines.push(` ${yamlString(pattern)}: deny`);
1027
+ }
1027
1028
  }
1028
1029
  if (!agent.spawn) lines.push(" task: deny");
1029
1030
  }
@@ -1033,7 +1034,11 @@ ${deny}`);
1033
1034
  content: `---
1034
1035
  ${lines.join("\n")}
1035
1036
  ---
1036
- ${agent.body}`,
1037
+ ${agent.body}${agent.bash === "git-read" ? `
1038
+
1039
+ Use only these read-only Git command prefixes; put refs and paths after --end-of-options:
1040
+ ${gitReadCommands.map((command) => `- \`${command}\``).join("\n")}
1041
+ ` : ""}`,
1037
1042
  kind: "agent"
1038
1043
  }
1039
1044
  ];
@@ -1145,7 +1150,7 @@ ${agent.body}`,
1145
1150
  if (isFreshConfig && defaults?.["permission"] !== void 0) {
1146
1151
  root["permission"] = defaults["permission"];
1147
1152
  ctx.warnings.push(
1148
- "OpenCode: fresh config enables read-anywhere via external_directory:*; edits, web egress and arbitrary bash remain approval-gated, but broad local reads can expose secrets not covered by deny rules."
1153
+ "OpenCode: fresh config allows ordinary reads, edits, web access and Bash; sensitive operations ask, while protected paths and obvious destruction are denied. Native matching is not a universal filesystem sandbox."
1149
1154
  );
1150
1155
  }
1151
1156
  const mcp = root["mcp"] ??= {};
@@ -1318,6 +1323,11 @@ ${agent.body}`,
1318
1323
  import path10 from "path";
1319
1324
  import fs7 from "fs";
1320
1325
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1326
+
1327
+ // src/lib/git-guard.ts
1328
+ var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
1329
+
1330
+ // src/adapters/claude-code.ts
1321
1331
  function yamlString2(value) {
1322
1332
  return JSON.stringify(value);
1323
1333
  }
@@ -2348,10 +2358,11 @@ function resolvePlaywrightCliState({ binPath, versionOutput }) {
2348
2358
  detectedVersion
2349
2359
  };
2350
2360
  }
2351
- function detectPlaywrightCli() {
2352
- const binPath = lookPath(PLAYWRIGHT_CLI.bin);
2361
+ function detectPlaywrightCli(env) {
2362
+ const actualEnv = env ?? process.env;
2363
+ const binPath = env === void 0 ? lookPath(PLAYWRIGHT_CLI.bin) : lookPath(PLAYWRIGHT_CLI.bin, env);
2353
2364
  if (binPath === null) {
2354
- const pnpmHome = resolvePnpmHome();
2365
+ const pnpmHome = resolvePnpmHome(actualEnv);
2355
2366
  const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
2356
2367
  const known = [path20.join(pnpmHome, name), path20.join(pnpmHome, "bin", name)].find((file) => {
2357
2368
  try {
@@ -2364,13 +2375,13 @@ function detectPlaywrightCli() {
2364
2375
  }
2365
2376
  return resolvePlaywrightCliState({
2366
2377
  binPath,
2367
- versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { NO_UPDATE_NOTIFIER: "1" }) : null
2378
+ versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { ...actualEnv, NO_UPDATE_NOTIFIER: "1" }) : null
2368
2379
  });
2369
2380
  }
2370
- function resolvePnpmBin() {
2371
- const pnpm = lookPath("pnpm");
2381
+ function resolvePnpmBin(env) {
2382
+ const pnpm = env === void 0 ? lookPath("pnpm") : lookPath("pnpm", env);
2372
2383
  if (pnpm !== null && !pnpm.toLowerCase().endsWith(".ps1")) return pnpm;
2373
- const pnpmCmd = lookPath("pnpm.cmd");
2384
+ const pnpmCmd = env === void 0 ? lookPath("pnpm.cmd") : lookPath("pnpm.cmd", env);
2374
2385
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
2375
2386
  }
2376
2387
  function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
@@ -2502,6 +2513,30 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2502
2513
  }
2503
2514
  }
2504
2515
 
2516
+ // src/lib/playwright-capability.ts
2517
+ function inspectPlaywrightCapability(options = {}) {
2518
+ const env = options.env ?? process.env;
2519
+ const cli = options.env === void 0 ? detectPlaywrightCli() : detectPlaywrightCli(env);
2520
+ const browserCache = options.env === void 0 ? isPlaywrightBrowserReady() : isPlaywrightBrowserReady(env);
2521
+ let browserVerified = false;
2522
+ if (cli.status === "current" && browserCache.status === "ready") {
2523
+ if (options.browserVerified === true) {
2524
+ browserVerified = true;
2525
+ } else {
2526
+ const pnpmBin = options.env === void 0 ? resolvePnpmBin() : resolvePnpmBin(env);
2527
+ if (pnpmBin !== null) {
2528
+ try {
2529
+ browserVerified = verifyPlaywrightBrowser(pnpmBin, env);
2530
+ } catch {
2531
+ browserVerified = false;
2532
+ }
2533
+ }
2534
+ }
2535
+ }
2536
+ const effective = cli.status === "current" && cli.binPath !== null && (options.browserVerified === true || browserCache.status === "ready") && browserVerified;
2537
+ return { cli, browserCache, browserVerified, effective };
2538
+ }
2539
+
2505
2540
  // src/lib/tool-preferences.ts
2506
2541
  import fs16 from "fs";
2507
2542
  import path21 from "path";
@@ -2762,7 +2797,7 @@ function persistConfigurationOwnershipChanges(runtime, configDir, plan) {
2762
2797
  const primaryFile = primaryModelOwnershipFile();
2763
2798
  for (const [field, owned] of primary) savePrimaryModelOwnership(primaryFile, runtime, configDir, field, owned);
2764
2799
  }
2765
- function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE, useBrowserPreferences = true) {
2800
+ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE, useBrowserPreferences = true, playwrightCapability) {
2766
2801
  const models = loadModelMap()[adapter.id];
2767
2802
  if (!models) return null;
2768
2803
  return {
@@ -2774,7 +2809,7 @@ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE,
2774
2809
  models,
2775
2810
  warnings: [],
2776
2811
  enabledMcpServers: enabledMcpServers(adapter.id, void 0, useBrowserPreferences),
2777
- playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true,
2812
+ playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true && (playwrightCapability ?? true),
2778
2813
  ownedMcpServers: ownedMcpServers(adapter.id, useBrowserPreferences),
2779
2814
  ownedPrimaryModelFields: useBrowserPreferences ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), adapter.id, configDir) : /* @__PURE__ */ new Set()
2780
2815
  };
@@ -2809,14 +2844,14 @@ function applyChanges(changes, onOwnershipWritten) {
2809
2844
  } else copyFile(action.source, action.target);
2810
2845
  }
2811
2846
  }
2812
- function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2847
+ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE, playwrightCapability) {
2813
2848
  const targets = /* @__PURE__ */ new Set();
2814
2849
  let complete = true;
2815
2850
  const warnings = [];
2816
2851
  for (const adapter of Object.values(ADAPTERS)) {
2817
2852
  const detection = adapter.detect();
2818
2853
  if (!detection.installed) continue;
2819
- const ctx = makeContext(adapter, detection.configDir, mode);
2854
+ const ctx = makeContext(adapter, detection.configDir, mode, true, playwrightCapability);
2820
2855
  if (!ctx) {
2821
2856
  complete = false;
2822
2857
  warnings.push(`${adapter.name}: limpieza de hu\xE9rfanos deshabilitada \u2014 falta contexto/model-map instalable para este runtime.`);
@@ -2873,6 +2908,10 @@ async function runInstall(opts) {
2873
2908
  });
2874
2909
  const projectPlaywrightPrompt = opts.dryRun && toolPlan?.persistEnabledOnSuccess === true;
2875
2910
  const hasFileRuntimes = opts.runtimes.length > 0;
2911
+ const shouldInspectPlaywright = useManifest && !opts.dryRun && (toolPlan === null || toolPlan.actions.length === 0) && loadPlaywrightCliPreference() === true;
2912
+ const playwrightCapability = opts.dryRun || !useManifest ? void 0 : opts.playwrightCapability ?? (shouldInspectPlaywright ? inspectPlaywrightCapability() : void 0);
2913
+ const effectivePlaywright = playwrightCapability?.effective;
2914
+ const plannedPlaywright = effectivePlaywright ?? (toolPlan !== null && toolPlan.actions.length > 0 ? false : void 0);
2876
2915
  const modelMap = hasFileRuntimes ? loadModelMap() : {};
2877
2916
  if (preparedStyle !== void 0) {
2878
2917
  try {
@@ -2885,7 +2924,7 @@ async function runInstall(opts) {
2885
2924
  }
2886
2925
  if (hasFileRuntimes && useManifest && !opts.dryRun) ensureModelMapFile();
2887
2926
  p.log.info(engramBin ? `Engram detectado: ${engramBin} (se respeta, D7)` : "Engram NO detectado.");
2888
- const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
2927
+ const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference, plannedPlaywright) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
2889
2928
  const canOrphan = hasFileRuntimes && useManifest && current.complete;
2890
2929
  const canonicalMcp = loadCanonicalMcp(stackDir);
2891
2930
  const canonicalHooks = loadCanonicalHooks(stackDir);
@@ -2924,7 +2963,7 @@ async function runInstall(opts) {
2924
2963
  models,
2925
2964
  warnings: [],
2926
2965
  enabledMcpServers: enabledMcpServers(id, opts.devtoolsMcpSelection?.[id], useManifest),
2927
- playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true,
2966
+ playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true && (plannedPlaywright ?? true),
2928
2967
  ownedMcpServers: ownedMcpServers(id, useManifest),
2929
2968
  ownedPrimaryModelFields: useManifest ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), id, configDir) : /* @__PURE__ */ new Set()
2930
2969
  };
@@ -3090,21 +3129,23 @@ async function runInstall(opts) {
3090
3129
  exitCode = 1;
3091
3130
  p.log.error("Playwright CLI y navegador se han instalado y la preferencia est\xE1 activada, pero la gu\xEDa de navegador qued\xF3 en estado parcial. Ejecuta 'jorgex-stack sync' para repararla.");
3092
3131
  } else {
3132
+ const verified = preparedEnv === void 0 ? inspectPlaywrightCapability({ browserVerified: true }) : inspectPlaywrightCapability({ browserVerified: true, env: preparedEnv });
3133
+ if (verified.effective && verified.cli.status === "current" && verified.cli.binPath !== null && verified.cli.detectedVersion !== null && verified.browserCache.status === "ready") {
3134
+ opts.onPlaywrightCapability?.(verified);
3135
+ }
3093
3136
  p.log.success("Playwright CLI instalado y arranque de Chromium verificado.");
3094
3137
  }
3095
3138
  }
3096
3139
  }
3097
- } else if (useManifest && opts.playwrightToolConsent?.command === "sync" && loadPlaywrightCliPreference() === true) {
3098
- const cli = detectPlaywrightCli();
3099
- if (cli.status !== "current") {
3140
+ } else if (playwrightCapability !== void 0 && !playwrightCapability.effective) {
3141
+ if (playwrightCapability.cli.status !== "current") {
3100
3142
  p.log.warn("Playwright CLI sigue habilitado, pero el paquete no est\xE1 listo; sync no instala herramientas. Ejecuta 'jorgex-stack install --playwright'.");
3143
+ } else if (playwrightCapability.browserCache.status === "unreadable") {
3144
+ p.log.warn(`Playwright CLI sigue habilitado, pero no se puede leer la cach\xE9 de navegadores en ${playwrightCapability.browserCache.path} (${playwrightCapability.browserCache.errorCode}). Revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
3145
+ } else if (playwrightCapability.browserCache.status === "missing") {
3146
+ p.log.warn("Playwright CLI sigue habilitado, pero falta el navegador; sync no descarga navegadores. Ejecuta 'jorgex-stack install --playwright'.");
3101
3147
  } else {
3102
- const browserCache = isPlaywrightBrowserReady();
3103
- if (browserCache.status === "unreadable") {
3104
- p.log.warn(`Playwright CLI sigue habilitado, pero no se puede leer la cach\xE9 de navegadores en ${browserCache.path} (${browserCache.errorCode}). Revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
3105
- } else if (browserCache.status === "missing") {
3106
- p.log.warn("Playwright CLI sigue habilitado, pero falta el navegador; sync no descarga navegadores. Ejecuta 'jorgex-stack install --playwright'.");
3107
- }
3148
+ p.log.warn("Playwright CLI sigue habilitado, pero Chromium no arranca; se ha retirado la gu\xEDa. Ejecuta 'jorgex-stack install --playwright' para repararlo.");
3108
3149
  }
3109
3150
  }
3110
3151
  if (useManifest && !opts.dryRun && exitCode === 0 && successfulRuns > 0) {
@@ -4376,6 +4417,7 @@ function resolvePlaywrightDoctorState(input) {
4376
4417
  if (input.browserCache?.status === "unreadable") {
4377
4418
  return { status: "unreadable", path: input.browserCache.path, errorCode: input.browserCache.errorCode };
4378
4419
  }
4420
+ if (input.browserVerified === false && input.browserCache?.status === "ready") return { status: "broken" };
4379
4421
  if (!input.browserReady) return { status: "missing", missing: "browser" };
4380
4422
  return { status: "healthy" };
4381
4423
  }
@@ -4471,21 +4513,28 @@ async function runDoctor(options = {}) {
4471
4513
  p3.log.error(primaryOwnershipError);
4472
4514
  problems++;
4473
4515
  }
4516
+ let effectivePlaywright;
4474
4517
  if (preferenceErrors.length > 0) {
4475
4518
  for (const error of preferenceErrors) p3.log.error(error);
4476
4519
  problems += preferenceErrors.length;
4520
+ } else if (options.dryRun) {
4521
+ p3.log.info("Playwright CLI: comprobaci\xF3n omitida en dry-run; no se eval\xFAa el estado del paquete ni del navegador.");
4477
4522
  } else {
4478
- const browserCache = isPlaywrightBrowserReady();
4523
+ const enabled = loadPlaywrightCliPreference();
4524
+ const capability = options.playwrightCapability ?? (enabled === true ? inspectPlaywrightCapability() : void 0);
4525
+ effectivePlaywright = capability?.effective;
4526
+ const cli = capability?.cli ?? { status: "absent" };
4479
4527
  const playwright = resolvePlaywrightDoctorState({
4480
- enabled: loadPlaywrightCliPreference(),
4481
- cli: detectPlaywrightCli(),
4482
- browserReady: browserCache.status === "ready",
4483
- browserCache
4528
+ enabled,
4529
+ cli,
4530
+ browserReady: capability?.effective ?? false,
4531
+ browserVerified: capability?.browserVerified,
4532
+ browserCache: capability?.browserCache
4484
4533
  });
4485
4534
  if (playwright.status === "disabled") {
4486
4535
  p3.log.info("Playwright CLI: deshabilitado (opcional). Usa 'install --playwright' para instalarlo de forma expl\xEDcita.");
4487
4536
  } else if (playwright.status === "healthy") {
4488
- p3.log.success("Playwright CLI: paquete y cach\xE9 de Chromium detectados (doctor no prueba el arranque).");
4537
+ p3.log.success("Playwright CLI: paquete y arranque de Chromium verificados.");
4489
4538
  } else if (playwright.status === "not-in-path") {
4490
4539
  p3.log.warn("Playwright CLI: instalado en el directorio de pnpm, pero fuera del PATH de esta terminal. Abre una terminal nueva tras pnpm setup; no hace falta reinstalarlo.");
4491
4540
  problems++;
@@ -4494,7 +4543,8 @@ async function runDoctor(options = {}) {
4494
4543
  p3.log.warn(`Playwright CLI: habilitado, pero falta ${target} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
4495
4544
  problems++;
4496
4545
  } else if (playwright.status === "broken") {
4497
- p3.log.error("Playwright CLI: el binario detectado no responde correctamente \u2192 ejecuta 'jorgex-stack install --playwright'.");
4546
+ const launchFailed = capability?.cli.status === "current" && capability.browserCache.status === "ready" && capability.browserVerified === false;
4547
+ p3.log.error(`Playwright CLI: ${launchFailed ? "Chromium no arranca" : "el binario detectado no responde correctamente"} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
4498
4548
  problems++;
4499
4549
  } else if (playwright.status === "unreadable") {
4500
4550
  p3.log.error(`Playwright CLI: no se puede leer la cach\xE9 de navegadores en ${playwright.path} (${playwright.errorCode}) \u2192 revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
@@ -4505,7 +4555,7 @@ async function runDoctor(options = {}) {
4505
4555
  }
4506
4556
  }
4507
4557
  const manifest = readManifest();
4508
- const current = collectAllCurrentTargets(modePreference);
4558
+ const current = collectAllCurrentTargets(modePreference, effectivePlaywright);
4509
4559
  if (!current.complete || current.warnings.length > 0) {
4510
4560
  p3.log.warn("Limpieza de hu\xE9rfanos deshabilitada: no se pudo construir el plan completo de todos los runtimes.");
4511
4561
  for (const warning of current.warnings) p3.log.warn(warning);
@@ -4521,7 +4571,7 @@ async function runDoctor(options = {}) {
4521
4571
  const capabilityReport = adapter.reportCapabilities(detection.configDir);
4522
4572
  const capabilitySummary = capabilityReport.capabilities.map((capability) => `${capability.id}=${capability.state}`).join(", ");
4523
4573
  p3.log.info(`${adapter.name}: capabilities diagnostic (${capabilitySummary}); no certifica enforcement local.`);
4524
- const ctx = makeContext(adapter, detection.configDir, modePreference);
4574
+ const ctx = makeContext(adapter, detection.configDir, modePreference, true, effectivePlaywright);
4525
4575
  if (!ctx) continue;
4526
4576
  ctx.writingStyle = writingStyle;
4527
4577
  let pending;
@@ -5300,6 +5350,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
5300
5350
  let exitCode = 0;
5301
5351
  let appliedUpdates = false;
5302
5352
  const updated = [];
5353
+ let playwrightCapability;
5303
5354
  const maintainer = isGitClone();
5304
5355
  const spin = p4.spinner();
5305
5356
  spin.start("Consultando versiones upstream\u2026");
@@ -5519,6 +5570,7 @@ ${diff}`);
5519
5570
  p4.log.success("Playwright CLI actualizado al pin aprobado.");
5520
5571
  appliedUpdates = true;
5521
5572
  updated.push("playwright-cli");
5573
+ playwrightCapability = inspectPlaywrightCapability({ browserVerified: true });
5522
5574
  } else {
5523
5575
  const failedResult = packageResult.ok ? browserResult : packageResult;
5524
5576
  const pnpmRemedy = failedResult && !failedResult.ok ? resolvePnpmFailureRemedy(failedResult.reason) : null;
@@ -5555,7 +5607,12 @@ ${diff}`);
5555
5607
  }
5556
5608
  }
5557
5609
  p4.outro(exitCode === 0 ? "Update completado." : "Update completado con errores (revisa arriba).");
5558
- return { exitCode, appliedUpdates, syncRequired: resolveUpdateSyncRequired(updated) };
5610
+ return {
5611
+ exitCode,
5612
+ appliedUpdates,
5613
+ syncRequired: resolveUpdateSyncRequired(updated),
5614
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
5615
+ };
5559
5616
  }
5560
5617
 
5561
5618
  // src/models-picker.ts
@@ -6430,10 +6487,16 @@ async function runManagedPiSystem(input) {
6430
6487
  const {
6431
6488
  devtoolsMcpEnabled: explicitDevtools,
6432
6489
  playwrightCliEnabled: explicitPlaywright,
6490
+ playwrightCapability,
6491
+ packageOnly,
6433
6492
  writingStyle: suppliedStyle,
6434
6493
  writingStyleMode,
6435
6494
  ...runtimeInput
6436
6495
  } = input;
6496
+ if (input.operation === "doctor" && packageOnly) {
6497
+ const result2 = managedPackageResult(await runPiRuntimeSystem(runtimeInput));
6498
+ return result2.kind === "manual-existing" ? manualExistingResult(result2) : result2;
6499
+ }
6437
6500
  const readsStyle = input.operation !== "uninstall" && input.operation !== "models";
6438
6501
  if (input.operation !== "models") {
6439
6502
  try {
@@ -6448,8 +6511,11 @@ async function runManagedPiSystem(input) {
6448
6511
  const writingStyle = style && mode === "programmatic" ? { ...style, content: null } : style;
6449
6512
  const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
6450
6513
  const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
6451
- const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
6452
- const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
6514
+ const persistedPlaywright = input.targetDir === void 0 && loadPlaywrightCliPreference(void 0, "pi") === true;
6515
+ const selectedPlaywright = explicitPlaywright ?? persistedPlaywright;
6516
+ const playwrightCapabilityEffective = playwrightCapability?.effective === true;
6517
+ const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && selectedPlaywright && playwrightCapabilityEffective;
6518
+ const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? playwrightCapability?.cli.binPath ?? null : null;
6453
6519
  const projectionInput = {
6454
6520
  writingStyle,
6455
6521
  targetDir: input.targetDir,
@@ -7001,6 +7067,9 @@ async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
7001
7067
  );
7002
7068
  return false;
7003
7069
  }
7070
+ function shouldInspectPlaywrightCapability(targetDir, dryRun) {
7071
+ return targetDir === void 0 && !dryRun && browserPreferenceErrors().length === 0 && loadPlaywrightCliPreference() === true;
7072
+ }
7004
7073
  function parseFlags(args, allowReceipt = false) {
7005
7074
  const flags = {
7006
7075
  agents: [],
@@ -7310,7 +7379,19 @@ async function resolveHostEngramForInstall(flags) {
7310
7379
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
7311
7380
  }
7312
7381
  }
7313
- async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, writingStyle, modePreference, playwrightCliEnabled) {
7382
+ async function runSelectedPi(options) {
7383
+ const {
7384
+ operation,
7385
+ targetDir,
7386
+ yes = false,
7387
+ resolvedEngramBin,
7388
+ devtoolsMcpEnabled,
7389
+ writingStyle: suppliedWritingStyle,
7390
+ modePreference,
7391
+ playwrightCliEnabled,
7392
+ playwrightCapability
7393
+ } = options;
7394
+ let writingStyle = suppliedWritingStyle;
7314
7395
  if (targetDir === void 0 && operation !== "models") {
7315
7396
  const preferenceErrors = browserPreferenceErrors();
7316
7397
  if (preferenceErrors.length > 0) {
@@ -7356,6 +7437,7 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7356
7437
  engramBin = requirement.bin;
7357
7438
  }
7358
7439
  const result = await runManagedPiSystem({
7440
+ ...options.packageOnly ? { packageOnly: true } : {},
7359
7441
  writingStyle,
7360
7442
  writingStyleMode: modePreference?.mode,
7361
7443
  operation,
@@ -7363,7 +7445,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7363
7445
  detected: { executable: detected.executable, version: detected.version },
7364
7446
  engramBin,
7365
7447
  ...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled },
7366
- ...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled }
7448
+ ...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled },
7449
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
7367
7450
  });
7368
7451
  if (result.kind === "blocked") {
7369
7452
  const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
@@ -7521,6 +7604,11 @@ Flags disponibles: jorgex-stack --help`
7521
7604
  exitCode = process.exitCode === 1 ? 1 : 0;
7522
7605
  return;
7523
7606
  }
7607
+ const playwrightToolPlan = resolvePlaywrightToolPlan(playwrightToolConsent);
7608
+ let playwrightCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) && playwrightToolPlan.actions.length === 0 ? inspectPlaywrightCapability() : void 0;
7609
+ const capturePlaywrightCapability = (snapshot) => {
7610
+ playwrightCapability = snapshot;
7611
+ };
7524
7612
  p6.log.info(`Estilo de escritura: ${writingStyle.sourcePath}${flags.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
7525
7613
  applyWritingStyle(writingStyle, flags.dryRun);
7526
7614
  if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
@@ -7548,11 +7636,13 @@ Flags disponibles: jorgex-stack --help`
7548
7636
  playwrightToolConsent,
7549
7637
  devtoolsMcpSelection,
7550
7638
  engramBin,
7639
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability },
7640
+ ...playwrightToolPlan.actions.length === 0 ? {} : { onPlaywrightCapability: capturePlaywrightCapability },
7551
7641
  showSummary: false
7552
7642
  });
7553
7643
  }
7554
7644
  let piCanRun = true;
7555
- if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi") && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0) {
7645
+ if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi") && playwrightToolPlan.actions.length > 0) {
7556
7646
  if (flags.dryRun) {
7557
7647
  p6.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
7558
7648
  } else {
@@ -7564,6 +7654,7 @@ Flags disponibles: jorgex-stack --help`
7564
7654
  yes: flags.yes,
7565
7655
  playwrightToolConsent,
7566
7656
  engramBin,
7657
+ onPlaywrightCapability: capturePlaywrightCapability,
7567
7658
  showSummary: false
7568
7659
  });
7569
7660
  piCanRun = exitCode === 0;
@@ -7572,16 +7663,17 @@ Flags disponibles: jorgex-stack --help`
7572
7663
  if (runtimes.includes("pi") && piCanRun) {
7573
7664
  if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
7574
7665
  else {
7575
- const piExitCode = await runSelectedPi(
7576
- command,
7577
- flags.targetDir,
7578
- flags.yes,
7579
- flags.targetDir === void 0 ? engramBin : void 0,
7580
- devtoolsMcpSelection.pi,
7666
+ const piExitCode = await runSelectedPi({
7667
+ operation: command,
7668
+ targetDir: flags.targetDir,
7669
+ yes: flags.yes,
7670
+ resolvedEngramBin: flags.targetDir === void 0 ? engramBin : void 0,
7671
+ devtoolsMcpEnabled: devtoolsMcpSelection.pi,
7581
7672
  writingStyle,
7582
- mode,
7583
- flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
7584
- );
7673
+ modePreference: mode,
7674
+ playwrightCliEnabled: flags.targetDir === void 0 && exitCode === 0 && playwrightToolPlan.actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0,
7675
+ playwrightCapability
7676
+ });
7585
7677
  exitCode = Math.max(exitCode, piExitCode);
7586
7678
  }
7587
7679
  }
@@ -7623,7 +7715,7 @@ Flags disponibles: jorgex-stack --help`
7623
7715
  }) : 0;
7624
7716
  if (runtimes.includes("pi")) {
7625
7717
  if (flags.dryRun) p6.log.info("Pi: uninstall previsto; dry-run conserva paquete y receipt.");
7626
- else exitCode = Math.max(exitCode, await runSelectedPi("uninstall", flags.targetDir));
7718
+ else exitCode = Math.max(exitCode, await runSelectedPi({ operation: "uninstall", targetDir: flags.targetDir }));
7627
7719
  }
7628
7720
  process.exitCode = exitCode;
7629
7721
  return;
@@ -7636,8 +7728,25 @@ Flags disponibles: jorgex-stack --help`
7636
7728
  ...Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => adapter.id),
7637
7729
  ...piSelected ? ["pi"] : []
7638
7730
  ] : [...Object.keys(ADAPTERS), ...piSelected ? ["pi"] : []];
7639
- let exitCode = await runDoctor({ targetDir: flags.targetDir, runtimes: doctorRuntimes, mode });
7640
- if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir, false, void 0, void 0, void 0, mode));
7731
+ const doctorCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7732
+ let exitCode = await runDoctor({
7733
+ targetDir: flags.targetDir,
7734
+ runtimes: doctorRuntimes,
7735
+ mode,
7736
+ ...flags.dryRun ? { dryRun: true } : {},
7737
+ ...doctorCapability === void 0 ? {} : { playwrightCapability: doctorCapability }
7738
+ });
7739
+ if (piSelected) {
7740
+ if (flags.dryRun) p6.log.info("Pi: doctor previsto; dry-run no ejecuta subprocess ni smoke de Playwright.");
7741
+ else {
7742
+ exitCode = Math.max(exitCode, await runSelectedPi({
7743
+ operation: "doctor",
7744
+ targetDir: flags.targetDir,
7745
+ modePreference: mode,
7746
+ playwrightCapability: doctorCapability
7747
+ }));
7748
+ }
7749
+ }
7641
7750
  process.exitCode = exitCode;
7642
7751
  return;
7643
7752
  }
@@ -7646,7 +7755,16 @@ Flags disponibles: jorgex-stack --help`
7646
7755
  const piExplicit = flags.agents.includes("pi");
7647
7756
  const fileRuntimeExplicit = flags.agents.some(isFileManagedRuntime);
7648
7757
  let exitCode = flags.agents.length === 0 || fileRuntimeExplicit ? await runUpdateCheck(VERSION, flags.targetDir === void 0) : 0;
7649
- if (piExplicit) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
7758
+ if (piExplicit) {
7759
+ if (flags.dryRun) p6.log.info("Pi: doctor previsto; dry-run no ejecuta subprocess ni smoke de Playwright.");
7760
+ else {
7761
+ exitCode = Math.max(exitCode, await runSelectedPi({
7762
+ operation: "doctor",
7763
+ targetDir: flags.targetDir,
7764
+ packageOnly: true
7765
+ }));
7766
+ }
7767
+ }
7650
7768
  process.exitCode = exitCode;
7651
7769
  return;
7652
7770
  }
@@ -7671,8 +7789,15 @@ Flags disponibles: jorgex-stack --help`
7671
7789
  { rootDir: flags.targetDir }
7672
7790
  );
7673
7791
  applyWritingStyle(writingStyle, flags.dryRun);
7792
+ let updateCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7674
7793
  if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
7675
- const piExitCode = await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode);
7794
+ const piExitCode = await runSelectedPi({
7795
+ operation: "update",
7796
+ targetDir: flags.targetDir,
7797
+ writingStyle,
7798
+ modePreference: mode,
7799
+ playwrightCapability: updateCapability
7800
+ });
7676
7801
  process.exitCode = piExitCode;
7677
7802
  persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, piExitCode);
7678
7803
  return;
@@ -7685,7 +7810,8 @@ Flags disponibles: jorgex-stack --help`
7685
7810
  targetDir: flags.targetDir,
7686
7811
  dryRun: flags.dryRun,
7687
7812
  yes: true,
7688
- mode
7813
+ mode,
7814
+ ...updateCapability === void 0 ? {} : { playwrightCapability: updateCapability }
7689
7815
  });
7690
7816
  if (code !== 0) {
7691
7817
  process.exitCode = code;
@@ -7701,12 +7827,35 @@ Flags disponibles: jorgex-stack --help`
7701
7827
  flags.targetDir === void 0
7702
7828
  );
7703
7829
  process.exitCode = result.exitCode;
7830
+ let playwrightReconciled = false;
7831
+ if (result.exitCode === 0 && result.playwrightCapability !== void 0) {
7832
+ updateCapability = result.playwrightCapability;
7833
+ if (fileRuntimes.length > 0 && canSync) {
7834
+ const code = await runInstall({
7835
+ runtimes: fileRuntimes,
7836
+ writingStyle,
7837
+ targetDir: flags.targetDir,
7838
+ dryRun: false,
7839
+ yes: true,
7840
+ mode,
7841
+ playwrightCapability: updateCapability
7842
+ });
7843
+ process.exitCode = Math.max(process.exitCode ?? 0, code);
7844
+ playwrightReconciled = true;
7845
+ }
7846
+ }
7704
7847
  if (result.exitCode === 0 && runtimes.includes("pi")) {
7705
- process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode));
7848
+ process.exitCode = Math.max(process.exitCode ?? 0, await runSelectedPi({
7849
+ operation: "update",
7850
+ targetDir: flags.targetDir,
7851
+ writingStyle,
7852
+ modePreference: mode,
7853
+ playwrightCapability: updateCapability
7854
+ }));
7706
7855
  }
7707
7856
  if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
7708
7857
  p6.log.warn("Skills/stack actualizados, pero el sync con los runtimes sigue pendiente. Ejecuta jorgex-stack sync --mode human|programmatic.");
7709
- } else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
7858
+ } else if (!playwrightReconciled && result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
7710
7859
  const apply = await p6.confirm({ message: "\xBFRe-aplicar a los runtimes ahora? (sync)" });
7711
7860
  if (!p6.isCancel(apply) && apply) {
7712
7861
  process.exitCode = await runInstall({
@@ -7715,7 +7864,8 @@ Flags disponibles: jorgex-stack --help`
7715
7864
  targetDir: flags.targetDir,
7716
7865
  dryRun: false,
7717
7866
  yes: false,
7718
- mode
7867
+ mode,
7868
+ ...updateCapability === void 0 ? {} : { playwrightCapability: updateCapability }
7719
7869
  });
7720
7870
  } else {
7721
7871
  console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
@@ -7738,7 +7888,7 @@ Flags disponibles: jorgex-stack --help`
7738
7888
  }
7739
7889
  const fileRuntimes = runtimes.filter(isFileManagedRuntime);
7740
7890
  let code = fileRuntimes.length > 0 ? await runModelsPicker({ yes: flags.yes, runtimes: fileRuntimes }) : 0;
7741
- if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi("models", flags.targetDir));
7891
+ if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi({ operation: "models", targetDir: flags.targetDir }));
7742
7892
  process.exitCode = code;
7743
7893
  if (code === 0 && fileRuntimes.length > 0 && !flags.yes && process.stdout.isTTY) {
7744
7894
  const apply = await p6.confirm({ message: "\xBFAplicar ahora los modelos a los agentes instalados? (sync)" });
@@ -7753,7 +7903,15 @@ Flags disponibles: jorgex-stack --help`
7753
7903
  }
7754
7904
  const mode = await resolveInstallMode(flags, false);
7755
7905
  if (mode === null) return;
7756
- process.exitCode = await runInstall({ runtimes: fileRuntimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
7906
+ const playwrightCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7907
+ process.exitCode = await runInstall({
7908
+ runtimes: fileRuntimes,
7909
+ targetDir: flags.targetDir,
7910
+ dryRun: flags.dryRun,
7911
+ yes: false,
7912
+ mode,
7913
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
7914
+ });
7757
7915
  } else {
7758
7916
  console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
7759
7917
  }