jorgex-stack 1.9.29 → 1.9.31

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 (3) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +172 -44
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -120,7 +120,7 @@ pnpm dlx jorgex-stack@1.9.7 sync --agents pi
120
120
  pnpm dlx jorgex-stack@1.9.7 uninstall --agents pi
121
121
  ```
122
122
 
123
- Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The Pi-only `install --agents pi --playwright` flow installs and persists that Playwright capability just like the other harnesses. Chrome DevTools MCP and Context7 remain outside the Pi scope. The published `1.9.7` entry `{ "source": "npm:jorgex-pi@0.8.4", "skills": [], "prompts": [] }` and the `0.8.5` snapshot reference are historical; they do not describe the adopted pin. Filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Both receipts are scope-bound and fail closed for manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state.
123
+ Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The Pi-only `install --agents pi --playwright` flow installs and persists that Playwright capability just like the other harnesses. The adopted Pi `0.8.13` candidate also supports the optional Chrome DevTools handoff; Stack persists the selected handoff as part of the Pi projection and records its SHA-256 in the optional `devtools.sha256` field of `~/.jorgex-stack/pi-projection-receipt.json` after a successful operation. Context7 remains outside the Pi scope. The published `1.9.7` entry `{ "source": "npm:jorgex-pi@0.8.4", "skills": [], "prompts": [] }` and the `0.8.5` snapshot reference are historical; they do not describe the adopted pin. Filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Package receipts reject manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state. Projection cleanup requires an exact scope-bound ownership receipt; DevTools conflicts preserve the handoff for review.
124
124
 
125
125
  Historically, the published Pi 0.8.0 direct-package snapshot added `work-audit`: the snapshot grew from **17 to 18 skill trees** (96 to 97 files), and the active runtime allowlist grew from **16 to 17 skills**. The historical Pi 0.8.5 snapshot reference records 18 skill trees and 98 files; it does not describe the adopted pin. Reference F2-A is included while the private F1 skills remain preserved. `playwright-cli` remains in the snapshot but inactive because browser automation is a separate opt-in integration.
126
126
 
@@ -159,6 +159,8 @@ pnpm dlx jorgex-stack install --yes --playwright
159
159
  # Enable Chrome DevTools MCP explicitly per runtime.
160
160
  pnpm dlx jorgex-stack install --devtools
161
161
  pnpm dlx jorgex-stack install --no-devtools
162
+ pnpm dlx jorgex-stack install --agents pi --devtools
163
+ pnpm dlx jorgex-stack sync --agents pi --no-devtools
162
164
  ```
163
165
 
164
166
  Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pnpm add --global @playwright/cli@0.1.18` (the package) and then `pnpm dlx @playwright/cli@0.1.18 install-browser` (the browser binary cache). Removal is `pnpm remove --global @playwright/cli` (no version suffix). If installation fails, the error identifies the failed phase — global package, browser download, or preference persistence — and recommends `jorgex-stack install --playwright`; the preference is not marked enabled unless the complete plan succeeds.
package/dist/cli.js CHANGED
@@ -2257,10 +2257,12 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2257
2257
  const preflight = planDetectedBinCommand(pnpmBin, ["bin", "--global"]);
2258
2258
  if (preflight === null) return { ok: false, reason: "pnpm-command" };
2259
2259
  try {
2260
- execFileSync2(preflight.command, preflight.args, {
2261
- stdio: "inherit",
2260
+ const globalBin = execFileSync2(preflight.command, preflight.args, {
2261
+ encoding: "utf8",
2262
+ stdio: ["ignore", "pipe", "inherit"],
2262
2263
  ...env === void 0 ? {} : { env }
2263
2264
  });
2265
+ if (globalBin.trim() === "") return { ok: false, reason: "pnpm-global-bin" };
2264
2266
  } catch (error) {
2265
2267
  return { ok: false, reason: hasNonzeroProcessStatus(error) ? "pnpm-global-bin" : "pnpm-command" };
2266
2268
  }
@@ -2341,7 +2343,7 @@ function parseDevtoolsMcpState(raw) {
2341
2343
  if (!isRecord2(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord2(value.enabled) || !isRecord2(value.owned)) return null;
2342
2344
  const enabled = {};
2343
2345
  for (const [runtime, selected] of Object.entries(value.enabled)) {
2344
- if (!isRuntimeId(runtime) || typeof selected !== "boolean") return null;
2346
+ if (!isRuntimeId(runtime) && runtime !== "pi" || typeof selected !== "boolean") return null;
2345
2347
  enabled[runtime] = selected;
2346
2348
  }
2347
2349
  const owned = {};
@@ -2844,6 +2846,7 @@ import * as p2 from "@clack/prompts";
2844
2846
  // src/lib/pi-projection-lifecycle.ts
2845
2847
  import fs16 from "fs";
2846
2848
  import path23 from "path";
2849
+ import { createHash } from "crypto";
2847
2850
 
2848
2851
  // src/adapters/pi.ts
2849
2852
  import path21 from "path";
@@ -3307,14 +3310,33 @@ function projectionPlan(input, scope) {
3307
3310
  engramBin: input.engramBin,
3308
3311
  models: DEFAULT_MODEL_MAP.codex,
3309
3312
  warnings: [],
3310
- playwrightCliEnabled: input.playwrightCliEnabled
3313
+ playwrightCliEnabled: input.playwrightCliEnabled,
3314
+ enabledMcpServers: input.devtoolsMcpEnabled ? /* @__PURE__ */ new Set([DEVTOOLS_MCP_SERVER]) : void 0
3311
3315
  };
3312
3316
  const adapter = projectedPiAdapter(scope);
3313
- return [
3317
+ const actions = [
3314
3318
  ...planSystemPrompt(adapter, ctx),
3315
3319
  ...planSkills(adapter, ctx),
3316
3320
  ...planCommands(adapter, ctx)
3317
3321
  ];
3322
+ if (input.devtoolsMcpEnabled && input.pnpmBin) {
3323
+ const server = loadCanonicalMcp(input.stackDir).servers[DEVTOOLS_MCP_SERVER];
3324
+ if (server?.transport !== "stdio" || !Array.isArray(server.args)) throw new Error("Falta la configuraci\xF3n can\xF3nica de DevTools.");
3325
+ actions.push({ kind: "write", target: devtoolsPath(scope), content: `${JSON.stringify({
3326
+ schemaVersion: 1,
3327
+ enabled: true,
3328
+ command: input.pnpmBin,
3329
+ args: server.args
3330
+ }, null, 2)}
3331
+ ` });
3332
+ }
3333
+ return actions;
3334
+ }
3335
+ function devtoolsPath(scope) {
3336
+ return path23.resolve(scope.codingAgentDir, "jorgex-pi", "devtools.v1.json");
3337
+ }
3338
+ function contentHash(content) {
3339
+ return createHash("sha256").update(content).digest("hex");
3318
3340
  }
3319
3341
  function canonicalActionContent(action) {
3320
3342
  return action.kind === "write" ? action.content : fs16.readFileSync(action.source, "utf8");
@@ -3322,8 +3344,15 @@ function canonicalActionContent(action) {
3322
3344
  function hasActionDrift(action, deps) {
3323
3345
  return deps.readText(action.target) !== canonicalActionContent(action);
3324
3346
  }
3325
- function applyActions(actions, deps) {
3347
+ function applyActions(actions, deps, handoff) {
3326
3348
  for (const action of actions) {
3349
+ if (handoff && path23.resolve(action.target) === handoff.file) {
3350
+ try {
3351
+ if (deps.readText(handoff.file) !== handoff.previous) return devtoolsConflict(handoff.file);
3352
+ } catch {
3353
+ return devtoolsConflict(handoff.file);
3354
+ }
3355
+ }
3327
3356
  if (action.kind === "write") deps.writeText(action.target, action.content);
3328
3357
  else deps.copyFile(action.source, action.target);
3329
3358
  }
@@ -3340,6 +3369,7 @@ function receiptFor(plan, scope) {
3340
3369
  const owned = [...new Set(
3341
3370
  plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
3342
3371
  )];
3372
+ const handoff = plan.find((action) => path23.resolve(action.target) === devtoolsPath(scope));
3343
3373
  return {
3344
3374
  schemaVersion: 1,
3345
3375
  scope: {
@@ -3348,7 +3378,8 @@ function receiptFor(plan, scope) {
3348
3378
  codingAgentDir: scope.codingAgentDir,
3349
3379
  receiptFile: scope.receiptFile
3350
3380
  },
3351
- owned
3381
+ owned,
3382
+ ...handoff ? { devtools: { sha256: contentHash(canonicalActionContent(handoff)) } } : {}
3352
3383
  };
3353
3384
  }
3354
3385
  function receiptContent(receipt) {
@@ -3367,16 +3398,25 @@ function parseReceipt2(raw, expected) {
3367
3398
  const version = Reflect.get(receipt, "schemaVersion");
3368
3399
  const receivedScope = Reflect.get(receipt, "scope");
3369
3400
  const owned = Reflect.get(receipt, "owned");
3370
- if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned"]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
3371
- return null;
3372
- }
3373
- if (Reflect.get(receivedScope, "kind") !== expected.scope.kind || Reflect.get(receivedScope, "home") !== expected.scope.home || Reflect.get(receivedScope, "codingAgentDir") !== expected.scope.codingAgentDir || Reflect.get(receivedScope, "receiptFile") !== expected.scope.receiptFile || !Array.isArray(owned) || owned.length !== expected.owned.length) {
3401
+ const devtools = Reflect.get(receipt, "devtools");
3402
+ const hasDevtools = Object.hasOwn(receipt, "devtools");
3403
+ if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned", ...hasDevtools ? ["devtools"] : []]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
3374
3404
  return null;
3375
3405
  }
3376
- if (!owned.every((file, index) => typeof file === "string" && file === expected.owned[index])) {
3406
+ if (Reflect.get(receivedScope, "kind") !== expected.scope.kind || Reflect.get(receivedScope, "home") !== expected.scope.home || Reflect.get(receivedScope, "codingAgentDir") !== expected.scope.codingAgentDir || Reflect.get(receivedScope, "receiptFile") !== expected.scope.receiptFile || !Array.isArray(owned)) {
3377
3407
  return null;
3378
3408
  }
3379
- return expected;
3409
+ if (hasDevtools && (devtools === null || typeof devtools !== "object" || Array.isArray(devtools) || !hasExactKeys(devtools, ["sha256"]) || typeof Reflect.get(devtools, "sha256") !== "string" || !/^[a-f0-9]{64}$/.test(Reflect.get(devtools, "sha256")))) return null;
3410
+ const handoff = devtoolsPath(expected.scope);
3411
+ const expectedOwned = expected.owned.filter((file) => file !== handoff);
3412
+ if (hasDevtools) expectedOwned.push(handoff);
3413
+ if (owned.length !== expectedOwned.length || !owned.every((file, index) => typeof file === "string" && file === expectedOwned[index])) return null;
3414
+ return {
3415
+ schemaVersion: 1,
3416
+ scope: expected.scope,
3417
+ owned: expectedOwned,
3418
+ ...hasDevtools ? { devtools: { sha256: Reflect.get(devtools, "sha256") } } : {}
3419
+ };
3380
3420
  } catch {
3381
3421
  return null;
3382
3422
  }
@@ -3502,6 +3542,35 @@ function readProjectionReceipt(receiptFile, deps) {
3502
3542
  return { kind: "unreadable" };
3503
3543
  }
3504
3544
  }
3545
+ function devtoolsConflict(file) {
3546
+ return blocked2(
3547
+ "projection-devtools-conflict",
3548
+ [file],
3549
+ "El handoff de DevTools es ajeno o fue modificado. Cons\xE9rvalo y revisa su propiedad antes de reintentar; Stack no lo sobrescribe ni elimina."
3550
+ );
3551
+ }
3552
+ function inspectDevtools(input, scope, expected, deps) {
3553
+ const file = devtoolsPath(scope);
3554
+ const read = readProjectionReceipt(scope.receiptFile, deps);
3555
+ if (read.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3556
+ const previous = read.kind === "present" ? parseReceipt2(read.content, expected) : null;
3557
+ let content;
3558
+ try {
3559
+ content = deps.readText(file);
3560
+ } catch {
3561
+ return devtoolsConflict(file);
3562
+ }
3563
+ let recordedDevtools = false;
3564
+ if (read.kind === "present") {
3565
+ try {
3566
+ recordedDevtools = Object.hasOwn(JSON.parse(read.content), "devtools");
3567
+ } catch {
3568
+ }
3569
+ if (!previous && (input.devtoolsMcpEnabled || content !== null || recordedDevtools)) return receiptInvalid(scope.receiptFile);
3570
+ }
3571
+ if (content !== null && (!previous?.devtools || contentHash(content) !== previous.devtools.sha256)) return devtoolsConflict(file);
3572
+ return { kind: "checked", previous, content };
3573
+ }
3505
3574
  function preparePiProjectionUninstall(input, deps) {
3506
3575
  if (input.operation !== "uninstall") {
3507
3576
  return cleanupFailure([]);
@@ -3520,11 +3589,13 @@ function preparePiProjectionUninstall(input, deps) {
3520
3589
  const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
3521
3590
  if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3522
3591
  let ownedToRemove = [];
3592
+ let ownedReceipt = null;
3523
3593
  let receiptFile = null;
3524
3594
  let backupTargets = promptUpdate === null ? [] : [promptUpdate.file];
3525
3595
  if (receiptRead.kind === "present") {
3526
3596
  const receipt = parseReceipt2(receiptRead.content, expectedReceipt2);
3527
3597
  if (receipt === null) return receiptInvalid(scope.receiptFile);
3598
+ ownedReceipt = receipt;
3528
3599
  let retained;
3529
3600
  try {
3530
3601
  retained = manifestOwned(deps.readManifest());
@@ -3535,13 +3606,22 @@ function preparePiProjectionUninstall(input, deps) {
3535
3606
  receiptFile = path23.resolve(scope.receiptFile);
3536
3607
  backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
3537
3608
  }
3609
+ const handoff = devtoolsPath(scope);
3610
+ let handoffContent;
3611
+ try {
3612
+ handoffContent = deps.readText(handoff);
3613
+ } catch {
3614
+ return devtoolsConflict(handoff);
3615
+ }
3616
+ if (handoffContent !== null && (!ownedReceipt?.devtools || contentHash(handoffContent) !== ownedReceipt.devtools.sha256)) return devtoolsConflict(handoff);
3538
3617
  const backup = backupExisting(backupTargets, deps);
3539
3618
  if (backup.kind === "backup-failed") return backupFailure(backup.paths);
3540
3619
  const token = {};
3541
3620
  preparedPiProjectionUninstalls.set(token, {
3542
3621
  prompt: promptUpdate,
3543
3622
  ownedToRemove,
3544
- receiptFile
3623
+ receiptFile,
3624
+ ...ownedReceipt?.devtools ? { devtools: { file: handoff, sha256: ownedReceipt.devtools.sha256 } } : {}
3545
3625
  });
3546
3626
  return { kind: "prepared", plan: token };
3547
3627
  }
@@ -3553,6 +3633,15 @@ function completePiProjectionUninstall(plan, deps) {
3553
3633
  if (prepared === void 0) {
3554
3634
  return cleanupFailure([]);
3555
3635
  }
3636
+ if (prepared.devtools) {
3637
+ let current;
3638
+ try {
3639
+ current = deps.readText(prepared.devtools.file);
3640
+ } catch {
3641
+ return devtoolsConflict(prepared.devtools.file);
3642
+ }
3643
+ if (current !== null && contentHash(current) !== prepared.devtools.sha256) return devtoolsConflict(prepared.devtools.file);
3644
+ }
3556
3645
  if (prepared.prompt !== null) {
3557
3646
  try {
3558
3647
  deps.writeText(prepared.prompt.file, prepared.prompt.content);
@@ -3562,6 +3651,10 @@ function completePiProjectionUninstall(plan, deps) {
3562
3651
  }
3563
3652
  for (const owned of prepared.ownedToRemove) {
3564
3653
  try {
3654
+ if (prepared.devtools?.file === owned) {
3655
+ const current = deps.readText(owned);
3656
+ if (current !== null && contentHash(current) !== prepared.devtools.sha256) return devtoolsConflict(owned);
3657
+ }
3565
3658
  deps.removeFile(owned);
3566
3659
  } catch {
3567
3660
  return cleanupFailure([owned]);
@@ -3582,13 +3675,23 @@ function runPiProjectionLifecycle(input, deps) {
3582
3675
  const prepared = preparePiProjectionUninstall(input, deps);
3583
3676
  return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
3584
3677
  }
3678
+ if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path23.isAbsolute(input.pnpmBin))) {
3679
+ return blocked2("projection-devtools-command", [devtoolsPath(scope)], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
3680
+ }
3585
3681
  const plan = projectionPlan(input, scope);
3586
3682
  assertPlanContained(plan, scope);
3587
3683
  const receipt = receiptFor(plan, scope);
3684
+ const devtools = inspectDevtools(input, scope, receipt, deps);
3685
+ if (devtools.kind === "blocked") return input.operation === "doctor" ? { kind: "drift", paths: devtools.paths } : devtools;
3686
+ const removeHandoff = !input.devtoolsMcpEnabled && devtools.previous?.devtools !== void 0 && devtools.content !== null;
3588
3687
  const expectedReceipt2 = receiptContent(receipt);
3589
3688
  const drifted = plan.filter((action) => hasActionDrift(action, deps));
3689
+ if (input.devtoolsMcpEnabled && devtools.previous?.devtools?.sha256 !== receipt.devtools?.sha256 && !drifted.some((action) => path23.resolve(action.target) === devtoolsPath(scope))) {
3690
+ return input.operation === "doctor" ? { kind: "drift", paths: [devtoolsPath(scope)] } : devtoolsConflict(devtoolsPath(scope));
3691
+ }
3590
3692
  if (input.operation === "doctor") {
3591
3693
  const paths = drifted.map((action) => path23.resolve(action.target));
3694
+ if (removeHandoff) paths.push(devtoolsPath(scope));
3592
3695
  const currentSettings2 = deps.readText(scope.settingsFile);
3593
3696
  if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
3594
3697
  paths.push(scope.settingsFile);
@@ -3604,19 +3707,30 @@ function runPiProjectionLifecycle(input, deps) {
3604
3707
  }
3605
3708
  const packageWillChange = filteredSettings !== null && filteredSettings !== currentSettings;
3606
3709
  const receiptChanged = deps.readText(scope.receiptFile) !== expectedReceipt2;
3607
- if (drifted.length > 0 || packageWillChange || receiptChanged) {
3710
+ if (drifted.length > 0 || packageWillChange || receiptChanged || removeHandoff) {
3608
3711
  const backup = backupExisting([
3609
3712
  ...drifted.map((action) => action.target),
3713
+ ...removeHandoff ? [devtoolsPath(scope)] : [],
3610
3714
  ...packageWillChange ? [scope.settingsFile] : [],
3611
3715
  ...receiptChanged ? [scope.receiptFile] : []
3612
3716
  ], deps);
3613
3717
  if (backup.kind === "backup-failed") return backupFailure(backup.paths);
3614
3718
  }
3615
- applyActions(drifted, deps);
3719
+ if (removeHandoff) {
3720
+ try {
3721
+ const current = deps.readText(devtoolsPath(scope));
3722
+ if (current !== null && contentHash(current) !== devtools.previous?.devtools?.sha256) return devtoolsConflict(devtoolsPath(scope));
3723
+ deps.removeFile(devtoolsPath(scope));
3724
+ } catch {
3725
+ return cleanupFailure([devtoolsPath(scope)]);
3726
+ }
3727
+ }
3728
+ const writeResult = applyActions(drifted, deps, input.devtoolsMcpEnabled ? { file: devtoolsPath(scope), previous: devtools.content } : void 0);
3729
+ if (writeResult) return writeResult;
3616
3730
  if (packageWillChange && filteredSettings !== null) deps.writeText(scope.settingsFile, filteredSettings);
3617
3731
  if (receiptChanged) deps.writeText(scope.receiptFile, expectedReceipt2);
3618
3732
  if (input.operation === "install") return { kind: "installed", receipt };
3619
- return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged };
3733
+ return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removeHandoff };
3620
3734
  }
3621
3735
  function systemProjectionLifecycle(input) {
3622
3736
  const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
@@ -3633,7 +3747,9 @@ function systemProjectionLifecycle(input) {
3633
3747
  packageSource: input.packageSource,
3634
3748
  stackDir: stackRoot(),
3635
3749
  engramBin: input.engramBin ?? "",
3636
- playwrightCliEnabled: input.playwrightCliEnabled
3750
+ playwrightCliEnabled: input.playwrightCliEnabled,
3751
+ devtoolsMcpEnabled: input.devtoolsMcpEnabled,
3752
+ pnpmBin: input.pnpmBin
3637
3753
  },
3638
3754
  deps: {
3639
3755
  readText: readTextOnlyIfMissing,
@@ -5218,22 +5334,22 @@ import os4 from "os";
5218
5334
  import path31 from "path";
5219
5335
  import fs23 from "fs";
5220
5336
  import { spawnSync } from "child_process";
5221
- import { createHash } from "crypto";
5337
+ import { createHash as createHash2 } from "crypto";
5222
5338
 
5223
5339
  // src/lib/pi-runtime-pin.json
5224
5340
  var pi_runtime_pin_default = {
5225
5341
  package: {
5226
5342
  name: "jorgex-pi",
5227
- version: "0.8.12",
5228
- source: "npm:jorgex-pi@0.8.12"
5343
+ version: "0.8.13",
5344
+ source: "npm:jorgex-pi@0.8.13"
5229
5345
  },
5230
5346
  provenance: {
5231
- commit: "a041e8f290021b5a6bb161db77e03663f37c82cb"
5347
+ commit: "e28a677da0a112925251cf11269b70cb5c8aa15e"
5232
5348
  },
5233
5349
  tarball: {
5234
- bytes: 89141968,
5235
- sha256: "eb7739852504bf4cd8cdd9f260c67da979728378a756db7f1ff6b0c1d60368c6",
5236
- sha512: "a37b36e5c846e1694db3c7bd7e9acf23f39b8ba45d348bf039fd218f90f041a6d95268af2e3b6248fc4e759dfd6441df1c44490411c1e13c2b0cedbc0ef526b2"
5350
+ bytes: 89143404,
5351
+ sha256: "6bbe877249b900af582c8881e0dd7993951ea1cef6e7252522c626c078de7478",
5352
+ sha512: "bb527d1f3812406f2184f778b22cca00ead33692fecac0012cf8cdcdc469b7cccf3c789c35b4327dd599d06542162bd07eefeeaaa40de19662ec5fc422b14af8"
5237
5353
  }
5238
5354
  };
5239
5355
 
@@ -5255,6 +5371,7 @@ var PI_RUNTIME_CANDIDATE = {
5255
5371
  "goal-continuation-v1",
5256
5372
  "mcp-adapter-v1",
5257
5373
  "engram-runtime-tools-v1",
5374
+ "chrome-devtools-handoff-v1",
5258
5375
  "runner-json-v1",
5259
5376
  "tui-branding-v1",
5260
5377
  "managed-primary-model-v1",
@@ -5607,8 +5724,8 @@ function readOptional(file, fallback) {
5607
5724
  }
5608
5725
  function hashPiTarball(file) {
5609
5726
  const descriptor = fs23.openSync(file, "r");
5610
- const sha2562 = createHash("sha256");
5611
- const sha512 = createHash("sha512");
5727
+ const sha2562 = createHash2("sha256");
5728
+ const sha512 = createHash2("sha512");
5612
5729
  const buffer = Buffer.allocUnsafe(1024 * 1024);
5613
5730
  let bytes = 0;
5614
5731
  try {
@@ -5826,36 +5943,44 @@ function managedPackageResult(result) {
5826
5943
  return result;
5827
5944
  }
5828
5945
  async function runManagedPiSystem(input) {
5946
+ const { devtoolsMcpEnabled: explicitDevtools, ...runtimeInput } = input;
5947
+ const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
5829
5948
  const playwrightCliEnabled = input.targetDir === void 0 && loadPlaywrightCliPreference() === true;
5830
5949
  const projectionInput = {
5831
5950
  targetDir: input.targetDir,
5832
5951
  packageSource: PI_RUNTIME_CANDIDATE.package.source,
5833
5952
  engramBin: input.engramBin,
5834
- playwrightCliEnabled
5953
+ playwrightCliEnabled,
5954
+ devtoolsMcpEnabled,
5955
+ pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
5835
5956
  };
5836
- return runManagedPiOperation(input.operation, {
5957
+ const result = await runManagedPiOperation(input.operation, {
5837
5958
  async runPackage(operation) {
5838
- return managedPackageResult(await runPiRuntimeSystem({ ...input, operation }));
5959
+ return managedPackageResult(await runPiRuntimeSystem({ ...runtimeInput, operation }));
5839
5960
  },
5840
5961
  runProjection(operation) {
5841
- const result = runPiProjectionLifecycleSystem({
5962
+ const result2 = runPiProjectionLifecycleSystem({
5842
5963
  operation,
5843
5964
  ...projectionInput
5844
5965
  });
5845
- return Promise.resolve(result.kind === "drift" ? {
5966
+ return Promise.resolve(result2.kind === "drift" ? {
5846
5967
  kind: "drift",
5847
- paths: result.paths,
5968
+ paths: result2.paths,
5848
5969
  remedy: "Ejecuta sync --agents pi para reparar la proyecci\xF3n de Pi."
5849
- } : result);
5970
+ } : result2);
5850
5971
  },
5851
5972
  prepareProjectionUninstall() {
5852
- const result = preparePiProjectionUninstallSystem({ operation: "uninstall", ...projectionInput });
5853
- return Promise.resolve(result.kind === "prepared" ? { kind: "prepared", token: result.plan } : result);
5973
+ const result2 = preparePiProjectionUninstallSystem({ operation: "uninstall", ...projectionInput });
5974
+ return Promise.resolve(result2.kind === "prepared" ? { kind: "prepared", token: result2.plan } : result2);
5854
5975
  },
5855
5976
  completeProjectionUninstall(token) {
5856
5977
  return Promise.resolve(completePiProjectionUninstallSystem(token, { operation: "uninstall", ...projectionInput }));
5857
5978
  }
5858
5979
  });
5980
+ if (input.targetDir === void 0 && explicitDevtools !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
5981
+ saveDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi", explicitDevtools);
5982
+ }
5983
+ return result;
5859
5984
  }
5860
5985
 
5861
5986
  // src/lib/quality-runner.ts
@@ -6241,7 +6366,7 @@ import fs24 from "fs";
6241
6366
  import os5 from "os";
6242
6367
  import path33 from "path";
6243
6368
  import { execFileSync as execFileSync7 } from "child_process";
6244
- import { createHash as createHash2 } from "crypto";
6369
+ import { createHash as createHash3 } from "crypto";
6245
6370
 
6246
6371
  // src/lib/engram-release.json
6247
6372
  var engram_release_default = {
@@ -6318,7 +6443,7 @@ async function installMissingEngram(options = {}) {
6318
6443
  await reader.cancel();
6319
6444
  }
6320
6445
  const archive = Buffer.concat(chunks);
6321
- if (size !== asset.size || createHash2("sha256").update(archive).digest("hex") !== asset.sha256) {
6446
+ if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
6322
6447
  throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
6323
6448
  }
6324
6449
  fs24.mkdirSync(path33.dirname(bin), { recursive: true });
@@ -6544,8 +6669,9 @@ async function resolveDevtoolsMcpSelection(command, flags, runtimes) {
6544
6669
  }
6545
6670
  const file = devtoolsMcpPreferenceFile();
6546
6671
  const selected = await p6.multiselect({
6547
- message: "Chrome DevTools MCP avanzado (full: ~29 tools y ~5.8\u20137.7k tokens de schemas). \xBFEn qu\xE9 runtimes activarlo?",
6548
- options: runtimes.map((runtime) => ({ value: runtime, label: ADAPTERS[runtime]?.name ?? runtime })),
6672
+ message: "Chrome DevTools MCP avanzado (opcional). \xBFEn qu\xE9 runtimes activarlo?",
6673
+ required: false,
6674
+ options: runtimes.map((runtime) => ({ value: runtime, label: runtime === "pi" ? "Pi" : ADAPTERS[runtime]?.name ?? runtime })),
6549
6675
  initialValues: runtimes.filter((runtime) => loadDevtoolsMcpPreference(file, runtime))
6550
6676
  });
6551
6677
  if (p6.isCancel(selected)) return null;
@@ -6617,7 +6743,7 @@ async function resolveHostEngramForInstall(flags) {
6617
6743
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
6618
6744
  }
6619
6745
  }
6620
- async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin) {
6746
+ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled) {
6621
6747
  if (targetDir === void 0 && operation !== "models") {
6622
6748
  const preferenceErrors = browserPreferenceErrors();
6623
6749
  if (preferenceErrors.length > 0) {
@@ -6659,7 +6785,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
6659
6785
  operation,
6660
6786
  targetDir,
6661
6787
  detected: { executable: detected.executable, version: detected.version },
6662
- engramBin
6788
+ engramBin,
6789
+ ...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled }
6663
6790
  });
6664
6791
  if (result.kind === "blocked") {
6665
6792
  const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
@@ -6795,7 +6922,7 @@ Flags disponibles: jorgex-stack --help`
6795
6922
  }
6796
6923
  const mode = fileRuntimes.length > 0 ? await resolveInstallMode(flags) : void 0;
6797
6924
  if (mode === null) return;
6798
- const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, fileRuntimes);
6925
+ const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, runtimes);
6799
6926
  if (devtoolsMcpSelection === null) {
6800
6927
  exitCode = process.exitCode === 1 ? 1 : 0;
6801
6928
  return;
@@ -6852,7 +6979,8 @@ Flags disponibles: jorgex-stack --help`
6852
6979
  command,
6853
6980
  flags.targetDir,
6854
6981
  flags.yes,
6855
- flags.targetDir === void 0 ? engramBin : void 0
6982
+ flags.targetDir === void 0 ? engramBin : void 0,
6983
+ devtoolsMcpSelection.pi
6856
6984
  ));
6857
6985
  }
6858
6986
  completed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.29",
3
+ "version": "1.9.31",
4
4
  "description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI, OpenCode y Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",