jorgex-stack 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-IO2FFZOH.js +496 -0
- package/dist/cli.d.ts +41 -0
- package/dist/cli.js +728 -304
- package/dist/quality-verifier.d.ts +150 -0
- package/dist/quality-verifier.js +249 -0
- package/package.json +7 -1
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HOME,
|
|
4
|
+
QUALITY_PROFILES,
|
|
5
|
+
canonicalJson,
|
|
6
|
+
createQualityReceipt,
|
|
7
|
+
dataDir,
|
|
8
|
+
evaluateQualityPolicy,
|
|
9
|
+
samePath,
|
|
10
|
+
serializeQualityReceipt,
|
|
11
|
+
sha256,
|
|
12
|
+
stackRoot,
|
|
13
|
+
validateQualityReceipt
|
|
14
|
+
} from "./chunk-IO2FFZOH.js";
|
|
2
15
|
|
|
3
16
|
// src/cli.ts
|
|
4
17
|
import * as p6 from "@clack/prompts";
|
|
18
|
+
import fs24 from "fs";
|
|
5
19
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
6
20
|
|
|
7
21
|
// src/install.ts
|
|
8
22
|
import fs15 from "fs";
|
|
9
|
-
import
|
|
23
|
+
import path20 from "path";
|
|
10
24
|
import * as p from "@clack/prompts";
|
|
11
25
|
|
|
12
26
|
// src/adapters/opencode.ts
|
|
13
|
-
import
|
|
27
|
+
import path6 from "path";
|
|
14
28
|
import fs3 from "fs";
|
|
15
29
|
import { pathToFileURL } from "url";
|
|
16
30
|
|
|
@@ -67,37 +81,12 @@ function loadCanonicalDefaults(stackDir) {
|
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
// src/lib/model-map.ts
|
|
70
|
-
import
|
|
71
|
-
import { existsSync as existsSync2 } from "fs";
|
|
72
|
-
|
|
73
|
-
// src/lib/paths.ts
|
|
74
|
-
import os from "os";
|
|
75
|
-
import path2 from "path";
|
|
84
|
+
import path3 from "path";
|
|
76
85
|
import { existsSync } from "fs";
|
|
77
|
-
import { fileURLToPath } from "url";
|
|
78
|
-
var HOME = os.homedir();
|
|
79
|
-
function stackRoot() {
|
|
80
|
-
let dir = path2.dirname(fileURLToPath(import.meta.url));
|
|
81
|
-
for (let i = 0; i < 6; i++) {
|
|
82
|
-
const candidate = path2.join(dir, "stack");
|
|
83
|
-
if (existsSync(path2.join(candidate, "system-prompt", "AGENTS.md"))) return candidate;
|
|
84
|
-
dir = path2.dirname(dir);
|
|
85
|
-
}
|
|
86
|
-
throw new Error("No se encontr\xF3 stack/ relativa al CLI \u2014 instalaci\xF3n rota.");
|
|
87
|
-
}
|
|
88
|
-
function dataDir() {
|
|
89
|
-
return path2.join(HOME, ".jorgex-stack");
|
|
90
|
-
}
|
|
91
|
-
function samePath(a, b) {
|
|
92
|
-
const resolvedA = path2.resolve(a);
|
|
93
|
-
const resolvedB = path2.resolve(b);
|
|
94
|
-
if (process.platform === "win32") return resolvedA.toLowerCase() === resolvedB.toLowerCase();
|
|
95
|
-
return resolvedA === resolvedB;
|
|
96
|
-
}
|
|
97
86
|
|
|
98
87
|
// src/lib/fsx.ts
|
|
99
88
|
import fs2 from "fs";
|
|
100
|
-
import
|
|
89
|
+
import path2 from "path";
|
|
101
90
|
function readTextIfExists(file) {
|
|
102
91
|
try {
|
|
103
92
|
return fs2.readFileSync(file, "utf8");
|
|
@@ -110,7 +99,7 @@ function ensureDir(dir) {
|
|
|
110
99
|
}
|
|
111
100
|
var tmpCounter = 0;
|
|
112
101
|
function tmpPath(target) {
|
|
113
|
-
return
|
|
102
|
+
return path2.join(path2.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
|
|
114
103
|
}
|
|
115
104
|
function renameInto(tmp, target) {
|
|
116
105
|
try {
|
|
@@ -121,23 +110,23 @@ function renameInto(tmp, target) {
|
|
|
121
110
|
}
|
|
122
111
|
}
|
|
123
112
|
function writeText(file, content) {
|
|
124
|
-
ensureDir(
|
|
113
|
+
ensureDir(path2.dirname(file));
|
|
125
114
|
const tmp = tmpPath(file);
|
|
126
115
|
fs2.writeFileSync(tmp, content, "utf8");
|
|
127
116
|
renameInto(tmp, file);
|
|
128
117
|
}
|
|
129
118
|
function copyFile(source, target) {
|
|
130
|
-
ensureDir(
|
|
119
|
+
ensureDir(path2.dirname(target));
|
|
131
120
|
const tmp = tmpPath(target);
|
|
132
121
|
fs2.copyFileSync(source, tmp);
|
|
133
122
|
renameInto(tmp, target);
|
|
134
123
|
}
|
|
135
124
|
function isContainedIn(child, root) {
|
|
136
|
-
const rel =
|
|
137
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
125
|
+
const rel = path2.relative(path2.resolve(root), path2.resolve(child));
|
|
126
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path2.sep}`) && !path2.isAbsolute(rel);
|
|
138
127
|
}
|
|
139
128
|
function pruneEmptyDirs(file, root) {
|
|
140
|
-
let dir =
|
|
129
|
+
let dir = path2.dirname(file);
|
|
141
130
|
while (dir.startsWith(root) && dir !== root) {
|
|
142
131
|
try {
|
|
143
132
|
if (fs2.readdirSync(dir).length > 0) return;
|
|
@@ -145,7 +134,7 @@ function pruneEmptyDirs(file, root) {
|
|
|
145
134
|
} catch {
|
|
146
135
|
return;
|
|
147
136
|
}
|
|
148
|
-
dir =
|
|
137
|
+
dir = path2.dirname(dir);
|
|
149
138
|
}
|
|
150
139
|
}
|
|
151
140
|
function sameFileContent(a, b) {
|
|
@@ -162,7 +151,7 @@ function listFilesRecursive(dir) {
|
|
|
162
151
|
if (!fs2.existsSync(dir)) return out;
|
|
163
152
|
const walk = (d) => {
|
|
164
153
|
for (const entry of fs2.readdirSync(d, { withFileTypes: true })) {
|
|
165
|
-
const p7 =
|
|
154
|
+
const p7 = path2.join(d, entry.name);
|
|
166
155
|
if (entry.isDirectory()) walk(p7);
|
|
167
156
|
else out.push(p7);
|
|
168
157
|
}
|
|
@@ -197,7 +186,7 @@ var DEFAULT_MODEL_MAP = {
|
|
|
197
186
|
}
|
|
198
187
|
};
|
|
199
188
|
function modelMapFile() {
|
|
200
|
-
return
|
|
189
|
+
return path3.join(dataDir(), "model-map.json");
|
|
201
190
|
}
|
|
202
191
|
function loadModelMap() {
|
|
203
192
|
const raw = readTextIfExists(modelMapFile());
|
|
@@ -216,22 +205,22 @@ function loadModelMap() {
|
|
|
216
205
|
}
|
|
217
206
|
function ensureModelMapFile() {
|
|
218
207
|
const file = modelMapFile();
|
|
219
|
-
if (!
|
|
208
|
+
if (!existsSync(file)) {
|
|
220
209
|
writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
|
|
221
210
|
}
|
|
222
211
|
return file;
|
|
223
212
|
}
|
|
224
213
|
|
|
225
214
|
// src/lib/detect.ts
|
|
226
|
-
import
|
|
227
|
-
import { existsSync as
|
|
215
|
+
import path4 from "path";
|
|
216
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
228
217
|
import { execFileSync } from "child_process";
|
|
229
218
|
function lookPath(cmd) {
|
|
230
219
|
const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
|
|
231
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
220
|
+
for (const dir of (process.env.PATH ?? "").split(path4.delimiter)) {
|
|
232
221
|
if (!dir) continue;
|
|
233
222
|
for (const ext of exts) {
|
|
234
|
-
const candidate =
|
|
223
|
+
const candidate = path4.join(dir, cmd + ext);
|
|
235
224
|
if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
|
|
236
225
|
}
|
|
237
226
|
}
|
|
@@ -260,50 +249,50 @@ function runDetectedBin(bin, args, timeoutMs) {
|
|
|
260
249
|
}
|
|
261
250
|
}
|
|
262
251
|
function detectOpenCode() {
|
|
263
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ??
|
|
252
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR ?? path4.join(HOME, ".config", "opencode");
|
|
264
253
|
const binPath = lookPath("opencode");
|
|
265
254
|
return {
|
|
266
255
|
id: "opencode",
|
|
267
256
|
name: "OpenCode",
|
|
268
|
-
installed: binPath !== null ||
|
|
257
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
269
258
|
binPath,
|
|
270
259
|
configDir
|
|
271
260
|
};
|
|
272
261
|
}
|
|
273
262
|
function detectClaudeCode() {
|
|
274
|
-
const configDir =
|
|
263
|
+
const configDir = path4.join(HOME, ".claude");
|
|
275
264
|
const binPath = lookPath("claude");
|
|
276
265
|
return {
|
|
277
266
|
id: "claude-code",
|
|
278
267
|
name: "Claude Code",
|
|
279
|
-
installed: binPath !== null ||
|
|
268
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
280
269
|
binPath,
|
|
281
270
|
configDir
|
|
282
271
|
};
|
|
283
272
|
}
|
|
284
273
|
function detectCodex() {
|
|
285
|
-
const configDir = process.env.CODEX_HOME ??
|
|
274
|
+
const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
|
|
286
275
|
const binPath = lookPath("codex");
|
|
287
276
|
return {
|
|
288
277
|
id: "codex",
|
|
289
278
|
name: "Codex CLI",
|
|
290
|
-
installed: binPath !== null ||
|
|
279
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
291
280
|
binPath,
|
|
292
281
|
configDir
|
|
293
282
|
};
|
|
294
283
|
}
|
|
295
284
|
function detectEngram() {
|
|
296
285
|
const fromEnv = process.env.ENGRAM_BIN;
|
|
297
|
-
if (fromEnv &&
|
|
286
|
+
if (fromEnv && existsSync2(fromEnv)) return fromEnv;
|
|
298
287
|
const onPath = lookPath("engram");
|
|
299
288
|
if (onPath) return onPath;
|
|
300
289
|
const candidates = [
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
290
|
+
path4.join(HOME, "go", "bin", "engram.exe"),
|
|
291
|
+
path4.join(HOME, "go", "bin", "engram"),
|
|
292
|
+
path4.join(HOME, ".local", "bin", "engram.exe"),
|
|
293
|
+
path4.join(HOME, ".local", "bin", "engram")
|
|
305
294
|
];
|
|
306
|
-
for (const c of candidates) if (
|
|
295
|
+
for (const c of candidates) if (existsSync2(c)) return c;
|
|
307
296
|
return null;
|
|
308
297
|
}
|
|
309
298
|
|
|
@@ -472,9 +461,9 @@ function readTomlSection(existing, section) {
|
|
|
472
461
|
}
|
|
473
462
|
|
|
474
463
|
// src/lib/hooks-format.ts
|
|
475
|
-
import
|
|
464
|
+
import path5 from "path";
|
|
476
465
|
function hookScriptNames(canonical) {
|
|
477
|
-
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) =>
|
|
466
|
+
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) => path5.basename(s));
|
|
478
467
|
}
|
|
479
468
|
function plainHookCommands(canonical) {
|
|
480
469
|
return new Set(
|
|
@@ -521,7 +510,7 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
|
|
|
521
510
|
...h.timeout !== void 0 ? { timeout: h.timeout } : {}
|
|
522
511
|
}))
|
|
523
512
|
};
|
|
524
|
-
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) =>
|
|
513
|
+
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
|
|
525
514
|
const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
|
|
526
515
|
const index = list.findIndex((existingEntry) => {
|
|
527
516
|
const e = existingEntry;
|
|
@@ -601,16 +590,16 @@ var opencodeAdapter = {
|
|
|
601
590
|
paths(configDir) {
|
|
602
591
|
const isRealConfigDir = samePath(
|
|
603
592
|
configDir,
|
|
604
|
-
process.env.OPENCODE_CONFIG_DIR ??
|
|
593
|
+
process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode")
|
|
605
594
|
);
|
|
606
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
595
|
+
const agentsHome = isRealConfigDir ? HOME : path6.dirname(configDir);
|
|
607
596
|
return {
|
|
608
|
-
systemPromptFile:
|
|
609
|
-
agentsDir:
|
|
610
|
-
skillsDir:
|
|
611
|
-
commandsDir:
|
|
612
|
-
pluginsDir:
|
|
613
|
-
scriptsDir:
|
|
597
|
+
systemPromptFile: path6.join(configDir, "AGENTS.md"),
|
|
598
|
+
agentsDir: path6.join(configDir, "agents"),
|
|
599
|
+
skillsDir: path6.join(agentsHome, ".agents", "skills"),
|
|
600
|
+
commandsDir: path6.join(configDir, "commands"),
|
|
601
|
+
pluginsDir: path6.join(configDir, "plugins"),
|
|
602
|
+
scriptsDir: path6.join(configDir, "scripts"),
|
|
614
603
|
outputStylesDir: null,
|
|
615
604
|
profilesDir: null
|
|
616
605
|
};
|
|
@@ -668,12 +657,12 @@ ${agent.body}`,
|
|
|
668
657
|
ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
|
|
669
658
|
continue;
|
|
670
659
|
}
|
|
671
|
-
const script = `scripts/${
|
|
660
|
+
const script = `scripts/${path6.basename(match[1])}`;
|
|
672
661
|
(bashEntries[includes] ??= []).push(script);
|
|
673
662
|
}
|
|
674
663
|
}
|
|
675
664
|
}
|
|
676
|
-
const hooksFile =
|
|
665
|
+
const hooksFile = path6.join(ctx.configDir, "hooks.json");
|
|
677
666
|
const content = upsertJson(readTextIfExists(hooksFile), (root) => {
|
|
678
667
|
const afterValue = root["tool.execute.after"] ??= {};
|
|
679
668
|
if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
|
|
@@ -708,16 +697,16 @@ ${agent.body}`,
|
|
|
708
697
|
}
|
|
709
698
|
});
|
|
710
699
|
actions.push({ kind: "write", target: hooksFile, content });
|
|
711
|
-
const scriptsSource =
|
|
700
|
+
const scriptsSource = path6.join(ctx.stackDir, "scripts");
|
|
712
701
|
if (fs3.existsSync(scriptsSource)) {
|
|
713
702
|
for (const f of fs3.readdirSync(scriptsSource)) {
|
|
714
|
-
actions.push({ kind: "copy", source:
|
|
703
|
+
actions.push({ kind: "copy", source: path6.join(scriptsSource, f), target: path6.join(scriptsDir, f) });
|
|
715
704
|
}
|
|
716
705
|
}
|
|
717
706
|
return actions;
|
|
718
707
|
},
|
|
719
708
|
planMainConfig(canonical, ctx) {
|
|
720
|
-
const file =
|
|
709
|
+
const file = path6.join(ctx.configDir, "opencode.json");
|
|
721
710
|
const { pluginsDir } = this.paths(ctx.configDir);
|
|
722
711
|
const original = readTextIfExists(file);
|
|
723
712
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
@@ -829,7 +818,7 @@ ${agent.body}`,
|
|
|
829
818
|
content = removeMarkdownSection(content, "browser");
|
|
830
819
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
831
820
|
}
|
|
832
|
-
const configFile =
|
|
821
|
+
const configFile = path6.join(ctx.configDir, "opencode.json");
|
|
833
822
|
const config = readTextIfExists(configFile);
|
|
834
823
|
if (config !== null) {
|
|
835
824
|
const mcpOwnership = [];
|
|
@@ -897,7 +886,7 @@ ${agent.body}`,
|
|
|
897
886
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
898
887
|
});
|
|
899
888
|
}
|
|
900
|
-
const hooksFile =
|
|
889
|
+
const hooksFile = path6.join(ctx.configDir, "hooks.json");
|
|
901
890
|
const hooksJson = readTextIfExists(hooksFile);
|
|
902
891
|
if (hooksJson !== null) {
|
|
903
892
|
const ourScripts = hookScriptNames(hooks);
|
|
@@ -923,7 +912,7 @@ ${agent.body}`,
|
|
|
923
912
|
};
|
|
924
913
|
|
|
925
914
|
// src/adapters/claude-code.ts
|
|
926
|
-
import
|
|
915
|
+
import path7 from "path";
|
|
927
916
|
import fs4 from "fs";
|
|
928
917
|
function yamlString2(value) {
|
|
929
918
|
return JSON.stringify(value);
|
|
@@ -944,8 +933,8 @@ function toolsFor(agent) {
|
|
|
944
933
|
return tools.join(", ");
|
|
945
934
|
}
|
|
946
935
|
function hasEngramPlugin(configDir) {
|
|
947
|
-
if (fs4.existsSync(
|
|
948
|
-
const registry = readTextIfExists(
|
|
936
|
+
if (fs4.existsSync(path7.join(configDir, "plugins", "marketplaces", "engram"))) return true;
|
|
937
|
+
const registry = readTextIfExists(path7.join(configDir, "plugins", "installed_plugins.json"));
|
|
949
938
|
if (registry === null) return false;
|
|
950
939
|
try {
|
|
951
940
|
const parsed = JSON.parse(registry);
|
|
@@ -972,13 +961,13 @@ var claudeCodeAdapter = {
|
|
|
972
961
|
},
|
|
973
962
|
paths(configDir) {
|
|
974
963
|
return {
|
|
975
|
-
systemPromptFile:
|
|
976
|
-
agentsDir:
|
|
977
|
-
skillsDir:
|
|
978
|
-
commandsDir:
|
|
964
|
+
systemPromptFile: path7.join(configDir, "CLAUDE.md"),
|
|
965
|
+
agentsDir: path7.join(configDir, "agents"),
|
|
966
|
+
skillsDir: path7.join(configDir, "skills"),
|
|
967
|
+
commandsDir: path7.join(configDir, "commands"),
|
|
979
968
|
pluginsDir: null,
|
|
980
|
-
scriptsDir:
|
|
981
|
-
outputStylesDir:
|
|
969
|
+
scriptsDir: path7.join(configDir, "scripts"),
|
|
970
|
+
outputStylesDir: path7.join(configDir, "output-styles"),
|
|
982
971
|
profilesDir: null
|
|
983
972
|
};
|
|
984
973
|
},
|
|
@@ -1024,9 +1013,9 @@ ${agent.body}`,
|
|
|
1024
1013
|
planHooks(canonical, ctx) {
|
|
1025
1014
|
const actions = [];
|
|
1026
1015
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1027
|
-
const original = readTextIfExists(
|
|
1016
|
+
const original = readTextIfExists(path7.join(ctx.configDir, "settings.json"));
|
|
1028
1017
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1029
|
-
const settingsFile =
|
|
1018
|
+
const settingsFile = path7.join(ctx.configDir, "settings.json");
|
|
1030
1019
|
let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
|
|
1031
1020
|
const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
|
|
1032
1021
|
if (contentSource === null && defaults?.["permissions"] !== void 0) {
|
|
@@ -1040,16 +1029,16 @@ ${agent.body}`,
|
|
|
1040
1029
|
});
|
|
1041
1030
|
}
|
|
1042
1031
|
actions.push({ kind: "write", target: settingsFile, content });
|
|
1043
|
-
const scriptsSource =
|
|
1032
|
+
const scriptsSource = path7.join(ctx.stackDir, "scripts");
|
|
1044
1033
|
if (fs4.existsSync(scriptsSource)) {
|
|
1045
1034
|
for (const f of fs4.readdirSync(scriptsSource)) {
|
|
1046
|
-
actions.push({ kind: "copy", source:
|
|
1035
|
+
actions.push({ kind: "copy", source: path7.join(scriptsSource, f), target: path7.join(scriptsDir, f) });
|
|
1047
1036
|
}
|
|
1048
1037
|
}
|
|
1049
1038
|
return actions;
|
|
1050
1039
|
},
|
|
1051
1040
|
planMainConfig(canonical, ctx) {
|
|
1052
|
-
const file =
|
|
1041
|
+
const file = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
|
|
1053
1042
|
const mcpOwnership = [];
|
|
1054
1043
|
const content = upsertJson(readTextIfExists(file), (root) => {
|
|
1055
1044
|
const servers = root["mcpServers"] ??= {};
|
|
@@ -1114,7 +1103,7 @@ ${agent.body}`,
|
|
|
1114
1103
|
content = removeMarkdownSection(content, "browser");
|
|
1115
1104
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1116
1105
|
}
|
|
1117
|
-
const settingsFile =
|
|
1106
|
+
const settingsFile = path7.join(ctx.configDir, "settings.json");
|
|
1118
1107
|
const settings = readTextIfExists(settingsFile);
|
|
1119
1108
|
if (settings !== null) {
|
|
1120
1109
|
const content = removeNativeHooks(settings, hooks);
|
|
@@ -1122,7 +1111,7 @@ ${agent.body}`,
|
|
|
1122
1111
|
actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
|
|
1123
1112
|
}
|
|
1124
1113
|
}
|
|
1125
|
-
const mainFile =
|
|
1114
|
+
const mainFile = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
|
|
1126
1115
|
const main2 = readTextIfExists(mainFile);
|
|
1127
1116
|
if (main2 !== null) {
|
|
1128
1117
|
const mcpOwnership = [];
|
|
@@ -1148,7 +1137,7 @@ ${agent.body}`,
|
|
|
1148
1137
|
};
|
|
1149
1138
|
|
|
1150
1139
|
// src/adapters/codex.ts
|
|
1151
|
-
import
|
|
1140
|
+
import path8 from "path";
|
|
1152
1141
|
import fs5 from "fs";
|
|
1153
1142
|
function tomlString(value) {
|
|
1154
1143
|
return JSON.stringify(value);
|
|
@@ -1172,15 +1161,15 @@ function isManagedOptionalStdioServer3(server, section) {
|
|
|
1172
1161
|
return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
|
|
1173
1162
|
}
|
|
1174
1163
|
function hasActiveEngramPlugin(configDir) {
|
|
1175
|
-
const config = readTextIfExists(
|
|
1164
|
+
const config = readTextIfExists(path8.join(configDir, "config.toml"));
|
|
1176
1165
|
if (config === null) return false;
|
|
1177
1166
|
const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
|
|
1178
1167
|
return match !== null && !/enabled\s*=\s*false/.test(match[1]);
|
|
1179
1168
|
}
|
|
1180
1169
|
function hasEngramProtocol(configDir) {
|
|
1181
1170
|
if (hasActiveEngramPlugin(configDir)) return true;
|
|
1182
|
-
if (fs5.existsSync(
|
|
1183
|
-
const config = readTextIfExists(
|
|
1171
|
+
if (fs5.existsSync(path8.join(configDir, "engram-instructions.md"))) return true;
|
|
1172
|
+
const config = readTextIfExists(path8.join(configDir, "config.toml"));
|
|
1184
1173
|
return config !== null && /engram-instructions\.md/.test(config);
|
|
1185
1174
|
}
|
|
1186
1175
|
var codexAdapter = {
|
|
@@ -1191,18 +1180,18 @@ var codexAdapter = {
|
|
|
1191
1180
|
return !hasEngramProtocol(ctx.configDir);
|
|
1192
1181
|
},
|
|
1193
1182
|
paths(configDir) {
|
|
1194
|
-
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ??
|
|
1195
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
1196
|
-
const skillsDir =
|
|
1183
|
+
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path8.join(HOME, ".codex"));
|
|
1184
|
+
const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
|
|
1185
|
+
const skillsDir = path8.join(agentsHome, ".agents", "skills");
|
|
1197
1186
|
return {
|
|
1198
|
-
systemPromptFile:
|
|
1199
|
-
agentsDir:
|
|
1187
|
+
systemPromptFile: path8.join(configDir, "AGENTS.md"),
|
|
1188
|
+
agentsDir: path8.join(configDir, "agents"),
|
|
1200
1189
|
skillsDir,
|
|
1201
1190
|
// Los custom prompts de Codex están deprecados: los commands se
|
|
1202
1191
|
// instalan como skills (renderCommand produce <nombre>/SKILL.md).
|
|
1203
1192
|
commandsDir: skillsDir,
|
|
1204
1193
|
pluginsDir: null,
|
|
1205
|
-
scriptsDir:
|
|
1194
|
+
scriptsDir: path8.join(configDir, "scripts"),
|
|
1206
1195
|
outputStylesDir: null,
|
|
1207
1196
|
profilesDir: configDir
|
|
1208
1197
|
};
|
|
@@ -1250,7 +1239,7 @@ ${body}`
|
|
|
1250
1239
|
planHooks(canonical, ctx) {
|
|
1251
1240
|
const actions = [];
|
|
1252
1241
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1253
|
-
const hooksFile =
|
|
1242
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
1254
1243
|
const content = upsertNativeHooks(
|
|
1255
1244
|
readTextIfExists(hooksFile),
|
|
1256
1245
|
canonical,
|
|
@@ -1261,16 +1250,16 @@ ${body}`
|
|
|
1261
1250
|
ctx.warnings.push(
|
|
1262
1251
|
"Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
|
|
1263
1252
|
);
|
|
1264
|
-
const scriptsSource =
|
|
1253
|
+
const scriptsSource = path8.join(ctx.stackDir, "scripts");
|
|
1265
1254
|
if (fs5.existsSync(scriptsSource)) {
|
|
1266
1255
|
for (const f of fs5.readdirSync(scriptsSource)) {
|
|
1267
|
-
actions.push({ kind: "copy", source:
|
|
1256
|
+
actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
|
|
1268
1257
|
}
|
|
1269
1258
|
}
|
|
1270
1259
|
return actions;
|
|
1271
1260
|
},
|
|
1272
1261
|
planMainConfig(canonical, ctx) {
|
|
1273
|
-
const file =
|
|
1262
|
+
const file = path8.join(ctx.configDir, "config.toml");
|
|
1274
1263
|
const original = readTextIfExists(file);
|
|
1275
1264
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1276
1265
|
let content = contentSource;
|
|
@@ -1406,7 +1395,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1406
1395
|
content = removeMarkdownSection(content, "browser");
|
|
1407
1396
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1408
1397
|
}
|
|
1409
|
-
const configFile =
|
|
1398
|
+
const configFile = path8.join(ctx.configDir, "config.toml");
|
|
1410
1399
|
const config = readTextIfExists(configFile);
|
|
1411
1400
|
if (config !== null) {
|
|
1412
1401
|
let content = config;
|
|
@@ -1441,7 +1430,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1441
1430
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
1442
1431
|
});
|
|
1443
1432
|
}
|
|
1444
|
-
const hooksFile =
|
|
1433
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
1445
1434
|
const hooksJson = readTextIfExists(hooksFile);
|
|
1446
1435
|
if (hooksJson !== null) {
|
|
1447
1436
|
const content = removeNativeHooks(hooksJson, hooks);
|
|
@@ -1455,7 +1444,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1455
1444
|
|
|
1456
1445
|
// src/lib/install-mode.ts
|
|
1457
1446
|
import fs6 from "fs";
|
|
1458
|
-
import
|
|
1447
|
+
import path9 from "path";
|
|
1459
1448
|
var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
|
|
1460
1449
|
var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
1461
1450
|
var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
@@ -1463,7 +1452,7 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
|
1463
1452
|
subagentConcurrency: "serial"
|
|
1464
1453
|
};
|
|
1465
1454
|
function installModePreferenceFile() {
|
|
1466
|
-
return
|
|
1455
|
+
return path9.join(dataDir(), "install-mode.json");
|
|
1467
1456
|
}
|
|
1468
1457
|
function hasInstallModePreference(file = installModePreferenceFile()) {
|
|
1469
1458
|
return fs6.existsSync(file);
|
|
@@ -1538,11 +1527,11 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
|
|
|
1538
1527
|
|
|
1539
1528
|
// src/lib/backup.ts
|
|
1540
1529
|
import fs7 from "fs";
|
|
1541
|
-
import
|
|
1530
|
+
import path10 from "path";
|
|
1542
1531
|
import crypto from "crypto";
|
|
1543
1532
|
var KEEP_BACKUPS = 10;
|
|
1544
1533
|
function backupsRoot() {
|
|
1545
|
-
return
|
|
1534
|
+
return path10.join(dataDir(), "backups");
|
|
1546
1535
|
}
|
|
1547
1536
|
function compositeChecksum(files) {
|
|
1548
1537
|
const hash = crypto.createHash("sha256");
|
|
@@ -1561,19 +1550,19 @@ function createBackup(files, label, root = backupsRoot()) {
|
|
|
1561
1550
|
if (latest?.checksum === checksum) return latest;
|
|
1562
1551
|
const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
|
|
1563
1552
|
let id = base;
|
|
1564
|
-
let dir =
|
|
1553
|
+
let dir = path10.join(root, id);
|
|
1565
1554
|
for (let n = 1; fs7.existsSync(dir); n++) {
|
|
1566
1555
|
id = `${base}-${n}`;
|
|
1567
|
-
dir =
|
|
1556
|
+
dir = path10.join(root, id);
|
|
1568
1557
|
}
|
|
1569
|
-
ensureDir(
|
|
1558
|
+
ensureDir(path10.join(dir, "files"));
|
|
1570
1559
|
const entries = existing.map((original, i) => {
|
|
1571
|
-
const stored =
|
|
1560
|
+
const stored = path10.join(dir, "files", `${String(i).padStart(4, "0")}-${path10.basename(original)}`);
|
|
1572
1561
|
fs7.copyFileSync(original, stored);
|
|
1573
1562
|
return { original, stored };
|
|
1574
1563
|
});
|
|
1575
1564
|
const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
|
|
1576
|
-
writeText(
|
|
1565
|
+
writeText(path10.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
|
|
1577
1566
|
pruneBackups(root);
|
|
1578
1567
|
return info;
|
|
1579
1568
|
}
|
|
@@ -1582,7 +1571,7 @@ function listBackups(root = backupsRoot()) {
|
|
|
1582
1571
|
const infos = [];
|
|
1583
1572
|
for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
1584
1573
|
if (!entry.isDirectory()) continue;
|
|
1585
|
-
const manifest = readTextIfExists(
|
|
1574
|
+
const manifest = readTextIfExists(path10.join(root, entry.name, "manifest.json"));
|
|
1586
1575
|
if (manifest === null) continue;
|
|
1587
1576
|
try {
|
|
1588
1577
|
infos.push(JSON.parse(manifest));
|
|
@@ -1599,7 +1588,7 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
|
1599
1588
|
for (const { original, stored } of info.files) {
|
|
1600
1589
|
if (!fs7.existsSync(stored)) continue;
|
|
1601
1590
|
if (!isContainedIn(original, boundary)) continue;
|
|
1602
|
-
ensureDir(
|
|
1591
|
+
ensureDir(path10.dirname(original));
|
|
1603
1592
|
fs7.copyFileSync(stored, original);
|
|
1604
1593
|
restored++;
|
|
1605
1594
|
}
|
|
@@ -1608,15 +1597,15 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
|
1608
1597
|
function pruneBackups(root) {
|
|
1609
1598
|
const all = listBackups(root);
|
|
1610
1599
|
for (const old of all.slice(KEEP_BACKUPS)) {
|
|
1611
|
-
fs7.rmSync(
|
|
1600
|
+
fs7.rmSync(path10.join(root, old.id), { recursive: true, force: true });
|
|
1612
1601
|
}
|
|
1613
1602
|
}
|
|
1614
1603
|
|
|
1615
1604
|
// src/lib/manifest.ts
|
|
1616
1605
|
import fs8 from "fs";
|
|
1617
|
-
import
|
|
1606
|
+
import path11 from "path";
|
|
1618
1607
|
function manifestFile() {
|
|
1619
|
-
return
|
|
1608
|
+
return path11.join(dataDir(), "manifest.json");
|
|
1620
1609
|
}
|
|
1621
1610
|
function readManifest(file = manifestFile()) {
|
|
1622
1611
|
const raw = readTextIfExists(file);
|
|
@@ -1640,24 +1629,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
|
|
|
1640
1629
|
writeText(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
1641
1630
|
}
|
|
1642
1631
|
function findOrphans(prevOwned, currentTargets, root = HOME) {
|
|
1643
|
-
return prevOwned.map((f) =>
|
|
1644
|
-
(f) => !currentTargets.has(f) &&
|
|
1632
|
+
return prevOwned.map((f) => path11.resolve(f)).filter(
|
|
1633
|
+
(f) => !currentTargets.has(f) && path11.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs8.existsSync(f)
|
|
1645
1634
|
);
|
|
1646
1635
|
}
|
|
1647
1636
|
|
|
1648
1637
|
// src/components/system-prompt.ts
|
|
1649
|
-
import
|
|
1638
|
+
import path13 from "path";
|
|
1650
1639
|
import fs10 from "fs";
|
|
1651
1640
|
|
|
1652
1641
|
// src/lib/mode-composition.ts
|
|
1653
1642
|
import fs9 from "fs";
|
|
1654
|
-
import
|
|
1643
|
+
import path12 from "path";
|
|
1655
1644
|
var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
|
|
1656
1645
|
var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
|
|
1657
1646
|
var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
|
|
1658
1647
|
var normalize = (value) => value.replace(/\r\n/g, "\n");
|
|
1659
1648
|
function loadProgrammaticAddendum(stackDir, fileName) {
|
|
1660
|
-
return normalize(fs9.readFileSync(
|
|
1649
|
+
return normalize(fs9.readFileSync(path12.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
|
|
1661
1650
|
}
|
|
1662
1651
|
function appendAddendum(base, addendum) {
|
|
1663
1652
|
const normalizedBase = normalize(base);
|
|
@@ -1700,15 +1689,15 @@ function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
|
|
|
1700
1689
|
var normalize2 = (s) => s.replace(/\r\n/g, "\n");
|
|
1701
1690
|
function planSystemPrompt(adapter, ctx) {
|
|
1702
1691
|
const target = adapter.paths(ctx.configDir).systemPromptFile;
|
|
1703
|
-
const agentsMd = normalize2(fs10.readFileSync(
|
|
1692
|
+
const agentsMd = normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
|
|
1704
1693
|
const protocol = stripLeadingHtmlComments(
|
|
1705
|
-
normalize2(fs10.readFileSync(
|
|
1694
|
+
normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
|
|
1706
1695
|
);
|
|
1707
1696
|
const composedAgentsMd = composeProgrammaticSystemPrompt(ctx.stackDir, agentsMd, ctx.mode);
|
|
1708
1697
|
const browser = [
|
|
1709
1698
|
ctx.playwrightCliEnabled ? "browser-playwright.md" : null,
|
|
1710
1699
|
ctx.enabledMcpServers?.has(DEVTOOLS_MCP_SERVER) ? "browser-chrome-devtools.md" : null
|
|
1711
|
-
].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(
|
|
1700
|
+
].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
|
|
1712
1701
|
let content = readTextIfExists(target);
|
|
1713
1702
|
content = upsertMarkdownSection(content, "system-prompt", composedAgentsMd);
|
|
1714
1703
|
if (adapter.injectEngramProtocol(ctx)) {
|
|
@@ -1721,7 +1710,7 @@ function planSystemPrompt(adapter, ctx) {
|
|
|
1721
1710
|
}
|
|
1722
1711
|
|
|
1723
1712
|
// src/components/agents.ts
|
|
1724
|
-
import
|
|
1713
|
+
import path14 from "path";
|
|
1725
1714
|
function planAgents(adapter, ctx) {
|
|
1726
1715
|
const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
|
|
1727
1716
|
const dirFor = {
|
|
@@ -1731,7 +1720,7 @@ function planAgents(adapter, ctx) {
|
|
|
1731
1720
|
profile: profilesDir
|
|
1732
1721
|
};
|
|
1733
1722
|
const scriptsBase = scriptsDir.replace(/\\/g, "/");
|
|
1734
|
-
return loadCanonicalAgents(
|
|
1723
|
+
return loadCanonicalAgents(path14.join(ctx.stackDir, "agents")).flatMap((agent) => {
|
|
1735
1724
|
const composedAgent = {
|
|
1736
1725
|
...agent,
|
|
1737
1726
|
body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
|
|
@@ -1743,42 +1732,42 @@ function planAgents(adapter, ctx) {
|
|
|
1743
1732
|
return [];
|
|
1744
1733
|
}
|
|
1745
1734
|
const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
|
|
1746
|
-
return [{ kind: "write", target:
|
|
1735
|
+
return [{ kind: "write", target: path14.join(dir, rendered.file), content }];
|
|
1747
1736
|
});
|
|
1748
1737
|
});
|
|
1749
1738
|
}
|
|
1750
1739
|
|
|
1751
1740
|
// src/components/skills.ts
|
|
1752
|
-
import
|
|
1741
|
+
import path15 from "path";
|
|
1753
1742
|
function planSkills(adapter, ctx) {
|
|
1754
1743
|
const { skillsDir } = adapter.paths(ctx.configDir);
|
|
1755
|
-
const source =
|
|
1744
|
+
const source = path15.join(ctx.stackDir, "skills");
|
|
1756
1745
|
return listFilesRecursive(source).map((file) => {
|
|
1757
|
-
const relative =
|
|
1758
|
-
return { kind: "copy", source: file, target:
|
|
1746
|
+
const relative = path15.relative(source, file);
|
|
1747
|
+
return { kind: "copy", source: file, target: path15.join(skillsDir, relative) };
|
|
1759
1748
|
});
|
|
1760
1749
|
}
|
|
1761
1750
|
|
|
1762
1751
|
// src/components/commands.ts
|
|
1763
|
-
import
|
|
1752
|
+
import path16 from "path";
|
|
1764
1753
|
import fs11 from "fs";
|
|
1765
1754
|
function planCommands(adapter, ctx) {
|
|
1766
1755
|
const { commandsDir } = adapter.paths(ctx.configDir);
|
|
1767
|
-
const source =
|
|
1756
|
+
const source = path16.join(ctx.stackDir, "commands");
|
|
1768
1757
|
if (!fs11.existsSync(source)) return [];
|
|
1769
1758
|
const commandFiles = [
|
|
1770
1759
|
...listMarkdownFiles(source),
|
|
1771
|
-
...listMarkdownFiles(
|
|
1760
|
+
...listMarkdownFiles(path16.join(source, adapter.id))
|
|
1772
1761
|
];
|
|
1773
1762
|
return commandFiles.map(({ file, fullPath }) => {
|
|
1774
1763
|
const raw = fs11.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
|
|
1775
1764
|
const rendered = adapter.renderCommand(file, raw);
|
|
1776
|
-
return { kind: "write", target:
|
|
1765
|
+
return { kind: "write", target: path16.join(commandsDir, rendered.file), content: rendered.content };
|
|
1777
1766
|
});
|
|
1778
1767
|
}
|
|
1779
1768
|
function listMarkdownFiles(dir) {
|
|
1780
1769
|
if (!fs11.existsSync(dir)) return [];
|
|
1781
|
-
return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath:
|
|
1770
|
+
return fs11.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => ({ file: entry.name, fullPath: path16.join(dir, entry.name) }));
|
|
1782
1771
|
}
|
|
1783
1772
|
|
|
1784
1773
|
// src/components/hooks.ts
|
|
@@ -1792,15 +1781,15 @@ function planMcp(adapter, ctx) {
|
|
|
1792
1781
|
}
|
|
1793
1782
|
|
|
1794
1783
|
// src/components/plugins.ts
|
|
1795
|
-
import
|
|
1784
|
+
import path17 from "path";
|
|
1796
1785
|
import fs12 from "fs";
|
|
1797
1786
|
function planPlugins(adapter, ctx) {
|
|
1798
1787
|
const { pluginsDir } = adapter.paths(ctx.configDir);
|
|
1799
1788
|
if (pluginsDir === null) return [];
|
|
1800
|
-
const source =
|
|
1789
|
+
const source = path17.join(ctx.stackDir, "plugins", adapter.id);
|
|
1801
1790
|
if (!fs12.existsSync(source)) return [];
|
|
1802
1791
|
return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
|
|
1803
|
-
const target =
|
|
1792
|
+
const target = path17.join(pluginsDir, path17.relative(source, sourceFile));
|
|
1804
1793
|
const raw = fs12.readFileSync(sourceFile, "utf8");
|
|
1805
1794
|
let content = raw;
|
|
1806
1795
|
if (content.includes('"{{ENGRAM_BIN}}"')) {
|
|
@@ -1808,7 +1797,7 @@ function planPlugins(adapter, ctx) {
|
|
|
1808
1797
|
}
|
|
1809
1798
|
if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
|
|
1810
1799
|
const protocol = stripLeadingHtmlComments(
|
|
1811
|
-
fs12.readFileSync(
|
|
1800
|
+
fs12.readFileSync(path17.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
|
|
1812
1801
|
);
|
|
1813
1802
|
content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
|
|
1814
1803
|
}
|
|
@@ -1820,8 +1809,8 @@ function planPlugins(adapter, ctx) {
|
|
|
1820
1809
|
|
|
1821
1810
|
// src/lib/external-tools.ts
|
|
1822
1811
|
import fs13 from "fs";
|
|
1823
|
-
import
|
|
1824
|
-
import
|
|
1812
|
+
import os from "os";
|
|
1813
|
+
import path18 from "path";
|
|
1825
1814
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1826
1815
|
var PLAYWRIGHT_CLI = {
|
|
1827
1816
|
packageName: "@playwright/cli",
|
|
@@ -1872,9 +1861,9 @@ function resolvePnpmBin() {
|
|
|
1872
1861
|
const pnpmCmd = lookPath("pnpm.cmd");
|
|
1873
1862
|
return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
|
|
1874
1863
|
}
|
|
1875
|
-
function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir =
|
|
1864
|
+
function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
|
|
1876
1865
|
const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
1877
|
-
const cacheDir = configuredPath ?? (platform === "win32" ?
|
|
1866
|
+
const cacheDir = configuredPath ?? (platform === "win32" ? path18.join(env.LOCALAPPDATA ?? path18.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path18.join(homeDir, "Library", "Caches", "ms-playwright") : path18.join(env.XDG_CACHE_HOME ?? path18.join(homeDir, ".cache"), "ms-playwright"));
|
|
1878
1867
|
if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
|
|
1879
1868
|
try {
|
|
1880
1869
|
const ready = fs13.readdirSync(cacheDir, { withFileTypes: true }).some(
|
|
@@ -1922,7 +1911,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin()) {
|
|
|
1922
1911
|
|
|
1923
1912
|
// src/lib/tool-preferences.ts
|
|
1924
1913
|
import fs14 from "fs";
|
|
1925
|
-
import
|
|
1914
|
+
import path19 from "path";
|
|
1926
1915
|
var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
|
|
1927
1916
|
var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
|
|
1928
1917
|
var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
|
|
@@ -1935,7 +1924,7 @@ function readPreference(file) {
|
|
|
1935
1924
|
}
|
|
1936
1925
|
}
|
|
1937
1926
|
function playwrightCliPreferenceFile(stateDir = dataDir()) {
|
|
1938
|
-
return
|
|
1927
|
+
return path19.join(stateDir, "playwright-cli.json");
|
|
1939
1928
|
}
|
|
1940
1929
|
function parsePlaywrightCliPreference(raw) {
|
|
1941
1930
|
try {
|
|
@@ -1965,10 +1954,10 @@ function savePlaywrightCliPreference(file, enabled) {
|
|
|
1965
1954
|
writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
|
|
1966
1955
|
}
|
|
1967
1956
|
function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
|
|
1968
|
-
return
|
|
1957
|
+
return path19.join(stateDir, "devtools-mcp.json");
|
|
1969
1958
|
}
|
|
1970
1959
|
function primaryModelOwnershipFile(stateDir = dataDir()) {
|
|
1971
|
-
return
|
|
1960
|
+
return path19.join(stateDir, "primary-model.json");
|
|
1972
1961
|
}
|
|
1973
1962
|
function isRecord(value) {
|
|
1974
1963
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2078,7 +2067,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
|
|
|
2078
2067
|
return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
|
|
2079
2068
|
}
|
|
2080
2069
|
function primaryModelConfigKey(configDir) {
|
|
2081
|
-
const resolved =
|
|
2070
|
+
const resolved = path19.resolve(configDir);
|
|
2082
2071
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
2083
2072
|
}
|
|
2084
2073
|
function loadPrimaryModelOwnership(file, runtime, configDir) {
|
|
@@ -2224,7 +2213,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
|
|
|
2224
2213
|
continue;
|
|
2225
2214
|
}
|
|
2226
2215
|
try {
|
|
2227
|
-
for (const action of buildPlan(adapter, ctx)) targets.add(
|
|
2216
|
+
for (const action of buildPlan(adapter, ctx)) targets.add(path20.resolve(action.target));
|
|
2228
2217
|
} catch (error) {
|
|
2229
2218
|
complete = false;
|
|
2230
2219
|
warnings.push(
|
|
@@ -2326,10 +2315,10 @@ async function runInstall(opts) {
|
|
|
2326
2315
|
}
|
|
2327
2316
|
const writeManifest = () => {
|
|
2328
2317
|
if (!useManifest) return;
|
|
2329
|
-
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) =>
|
|
2318
|
+
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path20.resolve(a.target)));
|
|
2330
2319
|
const keepTarget = (target) => !unmergeTargets.has(target);
|
|
2331
|
-
const liveOwned = plan.map((a) =>
|
|
2332
|
-
const previousOwned = (prevManifest?.owned ?? []).map((target) =>
|
|
2320
|
+
const liveOwned = plan.map((a) => path20.resolve(a.target)).filter(keepTarget);
|
|
2321
|
+
const previousOwned = (prevManifest?.owned ?? []).map((target) => path20.resolve(target)).filter(keepTarget);
|
|
2333
2322
|
const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
|
|
2334
2323
|
writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2335
2324
|
};
|
|
@@ -2358,7 +2347,7 @@ async function runInstall(opts) {
|
|
|
2358
2347
|
const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
|
|
2359
2348
|
if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
|
|
2360
2349
|
applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
|
|
2361
|
-
const pruneRoot = useManifest ? HOME :
|
|
2350
|
+
const pruneRoot = useManifest ? HOME : path20.dirname(configDir);
|
|
2362
2351
|
for (const orphan of orphans) {
|
|
2363
2352
|
fs15.rmSync(orphan, { force: true });
|
|
2364
2353
|
pruneEmptyDirs(orphan, pruneRoot);
|
|
@@ -2443,27 +2432,27 @@ async function runInstall(opts) {
|
|
|
2443
2432
|
|
|
2444
2433
|
// src/uninstall.ts
|
|
2445
2434
|
import fs17 from "fs";
|
|
2446
|
-
import
|
|
2435
|
+
import path24 from "path";
|
|
2447
2436
|
import * as p2 from "@clack/prompts";
|
|
2448
2437
|
|
|
2449
2438
|
// src/lib/pi-projection-lifecycle.ts
|
|
2450
2439
|
import fs16 from "fs";
|
|
2451
|
-
import
|
|
2440
|
+
import path23 from "path";
|
|
2452
2441
|
|
|
2453
2442
|
// src/adapters/pi.ts
|
|
2454
|
-
import
|
|
2443
|
+
import path21 from "path";
|
|
2455
2444
|
var piAdapter = {
|
|
2456
2445
|
id: "pi",
|
|
2457
2446
|
paths(configDir) {
|
|
2458
|
-
const piConfigDir = process.env.PI_CODING_AGENT_DIR ??
|
|
2459
|
-
const agentsHome = samePath(configDir, piConfigDir) ? HOME :
|
|
2447
|
+
const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path21.join(HOME, ".pi", "agent");
|
|
2448
|
+
const agentsHome = samePath(configDir, piConfigDir) ? HOME : path21.join(path21.dirname(configDir), "home");
|
|
2460
2449
|
return {
|
|
2461
|
-
systemPromptFile:
|
|
2462
|
-
agentsDir:
|
|
2463
|
-
skillsDir:
|
|
2464
|
-
commandsDir:
|
|
2450
|
+
systemPromptFile: path21.join(configDir, "AGENTS.md"),
|
|
2451
|
+
agentsDir: path21.join(configDir, "agents"),
|
|
2452
|
+
skillsDir: path21.join(agentsHome, ".agents", "skills"),
|
|
2453
|
+
commandsDir: path21.join(configDir, "prompts"),
|
|
2465
2454
|
pluginsDir: null,
|
|
2466
|
-
scriptsDir:
|
|
2455
|
+
scriptsDir: path21.join(configDir, "scripts"),
|
|
2467
2456
|
outputStylesDir: null,
|
|
2468
2457
|
profilesDir: null
|
|
2469
2458
|
};
|
|
@@ -2477,7 +2466,7 @@ var piAdapter = {
|
|
|
2477
2466
|
};
|
|
2478
2467
|
|
|
2479
2468
|
// src/lib/pi-package-lifecycle.ts
|
|
2480
|
-
import
|
|
2469
|
+
import path22 from "path";
|
|
2481
2470
|
var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
2482
2471
|
"foundation-contract-v1",
|
|
2483
2472
|
"runner-json-v1",
|
|
@@ -2609,7 +2598,7 @@ function parseReceiptShape(receiptJson) {
|
|
|
2609
2598
|
return null;
|
|
2610
2599
|
}
|
|
2611
2600
|
if (engram === void 0) return "upgrade-required";
|
|
2612
|
-
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !
|
|
2601
|
+
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path22.isAbsolute(Reflect.get(engram, "binary"))) {
|
|
2613
2602
|
return null;
|
|
2614
2603
|
}
|
|
2615
2604
|
return parsed;
|
|
@@ -2646,7 +2635,7 @@ function planPiPackageLifecycle(input) {
|
|
|
2646
2635
|
if (input.receiptJson !== null) {
|
|
2647
2636
|
const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
|
|
2648
2637
|
kind: input.scope.kind,
|
|
2649
|
-
codingAgentDir:
|
|
2638
|
+
codingAgentDir: path22.resolve(input.scope.codingAgentDir)
|
|
2650
2639
|
}, input.engramBin);
|
|
2651
2640
|
if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
|
|
2652
2641
|
if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
|
|
@@ -2681,7 +2670,7 @@ function planPiPackageLifecycle(input) {
|
|
|
2681
2670
|
},
|
|
2682
2671
|
receipt: expectedReceipt(input.candidate, "installing", {
|
|
2683
2672
|
kind: input.scope.kind,
|
|
2684
|
-
codingAgentDir:
|
|
2673
|
+
codingAgentDir: path22.resolve(input.scope.codingAgentDir)
|
|
2685
2674
|
}, input.engramBin),
|
|
2686
2675
|
ownership: ownership(true)
|
|
2687
2676
|
};
|
|
@@ -2696,7 +2685,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
|
|
|
2696
2685
|
const parsed = JSON.parse(body);
|
|
2697
2686
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2698
2687
|
const record = parsed;
|
|
2699
|
-
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" || !
|
|
2688
|
+
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" || !path22.isAbsolute(record.package.root) || path22.resolve(packageRunner) !== path22.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
|
|
2700
2689
|
return null;
|
|
2701
2690
|
}
|
|
2702
2691
|
return record;
|
|
@@ -2782,10 +2771,10 @@ function validateOwnedOperationState(input) {
|
|
|
2782
2771
|
}))) {
|
|
2783
2772
|
return { kind: "blocked", reason: "receipt-untrusted" };
|
|
2784
2773
|
}
|
|
2785
|
-
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") ||
|
|
2774
|
+
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path22.resolve(receipt.scope.codingAgentDir) !== path22.resolve(input.paths.codingAgentDir)) {
|
|
2786
2775
|
return { kind: "blocked", reason: "source-divergent" };
|
|
2787
2776
|
}
|
|
2788
|
-
if (input.engramBin !== null &&
|
|
2777
|
+
if (input.engramBin !== null && path22.resolve(receipt.engram.binary) !== path22.resolve(input.engramBin)) {
|
|
2789
2778
|
return { kind: "blocked", reason: "receipt-corrupt" };
|
|
2790
2779
|
}
|
|
2791
2780
|
const source = receipt.candidate.package.source;
|
|
@@ -2882,18 +2871,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
|
|
|
2882
2871
|
function projectionScope(scope) {
|
|
2883
2872
|
return {
|
|
2884
2873
|
...scope,
|
|
2885
|
-
settingsFile:
|
|
2874
|
+
settingsFile: path23.join(scope.codingAgentDir, "settings.json")
|
|
2886
2875
|
};
|
|
2887
2876
|
}
|
|
2888
2877
|
function isInside(root, file) {
|
|
2889
|
-
const relative =
|
|
2890
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
2878
|
+
const relative = path23.relative(path23.resolve(root), path23.resolve(file));
|
|
2879
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path23.sep}`) && !path23.isAbsolute(relative);
|
|
2891
2880
|
}
|
|
2892
2881
|
function isManagedPath(scope, file) {
|
|
2893
2882
|
return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
|
|
2894
2883
|
}
|
|
2895
2884
|
function isAllowedPath(scope, file) {
|
|
2896
|
-
return isManagedPath(scope, file) ||
|
|
2885
|
+
return isManagedPath(scope, file) || path23.resolve(file) === path23.resolve(scope.receiptFile);
|
|
2897
2886
|
}
|
|
2898
2887
|
function projectedPiAdapter(scope) {
|
|
2899
2888
|
const paths = piAdapter.paths(scope.codingAgentDir);
|
|
@@ -2901,7 +2890,7 @@ function projectedPiAdapter(scope) {
|
|
|
2901
2890
|
...piAdapter,
|
|
2902
2891
|
paths: () => ({
|
|
2903
2892
|
...paths,
|
|
2904
|
-
skillsDir:
|
|
2893
|
+
skillsDir: path23.join(scope.home, ".agents", "skills")
|
|
2905
2894
|
})
|
|
2906
2895
|
};
|
|
2907
2896
|
}
|
|
@@ -2941,9 +2930,9 @@ function assertPlanContained(plan, scope) {
|
|
|
2941
2930
|
}
|
|
2942
2931
|
}
|
|
2943
2932
|
function receiptFor(plan, scope) {
|
|
2944
|
-
const systemPrompt =
|
|
2933
|
+
const systemPrompt = path23.join(scope.codingAgentDir, "AGENTS.md");
|
|
2945
2934
|
const owned = [...new Set(
|
|
2946
|
-
plan.map((action) =>
|
|
2935
|
+
plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
|
|
2947
2936
|
)];
|
|
2948
2937
|
return {
|
|
2949
2938
|
schemaVersion: 1,
|
|
@@ -2995,8 +2984,8 @@ function realPiProjectionScope() {
|
|
|
2995
2984
|
return {
|
|
2996
2985
|
kind: "real",
|
|
2997
2986
|
home: HOME,
|
|
2998
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
2999
|
-
receiptFile:
|
|
2987
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path23.join(HOME, ".pi", "agent"),
|
|
2988
|
+
receiptFile: path23.join(dataDir(), "pi-projection-receipt.json")
|
|
3000
2989
|
};
|
|
3001
2990
|
}
|
|
3002
2991
|
function readTextOnlyIfMissing(file) {
|
|
@@ -3034,13 +3023,13 @@ function manifestOwned(manifest) {
|
|
|
3034
3023
|
return new Set([
|
|
3035
3024
|
...manifest.runtimes.codex?.owned ?? [],
|
|
3036
3025
|
...manifest.runtimes.opencode?.owned ?? []
|
|
3037
|
-
].map((file) =>
|
|
3026
|
+
].map((file) => path23.resolve(file)));
|
|
3038
3027
|
}
|
|
3039
3028
|
function withoutManagedPromptSections(content) {
|
|
3040
3029
|
return ["system-prompt", "engram-protocol", "browser"].reduce((current, section) => removeMarkdownSection(current, section), content);
|
|
3041
3030
|
}
|
|
3042
3031
|
function uniquePaths(paths) {
|
|
3043
|
-
return [...new Set(paths.map((file) =>
|
|
3032
|
+
return [...new Set(paths.map((file) => path23.resolve(file)))];
|
|
3044
3033
|
}
|
|
3045
3034
|
function blocked2(reason, paths, remedy) {
|
|
3046
3035
|
return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
|
|
@@ -3113,7 +3102,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3113
3102
|
}
|
|
3114
3103
|
const scope = projectionScope(input.scope);
|
|
3115
3104
|
const expectedReceipt2 = expectedProjectionReceipt(input, scope);
|
|
3116
|
-
const prompt =
|
|
3105
|
+
const prompt = path23.join(scope.codingAgentDir, "AGENTS.md");
|
|
3117
3106
|
let existingPrompt;
|
|
3118
3107
|
try {
|
|
3119
3108
|
existingPrompt = deps.readText(prompt);
|
|
@@ -3121,7 +3110,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3121
3110
|
return cleanupFailure([prompt]);
|
|
3122
3111
|
}
|
|
3123
3112
|
const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
|
|
3124
|
-
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file:
|
|
3113
|
+
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path23.resolve(prompt), content: promptContent };
|
|
3125
3114
|
const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
|
|
3126
3115
|
if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
|
|
3127
3116
|
let ownedToRemove = [];
|
|
@@ -3137,7 +3126,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3137
3126
|
return cleanupFailure([scope.receiptFile]);
|
|
3138
3127
|
}
|
|
3139
3128
|
ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
|
|
3140
|
-
receiptFile =
|
|
3129
|
+
receiptFile = path23.resolve(scope.receiptFile);
|
|
3141
3130
|
backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
|
|
3142
3131
|
}
|
|
3143
3132
|
const backup = backupExisting(backupTargets, deps);
|
|
@@ -3193,7 +3182,7 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3193
3182
|
const expectedReceipt2 = receiptContent(receipt);
|
|
3194
3183
|
const drifted = plan.filter((action) => hasActionDrift(action, deps));
|
|
3195
3184
|
if (input.operation === "doctor") {
|
|
3196
|
-
const paths = drifted.map((action) =>
|
|
3185
|
+
const paths = drifted.map((action) => path23.resolve(action.target));
|
|
3197
3186
|
const currentSettings2 = deps.readText(scope.settingsFile);
|
|
3198
3187
|
if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
|
|
3199
3188
|
paths.push(scope.settingsFile);
|
|
@@ -3224,12 +3213,12 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3224
3213
|
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged };
|
|
3225
3214
|
}
|
|
3226
3215
|
function systemProjectionLifecycle(input) {
|
|
3227
|
-
const targetRoot = input.targetDir === void 0 ? null :
|
|
3216
|
+
const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
|
|
3228
3217
|
const scope = targetRoot === null ? realPiProjectionScope() : {
|
|
3229
3218
|
kind: "target-dir",
|
|
3230
|
-
home:
|
|
3231
|
-
codingAgentDir:
|
|
3232
|
-
receiptFile:
|
|
3219
|
+
home: path23.join(targetRoot, "home"),
|
|
3220
|
+
codingAgentDir: path23.join(targetRoot, "pi-agent"),
|
|
3221
|
+
receiptFile: path23.join(targetRoot, "state", "pi-projection-receipt.json")
|
|
3233
3222
|
};
|
|
3234
3223
|
return {
|
|
3235
3224
|
input: {
|
|
@@ -3245,7 +3234,7 @@ function systemProjectionLifecycle(input) {
|
|
|
3245
3234
|
backup: (paths) => createBackup(
|
|
3246
3235
|
paths,
|
|
3247
3236
|
`pi-projection-${input.operation}`,
|
|
3248
|
-
targetRoot === null ? void 0 :
|
|
3237
|
+
targetRoot === null ? void 0 : path23.join(targetRoot, "backups")
|
|
3249
3238
|
),
|
|
3250
3239
|
writeText,
|
|
3251
3240
|
copyFile,
|
|
@@ -3326,7 +3315,7 @@ async function runUninstall(opts) {
|
|
|
3326
3315
|
if (!detection.installed) continue;
|
|
3327
3316
|
const keepCtx = makeContext(keep, detection.configDir);
|
|
3328
3317
|
if (!keepCtx) continue;
|
|
3329
|
-
for (const action of buildPlan(keep, keepCtx)) retained.add(
|
|
3318
|
+
for (const action of buildPlan(keep, keepCtx)) retained.add(path24.resolve(action.target));
|
|
3330
3319
|
}
|
|
3331
3320
|
}
|
|
3332
3321
|
for (const id of opts.runtimes) {
|
|
@@ -3345,15 +3334,15 @@ async function runUninstall(opts) {
|
|
|
3345
3334
|
if (!ctx) continue;
|
|
3346
3335
|
ctx.preserveEngram = !removeEngram;
|
|
3347
3336
|
const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
|
|
3348
|
-
const mergedTargets = new Set(unmerge.map((a) =>
|
|
3337
|
+
const mergedTargets = new Set(unmerge.map((a) => path24.resolve(a.target)));
|
|
3349
3338
|
const usingRealConfig = opts.targetDir === void 0;
|
|
3350
3339
|
const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
|
|
3351
|
-
const pruneRoot = usingRealConfig ? HOME :
|
|
3340
|
+
const pruneRoot = usingRealConfig ? HOME : path24.dirname(configDir);
|
|
3352
3341
|
const planTargets = [
|
|
3353
|
-
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) =>
|
|
3342
|
+
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path24.resolve(a.target)), ...prevOwned.map((t) => path24.resolve(t))])
|
|
3354
3343
|
].filter((t) => !mergedTargets.has(t) && fs17.existsSync(t));
|
|
3355
3344
|
const deleteTargets = planTargets.filter(
|
|
3356
|
-
(t) => !retained.has(t) && !(ctx.preserveEngram &&
|
|
3345
|
+
(t) => !retained.has(t) && !(ctx.preserveEngram && path24.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
|
|
3357
3346
|
);
|
|
3358
3347
|
const sharedKept = planTargets.length - deleteTargets.length;
|
|
3359
3348
|
p2.log.step(`${adapter.name} \u2192 ${configDir}`);
|
|
@@ -3433,7 +3422,7 @@ async function runUninstall(opts) {
|
|
|
3433
3422
|
}
|
|
3434
3423
|
|
|
3435
3424
|
// src/doctor.ts
|
|
3436
|
-
import
|
|
3425
|
+
import path25 from "path";
|
|
3437
3426
|
import fs18 from "fs";
|
|
3438
3427
|
import * as p3 from "@clack/prompts";
|
|
3439
3428
|
function engramVersion(bin) {
|
|
@@ -3453,7 +3442,7 @@ function resolvePlaywrightDoctorState(input) {
|
|
|
3453
3442
|
return { status: "healthy" };
|
|
3454
3443
|
}
|
|
3455
3444
|
function context7KeyConfigured(id, configDir) {
|
|
3456
|
-
const file = id === "codex" ?
|
|
3445
|
+
const file = id === "codex" ? path25.join(configDir, "config.toml") : id === "claude-code" ? path25.join(path25.dirname(configDir), `${path25.basename(configDir)}.json`) : path25.join(configDir, "opencode.json");
|
|
3457
3446
|
const content = readTextIfExists(file);
|
|
3458
3447
|
if (content === null) return null;
|
|
3459
3448
|
const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
|
|
@@ -3476,8 +3465,8 @@ async function runDoctor() {
|
|
|
3476
3465
|
p3.log.success(`Engram: ${version} (${engramBin})`);
|
|
3477
3466
|
}
|
|
3478
3467
|
}
|
|
3479
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
3480
|
-
const engramDb =
|
|
3468
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path25.join(HOME, ".engram");
|
|
3469
|
+
const engramDb = path25.join(engramDataDir, "engram.db");
|
|
3481
3470
|
if (fs18.existsSync(engramDb)) {
|
|
3482
3471
|
const sizeMb = (fs18.statSync(engramDb).size / 1024 / 1024).toFixed(1);
|
|
3483
3472
|
p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
|
|
@@ -3557,10 +3546,10 @@ async function runDoctor() {
|
|
|
3557
3546
|
p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
|
|
3558
3547
|
problems++;
|
|
3559
3548
|
}
|
|
3560
|
-
if (adapter.id === "codex" && fs18.existsSync(
|
|
3549
|
+
if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "hooks.json"))) {
|
|
3561
3550
|
p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
|
|
3562
3551
|
}
|
|
3563
|
-
if (adapter.id === "codex" && fs18.existsSync(
|
|
3552
|
+
if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "AGENTS.override.md"))) {
|
|
3564
3553
|
p3.log.warn(
|
|
3565
3554
|
"Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
|
|
3566
3555
|
);
|
|
@@ -3575,16 +3564,16 @@ async function runDoctor() {
|
|
|
3575
3564
|
|
|
3576
3565
|
// src/update.ts
|
|
3577
3566
|
import fs21 from "fs";
|
|
3578
|
-
import
|
|
3579
|
-
import
|
|
3567
|
+
import path28 from "path";
|
|
3568
|
+
import os3 from "os";
|
|
3580
3569
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3581
3570
|
import * as p4 from "@clack/prompts";
|
|
3582
3571
|
|
|
3583
3572
|
// src/lib/github.ts
|
|
3584
3573
|
import fs19 from "fs";
|
|
3585
|
-
import
|
|
3574
|
+
import path26 from "path";
|
|
3586
3575
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3587
|
-
import
|
|
3576
|
+
import os2 from "os";
|
|
3588
3577
|
import { Readable } from "stream";
|
|
3589
3578
|
import { pipeline } from "stream/promises";
|
|
3590
3579
|
var cachedToken;
|
|
@@ -3652,7 +3641,7 @@ async function latestGithubCommit(repo) {
|
|
|
3652
3641
|
}
|
|
3653
3642
|
}
|
|
3654
3643
|
function validateExtractedTree(destDir) {
|
|
3655
|
-
const resolved =
|
|
3644
|
+
const resolved = path26.resolve(destDir);
|
|
3656
3645
|
const walk = (dir) => {
|
|
3657
3646
|
let entries;
|
|
3658
3647
|
try {
|
|
@@ -3661,7 +3650,7 @@ function validateExtractedTree(destDir) {
|
|
|
3661
3650
|
return false;
|
|
3662
3651
|
}
|
|
3663
3652
|
for (const entry of entries) {
|
|
3664
|
-
const full =
|
|
3653
|
+
const full = path26.join(dir, entry.name);
|
|
3665
3654
|
let stat;
|
|
3666
3655
|
try {
|
|
3667
3656
|
stat = fs19.lstatSync(full);
|
|
@@ -3680,12 +3669,12 @@ function validateExtractedTree(destDir) {
|
|
|
3680
3669
|
}
|
|
3681
3670
|
function resolveTarBin() {
|
|
3682
3671
|
if (process.platform !== "win32") return "tar";
|
|
3683
|
-
const winTar =
|
|
3672
|
+
const winTar = path26.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
|
|
3684
3673
|
return fs19.existsSync(winTar) ? winTar : "tar";
|
|
3685
3674
|
}
|
|
3686
3675
|
async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
3687
3676
|
const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
|
|
3688
|
-
const tmp =
|
|
3677
|
+
const tmp = path26.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
|
|
3689
3678
|
const fail = (reason) => {
|
|
3690
3679
|
try {
|
|
3691
3680
|
fs19.rmSync(destDir, { recursive: true, force: true });
|
|
@@ -3718,8 +3707,8 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
3718
3707
|
const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
|
|
3719
3708
|
return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
|
|
3720
3709
|
}
|
|
3721
|
-
const resolvedDest =
|
|
3722
|
-
const validateRoot = validateSubdir ?
|
|
3710
|
+
const resolvedDest = path26.resolve(destDir);
|
|
3711
|
+
const validateRoot = validateSubdir ? path26.resolve(resolvedDest, validateSubdir) : resolvedDest;
|
|
3723
3712
|
if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
|
|
3724
3713
|
return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
|
|
3725
3714
|
}
|
|
@@ -3741,7 +3730,7 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
3741
3730
|
|
|
3742
3731
|
// src/lib/skill-update.ts
|
|
3743
3732
|
import fs20 from "fs";
|
|
3744
|
-
import
|
|
3733
|
+
import path27 from "path";
|
|
3745
3734
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3746
3735
|
var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
|
|
3747
3736
|
"agent-delegation",
|
|
@@ -3760,10 +3749,10 @@ function sameTextContentNormalized(a, b) {
|
|
|
3760
3749
|
}
|
|
3761
3750
|
function diffSkillDirs(upstreamDir, localDir) {
|
|
3762
3751
|
const upstreamFiles = new Set(
|
|
3763
|
-
listFilesRecursive(upstreamDir).map((f) =>
|
|
3752
|
+
listFilesRecursive(upstreamDir).map((f) => path27.relative(upstreamDir, f))
|
|
3764
3753
|
);
|
|
3765
3754
|
const localFiles = new Set(
|
|
3766
|
-
listFilesRecursive(localDir).map((f) =>
|
|
3755
|
+
listFilesRecursive(localDir).map((f) => path27.relative(localDir, f))
|
|
3767
3756
|
);
|
|
3768
3757
|
const added = [];
|
|
3769
3758
|
const modified = [];
|
|
@@ -3771,7 +3760,7 @@ function diffSkillDirs(upstreamDir, localDir) {
|
|
|
3771
3760
|
for (const rel of upstreamFiles) {
|
|
3772
3761
|
if (!localFiles.has(rel)) {
|
|
3773
3762
|
added.push(rel);
|
|
3774
|
-
} else if (!sameTextContentNormalized(
|
|
3763
|
+
} else if (!sameTextContentNormalized(path27.join(upstreamDir, rel), path27.join(localDir, rel))) {
|
|
3775
3764
|
modified.push(rel);
|
|
3776
3765
|
}
|
|
3777
3766
|
}
|
|
@@ -3833,7 +3822,7 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3833
3822
|
if (PROTECTED_SKILLS.has(name)) {
|
|
3834
3823
|
throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
|
|
3835
3824
|
}
|
|
3836
|
-
const upstreamsFile = upstreamsFilePath ??
|
|
3825
|
+
const upstreamsFile = upstreamsFilePath ?? path27.join(path27.dirname(stackRoot()), "upstreams.json");
|
|
3837
3826
|
const raw = fs20.readFileSync(upstreamsFile, "utf8");
|
|
3838
3827
|
const data = JSON.parse(raw);
|
|
3839
3828
|
const skillEntry = data?.skills?.[name];
|
|
@@ -3843,8 +3832,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3843
3832
|
if (skillEntry.kind === "release") {
|
|
3844
3833
|
throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
|
|
3845
3834
|
}
|
|
3846
|
-
const skillsRoot = localSkillsRoot ??
|
|
3847
|
-
const localSkillDir =
|
|
3835
|
+
const skillsRoot = localSkillsRoot ?? path27.join(stackRoot(), "skills");
|
|
3836
|
+
const localSkillDir = path27.join(skillsRoot, name);
|
|
3848
3837
|
const localFiles = listFilesRecursive(localSkillDir);
|
|
3849
3838
|
if (localFiles.length > 0) {
|
|
3850
3839
|
createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
|
|
@@ -3857,9 +3846,9 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3857
3846
|
if (st.isSymbolicLink()) {
|
|
3858
3847
|
throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
|
|
3859
3848
|
}
|
|
3860
|
-
const rel =
|
|
3861
|
-
const dest =
|
|
3862
|
-
ensureDir(
|
|
3849
|
+
const rel = path27.relative(upstreamSkillDir, src);
|
|
3850
|
+
const dest = path27.join(stagingDir, rel);
|
|
3851
|
+
ensureDir(path27.dirname(dest));
|
|
3863
3852
|
copyFile(src, dest);
|
|
3864
3853
|
}
|
|
3865
3854
|
const oldDir = `${localSkillDir}.old-${process.pid}`;
|
|
@@ -3884,7 +3873,7 @@ function rateLimitHint(prefix) {
|
|
|
3884
3873
|
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.`;
|
|
3885
3874
|
}
|
|
3886
3875
|
function loadUpstreams() {
|
|
3887
|
-
const file =
|
|
3876
|
+
const file = path28.join(path28.dirname(stackRoot()), "upstreams.json");
|
|
3888
3877
|
return JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
3889
3878
|
}
|
|
3890
3879
|
function skillsToScan(maintainer, upstreams) {
|
|
@@ -4034,8 +4023,8 @@ function isEngramRunning() {
|
|
|
4034
4023
|
return null;
|
|
4035
4024
|
}
|
|
4036
4025
|
}
|
|
4037
|
-
function isGitClone(projectRoot =
|
|
4038
|
-
return fs21.existsSync(
|
|
4026
|
+
function isGitClone(projectRoot = path28.dirname(stackRoot())) {
|
|
4027
|
+
return fs21.existsSync(path28.join(projectRoot, ".git"));
|
|
4039
4028
|
}
|
|
4040
4029
|
var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
|
|
4041
4030
|
function resolvePnpm() {
|
|
@@ -4050,7 +4039,7 @@ function cleanupTmp(dir) {
|
|
|
4050
4039
|
}
|
|
4051
4040
|
}
|
|
4052
4041
|
function updateStackGitClone() {
|
|
4053
|
-
const projectRoot =
|
|
4042
|
+
const projectRoot = path28.dirname(stackRoot());
|
|
4054
4043
|
const git = lookPath("git");
|
|
4055
4044
|
if (!git) throw new Error("git no encontrado en PATH.");
|
|
4056
4045
|
const pnpm = resolvePnpm();
|
|
@@ -4067,7 +4056,7 @@ function updateStackGlobal() {
|
|
|
4067
4056
|
execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
|
|
4068
4057
|
}
|
|
4069
4058
|
async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
4070
|
-
const root = fs21.mkdtempSync(
|
|
4059
|
+
const root = fs21.mkdtempSync(path28.join(os3.tmpdir(), "jorgex-skill-"));
|
|
4071
4060
|
try {
|
|
4072
4061
|
const result = await downloadRepoTarball(repo, sha, root, skillPath);
|
|
4073
4062
|
if (!result.ok) {
|
|
@@ -4075,14 +4064,14 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
|
4075
4064
|
return { error: result.reason };
|
|
4076
4065
|
}
|
|
4077
4066
|
if (skillPath) {
|
|
4078
|
-
const sub =
|
|
4067
|
+
const sub = path28.resolve(path28.join(root, skillPath));
|
|
4079
4068
|
if (!isContainedIn(sub, root)) {
|
|
4080
4069
|
cleanupTmp(root);
|
|
4081
4070
|
return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
|
|
4082
4071
|
}
|
|
4083
4072
|
if (fs21.existsSync(sub)) return { dir: sub, root };
|
|
4084
4073
|
const lastSeg = skillPath.split("/").pop();
|
|
4085
|
-
const sub2 =
|
|
4074
|
+
const sub2 = path28.resolve(path28.join(root, lastSeg));
|
|
4086
4075
|
if (isContainedIn(sub2, root) && fs21.existsSync(sub2)) {
|
|
4087
4076
|
if (!validateExtractedTree(sub2)) {
|
|
4088
4077
|
cleanupTmp(root);
|
|
@@ -4103,10 +4092,10 @@ function pruneEngramDbBackups() {
|
|
|
4103
4092
|
try {
|
|
4104
4093
|
const dir = dataDir();
|
|
4105
4094
|
if (!fs21.existsSync(dir)) return;
|
|
4106
|
-
const backups = fs21.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs21.statSync(
|
|
4095
|
+
const backups = fs21.readdirSync(dir).filter((f) => f.startsWith("engram-db-backup-") && f.endsWith(".db")).map((f) => ({ name: f, mtime: fs21.statSync(path28.join(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
4107
4096
|
for (const old of backups.slice(3)) {
|
|
4108
4097
|
try {
|
|
4109
|
-
fs21.rmSync(
|
|
4098
|
+
fs21.rmSync(path28.join(dir, old.name));
|
|
4110
4099
|
} catch {
|
|
4111
4100
|
}
|
|
4112
4101
|
}
|
|
@@ -4115,17 +4104,17 @@ function pruneEngramDbBackups() {
|
|
|
4115
4104
|
}
|
|
4116
4105
|
function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
4117
4106
|
if (!fs21.existsSync(binPath)) return null;
|
|
4118
|
-
const dir =
|
|
4119
|
-
const base =
|
|
4107
|
+
const dir = path28.dirname(binPath);
|
|
4108
|
+
const base = path28.basename(binPath);
|
|
4120
4109
|
const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
|
|
4121
4110
|
const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
|
|
4122
|
-
const resolvedDir =
|
|
4123
|
-
if (resolvedDir ===
|
|
4111
|
+
const resolvedDir = path28.resolve(dir);
|
|
4112
|
+
if (resolvedDir === path28.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
|
|
4124
4113
|
try {
|
|
4125
4114
|
for (const entry of fs21.readdirSync(dir)) {
|
|
4126
4115
|
if (oldPattern.test(entry)) {
|
|
4127
4116
|
try {
|
|
4128
|
-
fs21.rmSync(
|
|
4117
|
+
fs21.rmSync(path28.join(dir, entry), { force: true });
|
|
4129
4118
|
} catch {
|
|
4130
4119
|
}
|
|
4131
4120
|
}
|
|
@@ -4133,7 +4122,7 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
|
4133
4122
|
} catch {
|
|
4134
4123
|
}
|
|
4135
4124
|
}
|
|
4136
|
-
const rotated =
|
|
4125
|
+
const rotated = path28.join(dir, `${base}.old-${Date.now()}`);
|
|
4137
4126
|
fs21.renameSync(binPath, rotated);
|
|
4138
4127
|
return rotated;
|
|
4139
4128
|
}
|
|
@@ -4143,8 +4132,8 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4143
4132
|
"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)."
|
|
4144
4133
|
);
|
|
4145
4134
|
}
|
|
4146
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
4147
|
-
const engramDb =
|
|
4135
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path28.join(HOME, ".engram");
|
|
4136
|
+
const engramDb = path28.join(engramDataDir, "engram.db");
|
|
4148
4137
|
if (fs21.existsSync(engramDb)) {
|
|
4149
4138
|
const doBackup = await p4.confirm({
|
|
4150
4139
|
message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
|
|
@@ -4152,7 +4141,7 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4152
4141
|
});
|
|
4153
4142
|
if (!p4.isCancel(doBackup) && doBackup) {
|
|
4154
4143
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4155
|
-
const dest =
|
|
4144
|
+
const dest = path28.join(dataDir(), `engram-db-backup-${ts}.db`);
|
|
4156
4145
|
try {
|
|
4157
4146
|
if (!fs21.existsSync(dataDir())) fs21.mkdirSync(dataDir(), { recursive: true });
|
|
4158
4147
|
fs21.copyFileSync(engramDb, dest);
|
|
@@ -4489,7 +4478,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
|
|
|
4489
4478
|
continue;
|
|
4490
4479
|
}
|
|
4491
4480
|
const { dir: tmpDir, root: tmpRoot } = tmpResult;
|
|
4492
|
-
const localSkillDir =
|
|
4481
|
+
const localSkillDir = path28.join(stackRoot(), "skills", skillInfo.name);
|
|
4493
4482
|
const diff = renderSkillDiff(tmpDir, localSkillDir);
|
|
4494
4483
|
if (diff) {
|
|
4495
4484
|
p4.log.info(`Diff de ${skillInfo.name}:
|
|
@@ -4575,7 +4564,7 @@ ${diff}`);
|
|
|
4575
4564
|
}
|
|
4576
4565
|
|
|
4577
4566
|
// src/models-picker.ts
|
|
4578
|
-
import
|
|
4567
|
+
import path29 from "path";
|
|
4579
4568
|
import * as p5 from "@clack/prompts";
|
|
4580
4569
|
var TIERS = ["strong", "standard", "cheap"];
|
|
4581
4570
|
var EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
@@ -4599,7 +4588,7 @@ function opencodeLiveModels(binPath) {
|
|
|
4599
4588
|
}
|
|
4600
4589
|
function agentsByTier() {
|
|
4601
4590
|
const grouped = { strong: [], standard: [], cheap: [] };
|
|
4602
|
-
for (const agent of loadCanonicalAgents(
|
|
4591
|
+
for (const agent of loadCanonicalAgents(path29.join(stackRoot(), "agents"))) {
|
|
4603
4592
|
if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
|
|
4604
4593
|
}
|
|
4605
4594
|
return grouped;
|
|
@@ -4780,15 +4769,15 @@ function cancelled() {
|
|
|
4780
4769
|
|
|
4781
4770
|
// src/lib/release.ts
|
|
4782
4771
|
import fs22 from "fs";
|
|
4783
|
-
import
|
|
4772
|
+
import path30 from "path";
|
|
4784
4773
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
4785
|
-
import { fileURLToPath
|
|
4774
|
+
import { fileURLToPath } from "url";
|
|
4786
4775
|
function findPackageJson() {
|
|
4787
|
-
let dir =
|
|
4776
|
+
let dir = path30.dirname(fileURLToPath(import.meta.url));
|
|
4788
4777
|
for (let i = 0; i < 6; i++) {
|
|
4789
|
-
const candidate =
|
|
4778
|
+
const candidate = path30.join(dir, "package.json");
|
|
4790
4779
|
if (fs22.existsSync(candidate)) return candidate;
|
|
4791
|
-
dir =
|
|
4780
|
+
dir = path30.dirname(dir);
|
|
4792
4781
|
}
|
|
4793
4782
|
throw new Error("No se encontr\xF3 package.json cerca del CLI.");
|
|
4794
4783
|
}
|
|
@@ -4808,8 +4797,8 @@ function readPackageMetadata() {
|
|
|
4808
4797
|
}
|
|
4809
4798
|
|
|
4810
4799
|
// src/lib/pi-runtime.ts
|
|
4811
|
-
import
|
|
4812
|
-
import
|
|
4800
|
+
import os4 from "os";
|
|
4801
|
+
import path31 from "path";
|
|
4813
4802
|
import fs23 from "fs";
|
|
4814
4803
|
import { spawnSync } from "child_process";
|
|
4815
4804
|
import { createHash } from "crypto";
|
|
@@ -4963,14 +4952,14 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
|
|
|
4963
4952
|
if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
|
|
4964
4953
|
const packageValue = Reflect.get(record, "package");
|
|
4965
4954
|
const result = Reflect.get(record, "result");
|
|
4966
|
-
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]) &&
|
|
4955
|
+
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]) && path31.resolve(packageRunner) === path31.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
|
|
4967
4956
|
} catch {
|
|
4968
4957
|
return false;
|
|
4969
4958
|
}
|
|
4970
4959
|
}
|
|
4971
4960
|
function installPiFromVerifiedTarball(input, deps) {
|
|
4972
4961
|
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
|
|
4973
|
-
const destination = input.targetDir === void 0 ?
|
|
4962
|
+
const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
4974
4963
|
const artifact = deps.download(destination);
|
|
4975
4964
|
if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
|
|
4976
4965
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
@@ -4978,7 +4967,7 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
4978
4967
|
deps.backupSettings();
|
|
4979
4968
|
const scope = {
|
|
4980
4969
|
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4981
|
-
codingAgentDir:
|
|
4970
|
+
codingAgentDir: path31.resolve(paths.codingAgentDir)
|
|
4982
4971
|
};
|
|
4983
4972
|
const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
|
|
4984
4973
|
deps.writeReceiptAtomic(`${JSON.stringify(installing)}
|
|
@@ -5007,30 +4996,30 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
5007
4996
|
return { kind: "installed", receipt };
|
|
5008
4997
|
}
|
|
5009
4998
|
function runtimePath(piExecutable) {
|
|
5010
|
-
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null :
|
|
5011
|
-
return [...new Set(entries.filter((entry) => entry !== null))].join(
|
|
4999
|
+
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), process.env.SystemRoot ? path31.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path31.dirname(piExecutable), path31.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
|
|
5000
|
+
return [...new Set(entries.filter((entry) => entry !== null))].join(path31.delimiter);
|
|
5012
5001
|
}
|
|
5013
5002
|
function targetPaths(targetDir, engramBin, piExecutable) {
|
|
5014
|
-
const root =
|
|
5015
|
-
const codingAgentDir =
|
|
5016
|
-
const home =
|
|
5017
|
-
const temporary =
|
|
5003
|
+
const root = path31.resolve(targetDir);
|
|
5004
|
+
const codingAgentDir = path31.join(root, "pi-agent");
|
|
5005
|
+
const home = path31.join(root, "home");
|
|
5006
|
+
const temporary = path31.join(root, "tmp");
|
|
5018
5007
|
return {
|
|
5019
5008
|
codingAgentDir,
|
|
5020
|
-
receiptPath:
|
|
5021
|
-
packageRunner:
|
|
5009
|
+
receiptPath: path31.join(root, "state", "pi-receipt.json"),
|
|
5010
|
+
packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5022
5011
|
environment: {
|
|
5023
5012
|
HOME: home,
|
|
5024
5013
|
USERPROFILE: home,
|
|
5025
|
-
APPDATA:
|
|
5026
|
-
LOCALAPPDATA:
|
|
5027
|
-
XDG_CONFIG_HOME:
|
|
5028
|
-
XDG_DATA_HOME:
|
|
5029
|
-
XDG_CACHE_HOME:
|
|
5014
|
+
APPDATA: path31.join(root, "appdata"),
|
|
5015
|
+
LOCALAPPDATA: path31.join(root, "localappdata"),
|
|
5016
|
+
XDG_CONFIG_HOME: path31.join(root, "xdg-config"),
|
|
5017
|
+
XDG_DATA_HOME: path31.join(root, "xdg-data"),
|
|
5018
|
+
XDG_CACHE_HOME: path31.join(root, "xdg-cache"),
|
|
5030
5019
|
TEMP: temporary,
|
|
5031
5020
|
TMP: temporary,
|
|
5032
5021
|
TMPDIR: temporary,
|
|
5033
|
-
npm_config_cache:
|
|
5022
|
+
npm_config_cache: path31.join(root, "npm-cache"),
|
|
5034
5023
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5035
5024
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
5036
5025
|
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
@@ -5040,17 +5029,17 @@ function targetPaths(targetDir, engramBin, piExecutable) {
|
|
|
5040
5029
|
};
|
|
5041
5030
|
}
|
|
5042
5031
|
function userPaths(engramBin, piExecutable) {
|
|
5043
|
-
const home =
|
|
5044
|
-
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ??
|
|
5032
|
+
const home = os4.homedir();
|
|
5033
|
+
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent");
|
|
5045
5034
|
return {
|
|
5046
5035
|
codingAgentDir,
|
|
5047
|
-
receiptPath:
|
|
5048
|
-
packageRunner:
|
|
5036
|
+
receiptPath: path31.join(dataDir(), "pi-receipt.json"),
|
|
5037
|
+
packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5049
5038
|
environment: {
|
|
5050
5039
|
HOME: home,
|
|
5051
|
-
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ??
|
|
5052
|
-
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ??
|
|
5053
|
-
TMPDIR:
|
|
5040
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path31.join(home, ".config"),
|
|
5041
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path31.join(home, ".cache"),
|
|
5042
|
+
TMPDIR: os4.tmpdir(),
|
|
5054
5043
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5055
5044
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
5056
5045
|
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
@@ -5077,7 +5066,7 @@ function runPiRuntime(input, deps) {
|
|
|
5077
5066
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
5078
5067
|
}
|
|
5079
5068
|
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
5080
|
-
const settingsJson = deps.readSettings(
|
|
5069
|
+
const settingsJson = deps.readSettings(path31.join(paths.codingAgentDir, "settings.json"));
|
|
5081
5070
|
const receiptJson = deps.readReceipt(paths.receiptPath);
|
|
5082
5071
|
const lifecycleInput = {
|
|
5083
5072
|
candidate: PI_RUNTIME_CANDIDATE,
|
|
@@ -5137,14 +5126,14 @@ function readJsonFile(file) {
|
|
|
5137
5126
|
function packageVersionFromExecutable(executable) {
|
|
5138
5127
|
let current;
|
|
5139
5128
|
try {
|
|
5140
|
-
current =
|
|
5129
|
+
current = path31.dirname(fs23.realpathSync(executable));
|
|
5141
5130
|
} catch {
|
|
5142
5131
|
return null;
|
|
5143
5132
|
}
|
|
5144
5133
|
for (let depth = 0; depth < 8; depth++) {
|
|
5145
5134
|
const manifests = [
|
|
5146
|
-
|
|
5147
|
-
|
|
5135
|
+
path31.join(current, "package.json"),
|
|
5136
|
+
path31.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
|
|
5148
5137
|
];
|
|
5149
5138
|
for (const manifest of manifests) {
|
|
5150
5139
|
try {
|
|
@@ -5155,7 +5144,7 @@ function packageVersionFromExecutable(executable) {
|
|
|
5155
5144
|
} catch {
|
|
5156
5145
|
}
|
|
5157
5146
|
}
|
|
5158
|
-
const parent =
|
|
5147
|
+
const parent = path31.dirname(current);
|
|
5159
5148
|
if (parent === current) break;
|
|
5160
5149
|
current = parent;
|
|
5161
5150
|
}
|
|
@@ -5163,23 +5152,23 @@ function packageVersionFromExecutable(executable) {
|
|
|
5163
5152
|
}
|
|
5164
5153
|
function detectPiRuntime() {
|
|
5165
5154
|
const executable = lookPath("pi");
|
|
5166
|
-
const home =
|
|
5155
|
+
const home = os4.homedir();
|
|
5167
5156
|
return {
|
|
5168
5157
|
id: "pi",
|
|
5169
5158
|
name: "Pi",
|
|
5170
5159
|
installed: executable !== null,
|
|
5171
5160
|
executable,
|
|
5172
5161
|
version: executable === null ? null : packageVersionFromExecutable(executable),
|
|
5173
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
5162
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent")
|
|
5174
5163
|
};
|
|
5175
5164
|
}
|
|
5176
5165
|
function hasManagedPiRuntime(targetDir) {
|
|
5177
|
-
const receipt = targetDir === void 0 ?
|
|
5166
|
+
const receipt = targetDir === void 0 ? path31.join(dataDir(), "pi-receipt.json") : path31.join(path31.resolve(targetDir), "state", "pi-receipt.json");
|
|
5178
5167
|
return fs23.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
|
|
5179
5168
|
}
|
|
5180
5169
|
function resolvePiEngramBin(targetDir) {
|
|
5181
5170
|
if (targetDir === void 0) return detectEngram();
|
|
5182
|
-
const candidate =
|
|
5171
|
+
const candidate = path31.join(path31.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
|
|
5183
5172
|
return fs23.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
|
|
5184
5173
|
}
|
|
5185
5174
|
function readOptional(file, fallback) {
|
|
@@ -5192,7 +5181,7 @@ function readOptional(file, fallback) {
|
|
|
5192
5181
|
}
|
|
5193
5182
|
function hashPiTarball(file) {
|
|
5194
5183
|
const descriptor = fs23.openSync(file, "r");
|
|
5195
|
-
const
|
|
5184
|
+
const sha2562 = createHash("sha256");
|
|
5196
5185
|
const sha512 = createHash("sha512");
|
|
5197
5186
|
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
5198
5187
|
let bytes = 0;
|
|
@@ -5202,13 +5191,13 @@ function hashPiTarball(file) {
|
|
|
5202
5191
|
if (read === 0) break;
|
|
5203
5192
|
bytes += read;
|
|
5204
5193
|
const chunk = buffer.subarray(0, read);
|
|
5205
|
-
|
|
5194
|
+
sha2562.update(chunk);
|
|
5206
5195
|
sha512.update(chunk);
|
|
5207
5196
|
}
|
|
5208
5197
|
} finally {
|
|
5209
5198
|
fs23.closeSync(descriptor);
|
|
5210
5199
|
}
|
|
5211
|
-
return { path: file, bytes, sha256:
|
|
5200
|
+
return { path: file, bytes, sha256: sha2562.digest("hex"), sha512: sha512.digest("hex") };
|
|
5212
5201
|
}
|
|
5213
5202
|
async function acquirePiTarball(destination) {
|
|
5214
5203
|
const existing = fs23.statSync(destination, { throwIfNoEntry: false });
|
|
@@ -5219,7 +5208,7 @@ async function acquirePiTarball(destination) {
|
|
|
5219
5208
|
}
|
|
5220
5209
|
fs23.rmSync(destination, { force: true });
|
|
5221
5210
|
}
|
|
5222
|
-
fs23.mkdirSync(
|
|
5211
|
+
fs23.mkdirSync(path31.dirname(destination), { recursive: true });
|
|
5223
5212
|
const partial = `${destination}.partial-${process.pid}`;
|
|
5224
5213
|
const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
|
|
5225
5214
|
redirect: "error",
|
|
@@ -5292,7 +5281,7 @@ async function runPiRuntimeSystem(input) {
|
|
|
5292
5281
|
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
5293
5282
|
};
|
|
5294
5283
|
}
|
|
5295
|
-
const destination = input.targetDir === void 0 ?
|
|
5284
|
+
const destination = input.targetDir === void 0 ? path31.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path31.join(path31.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
5296
5285
|
let artifact;
|
|
5297
5286
|
try {
|
|
5298
5287
|
artifact = await acquirePiTarball(destination);
|
|
@@ -5312,18 +5301,18 @@ async function runPiRuntimeSystem(input) {
|
|
|
5312
5301
|
}, {
|
|
5313
5302
|
download: () => artifact,
|
|
5314
5303
|
backupSettings: () => createBackup(
|
|
5315
|
-
[
|
|
5304
|
+
[path31.join(paths.codingAgentDir, "settings.json")],
|
|
5316
5305
|
"pi-package-install",
|
|
5317
|
-
input.targetDir === void 0 ? void 0 :
|
|
5306
|
+
input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
|
|
5318
5307
|
),
|
|
5319
5308
|
run: runProcess,
|
|
5320
|
-
readSettings: () => readOptional(
|
|
5321
|
-
rewriteSettings: (content) => writeText(
|
|
5309
|
+
readSettings: () => readOptional(path31.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
|
|
5310
|
+
rewriteSettings: (content) => writeText(path31.join(paths.codingAgentDir, "settings.json"), `${content}
|
|
5322
5311
|
`),
|
|
5323
5312
|
writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
|
|
5324
5313
|
});
|
|
5325
5314
|
}
|
|
5326
|
-
const packageRoot =
|
|
5315
|
+
const packageRoot = path31.dirname(path31.dirname(paths.packageRunner));
|
|
5327
5316
|
const writeReceipt = (receipt) => {
|
|
5328
5317
|
writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
|
|
5329
5318
|
`);
|
|
@@ -5341,9 +5330,9 @@ async function runPiRuntimeSystem(input) {
|
|
|
5341
5330
|
value,
|
|
5342
5331
|
{
|
|
5343
5332
|
backupSettings: () => createBackup(
|
|
5344
|
-
[
|
|
5333
|
+
[path31.join(paths.codingAgentDir, "settings.json")],
|
|
5345
5334
|
"pi-package-uninstall",
|
|
5346
|
-
input.targetDir === void 0 ? void 0 :
|
|
5335
|
+
input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
|
|
5347
5336
|
),
|
|
5348
5337
|
run: runProcess,
|
|
5349
5338
|
isPackageAbsent: () => !fs23.existsSync(packageRoot),
|
|
@@ -5443,9 +5432,390 @@ async function runManagedPiSystem(input) {
|
|
|
5443
5432
|
});
|
|
5444
5433
|
}
|
|
5445
5434
|
|
|
5435
|
+
// src/lib/quality-runner.ts
|
|
5436
|
+
import path32 from "path";
|
|
5437
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
5438
|
+
function normalizeLimit(value, name) {
|
|
5439
|
+
if (value === void 0) return void 0;
|
|
5440
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
5441
|
+
throw new Error(`${name} must be a non-negative safe integer`);
|
|
5442
|
+
}
|
|
5443
|
+
return value;
|
|
5444
|
+
}
|
|
5445
|
+
var MAX_NODE_TIMEOUT_MS = 2147483647;
|
|
5446
|
+
var TERMINATION_GRACE_MS = 250;
|
|
5447
|
+
var TASKKILL_TIMEOUT_MS = 2e3;
|
|
5448
|
+
function normalizeTimeout(value) {
|
|
5449
|
+
const timeoutMs = normalizeLimit(value, "timeoutMs");
|
|
5450
|
+
if (timeoutMs !== void 0 && timeoutMs > MAX_NODE_TIMEOUT_MS) {
|
|
5451
|
+
throw new Error(`timeoutMs must not exceed ${MAX_NODE_TIMEOUT_MS}`);
|
|
5452
|
+
}
|
|
5453
|
+
return timeoutMs;
|
|
5454
|
+
}
|
|
5455
|
+
var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
5456
|
+
function isRecord2(value) {
|
|
5457
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5458
|
+
}
|
|
5459
|
+
function hasText(value) {
|
|
5460
|
+
return typeof value === "string" && value.trim() !== "";
|
|
5461
|
+
}
|
|
5462
|
+
function isDenseStringArray(value) {
|
|
5463
|
+
if (!Array.isArray(value)) return false;
|
|
5464
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
5465
|
+
if (!Object.prototype.hasOwnProperty.call(value, String(index)) || typeof value[index] !== "string") return false;
|
|
5466
|
+
}
|
|
5467
|
+
return true;
|
|
5468
|
+
}
|
|
5469
|
+
function isNonNegativeSafeInteger(value) {
|
|
5470
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
5471
|
+
}
|
|
5472
|
+
function isQualityProfile(value) {
|
|
5473
|
+
return typeof value === "string" && QUALITY_PROFILES.includes(value);
|
|
5474
|
+
}
|
|
5475
|
+
function assertValidEnvironment(value, label) {
|
|
5476
|
+
if (!isRecord2(value)) throw new Error(`Invalid ${label}`);
|
|
5477
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
5478
|
+
if (typeof entry !== "string") throw new Error(`Invalid ${label}.${key}`);
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
function assertQualityPlanInput(value) {
|
|
5482
|
+
if (!isRecord2(value)) throw new Error("Invalid quality plan");
|
|
5483
|
+
if (!isRecord2(value.identity)) throw new Error("Invalid quality plan identity");
|
|
5484
|
+
if (typeof value.identity.baseSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.baseSha)) {
|
|
5485
|
+
throw new Error("Invalid quality plan identity.baseSha");
|
|
5486
|
+
}
|
|
5487
|
+
if (typeof value.identity.headSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.headSha)) {
|
|
5488
|
+
throw new Error("Invalid quality plan identity.headSha");
|
|
5489
|
+
}
|
|
5490
|
+
if (!isQualityProfile(value.profile)) throw new Error("Invalid quality plan profile");
|
|
5491
|
+
if (!Array.isArray(value.controls)) throw new Error("Invalid quality plan controls");
|
|
5492
|
+
const controlIds = /* @__PURE__ */ new Set();
|
|
5493
|
+
for (let index = 0; index < value.controls.length; index += 1) {
|
|
5494
|
+
const control = value.controls[index];
|
|
5495
|
+
if (!isRecord2(control) || !hasText(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
|
|
5496
|
+
throw new Error(`Invalid quality plan controls[${index}]`);
|
|
5497
|
+
}
|
|
5498
|
+
if (controlIds.has(control.id)) {
|
|
5499
|
+
throw new Error(`Duplicate quality plan control id: ${control.id}`);
|
|
5500
|
+
}
|
|
5501
|
+
controlIds.add(control.id);
|
|
5502
|
+
if (Object.prototype.hasOwnProperty.call(control, "notApplicable") && typeof control.notApplicable !== "boolean") {
|
|
5503
|
+
throw new Error(`Invalid quality plan controls[${index}].notApplicable`);
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
if (!Array.isArray(value.commands)) throw new Error("Invalid quality plan commands");
|
|
5507
|
+
const commandIds = /* @__PURE__ */ new Set();
|
|
5508
|
+
const commandControlIds = /* @__PURE__ */ new Set();
|
|
5509
|
+
for (let index = 0; index < value.commands.length; index += 1) {
|
|
5510
|
+
const command = value.commands[index];
|
|
5511
|
+
if (!isRecord2(command)) throw new Error(`Invalid quality plan commands[${index}]`);
|
|
5512
|
+
const timeoutMs = command.timeoutMs;
|
|
5513
|
+
if (!hasText(command.controlId) || !hasText(command.commandId) || !hasText(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
|
|
5514
|
+
throw new Error(`Invalid quality plan commands[${index}]`);
|
|
5515
|
+
}
|
|
5516
|
+
if (timeoutMs > MAX_NODE_TIMEOUT_MS) {
|
|
5517
|
+
throw new Error(`Invalid quality plan commands[${index}].timeoutMs: maximum is ${MAX_NODE_TIMEOUT_MS}`);
|
|
5518
|
+
}
|
|
5519
|
+
if (!controlIds.has(command.controlId)) {
|
|
5520
|
+
throw new Error(`Unknown quality plan command control id: ${command.controlId}`);
|
|
5521
|
+
}
|
|
5522
|
+
if (commandIds.has(command.commandId)) {
|
|
5523
|
+
throw new Error(`Duplicate quality plan command id: ${command.commandId}`);
|
|
5524
|
+
}
|
|
5525
|
+
if (commandControlIds.has(command.controlId)) {
|
|
5526
|
+
throw new Error(`Multiple quality plan commands for control id: ${command.controlId}`);
|
|
5527
|
+
}
|
|
5528
|
+
commandIds.add(command.commandId);
|
|
5529
|
+
commandControlIds.add(command.controlId);
|
|
5530
|
+
if (command.maxOutputBytes !== void 0 && !isNonNegativeSafeInteger(command.maxOutputBytes)) {
|
|
5531
|
+
throw new Error(`Invalid quality plan commands[${index}].maxOutputBytes`);
|
|
5532
|
+
}
|
|
5533
|
+
if (command.env !== void 0) assertValidEnvironment(command.env, `quality plan commands[${index}].env`);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
function appendOutput(captured, stream, chunk, maxOutputBytes) {
|
|
5537
|
+
if (maxOutputBytes === void 0) {
|
|
5538
|
+
captured[stream].push(chunk);
|
|
5539
|
+
return false;
|
|
5540
|
+
}
|
|
5541
|
+
const remaining = maxOutputBytes - captured.bytes;
|
|
5542
|
+
if (remaining <= 0) return chunk.length > 0;
|
|
5543
|
+
if (chunk.length > remaining) {
|
|
5544
|
+
captured[stream].push(chunk.subarray(0, remaining));
|
|
5545
|
+
captured.bytes += remaining;
|
|
5546
|
+
return true;
|
|
5547
|
+
}
|
|
5548
|
+
captured[stream].push(chunk);
|
|
5549
|
+
captured.bytes += chunk.length;
|
|
5550
|
+
return captured.bytes >= maxOutputBytes;
|
|
5551
|
+
}
|
|
5552
|
+
function decodeOutput(chunks, maxOutputBytes) {
|
|
5553
|
+
const output = Buffer.concat(chunks);
|
|
5554
|
+
if (maxOutputBytes === void 0 || output.length === 0) return output.toString("utf8");
|
|
5555
|
+
let end = output.length;
|
|
5556
|
+
let decoded = output.subarray(0, end).toString("utf8");
|
|
5557
|
+
while (Buffer.byteLength(decoded, "utf8") > maxOutputBytes && end > 0) {
|
|
5558
|
+
end -= 1;
|
|
5559
|
+
decoded = output.subarray(0, end).toString("utf8");
|
|
5560
|
+
}
|
|
5561
|
+
return decoded;
|
|
5562
|
+
}
|
|
5563
|
+
function killProcessTree(child) {
|
|
5564
|
+
const pid = child.pid;
|
|
5565
|
+
if (pid === void 0) return;
|
|
5566
|
+
if (process.platform === "win32") {
|
|
5567
|
+
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
|
|
5568
|
+
const taskkill = path32.join(systemRoot, "System32", "taskkill.exe");
|
|
5569
|
+
const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
|
|
5570
|
+
shell: false,
|
|
5571
|
+
stdio: "ignore",
|
|
5572
|
+
timeout: TASKKILL_TIMEOUT_MS,
|
|
5573
|
+
windowsHide: true
|
|
5574
|
+
});
|
|
5575
|
+
if (result.error !== void 0 || result.status !== 0) {
|
|
5576
|
+
try {
|
|
5577
|
+
child.kill("SIGKILL");
|
|
5578
|
+
} catch {
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
return;
|
|
5582
|
+
}
|
|
5583
|
+
try {
|
|
5584
|
+
process.kill(-pid, "SIGKILL");
|
|
5585
|
+
} catch {
|
|
5586
|
+
try {
|
|
5587
|
+
child.kill("SIGKILL");
|
|
5588
|
+
} catch {
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
function resultFromCaptured(input, status, exitCode, startedAt, captured, maxOutputBytes, reason) {
|
|
5593
|
+
return {
|
|
5594
|
+
commandId: input.commandId,
|
|
5595
|
+
status,
|
|
5596
|
+
exitCode,
|
|
5597
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
5598
|
+
output: {
|
|
5599
|
+
stdout: decodeOutput(captured.stdout, maxOutputBytes),
|
|
5600
|
+
stderr: decodeOutput(captured.stderr, maxOutputBytes)
|
|
5601
|
+
},
|
|
5602
|
+
...reason === void 0 ? {} : { reason }
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5605
|
+
async function runQualityCommand(input, deps = { terminate: killProcessTree }) {
|
|
5606
|
+
const timeoutMs = normalizeTimeout(input.timeoutMs);
|
|
5607
|
+
const maxOutputBytes = normalizeLimit(input.maxOutputBytes, "maxOutputBytes");
|
|
5608
|
+
if (timeoutMs === void 0) throw new Error("timeoutMs is required");
|
|
5609
|
+
const planned = planDetectedBinCommand(input.executable, [...input.argv]);
|
|
5610
|
+
const startedAt = Date.now();
|
|
5611
|
+
const captured = { stdout: [], stderr: [], bytes: 0 };
|
|
5612
|
+
if (planned === null) {
|
|
5613
|
+
return resultFromCaptured(input, "error", null, startedAt, captured, maxOutputBytes, "unsafe-command");
|
|
5614
|
+
}
|
|
5615
|
+
return new Promise((resolve) => {
|
|
5616
|
+
let settled = false;
|
|
5617
|
+
let terminationReason;
|
|
5618
|
+
let spawnError;
|
|
5619
|
+
let timeout;
|
|
5620
|
+
let terminationDeadline;
|
|
5621
|
+
let child;
|
|
5622
|
+
let cleanedUp = false;
|
|
5623
|
+
let onStdoutData = (_chunk) => {
|
|
5624
|
+
};
|
|
5625
|
+
let onStderrData = (_chunk) => {
|
|
5626
|
+
};
|
|
5627
|
+
let onError = (_error) => {
|
|
5628
|
+
};
|
|
5629
|
+
const onErrorSink = () => {
|
|
5630
|
+
};
|
|
5631
|
+
let onClose = (_exitCode) => {
|
|
5632
|
+
};
|
|
5633
|
+
const cleanupChild = () => {
|
|
5634
|
+
if (cleanedUp || child === void 0) return;
|
|
5635
|
+
cleanedUp = true;
|
|
5636
|
+
child.removeListener("close", onClose);
|
|
5637
|
+
child.removeListener("error", onError);
|
|
5638
|
+
child.on("error", onErrorSink);
|
|
5639
|
+
child.stdout?.removeListener("data", onStdoutData);
|
|
5640
|
+
child.stderr?.removeListener("data", onStderrData);
|
|
5641
|
+
child.stdout?.destroy();
|
|
5642
|
+
child.stderr?.destroy();
|
|
5643
|
+
child.unref();
|
|
5644
|
+
};
|
|
5645
|
+
const settle = (result) => {
|
|
5646
|
+
if (settled) return;
|
|
5647
|
+
settled = true;
|
|
5648
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
5649
|
+
if (terminationDeadline !== void 0) clearTimeout(terminationDeadline);
|
|
5650
|
+
cleanupChild();
|
|
5651
|
+
resolve(result);
|
|
5652
|
+
};
|
|
5653
|
+
try {
|
|
5654
|
+
child = spawn(planned.command, planned.args, {
|
|
5655
|
+
detached: true,
|
|
5656
|
+
env: input.env === void 0 ? {} : { ...input.env },
|
|
5657
|
+
shell: false,
|
|
5658
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5659
|
+
windowsHide: true
|
|
5660
|
+
});
|
|
5661
|
+
} catch (error) {
|
|
5662
|
+
settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
|
|
5663
|
+
return;
|
|
5664
|
+
}
|
|
5665
|
+
const stopFor = (reason) => {
|
|
5666
|
+
if (settled || terminationReason !== void 0) return;
|
|
5667
|
+
terminationReason = reason;
|
|
5668
|
+
try {
|
|
5669
|
+
deps.terminate(child);
|
|
5670
|
+
} catch {
|
|
5671
|
+
}
|
|
5672
|
+
if (settled) return;
|
|
5673
|
+
terminationDeadline = setTimeout(() => {
|
|
5674
|
+
try {
|
|
5675
|
+
deps.terminate(child);
|
|
5676
|
+
} catch {
|
|
5677
|
+
}
|
|
5678
|
+
cleanupChild();
|
|
5679
|
+
settle(resultFromCaptured(input, "error", null, startedAt, captured, maxOutputBytes, "termination-timeout"));
|
|
5680
|
+
}, TERMINATION_GRACE_MS);
|
|
5681
|
+
};
|
|
5682
|
+
const onOutput = (stream, chunk) => {
|
|
5683
|
+
if (settled || appendOutput(captured, stream, chunk, maxOutputBytes)) {
|
|
5684
|
+
if (!settled && maxOutputBytes !== void 0) stopFor("output-limit");
|
|
5685
|
+
}
|
|
5686
|
+
};
|
|
5687
|
+
onStdoutData = (chunk) => onOutput("stdout", chunk);
|
|
5688
|
+
onStderrData = (chunk) => onOutput("stderr", chunk);
|
|
5689
|
+
onError = (error) => {
|
|
5690
|
+
spawnError = error;
|
|
5691
|
+
if (terminationReason === void 0 && (error.code === "ENOENT" || error.code === "EACCES")) {
|
|
5692
|
+
settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
|
|
5693
|
+
}
|
|
5694
|
+
};
|
|
5695
|
+
onClose = (exitCode) => {
|
|
5696
|
+
if (terminationReason === "timeout") {
|
|
5697
|
+
settle(resultFromCaptured(input, "timeout", exitCode, startedAt, captured, maxOutputBytes));
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5700
|
+
if (terminationReason === "output-limit") {
|
|
5701
|
+
settle(resultFromCaptured(input, "error", exitCode, startedAt, captured, maxOutputBytes, "output-limit"));
|
|
5702
|
+
return;
|
|
5703
|
+
}
|
|
5704
|
+
if (spawnError !== void 0) {
|
|
5705
|
+
settle(resultFromCaptured(input, "unavailable", null, startedAt, captured, maxOutputBytes, "spawn-error"));
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
settle(resultFromCaptured(
|
|
5709
|
+
input,
|
|
5710
|
+
exitCode === 0 ? "pass" : "fail",
|
|
5711
|
+
exitCode,
|
|
5712
|
+
startedAt,
|
|
5713
|
+
captured,
|
|
5714
|
+
maxOutputBytes
|
|
5715
|
+
));
|
|
5716
|
+
};
|
|
5717
|
+
child.stdout?.on("data", onStdoutData);
|
|
5718
|
+
child.stderr?.on("data", onStderrData);
|
|
5719
|
+
child.once("error", onError);
|
|
5720
|
+
child.once("close", onClose);
|
|
5721
|
+
timeout = setTimeout(() => stopFor("timeout"), timeoutMs);
|
|
5722
|
+
});
|
|
5723
|
+
}
|
|
5724
|
+
function resultEvidence(result) {
|
|
5725
|
+
const exitCode = result.exitCode === null ? "none" : String(result.exitCode);
|
|
5726
|
+
return `exit=${exitCode}; status=${result.status}; durationMs=${result.durationMs}`;
|
|
5727
|
+
}
|
|
5728
|
+
function resultReason(result) {
|
|
5729
|
+
switch (result.status) {
|
|
5730
|
+
case "pass":
|
|
5731
|
+
return void 0;
|
|
5732
|
+
case "fail":
|
|
5733
|
+
return "nonzero-exit";
|
|
5734
|
+
case "timeout":
|
|
5735
|
+
return "timeout";
|
|
5736
|
+
case "unavailable":
|
|
5737
|
+
return result.reason ?? "unavailable";
|
|
5738
|
+
case "error":
|
|
5739
|
+
return result.reason ?? "error";
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
function receiptResultFor(controlId, result) {
|
|
5743
|
+
const evidence = resultEvidence(result);
|
|
5744
|
+
if (result.status === "pass") return { controlId, status: "pass", evidence };
|
|
5745
|
+
return {
|
|
5746
|
+
controlId,
|
|
5747
|
+
status: result.status === "fail" ? "fail" : "incomplete",
|
|
5748
|
+
evidence,
|
|
5749
|
+
reason: resultReason(result)
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5752
|
+
function missingRequiredResults(controls, commands) {
|
|
5753
|
+
const commandControlIds = new Set(commands.map((command) => command.controlId));
|
|
5754
|
+
const missing = /* @__PURE__ */ new Set();
|
|
5755
|
+
for (const control of controls) {
|
|
5756
|
+
if (control.requirement !== "required" || !hasText(control.id) || commandControlIds.has(control.id)) continue;
|
|
5757
|
+
missing.add(control.id);
|
|
5758
|
+
}
|
|
5759
|
+
return [...missing].map((controlId) => ({
|
|
5760
|
+
controlId,
|
|
5761
|
+
status: "incomplete",
|
|
5762
|
+
evidence: "required control has no declared command",
|
|
5763
|
+
reason: "required-control-missing"
|
|
5764
|
+
}));
|
|
5765
|
+
}
|
|
5766
|
+
function receiptCommandFor(command, result) {
|
|
5767
|
+
return {
|
|
5768
|
+
commandId: command.commandId,
|
|
5769
|
+
executable: command.executable,
|
|
5770
|
+
argv: command.argv,
|
|
5771
|
+
exitCode: result.exitCode ?? -1,
|
|
5772
|
+
durationMs: result.durationMs,
|
|
5773
|
+
output: result.output
|
|
5774
|
+
};
|
|
5775
|
+
}
|
|
5776
|
+
async function runQualityPlan(input) {
|
|
5777
|
+
assertQualityPlanInput(input);
|
|
5778
|
+
const policy = { controls: input.controls, profile: input.profile };
|
|
5779
|
+
const policyDigest = sha256(canonicalJson(policy));
|
|
5780
|
+
const identity = {
|
|
5781
|
+
profile: input.profile,
|
|
5782
|
+
baseSha: input.identity.baseSha,
|
|
5783
|
+
headSha: input.identity.headSha,
|
|
5784
|
+
policyDigest
|
|
5785
|
+
};
|
|
5786
|
+
const commands = [];
|
|
5787
|
+
const results = [];
|
|
5788
|
+
for (const command of input.commands) {
|
|
5789
|
+
const result = await runQualityCommand(command);
|
|
5790
|
+
commands.push(receiptCommandFor(command, result));
|
|
5791
|
+
results.push(receiptResultFor(command.controlId, result));
|
|
5792
|
+
}
|
|
5793
|
+
results.push(...missingRequiredResults(input.controls, input.commands));
|
|
5794
|
+
const evaluation = evaluateQualityPolicy({
|
|
5795
|
+
profile: input.profile,
|
|
5796
|
+
controls: input.controls,
|
|
5797
|
+
results
|
|
5798
|
+
});
|
|
5799
|
+
const receipt = createQualityReceipt({
|
|
5800
|
+
authority: "local",
|
|
5801
|
+
identity,
|
|
5802
|
+
commands,
|
|
5803
|
+
results
|
|
5804
|
+
});
|
|
5805
|
+
validateQualityReceipt(receipt, identity);
|
|
5806
|
+
return { evaluation, receipt };
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5446
5809
|
// src/cli.ts
|
|
5447
5810
|
var VERSION = readPackageVersion();
|
|
5448
|
-
var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall"];
|
|
5811
|
+
var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
|
|
5812
|
+
var QUALITY_REJECTED_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
5813
|
+
"--agents",
|
|
5814
|
+
"-a",
|
|
5815
|
+
"--target-dir",
|
|
5816
|
+
"--mode",
|
|
5817
|
+
"--subagent-concurrency"
|
|
5818
|
+
]);
|
|
5449
5819
|
async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
|
|
5450
5820
|
if (!runtimes.includes("opencode") || loadModelMap().opencode) return true;
|
|
5451
5821
|
const canPrompt = command === "install" && !flags.yes && !flags.dryRun && process.stdout.isTTY;
|
|
@@ -5458,7 +5828,7 @@ async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
|
|
|
5458
5828
|
);
|
|
5459
5829
|
return false;
|
|
5460
5830
|
}
|
|
5461
|
-
function parseFlags(args) {
|
|
5831
|
+
function parseFlags(args, allowReceipt = false) {
|
|
5462
5832
|
const flags = {
|
|
5463
5833
|
agents: [],
|
|
5464
5834
|
dryRun: false,
|
|
@@ -5474,6 +5844,7 @@ function parseFlags(args) {
|
|
|
5474
5844
|
removePlaywright: false,
|
|
5475
5845
|
devtools: false,
|
|
5476
5846
|
noDevtools: false,
|
|
5847
|
+
receipt: void 0,
|
|
5477
5848
|
positional: [],
|
|
5478
5849
|
unknownFlags: []
|
|
5479
5850
|
};
|
|
@@ -5484,6 +5855,26 @@ function parseFlags(args) {
|
|
|
5484
5855
|
};
|
|
5485
5856
|
for (let i = 0; i < args.length; i++) {
|
|
5486
5857
|
const arg = args[i];
|
|
5858
|
+
if (allowReceipt) {
|
|
5859
|
+
if (arg === "--help" || arg === "-h") flags.help = true;
|
|
5860
|
+
else if (arg === "--version" || arg === "-v") flags.version = true;
|
|
5861
|
+
else if (arg === "--receipt") {
|
|
5862
|
+
const [value, nextIndex] = readValue(i);
|
|
5863
|
+
if (value === void 0 || value === "") flags.unknownFlags.push(arg);
|
|
5864
|
+
else flags.receipt = value;
|
|
5865
|
+
i = nextIndex;
|
|
5866
|
+
} else if (arg.startsWith("--receipt=")) {
|
|
5867
|
+
const value = arg.slice(10);
|
|
5868
|
+
if (value === "") flags.unknownFlags.push(arg);
|
|
5869
|
+
else flags.receipt = value;
|
|
5870
|
+
} else if (QUALITY_REJECTED_VALUE_FLAGS.has(arg)) {
|
|
5871
|
+
flags.unknownFlags.push(arg);
|
|
5872
|
+
const [, nextIndex] = readValue(i);
|
|
5873
|
+
i = nextIndex;
|
|
5874
|
+
} else if (arg.startsWith("-")) flags.unknownFlags.push(arg);
|
|
5875
|
+
else flags.positional.push(arg);
|
|
5876
|
+
continue;
|
|
5877
|
+
}
|
|
5487
5878
|
if (arg === "--agents" || arg === "-a") {
|
|
5488
5879
|
const [value, nextIndex] = readValue(i);
|
|
5489
5880
|
flags.agents = (value ?? "").split(",").filter(Boolean);
|
|
@@ -5635,7 +6026,7 @@ function parseCliArgs(argv) {
|
|
|
5635
6026
|
};
|
|
5636
6027
|
}
|
|
5637
6028
|
const command = isCommand ? first ?? "install" : "install";
|
|
5638
|
-
const flags = parseFlags(isCommand ? rest : argv);
|
|
6029
|
+
const flags = parseFlags(isCommand ? rest : argv, command === "quality");
|
|
5639
6030
|
if (first === "--help" || first === "-h" || flags.help) return { action: "help", command, flags };
|
|
5640
6031
|
if (first === "--version" || first === "-v" || flags.version) return { action: "version", command, flags };
|
|
5641
6032
|
if (flags.unknownFlags.length > 0) return { action: "unknown-flags", command, flags };
|
|
@@ -5727,8 +6118,9 @@ Comandos:
|
|
|
5727
6118
|
doctor Estado: Engram, drift de config, hooks de Codex, key de context7
|
|
5728
6119
|
restore --list para ver backups \xB7 'restore <id>' para restaurar
|
|
5729
6120
|
uninstall Retira SOLO lo gestionado por el stack (con backup).
|
|
5730
|
-
|
|
5731
|
-
|
|
6121
|
+
Engram se CONSERVA por defecto (memorias, binario y registro);
|
|
6122
|
+
desregistrarlo exige --remove-engram o el s\xED expl\xEDcito
|
|
6123
|
+
quality Ejecuta un plan JSON expl\xEDcito y emite un receipt local
|
|
5732
6124
|
|
|
5733
6125
|
Opciones:
|
|
5734
6126
|
--agents, -a opencode,claude-code,codex,pi Runtimes destino (default: detectados)
|
|
@@ -5744,6 +6136,7 @@ Opciones:
|
|
|
5744
6136
|
memorias y binario quedan intactos igualmente
|
|
5745
6137
|
--remove-playwright (uninstall) retira solo el paquete global de Playwright;
|
|
5746
6138
|
nunca perfiles, cach\xE9 ni navegadores
|
|
6139
|
+
--receipt <path> (quality) escribe el receipt en ese path de forma at\xF3mica
|
|
5747
6140
|
|
|
5748
6141
|
Ver PRD.md para el dise\xF1o completo.`);
|
|
5749
6142
|
}
|
|
@@ -5770,12 +6163,43 @@ Flags disponibles: jorgex-stack --help`
|
|
|
5770
6163
|
return;
|
|
5771
6164
|
}
|
|
5772
6165
|
const { command, flags } = parsed;
|
|
5773
|
-
if (flags.targetDir !== void 0 && flags.agents.length !== 1) {
|
|
6166
|
+
if (command !== "quality" && flags.targetDir !== void 0 && flags.agents.length !== 1) {
|
|
5774
6167
|
console.error("--target-dir requiere exactamente un runtime en --agents.");
|
|
5775
6168
|
process.exitCode = 1;
|
|
5776
6169
|
return;
|
|
5777
6170
|
}
|
|
5778
6171
|
switch (command) {
|
|
6172
|
+
case "quality": {
|
|
6173
|
+
if (flags.targetDir !== void 0 || flags.agents.length > 0 || flags.dryRun || flags.yes || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 || flags.list || flags.check || flags.removeEngram || flags.playwright || flags.removePlaywright || flags.devtools || flags.noDevtools) {
|
|
6174
|
+
console.error("quality solo admite <plan.json> y, opcionalmente, --receipt <path>.");
|
|
6175
|
+
process.exitCode = 1;
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
if (flags.positional.length !== 1) {
|
|
6179
|
+
console.error("Uso: jorgex-stack quality <plan.json> [--receipt <path>]");
|
|
6180
|
+
process.exitCode = 1;
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
if (flags.receipt !== void 0 && flags.receipt.trim() === "") {
|
|
6184
|
+
console.error("--receipt requiere un path no vac\xEDo.");
|
|
6185
|
+
process.exitCode = 1;
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
try {
|
|
6189
|
+
const plan = JSON.parse(fs24.readFileSync(flags.positional[0], "utf8"));
|
|
6190
|
+
const result = await runQualityPlan(plan);
|
|
6191
|
+
const serialized = serializeQualityReceipt(result.receipt);
|
|
6192
|
+
if (flags.receipt === void 0) process.stdout.write(`${serialized}
|
|
6193
|
+
`);
|
|
6194
|
+
else writeText(flags.receipt, `${serialized}
|
|
6195
|
+
`);
|
|
6196
|
+
process.exitCode = result.evaluation.status === "pass" ? 0 : 1;
|
|
6197
|
+
} catch (error) {
|
|
6198
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6199
|
+
process.exitCode = 1;
|
|
6200
|
+
}
|
|
6201
|
+
return;
|
|
6202
|
+
}
|
|
5779
6203
|
case "install":
|
|
5780
6204
|
case "sync": {
|
|
5781
6205
|
const runtimes = await resolveRuntimes(flags, command === "install");
|