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