jorgex-stack 1.9.32 → 1.9.33
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/README.md +12 -2
- package/dist/cli.js +140 -84
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,14 @@ pnpm dlx jorgex-stack install
|
|
|
42
42
|
pnpm dlx jorgex-stack sync
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
For a fresh Engram installation, always consult the current published Stack and bypass only the `pnpm dlx` cache:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm --config.dlx-cache-max-age=0 dlx jorgex-stack@latest install --engram
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`dlx-cache-max-age` is separate from the pnpm 11 dependency-age filter: it controls only the cached `dlx` package, while `minimumReleaseAgeExclude` applies only to the named package resolution. An explicit Stack version such as `@1.9.30` does not reuse the cache entry for another version. Do not use `@latest` for Pi; Pi consumption still requires its exact validated pin.
|
|
52
|
+
|
|
45
53
|
Other important commands:
|
|
46
54
|
|
|
47
55
|
```bash
|
|
@@ -120,7 +128,7 @@ pnpm dlx jorgex-stack@1.9.7 sync --agents pi
|
|
|
120
128
|
pnpm dlx jorgex-stack@1.9.7 uninstall --agents pi
|
|
121
129
|
```
|
|
122
130
|
|
|
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 global Playwright package and Chromium cache are shared by the machine; `--playwright-runtimes` controls which runtime receives the guide.
|
|
131
|
+
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 global Playwright package and Chromium cache are shared by the machine; `--playwright-runtimes` controls which runtime receives the guide. The pin vigente declara `playwright-handoff-v1`, por lo que Pi puede seleccionarse y recibe su handoff de Playwright además de la guía. El paquete Pi fijado también soporta el handoff opcional de Chrome DevTools; Stack persiste cada handoff como parte de la proyección y registra su SHA-256 en el receipt correspondiente tras una operación correcta. 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
132
|
|
|
125
133
|
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
134
|
|
|
@@ -136,7 +144,7 @@ Engram remains mandatory and user-owned. An existing binary is preserved. `insta
|
|
|
136
144
|
|
|
137
145
|
Históricamente, Stack `1.9.7` reconocía el receipt exacto de Pi `npm:jorgex-pi@0.8.4`. Usa versiones exactas, nunca `latest`, y no edites receipts o hashes ni borres `HOME`, Engram o la proyección de otro runtime para forzar confianza. El pin y los comandos actuales de transición y rollback están en [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
Pi puede consumirse inmediatamente después de publicar y verificar el artefacto adoptado. El pin exacto, la procedencia, los SHA-256/SHA-512, el SRI, la compatibilidad y el procedimiento de rollback siguen siendo obligatorios; no uses `latest` ni una versión aproximada.
|
|
140
148
|
|
|
141
149
|
`update --agents pi` only runs the Pi package lifecycle; it does not enter the global Stack updater. `update --check --agents pi` is a read-only Pi doctor. Uninstall runs package cleanup, backs up Pi's settings before removal, removes only the exact receipt-owned package after verifying absence, and preserves all companion/user state. Full behavior, failure states and troubleshooting are in [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
142
150
|
|
|
@@ -240,6 +248,8 @@ La integración de este App pertenece al release del repositorio Stack. No conce
|
|
|
240
248
|
|
|
241
249
|
Requirements: Node >= 22.5 and pnpm (never npm).
|
|
242
250
|
|
|
251
|
+
pnpm 11 dependency resolution uses `minimumReleaseAge=1440` by default, with `minimumReleaseAgeExclude` for `jorgex-stack` and `jorgex-pi`. Keep that exclusion in the user-level pnpm config for `pnpm dlx` launched from HOME and in each repository workspace config for local commands; preserve every unrelated existing setting. This documents the maintainer environment and does not change other users' global configuration.
|
|
252
|
+
|
|
243
253
|
```
|
|
244
254
|
pnpm install
|
|
245
255
|
pnpm build # tsup -> dist/
|
package/dist/cli.js
CHANGED
|
@@ -2826,7 +2826,11 @@ async function runInstall(opts) {
|
|
|
2826
2826
|
const pnpmBin = resolvePnpmBin();
|
|
2827
2827
|
return executePlaywrightToolAction2(action, pnpmBin, env);
|
|
2828
2828
|
},
|
|
2829
|
-
persistEnabled: (enabled) =>
|
|
2829
|
+
persistEnabled: (enabled) => {
|
|
2830
|
+
const selected = opts.playwrightToolConsent?.runtimeSelection;
|
|
2831
|
+
const fileSelection = selected === void 0 ? void 0 : Object.fromEntries(Object.entries(selected).filter(([runtime]) => runtime !== "pi"));
|
|
2832
|
+
savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled, fileSelection);
|
|
2833
|
+
},
|
|
2830
2834
|
setupPnpm: (pnpmBin) => setupPnpmGlobal(pnpmBin)
|
|
2831
2835
|
};
|
|
2832
2836
|
let setupAttempted = false;
|
|
@@ -3357,6 +3361,10 @@ function runPiPackageManagedOperation(input, deps) {
|
|
|
3357
3361
|
}
|
|
3358
3362
|
|
|
3359
3363
|
// src/lib/pi-projection-lifecycle.ts
|
|
3364
|
+
var HANDOFF_KINDS = ["devtools", "playwright"];
|
|
3365
|
+
function handoffEnabled(input, kind) {
|
|
3366
|
+
return (kind === "devtools" ? input.devtoolsMcpEnabled : input.playwrightHandoffEnabled) === true;
|
|
3367
|
+
}
|
|
3360
3368
|
var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
|
|
3361
3369
|
function projectionScope(scope) {
|
|
3362
3370
|
return {
|
|
@@ -3403,18 +3411,27 @@ function projectionPlan(input, scope) {
|
|
|
3403
3411
|
if (input.devtoolsMcpEnabled && input.pnpmBin) {
|
|
3404
3412
|
const server = loadCanonicalMcp(input.stackDir).servers[DEVTOOLS_MCP_SERVER];
|
|
3405
3413
|
if (server?.transport !== "stdio" || !Array.isArray(server.args)) throw new Error("Falta la configuraci\xF3n can\xF3nica de DevTools.");
|
|
3406
|
-
actions.push({ kind: "write", target:
|
|
3414
|
+
actions.push({ kind: "write", target: handoffPath(scope, "devtools"), content: `${JSON.stringify({
|
|
3407
3415
|
schemaVersion: 1,
|
|
3408
3416
|
enabled: true,
|
|
3409
3417
|
command: input.pnpmBin,
|
|
3410
3418
|
args: server.args
|
|
3411
3419
|
}, null, 2)}
|
|
3420
|
+
` });
|
|
3421
|
+
}
|
|
3422
|
+
if (input.playwrightHandoffEnabled && input.playwrightCliCommand) {
|
|
3423
|
+
actions.push({ kind: "write", target: handoffPath(scope, "playwright"), content: `${JSON.stringify({
|
|
3424
|
+
schemaVersion: 1,
|
|
3425
|
+
enabled: true,
|
|
3426
|
+
command: input.playwrightCliCommand,
|
|
3427
|
+
version: PLAYWRIGHT_CLI.version
|
|
3428
|
+
}, null, 2)}
|
|
3412
3429
|
` });
|
|
3413
3430
|
}
|
|
3414
3431
|
return actions;
|
|
3415
3432
|
}
|
|
3416
|
-
function
|
|
3417
|
-
return path23.resolve(scope.codingAgentDir, "jorgex-pi",
|
|
3433
|
+
function handoffPath(scope, kind) {
|
|
3434
|
+
return path23.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
|
|
3418
3435
|
}
|
|
3419
3436
|
function contentHash(content) {
|
|
3420
3437
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -3425,13 +3442,14 @@ function canonicalActionContent(action) {
|
|
|
3425
3442
|
function hasActionDrift(action, deps) {
|
|
3426
3443
|
return deps.readText(action.target) !== canonicalActionContent(action);
|
|
3427
3444
|
}
|
|
3428
|
-
function applyActions(actions, deps,
|
|
3445
|
+
function applyActions(actions, deps, handoffs = []) {
|
|
3429
3446
|
for (const action of actions) {
|
|
3430
|
-
|
|
3447
|
+
const handoff = handoffs.find((entry) => path23.resolve(action.target) === entry.file);
|
|
3448
|
+
if (handoff) {
|
|
3431
3449
|
try {
|
|
3432
|
-
if (deps.readText(handoff.file) !== handoff.previous) return
|
|
3450
|
+
if (deps.readText(handoff.file) !== handoff.previous) return handoffConflict(handoff.kind, handoff.file);
|
|
3433
3451
|
} catch {
|
|
3434
|
-
return
|
|
3452
|
+
return handoffConflict(handoff.kind, handoff.file);
|
|
3435
3453
|
}
|
|
3436
3454
|
}
|
|
3437
3455
|
if (action.kind === "write") deps.writeText(action.target, action.content);
|
|
@@ -3450,8 +3468,7 @@ function receiptFor(plan, scope) {
|
|
|
3450
3468
|
const owned = [...new Set(
|
|
3451
3469
|
plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
|
|
3452
3470
|
)];
|
|
3453
|
-
const
|
|
3454
|
-
return {
|
|
3471
|
+
const receipt = {
|
|
3455
3472
|
schemaVersion: 1,
|
|
3456
3473
|
scope: {
|
|
3457
3474
|
kind: scope.kind,
|
|
@@ -3459,9 +3476,13 @@ function receiptFor(plan, scope) {
|
|
|
3459
3476
|
codingAgentDir: scope.codingAgentDir,
|
|
3460
3477
|
receiptFile: scope.receiptFile
|
|
3461
3478
|
},
|
|
3462
|
-
owned
|
|
3463
|
-
...handoff ? { devtools: { sha256: contentHash(canonicalActionContent(handoff)) } } : {}
|
|
3479
|
+
owned
|
|
3464
3480
|
};
|
|
3481
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3482
|
+
const action = plan.find((entry) => path23.resolve(entry.target) === handoffPath(scope, kind));
|
|
3483
|
+
if (action) receipt[kind] = { sha256: contentHash(canonicalActionContent(action)) };
|
|
3484
|
+
}
|
|
3485
|
+
return receipt;
|
|
3465
3486
|
}
|
|
3466
3487
|
function receiptContent(receipt) {
|
|
3467
3488
|
return `${JSON.stringify(receipt, null, 2)}
|
|
@@ -3479,25 +3500,24 @@ function parseReceipt2(raw, expected) {
|
|
|
3479
3500
|
const version = Reflect.get(receipt, "schemaVersion");
|
|
3480
3501
|
const receivedScope = Reflect.get(receipt, "scope");
|
|
3481
3502
|
const owned = Reflect.get(receipt, "owned");
|
|
3482
|
-
const
|
|
3483
|
-
|
|
3484
|
-
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"])) {
|
|
3503
|
+
const presentKinds = HANDOFF_KINDS.filter((kind) => Object.hasOwn(receipt, kind));
|
|
3504
|
+
if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned", ...presentKinds]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
|
|
3485
3505
|
return null;
|
|
3486
3506
|
}
|
|
3487
3507
|
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)) {
|
|
3488
3508
|
return null;
|
|
3489
3509
|
}
|
|
3490
|
-
|
|
3491
|
-
const
|
|
3492
|
-
|
|
3493
|
-
|
|
3510
|
+
const digests = {};
|
|
3511
|
+
for (const kind of presentKinds) {
|
|
3512
|
+
const value = Reflect.get(receipt, kind);
|
|
3513
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || !hasExactKeys(value, ["sha256"]) || typeof Reflect.get(value, "sha256") !== "string" || !/^[a-f0-9]{64}$/.test(Reflect.get(value, "sha256"))) return null;
|
|
3514
|
+
digests[kind] = { sha256: Reflect.get(value, "sha256") };
|
|
3515
|
+
}
|
|
3516
|
+
const handoffPaths = HANDOFF_KINDS.map((kind) => handoffPath(expected.scope, kind));
|
|
3517
|
+
const expectedOwned = expected.owned.filter((file) => !handoffPaths.includes(file));
|
|
3518
|
+
for (const kind of presentKinds) expectedOwned.push(handoffPath(expected.scope, kind));
|
|
3494
3519
|
if (owned.length !== expectedOwned.length || !owned.every((file, index) => typeof file === "string" && file === expectedOwned[index])) return null;
|
|
3495
|
-
return {
|
|
3496
|
-
schemaVersion: 1,
|
|
3497
|
-
scope: expected.scope,
|
|
3498
|
-
owned: expectedOwned,
|
|
3499
|
-
...hasDevtools ? { devtools: { sha256: Reflect.get(devtools, "sha256") } } : {}
|
|
3500
|
-
};
|
|
3520
|
+
return { schemaVersion: 1, scope: expected.scope, owned: expectedOwned, ...digests };
|
|
3501
3521
|
} catch {
|
|
3502
3522
|
return null;
|
|
3503
3523
|
}
|
|
@@ -3623,34 +3643,39 @@ function readProjectionReceipt(receiptFile, deps) {
|
|
|
3623
3643
|
return { kind: "unreadable" };
|
|
3624
3644
|
}
|
|
3625
3645
|
}
|
|
3626
|
-
function
|
|
3646
|
+
function handoffConflict(kind, file) {
|
|
3647
|
+
const label = kind === "devtools" ? "DevTools" : "Playwright";
|
|
3627
3648
|
return blocked2(
|
|
3628
|
-
|
|
3649
|
+
`projection-${kind}-conflict`,
|
|
3629
3650
|
[file],
|
|
3630
|
-
|
|
3651
|
+
`El handoff de ${label} es ajeno o fue modificado. Cons\xE9rvalo y revisa su propiedad antes de reintentar; Stack no lo sobrescribe ni elimina.`
|
|
3631
3652
|
);
|
|
3632
3653
|
}
|
|
3633
|
-
function
|
|
3634
|
-
const file = devtoolsPath(scope);
|
|
3654
|
+
function inspectHandoffs(input, scope, expected, deps) {
|
|
3635
3655
|
const read = readProjectionReceipt(scope.receiptFile, deps);
|
|
3636
3656
|
if (read.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
|
|
3637
3657
|
const previous = read.kind === "present" ? parseReceipt2(read.content, expected) : null;
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
return devtoolsConflict(file);
|
|
3643
|
-
}
|
|
3644
|
-
let recordedDevtools = false;
|
|
3645
|
-
if (read.kind === "present") {
|
|
3658
|
+
const handoffs = [];
|
|
3659
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3660
|
+
const file = handoffPath(scope, kind);
|
|
3661
|
+
let content;
|
|
3646
3662
|
try {
|
|
3647
|
-
|
|
3663
|
+
content = deps.readText(file);
|
|
3648
3664
|
} catch {
|
|
3665
|
+
return handoffConflict(kind, file);
|
|
3649
3666
|
}
|
|
3650
|
-
|
|
3667
|
+
let recorded = false;
|
|
3668
|
+
if (read.kind === "present") {
|
|
3669
|
+
try {
|
|
3670
|
+
recorded = Object.hasOwn(JSON.parse(read.content), kind);
|
|
3671
|
+
} catch {
|
|
3672
|
+
}
|
|
3673
|
+
if (!previous && (handoffEnabled(input, kind) || content !== null || recorded)) return receiptInvalid(scope.receiptFile);
|
|
3674
|
+
}
|
|
3675
|
+
if (content !== null && (!previous?.[kind] || contentHash(content) !== previous[kind].sha256)) return handoffConflict(kind, file);
|
|
3676
|
+
handoffs.push({ kind, file, content });
|
|
3651
3677
|
}
|
|
3652
|
-
|
|
3653
|
-
return { kind: "checked", previous, content };
|
|
3678
|
+
return { kind: "checked", previous, handoffs };
|
|
3654
3679
|
}
|
|
3655
3680
|
function preparePiProjectionUninstall(input, deps) {
|
|
3656
3681
|
if (input.operation !== "uninstall") {
|
|
@@ -3687,14 +3712,19 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3687
3712
|
receiptFile = path23.resolve(scope.receiptFile);
|
|
3688
3713
|
backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
|
|
3689
3714
|
}
|
|
3690
|
-
const
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3715
|
+
const handoffs = [];
|
|
3716
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3717
|
+
const file = handoffPath(scope, kind);
|
|
3718
|
+
let content;
|
|
3719
|
+
try {
|
|
3720
|
+
content = deps.readText(file);
|
|
3721
|
+
} catch {
|
|
3722
|
+
return handoffConflict(kind, file);
|
|
3723
|
+
}
|
|
3724
|
+
const digest = ownedReceipt?.[kind]?.sha256;
|
|
3725
|
+
if (content !== null && (digest === void 0 || contentHash(content) !== digest)) return handoffConflict(kind, file);
|
|
3726
|
+
if (digest !== void 0) handoffs.push({ kind, file, sha256: digest });
|
|
3696
3727
|
}
|
|
3697
|
-
if (handoffContent !== null && (!ownedReceipt?.devtools || contentHash(handoffContent) !== ownedReceipt.devtools.sha256)) return devtoolsConflict(handoff);
|
|
3698
3728
|
const backup = backupExisting(backupTargets, deps);
|
|
3699
3729
|
if (backup.kind === "backup-failed") return backupFailure(backup.paths);
|
|
3700
3730
|
const token = {};
|
|
@@ -3702,7 +3732,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3702
3732
|
prompt: promptUpdate,
|
|
3703
3733
|
ownedToRemove,
|
|
3704
3734
|
receiptFile,
|
|
3705
|
-
|
|
3735
|
+
handoffs
|
|
3706
3736
|
});
|
|
3707
3737
|
return { kind: "prepared", plan: token };
|
|
3708
3738
|
}
|
|
@@ -3714,14 +3744,14 @@ function completePiProjectionUninstall(plan, deps) {
|
|
|
3714
3744
|
if (prepared === void 0) {
|
|
3715
3745
|
return cleanupFailure([]);
|
|
3716
3746
|
}
|
|
3717
|
-
|
|
3747
|
+
for (const handoff of prepared.handoffs) {
|
|
3718
3748
|
let current;
|
|
3719
3749
|
try {
|
|
3720
|
-
current = deps.readText(
|
|
3750
|
+
current = deps.readText(handoff.file);
|
|
3721
3751
|
} catch {
|
|
3722
|
-
return
|
|
3752
|
+
return handoffConflict(handoff.kind, handoff.file);
|
|
3723
3753
|
}
|
|
3724
|
-
if (current !== null && contentHash(current) !==
|
|
3754
|
+
if (current !== null && contentHash(current) !== handoff.sha256) return handoffConflict(handoff.kind, handoff.file);
|
|
3725
3755
|
}
|
|
3726
3756
|
if (prepared.prompt !== null) {
|
|
3727
3757
|
try {
|
|
@@ -3732,9 +3762,10 @@ function completePiProjectionUninstall(plan, deps) {
|
|
|
3732
3762
|
}
|
|
3733
3763
|
for (const owned of prepared.ownedToRemove) {
|
|
3734
3764
|
try {
|
|
3735
|
-
|
|
3765
|
+
const handoff = prepared.handoffs.find((entry) => entry.file === owned);
|
|
3766
|
+
if (handoff) {
|
|
3736
3767
|
const current = deps.readText(owned);
|
|
3737
|
-
if (current !== null && contentHash(current) !==
|
|
3768
|
+
if (current !== null && contentHash(current) !== handoff.sha256) return handoffConflict(handoff.kind, owned);
|
|
3738
3769
|
}
|
|
3739
3770
|
deps.removeFile(owned);
|
|
3740
3771
|
} catch {
|
|
@@ -3757,22 +3788,27 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3757
3788
|
return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
|
|
3758
3789
|
}
|
|
3759
3790
|
if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path23.isAbsolute(input.pnpmBin))) {
|
|
3760
|
-
return blocked2("projection-devtools-command", [
|
|
3791
|
+
return blocked2("projection-devtools-command", [handoffPath(scope, "devtools")], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
|
|
3792
|
+
}
|
|
3793
|
+
if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path23.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
|
|
3794
|
+
return blocked2("projection-playwright-command", [handoffPath(scope, "playwright")], "Playwright requiere un ejecutable absoluto verificado. Abre una terminal nueva tras pnpm setup y reintenta install --playwright.");
|
|
3761
3795
|
}
|
|
3762
3796
|
const plan = projectionPlan(input, scope);
|
|
3763
3797
|
assertPlanContained(plan, scope);
|
|
3764
3798
|
const receipt = receiptFor(plan, scope);
|
|
3765
|
-
const
|
|
3766
|
-
if (
|
|
3767
|
-
const
|
|
3799
|
+
const checked = inspectHandoffs(input, scope, receipt, deps);
|
|
3800
|
+
if (checked.kind === "blocked") return input.operation === "doctor" ? { kind: "drift", paths: checked.paths } : checked;
|
|
3801
|
+
const removedHandoffs = checked.handoffs.filter(({ kind, content }) => !handoffEnabled(input, kind) && checked.previous?.[kind] !== void 0 && content !== null);
|
|
3768
3802
|
const expectedReceipt2 = receiptContent(receipt);
|
|
3769
3803
|
const drifted = plan.filter((action) => hasActionDrift(action, deps));
|
|
3770
|
-
|
|
3771
|
-
|
|
3804
|
+
for (const { kind, file } of checked.handoffs) {
|
|
3805
|
+
if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path23.resolve(action.target) === file)) {
|
|
3806
|
+
return input.operation === "doctor" ? { kind: "drift", paths: [file] } : handoffConflict(kind, file);
|
|
3807
|
+
}
|
|
3772
3808
|
}
|
|
3773
3809
|
if (input.operation === "doctor") {
|
|
3774
3810
|
const paths = drifted.map((action) => path23.resolve(action.target));
|
|
3775
|
-
|
|
3811
|
+
paths.push(...removedHandoffs.map(({ file }) => file));
|
|
3776
3812
|
const currentSettings2 = deps.readText(scope.settingsFile);
|
|
3777
3813
|
if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
|
|
3778
3814
|
paths.push(scope.settingsFile);
|
|
@@ -3788,30 +3824,38 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3788
3824
|
}
|
|
3789
3825
|
const packageWillChange = filteredSettings !== null && filteredSettings !== currentSettings;
|
|
3790
3826
|
const receiptChanged = deps.readText(scope.receiptFile) !== expectedReceipt2;
|
|
3791
|
-
if (drifted.length > 0 || packageWillChange || receiptChanged ||
|
|
3827
|
+
if (drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0) {
|
|
3792
3828
|
const backup = backupExisting([
|
|
3793
3829
|
...drifted.map((action) => action.target),
|
|
3794
|
-
...
|
|
3830
|
+
...removedHandoffs.map(({ file }) => file),
|
|
3795
3831
|
...packageWillChange ? [scope.settingsFile] : [],
|
|
3796
3832
|
...receiptChanged ? [scope.receiptFile] : []
|
|
3797
3833
|
], deps);
|
|
3798
3834
|
if (backup.kind === "backup-failed") return backupFailure(backup.paths);
|
|
3799
3835
|
}
|
|
3800
|
-
|
|
3836
|
+
for (const { kind, file } of removedHandoffs) {
|
|
3837
|
+
try {
|
|
3838
|
+
const current = deps.readText(file);
|
|
3839
|
+
if (current !== null && contentHash(current) !== checked.previous?.[kind]?.sha256) return handoffConflict(kind, file);
|
|
3840
|
+
} catch {
|
|
3841
|
+
return handoffConflict(kind, file);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
for (const { kind, file } of removedHandoffs) {
|
|
3801
3845
|
try {
|
|
3802
|
-
const current = deps.readText(
|
|
3803
|
-
if (current !== null && contentHash(current) !==
|
|
3804
|
-
deps.removeFile(
|
|
3846
|
+
const current = deps.readText(file);
|
|
3847
|
+
if (current !== null && contentHash(current) !== checked.previous?.[kind]?.sha256) return handoffConflict(kind, file);
|
|
3848
|
+
deps.removeFile(file);
|
|
3805
3849
|
} catch {
|
|
3806
|
-
return cleanupFailure([
|
|
3850
|
+
return cleanupFailure([file]);
|
|
3807
3851
|
}
|
|
3808
3852
|
}
|
|
3809
|
-
const writeResult = applyActions(drifted, deps,
|
|
3853
|
+
const writeResult = applyActions(drifted, deps, checked.handoffs.filter(({ kind }) => handoffEnabled(input, kind)).map(({ kind, file, content }) => ({ kind, file, previous: content })));
|
|
3810
3854
|
if (writeResult) return writeResult;
|
|
3811
3855
|
if (packageWillChange && filteredSettings !== null) deps.writeText(scope.settingsFile, filteredSettings);
|
|
3812
3856
|
if (receiptChanged) deps.writeText(scope.receiptFile, expectedReceipt2);
|
|
3813
3857
|
if (input.operation === "install") return { kind: "installed", receipt };
|
|
3814
|
-
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged ||
|
|
3858
|
+
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0 };
|
|
3815
3859
|
}
|
|
3816
3860
|
function systemProjectionLifecycle(input) {
|
|
3817
3861
|
const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
|
|
@@ -3830,7 +3874,9 @@ function systemProjectionLifecycle(input) {
|
|
|
3830
3874
|
engramBin: input.engramBin ?? "",
|
|
3831
3875
|
playwrightCliEnabled: input.playwrightCliEnabled,
|
|
3832
3876
|
devtoolsMcpEnabled: input.devtoolsMcpEnabled,
|
|
3833
|
-
pnpmBin: input.pnpmBin
|
|
3877
|
+
pnpmBin: input.pnpmBin,
|
|
3878
|
+
playwrightHandoffEnabled: input.playwrightHandoffEnabled,
|
|
3879
|
+
playwrightCliCommand: input.playwrightCliCommand
|
|
3834
3880
|
},
|
|
3835
3881
|
deps: {
|
|
3836
3882
|
readText: readTextOnlyIfMissing,
|
|
@@ -5425,16 +5471,16 @@ import { createHash as createHash2 } from "crypto";
|
|
|
5425
5471
|
var pi_runtime_pin_default = {
|
|
5426
5472
|
package: {
|
|
5427
5473
|
name: "jorgex-pi",
|
|
5428
|
-
version: "0.8.
|
|
5429
|
-
source: "npm:jorgex-pi@0.8.
|
|
5474
|
+
version: "0.8.15",
|
|
5475
|
+
source: "npm:jorgex-pi@0.8.15"
|
|
5430
5476
|
},
|
|
5431
5477
|
provenance: {
|
|
5432
|
-
commit: "
|
|
5478
|
+
commit: "9d619e205685df0b8b122aa24b81e8694182e3ca"
|
|
5433
5479
|
},
|
|
5434
5480
|
tarball: {
|
|
5435
|
-
bytes:
|
|
5436
|
-
sha256: "
|
|
5437
|
-
sha512: "
|
|
5481
|
+
bytes: 89145074,
|
|
5482
|
+
sha256: "a866da924e0d7ad2b5a5ef285671c06b742d5b64cd62d463a8a39d8e7b5d4453",
|
|
5483
|
+
sha512: "ac5a70bcf336bd28eccb06360a8874c2a3bb2887fe420b6f97a0b61ca1b1c97d2db26fd28033fa296ab273423b1b5884d123dfc56ad1d6e274aefd828d9d25c6"
|
|
5438
5484
|
}
|
|
5439
5485
|
};
|
|
5440
5486
|
|
|
@@ -5457,6 +5503,7 @@ var PI_RUNTIME_CANDIDATE = {
|
|
|
5457
5503
|
"mcp-adapter-v1",
|
|
5458
5504
|
"engram-runtime-tools-v1",
|
|
5459
5505
|
"chrome-devtools-handoff-v1",
|
|
5506
|
+
"playwright-handoff-v1",
|
|
5460
5507
|
"runner-json-v1",
|
|
5461
5508
|
"tui-branding-v1",
|
|
5462
5509
|
"managed-primary-model-v1",
|
|
@@ -6031,14 +6078,18 @@ function managedPackageResult(result) {
|
|
|
6031
6078
|
return result;
|
|
6032
6079
|
}
|
|
6033
6080
|
async function runManagedPiSystem(input) {
|
|
6034
|
-
const { devtoolsMcpEnabled: explicitDevtools, ...runtimeInput } = input;
|
|
6081
|
+
const { devtoolsMcpEnabled: explicitDevtools, playwrightCliEnabled: explicitPlaywright, ...runtimeInput } = input;
|
|
6035
6082
|
const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
|
|
6036
|
-
const
|
|
6083
|
+
const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
|
|
6084
|
+
const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
|
|
6085
|
+
const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
|
|
6037
6086
|
const projectionInput = {
|
|
6038
6087
|
targetDir: input.targetDir,
|
|
6039
6088
|
packageSource: PI_RUNTIME_CANDIDATE.package.source,
|
|
6040
6089
|
engramBin: input.engramBin,
|
|
6041
6090
|
playwrightCliEnabled,
|
|
6091
|
+
playwrightHandoffEnabled: playwrightCliEnabled,
|
|
6092
|
+
playwrightCliCommand,
|
|
6042
6093
|
devtoolsMcpEnabled,
|
|
6043
6094
|
pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
|
|
6044
6095
|
};
|
|
@@ -6068,6 +6119,9 @@ async function runManagedPiSystem(input) {
|
|
|
6068
6119
|
if (input.targetDir === void 0 && explicitDevtools !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
|
|
6069
6120
|
saveDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi", explicitDevtools);
|
|
6070
6121
|
}
|
|
6122
|
+
if (input.targetDir === void 0 && supportsPlaywright && explicitPlaywright !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
|
|
6123
|
+
savePlaywrightCliPreference(playwrightCliPreferenceFile(), true, { pi: explicitPlaywright });
|
|
6124
|
+
}
|
|
6071
6125
|
return result;
|
|
6072
6126
|
}
|
|
6073
6127
|
|
|
@@ -6875,7 +6929,7 @@ async function resolveHostEngramForInstall(flags) {
|
|
|
6875
6929
|
return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
|
|
6876
6930
|
}
|
|
6877
6931
|
}
|
|
6878
|
-
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled) {
|
|
6932
|
+
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, playwrightCliEnabled) {
|
|
6879
6933
|
if (targetDir === void 0 && operation !== "models") {
|
|
6880
6934
|
const preferenceErrors = browserPreferenceErrors();
|
|
6881
6935
|
if (preferenceErrors.length > 0) {
|
|
@@ -6918,7 +6972,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
|
|
|
6918
6972
|
targetDir,
|
|
6919
6973
|
detected: { executable: detected.executable, version: detected.version },
|
|
6920
6974
|
engramBin,
|
|
6921
|
-
...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled }
|
|
6975
|
+
...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled },
|
|
6976
|
+
...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled }
|
|
6922
6977
|
});
|
|
6923
6978
|
if (result.kind === "blocked") {
|
|
6924
6979
|
const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
|
|
@@ -7121,7 +7176,8 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7121
7176
|
flags.targetDir,
|
|
7122
7177
|
flags.yes,
|
|
7123
7178
|
flags.targetDir === void 0 ? engramBin : void 0,
|
|
7124
|
-
devtoolsMcpSelection.pi
|
|
7179
|
+
devtoolsMcpSelection.pi,
|
|
7180
|
+
flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
|
|
7125
7181
|
));
|
|
7126
7182
|
}
|
|
7127
7183
|
completed = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jorgex-stack",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.33",
|
|
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",
|