jorgex-stack 1.9.35 → 1.9.37
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 +10 -2
- package/dist/cli.js +931 -693
- package/package.json +1 -1
- package/stack/system-prompt/AGENTS.md +0 -10
- package/stack/system-prompt/writing-style.md +31 -0
package/dist/cli.js
CHANGED
|
@@ -15,133 +15,80 @@ import {
|
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
17
|
import * as p6 from "@clack/prompts";
|
|
18
|
-
import
|
|
18
|
+
import fs26 from "fs";
|
|
19
19
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
20
20
|
|
|
21
21
|
// src/install.ts
|
|
22
|
-
import
|
|
23
|
-
import
|
|
22
|
+
import fs16 from "fs";
|
|
23
|
+
import path21 from "path";
|
|
24
24
|
import * as p from "@clack/prompts";
|
|
25
25
|
|
|
26
|
-
// src/
|
|
27
|
-
import path6 from "path";
|
|
26
|
+
// src/lib/writing-style.ts
|
|
28
27
|
import fs3 from "fs";
|
|
29
|
-
import { isDeepStrictEqual } from "util";
|
|
30
|
-
import { pathToFileURL } from "url";
|
|
31
|
-
|
|
32
|
-
// src/lib/canonical.ts
|
|
33
|
-
import path from "path";
|
|
34
|
-
import fs from "fs";
|
|
35
|
-
function parseCanonicalAgent(source, file) {
|
|
36
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
|
|
37
|
-
if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
|
|
38
|
-
const fields = {};
|
|
39
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
40
|
-
const sep = line.indexOf(":");
|
|
41
|
-
if (sep === -1) continue;
|
|
42
|
-
fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
43
|
-
}
|
|
44
|
-
const required = ["name", "description", "mode", "tier"];
|
|
45
|
-
for (const key of required) {
|
|
46
|
-
if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
name: fields.name,
|
|
50
|
-
description: fields.description,
|
|
51
|
-
mode: fields.mode === "primary" ? "primary" : "subagent",
|
|
52
|
-
tier: fields.tier,
|
|
53
|
-
readonly: fields.readonly === "true",
|
|
54
|
-
bash: fields.bash ?? "full",
|
|
55
|
-
spawn: fields.spawn !== "false",
|
|
56
|
-
body: source.slice(match[0].length)
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function loadCanonicalAgents(agentsDir) {
|
|
60
|
-
return fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
|
|
61
|
-
const file = path.join(agentsDir, f);
|
|
62
|
-
const source = fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
|
|
63
|
-
return parseCanonicalAgent(source, file);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
var DEVTOOLS_MCP_SERVER = "chrome-devtools";
|
|
67
|
-
function isCanonicalMcpServerEnabled(name, server, enabledServers) {
|
|
68
|
-
return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
|
|
69
|
-
}
|
|
70
|
-
function loadCanonicalMcp(stackDir) {
|
|
71
|
-
return JSON.parse(fs.readFileSync(path.join(stackDir, "mcp", "servers.json"), "utf8"));
|
|
72
|
-
}
|
|
73
|
-
function loadCanonicalHooks(stackDir) {
|
|
74
|
-
return JSON.parse(fs.readFileSync(path.join(stackDir, "hooks", "hooks.json"), "utf8"));
|
|
75
|
-
}
|
|
76
|
-
function loadCanonicalDefaults(stackDir) {
|
|
77
|
-
const file = path.join(stackDir, "config", "defaults.json");
|
|
78
|
-
if (!fs.existsSync(file)) return {};
|
|
79
|
-
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
80
|
-
delete parsed["$comment"];
|
|
81
|
-
return parsed;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/lib/model-map.ts
|
|
85
28
|
import path3 from "path";
|
|
86
|
-
import { existsSync } from "fs";
|
|
87
29
|
|
|
88
|
-
// src/lib/
|
|
30
|
+
// src/lib/backup.ts
|
|
89
31
|
import fs2 from "fs";
|
|
90
32
|
import path2 from "path";
|
|
33
|
+
import crypto from "crypto";
|
|
34
|
+
|
|
35
|
+
// src/lib/fsx.ts
|
|
36
|
+
import fs from "fs";
|
|
37
|
+
import path from "path";
|
|
91
38
|
function readTextIfExists(file) {
|
|
92
39
|
try {
|
|
93
|
-
return
|
|
40
|
+
return fs.readFileSync(file, "utf8");
|
|
94
41
|
} catch {
|
|
95
42
|
return null;
|
|
96
43
|
}
|
|
97
44
|
}
|
|
98
45
|
function ensureDir(dir) {
|
|
99
|
-
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
100
47
|
}
|
|
101
48
|
var tmpCounter = 0;
|
|
102
49
|
function tmpPath(target) {
|
|
103
|
-
return
|
|
50
|
+
return path.join(path.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
|
|
104
51
|
}
|
|
105
52
|
function renameInto(tmp, target) {
|
|
106
53
|
try {
|
|
107
|
-
|
|
54
|
+
fs.renameSync(tmp, target);
|
|
108
55
|
} catch (err) {
|
|
109
|
-
|
|
56
|
+
fs.rmSync(tmp, { force: true });
|
|
110
57
|
throw err;
|
|
111
58
|
}
|
|
112
59
|
}
|
|
113
|
-
function writeText(file, content) {
|
|
114
|
-
ensureDir(
|
|
60
|
+
function writeText(file, content, mode) {
|
|
61
|
+
ensureDir(path.dirname(file));
|
|
115
62
|
const tmp = tmpPath(file);
|
|
116
|
-
|
|
63
|
+
fs.writeFileSync(tmp, content, { encoding: "utf8", mode });
|
|
117
64
|
renameInto(tmp, file);
|
|
118
65
|
}
|
|
119
66
|
function copyFile(source, target) {
|
|
120
|
-
ensureDir(
|
|
67
|
+
ensureDir(path.dirname(target));
|
|
121
68
|
const tmp = tmpPath(target);
|
|
122
|
-
|
|
69
|
+
fs.copyFileSync(source, tmp);
|
|
123
70
|
renameInto(tmp, target);
|
|
124
71
|
}
|
|
125
72
|
function isContainedIn(child, root) {
|
|
126
|
-
const rel =
|
|
127
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
73
|
+
const rel = path.relative(path.resolve(root), path.resolve(child));
|
|
74
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
|
|
128
75
|
}
|
|
129
76
|
function pruneEmptyDirs(file, root) {
|
|
130
|
-
let dir =
|
|
77
|
+
let dir = path.dirname(file);
|
|
131
78
|
while (dir.startsWith(root) && dir !== root) {
|
|
132
79
|
try {
|
|
133
|
-
if (
|
|
134
|
-
|
|
80
|
+
if (fs.readdirSync(dir).length > 0) return;
|
|
81
|
+
fs.rmdirSync(dir);
|
|
135
82
|
} catch {
|
|
136
83
|
return;
|
|
137
84
|
}
|
|
138
|
-
dir =
|
|
85
|
+
dir = path.dirname(dir);
|
|
139
86
|
}
|
|
140
87
|
}
|
|
141
88
|
function sameFileContent(a, b) {
|
|
142
89
|
try {
|
|
143
|
-
const bufA =
|
|
144
|
-
const bufB =
|
|
90
|
+
const bufA = fs.readFileSync(a);
|
|
91
|
+
const bufB = fs.readFileSync(b);
|
|
145
92
|
return bufA.equals(bufB);
|
|
146
93
|
} catch {
|
|
147
94
|
return false;
|
|
@@ -149,10 +96,10 @@ function sameFileContent(a, b) {
|
|
|
149
96
|
}
|
|
150
97
|
function listFilesRecursive(dir) {
|
|
151
98
|
const out = [];
|
|
152
|
-
if (!
|
|
99
|
+
if (!fs.existsSync(dir)) return out;
|
|
153
100
|
const walk = (d) => {
|
|
154
|
-
for (const entry of
|
|
155
|
-
const p7 =
|
|
101
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
102
|
+
const p7 = path.join(d, entry.name);
|
|
156
103
|
if (entry.isDirectory()) walk(p7);
|
|
157
104
|
else out.push(p7);
|
|
158
105
|
}
|
|
@@ -161,143 +108,77 @@ function listFilesRecursive(dir) {
|
|
|
161
108
|
return out;
|
|
162
109
|
}
|
|
163
110
|
|
|
164
|
-
// src/lib/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!override) return base;
|
|
169
|
-
return {
|
|
170
|
-
model: override.model || base.model,
|
|
171
|
-
variant: "variant" in override ? override.variant || void 0 : base.variant
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
var DEFAULT_MODEL_MAP = {
|
|
175
|
-
"claude-code": {
|
|
176
|
-
strong: { model: "fable" },
|
|
177
|
-
standard: { model: "sonnet" },
|
|
178
|
-
cheap: { model: "haiku" }
|
|
179
|
-
},
|
|
180
|
-
// El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
|
|
181
|
-
// la skill de la app heredan el modelo elegido por el usuario. Estos defaults
|
|
182
|
-
// son solo para subagentes; variant → model_reasoning_effort.
|
|
183
|
-
codex: {
|
|
184
|
-
strong: { model: "gpt-6-astra", variant: "low" },
|
|
185
|
-
standard: { model: "gpt-5.6-luna", variant: "max" },
|
|
186
|
-
cheap: { model: "gpt-5.6-luna", variant: "medium" }
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
function modelMapFile() {
|
|
190
|
-
return path3.join(dataDir(), "model-map.json");
|
|
111
|
+
// src/lib/backup.ts
|
|
112
|
+
var KEEP_BACKUPS = 10;
|
|
113
|
+
function backupsRoot() {
|
|
114
|
+
return path2.join(dataDir(), "backups");
|
|
191
115
|
}
|
|
192
|
-
function
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
fromDisk = JSON.parse(raw);
|
|
199
|
-
} catch {
|
|
200
|
-
throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
|
|
201
|
-
}
|
|
202
|
-
const merged = { ...fromDisk };
|
|
203
|
-
for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
|
|
204
|
-
merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
|
|
205
|
-
if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
|
|
116
|
+
function compositeChecksum(files) {
|
|
117
|
+
const hash = crypto.createHash("sha256");
|
|
118
|
+
for (const file of [...files].sort()) {
|
|
119
|
+
const content = crypto.createHash("sha256").update(fs2.readFileSync(file)).digest("hex");
|
|
120
|
+
hash.update(`${file}:${content}
|
|
121
|
+
`);
|
|
206
122
|
}
|
|
207
|
-
return
|
|
123
|
+
return hash.digest("hex");
|
|
208
124
|
}
|
|
209
|
-
function
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
|
|
125
|
+
function createBackup(files, label, root = backupsRoot()) {
|
|
126
|
+
const existing = [...new Set(files)].filter((f) => fs2.existsSync(f));
|
|
127
|
+
if (existing.length === 0) return null;
|
|
128
|
+
const checksum = compositeChecksum(existing);
|
|
129
|
+
const latest = listBackups(root)[0];
|
|
130
|
+
if (latest?.checksum === checksum) return latest;
|
|
131
|
+
const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
|
|
132
|
+
let id = base;
|
|
133
|
+
let dir = path2.join(root, id);
|
|
134
|
+
for (let n = 1; fs2.existsSync(dir); n++) {
|
|
135
|
+
id = `${base}-${n}`;
|
|
136
|
+
dir = path2.join(root, id);
|
|
213
137
|
}
|
|
214
|
-
|
|
138
|
+
ensureDir(path2.join(dir, "files"));
|
|
139
|
+
const entries = existing.map((original, i) => {
|
|
140
|
+
const stored = path2.join(dir, "files", `${String(i).padStart(4, "0")}-${path2.basename(original)}`);
|
|
141
|
+
fs2.copyFileSync(original, stored);
|
|
142
|
+
return { original, stored };
|
|
143
|
+
});
|
|
144
|
+
const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
|
|
145
|
+
writeText(path2.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
|
|
146
|
+
pruneBackups(root);
|
|
147
|
+
return info;
|
|
215
148
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
|
|
149
|
+
function listBackups(root = backupsRoot()) {
|
|
150
|
+
if (!fs2.existsSync(root)) return [];
|
|
151
|
+
const infos = [];
|
|
152
|
+
for (const entry of fs2.readdirSync(root, { withFileTypes: true })) {
|
|
153
|
+
if (!entry.isDirectory()) continue;
|
|
154
|
+
const manifest = readTextIfExists(path2.join(root, entry.name, "manifest.json"));
|
|
155
|
+
if (manifest === null) continue;
|
|
156
|
+
try {
|
|
157
|
+
infos.push(JSON.parse(manifest));
|
|
158
|
+
} catch {
|
|
159
|
+
infos.push({ id: entry.name, label: "(manifest corrupto)", createdAt: "", files: [] });
|
|
228
160
|
}
|
|
229
161
|
}
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
function planDetectedBinCommand(bin, args) {
|
|
233
|
-
if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
|
|
234
|
-
if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
|
|
235
|
-
const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
|
|
236
|
-
return {
|
|
237
|
-
command: process.env.ComSpec ?? "cmd.exe",
|
|
238
|
-
args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
|
|
239
|
-
};
|
|
162
|
+
return infos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
240
163
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} catch {
|
|
252
|
-
return null;
|
|
164
|
+
function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
165
|
+
const info = listBackups(root).find((b) => b.id === id);
|
|
166
|
+
if (!info) throw new Error(`Backup no encontrado: ${id}`);
|
|
167
|
+
let restored = 0;
|
|
168
|
+
for (const { original, stored } of info.files) {
|
|
169
|
+
if (!fs2.existsSync(stored)) continue;
|
|
170
|
+
if (!isContainedIn(original, boundary)) continue;
|
|
171
|
+
ensureDir(path2.dirname(original));
|
|
172
|
+
fs2.copyFileSync(stored, original);
|
|
173
|
+
restored++;
|
|
253
174
|
}
|
|
175
|
+
return restored;
|
|
254
176
|
}
|
|
255
|
-
function
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
name: "OpenCode",
|
|
261
|
-
installed: binPath !== null || existsSync2(configDir),
|
|
262
|
-
binPath,
|
|
263
|
-
configDir
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
function detectClaudeCode() {
|
|
267
|
-
const configDir = path4.join(HOME, ".claude");
|
|
268
|
-
const binPath = lookPath("claude");
|
|
269
|
-
return {
|
|
270
|
-
id: "claude-code",
|
|
271
|
-
name: "Claude Code",
|
|
272
|
-
installed: binPath !== null || existsSync2(configDir),
|
|
273
|
-
binPath,
|
|
274
|
-
configDir
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
function detectCodex() {
|
|
278
|
-
const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
|
|
279
|
-
const binPath = lookPath("codex");
|
|
280
|
-
return {
|
|
281
|
-
id: "codex",
|
|
282
|
-
name: "Codex CLI",
|
|
283
|
-
installed: binPath !== null || existsSync2(configDir),
|
|
284
|
-
binPath,
|
|
285
|
-
configDir
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
function detectEngram() {
|
|
289
|
-
const fromEnv = process.env.ENGRAM_BIN;
|
|
290
|
-
if (fromEnv && existsSync2(fromEnv)) return fromEnv;
|
|
291
|
-
const onPath = lookPath("engram");
|
|
292
|
-
if (onPath) return onPath;
|
|
293
|
-
const candidates = [
|
|
294
|
-
path4.join(HOME, "go", "bin", "engram.exe"),
|
|
295
|
-
path4.join(HOME, "go", "bin", "engram"),
|
|
296
|
-
path4.join(HOME, ".local", "bin", "engram.exe"),
|
|
297
|
-
path4.join(HOME, ".local", "bin", "engram")
|
|
298
|
-
];
|
|
299
|
-
for (const c of candidates) if (existsSync2(c)) return c;
|
|
300
|
-
return null;
|
|
177
|
+
function pruneBackups(root) {
|
|
178
|
+
const all = listBackups(root);
|
|
179
|
+
for (const old of all.slice(KEEP_BACKUPS)) {
|
|
180
|
+
fs2.rmSync(path2.join(root, old.id), { recursive: true, force: true });
|
|
181
|
+
}
|
|
301
182
|
}
|
|
302
183
|
|
|
303
184
|
// src/lib/filemerge.ts
|
|
@@ -331,7 +212,7 @@ ${close}`;
|
|
|
331
212
|
if (start !== -1 && end !== -1 && end > start) {
|
|
332
213
|
return existing.slice(0, start) + block + existing.slice(end + close.length);
|
|
333
214
|
}
|
|
334
|
-
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
215
|
+
const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
335
216
|
return existing + sep + block + "\n";
|
|
336
217
|
}
|
|
337
218
|
function removeMarkdownSection(existing, name) {
|
|
@@ -513,18 +394,313 @@ function removeTomlSection(existing, section) {
|
|
|
513
394
|
if (result.trim() === "") return "";
|
|
514
395
|
return result.endsWith(eol) ? result : result + eol;
|
|
515
396
|
}
|
|
516
|
-
function readTomlSection(existing, section) {
|
|
517
|
-
if (existing === null) return null;
|
|
518
|
-
const lines = existing.split(/\r?\n/);
|
|
519
|
-
const found = findTomlSection(lines, section);
|
|
520
|
-
if (found === null) return null;
|
|
521
|
-
return lines.slice(found.start + 1, found.end).join("\n");
|
|
397
|
+
function readTomlSection(existing, section) {
|
|
398
|
+
if (existing === null) return null;
|
|
399
|
+
const lines = existing.split(/\r?\n/);
|
|
400
|
+
const found = findTomlSection(lines, section);
|
|
401
|
+
if (found === null) return null;
|
|
402
|
+
return lines.slice(found.start + 1, found.end).join("\n");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/lib/writing-style.ts
|
|
406
|
+
var DEFAULT_SECTION = "writing-style-default";
|
|
407
|
+
var DEFAULT_OPEN = `<!-- jorgex:${DEFAULT_SECTION} -->`;
|
|
408
|
+
var DEFAULT_CLOSE = `<!-- /jorgex:${DEFAULT_SECTION} -->`;
|
|
409
|
+
function assertContained(file, rootDir) {
|
|
410
|
+
const relative = path3.relative(fs3.realpathSync(rootDir), fs3.realpathSync(file));
|
|
411
|
+
if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
|
|
412
|
+
throw new Error(`La ruta de estilo ${file} sale del destino aislado.`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function validateBackupRoot(rootDir) {
|
|
416
|
+
if (rootDir === void 0) return;
|
|
417
|
+
const backupRoot = path3.join(rootDir, "backups");
|
|
418
|
+
try {
|
|
419
|
+
fs3.lstatSync(backupRoot);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error.code === "ENOENT") return;
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
assertContained(backupRoot, rootDir);
|
|
425
|
+
if (!fs3.statSync(backupRoot).isDirectory()) throw new Error(`El destino de backups ${backupRoot} no es un directorio.`);
|
|
426
|
+
}
|
|
427
|
+
function resolveWritingStyleFile(options = {}) {
|
|
428
|
+
return path3.join(options.targetDir ?? options.stateDir ?? dataDir(), "writing-style.md");
|
|
429
|
+
}
|
|
430
|
+
function readStyleText(sourcePath, options = {}) {
|
|
431
|
+
let stat;
|
|
432
|
+
try {
|
|
433
|
+
stat = fs3.lstatSync(sourcePath);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error.code === "ENOENT") return null;
|
|
436
|
+
throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
|
|
437
|
+
}
|
|
438
|
+
if (options.rootDir !== void 0) {
|
|
439
|
+
assertContained(sourcePath, options.rootDir);
|
|
440
|
+
}
|
|
441
|
+
if (!(stat.isSymbolicLink() ? fs3.statSync(sourcePath) : stat).isFile()) {
|
|
442
|
+
throw new Error(`La fuente de estilo ${sourcePath} debe ser un archivo.`);
|
|
443
|
+
}
|
|
444
|
+
let bytes;
|
|
445
|
+
try {
|
|
446
|
+
bytes = fs3.readFileSync(sourcePath);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
throw new Error(`No se puede leer el estilo ${sourcePath}. Revisa los permisos del archivo.`, { cause: error });
|
|
449
|
+
}
|
|
450
|
+
let content;
|
|
451
|
+
try {
|
|
452
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
453
|
+
} catch {
|
|
454
|
+
throw new Error(`La fuente de estilo ${sourcePath} debe usar UTF-8 v\xE1lido.`);
|
|
455
|
+
}
|
|
456
|
+
return content;
|
|
457
|
+
}
|
|
458
|
+
function readWritingStyle(sourcePath, options = {}) {
|
|
459
|
+
const content = readStyleText(sourcePath, options)?.replace(/\r\n?/g, "\n").trim() ?? "";
|
|
460
|
+
if (content.includes("jorgex:")) throw new Error(`La fuente de estilo ${sourcePath} contiene marcadores jorgex: reservados.`);
|
|
461
|
+
return { sourcePath, content: content || null };
|
|
462
|
+
}
|
|
463
|
+
function prepareWritingStyle(sourcePath, options = {}) {
|
|
464
|
+
const canonicalPath = path3.join(options.stackDir ?? stackRoot(), "system-prompt", "writing-style.md");
|
|
465
|
+
const canonical = readWritingStyle(canonicalPath).content;
|
|
466
|
+
if (canonical === null) throw new Error(`Falta el estilo can\xF3nico o est\xE1 vac\xEDo en ${canonicalPath}; repara la instalaci\xF3n de Stack.`);
|
|
467
|
+
const originalContent = readStyleText(sourcePath, options);
|
|
468
|
+
if (originalContent?.includes("jorgex:")) {
|
|
469
|
+
if (!hasHealthyManagedMarkdownMarkers(originalContent, DEFAULT_SECTION) || originalContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").includes("jorgex:")) {
|
|
470
|
+
throw new Error(`Marcadores de estilo ambiguos o desconocidos en ${sourcePath}; revisa el bloque ${DEFAULT_SECTION}.`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const installedContent = upsertMarkdownSection(originalContent, DEFAULT_SECTION, canonical);
|
|
474
|
+
if (originalContent !== null && originalContent !== installedContent) validateBackupRoot(options.rootDir);
|
|
475
|
+
const content = installedContent.replace(DEFAULT_OPEN, "").replace(DEFAULT_CLOSE, "").replace(/\r\n?/g, "\n").trim();
|
|
476
|
+
return {
|
|
477
|
+
sourcePath,
|
|
478
|
+
content,
|
|
479
|
+
canonicalPath,
|
|
480
|
+
originalContent,
|
|
481
|
+
installedContent,
|
|
482
|
+
rootDir: options.rootDir,
|
|
483
|
+
backupRoot: options.rootDir === void 0 ? void 0 : path3.join(options.rootDir, "backups")
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function applyWritingStyle(plan, dryRun = false) {
|
|
487
|
+
if (dryRun) return;
|
|
488
|
+
const current = readStyleText(plan.sourcePath, { rootDir: plan.rootDir });
|
|
489
|
+
if (current === plan.installedContent) return;
|
|
490
|
+
if (current !== plan.originalContent) {
|
|
491
|
+
throw new Error(`El estilo ${plan.sourcePath} cambi\xF3 durante la preparaci\xF3n; vuelve a ejecutar el comando.`);
|
|
492
|
+
}
|
|
493
|
+
if (current !== null) {
|
|
494
|
+
validateBackupRoot(plan.rootDir);
|
|
495
|
+
createBackup([plan.sourcePath], "writing-style", plan.backupRoot);
|
|
496
|
+
}
|
|
497
|
+
const mode = current === null ? 384 : fs3.statSync(plan.sourcePath).mode & 511;
|
|
498
|
+
writeText(plan.sourcePath, plan.installedContent, mode);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/adapters/opencode.ts
|
|
502
|
+
import path8 from "path";
|
|
503
|
+
import fs5 from "fs";
|
|
504
|
+
import { isDeepStrictEqual } from "util";
|
|
505
|
+
import { pathToFileURL } from "url";
|
|
506
|
+
|
|
507
|
+
// src/lib/canonical.ts
|
|
508
|
+
import path4 from "path";
|
|
509
|
+
import fs4 from "fs";
|
|
510
|
+
function parseCanonicalAgent(source, file) {
|
|
511
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
|
|
512
|
+
if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
|
|
513
|
+
const fields = {};
|
|
514
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
515
|
+
const sep = line.indexOf(":");
|
|
516
|
+
if (sep === -1) continue;
|
|
517
|
+
fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
|
|
518
|
+
}
|
|
519
|
+
const required = ["name", "description", "mode", "tier"];
|
|
520
|
+
for (const key of required) {
|
|
521
|
+
if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
name: fields.name,
|
|
525
|
+
description: fields.description,
|
|
526
|
+
mode: fields.mode === "primary" ? "primary" : "subagent",
|
|
527
|
+
tier: fields.tier,
|
|
528
|
+
readonly: fields.readonly === "true",
|
|
529
|
+
bash: fields.bash ?? "full",
|
|
530
|
+
spawn: fields.spawn !== "false",
|
|
531
|
+
body: source.slice(match[0].length)
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function loadCanonicalAgents(agentsDir) {
|
|
535
|
+
return fs4.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
|
|
536
|
+
const file = path4.join(agentsDir, f);
|
|
537
|
+
const source = fs4.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
|
|
538
|
+
return parseCanonicalAgent(source, file);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
var DEVTOOLS_MCP_SERVER = "chrome-devtools";
|
|
542
|
+
function isCanonicalMcpServerEnabled(name, server, enabledServers) {
|
|
543
|
+
return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
|
|
544
|
+
}
|
|
545
|
+
function loadCanonicalMcp(stackDir) {
|
|
546
|
+
return JSON.parse(fs4.readFileSync(path4.join(stackDir, "mcp", "servers.json"), "utf8"));
|
|
547
|
+
}
|
|
548
|
+
function loadCanonicalHooks(stackDir) {
|
|
549
|
+
return JSON.parse(fs4.readFileSync(path4.join(stackDir, "hooks", "hooks.json"), "utf8"));
|
|
550
|
+
}
|
|
551
|
+
function loadCanonicalDefaults(stackDir) {
|
|
552
|
+
const file = path4.join(stackDir, "config", "defaults.json");
|
|
553
|
+
if (!fs4.existsSync(file)) return {};
|
|
554
|
+
const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
555
|
+
delete parsed["$comment"];
|
|
556
|
+
return parsed;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/lib/model-map.ts
|
|
560
|
+
import path5 from "path";
|
|
561
|
+
import { existsSync } from "fs";
|
|
562
|
+
function resolveAgentModel(models, agentName, tier) {
|
|
563
|
+
const base = models[tier];
|
|
564
|
+
const override = models.overrides?.[agentName];
|
|
565
|
+
if (!override) return base;
|
|
566
|
+
return {
|
|
567
|
+
model: override.model || base.model,
|
|
568
|
+
variant: "variant" in override ? override.variant || void 0 : base.variant
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
var DEFAULT_MODEL_MAP = {
|
|
572
|
+
"claude-code": {
|
|
573
|
+
strong: { model: "fable" },
|
|
574
|
+
standard: { model: "sonnet" },
|
|
575
|
+
cheap: { model: "haiku" }
|
|
576
|
+
},
|
|
577
|
+
// El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
|
|
578
|
+
// la skill de la app heredan el modelo elegido por el usuario. Estos defaults
|
|
579
|
+
// son solo para subagentes; variant → model_reasoning_effort.
|
|
580
|
+
codex: {
|
|
581
|
+
strong: { model: "gpt-6-astra", variant: "low" },
|
|
582
|
+
standard: { model: "gpt-5.6-luna", variant: "max" },
|
|
583
|
+
cheap: { model: "gpt-5.6-luna", variant: "medium" }
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function modelMapFile() {
|
|
587
|
+
return path5.join(dataDir(), "model-map.json");
|
|
588
|
+
}
|
|
589
|
+
function loadModelMap() {
|
|
590
|
+
const file = modelMapFile();
|
|
591
|
+
const raw = readTextIfExists(file);
|
|
592
|
+
if (raw === null) return DEFAULT_MODEL_MAP;
|
|
593
|
+
let fromDisk;
|
|
594
|
+
try {
|
|
595
|
+
fromDisk = JSON.parse(raw);
|
|
596
|
+
} catch {
|
|
597
|
+
throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
|
|
598
|
+
}
|
|
599
|
+
const merged = { ...fromDisk };
|
|
600
|
+
for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
|
|
601
|
+
merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
|
|
602
|
+
if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
|
|
603
|
+
}
|
|
604
|
+
return merged;
|
|
605
|
+
}
|
|
606
|
+
function ensureModelMapFile() {
|
|
607
|
+
const file = modelMapFile();
|
|
608
|
+
if (!existsSync(file)) {
|
|
609
|
+
writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
|
|
610
|
+
}
|
|
611
|
+
return file;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/lib/detect.ts
|
|
615
|
+
import path6 from "path";
|
|
616
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
617
|
+
import { execFileSync } from "child_process";
|
|
618
|
+
function lookPath(cmd) {
|
|
619
|
+
const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
|
|
620
|
+
for (const dir of (process.env.PATH ?? "").split(path6.delimiter)) {
|
|
621
|
+
if (!dir) continue;
|
|
622
|
+
for (const ext of exts) {
|
|
623
|
+
const candidate = path6.join(dir, cmd + ext);
|
|
624
|
+
if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
function planDetectedBinCommand(bin, args) {
|
|
630
|
+
if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
|
|
631
|
+
if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
|
|
632
|
+
const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
|
|
633
|
+
return {
|
|
634
|
+
command: process.env.ComSpec ?? "cmd.exe",
|
|
635
|
+
args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function runDetectedBin(bin, args, timeoutMs, envOverrides) {
|
|
639
|
+
try {
|
|
640
|
+
const command = planDetectedBinCommand(bin, args);
|
|
641
|
+
if (command === null) return null;
|
|
642
|
+
return execFileSync(command.command, command.args, {
|
|
643
|
+
encoding: "utf8",
|
|
644
|
+
timeout: timeoutMs,
|
|
645
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
646
|
+
...envOverrides === void 0 ? {} : { env: { ...process.env, ...envOverrides } }
|
|
647
|
+
});
|
|
648
|
+
} catch {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function detectOpenCode() {
|
|
653
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode");
|
|
654
|
+
const binPath = lookPath("opencode");
|
|
655
|
+
return {
|
|
656
|
+
id: "opencode",
|
|
657
|
+
name: "OpenCode",
|
|
658
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
659
|
+
binPath,
|
|
660
|
+
configDir
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function detectClaudeCode() {
|
|
664
|
+
const configDir = path6.join(HOME, ".claude");
|
|
665
|
+
const binPath = lookPath("claude");
|
|
666
|
+
return {
|
|
667
|
+
id: "claude-code",
|
|
668
|
+
name: "Claude Code",
|
|
669
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
670
|
+
binPath,
|
|
671
|
+
configDir
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function detectCodex() {
|
|
675
|
+
const configDir = process.env.CODEX_HOME ?? path6.join(HOME, ".codex");
|
|
676
|
+
const binPath = lookPath("codex");
|
|
677
|
+
return {
|
|
678
|
+
id: "codex",
|
|
679
|
+
name: "Codex CLI",
|
|
680
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
681
|
+
binPath,
|
|
682
|
+
configDir
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function detectEngram() {
|
|
686
|
+
const fromEnv = process.env.ENGRAM_BIN;
|
|
687
|
+
if (fromEnv && existsSync2(fromEnv)) return fromEnv;
|
|
688
|
+
const onPath = lookPath("engram");
|
|
689
|
+
if (onPath) return onPath;
|
|
690
|
+
const candidates = [
|
|
691
|
+
path6.join(HOME, "go", "bin", "engram.exe"),
|
|
692
|
+
path6.join(HOME, "go", "bin", "engram"),
|
|
693
|
+
path6.join(HOME, ".local", "bin", "engram.exe"),
|
|
694
|
+
path6.join(HOME, ".local", "bin", "engram")
|
|
695
|
+
];
|
|
696
|
+
for (const c of candidates) if (existsSync2(c)) return c;
|
|
697
|
+
return null;
|
|
522
698
|
}
|
|
523
699
|
|
|
524
700
|
// src/lib/hooks-format.ts
|
|
525
|
-
import
|
|
701
|
+
import path7 from "path";
|
|
526
702
|
function hookScriptNames(canonical) {
|
|
527
|
-
return Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) =>
|
|
703
|
+
return Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path7.basename(s));
|
|
528
704
|
}
|
|
529
705
|
function plainHookCommands(canonical) {
|
|
530
706
|
return new Set(
|
|
@@ -571,7 +747,7 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
|
|
|
571
747
|
...h.timeout !== void 0 ? { timeout: h.timeout } : {}
|
|
572
748
|
}))
|
|
573
749
|
};
|
|
574
|
-
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) =>
|
|
750
|
+
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path7.basename(s));
|
|
575
751
|
const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
|
|
576
752
|
const index = list.findIndex((existingEntry) => {
|
|
577
753
|
const e = existingEntry;
|
|
@@ -715,7 +891,7 @@ function pruneEmpty(parent, key) {
|
|
|
715
891
|
if (value !== null && Object.keys(value).length === 0) delete parent[key];
|
|
716
892
|
}
|
|
717
893
|
function hasOpenCodeManualApproval(configDir) {
|
|
718
|
-
const content = readTextIfExists(
|
|
894
|
+
const content = readTextIfExists(path8.join(configDir, "opencode.json"));
|
|
719
895
|
if (content === null) return false;
|
|
720
896
|
try {
|
|
721
897
|
const root = JSON.parse(content);
|
|
@@ -731,7 +907,7 @@ var opencodeAdapter = {
|
|
|
731
907
|
name: "OpenCode",
|
|
732
908
|
detect: detectOpenCode,
|
|
733
909
|
reportCapabilities(configDir) {
|
|
734
|
-
const prompt = readTextIfExists(
|
|
910
|
+
const prompt = readTextIfExists(path8.join(configDir, "AGENTS.md"));
|
|
735
911
|
return createLocalCapabilityReport("opencode", [
|
|
736
912
|
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
737
913
|
id: "policy-guidance",
|
|
@@ -753,16 +929,16 @@ var opencodeAdapter = {
|
|
|
753
929
|
paths(configDir) {
|
|
754
930
|
const isRealConfigDir = samePath(
|
|
755
931
|
configDir,
|
|
756
|
-
process.env.OPENCODE_CONFIG_DIR ??
|
|
932
|
+
process.env.OPENCODE_CONFIG_DIR ?? path8.join(HOME, ".config", "opencode")
|
|
757
933
|
);
|
|
758
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
934
|
+
const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
|
|
759
935
|
return {
|
|
760
|
-
systemPromptFile:
|
|
761
|
-
agentsDir:
|
|
762
|
-
skillsDir:
|
|
763
|
-
commandsDir:
|
|
764
|
-
pluginsDir:
|
|
765
|
-
scriptsDir:
|
|
936
|
+
systemPromptFile: path8.join(configDir, "AGENTS.md"),
|
|
937
|
+
agentsDir: path8.join(configDir, "agents"),
|
|
938
|
+
skillsDir: path8.join(agentsHome, ".agents", "skills"),
|
|
939
|
+
commandsDir: path8.join(configDir, "commands"),
|
|
940
|
+
pluginsDir: path8.join(configDir, "plugins"),
|
|
941
|
+
scriptsDir: path8.join(configDir, "scripts"),
|
|
766
942
|
outputStylesDir: null,
|
|
767
943
|
profilesDir: null
|
|
768
944
|
};
|
|
@@ -820,12 +996,12 @@ ${agent.body}`,
|
|
|
820
996
|
ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
|
|
821
997
|
continue;
|
|
822
998
|
}
|
|
823
|
-
const script = `scripts/${
|
|
999
|
+
const script = `scripts/${path8.basename(match[1])}`;
|
|
824
1000
|
(bashEntries[includes] ??= []).push(script);
|
|
825
1001
|
}
|
|
826
1002
|
}
|
|
827
1003
|
}
|
|
828
|
-
const hooksFile =
|
|
1004
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
829
1005
|
const content = upsertJson(readTextIfExists(hooksFile), (root) => {
|
|
830
1006
|
const afterValue = root["tool.execute.after"] ??= {};
|
|
831
1007
|
if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
|
|
@@ -860,16 +1036,16 @@ ${agent.body}`,
|
|
|
860
1036
|
}
|
|
861
1037
|
});
|
|
862
1038
|
actions.push({ kind: "write", target: hooksFile, content });
|
|
863
|
-
const scriptsSource =
|
|
864
|
-
if (
|
|
865
|
-
for (const f of
|
|
866
|
-
actions.push({ kind: "copy", source:
|
|
1039
|
+
const scriptsSource = path8.join(ctx.stackDir, "scripts");
|
|
1040
|
+
if (fs5.existsSync(scriptsSource)) {
|
|
1041
|
+
for (const f of fs5.readdirSync(scriptsSource)) {
|
|
1042
|
+
actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
|
|
867
1043
|
}
|
|
868
1044
|
}
|
|
869
1045
|
return actions;
|
|
870
1046
|
},
|
|
871
1047
|
planMainConfig(canonical, ctx) {
|
|
872
|
-
const file =
|
|
1048
|
+
const file = path8.join(ctx.configDir, "opencode.json");
|
|
873
1049
|
const { pluginsDir } = this.paths(ctx.configDir);
|
|
874
1050
|
const original = readTextIfExists(file);
|
|
875
1051
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
@@ -979,9 +1155,10 @@ ${agent.body}`,
|
|
|
979
1155
|
let content = removeMarkdownSection(prompt, "system-prompt");
|
|
980
1156
|
content = removeMarkdownSection(content, "engram-protocol");
|
|
981
1157
|
content = removeMarkdownSection(content, "browser");
|
|
1158
|
+
content = removeMarkdownSection(content, "writing-style");
|
|
982
1159
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
983
1160
|
}
|
|
984
|
-
const configFile =
|
|
1161
|
+
const configFile = path8.join(ctx.configDir, "opencode.json");
|
|
985
1162
|
const config = readTextIfExists(configFile);
|
|
986
1163
|
if (config !== null) {
|
|
987
1164
|
const mcpOwnership = [];
|
|
@@ -1049,7 +1226,7 @@ ${agent.body}`,
|
|
|
1049
1226
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
1050
1227
|
});
|
|
1051
1228
|
}
|
|
1052
|
-
const hooksFile =
|
|
1229
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
1053
1230
|
const hooksJson = readTextIfExists(hooksFile);
|
|
1054
1231
|
if (hooksJson !== null) {
|
|
1055
1232
|
const ourScripts = hookScriptNames(hooks);
|
|
@@ -1075,8 +1252,8 @@ ${agent.body}`,
|
|
|
1075
1252
|
};
|
|
1076
1253
|
|
|
1077
1254
|
// src/adapters/claude-code.ts
|
|
1078
|
-
import
|
|
1079
|
-
import
|
|
1255
|
+
import path9 from "path";
|
|
1256
|
+
import fs6 from "fs";
|
|
1080
1257
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1081
1258
|
function yamlString2(value) {
|
|
1082
1259
|
return JSON.stringify(value);
|
|
@@ -1092,7 +1269,7 @@ function isRecord(value) {
|
|
|
1092
1269
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1093
1270
|
}
|
|
1094
1271
|
function hasClaudeManualApproval(configDir) {
|
|
1095
|
-
const content = readTextIfExists(
|
|
1272
|
+
const content = readTextIfExists(path9.join(configDir, "settings.json"));
|
|
1096
1273
|
if (content === null) return false;
|
|
1097
1274
|
try {
|
|
1098
1275
|
const root = JSON.parse(content);
|
|
@@ -1112,8 +1289,8 @@ function toolsFor(agent) {
|
|
|
1112
1289
|
return tools.join(", ");
|
|
1113
1290
|
}
|
|
1114
1291
|
function hasEngramPlugin(configDir) {
|
|
1115
|
-
if (
|
|
1116
|
-
const registry = readTextIfExists(
|
|
1292
|
+
if (fs6.existsSync(path9.join(configDir, "plugins", "marketplaces", "engram"))) return true;
|
|
1293
|
+
const registry = readTextIfExists(path9.join(configDir, "plugins", "installed_plugins.json"));
|
|
1117
1294
|
if (registry === null) return false;
|
|
1118
1295
|
try {
|
|
1119
1296
|
const parsed = JSON.parse(registry);
|
|
@@ -1136,7 +1313,7 @@ var claudeCodeAdapter = {
|
|
|
1136
1313
|
name: "Claude Code",
|
|
1137
1314
|
detect: detectClaudeCode,
|
|
1138
1315
|
reportCapabilities(configDir) {
|
|
1139
|
-
const prompt = readTextIfExists(
|
|
1316
|
+
const prompt = readTextIfExists(path9.join(configDir, "CLAUDE.md"));
|
|
1140
1317
|
return createLocalCapabilityReport("claude-code", [
|
|
1141
1318
|
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
1142
1319
|
id: "policy-guidance",
|
|
@@ -1157,13 +1334,13 @@ var claudeCodeAdapter = {
|
|
|
1157
1334
|
},
|
|
1158
1335
|
paths(configDir) {
|
|
1159
1336
|
return {
|
|
1160
|
-
systemPromptFile:
|
|
1161
|
-
agentsDir:
|
|
1162
|
-
skillsDir:
|
|
1163
|
-
commandsDir:
|
|
1337
|
+
systemPromptFile: path9.join(configDir, "CLAUDE.md"),
|
|
1338
|
+
agentsDir: path9.join(configDir, "agents"),
|
|
1339
|
+
skillsDir: path9.join(configDir, "skills"),
|
|
1340
|
+
commandsDir: path9.join(configDir, "commands"),
|
|
1164
1341
|
pluginsDir: null,
|
|
1165
|
-
scriptsDir:
|
|
1166
|
-
outputStylesDir:
|
|
1342
|
+
scriptsDir: path9.join(configDir, "scripts"),
|
|
1343
|
+
outputStylesDir: path9.join(configDir, "output-styles"),
|
|
1167
1344
|
profilesDir: null
|
|
1168
1345
|
};
|
|
1169
1346
|
},
|
|
@@ -1209,9 +1386,9 @@ ${agent.body}`,
|
|
|
1209
1386
|
planHooks(canonical, ctx) {
|
|
1210
1387
|
const actions = [];
|
|
1211
1388
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1212
|
-
const original = readTextIfExists(
|
|
1389
|
+
const original = readTextIfExists(path9.join(ctx.configDir, "settings.json"));
|
|
1213
1390
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1214
|
-
const settingsFile =
|
|
1391
|
+
const settingsFile = path9.join(ctx.configDir, "settings.json");
|
|
1215
1392
|
let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
|
|
1216
1393
|
const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
|
|
1217
1394
|
if (contentSource === null && defaults?.["permissions"] !== void 0) {
|
|
@@ -1225,16 +1402,16 @@ ${agent.body}`,
|
|
|
1225
1402
|
});
|
|
1226
1403
|
}
|
|
1227
1404
|
actions.push({ kind: "write", target: settingsFile, content });
|
|
1228
|
-
const scriptsSource =
|
|
1229
|
-
if (
|
|
1230
|
-
for (const f of
|
|
1231
|
-
actions.push({ kind: "copy", source:
|
|
1405
|
+
const scriptsSource = path9.join(ctx.stackDir, "scripts");
|
|
1406
|
+
if (fs6.existsSync(scriptsSource)) {
|
|
1407
|
+
for (const f of fs6.readdirSync(scriptsSource)) {
|
|
1408
|
+
actions.push({ kind: "copy", source: path9.join(scriptsSource, f), target: path9.join(scriptsDir, f) });
|
|
1232
1409
|
}
|
|
1233
1410
|
}
|
|
1234
1411
|
return actions;
|
|
1235
1412
|
},
|
|
1236
1413
|
planMainConfig(canonical, ctx) {
|
|
1237
|
-
const file =
|
|
1414
|
+
const file = path9.join(path9.dirname(ctx.configDir), `${path9.basename(ctx.configDir)}.json`);
|
|
1238
1415
|
const mcpOwnership = [];
|
|
1239
1416
|
const content = upsertJson(readTextIfExists(file), (root) => {
|
|
1240
1417
|
const servers = root["mcpServers"] ??= {};
|
|
@@ -1297,9 +1474,10 @@ ${agent.body}`,
|
|
|
1297
1474
|
let content = removeMarkdownSection(prompt, "system-prompt");
|
|
1298
1475
|
content = removeMarkdownSection(content, "engram-protocol");
|
|
1299
1476
|
content = removeMarkdownSection(content, "browser");
|
|
1477
|
+
content = removeMarkdownSection(content, "writing-style");
|
|
1300
1478
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1301
1479
|
}
|
|
1302
|
-
const settingsFile =
|
|
1480
|
+
const settingsFile = path9.join(ctx.configDir, "settings.json");
|
|
1303
1481
|
const settings = readTextIfExists(settingsFile);
|
|
1304
1482
|
if (settings !== null) {
|
|
1305
1483
|
const content = removeNativeHooks(settings, hooks);
|
|
@@ -1307,7 +1485,7 @@ ${agent.body}`,
|
|
|
1307
1485
|
actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
|
|
1308
1486
|
}
|
|
1309
1487
|
}
|
|
1310
|
-
const mainFile =
|
|
1488
|
+
const mainFile = path9.join(path9.dirname(ctx.configDir), `${path9.basename(ctx.configDir)}.json`);
|
|
1311
1489
|
const main2 = readTextIfExists(mainFile);
|
|
1312
1490
|
if (main2 !== null) {
|
|
1313
1491
|
const mcpOwnership = [];
|
|
@@ -1333,8 +1511,8 @@ ${agent.body}`,
|
|
|
1333
1511
|
};
|
|
1334
1512
|
|
|
1335
1513
|
// src/adapters/codex.ts
|
|
1336
|
-
import
|
|
1337
|
-
import
|
|
1514
|
+
import path10 from "path";
|
|
1515
|
+
import fs7 from "fs";
|
|
1338
1516
|
function tomlString(value) {
|
|
1339
1517
|
return JSON.stringify(value);
|
|
1340
1518
|
}
|
|
@@ -1357,15 +1535,15 @@ function isManagedOptionalStdioServer3(server, section) {
|
|
|
1357
1535
|
return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
|
|
1358
1536
|
}
|
|
1359
1537
|
function hasActiveEngramPlugin(configDir) {
|
|
1360
|
-
const config = readTextIfExists(
|
|
1538
|
+
const config = readTextIfExists(path10.join(configDir, "config.toml"));
|
|
1361
1539
|
if (config === null) return false;
|
|
1362
1540
|
const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
|
|
1363
1541
|
return match !== null && !/enabled\s*=\s*false/.test(match[1]);
|
|
1364
1542
|
}
|
|
1365
1543
|
function hasEngramProtocol(configDir) {
|
|
1366
1544
|
if (hasActiveEngramPlugin(configDir)) return true;
|
|
1367
|
-
if (
|
|
1368
|
-
const config = readTextIfExists(
|
|
1545
|
+
if (fs7.existsSync(path10.join(configDir, "engram-instructions.md"))) return true;
|
|
1546
|
+
const config = readTextIfExists(path10.join(configDir, "config.toml"));
|
|
1369
1547
|
return config !== null && /engram-instructions\.md/.test(config);
|
|
1370
1548
|
}
|
|
1371
1549
|
var CODEX_JSON_STRING = String.raw`"(?:\\.|[^"\\\r\n])*"`;
|
|
@@ -1490,7 +1668,7 @@ function hasCanonicalCodexPermissionProfile(scan) {
|
|
|
1490
1668
|
return true;
|
|
1491
1669
|
}
|
|
1492
1670
|
function hasCodexManualApproval(configDir) {
|
|
1493
|
-
const config = readTextIfExists(
|
|
1671
|
+
const config = readTextIfExists(path10.join(configDir, "config.toml"));
|
|
1494
1672
|
if (config === null) return false;
|
|
1495
1673
|
const scan = scanCodexToml(config);
|
|
1496
1674
|
if (scan === null) return false;
|
|
@@ -1508,7 +1686,7 @@ var codexAdapter = {
|
|
|
1508
1686
|
name: "Codex CLI",
|
|
1509
1687
|
detect: detectCodex,
|
|
1510
1688
|
reportCapabilities(configDir) {
|
|
1511
|
-
const prompt = readTextIfExists(
|
|
1689
|
+
const prompt = readTextIfExists(path10.join(configDir, "AGENTS.md"));
|
|
1512
1690
|
return createLocalCapabilityReport("codex", [
|
|
1513
1691
|
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
1514
1692
|
id: "policy-guidance",
|
|
@@ -1533,18 +1711,18 @@ var codexAdapter = {
|
|
|
1533
1711
|
return !hasEngramProtocol(ctx.configDir);
|
|
1534
1712
|
},
|
|
1535
1713
|
paths(configDir) {
|
|
1536
|
-
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ??
|
|
1537
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
1538
|
-
const skillsDir =
|
|
1714
|
+
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path10.join(HOME, ".codex"));
|
|
1715
|
+
const agentsHome = isRealConfigDir ? HOME : path10.dirname(configDir);
|
|
1716
|
+
const skillsDir = path10.join(agentsHome, ".agents", "skills");
|
|
1539
1717
|
return {
|
|
1540
|
-
systemPromptFile:
|
|
1541
|
-
agentsDir:
|
|
1718
|
+
systemPromptFile: path10.join(configDir, "AGENTS.md"),
|
|
1719
|
+
agentsDir: path10.join(configDir, "agents"),
|
|
1542
1720
|
skillsDir,
|
|
1543
1721
|
// Los custom prompts de Codex están deprecados: los commands se
|
|
1544
1722
|
// instalan como skills (renderCommand produce <nombre>/SKILL.md).
|
|
1545
1723
|
commandsDir: skillsDir,
|
|
1546
1724
|
pluginsDir: null,
|
|
1547
|
-
scriptsDir:
|
|
1725
|
+
scriptsDir: path10.join(configDir, "scripts"),
|
|
1548
1726
|
outputStylesDir: null,
|
|
1549
1727
|
profilesDir: configDir
|
|
1550
1728
|
};
|
|
@@ -1592,7 +1770,7 @@ ${body}`
|
|
|
1592
1770
|
planHooks(canonical, ctx) {
|
|
1593
1771
|
const actions = [];
|
|
1594
1772
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1595
|
-
const hooksFile =
|
|
1773
|
+
const hooksFile = path10.join(ctx.configDir, "hooks.json");
|
|
1596
1774
|
const content = upsertNativeHooks(
|
|
1597
1775
|
readTextIfExists(hooksFile),
|
|
1598
1776
|
canonical,
|
|
@@ -1603,16 +1781,16 @@ ${body}`
|
|
|
1603
1781
|
ctx.warnings.push(
|
|
1604
1782
|
"Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
|
|
1605
1783
|
);
|
|
1606
|
-
const scriptsSource =
|
|
1607
|
-
if (
|
|
1608
|
-
for (const f of
|
|
1609
|
-
actions.push({ kind: "copy", source:
|
|
1784
|
+
const scriptsSource = path10.join(ctx.stackDir, "scripts");
|
|
1785
|
+
if (fs7.existsSync(scriptsSource)) {
|
|
1786
|
+
for (const f of fs7.readdirSync(scriptsSource)) {
|
|
1787
|
+
actions.push({ kind: "copy", source: path10.join(scriptsSource, f), target: path10.join(scriptsDir, f) });
|
|
1610
1788
|
}
|
|
1611
1789
|
}
|
|
1612
1790
|
return actions;
|
|
1613
1791
|
},
|
|
1614
1792
|
planMainConfig(canonical, ctx) {
|
|
1615
|
-
const file =
|
|
1793
|
+
const file = path10.join(ctx.configDir, "config.toml");
|
|
1616
1794
|
const original = readTextIfExists(file);
|
|
1617
1795
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1618
1796
|
let content = contentSource;
|
|
@@ -1729,9 +1907,10 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1729
1907
|
let content = removeMarkdownSection(prompt, "system-prompt");
|
|
1730
1908
|
content = removeMarkdownSection(content, "engram-protocol");
|
|
1731
1909
|
content = removeMarkdownSection(content, "browser");
|
|
1910
|
+
content = removeMarkdownSection(content, "writing-style");
|
|
1732
1911
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1733
1912
|
}
|
|
1734
|
-
const configFile =
|
|
1913
|
+
const configFile = path10.join(ctx.configDir, "config.toml");
|
|
1735
1914
|
const config = readTextIfExists(configFile);
|
|
1736
1915
|
if (config !== null) {
|
|
1737
1916
|
let content = config;
|
|
@@ -1766,7 +1945,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1766
1945
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
1767
1946
|
});
|
|
1768
1947
|
}
|
|
1769
|
-
const hooksFile =
|
|
1948
|
+
const hooksFile = path10.join(ctx.configDir, "hooks.json");
|
|
1770
1949
|
const hooksJson = readTextIfExists(hooksFile);
|
|
1771
1950
|
if (hooksJson !== null) {
|
|
1772
1951
|
const content = removeNativeHooks(hooksJson, hooks);
|
|
@@ -1779,8 +1958,8 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1779
1958
|
};
|
|
1780
1959
|
|
|
1781
1960
|
// src/lib/install-mode.ts
|
|
1782
|
-
import
|
|
1783
|
-
import
|
|
1961
|
+
import fs8 from "fs";
|
|
1962
|
+
import path11 from "path";
|
|
1784
1963
|
var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
|
|
1785
1964
|
var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
1786
1965
|
var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
@@ -1788,10 +1967,10 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
|
1788
1967
|
subagentConcurrency: "serial"
|
|
1789
1968
|
};
|
|
1790
1969
|
function installModePreferenceFile() {
|
|
1791
|
-
return
|
|
1970
|
+
return path11.join(dataDir(), "install-mode.json");
|
|
1792
1971
|
}
|
|
1793
1972
|
function hasInstallModePreference(file = installModePreferenceFile()) {
|
|
1794
|
-
return
|
|
1973
|
+
return fs8.existsSync(file);
|
|
1795
1974
|
}
|
|
1796
1975
|
function isInstallMode(value) {
|
|
1797
1976
|
return INSTALL_MODES.has(value);
|
|
@@ -1822,9 +2001,9 @@ function normalizeInstallModePreference(value) {
|
|
|
1822
2001
|
throw new Error("Preferencia de instalaci\xF3n inv\xE1lida o corrupta.");
|
|
1823
2002
|
}
|
|
1824
2003
|
function loadInstallModePreference(file = installModePreferenceFile()) {
|
|
1825
|
-
if (!
|
|
2004
|
+
if (!fs8.existsSync(file)) return DEFAULT_INSTALL_MODE_PREFERENCE;
|
|
1826
2005
|
try {
|
|
1827
|
-
const raw =
|
|
2006
|
+
const raw = fs8.readFileSync(file, "utf8");
|
|
1828
2007
|
return normalizeInstallModePreference(JSON.parse(raw));
|
|
1829
2008
|
} catch (error) {
|
|
1830
2009
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1861,87 +2040,11 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
|
|
|
1861
2040
|
return {};
|
|
1862
2041
|
}
|
|
1863
2042
|
|
|
1864
|
-
// src/lib/backup.ts
|
|
1865
|
-
import fs7 from "fs";
|
|
1866
|
-
import path10 from "path";
|
|
1867
|
-
import crypto from "crypto";
|
|
1868
|
-
var KEEP_BACKUPS = 10;
|
|
1869
|
-
function backupsRoot() {
|
|
1870
|
-
return path10.join(dataDir(), "backups");
|
|
1871
|
-
}
|
|
1872
|
-
function compositeChecksum(files) {
|
|
1873
|
-
const hash = crypto.createHash("sha256");
|
|
1874
|
-
for (const file of [...files].sort()) {
|
|
1875
|
-
const content = crypto.createHash("sha256").update(fs7.readFileSync(file)).digest("hex");
|
|
1876
|
-
hash.update(`${file}:${content}
|
|
1877
|
-
`);
|
|
1878
|
-
}
|
|
1879
|
-
return hash.digest("hex");
|
|
1880
|
-
}
|
|
1881
|
-
function createBackup(files, label, root = backupsRoot()) {
|
|
1882
|
-
const existing = [...new Set(files)].filter((f) => fs7.existsSync(f));
|
|
1883
|
-
if (existing.length === 0) return null;
|
|
1884
|
-
const checksum = compositeChecksum(existing);
|
|
1885
|
-
const latest = listBackups(root)[0];
|
|
1886
|
-
if (latest?.checksum === checksum) return latest;
|
|
1887
|
-
const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
|
|
1888
|
-
let id = base;
|
|
1889
|
-
let dir = path10.join(root, id);
|
|
1890
|
-
for (let n = 1; fs7.existsSync(dir); n++) {
|
|
1891
|
-
id = `${base}-${n}`;
|
|
1892
|
-
dir = path10.join(root, id);
|
|
1893
|
-
}
|
|
1894
|
-
ensureDir(path10.join(dir, "files"));
|
|
1895
|
-
const entries = existing.map((original, i) => {
|
|
1896
|
-
const stored = path10.join(dir, "files", `${String(i).padStart(4, "0")}-${path10.basename(original)}`);
|
|
1897
|
-
fs7.copyFileSync(original, stored);
|
|
1898
|
-
return { original, stored };
|
|
1899
|
-
});
|
|
1900
|
-
const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
|
|
1901
|
-
writeText(path10.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
|
|
1902
|
-
pruneBackups(root);
|
|
1903
|
-
return info;
|
|
1904
|
-
}
|
|
1905
|
-
function listBackups(root = backupsRoot()) {
|
|
1906
|
-
if (!fs7.existsSync(root)) return [];
|
|
1907
|
-
const infos = [];
|
|
1908
|
-
for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
1909
|
-
if (!entry.isDirectory()) continue;
|
|
1910
|
-
const manifest = readTextIfExists(path10.join(root, entry.name, "manifest.json"));
|
|
1911
|
-
if (manifest === null) continue;
|
|
1912
|
-
try {
|
|
1913
|
-
infos.push(JSON.parse(manifest));
|
|
1914
|
-
} catch {
|
|
1915
|
-
infos.push({ id: entry.name, label: "(manifest corrupto)", createdAt: "", files: [] });
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
return infos.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1919
|
-
}
|
|
1920
|
-
function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
1921
|
-
const info = listBackups(root).find((b) => b.id === id);
|
|
1922
|
-
if (!info) throw new Error(`Backup no encontrado: ${id}`);
|
|
1923
|
-
let restored = 0;
|
|
1924
|
-
for (const { original, stored } of info.files) {
|
|
1925
|
-
if (!fs7.existsSync(stored)) continue;
|
|
1926
|
-
if (!isContainedIn(original, boundary)) continue;
|
|
1927
|
-
ensureDir(path10.dirname(original));
|
|
1928
|
-
fs7.copyFileSync(stored, original);
|
|
1929
|
-
restored++;
|
|
1930
|
-
}
|
|
1931
|
-
return restored;
|
|
1932
|
-
}
|
|
1933
|
-
function pruneBackups(root) {
|
|
1934
|
-
const all = listBackups(root);
|
|
1935
|
-
for (const old of all.slice(KEEP_BACKUPS)) {
|
|
1936
|
-
fs7.rmSync(path10.join(root, old.id), { recursive: true, force: true });
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
2043
|
// src/lib/manifest.ts
|
|
1941
|
-
import
|
|
1942
|
-
import
|
|
2044
|
+
import fs9 from "fs";
|
|
2045
|
+
import path12 from "path";
|
|
1943
2046
|
function manifestFile() {
|
|
1944
|
-
return
|
|
2047
|
+
return path12.join(dataDir(), "manifest.json");
|
|
1945
2048
|
}
|
|
1946
2049
|
function readManifest(file = manifestFile()) {
|
|
1947
2050
|
const raw = readTextIfExists(file);
|
|
@@ -1965,24 +2068,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
|
|
|
1965
2068
|
writeText(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
1966
2069
|
}
|
|
1967
2070
|
function findOrphans(prevOwned, currentTargets, root = HOME) {
|
|
1968
|
-
return prevOwned.map((f) =>
|
|
1969
|
-
(f) => !currentTargets.has(f) &&
|
|
2071
|
+
return prevOwned.map((f) => path12.resolve(f)).filter(
|
|
2072
|
+
(f) => !currentTargets.has(f) && path12.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs9.existsSync(f)
|
|
1970
2073
|
);
|
|
1971
2074
|
}
|
|
1972
2075
|
|
|
1973
2076
|
// src/components/system-prompt.ts
|
|
1974
|
-
import
|
|
1975
|
-
import
|
|
2077
|
+
import path14 from "path";
|
|
2078
|
+
import fs11 from "fs";
|
|
1976
2079
|
|
|
1977
2080
|
// src/lib/mode-composition.ts
|
|
1978
|
-
import
|
|
1979
|
-
import
|
|
2081
|
+
import fs10 from "fs";
|
|
2082
|
+
import path13 from "path";
|
|
1980
2083
|
var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
|
|
1981
2084
|
var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
|
|
1982
2085
|
var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
|
|
1983
2086
|
var normalize = (value) => value.replace(/\r\n/g, "\n");
|
|
1984
2087
|
function loadProgrammaticAddendum(stackDir, fileName) {
|
|
1985
|
-
return normalize(
|
|
2088
|
+
return normalize(fs10.readFileSync(path13.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
|
|
1986
2089
|
}
|
|
1987
2090
|
function appendAddendum(base, addendum) {
|
|
1988
2091
|
const normalizedBase = normalize(base);
|
|
@@ -2025,15 +2128,15 @@ function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
|
|
|
2025
2128
|
var normalize2 = (s) => s.replace(/\r\n/g, "\n");
|
|
2026
2129
|
function planSystemPrompt(adapter, ctx) {
|
|
2027
2130
|
const target = adapter.paths(ctx.configDir).systemPromptFile;
|
|
2028
|
-
const agentsMd = normalize2(
|
|
2131
|
+
const agentsMd = normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
|
|
2029
2132
|
const protocol = stripLeadingHtmlComments(
|
|
2030
|
-
normalize2(
|
|
2133
|
+
normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
|
|
2031
2134
|
);
|
|
2032
2135
|
const composedAgentsMd = composeProgrammaticSystemPrompt(ctx.stackDir, agentsMd, ctx.mode);
|
|
2033
2136
|
const browser = [
|
|
2034
2137
|
ctx.playwrightCliEnabled ? "browser-playwright.md" : null,
|
|
2035
2138
|
ctx.enabledMcpServers?.has(DEVTOOLS_MCP_SERVER) ? "browser-chrome-devtools.md" : null
|
|
2036
|
-
].filter((file) => file !== null).map((file) => normalize2(
|
|
2139
|
+
].filter((file) => file !== null).map((file) => normalize2(fs11.readFileSync(path14.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
|
|
2037
2140
|
let content = readTextIfExists(target);
|
|
2038
2141
|
content = upsertMarkdownSection(content, "system-prompt", composedAgentsMd);
|
|
2039
2142
|
if (adapter.injectEngramProtocol(ctx)) {
|
|
@@ -2042,11 +2145,13 @@ function planSystemPrompt(adapter, ctx) {
|
|
|
2042
2145
|
content = removeMarkdownSection(content, "engram-protocol");
|
|
2043
2146
|
}
|
|
2044
2147
|
content = browser === "" ? removeMarkdownSection(content, "browser") : upsertMarkdownSection(content, "browser", browser);
|
|
2148
|
+
const style = ctx.mode === "programmatic" ? null : ctx.writingStyle?.content;
|
|
2149
|
+
content = style ? upsertMarkdownSection(content, "writing-style", style) : removeMarkdownSection(content, "writing-style");
|
|
2045
2150
|
return [{ kind: "write", target, content }];
|
|
2046
2151
|
}
|
|
2047
2152
|
|
|
2048
2153
|
// src/components/agents.ts
|
|
2049
|
-
import
|
|
2154
|
+
import path15 from "path";
|
|
2050
2155
|
function planAgents(adapter, ctx) {
|
|
2051
2156
|
const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
|
|
2052
2157
|
const dirFor = {
|
|
@@ -2056,7 +2161,7 @@ function planAgents(adapter, ctx) {
|
|
|
2056
2161
|
profile: profilesDir
|
|
2057
2162
|
};
|
|
2058
2163
|
const scriptsBase = scriptsDir.replace(/\\/g, "/");
|
|
2059
|
-
return loadCanonicalAgents(
|
|
2164
|
+
return loadCanonicalAgents(path15.join(ctx.stackDir, "agents")).flatMap((agent) => {
|
|
2060
2165
|
const composedAgent = {
|
|
2061
2166
|
...agent,
|
|
2062
2167
|
body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
|
|
@@ -2068,42 +2173,42 @@ function planAgents(adapter, ctx) {
|
|
|
2068
2173
|
return [];
|
|
2069
2174
|
}
|
|
2070
2175
|
const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
|
|
2071
|
-
return [{ kind: "write", target:
|
|
2176
|
+
return [{ kind: "write", target: path15.join(dir, rendered.file), content }];
|
|
2072
2177
|
});
|
|
2073
2178
|
});
|
|
2074
2179
|
}
|
|
2075
2180
|
|
|
2076
2181
|
// src/components/skills.ts
|
|
2077
|
-
import
|
|
2182
|
+
import path16 from "path";
|
|
2078
2183
|
function planSkills(adapter, ctx) {
|
|
2079
2184
|
const { skillsDir } = adapter.paths(ctx.configDir);
|
|
2080
|
-
const source =
|
|
2185
|
+
const source = path16.join(ctx.stackDir, "skills");
|
|
2081
2186
|
return listFilesRecursive(source).map((file) => {
|
|
2082
|
-
const relative =
|
|
2083
|
-
return { kind: "copy", source: file, target:
|
|
2187
|
+
const relative = path16.relative(source, file);
|
|
2188
|
+
return { kind: "copy", source: file, target: path16.join(skillsDir, relative) };
|
|
2084
2189
|
});
|
|
2085
2190
|
}
|
|
2086
2191
|
|
|
2087
2192
|
// src/components/commands.ts
|
|
2088
|
-
import
|
|
2089
|
-
import
|
|
2193
|
+
import path17 from "path";
|
|
2194
|
+
import fs12 from "fs";
|
|
2090
2195
|
function planCommands(adapter, ctx) {
|
|
2091
2196
|
const { commandsDir } = adapter.paths(ctx.configDir);
|
|
2092
|
-
const source =
|
|
2093
|
-
if (!
|
|
2197
|
+
const source = path17.join(ctx.stackDir, "commands");
|
|
2198
|
+
if (!fs12.existsSync(source)) return [];
|
|
2094
2199
|
const commandFiles = [
|
|
2095
2200
|
...listMarkdownFiles(source),
|
|
2096
|
-
...listMarkdownFiles(
|
|
2201
|
+
...listMarkdownFiles(path17.join(source, adapter.id))
|
|
2097
2202
|
];
|
|
2098
2203
|
return commandFiles.map(({ file, fullPath }) => {
|
|
2099
|
-
const raw =
|
|
2204
|
+
const raw = fs12.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
|
|
2100
2205
|
const rendered = adapter.renderCommand(file, raw);
|
|
2101
|
-
return { kind: "write", target:
|
|
2206
|
+
return { kind: "write", target: path17.join(commandsDir, rendered.file), content: rendered.content };
|
|
2102
2207
|
});
|
|
2103
2208
|
}
|
|
2104
2209
|
function listMarkdownFiles(dir) {
|
|
2105
|
-
if (!
|
|
2106
|
-
return
|
|
2210
|
+
if (!fs12.existsSync(dir)) return [];
|
|
2211
|
+
return fs12.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path17.join(dir, entry.name) }));
|
|
2107
2212
|
}
|
|
2108
2213
|
|
|
2109
2214
|
// src/components/hooks.ts
|
|
@@ -2117,23 +2222,23 @@ function planMcp(adapter, ctx) {
|
|
|
2117
2222
|
}
|
|
2118
2223
|
|
|
2119
2224
|
// src/components/plugins.ts
|
|
2120
|
-
import
|
|
2121
|
-
import
|
|
2225
|
+
import path18 from "path";
|
|
2226
|
+
import fs13 from "fs";
|
|
2122
2227
|
function planPlugins(adapter, ctx) {
|
|
2123
2228
|
const { pluginsDir } = adapter.paths(ctx.configDir);
|
|
2124
2229
|
if (pluginsDir === null) return [];
|
|
2125
|
-
const source =
|
|
2126
|
-
if (!
|
|
2230
|
+
const source = path18.join(ctx.stackDir, "plugins", adapter.id);
|
|
2231
|
+
if (!fs13.existsSync(source)) return [];
|
|
2127
2232
|
return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
|
|
2128
|
-
const target =
|
|
2129
|
-
const raw =
|
|
2233
|
+
const target = path18.join(pluginsDir, path18.relative(source, sourceFile));
|
|
2234
|
+
const raw = fs13.readFileSync(sourceFile, "utf8");
|
|
2130
2235
|
let content = raw;
|
|
2131
2236
|
if (content.includes('"{{ENGRAM_BIN}}"')) {
|
|
2132
2237
|
content = content.replace(/"\{\{ENGRAM_BIN\}\}"/g, JSON.stringify(ctx.engramBin ?? "engram"));
|
|
2133
2238
|
}
|
|
2134
2239
|
if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
|
|
2135
2240
|
const protocol = stripLeadingHtmlComments(
|
|
2136
|
-
|
|
2241
|
+
fs13.readFileSync(path18.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
|
|
2137
2242
|
);
|
|
2138
2243
|
content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
|
|
2139
2244
|
}
|
|
@@ -2144,9 +2249,9 @@ function planPlugins(adapter, ctx) {
|
|
|
2144
2249
|
}
|
|
2145
2250
|
|
|
2146
2251
|
// src/lib/external-tools.ts
|
|
2147
|
-
import
|
|
2252
|
+
import fs14 from "fs";
|
|
2148
2253
|
import os from "os";
|
|
2149
|
-
import
|
|
2254
|
+
import path19 from "path";
|
|
2150
2255
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2151
2256
|
var PLAYWRIGHT_CLI = {
|
|
2152
2257
|
packageName: "@playwright/cli",
|
|
@@ -2191,9 +2296,9 @@ function detectPlaywrightCli() {
|
|
|
2191
2296
|
if (binPath === null) {
|
|
2192
2297
|
const pnpmHome = resolvePnpmHome();
|
|
2193
2298
|
const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
|
|
2194
|
-
const known = [
|
|
2299
|
+
const known = [path19.join(pnpmHome, name), path19.join(pnpmHome, "bin", name)].find((file) => {
|
|
2195
2300
|
try {
|
|
2196
|
-
return
|
|
2301
|
+
return fs14.statSync(file).isFile();
|
|
2197
2302
|
} catch {
|
|
2198
2303
|
return false;
|
|
2199
2304
|
}
|
|
@@ -2212,7 +2317,7 @@ function resolvePnpmBin() {
|
|
|
2212
2317
|
return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
|
|
2213
2318
|
}
|
|
2214
2319
|
function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
|
|
2215
|
-
const paths = platform === "win32" ?
|
|
2320
|
+
const paths = platform === "win32" ? path19.win32 : path19.posix;
|
|
2216
2321
|
if (env.PNPM_HOME) return env.PNPM_HOME;
|
|
2217
2322
|
if (env.XDG_DATA_HOME) return paths.join(env.XDG_DATA_HOME, "pnpm");
|
|
2218
2323
|
if (platform === "darwin") return paths.join(homeDir, "Library", "pnpm");
|
|
@@ -2221,7 +2326,7 @@ function resolvePnpmHome(env = process.env, platform = process.platform, homeDir
|
|
|
2221
2326
|
}
|
|
2222
2327
|
function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform, homeDir = os.homedir()) {
|
|
2223
2328
|
const pnpmHome = resolvePnpmHome(env, platform, homeDir);
|
|
2224
|
-
const paths = platform === "win32" ?
|
|
2329
|
+
const paths = platform === "win32" ? path19.win32 : path19.posix;
|
|
2225
2330
|
if (!paths.isAbsolute(pnpmHome)) return { ok: false, reason: "PNPM_HOME debe ser una ruta absoluta; corrige la configuraci\xF3n antes de reintentar." };
|
|
2226
2331
|
const pathEntries = [pnpmHome, paths.join(pnpmHome, "bin")];
|
|
2227
2332
|
if (env.PATH !== void 0) pathEntries.push(env.PATH);
|
|
@@ -2241,10 +2346,10 @@ function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform
|
|
|
2241
2346
|
}
|
|
2242
2347
|
function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
|
|
2243
2348
|
const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
2244
|
-
const cacheDir = configuredPath ?? (platform === "win32" ?
|
|
2349
|
+
const cacheDir = configuredPath ?? (platform === "win32" ? path19.join(env.LOCALAPPDATA ?? path19.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path19.join(homeDir, "Library", "Caches", "ms-playwright") : path19.join(env.XDG_CACHE_HOME ?? path19.join(homeDir, ".cache"), "ms-playwright"));
|
|
2245
2350
|
if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
|
|
2246
2351
|
try {
|
|
2247
|
-
const ready =
|
|
2352
|
+
const ready = fs14.readdirSync(cacheDir, { withFileTypes: true }).some(
|
|
2248
2353
|
(entry) => entry.isDirectory() && /^chromium(?:_headless_shell)?-/.test(entry.name)
|
|
2249
2354
|
);
|
|
2250
2355
|
return ready ? { status: "ready", path: cacheDir } : { status: "missing", path: cacheDir };
|
|
@@ -2276,9 +2381,9 @@ function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
|
|
|
2276
2381
|
cwd,
|
|
2277
2382
|
stdio: ["ignore", "pipe", "inherit"]
|
|
2278
2383
|
}).trim();
|
|
2279
|
-
if (!
|
|
2280
|
-
const packageFile =
|
|
2281
|
-
const installed = JSON.parse(
|
|
2384
|
+
if (!path19.isAbsolute(root)) return false;
|
|
2385
|
+
const packageFile = fs14.realpathSync(path19.join(root, "@playwright", "cli", "package.json"));
|
|
2386
|
+
const installed = JSON.parse(fs14.readFileSync(packageFile, "utf8"));
|
|
2282
2387
|
if (installed.name !== PLAYWRIGHT_CLI.packageName || installed.version !== PLAYWRIGHT_CLI.version) return false;
|
|
2283
2388
|
const probe = `
|
|
2284
2389
|
const { createRequire } = require('node:module');
|
|
@@ -2321,7 +2426,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
|
|
|
2321
2426
|
const command = planPlaywrightCliCommand(action, pnpmBin);
|
|
2322
2427
|
const invocation = planDetectedBinCommand(command.command, command.args);
|
|
2323
2428
|
if (invocation === null) return { ok: false, reason: "pnpm-command" };
|
|
2324
|
-
const cwd = action === "install-browser" ?
|
|
2429
|
+
const cwd = action === "install-browser" ? fs14.mkdtempSync(path19.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
|
|
2325
2430
|
const childEnv = action === "install-browser" ? { ...env ?? process.env, NO_UPDATE_NOTIFIER: "1" } : env ?? process.env;
|
|
2326
2431
|
try {
|
|
2327
2432
|
execFileSync2(invocation.command, invocation.args, {
|
|
@@ -2336,27 +2441,27 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
|
|
|
2336
2441
|
} catch {
|
|
2337
2442
|
return { ok: false, reason: "action-failed" };
|
|
2338
2443
|
} finally {
|
|
2339
|
-
if (cwd !== void 0)
|
|
2444
|
+
if (cwd !== void 0) fs14.rmSync(cwd, { recursive: true, force: true });
|
|
2340
2445
|
}
|
|
2341
2446
|
}
|
|
2342
2447
|
|
|
2343
2448
|
// src/lib/tool-preferences.ts
|
|
2344
|
-
import
|
|
2345
|
-
import
|
|
2449
|
+
import fs15 from "fs";
|
|
2450
|
+
import path20 from "path";
|
|
2346
2451
|
var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
|
|
2347
2452
|
var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
|
|
2348
2453
|
var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
|
|
2349
2454
|
var PLAYWRIGHT_RUNTIMES = ["opencode", "claude-code", "codex", "pi"];
|
|
2350
2455
|
function readPreference(file) {
|
|
2351
2456
|
try {
|
|
2352
|
-
return { raw:
|
|
2457
|
+
return { raw: fs15.readFileSync(file, "utf8"), errorCode: null };
|
|
2353
2458
|
} catch (error) {
|
|
2354
2459
|
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "UNKNOWN";
|
|
2355
2460
|
return code === "ENOENT" ? { raw: null, errorCode: null } : { raw: null, errorCode: code };
|
|
2356
2461
|
}
|
|
2357
2462
|
}
|
|
2358
2463
|
function playwrightCliPreferenceFile(stateDir = dataDir()) {
|
|
2359
|
-
return
|
|
2464
|
+
return path20.join(stateDir, "playwright-cli.json");
|
|
2360
2465
|
}
|
|
2361
2466
|
function parsePlaywrightCliPreference(raw) {
|
|
2362
2467
|
try {
|
|
@@ -2402,10 +2507,10 @@ function savePlaywrightCliPreference(file, enabled, selection) {
|
|
|
2402
2507
|
writeText(file, content);
|
|
2403
2508
|
}
|
|
2404
2509
|
function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
|
|
2405
|
-
return
|
|
2510
|
+
return path20.join(stateDir, "devtools-mcp.json");
|
|
2406
2511
|
}
|
|
2407
2512
|
function primaryModelOwnershipFile(stateDir = dataDir()) {
|
|
2408
|
-
return
|
|
2513
|
+
return path20.join(stateDir, "primary-model.json");
|
|
2409
2514
|
}
|
|
2410
2515
|
function isRecord2(value) {
|
|
2411
2516
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2515,7 +2620,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
|
|
|
2515
2620
|
return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
|
|
2516
2621
|
}
|
|
2517
2622
|
function primaryModelConfigKey(configDir) {
|
|
2518
|
-
const resolved =
|
|
2623
|
+
const resolved = path20.resolve(configDir);
|
|
2519
2624
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
2520
2625
|
}
|
|
2521
2626
|
function loadPrimaryModelOwnership(file, runtime, configDir) {
|
|
@@ -2635,7 +2740,7 @@ function diffPlan(plan) {
|
|
|
2635
2740
|
if (current === null) return { action, status: "create" };
|
|
2636
2741
|
return { action, status: current === action.content ? "unchanged" : "update" };
|
|
2637
2742
|
}
|
|
2638
|
-
if (!
|
|
2743
|
+
if (!fs16.existsSync(action.target)) return { action, status: "create" };
|
|
2639
2744
|
return { action, status: sameFileContent(action.source, action.target) ? "unchanged" : "update" };
|
|
2640
2745
|
});
|
|
2641
2746
|
}
|
|
@@ -2661,7 +2766,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
|
|
|
2661
2766
|
continue;
|
|
2662
2767
|
}
|
|
2663
2768
|
try {
|
|
2664
|
-
for (const action of buildPlan(adapter, ctx)) targets.add(
|
|
2769
|
+
for (const action of buildPlan(adapter, ctx)) targets.add(path21.resolve(action.target));
|
|
2665
2770
|
} catch (error) {
|
|
2666
2771
|
complete = false;
|
|
2667
2772
|
warnings.push(
|
|
@@ -2674,6 +2779,15 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
|
|
|
2674
2779
|
async function runInstall(opts) {
|
|
2675
2780
|
const showSummary = opts.showSummary !== false;
|
|
2676
2781
|
if (showSummary) p.intro(`jorgex-stack ${opts.dryRun ? "install (dry-run)" : "install"}`);
|
|
2782
|
+
let writingStyle;
|
|
2783
|
+
let preparedStyle;
|
|
2784
|
+
try {
|
|
2785
|
+
if (opts.writingStyle !== void 0) writingStyle = opts.writingStyle;
|
|
2786
|
+
else writingStyle = preparedStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: opts.targetDir }), { rootDir: opts.targetDir });
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
2789
|
+
return 1;
|
|
2790
|
+
}
|
|
2677
2791
|
const stackDir = stackRoot();
|
|
2678
2792
|
const engramBin = opts.engramBin === void 0 ? detectEngram() : opts.engramBin;
|
|
2679
2793
|
const modePreference = opts.mode === void 0 ? opts.targetDir === void 0 ? loadInstallModePreference() : DEFAULT_INSTALL_MODE_PREFERENCE : normalizeInstallModePreference(opts.mode);
|
|
@@ -2691,7 +2805,16 @@ async function runInstall(opts) {
|
|
|
2691
2805
|
const projectPlaywrightPrompt = opts.dryRun && toolPlan?.persistEnabledOnSuccess === true;
|
|
2692
2806
|
const hasFileRuntimes = opts.runtimes.length > 0;
|
|
2693
2807
|
const modelMap = hasFileRuntimes ? loadModelMap() : {};
|
|
2694
|
-
if (
|
|
2808
|
+
if (preparedStyle !== void 0) {
|
|
2809
|
+
try {
|
|
2810
|
+
p.log.info(`Estilo de escritura: ${preparedStyle.sourcePath}${opts.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
|
|
2811
|
+
applyWritingStyle(preparedStyle, opts.dryRun);
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
2814
|
+
return 1;
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
if (hasFileRuntimes && useManifest && !opts.dryRun) ensureModelMapFile();
|
|
2695
2818
|
p.log.info(engramBin ? `Engram detectado: ${engramBin} (se respeta, D7)` : "Engram NO detectado.");
|
|
2696
2819
|
const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
|
|
2697
2820
|
const canOrphan = hasFileRuntimes && useManifest && current.complete;
|
|
@@ -2723,6 +2846,7 @@ async function runInstall(opts) {
|
|
|
2723
2846
|
continue;
|
|
2724
2847
|
}
|
|
2725
2848
|
const ctx = {
|
|
2849
|
+
writingStyle,
|
|
2726
2850
|
stackDir,
|
|
2727
2851
|
configDir,
|
|
2728
2852
|
mode: modePreference.mode,
|
|
@@ -2765,10 +2889,10 @@ async function runInstall(opts) {
|
|
|
2765
2889
|
}
|
|
2766
2890
|
const writeManifest = () => {
|
|
2767
2891
|
if (!useManifest) return;
|
|
2768
|
-
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) =>
|
|
2892
|
+
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path21.resolve(a.target)));
|
|
2769
2893
|
const keepTarget = (target) => !unmergeTargets.has(target);
|
|
2770
|
-
const liveOwned = plan.map((a) =>
|
|
2771
|
-
const previousOwned = (prevManifest?.owned ?? []).map((target) =>
|
|
2894
|
+
const liveOwned = plan.map((a) => path21.resolve(a.target)).filter(keepTarget);
|
|
2895
|
+
const previousOwned = (prevManifest?.owned ?? []).map((target) => path21.resolve(target)).filter(keepTarget);
|
|
2772
2896
|
const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
|
|
2773
2897
|
writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2774
2898
|
};
|
|
@@ -2797,9 +2921,9 @@ async function runInstall(opts) {
|
|
|
2797
2921
|
const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
|
|
2798
2922
|
if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
|
|
2799
2923
|
applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
|
|
2800
|
-
const pruneRoot = useManifest ? HOME :
|
|
2924
|
+
const pruneRoot = useManifest ? HOME : path21.dirname(configDir);
|
|
2801
2925
|
for (const orphan of orphans) {
|
|
2802
|
-
|
|
2926
|
+
fs16.rmSync(orphan, { force: true });
|
|
2803
2927
|
pruneEmptyDirs(orphan, pruneRoot);
|
|
2804
2928
|
}
|
|
2805
2929
|
const verifyCtx = { ...ctx, warnings: [] };
|
|
@@ -2924,29 +3048,29 @@ async function runInstall(opts) {
|
|
|
2924
3048
|
}
|
|
2925
3049
|
|
|
2926
3050
|
// src/uninstall.ts
|
|
2927
|
-
import
|
|
2928
|
-
import
|
|
3051
|
+
import fs18 from "fs";
|
|
3052
|
+
import path25 from "path";
|
|
2929
3053
|
import * as p2 from "@clack/prompts";
|
|
2930
3054
|
|
|
2931
3055
|
// src/lib/pi-projection-lifecycle.ts
|
|
2932
|
-
import
|
|
2933
|
-
import
|
|
3056
|
+
import fs17 from "fs";
|
|
3057
|
+
import path24 from "path";
|
|
2934
3058
|
import { createHash } from "crypto";
|
|
2935
3059
|
|
|
2936
3060
|
// src/adapters/pi.ts
|
|
2937
|
-
import
|
|
3061
|
+
import path22 from "path";
|
|
2938
3062
|
var piAdapter = {
|
|
2939
3063
|
id: "pi",
|
|
2940
3064
|
paths(configDir) {
|
|
2941
|
-
const piConfigDir = process.env.PI_CODING_AGENT_DIR ??
|
|
2942
|
-
const agentsHome = samePath(configDir, piConfigDir) ? HOME :
|
|
3065
|
+
const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path22.join(HOME, ".pi", "agent");
|
|
3066
|
+
const agentsHome = samePath(configDir, piConfigDir) ? HOME : path22.join(path22.dirname(configDir), "home");
|
|
2943
3067
|
return {
|
|
2944
|
-
systemPromptFile:
|
|
2945
|
-
agentsDir:
|
|
2946
|
-
skillsDir:
|
|
2947
|
-
commandsDir:
|
|
3068
|
+
systemPromptFile: path22.join(configDir, "AGENTS.md"),
|
|
3069
|
+
agentsDir: path22.join(configDir, "agents"),
|
|
3070
|
+
skillsDir: path22.join(agentsHome, ".agents", "skills"),
|
|
3071
|
+
commandsDir: path22.join(configDir, "prompts"),
|
|
2948
3072
|
pluginsDir: null,
|
|
2949
|
-
scriptsDir:
|
|
3073
|
+
scriptsDir: path22.join(configDir, "scripts"),
|
|
2950
3074
|
outputStylesDir: null,
|
|
2951
3075
|
profilesDir: null
|
|
2952
3076
|
};
|
|
@@ -2960,7 +3084,7 @@ var piAdapter = {
|
|
|
2960
3084
|
};
|
|
2961
3085
|
|
|
2962
3086
|
// src/lib/pi-package-lifecycle.ts
|
|
2963
|
-
import
|
|
3087
|
+
import path23 from "path";
|
|
2964
3088
|
var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
2965
3089
|
"foundation-contract-v1",
|
|
2966
3090
|
"runner-json-v1",
|
|
@@ -3092,7 +3216,7 @@ function parseReceiptShape(receiptJson) {
|
|
|
3092
3216
|
return null;
|
|
3093
3217
|
}
|
|
3094
3218
|
if (engram === void 0) return "upgrade-required";
|
|
3095
|
-
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !
|
|
3219
|
+
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path23.isAbsolute(Reflect.get(engram, "binary"))) {
|
|
3096
3220
|
return null;
|
|
3097
3221
|
}
|
|
3098
3222
|
return parsed;
|
|
@@ -3129,7 +3253,7 @@ function planPiPackageLifecycle(input) {
|
|
|
3129
3253
|
if (input.receiptJson !== null) {
|
|
3130
3254
|
const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
|
|
3131
3255
|
kind: input.scope.kind,
|
|
3132
|
-
codingAgentDir:
|
|
3256
|
+
codingAgentDir: path23.resolve(input.scope.codingAgentDir)
|
|
3133
3257
|
}, input.engramBin);
|
|
3134
3258
|
if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
|
|
3135
3259
|
if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
|
|
@@ -3164,7 +3288,7 @@ function planPiPackageLifecycle(input) {
|
|
|
3164
3288
|
},
|
|
3165
3289
|
receipt: expectedReceipt(input.candidate, "installing", {
|
|
3166
3290
|
kind: input.scope.kind,
|
|
3167
|
-
codingAgentDir:
|
|
3291
|
+
codingAgentDir: path23.resolve(input.scope.codingAgentDir)
|
|
3168
3292
|
}, input.engramBin),
|
|
3169
3293
|
ownership: ownership(true)
|
|
3170
3294
|
};
|
|
@@ -3179,7 +3303,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
|
|
|
3179
3303
|
const parsed = JSON.parse(body);
|
|
3180
3304
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3181
3305
|
const record = parsed;
|
|
3182
|
-
if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !
|
|
3306
|
+
if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path23.isAbsolute(record.package.root) || path23.resolve(packageRunner) !== path23.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
|
|
3183
3307
|
return null;
|
|
3184
3308
|
}
|
|
3185
3309
|
return record;
|
|
@@ -3265,10 +3389,10 @@ function validateOwnedOperationState(input) {
|
|
|
3265
3389
|
}))) {
|
|
3266
3390
|
return { kind: "blocked", reason: "receipt-untrusted" };
|
|
3267
3391
|
}
|
|
3268
|
-
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") ||
|
|
3392
|
+
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path23.resolve(receipt.scope.codingAgentDir) !== path23.resolve(input.paths.codingAgentDir)) {
|
|
3269
3393
|
return { kind: "blocked", reason: "source-divergent" };
|
|
3270
3394
|
}
|
|
3271
|
-
if (input.engramBin !== null &&
|
|
3395
|
+
if (input.engramBin !== null && path23.resolve(receipt.engram.binary) !== path23.resolve(input.engramBin)) {
|
|
3272
3396
|
return { kind: "blocked", reason: "receipt-corrupt" };
|
|
3273
3397
|
}
|
|
3274
3398
|
const source = receipt.candidate.package.source;
|
|
@@ -3375,7 +3499,7 @@ var RETIRED_PLAYWRIGHT_FILES = [
|
|
|
3375
3499
|
"references/video-recording.md"
|
|
3376
3500
|
];
|
|
3377
3501
|
function retiredPlaywrightFiles(scope) {
|
|
3378
|
-
return RETIRED_PLAYWRIGHT_FILES.map((file) =>
|
|
3502
|
+
return RETIRED_PLAYWRIGHT_FILES.map((file) => path24.resolve(scope.home, ".agents", "skills", "playwright-cli", file));
|
|
3379
3503
|
}
|
|
3380
3504
|
function handoffEnabled(input, kind) {
|
|
3381
3505
|
return (kind === "devtools" ? input.devtoolsMcpEnabled : input.playwrightHandoffEnabled) === true;
|
|
@@ -3384,18 +3508,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
|
|
|
3384
3508
|
function projectionScope(scope) {
|
|
3385
3509
|
return {
|
|
3386
3510
|
...scope,
|
|
3387
|
-
settingsFile:
|
|
3511
|
+
settingsFile: path24.join(scope.codingAgentDir, "settings.json")
|
|
3388
3512
|
};
|
|
3389
3513
|
}
|
|
3390
3514
|
function isInside(root, file) {
|
|
3391
|
-
const relative =
|
|
3392
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
3515
|
+
const relative = path24.relative(path24.resolve(root), path24.resolve(file));
|
|
3516
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path24.sep}`) && !path24.isAbsolute(relative);
|
|
3393
3517
|
}
|
|
3394
3518
|
function isManagedPath(scope, file) {
|
|
3395
3519
|
return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
|
|
3396
3520
|
}
|
|
3397
3521
|
function isAllowedPath(scope, file) {
|
|
3398
|
-
return isManagedPath(scope, file) ||
|
|
3522
|
+
return isManagedPath(scope, file) || path24.resolve(file) === path24.resolve(scope.receiptFile);
|
|
3399
3523
|
}
|
|
3400
3524
|
function projectedPiAdapter(scope) {
|
|
3401
3525
|
const paths = piAdapter.paths(scope.codingAgentDir);
|
|
@@ -3403,12 +3527,13 @@ function projectedPiAdapter(scope) {
|
|
|
3403
3527
|
...piAdapter,
|
|
3404
3528
|
paths: () => ({
|
|
3405
3529
|
...paths,
|
|
3406
|
-
skillsDir:
|
|
3530
|
+
skillsDir: path24.join(scope.home, ".agents", "skills")
|
|
3407
3531
|
})
|
|
3408
3532
|
};
|
|
3409
3533
|
}
|
|
3410
3534
|
function projectionPlan(input, scope) {
|
|
3411
3535
|
const ctx = {
|
|
3536
|
+
writingStyle: input.writingStyle,
|
|
3412
3537
|
stackDir: input.stackDir,
|
|
3413
3538
|
configDir: scope.codingAgentDir,
|
|
3414
3539
|
engramBin: input.engramBin,
|
|
@@ -3446,20 +3571,20 @@ function projectionPlan(input, scope) {
|
|
|
3446
3571
|
return actions;
|
|
3447
3572
|
}
|
|
3448
3573
|
function handoffPath(scope, kind) {
|
|
3449
|
-
return
|
|
3574
|
+
return path24.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
|
|
3450
3575
|
}
|
|
3451
3576
|
function contentHash(content) {
|
|
3452
3577
|
return createHash("sha256").update(content).digest("hex");
|
|
3453
3578
|
}
|
|
3454
3579
|
function canonicalActionContent(action) {
|
|
3455
|
-
return action.kind === "write" ? action.content :
|
|
3580
|
+
return action.kind === "write" ? action.content : fs17.readFileSync(action.source, "utf8");
|
|
3456
3581
|
}
|
|
3457
3582
|
function hasActionDrift(action, deps) {
|
|
3458
3583
|
return deps.readText(action.target) !== canonicalActionContent(action);
|
|
3459
3584
|
}
|
|
3460
3585
|
function applyActions(actions, deps, handoffs = []) {
|
|
3461
3586
|
for (const action of actions) {
|
|
3462
|
-
const handoff = handoffs.find((entry) =>
|
|
3587
|
+
const handoff = handoffs.find((entry) => path24.resolve(action.target) === entry.file);
|
|
3463
3588
|
if (handoff) {
|
|
3464
3589
|
try {
|
|
3465
3590
|
if (deps.readText(handoff.file) !== handoff.previous) return handoffConflict(handoff.kind, handoff.file);
|
|
@@ -3479,9 +3604,9 @@ function assertPlanContained(plan, scope) {
|
|
|
3479
3604
|
}
|
|
3480
3605
|
}
|
|
3481
3606
|
function receiptFor(plan, scope) {
|
|
3482
|
-
const systemPrompt =
|
|
3607
|
+
const systemPrompt = path24.join(scope.codingAgentDir, "AGENTS.md");
|
|
3483
3608
|
const owned = [...new Set(
|
|
3484
|
-
plan.map((action) =>
|
|
3609
|
+
plan.map((action) => path24.resolve(action.target)).filter((target) => target !== systemPrompt)
|
|
3485
3610
|
)];
|
|
3486
3611
|
const receipt = {
|
|
3487
3612
|
schemaVersion: 1,
|
|
@@ -3494,7 +3619,7 @@ function receiptFor(plan, scope) {
|
|
|
3494
3619
|
owned
|
|
3495
3620
|
};
|
|
3496
3621
|
for (const kind of HANDOFF_KINDS) {
|
|
3497
|
-
const action = plan.find((entry) =>
|
|
3622
|
+
const action = plan.find((entry) => path24.resolve(entry.target) === handoffPath(scope, kind));
|
|
3498
3623
|
if (action) receipt[kind] = { sha256: contentHash(canonicalActionContent(action)) };
|
|
3499
3624
|
}
|
|
3500
3625
|
return receipt;
|
|
@@ -3535,7 +3660,7 @@ function parseReceipt2(raw, expected) {
|
|
|
3535
3660
|
if (matches(expectedOwned)) return { schemaVersion: 1, scope: expected.scope, owned: expectedOwned, ...digests };
|
|
3536
3661
|
const retired = retiredPlaywrightFiles(expected.scope);
|
|
3537
3662
|
if (retired.some((file) => expectedOwned.includes(file))) return null;
|
|
3538
|
-
const skillsRoot = `${
|
|
3663
|
+
const skillsRoot = `${path24.resolve(expected.scope.home, ".agents", "skills")}${path24.sep}`;
|
|
3539
3664
|
const insertion = expectedOwned.findIndex((file) => !file.startsWith(skillsRoot) || file > retired[0]);
|
|
3540
3665
|
const legacyOwned = [...expectedOwned];
|
|
3541
3666
|
legacyOwned.splice(insertion === -1 ? legacyOwned.length : insertion, 0, ...retired);
|
|
@@ -3554,13 +3679,13 @@ function realPiProjectionScope() {
|
|
|
3554
3679
|
return {
|
|
3555
3680
|
kind: "real",
|
|
3556
3681
|
home: HOME,
|
|
3557
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
3558
|
-
receiptFile:
|
|
3682
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path24.join(HOME, ".pi", "agent"),
|
|
3683
|
+
receiptFile: path24.join(dataDir(), "pi-projection-receipt.json")
|
|
3559
3684
|
};
|
|
3560
3685
|
}
|
|
3561
3686
|
function readTextOnlyIfMissing(file) {
|
|
3562
3687
|
try {
|
|
3563
|
-
return
|
|
3688
|
+
return fs17.readFileSync(file, "utf8");
|
|
3564
3689
|
} catch (error) {
|
|
3565
3690
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT") return null;
|
|
3566
3691
|
throw error;
|
|
@@ -3593,13 +3718,13 @@ function manifestOwned(manifest) {
|
|
|
3593
3718
|
return new Set([
|
|
3594
3719
|
...manifest.runtimes.codex?.owned ?? [],
|
|
3595
3720
|
...manifest.runtimes.opencode?.owned ?? []
|
|
3596
|
-
].map((file) =>
|
|
3721
|
+
].map((file) => path24.resolve(file)));
|
|
3597
3722
|
}
|
|
3598
3723
|
function withoutManagedPromptSections(content) {
|
|
3599
|
-
return ["system-prompt", "engram-protocol", "browser"].reduce((current, section) => removeMarkdownSection(current, section), content);
|
|
3724
|
+
return ["system-prompt", "engram-protocol", "browser", "writing-style"].reduce((current, section) => removeMarkdownSection(current, section), content);
|
|
3600
3725
|
}
|
|
3601
3726
|
function uniquePaths(paths) {
|
|
3602
|
-
return [...new Set(paths.map((file) =>
|
|
3727
|
+
return [...new Set(paths.map((file) => path24.resolve(file)))];
|
|
3603
3728
|
}
|
|
3604
3729
|
function blocked2(reason, paths, remedy) {
|
|
3605
3730
|
return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
|
|
@@ -3706,7 +3831,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3706
3831
|
}
|
|
3707
3832
|
const scope = projectionScope(input.scope);
|
|
3708
3833
|
const expectedReceipt2 = expectedProjectionReceipt(input, scope);
|
|
3709
|
-
const prompt =
|
|
3834
|
+
const prompt = path24.join(scope.codingAgentDir, "AGENTS.md");
|
|
3710
3835
|
let existingPrompt;
|
|
3711
3836
|
try {
|
|
3712
3837
|
existingPrompt = deps.readText(prompt);
|
|
@@ -3714,7 +3839,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3714
3839
|
return cleanupFailure([prompt]);
|
|
3715
3840
|
}
|
|
3716
3841
|
const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
|
|
3717
|
-
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file:
|
|
3842
|
+
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path24.resolve(prompt), content: promptContent };
|
|
3718
3843
|
const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
|
|
3719
3844
|
if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
|
|
3720
3845
|
let ownedToRemove = [];
|
|
@@ -3732,7 +3857,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3732
3857
|
return cleanupFailure([scope.receiptFile]);
|
|
3733
3858
|
}
|
|
3734
3859
|
ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
|
|
3735
|
-
receiptFile =
|
|
3860
|
+
receiptFile = path24.resolve(scope.receiptFile);
|
|
3736
3861
|
backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
|
|
3737
3862
|
}
|
|
3738
3863
|
const handoffs = [];
|
|
@@ -3810,10 +3935,10 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3810
3935
|
const prepared = preparePiProjectionUninstall(input, deps);
|
|
3811
3936
|
return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
|
|
3812
3937
|
}
|
|
3813
|
-
if (input.devtoolsMcpEnabled && (!input.pnpmBin || !
|
|
3938
|
+
if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path24.isAbsolute(input.pnpmBin))) {
|
|
3814
3939
|
return blocked2("projection-devtools-command", [handoffPath(scope, "devtools")], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
|
|
3815
3940
|
}
|
|
3816
|
-
if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !
|
|
3941
|
+
if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path24.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
|
|
3817
3942
|
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.");
|
|
3818
3943
|
}
|
|
3819
3944
|
const plan = projectionPlan(input, scope);
|
|
@@ -3826,12 +3951,12 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3826
3951
|
const expectedReceipt2 = receiptContent(receipt);
|
|
3827
3952
|
const drifted = plan.filter((action) => hasActionDrift(action, deps));
|
|
3828
3953
|
for (const { kind, file } of checked.handoffs) {
|
|
3829
|
-
if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) =>
|
|
3954
|
+
if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path24.resolve(action.target) === file)) {
|
|
3830
3955
|
return input.operation === "doctor" ? { kind: "drift", paths: [file] } : handoffConflict(kind, file);
|
|
3831
3956
|
}
|
|
3832
3957
|
}
|
|
3833
3958
|
if (input.operation === "doctor") {
|
|
3834
|
-
const paths = drifted.map((action) =>
|
|
3959
|
+
const paths = drifted.map((action) => path24.resolve(action.target));
|
|
3835
3960
|
paths.push(...removedHandoffs.map(({ file }) => file), ...retiredSkills);
|
|
3836
3961
|
const currentSettings2 = deps.readText(scope.settingsFile);
|
|
3837
3962
|
if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
|
|
@@ -3890,16 +4015,17 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3890
4015
|
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0 };
|
|
3891
4016
|
}
|
|
3892
4017
|
function systemProjectionLifecycle(input) {
|
|
3893
|
-
const targetRoot = input.targetDir === void 0 ? null :
|
|
4018
|
+
const targetRoot = input.targetDir === void 0 ? null : path24.resolve(input.targetDir);
|
|
3894
4019
|
const scope = targetRoot === null ? realPiProjectionScope() : {
|
|
3895
4020
|
kind: "target-dir",
|
|
3896
|
-
home:
|
|
3897
|
-
codingAgentDir:
|
|
3898
|
-
receiptFile:
|
|
4021
|
+
home: path24.join(targetRoot, "home"),
|
|
4022
|
+
codingAgentDir: path24.join(targetRoot, "pi-agent"),
|
|
4023
|
+
receiptFile: path24.join(targetRoot, "state", "pi-projection-receipt.json")
|
|
3899
4024
|
};
|
|
3900
4025
|
return {
|
|
3901
4026
|
input: {
|
|
3902
4027
|
operation: input.operation,
|
|
4028
|
+
writingStyle: input.writingStyle,
|
|
3903
4029
|
scope,
|
|
3904
4030
|
packageSource: input.packageSource,
|
|
3905
4031
|
stackDir: stackRoot(),
|
|
@@ -3915,14 +4041,14 @@ function systemProjectionLifecycle(input) {
|
|
|
3915
4041
|
backup: (paths) => createBackup(
|
|
3916
4042
|
paths,
|
|
3917
4043
|
`pi-projection-${input.operation}`,
|
|
3918
|
-
targetRoot === null ? void 0 :
|
|
4044
|
+
targetRoot === null ? void 0 : path24.join(targetRoot, "backups")
|
|
3919
4045
|
),
|
|
3920
4046
|
writeText,
|
|
3921
4047
|
copyFile,
|
|
3922
4048
|
removeFile: (file) => {
|
|
3923
|
-
|
|
3924
|
-
if (retiredPlaywrightFiles(scope).includes(
|
|
3925
|
-
pruneEmptyDirs(file,
|
|
4049
|
+
fs17.rmSync(file, { force: true });
|
|
4050
|
+
if (retiredPlaywrightFiles(scope).includes(path24.resolve(file))) {
|
|
4051
|
+
pruneEmptyDirs(file, path24.join(scope.home, ".agents", "skills"));
|
|
3926
4052
|
}
|
|
3927
4053
|
},
|
|
3928
4054
|
readManifest: targetRoot === null ? readManifest : () => ({ runtimes: {} })
|
|
@@ -4001,7 +4127,7 @@ async function runUninstall(opts) {
|
|
|
4001
4127
|
if (!detection.installed) continue;
|
|
4002
4128
|
const keepCtx = makeContext(keep, detection.configDir);
|
|
4003
4129
|
if (!keepCtx) continue;
|
|
4004
|
-
for (const action of buildPlan(keep, keepCtx)) retained.add(
|
|
4130
|
+
for (const action of buildPlan(keep, keepCtx)) retained.add(path25.resolve(action.target));
|
|
4005
4131
|
}
|
|
4006
4132
|
}
|
|
4007
4133
|
for (const id of opts.runtimes) {
|
|
@@ -4020,15 +4146,15 @@ async function runUninstall(opts) {
|
|
|
4020
4146
|
if (!ctx) continue;
|
|
4021
4147
|
ctx.preserveEngram = !removeEngram;
|
|
4022
4148
|
const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
|
|
4023
|
-
const mergedTargets = new Set(unmerge.map((a) =>
|
|
4149
|
+
const mergedTargets = new Set(unmerge.map((a) => path25.resolve(a.target)));
|
|
4024
4150
|
const usingRealConfig = opts.targetDir === void 0;
|
|
4025
4151
|
const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
|
|
4026
|
-
const pruneRoot = usingRealConfig ? HOME :
|
|
4152
|
+
const pruneRoot = usingRealConfig ? HOME : path25.dirname(configDir);
|
|
4027
4153
|
const planTargets = [
|
|
4028
|
-
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) =>
|
|
4029
|
-
].filter((t) => !mergedTargets.has(t) &&
|
|
4154
|
+
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path25.resolve(a.target)), ...prevOwned.map((t) => path25.resolve(t))])
|
|
4155
|
+
].filter((t) => !mergedTargets.has(t) && fs18.existsSync(t));
|
|
4030
4156
|
const deleteTargets = planTargets.filter(
|
|
4031
|
-
(t) => !retained.has(t) && !(ctx.preserveEngram &&
|
|
4157
|
+
(t) => !retained.has(t) && !(ctx.preserveEngram && path25.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
|
|
4032
4158
|
);
|
|
4033
4159
|
const sharedKept = planTargets.length - deleteTargets.length;
|
|
4034
4160
|
p2.log.step(`${adapter.name} \u2192 ${configDir}`);
|
|
@@ -4038,7 +4164,7 @@ async function runUninstall(opts) {
|
|
|
4038
4164
|
let backup;
|
|
4039
4165
|
try {
|
|
4040
4166
|
backup = createBackup(
|
|
4041
|
-
[...deleteTargets, ...unmerge.map((a) => a.target).filter((t) =>
|
|
4167
|
+
[...deleteTargets, ...unmerge.map((a) => a.target).filter((t) => fs18.existsSync(t))],
|
|
4042
4168
|
`uninstall-${id}`
|
|
4043
4169
|
);
|
|
4044
4170
|
} catch (error) {
|
|
@@ -4048,13 +4174,13 @@ async function runUninstall(opts) {
|
|
|
4048
4174
|
}
|
|
4049
4175
|
if (backup) p2.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
|
|
4050
4176
|
for (const target of deleteTargets) {
|
|
4051
|
-
|
|
4177
|
+
fs18.rmSync(target, { force: true });
|
|
4052
4178
|
pruneEmptyDirs(target, pruneRoot);
|
|
4053
4179
|
}
|
|
4054
4180
|
for (const action of unmerge) {
|
|
4055
4181
|
if (action.kind !== "write") continue;
|
|
4056
4182
|
if (action.content.trim() === "") {
|
|
4057
|
-
|
|
4183
|
+
fs18.rmSync(action.target, { force: true });
|
|
4058
4184
|
} else {
|
|
4059
4185
|
writeText(action.target, action.content);
|
|
4060
4186
|
}
|
|
@@ -4108,9 +4234,17 @@ async function runUninstall(opts) {
|
|
|
4108
4234
|
}
|
|
4109
4235
|
|
|
4110
4236
|
// src/doctor.ts
|
|
4111
|
-
import
|
|
4112
|
-
import
|
|
4237
|
+
import path26 from "path";
|
|
4238
|
+
import fs19 from "fs";
|
|
4113
4239
|
import * as p3 from "@clack/prompts";
|
|
4240
|
+
function readDoctorTextIfExists(file) {
|
|
4241
|
+
try {
|
|
4242
|
+
return fs19.readFileSync(file, "utf8");
|
|
4243
|
+
} catch (error) {
|
|
4244
|
+
if (error.code === "ENOENT") return null;
|
|
4245
|
+
throw error;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4114
4248
|
function engramVersion(bin) {
|
|
4115
4249
|
const out = runDetectedBin(bin, ["--version"], 5e3);
|
|
4116
4250
|
if (out === null) return null;
|
|
@@ -4129,16 +4263,71 @@ function resolvePlaywrightDoctorState(input) {
|
|
|
4129
4263
|
return { status: "healthy" };
|
|
4130
4264
|
}
|
|
4131
4265
|
function context7KeyConfigured(id, configDir) {
|
|
4132
|
-
const file = id === "codex" ?
|
|
4266
|
+
const file = id === "codex" ? path26.join(configDir, "config.toml") : id === "claude-code" ? path26.join(path26.dirname(configDir), `${path26.basename(configDir)}.json`) : path26.join(configDir, "opencode.json");
|
|
4133
4267
|
const content = readTextIfExists(file);
|
|
4134
4268
|
if (content === null) return null;
|
|
4135
4269
|
const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
|
|
4136
4270
|
if (!match) return null;
|
|
4137
4271
|
return match[1] !== "";
|
|
4138
4272
|
}
|
|
4139
|
-
|
|
4140
|
-
p3.intro("jorgex-stack doctor");
|
|
4273
|
+
function reportWritingStyle(options, style, mode) {
|
|
4141
4274
|
let problems = 0;
|
|
4275
|
+
p3.log.info(`Estilo de escritura incluido: ${style.canonicalPath}; fuente local ${style.sourcePath}; tama\xF1o ${Buffer.byteLength(style.content, "utf8")} bytes de texto normalizado. La carga nativa no est\xE1 verificada.`);
|
|
4276
|
+
if (style.originalContent === style.installedContent) p3.log.success("Archivo local de estilo actualizado con el canon incluido.");
|
|
4277
|
+
else {
|
|
4278
|
+
p3.log.warn(`Archivo local de estilo ${style.originalContent === null ? "pendiente de instalar" : "desactualizado; pendiente de sincronizar"}; ejecuta install o sync (${style.sourcePath}).`);
|
|
4279
|
+
problems++;
|
|
4280
|
+
}
|
|
4281
|
+
const expected = mode.mode !== "programmatic" ? upsertMarkdownSection(null, "writing-style", style.content).trim() : null;
|
|
4282
|
+
const runtimes = options.runtimes ?? Object.values(ADAPTERS).filter((adapter) => options.targetDir !== void 0 || adapter.detect().installed).map((adapter) => adapter.id);
|
|
4283
|
+
for (const id of runtimes) {
|
|
4284
|
+
const adapter = id === "pi" ? piAdapter : ADAPTERS[id];
|
|
4285
|
+
if (adapter === void 0) continue;
|
|
4286
|
+
const configDir = id === "pi" ? options.targetDir === void 0 ? process.env.PI_CODING_AGENT_DIR ?? path26.join(HOME, ".pi", "agent") : path26.join(options.targetDir, "pi-agent") : options.targetDir ?? ADAPTERS[id].detect().configDir;
|
|
4287
|
+
const file = adapter.paths(configDir).systemPromptFile;
|
|
4288
|
+
try {
|
|
4289
|
+
const content = readDoctorTextIfExists(file) ?? "";
|
|
4290
|
+
const open = "<!-- jorgex:writing-style -->";
|
|
4291
|
+
const close = "<!-- /jorgex:writing-style -->";
|
|
4292
|
+
const healthy = hasHealthyManagedMarkdownMarkers(content, "writing-style");
|
|
4293
|
+
const block = healthy ? content.slice(content.indexOf(open), content.indexOf(close) + close.length) : null;
|
|
4294
|
+
const matches = expected === null ? !content.includes(open) && !content.includes(close) : healthy && block === expected;
|
|
4295
|
+
if (matches) p3.log.success(`${id}: proyecci\xF3n de estilo coincide (${file})${mode.mode === "programmatic" ? "; omitida en modo programmatic" : ""}.`);
|
|
4296
|
+
else {
|
|
4297
|
+
p3.log.warn(`${id}: proyecci\xF3n de estilo desactualizada o ausente (${file}); ejecuta sync.`);
|
|
4298
|
+
problems++;
|
|
4299
|
+
}
|
|
4300
|
+
if (id === "codex") {
|
|
4301
|
+
const override = path26.join(configDir, "AGENTS.override.md");
|
|
4302
|
+
if ((readDoctorTextIfExists(override) ?? "").trim() !== "") {
|
|
4303
|
+
p3.log.warn(`Codex: ${override} no vac\xEDo puede ocultar el AGENTS.md gestionado; rev\xEDsalo sin modificarlo autom\xE1ticamente.`);
|
|
4304
|
+
problems++;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
} catch {
|
|
4308
|
+
p3.log.error(`${id}: no se puede leer la proyecci\xF3n de estilo o su override en ${configDir}; revisa archivos y permisos.`);
|
|
4309
|
+
problems++;
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return problems;
|
|
4313
|
+
}
|
|
4314
|
+
async function runDoctor(options = {}) {
|
|
4315
|
+
p3.intro("jorgex-stack doctor");
|
|
4316
|
+
let writingStyle;
|
|
4317
|
+
let modePreference;
|
|
4318
|
+
try {
|
|
4319
|
+
writingStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: options.targetDir }), { rootDir: options.targetDir });
|
|
4320
|
+
modePreference = options.mode ?? (options.targetDir === void 0 ? loadInstallModePreference() : DEFAULT_INSTALL_MODE_PREFERENCE);
|
|
4321
|
+
} catch (error) {
|
|
4322
|
+
p3.log.error(error instanceof Error ? error.message : String(error));
|
|
4323
|
+
p3.outro("No se puede diagnosticar el estilo; corrige la fuente o la preferencia indicada.");
|
|
4324
|
+
return 1;
|
|
4325
|
+
}
|
|
4326
|
+
let problems = reportWritingStyle(options, writingStyle, modePreference);
|
|
4327
|
+
if (options.targetDir !== void 0 || options.runtimes !== void 0 && options.runtimes.length > 0 && options.runtimes.every((id) => id === "pi")) {
|
|
4328
|
+
p3.outro("Diagn\xF3stico limitado al estilo; no se han ejecutado las comprobaciones globales del sistema.");
|
|
4329
|
+
return problems > 0 ? 1 : 0;
|
|
4330
|
+
}
|
|
4142
4331
|
const engramBin = detectEngram();
|
|
4143
4332
|
if (engramBin === null) {
|
|
4144
4333
|
p3.log.warn("Engram: NO detectado. El protocolo de memoria no funcionar\xE1 \u2014 inst\xE1lalo: github.com/Gentleman-Programming/engram");
|
|
@@ -4152,13 +4341,13 @@ async function runDoctor() {
|
|
|
4152
4341
|
p3.log.success(`Engram: ${version} (${engramBin})`);
|
|
4153
4342
|
}
|
|
4154
4343
|
}
|
|
4155
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
4156
|
-
const engramDb =
|
|
4157
|
-
if (
|
|
4158
|
-
const sizeMb = (
|
|
4344
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path26.join(HOME, ".engram");
|
|
4345
|
+
const engramDb = path26.join(engramDataDir, "engram.db");
|
|
4346
|
+
if (fs19.existsSync(engramDb)) {
|
|
4347
|
+
const sizeMb = (fs19.statSync(engramDb).size / 1024 / 1024).toFixed(1);
|
|
4159
4348
|
p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
|
|
4160
4349
|
}
|
|
4161
|
-
if (!
|
|
4350
|
+
if (!fs19.existsSync(modelMapFile())) p3.log.info("model-map: a\xFAn no creado (se crea en el primer install o con 'models').");
|
|
4162
4351
|
const preferenceErrors = browserPreferenceErrors();
|
|
4163
4352
|
const primaryOwnershipError = primaryModelOwnershipError();
|
|
4164
4353
|
if (primaryOwnershipError !== null) {
|
|
@@ -4199,7 +4388,6 @@ async function runDoctor() {
|
|
|
4199
4388
|
}
|
|
4200
4389
|
}
|
|
4201
4390
|
const manifest = readManifest();
|
|
4202
|
-
const modePreference = loadInstallModePreference();
|
|
4203
4391
|
const current = collectAllCurrentTargets(modePreference);
|
|
4204
4392
|
if (!current.complete || current.warnings.length > 0) {
|
|
4205
4393
|
p3.log.warn("Limpieza de hu\xE9rfanos deshabilitada: no se pudo construir el plan completo de todos los runtimes.");
|
|
@@ -4207,6 +4395,7 @@ async function runDoctor() {
|
|
|
4207
4395
|
problems++;
|
|
4208
4396
|
}
|
|
4209
4397
|
for (const adapter of Object.values(ADAPTERS)) {
|
|
4398
|
+
if (options.runtimes !== void 0 && !options.runtimes.includes(adapter.id)) continue;
|
|
4210
4399
|
const detection = adapter.detect();
|
|
4211
4400
|
if (!detection.installed) {
|
|
4212
4401
|
p3.log.warn(`${adapter.name}: no instalado en esta m\xE1quina.`);
|
|
@@ -4217,6 +4406,7 @@ async function runDoctor() {
|
|
|
4217
4406
|
p3.log.info(`${adapter.name}: capabilities diagnostic (${capabilitySummary}); no certifica enforcement local.`);
|
|
4218
4407
|
const ctx = makeContext(adapter, detection.configDir, modePreference);
|
|
4219
4408
|
if (!ctx) continue;
|
|
4409
|
+
ctx.writingStyle = writingStyle;
|
|
4220
4410
|
let pending;
|
|
4221
4411
|
try {
|
|
4222
4412
|
pending = diffPlan(buildPlan(adapter, ctx)).filter((d) => d.status !== "unchanged").length;
|
|
@@ -4239,15 +4429,9 @@ async function runDoctor() {
|
|
|
4239
4429
|
p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
|
|
4240
4430
|
problems++;
|
|
4241
4431
|
}
|
|
4242
|
-
if (adapter.id === "codex" &&
|
|
4432
|
+
if (adapter.id === "codex" && fs19.existsSync(path26.join(detection.configDir, "hooks.json"))) {
|
|
4243
4433
|
p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
|
|
4244
4434
|
}
|
|
4245
|
-
if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "AGENTS.override.md"))) {
|
|
4246
|
-
p3.log.warn(
|
|
4247
|
-
"Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
|
|
4248
|
-
);
|
|
4249
|
-
problems++;
|
|
4250
|
-
}
|
|
4251
4435
|
const key = context7KeyConfigured(adapter.id, detection.configDir);
|
|
4252
4436
|
if (key === false) p3.log.info(`${adapter.name}: context7 sin key (opcional \u2014 con\xE9ctala cuando quieras).`);
|
|
4253
4437
|
}
|
|
@@ -4256,15 +4440,15 @@ async function runDoctor() {
|
|
|
4256
4440
|
}
|
|
4257
4441
|
|
|
4258
4442
|
// src/update.ts
|
|
4259
|
-
import
|
|
4260
|
-
import
|
|
4443
|
+
import fs22 from "fs";
|
|
4444
|
+
import path29 from "path";
|
|
4261
4445
|
import os3 from "os";
|
|
4262
4446
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
4263
4447
|
import * as p4 from "@clack/prompts";
|
|
4264
4448
|
|
|
4265
4449
|
// src/lib/github.ts
|
|
4266
|
-
import
|
|
4267
|
-
import
|
|
4450
|
+
import fs20 from "fs";
|
|
4451
|
+
import path27 from "path";
|
|
4268
4452
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4269
4453
|
import os2 from "os";
|
|
4270
4454
|
import { Readable } from "stream";
|
|
@@ -4334,19 +4518,19 @@ async function latestGithubCommit(repo) {
|
|
|
4334
4518
|
}
|
|
4335
4519
|
}
|
|
4336
4520
|
function validateExtractedTree(destDir) {
|
|
4337
|
-
const resolved =
|
|
4521
|
+
const resolved = path27.resolve(destDir);
|
|
4338
4522
|
const walk = (dir) => {
|
|
4339
4523
|
let entries;
|
|
4340
4524
|
try {
|
|
4341
|
-
entries =
|
|
4525
|
+
entries = fs20.readdirSync(dir, { withFileTypes: true });
|
|
4342
4526
|
} catch {
|
|
4343
4527
|
return false;
|
|
4344
4528
|
}
|
|
4345
4529
|
for (const entry of entries) {
|
|
4346
|
-
const full =
|
|
4530
|
+
const full = path27.join(dir, entry.name);
|
|
4347
4531
|
let stat;
|
|
4348
4532
|
try {
|
|
4349
|
-
stat =
|
|
4533
|
+
stat = fs20.lstatSync(full);
|
|
4350
4534
|
} catch {
|
|
4351
4535
|
return false;
|
|
4352
4536
|
}
|
|
@@ -4362,15 +4546,15 @@ function validateExtractedTree(destDir) {
|
|
|
4362
4546
|
}
|
|
4363
4547
|
function resolveTarBin() {
|
|
4364
4548
|
if (process.platform !== "win32") return "tar";
|
|
4365
|
-
const winTar =
|
|
4366
|
-
return
|
|
4549
|
+
const winTar = path27.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
|
|
4550
|
+
return fs20.existsSync(winTar) ? winTar : "tar";
|
|
4367
4551
|
}
|
|
4368
4552
|
async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
4369
4553
|
const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
|
|
4370
|
-
const tmp =
|
|
4554
|
+
const tmp = path27.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
|
|
4371
4555
|
const fail = (reason) => {
|
|
4372
4556
|
try {
|
|
4373
|
-
|
|
4557
|
+
fs20.rmSync(destDir, { recursive: true, force: true });
|
|
4374
4558
|
} catch {
|
|
4375
4559
|
}
|
|
4376
4560
|
return { ok: false, reason };
|
|
@@ -4389,10 +4573,10 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
4389
4573
|
if (!res.body) return fail("respuesta HTTP sin cuerpo");
|
|
4390
4574
|
await pipeline(
|
|
4391
4575
|
Readable.fromWeb(res.body),
|
|
4392
|
-
|
|
4576
|
+
fs20.createWriteStream(tmp)
|
|
4393
4577
|
);
|
|
4394
|
-
|
|
4395
|
-
|
|
4578
|
+
fs20.rmSync(destDir, { recursive: true, force: true });
|
|
4579
|
+
fs20.mkdirSync(destDir, { recursive: true });
|
|
4396
4580
|
try {
|
|
4397
4581
|
execFileSync3(resolveTarBin(), ["-xzf", tmp, "--strip-components=1", "-C", destDir], { stdio: "pipe" });
|
|
4398
4582
|
} catch (err) {
|
|
@@ -4400,30 +4584,30 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
4400
4584
|
const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
|
|
4401
4585
|
return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
|
|
4402
4586
|
}
|
|
4403
|
-
const resolvedDest =
|
|
4404
|
-
const validateRoot = validateSubdir ?
|
|
4587
|
+
const resolvedDest = path27.resolve(destDir);
|
|
4588
|
+
const validateRoot = validateSubdir ? path27.resolve(resolvedDest, validateSubdir) : resolvedDest;
|
|
4405
4589
|
if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
|
|
4406
4590
|
return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
|
|
4407
4591
|
}
|
|
4408
|
-
if (
|
|
4592
|
+
if (fs20.existsSync(validateRoot) && !validateExtractedTree(validateRoot)) {
|
|
4409
4593
|
return fail("el \xE1rbol extra\xEDdo contiene symlinks o rutas fuera del destino");
|
|
4410
4594
|
}
|
|
4411
|
-
const validated =
|
|
4595
|
+
const validated = fs20.existsSync(validateRoot);
|
|
4412
4596
|
return { ok: true, validated };
|
|
4413
4597
|
} catch (err) {
|
|
4414
4598
|
const timedOut = err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
|
|
4415
4599
|
return fail(timedOut ? "timeout de descarga (120s)" : err instanceof Error ? `fallo de red: ${err.message}` : "error desconocido");
|
|
4416
4600
|
} finally {
|
|
4417
4601
|
try {
|
|
4418
|
-
|
|
4602
|
+
fs20.rmSync(tmp, { force: true });
|
|
4419
4603
|
} catch {
|
|
4420
4604
|
}
|
|
4421
4605
|
}
|
|
4422
4606
|
}
|
|
4423
4607
|
|
|
4424
4608
|
// src/lib/skill-update.ts
|
|
4425
|
-
import
|
|
4426
|
-
import
|
|
4609
|
+
import fs21 from "fs";
|
|
4610
|
+
import path28 from "path";
|
|
4427
4611
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
4428
4612
|
var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
|
|
4429
4613
|
"agent-delegation",
|
|
@@ -4434,8 +4618,8 @@ var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
|
|
|
4434
4618
|
"xreview"
|
|
4435
4619
|
]);
|
|
4436
4620
|
function sameTextContentNormalized(a, b) {
|
|
4437
|
-
const ba =
|
|
4438
|
-
const bb =
|
|
4621
|
+
const ba = fs21.readFileSync(a);
|
|
4622
|
+
const bb = fs21.readFileSync(b);
|
|
4439
4623
|
if (ba.equals(bb)) return true;
|
|
4440
4624
|
const sa = ba.toString("utf8").replace(/\r\n/g, "\n");
|
|
4441
4625
|
const sb = bb.toString("utf8").replace(/\r\n/g, "\n");
|
|
@@ -4443,10 +4627,10 @@ function sameTextContentNormalized(a, b) {
|
|
|
4443
4627
|
}
|
|
4444
4628
|
function diffSkillDirs(upstreamDir, localDir) {
|
|
4445
4629
|
const upstreamFiles = new Set(
|
|
4446
|
-
listFilesRecursive(upstreamDir).map((f) =>
|
|
4630
|
+
listFilesRecursive(upstreamDir).map((f) => path28.relative(upstreamDir, f))
|
|
4447
4631
|
);
|
|
4448
4632
|
const localFiles = new Set(
|
|
4449
|
-
listFilesRecursive(localDir).map((f) =>
|
|
4633
|
+
listFilesRecursive(localDir).map((f) => path28.relative(localDir, f))
|
|
4450
4634
|
);
|
|
4451
4635
|
const added = [];
|
|
4452
4636
|
const modified = [];
|
|
@@ -4454,7 +4638,7 @@ function diffSkillDirs(upstreamDir, localDir) {
|
|
|
4454
4638
|
for (const rel of upstreamFiles) {
|
|
4455
4639
|
if (!localFiles.has(rel)) {
|
|
4456
4640
|
added.push(rel);
|
|
4457
|
-
} else if (!sameTextContentNormalized(
|
|
4641
|
+
} else if (!sameTextContentNormalized(path28.join(upstreamDir, rel), path28.join(localDir, rel))) {
|
|
4458
4642
|
modified.push(rel);
|
|
4459
4643
|
}
|
|
4460
4644
|
}
|
|
@@ -4516,8 +4700,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
4516
4700
|
if (PROTECTED_SKILLS.has(name)) {
|
|
4517
4701
|
throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
|
|
4518
4702
|
}
|
|
4519
|
-
const upstreamsFile = upstreamsFilePath ??
|
|
4520
|
-
const raw =
|
|
4703
|
+
const upstreamsFile = upstreamsFilePath ?? path28.join(path28.dirname(stackRoot()), "upstreams.json");
|
|
4704
|
+
const raw = fs21.readFileSync(upstreamsFile, "utf8");
|
|
4521
4705
|
const data = JSON.parse(raw);
|
|
4522
4706
|
const skillEntry = data?.skills?.[name];
|
|
4523
4707
|
if (!skillEntry) {
|
|
@@ -4526,8 +4710,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
4526
4710
|
if (skillEntry.kind === "release") {
|
|
4527
4711
|
throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
|
|
4528
4712
|
}
|
|
4529
|
-
const skillsRoot = localSkillsRoot ??
|
|
4530
|
-
const localSkillDir =
|
|
4713
|
+
const skillsRoot = localSkillsRoot ?? path28.join(stackRoot(), "skills");
|
|
4714
|
+
const localSkillDir = path28.join(skillsRoot, name);
|
|
4531
4715
|
const localFiles = listFilesRecursive(localSkillDir);
|
|
4532
4716
|
if (localFiles.length > 0) {
|
|
4533
4717
|
createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
|
|
@@ -4536,24 +4720,24 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
4536
4720
|
try {
|
|
4537
4721
|
const upstreamFiles = listFilesRecursive(upstreamSkillDir);
|
|
4538
4722
|
for (const src of upstreamFiles) {
|
|
4539
|
-
const st =
|
|
4723
|
+
const st = fs21.lstatSync(src);
|
|
4540
4724
|
if (st.isSymbolicLink()) {
|
|
4541
4725
|
throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
|
|
4542
4726
|
}
|
|
4543
|
-
const rel =
|
|
4544
|
-
const dest =
|
|
4545
|
-
ensureDir(
|
|
4727
|
+
const rel = path28.relative(upstreamSkillDir, src);
|
|
4728
|
+
const dest = path28.join(stagingDir, rel);
|
|
4729
|
+
ensureDir(path28.dirname(dest));
|
|
4546
4730
|
copyFile(src, dest);
|
|
4547
4731
|
}
|
|
4548
4732
|
const oldDir = `${localSkillDir}.old-${process.pid}`;
|
|
4549
|
-
if (
|
|
4550
|
-
|
|
4733
|
+
if (fs21.existsSync(localSkillDir)) {
|
|
4734
|
+
fs21.renameSync(localSkillDir, oldDir);
|
|
4551
4735
|
}
|
|
4552
|
-
|
|
4553
|
-
|
|
4736
|
+
fs21.renameSync(stagingDir, localSkillDir);
|
|
4737
|
+
fs21.rmSync(oldDir, { recursive: true, force: true });
|
|
4554
4738
|
} catch (err) {
|
|
4555
4739
|
try {
|
|
4556
|
-
|
|
4740
|
+
fs21.rmSync(stagingDir, { recursive: true, force: true });
|
|
4557
4741
|
} catch {
|
|
4558
4742
|
}
|
|
4559
4743
|
throw err;
|
|
@@ -4567,8 +4751,8 @@ function rateLimitHint(prefix) {
|
|
|
4567
4751
|
return ghPresentButTokenFailed() ? `${prefix} Tienes gh instalado pero \`gh auth token\` no devolvi\xF3 credencial (\xBFsesi\xF3n caducada?) \u2014 prueba \`gh auth login\` o define GH_TOKEN.` : `${prefix} Define GH_TOKEN o inicia sesi\xF3n en gh CLI.`;
|
|
4568
4752
|
}
|
|
4569
4753
|
function loadUpstreams() {
|
|
4570
|
-
const file =
|
|
4571
|
-
return JSON.parse(
|
|
4754
|
+
const file = path29.join(path29.dirname(stackRoot()), "upstreams.json");
|
|
4755
|
+
return JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
4572
4756
|
}
|
|
4573
4757
|
function skillsToScan(maintainer, upstreams) {
|
|
4574
4758
|
return maintainer ? Object.keys(upstreams.skills) : [];
|
|
@@ -4717,8 +4901,8 @@ function isEngramRunning() {
|
|
|
4717
4901
|
return null;
|
|
4718
4902
|
}
|
|
4719
4903
|
}
|
|
4720
|
-
function isGitClone(projectRoot =
|
|
4721
|
-
return
|
|
4904
|
+
function isGitClone(projectRoot = path29.dirname(stackRoot())) {
|
|
4905
|
+
return fs22.existsSync(path29.join(projectRoot, ".git"));
|
|
4722
4906
|
}
|
|
4723
4907
|
var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
|
|
4724
4908
|
function resolvePnpm() {
|
|
@@ -4728,12 +4912,12 @@ function resolvePnpm() {
|
|
|
4728
4912
|
}
|
|
4729
4913
|
function cleanupTmp(dir) {
|
|
4730
4914
|
try {
|
|
4731
|
-
|
|
4915
|
+
fs22.rmSync(dir, { recursive: true, force: true });
|
|
4732
4916
|
} catch {
|
|
4733
4917
|
}
|
|
4734
4918
|
}
|
|
4735
4919
|
function updateStackGitClone() {
|
|
4736
|
-
const projectRoot =
|
|
4920
|
+
const projectRoot = path29.dirname(stackRoot());
|
|
4737
4921
|
const git = lookPath("git");
|
|
4738
4922
|
if (!git) throw new Error("git no encontrado en PATH.");
|
|
4739
4923
|
const pnpm = resolvePnpm();
|
|
@@ -4750,7 +4934,7 @@ function updateStackGlobal() {
|
|
|
4750
4934
|
execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
|
|
4751
4935
|
}
|
|
4752
4936
|
async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
4753
|
-
const root =
|
|
4937
|
+
const root = fs22.mkdtempSync(path29.join(os3.tmpdir(), "jorgex-skill-"));
|
|
4754
4938
|
try {
|
|
4755
4939
|
const result = await downloadRepoTarball(repo, sha, root, skillPath);
|
|
4756
4940
|
if (!result.ok) {
|
|
@@ -4758,15 +4942,15 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
|
4758
4942
|
return { error: result.reason };
|
|
4759
4943
|
}
|
|
4760
4944
|
if (skillPath) {
|
|
4761
|
-
const sub =
|
|
4945
|
+
const sub = path29.resolve(path29.join(root, skillPath));
|
|
4762
4946
|
if (!isContainedIn(sub, root)) {
|
|
4763
4947
|
cleanupTmp(root);
|
|
4764
4948
|
return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
|
|
4765
4949
|
}
|
|
4766
|
-
if (
|
|
4950
|
+
if (fs22.existsSync(sub)) return { dir: sub, root };
|
|
4767
4951
|
const lastSeg = skillPath.split("/").pop();
|
|
4768
|
-
const sub2 =
|
|
4769
|
-
if (isContainedIn(sub2, root) &&
|
|
4952
|
+
const sub2 = path29.resolve(path29.join(root, lastSeg));
|
|
4953
|
+
if (isContainedIn(sub2, root) && fs22.existsSync(sub2)) {
|
|
4770
4954
|
if (!validateExtractedTree(sub2)) {
|
|
4771
4955
|
cleanupTmp(root);
|
|
4772
4956
|
return { error: `el sub\xE1rbol "${lastSeg}" contiene symlinks o rutas fuera del destino` };
|
|
@@ -4785,11 +4969,11 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
|
4785
4969
|
function pruneEngramDbBackups() {
|
|
4786
4970
|
try {
|
|
4787
4971
|
const dir = dataDir();
|
|
4788
|
-
if (!
|
|
4789
|
-
const backups =
|
|
4972
|
+
if (!fs22.existsSync(dir)) return;
|
|
4973
|
+
const backups = fs22.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs22.statSync(path29.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
4790
4974
|
for (const old of backups.slice(3)) {
|
|
4791
4975
|
try {
|
|
4792
|
-
|
|
4976
|
+
fs22.rmSync(path29.join(dir, old.name));
|
|
4793
4977
|
} catch {
|
|
4794
4978
|
}
|
|
4795
4979
|
}
|
|
@@ -4797,18 +4981,18 @@ function pruneEngramDbBackups() {
|
|
|
4797
4981
|
}
|
|
4798
4982
|
}
|
|
4799
4983
|
function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
4800
|
-
if (!
|
|
4801
|
-
const dir =
|
|
4802
|
-
const base =
|
|
4984
|
+
if (!fs22.existsSync(binPath)) return null;
|
|
4985
|
+
const dir = path29.dirname(binPath);
|
|
4986
|
+
const base = path29.basename(binPath);
|
|
4803
4987
|
const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
|
|
4804
4988
|
const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
|
|
4805
|
-
const resolvedDir =
|
|
4806
|
-
if (resolvedDir ===
|
|
4989
|
+
const resolvedDir = path29.resolve(dir);
|
|
4990
|
+
if (resolvedDir === path29.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
|
|
4807
4991
|
try {
|
|
4808
|
-
for (const entry of
|
|
4992
|
+
for (const entry of fs22.readdirSync(dir)) {
|
|
4809
4993
|
if (oldPattern.test(entry)) {
|
|
4810
4994
|
try {
|
|
4811
|
-
|
|
4995
|
+
fs22.rmSync(path29.join(dir, entry), { force: true });
|
|
4812
4996
|
} catch {
|
|
4813
4997
|
}
|
|
4814
4998
|
}
|
|
@@ -4816,8 +5000,8 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
|
4816
5000
|
} catch {
|
|
4817
5001
|
}
|
|
4818
5002
|
}
|
|
4819
|
-
const rotated =
|
|
4820
|
-
|
|
5003
|
+
const rotated = path29.join(dir, `${base}.old-${Date.now()}`);
|
|
5004
|
+
fs22.renameSync(binPath, rotated);
|
|
4821
5005
|
return rotated;
|
|
4822
5006
|
}
|
|
4823
5007
|
async function updateEngram(engramRepo, latestVersion) {
|
|
@@ -4826,19 +5010,19 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4826
5010
|
"Engram est\xE1 en ejecuci\xF3n: los procesos vivos seguir\xE1n usando la versi\xF3n antigua hasta que reinicies los clientes (Claude Code/OpenCode/Codex)."
|
|
4827
5011
|
);
|
|
4828
5012
|
}
|
|
4829
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
4830
|
-
const engramDb =
|
|
4831
|
-
if (
|
|
5013
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path29.join(HOME, ".engram");
|
|
5014
|
+
const engramDb = path29.join(engramDataDir, "engram.db");
|
|
5015
|
+
if (fs22.existsSync(engramDb)) {
|
|
4832
5016
|
const doBackup = await p4.confirm({
|
|
4833
5017
|
message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
|
|
4834
5018
|
initialValue: true
|
|
4835
5019
|
});
|
|
4836
5020
|
if (!p4.isCancel(doBackup) && doBackup) {
|
|
4837
5021
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4838
|
-
const dest =
|
|
5022
|
+
const dest = path29.join(dataDir(), `engram-db-backup-${ts}.db`);
|
|
4839
5023
|
try {
|
|
4840
|
-
if (!
|
|
4841
|
-
|
|
5024
|
+
if (!fs22.existsSync(dataDir())) fs22.mkdirSync(dataDir(), { recursive: true });
|
|
5025
|
+
fs22.copyFileSync(engramDb, dest);
|
|
4842
5026
|
p4.log.success(`DB respaldada en ${dest} (la DB original NO se modifica jam\xE1s).`);
|
|
4843
5027
|
pruneEngramDbBackups();
|
|
4844
5028
|
} catch (err) {
|
|
@@ -4888,10 +5072,10 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4888
5072
|
["install", `github.com/Gentleman-Programming/engram/cmd/engram@v${latestVersion}`],
|
|
4889
5073
|
{ stdio: "inherit" }
|
|
4890
5074
|
);
|
|
4891
|
-
const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists:
|
|
5075
|
+
const rollbackOk = resolveEngramRollback({ installOk: true, rotated, bin, binExists: fs22.existsSync(bin ?? "") });
|
|
4892
5076
|
if (rollbackOk.action === "restore") {
|
|
4893
5077
|
try {
|
|
4894
|
-
|
|
5078
|
+
fs22.renameSync(rotated, bin);
|
|
4895
5079
|
p4.log.warn(rollbackOk.messages.onRestore);
|
|
4896
5080
|
} catch {
|
|
4897
5081
|
p4.log.warn(rollbackOk.messages.onRenameFail);
|
|
@@ -4899,10 +5083,10 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4899
5083
|
}
|
|
4900
5084
|
return true;
|
|
4901
5085
|
} catch (err) {
|
|
4902
|
-
const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists:
|
|
5086
|
+
const rollbackFail = resolveEngramRollback({ installOk: false, rotated, bin, binExists: fs22.existsSync(bin ?? "") });
|
|
4903
5087
|
if (rollbackFail.action === "restore") {
|
|
4904
5088
|
try {
|
|
4905
|
-
|
|
5089
|
+
fs22.renameSync(rotated, bin);
|
|
4906
5090
|
p4.log.info(rollbackFail.messages.onRestore);
|
|
4907
5091
|
} catch {
|
|
4908
5092
|
p4.log.error(rollbackFail.messages.onRenameFail);
|
|
@@ -5172,7 +5356,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
|
|
|
5172
5356
|
continue;
|
|
5173
5357
|
}
|
|
5174
5358
|
const { dir: tmpDir, root: tmpRoot } = tmpResult;
|
|
5175
|
-
const localSkillDir =
|
|
5359
|
+
const localSkillDir = path29.join(stackRoot(), "skills", skillInfo.name);
|
|
5176
5360
|
const diff = renderSkillDiff(tmpDir, localSkillDir);
|
|
5177
5361
|
if (diff) {
|
|
5178
5362
|
p4.log.info(`Diff de ${skillInfo.name}:
|
|
@@ -5258,7 +5442,7 @@ ${diff}`);
|
|
|
5258
5442
|
}
|
|
5259
5443
|
|
|
5260
5444
|
// src/models-picker.ts
|
|
5261
|
-
import
|
|
5445
|
+
import path30 from "path";
|
|
5262
5446
|
import * as p5 from "@clack/prompts";
|
|
5263
5447
|
var TIERS = ["strong", "standard", "cheap"];
|
|
5264
5448
|
var EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
@@ -5284,7 +5468,7 @@ function opencodeLiveModels(binPath) {
|
|
|
5284
5468
|
}
|
|
5285
5469
|
function agentsByTier() {
|
|
5286
5470
|
const grouped = { strong: [], standard: [], cheap: [] };
|
|
5287
|
-
for (const agent of loadCanonicalAgents(
|
|
5471
|
+
for (const agent of loadCanonicalAgents(path30.join(stackRoot(), "agents"))) {
|
|
5288
5472
|
if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
|
|
5289
5473
|
}
|
|
5290
5474
|
return grouped;
|
|
@@ -5469,16 +5653,16 @@ function cancelled() {
|
|
|
5469
5653
|
}
|
|
5470
5654
|
|
|
5471
5655
|
// src/lib/release.ts
|
|
5472
|
-
import
|
|
5473
|
-
import
|
|
5656
|
+
import fs23 from "fs";
|
|
5657
|
+
import path31 from "path";
|
|
5474
5658
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
5475
5659
|
import { fileURLToPath } from "url";
|
|
5476
5660
|
function findPackageJson() {
|
|
5477
|
-
let dir =
|
|
5661
|
+
let dir = path31.dirname(fileURLToPath(import.meta.url));
|
|
5478
5662
|
for (let i = 0; i < 6; i++) {
|
|
5479
|
-
const candidate =
|
|
5480
|
-
if (
|
|
5481
|
-
dir =
|
|
5663
|
+
const candidate = path31.join(dir, "package.json");
|
|
5664
|
+
if (fs23.existsSync(candidate)) return candidate;
|
|
5665
|
+
dir = path31.dirname(dir);
|
|
5482
5666
|
}
|
|
5483
5667
|
throw new Error("No se encontr\xF3 package.json cerca del CLI.");
|
|
5484
5668
|
}
|
|
@@ -5487,7 +5671,7 @@ function readPackageVersion() {
|
|
|
5487
5671
|
}
|
|
5488
5672
|
function readPackageMetadata() {
|
|
5489
5673
|
const packageJson = findPackageJson();
|
|
5490
|
-
const raw =
|
|
5674
|
+
const raw = fs23.readFileSync(packageJson, "utf8");
|
|
5491
5675
|
const parsed = JSON.parse(raw);
|
|
5492
5676
|
const name = typeof parsed.name === "string" ? parsed.name.trim() : "";
|
|
5493
5677
|
const version = typeof parsed.version === "string" ? parsed.version.trim() : "";
|
|
@@ -5499,8 +5683,8 @@ function readPackageMetadata() {
|
|
|
5499
5683
|
|
|
5500
5684
|
// src/lib/pi-runtime.ts
|
|
5501
5685
|
import os4 from "os";
|
|
5502
|
-
import
|
|
5503
|
-
import
|
|
5686
|
+
import path32 from "path";
|
|
5687
|
+
import fs24 from "fs";
|
|
5504
5688
|
import { spawnSync } from "child_process";
|
|
5505
5689
|
import { createHash as createHash2 } from "crypto";
|
|
5506
5690
|
|
|
@@ -5508,16 +5692,16 @@ import { createHash as createHash2 } from "crypto";
|
|
|
5508
5692
|
var pi_runtime_pin_default = {
|
|
5509
5693
|
package: {
|
|
5510
5694
|
name: "jorgex-pi",
|
|
5511
|
-
version: "0.8.
|
|
5512
|
-
source: "npm:jorgex-pi@0.8.
|
|
5695
|
+
version: "0.8.17",
|
|
5696
|
+
source: "npm:jorgex-pi@0.8.17"
|
|
5513
5697
|
},
|
|
5514
5698
|
provenance: {
|
|
5515
|
-
commit: "
|
|
5699
|
+
commit: "53717b51dac13a460cd1c8c3ae4b6a3052fe70c2"
|
|
5516
5700
|
},
|
|
5517
5701
|
tarball: {
|
|
5518
|
-
bytes:
|
|
5519
|
-
sha256: "
|
|
5520
|
-
sha512: "
|
|
5702
|
+
bytes: 89122677,
|
|
5703
|
+
sha256: "07e8c36b237aac074c62a993cb2a89b45609e2d55cfab7f5f4dc33f5203d7750",
|
|
5704
|
+
sha512: "3bf7ffa62a992bf1a73f42821301b400f8ebe52d595cb4cb188e764bfeff8b0f3dd7247eda49990a97310728c6bd9699ffd03299b26641b8f17994b178763720"
|
|
5521
5705
|
}
|
|
5522
5706
|
};
|
|
5523
5707
|
|
|
@@ -5667,14 +5851,14 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
|
|
|
5667
5851
|
if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
|
|
5668
5852
|
const packageValue = Reflect.get(record, "package");
|
|
5669
5853
|
const result = Reflect.get(record, "result");
|
|
5670
|
-
return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) &&
|
|
5854
|
+
return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path32.resolve(packageRunner) === path32.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
|
|
5671
5855
|
} catch {
|
|
5672
5856
|
return false;
|
|
5673
5857
|
}
|
|
5674
5858
|
}
|
|
5675
5859
|
function installPiFromVerifiedTarball(input, deps) {
|
|
5676
5860
|
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
|
|
5677
|
-
const destination = input.targetDir === void 0 ?
|
|
5861
|
+
const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
5678
5862
|
const artifact = deps.download(destination);
|
|
5679
5863
|
if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
|
|
5680
5864
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
@@ -5682,7 +5866,7 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
5682
5866
|
deps.backupSettings();
|
|
5683
5867
|
const scope = {
|
|
5684
5868
|
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
5685
|
-
codingAgentDir:
|
|
5869
|
+
codingAgentDir: path32.resolve(paths.codingAgentDir)
|
|
5686
5870
|
};
|
|
5687
5871
|
const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
|
|
5688
5872
|
deps.writeReceiptAtomic(`${JSON.stringify(installing)}
|
|
@@ -5711,30 +5895,30 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
5711
5895
|
return { kind: "installed", receipt };
|
|
5712
5896
|
}
|
|
5713
5897
|
function runtimePath(piExecutable) {
|
|
5714
|
-
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null :
|
|
5715
|
-
return [...new Set(entries.filter((entry) => entry !== null))].join(
|
|
5898
|
+
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), process.env.SystemRoot ? path32.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path32.dirname(piExecutable), path32.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
|
|
5899
|
+
return [...new Set(entries.filter((entry) => entry !== null))].join(path32.delimiter);
|
|
5716
5900
|
}
|
|
5717
5901
|
function targetPaths(targetDir, engramBin, piExecutable) {
|
|
5718
|
-
const root =
|
|
5719
|
-
const codingAgentDir =
|
|
5720
|
-
const home =
|
|
5721
|
-
const temporary =
|
|
5902
|
+
const root = path32.resolve(targetDir);
|
|
5903
|
+
const codingAgentDir = path32.join(root, "pi-agent");
|
|
5904
|
+
const home = path32.join(root, "home");
|
|
5905
|
+
const temporary = path32.join(root, "tmp");
|
|
5722
5906
|
return {
|
|
5723
5907
|
codingAgentDir,
|
|
5724
|
-
receiptPath:
|
|
5725
|
-
packageRunner:
|
|
5908
|
+
receiptPath: path32.join(root, "state", "pi-receipt.json"),
|
|
5909
|
+
packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5726
5910
|
environment: {
|
|
5727
5911
|
HOME: home,
|
|
5728
5912
|
USERPROFILE: home,
|
|
5729
|
-
APPDATA:
|
|
5730
|
-
LOCALAPPDATA:
|
|
5731
|
-
XDG_CONFIG_HOME:
|
|
5732
|
-
XDG_DATA_HOME:
|
|
5733
|
-
XDG_CACHE_HOME:
|
|
5913
|
+
APPDATA: path32.join(root, "appdata"),
|
|
5914
|
+
LOCALAPPDATA: path32.join(root, "localappdata"),
|
|
5915
|
+
XDG_CONFIG_HOME: path32.join(root, "xdg-config"),
|
|
5916
|
+
XDG_DATA_HOME: path32.join(root, "xdg-data"),
|
|
5917
|
+
XDG_CACHE_HOME: path32.join(root, "xdg-cache"),
|
|
5734
5918
|
TEMP: temporary,
|
|
5735
5919
|
TMP: temporary,
|
|
5736
5920
|
TMPDIR: temporary,
|
|
5737
|
-
npm_config_cache:
|
|
5921
|
+
npm_config_cache: path32.join(root, "npm-cache"),
|
|
5738
5922
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5739
5923
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
5740
5924
|
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
@@ -5745,15 +5929,15 @@ function targetPaths(targetDir, engramBin, piExecutable) {
|
|
|
5745
5929
|
}
|
|
5746
5930
|
function userPaths(engramBin, piExecutable) {
|
|
5747
5931
|
const home = os4.homedir();
|
|
5748
|
-
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ??
|
|
5932
|
+
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent");
|
|
5749
5933
|
return {
|
|
5750
5934
|
codingAgentDir,
|
|
5751
|
-
receiptPath:
|
|
5752
|
-
packageRunner:
|
|
5935
|
+
receiptPath: path32.join(dataDir(), "pi-receipt.json"),
|
|
5936
|
+
packageRunner: path32.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5753
5937
|
environment: {
|
|
5754
5938
|
HOME: home,
|
|
5755
|
-
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ??
|
|
5756
|
-
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ??
|
|
5939
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path32.join(home, ".config"),
|
|
5940
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path32.join(home, ".cache"),
|
|
5757
5941
|
TMPDIR: os4.tmpdir(),
|
|
5758
5942
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5759
5943
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
@@ -5781,7 +5965,7 @@ function runPiRuntime(input, deps) {
|
|
|
5781
5965
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
5782
5966
|
}
|
|
5783
5967
|
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
5784
|
-
const settingsJson = deps.readSettings(
|
|
5968
|
+
const settingsJson = deps.readSettings(path32.join(paths.codingAgentDir, "settings.json"));
|
|
5785
5969
|
const receiptJson = deps.readReceipt(paths.receiptPath);
|
|
5786
5970
|
const lifecycleInput = {
|
|
5787
5971
|
candidate: PI_RUNTIME_CANDIDATE,
|
|
@@ -5839,19 +6023,19 @@ function runPiRuntime(input, deps) {
|
|
|
5839
6023
|
return result;
|
|
5840
6024
|
}
|
|
5841
6025
|
function readJsonFile(file) {
|
|
5842
|
-
return JSON.parse(
|
|
6026
|
+
return JSON.parse(fs24.readFileSync(file, "utf8"));
|
|
5843
6027
|
}
|
|
5844
6028
|
function packageVersionFromExecutable(executable) {
|
|
5845
6029
|
let current;
|
|
5846
6030
|
try {
|
|
5847
|
-
current =
|
|
6031
|
+
current = path32.dirname(fs24.realpathSync(executable));
|
|
5848
6032
|
} catch {
|
|
5849
6033
|
return null;
|
|
5850
6034
|
}
|
|
5851
6035
|
for (let depth = 0; depth < 8; depth++) {
|
|
5852
6036
|
const manifests = [
|
|
5853
|
-
|
|
5854
|
-
|
|
6037
|
+
path32.join(current, "package.json"),
|
|
6038
|
+
path32.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
|
|
5855
6039
|
];
|
|
5856
6040
|
for (const manifest of manifests) {
|
|
5857
6041
|
try {
|
|
@@ -5862,7 +6046,7 @@ function packageVersionFromExecutable(executable) {
|
|
|
5862
6046
|
} catch {
|
|
5863
6047
|
}
|
|
5864
6048
|
}
|
|
5865
|
-
const parent =
|
|
6049
|
+
const parent = path32.dirname(current);
|
|
5866
6050
|
if (parent === current) break;
|
|
5867
6051
|
current = parent;
|
|
5868
6052
|
}
|
|
@@ -5877,35 +6061,35 @@ function detectPiRuntime() {
|
|
|
5877
6061
|
installed: executable !== null,
|
|
5878
6062
|
executable,
|
|
5879
6063
|
version: executable === null ? null : packageVersionFromExecutable(executable),
|
|
5880
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
6064
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path32.join(home, ".pi", "agent")
|
|
5881
6065
|
};
|
|
5882
6066
|
}
|
|
5883
6067
|
function hasManagedPiRuntime(targetDir) {
|
|
5884
|
-
const receipt = targetDir === void 0 ?
|
|
5885
|
-
return
|
|
6068
|
+
const receipt = targetDir === void 0 ? path32.join(dataDir(), "pi-receipt.json") : path32.join(path32.resolve(targetDir), "state", "pi-receipt.json");
|
|
6069
|
+
return fs24.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
|
|
5886
6070
|
}
|
|
5887
6071
|
function resolvePiEngramBin(targetDir) {
|
|
5888
6072
|
if (targetDir === void 0) return detectEngram();
|
|
5889
|
-
const candidate =
|
|
5890
|
-
return
|
|
6073
|
+
const candidate = path32.join(path32.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
|
|
6074
|
+
return fs24.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
|
|
5891
6075
|
}
|
|
5892
6076
|
function readOptional(file, fallback) {
|
|
5893
6077
|
try {
|
|
5894
|
-
return
|
|
6078
|
+
return fs24.readFileSync(file, "utf8");
|
|
5895
6079
|
} catch (error) {
|
|
5896
6080
|
if (error.code === "ENOENT") return fallback;
|
|
5897
6081
|
throw error;
|
|
5898
6082
|
}
|
|
5899
6083
|
}
|
|
5900
6084
|
function hashPiTarball(file) {
|
|
5901
|
-
const descriptor =
|
|
6085
|
+
const descriptor = fs24.openSync(file, "r");
|
|
5902
6086
|
const sha2562 = createHash2("sha256");
|
|
5903
6087
|
const sha512 = createHash2("sha512");
|
|
5904
6088
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
5905
6089
|
let bytes = 0;
|
|
5906
6090
|
try {
|
|
5907
6091
|
while (true) {
|
|
5908
|
-
const read =
|
|
6092
|
+
const read = fs24.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
5909
6093
|
if (read === 0) break;
|
|
5910
6094
|
bytes += read;
|
|
5911
6095
|
const chunk = buffer.subarray(0, read);
|
|
@@ -5913,27 +6097,27 @@ function hashPiTarball(file) {
|
|
|
5913
6097
|
sha512.update(chunk);
|
|
5914
6098
|
}
|
|
5915
6099
|
} finally {
|
|
5916
|
-
|
|
6100
|
+
fs24.closeSync(descriptor);
|
|
5917
6101
|
}
|
|
5918
6102
|
return { path: file, bytes, sha256: sha2562.digest("hex"), sha512: sha512.digest("hex") };
|
|
5919
6103
|
}
|
|
5920
6104
|
async function acquirePiTarball(destination) {
|
|
5921
|
-
const existing =
|
|
6105
|
+
const existing = fs24.statSync(destination, { throwIfNoEntry: false });
|
|
5922
6106
|
if (existing?.isFile()) {
|
|
5923
6107
|
const observed = hashPiTarball(destination);
|
|
5924
6108
|
if (observed.bytes === PI_RUNTIME_CANDIDATE.tarball.bytes && observed.sha256 === PI_RUNTIME_CANDIDATE.tarball.sha256 && observed.sha512 === PI_RUNTIME_CANDIDATE.tarball.sha512) {
|
|
5925
6109
|
return observed;
|
|
5926
6110
|
}
|
|
5927
|
-
|
|
6111
|
+
fs24.rmSync(destination, { force: true });
|
|
5928
6112
|
}
|
|
5929
|
-
|
|
6113
|
+
fs24.mkdirSync(path32.dirname(destination), { recursive: true });
|
|
5930
6114
|
const partial = `${destination}.partial-${process.pid}`;
|
|
5931
6115
|
const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
|
|
5932
6116
|
redirect: "error",
|
|
5933
6117
|
headers: { accept: "application/octet-stream" }
|
|
5934
6118
|
});
|
|
5935
6119
|
if (!response.ok || response.body === null) throw new Error(`No se pudo descargar jorgex-pi@${PI_RUNTIME_CANDIDATE.package.version} (${response.status}).`);
|
|
5936
|
-
const descriptor =
|
|
6120
|
+
const descriptor = fs24.openSync(partial, "wx", 384);
|
|
5937
6121
|
let bytes = 0;
|
|
5938
6122
|
try {
|
|
5939
6123
|
for await (const chunk of response.body) {
|
|
@@ -5941,15 +6125,15 @@ async function acquirePiTarball(destination) {
|
|
|
5941
6125
|
bytes += buffer.length;
|
|
5942
6126
|
if (bytes > PI_RUNTIME_CANDIDATE.tarball.bytes) throw new Error("El tarball de jorgex-pi excede el tama\xF1o fijado.");
|
|
5943
6127
|
let offset = 0;
|
|
5944
|
-
while (offset < buffer.length) offset +=
|
|
6128
|
+
while (offset < buffer.length) offset += fs24.writeSync(descriptor, buffer, offset);
|
|
5945
6129
|
}
|
|
5946
6130
|
} catch (error) {
|
|
5947
|
-
|
|
5948
|
-
|
|
6131
|
+
fs24.closeSync(descriptor);
|
|
6132
|
+
fs24.rmSync(partial, { force: true });
|
|
5949
6133
|
throw error;
|
|
5950
6134
|
}
|
|
5951
|
-
|
|
5952
|
-
|
|
6135
|
+
fs24.closeSync(descriptor);
|
|
6136
|
+
fs24.renameSync(partial, destination);
|
|
5953
6137
|
return hashPiTarball(destination);
|
|
5954
6138
|
}
|
|
5955
6139
|
function runProcess(invocation) {
|
|
@@ -5999,7 +6183,7 @@ async function runPiRuntimeSystem(input) {
|
|
|
5999
6183
|
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
6000
6184
|
};
|
|
6001
6185
|
}
|
|
6002
|
-
const destination = input.targetDir === void 0 ?
|
|
6186
|
+
const destination = input.targetDir === void 0 ? path32.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path32.join(path32.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
6003
6187
|
let artifact;
|
|
6004
6188
|
try {
|
|
6005
6189
|
artifact = await acquirePiTarball(destination);
|
|
@@ -6019,18 +6203,18 @@ async function runPiRuntimeSystem(input) {
|
|
|
6019
6203
|
}, {
|
|
6020
6204
|
download: () => artifact,
|
|
6021
6205
|
backupSettings: () => createBackup(
|
|
6022
|
-
[
|
|
6206
|
+
[path32.join(paths.codingAgentDir, "settings.json")],
|
|
6023
6207
|
"pi-package-install",
|
|
6024
|
-
input.targetDir === void 0 ? void 0 :
|
|
6208
|
+
input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
|
|
6025
6209
|
),
|
|
6026
6210
|
run: runProcess,
|
|
6027
|
-
readSettings: () => readOptional(
|
|
6028
|
-
rewriteSettings: (content) => writeText(
|
|
6211
|
+
readSettings: () => readOptional(path32.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
|
|
6212
|
+
rewriteSettings: (content) => writeText(path32.join(paths.codingAgentDir, "settings.json"), `${content}
|
|
6029
6213
|
`),
|
|
6030
6214
|
writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
|
|
6031
6215
|
});
|
|
6032
6216
|
}
|
|
6033
|
-
const packageRoot =
|
|
6217
|
+
const packageRoot = path32.dirname(path32.dirname(paths.packageRunner));
|
|
6034
6218
|
const writeReceipt = (receipt) => {
|
|
6035
6219
|
writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
|
|
6036
6220
|
`);
|
|
@@ -6048,13 +6232,13 @@ async function runPiRuntimeSystem(input) {
|
|
|
6048
6232
|
value,
|
|
6049
6233
|
{
|
|
6050
6234
|
backupSettings: () => createBackup(
|
|
6051
|
-
[
|
|
6235
|
+
[path32.join(paths.codingAgentDir, "settings.json")],
|
|
6052
6236
|
"pi-package-uninstall",
|
|
6053
|
-
input.targetDir === void 0 ? void 0 :
|
|
6237
|
+
input.targetDir === void 0 ? void 0 : path32.join(path32.resolve(input.targetDir), "backups")
|
|
6054
6238
|
),
|
|
6055
6239
|
run: runProcess,
|
|
6056
|
-
isPackageAbsent: () => !
|
|
6057
|
-
deleteReceipt: () =>
|
|
6240
|
+
isPackageAbsent: () => !fs24.existsSync(packageRoot),
|
|
6241
|
+
deleteReceipt: () => fs24.rmSync(paths.receiptPath, { force: true })
|
|
6058
6242
|
}
|
|
6059
6243
|
)
|
|
6060
6244
|
};
|
|
@@ -6126,12 +6310,24 @@ async function runManagedPiSystem(input) {
|
|
|
6126
6310
|
remedy: `Pi ${input.detected.version} no est\xE1 entre las versiones verificadas (${supportedVersions.join(", ")}). Actualiza Stack a una versi\xF3n compatible antes de gestionar Pi.`
|
|
6127
6311
|
};
|
|
6128
6312
|
}
|
|
6129
|
-
const {
|
|
6313
|
+
const {
|
|
6314
|
+
devtoolsMcpEnabled: explicitDevtools,
|
|
6315
|
+
playwrightCliEnabled: explicitPlaywright,
|
|
6316
|
+
writingStyle: suppliedStyle,
|
|
6317
|
+
writingStyleMode,
|
|
6318
|
+
...runtimeInput
|
|
6319
|
+
} = input;
|
|
6320
|
+
const readsStyle = input.operation !== "uninstall" && input.operation !== "models";
|
|
6321
|
+
const preparedStyle = readsStyle && suppliedStyle === void 0 ? prepareWritingStyle(resolveWritingStyleFile({ targetDir: input.targetDir }), { rootDir: input.targetDir }) : void 0;
|
|
6322
|
+
const style = readsStyle ? suppliedStyle ?? preparedStyle : void 0;
|
|
6323
|
+
const mode = readsStyle ? writingStyleMode ?? (input.targetDir === void 0 ? loadInstallModePreference().mode : "human") : "human";
|
|
6324
|
+
const writingStyle = style && mode === "programmatic" ? { ...style, content: null } : style;
|
|
6130
6325
|
const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
|
|
6131
6326
|
const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
|
|
6132
6327
|
const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
|
|
6133
6328
|
const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
|
|
6134
6329
|
const projectionInput = {
|
|
6330
|
+
writingStyle,
|
|
6135
6331
|
targetDir: input.targetDir,
|
|
6136
6332
|
packageSource: PI_RUNTIME_CANDIDATE.package.source,
|
|
6137
6333
|
engramBin: input.engramBin,
|
|
@@ -6141,6 +6337,7 @@ async function runManagedPiSystem(input) {
|
|
|
6141
6337
|
devtoolsMcpEnabled,
|
|
6142
6338
|
pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
|
|
6143
6339
|
};
|
|
6340
|
+
if (preparedStyle !== void 0 && input.operation !== "doctor") applyWritingStyle(preparedStyle);
|
|
6144
6341
|
const result = await runManagedPiOperation(input.operation, {
|
|
6145
6342
|
async runPackage(operation) {
|
|
6146
6343
|
return managedPackageResult(await runPiRuntimeSystem({ ...runtimeInput, operation }));
|
|
@@ -6174,7 +6371,7 @@ async function runManagedPiSystem(input) {
|
|
|
6174
6371
|
}
|
|
6175
6372
|
|
|
6176
6373
|
// src/lib/quality-runner.ts
|
|
6177
|
-
import
|
|
6374
|
+
import path33 from "path";
|
|
6178
6375
|
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
6179
6376
|
function normalizeLimit(value, name) {
|
|
6180
6377
|
if (value === void 0) return void 0;
|
|
@@ -6306,7 +6503,7 @@ function killProcessTree(child) {
|
|
|
6306
6503
|
if (pid === void 0) return;
|
|
6307
6504
|
if (process.platform === "win32") {
|
|
6308
6505
|
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
|
|
6309
|
-
const taskkill =
|
|
6506
|
+
const taskkill = path33.join(systemRoot, "System32", "taskkill.exe");
|
|
6310
6507
|
const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
|
|
6311
6508
|
shell: false,
|
|
6312
6509
|
stdio: "ignore",
|
|
@@ -6552,9 +6749,9 @@ async function runQualityPlan(input) {
|
|
|
6552
6749
|
}
|
|
6553
6750
|
|
|
6554
6751
|
// src/lib/engram-install.ts
|
|
6555
|
-
import
|
|
6752
|
+
import fs25 from "fs";
|
|
6556
6753
|
import os5 from "os";
|
|
6557
|
-
import
|
|
6754
|
+
import path34 from "path";
|
|
6558
6755
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
6559
6756
|
import { createHash as createHash3 } from "crypto";
|
|
6560
6757
|
|
|
@@ -6599,15 +6796,15 @@ var engram_release_default = {
|
|
|
6599
6796
|
async function installMissingEngram(options = {}) {
|
|
6600
6797
|
const platform = options.platform ?? process.platform;
|
|
6601
6798
|
const homeDir = options.homeDir ?? os5.homedir();
|
|
6602
|
-
if (!
|
|
6799
|
+
if (!path34.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
|
|
6603
6800
|
const binaryName = platform === "win32" ? "engram.exe" : "engram";
|
|
6604
|
-
const bin =
|
|
6801
|
+
const bin = path34.join(homeDir, ".local", "bin", binaryName);
|
|
6605
6802
|
let staging;
|
|
6606
6803
|
try {
|
|
6607
|
-
const existing =
|
|
6804
|
+
const existing = fs25.lstatSync(bin, { throwIfNoEntry: false });
|
|
6608
6805
|
if (existing) {
|
|
6609
|
-
if (!
|
|
6610
|
-
|
|
6806
|
+
if (!fs25.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
|
|
6807
|
+
fs25.accessSync(bin, platform === "win32" ? fs25.constants.F_OK : fs25.constants.X_OK);
|
|
6611
6808
|
return { ok: true, bin };
|
|
6612
6809
|
}
|
|
6613
6810
|
const key = `${platform === "win32" ? "windows" : platform}_${options.arch ?? process.arch}`;
|
|
@@ -6636,24 +6833,24 @@ async function installMissingEngram(options = {}) {
|
|
|
6636
6833
|
if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
|
|
6637
6834
|
throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
|
|
6638
6835
|
}
|
|
6639
|
-
|
|
6640
|
-
staging =
|
|
6641
|
-
const archivePath =
|
|
6642
|
-
|
|
6836
|
+
fs25.mkdirSync(path34.dirname(bin), { recursive: true });
|
|
6837
|
+
staging = fs25.mkdtempSync(path34.join(path34.dirname(bin), ".engram-install-"));
|
|
6838
|
+
const archivePath = path34.join(staging, asset.name);
|
|
6839
|
+
fs25.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
|
|
6643
6840
|
const binary = execFileSync7(resolveTarBin(), ["-xOf", archivePath, binaryName], {
|
|
6644
6841
|
maxBuffer: 64 * 1024 * 1024,
|
|
6645
6842
|
timeout: 3e4,
|
|
6646
6843
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6647
6844
|
});
|
|
6648
6845
|
if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
|
|
6649
|
-
const stagedBin =
|
|
6650
|
-
|
|
6651
|
-
|
|
6846
|
+
const stagedBin = path34.join(staging, binaryName);
|
|
6847
|
+
fs25.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
|
|
6848
|
+
fs25.linkSync(stagedBin, bin);
|
|
6652
6849
|
return { ok: true, bin };
|
|
6653
6850
|
} catch (error) {
|
|
6654
6851
|
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
6655
6852
|
} finally {
|
|
6656
|
-
if (staging)
|
|
6853
|
+
if (staging) fs25.rmSync(staging, { recursive: true, force: true });
|
|
6657
6854
|
}
|
|
6658
6855
|
}
|
|
6659
6856
|
|
|
@@ -6977,7 +7174,7 @@ async function resolveHostEngramForInstall(flags) {
|
|
|
6977
7174
|
return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
|
|
6978
7175
|
}
|
|
6979
7176
|
}
|
|
6980
|
-
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, playwrightCliEnabled) {
|
|
7177
|
+
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, writingStyle, modePreference, playwrightCliEnabled) {
|
|
6981
7178
|
if (targetDir === void 0 && operation !== "models") {
|
|
6982
7179
|
const preferenceErrors = browserPreferenceErrors();
|
|
6983
7180
|
if (preferenceErrors.length > 0) {
|
|
@@ -6985,6 +7182,13 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
|
|
|
6985
7182
|
return 1;
|
|
6986
7183
|
}
|
|
6987
7184
|
}
|
|
7185
|
+
if (operation === "install" || operation === "sync" || operation === "update") {
|
|
7186
|
+
if (writingStyle === void 0) {
|
|
7187
|
+
const prepared = prepareWritingStyle(resolveWritingStyleFile({ targetDir }), { rootDir: targetDir });
|
|
7188
|
+
applyWritingStyle(prepared);
|
|
7189
|
+
writingStyle = prepared;
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
6988
7192
|
const detected = detectPiRuntime();
|
|
6989
7193
|
if (!detected.installed || detected.executable === null) {
|
|
6990
7194
|
console.error("Pi no detectado. Instala el runtime Pi antes de gestionar jorgex-pi.");
|
|
@@ -7016,6 +7220,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
|
|
|
7016
7220
|
engramBin = requirement.bin;
|
|
7017
7221
|
}
|
|
7018
7222
|
const result = await runManagedPiSystem({
|
|
7223
|
+
writingStyle,
|
|
7224
|
+
writingStyleMode: modePreference?.mode,
|
|
7019
7225
|
operation,
|
|
7020
7226
|
targetDir,
|
|
7021
7227
|
detected: { executable: detected.executable, version: detected.version },
|
|
@@ -7032,6 +7238,10 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
|
|
|
7032
7238
|
else p6.log.success(`Pi: ${result.kind}.`);
|
|
7033
7239
|
return 0;
|
|
7034
7240
|
}
|
|
7241
|
+
function persistSuccessfulGlobalMode(mode, targetDir, dryRun, exitCode) {
|
|
7242
|
+
if (mode === void 0 || targetDir !== void 0 || dryRun || exitCode !== 0) return;
|
|
7243
|
+
saveInstallModePreference(installModePreferenceFile(), mode);
|
|
7244
|
+
}
|
|
7035
7245
|
function printHelp() {
|
|
7036
7246
|
console.log(`jorgex-stack v${VERSION}
|
|
7037
7247
|
|
|
@@ -7125,7 +7335,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7125
7335
|
return;
|
|
7126
7336
|
}
|
|
7127
7337
|
try {
|
|
7128
|
-
const plan = JSON.parse(
|
|
7338
|
+
const plan = JSON.parse(fs26.readFileSync(flags.positional[0], "utf8"));
|
|
7129
7339
|
const result = await runQualityPlan(plan);
|
|
7130
7340
|
const serialized = serializeQualityReceipt(result.receipt);
|
|
7131
7341
|
if (flags.receipt === void 0) process.stdout.write(`${serialized}
|
|
@@ -7153,6 +7363,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7153
7363
|
let completed = false;
|
|
7154
7364
|
p6.intro(`jorgex-stack ${command}${flags.dryRun ? " (dry-run)" : ""}`);
|
|
7155
7365
|
try {
|
|
7366
|
+
const writingStyle = prepareWritingStyle(resolveWritingStyleFile({ targetDir: flags.targetDir }), { rootDir: flags.targetDir });
|
|
7156
7367
|
if (flags.targetDir === void 0) {
|
|
7157
7368
|
const errors = browserPreferenceErrors();
|
|
7158
7369
|
if (errors.length > 0) {
|
|
@@ -7161,7 +7372,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7161
7372
|
return;
|
|
7162
7373
|
}
|
|
7163
7374
|
}
|
|
7164
|
-
const mode = fileRuntimes.length > 0 ? await resolveInstallMode(flags) : void 0;
|
|
7375
|
+
const mode = fileRuntimes.length > 0 || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 ? await resolveInstallMode(flags) : void 0;
|
|
7165
7376
|
if (mode === null) return;
|
|
7166
7377
|
const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, runtimes);
|
|
7167
7378
|
if (devtoolsMcpSelection === null) {
|
|
@@ -7173,6 +7384,8 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7173
7384
|
exitCode = process.exitCode === 1 ? 1 : 0;
|
|
7174
7385
|
return;
|
|
7175
7386
|
}
|
|
7387
|
+
p6.log.info(`Estilo de escritura: ${writingStyle.sourcePath}${flags.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
|
|
7388
|
+
applyWritingStyle(writingStyle, flags.dryRun);
|
|
7176
7389
|
if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
|
|
7177
7390
|
exitCode = 1;
|
|
7178
7391
|
return;
|
|
@@ -7190,6 +7403,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7190
7403
|
if (fileRuntimes.length > 0) {
|
|
7191
7404
|
exitCode = await runInstall({
|
|
7192
7405
|
runtimes: fileRuntimes,
|
|
7406
|
+
writingStyle,
|
|
7193
7407
|
targetDir: flags.targetDir,
|
|
7194
7408
|
dryRun: flags.dryRun,
|
|
7195
7409
|
yes: flags.yes,
|
|
@@ -7206,6 +7420,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7206
7420
|
p6.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
|
|
7207
7421
|
} else {
|
|
7208
7422
|
exitCode = await runInstall({
|
|
7423
|
+
writingStyle,
|
|
7209
7424
|
runtimes: [],
|
|
7210
7425
|
targetDir: flags.targetDir,
|
|
7211
7426
|
dryRun: false,
|
|
@@ -7219,15 +7434,21 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7219
7434
|
}
|
|
7220
7435
|
if (runtimes.includes("pi") && piCanRun) {
|
|
7221
7436
|
if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
|
|
7222
|
-
else
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7437
|
+
else {
|
|
7438
|
+
const piExitCode = await runSelectedPi(
|
|
7439
|
+
command,
|
|
7440
|
+
flags.targetDir,
|
|
7441
|
+
flags.yes,
|
|
7442
|
+
flags.targetDir === void 0 ? engramBin : void 0,
|
|
7443
|
+
devtoolsMcpSelection.pi,
|
|
7444
|
+
writingStyle,
|
|
7445
|
+
mode,
|
|
7446
|
+
flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
|
|
7447
|
+
);
|
|
7448
|
+
exitCode = Math.max(exitCode, piExitCode);
|
|
7449
|
+
}
|
|
7230
7450
|
}
|
|
7451
|
+
if (runtimes.includes("pi")) persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, exitCode);
|
|
7231
7452
|
completed = true;
|
|
7232
7453
|
} catch (error) {
|
|
7233
7454
|
p6.log.error(error instanceof Error ? error.message : String(error));
|
|
@@ -7264,10 +7485,15 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7264
7485
|
return;
|
|
7265
7486
|
}
|
|
7266
7487
|
case "doctor": {
|
|
7267
|
-
const
|
|
7268
|
-
|
|
7488
|
+
const mode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0 ? await resolveInstallMode(flags) : void 0;
|
|
7489
|
+
if (mode === null) return;
|
|
7269
7490
|
const piSelected = flags.agents.includes("pi") || flags.agents.length === 0 && detectPiRuntime().installed && hasManagedPiRuntime(flags.targetDir);
|
|
7270
|
-
|
|
7491
|
+
const doctorRuntimes = flags.agents.length > 0 ? flags.agents : flags.targetDir === void 0 ? [
|
|
7492
|
+
...Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => adapter.id),
|
|
7493
|
+
...piSelected ? ["pi"] : []
|
|
7494
|
+
] : [...Object.keys(ADAPTERS), ...piSelected ? ["pi"] : []];
|
|
7495
|
+
let exitCode = await runDoctor({ targetDir: flags.targetDir, runtimes: doctorRuntimes, mode });
|
|
7496
|
+
if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir, false, void 0, void 0, void 0, mode));
|
|
7271
7497
|
process.exitCode = exitCode;
|
|
7272
7498
|
return;
|
|
7273
7499
|
}
|
|
@@ -7283,20 +7509,28 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7283
7509
|
const runtimes = await resolveRuntimes(flags);
|
|
7284
7510
|
if (runtimes === null) return;
|
|
7285
7511
|
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
7286
|
-
if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
|
|
7287
|
-
process.exitCode = await runSelectedPi("update", flags.targetDir);
|
|
7288
|
-
return;
|
|
7289
|
-
}
|
|
7290
7512
|
const preferenceFile = installModePreferenceFile();
|
|
7291
7513
|
const explicitMode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0;
|
|
7292
7514
|
const hasSavedMode = hasInstallModePreference(preferenceFile);
|
|
7293
7515
|
const canResolveMode = flags.targetDir !== void 0 || explicitMode || hasSavedMode;
|
|
7294
|
-
const mode =
|
|
7516
|
+
const mode = canResolveMode ? await resolveInstallMode(flags, false) : DEFAULT_INSTALL_MODE_PREFERENCE;
|
|
7295
7517
|
if (mode === null) return;
|
|
7518
|
+
const writingStyle = prepareWritingStyle(
|
|
7519
|
+
resolveWritingStyleFile({ targetDir: flags.targetDir }),
|
|
7520
|
+
{ rootDir: flags.targetDir }
|
|
7521
|
+
);
|
|
7522
|
+
applyWritingStyle(writingStyle, flags.dryRun);
|
|
7523
|
+
if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
|
|
7524
|
+
const piExitCode = await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode);
|
|
7525
|
+
process.exitCode = piExitCode;
|
|
7526
|
+
persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, piExitCode);
|
|
7527
|
+
return;
|
|
7528
|
+
}
|
|
7296
7529
|
const canSync = fileRuntimes.length === 0 || canResolveMode;
|
|
7297
7530
|
if (fileRuntimes.length > 0 && canSync) {
|
|
7298
7531
|
const code = await runInstall({
|
|
7299
7532
|
runtimes: fileRuntimes,
|
|
7533
|
+
writingStyle,
|
|
7300
7534
|
targetDir: flags.targetDir,
|
|
7301
7535
|
dryRun: flags.dryRun,
|
|
7302
7536
|
yes: true,
|
|
@@ -7317,7 +7551,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7317
7551
|
);
|
|
7318
7552
|
process.exitCode = result.exitCode;
|
|
7319
7553
|
if (result.exitCode === 0 && runtimes.includes("pi")) {
|
|
7320
|
-
process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir));
|
|
7554
|
+
process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode));
|
|
7321
7555
|
}
|
|
7322
7556
|
if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
|
|
7323
7557
|
p6.log.warn("Skills/stack actualizados, pero el sync con los runtimes sigue pendiente. Ejecuta jorgex-stack sync --mode human|programmatic.");
|
|
@@ -7326,6 +7560,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7326
7560
|
if (!p6.isCancel(apply) && apply) {
|
|
7327
7561
|
process.exitCode = await runInstall({
|
|
7328
7562
|
runtimes: fileRuntimes,
|
|
7563
|
+
writingStyle,
|
|
7329
7564
|
targetDir: flags.targetDir,
|
|
7330
7565
|
dryRun: false,
|
|
7331
7566
|
yes: false,
|
|
@@ -7337,6 +7572,9 @@ Flags disponibles: jorgex-stack --help`
|
|
|
7337
7572
|
} else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && (flags.yes || !process.stdout.isTTY)) {
|
|
7338
7573
|
console.log("Skills/stack actualizados. Ejecuta jorgex-stack sync para aplicarlos a los runtimes.");
|
|
7339
7574
|
}
|
|
7575
|
+
if (runtimes.includes("pi")) {
|
|
7576
|
+
persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, process.exitCode ?? result.exitCode);
|
|
7577
|
+
}
|
|
7340
7578
|
return;
|
|
7341
7579
|
}
|
|
7342
7580
|
case "models": {
|