jorgex-stack 1.5.0 → 1.7.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 +616 -799
- package/dist/quality-verifier.d.ts +150 -0
- package/dist/quality-verifier.js +249 -0
- package/package.json +7 -1
- package/stack/contracts/quality-capabilities.v1.schema.json +117 -0
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,12 +20,13 @@ 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";
|
|
29
|
+
import { isDeepStrictEqual } from "util";
|
|
16
30
|
import { pathToFileURL } from "url";
|
|
17
31
|
|
|
18
32
|
// src/lib/canonical.ts
|
|
@@ -68,37 +82,12 @@ function loadCanonicalDefaults(stackDir) {
|
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// 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";
|
|
85
|
+
import path3 from "path";
|
|
77
86
|
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
87
|
|
|
99
88
|
// src/lib/fsx.ts
|
|
100
89
|
import fs2 from "fs";
|
|
101
|
-
import
|
|
90
|
+
import path2 from "path";
|
|
102
91
|
function readTextIfExists(file) {
|
|
103
92
|
try {
|
|
104
93
|
return fs2.readFileSync(file, "utf8");
|
|
@@ -111,7 +100,7 @@ function ensureDir(dir) {
|
|
|
111
100
|
}
|
|
112
101
|
var tmpCounter = 0;
|
|
113
102
|
function tmpPath(target) {
|
|
114
|
-
return
|
|
103
|
+
return path2.join(path2.dirname(target), `.jorgex-${process.pid}-${tmpCounter++}.tmp`);
|
|
115
104
|
}
|
|
116
105
|
function renameInto(tmp, target) {
|
|
117
106
|
try {
|
|
@@ -122,23 +111,23 @@ function renameInto(tmp, target) {
|
|
|
122
111
|
}
|
|
123
112
|
}
|
|
124
113
|
function writeText(file, content) {
|
|
125
|
-
ensureDir(
|
|
114
|
+
ensureDir(path2.dirname(file));
|
|
126
115
|
const tmp = tmpPath(file);
|
|
127
116
|
fs2.writeFileSync(tmp, content, "utf8");
|
|
128
117
|
renameInto(tmp, file);
|
|
129
118
|
}
|
|
130
119
|
function copyFile(source, target) {
|
|
131
|
-
ensureDir(
|
|
120
|
+
ensureDir(path2.dirname(target));
|
|
132
121
|
const tmp = tmpPath(target);
|
|
133
122
|
fs2.copyFileSync(source, tmp);
|
|
134
123
|
renameInto(tmp, target);
|
|
135
124
|
}
|
|
136
125
|
function isContainedIn(child, root) {
|
|
137
|
-
const rel =
|
|
138
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
126
|
+
const rel = path2.relative(path2.resolve(root), path2.resolve(child));
|
|
127
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path2.sep}`) && !path2.isAbsolute(rel);
|
|
139
128
|
}
|
|
140
129
|
function pruneEmptyDirs(file, root) {
|
|
141
|
-
let dir =
|
|
130
|
+
let dir = path2.dirname(file);
|
|
142
131
|
while (dir.startsWith(root) && dir !== root) {
|
|
143
132
|
try {
|
|
144
133
|
if (fs2.readdirSync(dir).length > 0) return;
|
|
@@ -146,7 +135,7 @@ function pruneEmptyDirs(file, root) {
|
|
|
146
135
|
} catch {
|
|
147
136
|
return;
|
|
148
137
|
}
|
|
149
|
-
dir =
|
|
138
|
+
dir = path2.dirname(dir);
|
|
150
139
|
}
|
|
151
140
|
}
|
|
152
141
|
function sameFileContent(a, b) {
|
|
@@ -163,7 +152,7 @@ function listFilesRecursive(dir) {
|
|
|
163
152
|
if (!fs2.existsSync(dir)) return out;
|
|
164
153
|
const walk = (d) => {
|
|
165
154
|
for (const entry of fs2.readdirSync(d, { withFileTypes: true })) {
|
|
166
|
-
const p7 =
|
|
155
|
+
const p7 = path2.join(d, entry.name);
|
|
167
156
|
if (entry.isDirectory()) walk(p7);
|
|
168
157
|
else out.push(p7);
|
|
169
158
|
}
|
|
@@ -198,7 +187,7 @@ var DEFAULT_MODEL_MAP = {
|
|
|
198
187
|
}
|
|
199
188
|
};
|
|
200
189
|
function modelMapFile() {
|
|
201
|
-
return
|
|
190
|
+
return path3.join(dataDir(), "model-map.json");
|
|
202
191
|
}
|
|
203
192
|
function loadModelMap() {
|
|
204
193
|
const raw = readTextIfExists(modelMapFile());
|
|
@@ -217,22 +206,22 @@ function loadModelMap() {
|
|
|
217
206
|
}
|
|
218
207
|
function ensureModelMapFile() {
|
|
219
208
|
const file = modelMapFile();
|
|
220
|
-
if (!
|
|
209
|
+
if (!existsSync(file)) {
|
|
221
210
|
writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
|
|
222
211
|
}
|
|
223
212
|
return file;
|
|
224
213
|
}
|
|
225
214
|
|
|
226
215
|
// src/lib/detect.ts
|
|
227
|
-
import
|
|
228
|
-
import { existsSync as
|
|
216
|
+
import path4 from "path";
|
|
217
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
229
218
|
import { execFileSync } from "child_process";
|
|
230
219
|
function lookPath(cmd) {
|
|
231
220
|
const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
|
|
232
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
221
|
+
for (const dir of (process.env.PATH ?? "").split(path4.delimiter)) {
|
|
233
222
|
if (!dir) continue;
|
|
234
223
|
for (const ext of exts) {
|
|
235
|
-
const candidate =
|
|
224
|
+
const candidate = path4.join(dir, cmd + ext);
|
|
236
225
|
if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
|
|
237
226
|
}
|
|
238
227
|
}
|
|
@@ -261,50 +250,50 @@ function runDetectedBin(bin, args, timeoutMs) {
|
|
|
261
250
|
}
|
|
262
251
|
}
|
|
263
252
|
function detectOpenCode() {
|
|
264
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR ??
|
|
253
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR ?? path4.join(HOME, ".config", "opencode");
|
|
265
254
|
const binPath = lookPath("opencode");
|
|
266
255
|
return {
|
|
267
256
|
id: "opencode",
|
|
268
257
|
name: "OpenCode",
|
|
269
|
-
installed: binPath !== null ||
|
|
258
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
270
259
|
binPath,
|
|
271
260
|
configDir
|
|
272
261
|
};
|
|
273
262
|
}
|
|
274
263
|
function detectClaudeCode() {
|
|
275
|
-
const configDir =
|
|
264
|
+
const configDir = path4.join(HOME, ".claude");
|
|
276
265
|
const binPath = lookPath("claude");
|
|
277
266
|
return {
|
|
278
267
|
id: "claude-code",
|
|
279
268
|
name: "Claude Code",
|
|
280
|
-
installed: binPath !== null ||
|
|
269
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
281
270
|
binPath,
|
|
282
271
|
configDir
|
|
283
272
|
};
|
|
284
273
|
}
|
|
285
274
|
function detectCodex() {
|
|
286
|
-
const configDir = process.env.CODEX_HOME ??
|
|
275
|
+
const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
|
|
287
276
|
const binPath = lookPath("codex");
|
|
288
277
|
return {
|
|
289
278
|
id: "codex",
|
|
290
279
|
name: "Codex CLI",
|
|
291
|
-
installed: binPath !== null ||
|
|
280
|
+
installed: binPath !== null || existsSync2(configDir),
|
|
292
281
|
binPath,
|
|
293
282
|
configDir
|
|
294
283
|
};
|
|
295
284
|
}
|
|
296
285
|
function detectEngram() {
|
|
297
286
|
const fromEnv = process.env.ENGRAM_BIN;
|
|
298
|
-
if (fromEnv &&
|
|
287
|
+
if (fromEnv && existsSync2(fromEnv)) return fromEnv;
|
|
299
288
|
const onPath = lookPath("engram");
|
|
300
289
|
if (onPath) return onPath;
|
|
301
290
|
const candidates = [
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
291
|
+
path4.join(HOME, "go", "bin", "engram.exe"),
|
|
292
|
+
path4.join(HOME, "go", "bin", "engram"),
|
|
293
|
+
path4.join(HOME, ".local", "bin", "engram.exe"),
|
|
294
|
+
path4.join(HOME, ".local", "bin", "engram")
|
|
306
295
|
];
|
|
307
|
-
for (const c of candidates) if (
|
|
296
|
+
for (const c of candidates) if (existsSync2(c)) return c;
|
|
308
297
|
return null;
|
|
309
298
|
}
|
|
310
299
|
|
|
@@ -312,15 +301,19 @@ function detectEngram() {
|
|
|
312
301
|
function markers(name) {
|
|
313
302
|
return { open: `<!-- jorgex:${name} -->`, close: `<!-- /jorgex:${name} -->` };
|
|
314
303
|
}
|
|
304
|
+
function hasHealthyManagedMarkdownMarkers(existing, name) {
|
|
305
|
+
const { open, close } = markers(name);
|
|
306
|
+
const count = (marker) => existing.split(marker).length - 1;
|
|
307
|
+
const onOwnLine = (marker) => existing.split("\n").some((line) => line.trim() === marker);
|
|
308
|
+
return count(open) === 1 && count(close) === 1 && existing.indexOf(close) > existing.indexOf(open) && onOwnLine(open) && onOwnLine(close);
|
|
309
|
+
}
|
|
315
310
|
function repairOrphanMarkers(existing, name) {
|
|
316
311
|
const { open, close } = markers(name);
|
|
317
312
|
const count = (marker) => existing.split(marker).length - 1;
|
|
318
313
|
const opens = count(open);
|
|
319
314
|
const closes = count(close);
|
|
320
315
|
if (opens === 0 && closes === 0) return existing;
|
|
321
|
-
|
|
322
|
-
const healthy = opens === 1 && closes === 1 && existing.indexOf(close) > existing.indexOf(open) && onOwnLine(open) && onOwnLine(close);
|
|
323
|
-
if (healthy) return existing;
|
|
316
|
+
if (hasHealthyManagedMarkdownMarkers(existing, name)) return existing;
|
|
324
317
|
return existing.split("\n").map((line) => line.trim() === open || line.trim() === close ? null : line.split(open).join("").split(close).join("")).filter((line) => line !== null).join("\n");
|
|
325
318
|
}
|
|
326
319
|
function upsertMarkdownSection(existing, name, content) {
|
|
@@ -473,9 +466,9 @@ function readTomlSection(existing, section) {
|
|
|
473
466
|
}
|
|
474
467
|
|
|
475
468
|
// src/lib/hooks-format.ts
|
|
476
|
-
import
|
|
469
|
+
import path5 from "path";
|
|
477
470
|
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) =>
|
|
471
|
+
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
472
|
}
|
|
480
473
|
function plainHookCommands(canonical) {
|
|
481
474
|
return new Set(
|
|
@@ -522,7 +515,7 @@ function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) =>
|
|
|
522
515
|
...h.timeout !== void 0 ? { timeout: h.timeout } : {}
|
|
523
516
|
}))
|
|
524
517
|
};
|
|
525
|
-
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) =>
|
|
518
|
+
const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
|
|
526
519
|
const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
|
|
527
520
|
const index = list.findIndex((existingEntry) => {
|
|
528
521
|
const e = existingEntry;
|
|
@@ -552,6 +545,79 @@ var DESTRUCTIVE_GIT_DENY = [
|
|
|
552
545
|
];
|
|
553
546
|
var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
|
|
554
547
|
|
|
548
|
+
// src/lib/quality-capabilities.ts
|
|
549
|
+
var QUALITY_CAPABILITY_NAMESPACE = "jorgex.quality.capabilities";
|
|
550
|
+
var QUALITY_CAPABILITY_VERSION = 1;
|
|
551
|
+
var QUALITY_CAPABILITY_IDS = [
|
|
552
|
+
"policy-guidance",
|
|
553
|
+
"tool-approval",
|
|
554
|
+
"external-verification"
|
|
555
|
+
];
|
|
556
|
+
var UNAVAILABLE_REASON = "No reviewed local declaration is available";
|
|
557
|
+
var EXTERNAL_VERIFICATION_REASON = "External verification is available only through the external verifier";
|
|
558
|
+
function hasText(value) {
|
|
559
|
+
return typeof value === "string" && value.trim() !== "";
|
|
560
|
+
}
|
|
561
|
+
function isRuntime(value) {
|
|
562
|
+
return value === "claude-code" || value === "codex" || value === "opencode" || value === "pi";
|
|
563
|
+
}
|
|
564
|
+
function hasValidEvidence(value) {
|
|
565
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
566
|
+
const evidence = value;
|
|
567
|
+
return hasText(evidence.source) && hasText(evidence.version);
|
|
568
|
+
}
|
|
569
|
+
function unavailableCapability(id, reason = UNAVAILABLE_REASON) {
|
|
570
|
+
return { id, state: "unavailable", reason };
|
|
571
|
+
}
|
|
572
|
+
function declarationFor(id, declarations, runtime) {
|
|
573
|
+
if (runtime === "unknown") {
|
|
574
|
+
return unavailableCapability(id, "Runtime is unknown; local capability cannot be established");
|
|
575
|
+
}
|
|
576
|
+
if (id === "external-verification") {
|
|
577
|
+
return unavailableCapability(id, EXTERNAL_VERIFICATION_REASON);
|
|
578
|
+
}
|
|
579
|
+
const matches = declarations.filter((declaration2) => declaration2?.id === id);
|
|
580
|
+
if (matches.length === 0) return unavailableCapability(id);
|
|
581
|
+
if (matches.length !== 1) return unavailableCapability(id, "Duplicate capability declarations are ambiguous");
|
|
582
|
+
const declaration = matches[0];
|
|
583
|
+
const state = declaration.state;
|
|
584
|
+
const reason = hasText(declaration.reason) ? declaration.reason.trim() : void 0;
|
|
585
|
+
if (!reason) return unavailableCapability(id, "Capability declaration has no reason");
|
|
586
|
+
if (state !== "prompt-only" && state !== "manual" && state !== "unavailable") {
|
|
587
|
+
return unavailableCapability(id, "Capability state is not a supported local state");
|
|
588
|
+
}
|
|
589
|
+
if (state === "unavailable") return { id, state, reason };
|
|
590
|
+
if (!hasValidEvidence(declaration.evidence)) {
|
|
591
|
+
return unavailableCapability(id, "Capability declaration lacks reviewed source/version evidence");
|
|
592
|
+
}
|
|
593
|
+
const evidence = declaration.evidence;
|
|
594
|
+
return {
|
|
595
|
+
id,
|
|
596
|
+
state,
|
|
597
|
+
reason,
|
|
598
|
+
evidence: {
|
|
599
|
+
source: evidence.source.trim(),
|
|
600
|
+
version: evidence.version.trim()
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function hasManagedMarkdownSection(content, name) {
|
|
605
|
+
return content !== null && hasHealthyManagedMarkdownMarkers(content, name);
|
|
606
|
+
}
|
|
607
|
+
function createLocalCapabilityReport(runtime, declarations) {
|
|
608
|
+
const normalizedRuntime = isRuntime(runtime) ? runtime : "unknown";
|
|
609
|
+
return {
|
|
610
|
+
namespace: QUALITY_CAPABILITY_NAMESPACE,
|
|
611
|
+
version: QUALITY_CAPABILITY_VERSION,
|
|
612
|
+
runtime: normalizedRuntime,
|
|
613
|
+
capabilities: QUALITY_CAPABILITY_IDS.map((id) => declarationFor(id, declarations, normalizedRuntime))
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function localQualityStatus(profile, status) {
|
|
617
|
+
if ((profile === "high" || profile === "release") && status === "pass") return "incomplete";
|
|
618
|
+
return status;
|
|
619
|
+
}
|
|
620
|
+
|
|
555
621
|
// src/adapters/opencode.ts
|
|
556
622
|
function yamlString(value) {
|
|
557
623
|
return JSON.stringify(value);
|
|
@@ -592,26 +658,55 @@ function pruneEmpty(parent, key) {
|
|
|
592
658
|
const value = objectValue(parent[key]);
|
|
593
659
|
if (value !== null && Object.keys(value).length === 0) delete parent[key];
|
|
594
660
|
}
|
|
661
|
+
function hasOpenCodeManualApproval(configDir) {
|
|
662
|
+
const content = readTextIfExists(path6.join(configDir, "opencode.json"));
|
|
663
|
+
if (content === null) return false;
|
|
664
|
+
try {
|
|
665
|
+
const root = JSON.parse(content);
|
|
666
|
+
const permission = objectValue(objectValue(root)?.permission);
|
|
667
|
+
const expected = loadCanonicalDefaults(stackRoot())["opencode"]?.["permission"];
|
|
668
|
+
return permission !== null && expected !== void 0 && isDeepStrictEqual(permission, expected);
|
|
669
|
+
} catch {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
595
673
|
var opencodeAdapter = {
|
|
596
674
|
id: "opencode",
|
|
597
675
|
name: "OpenCode",
|
|
598
676
|
detect: detectOpenCode,
|
|
677
|
+
reportCapabilities(configDir) {
|
|
678
|
+
const prompt = readTextIfExists(path6.join(configDir, "AGENTS.md"));
|
|
679
|
+
return createLocalCapabilityReport("opencode", [
|
|
680
|
+
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
681
|
+
id: "policy-guidance",
|
|
682
|
+
state: "prompt-only",
|
|
683
|
+
reason: "The managed policy prompt is advisory and cannot enforce the policy",
|
|
684
|
+
evidence: { source: "jorgex-stack-system-prompt", version: "1" }
|
|
685
|
+
}] : [],
|
|
686
|
+
...hasOpenCodeManualApproval(configDir) ? [{
|
|
687
|
+
id: "tool-approval",
|
|
688
|
+
state: "manual",
|
|
689
|
+
reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
|
|
690
|
+
evidence: { source: "jorgex-stack-opencode-approval-policy", version: "1" }
|
|
691
|
+
}] : []
|
|
692
|
+
]);
|
|
693
|
+
},
|
|
599
694
|
injectEngramProtocol() {
|
|
600
695
|
return false;
|
|
601
696
|
},
|
|
602
697
|
paths(configDir) {
|
|
603
698
|
const isRealConfigDir = samePath(
|
|
604
699
|
configDir,
|
|
605
|
-
process.env.OPENCODE_CONFIG_DIR ??
|
|
700
|
+
process.env.OPENCODE_CONFIG_DIR ?? path6.join(HOME, ".config", "opencode")
|
|
606
701
|
);
|
|
607
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
702
|
+
const agentsHome = isRealConfigDir ? HOME : path6.dirname(configDir);
|
|
608
703
|
return {
|
|
609
|
-
systemPromptFile:
|
|
610
|
-
agentsDir:
|
|
611
|
-
skillsDir:
|
|
612
|
-
commandsDir:
|
|
613
|
-
pluginsDir:
|
|
614
|
-
scriptsDir:
|
|
704
|
+
systemPromptFile: path6.join(configDir, "AGENTS.md"),
|
|
705
|
+
agentsDir: path6.join(configDir, "agents"),
|
|
706
|
+
skillsDir: path6.join(agentsHome, ".agents", "skills"),
|
|
707
|
+
commandsDir: path6.join(configDir, "commands"),
|
|
708
|
+
pluginsDir: path6.join(configDir, "plugins"),
|
|
709
|
+
scriptsDir: path6.join(configDir, "scripts"),
|
|
615
710
|
outputStylesDir: null,
|
|
616
711
|
profilesDir: null
|
|
617
712
|
};
|
|
@@ -669,12 +764,12 @@ ${agent.body}`,
|
|
|
669
764
|
ctx.warnings.push(`opencode: hook sin {{SCRIPTS_DIR}} no traducible: ${hook.command}`);
|
|
670
765
|
continue;
|
|
671
766
|
}
|
|
672
|
-
const script = `scripts/${
|
|
767
|
+
const script = `scripts/${path6.basename(match[1])}`;
|
|
673
768
|
(bashEntries[includes] ??= []).push(script);
|
|
674
769
|
}
|
|
675
770
|
}
|
|
676
771
|
}
|
|
677
|
-
const hooksFile =
|
|
772
|
+
const hooksFile = path6.join(ctx.configDir, "hooks.json");
|
|
678
773
|
const content = upsertJson(readTextIfExists(hooksFile), (root) => {
|
|
679
774
|
const afterValue = root["tool.execute.after"] ??= {};
|
|
680
775
|
if (afterValue === null || typeof afterValue !== "object" || Array.isArray(afterValue)) {
|
|
@@ -709,16 +804,16 @@ ${agent.body}`,
|
|
|
709
804
|
}
|
|
710
805
|
});
|
|
711
806
|
actions.push({ kind: "write", target: hooksFile, content });
|
|
712
|
-
const scriptsSource =
|
|
807
|
+
const scriptsSource = path6.join(ctx.stackDir, "scripts");
|
|
713
808
|
if (fs3.existsSync(scriptsSource)) {
|
|
714
809
|
for (const f of fs3.readdirSync(scriptsSource)) {
|
|
715
|
-
actions.push({ kind: "copy", source:
|
|
810
|
+
actions.push({ kind: "copy", source: path6.join(scriptsSource, f), target: path6.join(scriptsDir, f) });
|
|
716
811
|
}
|
|
717
812
|
}
|
|
718
813
|
return actions;
|
|
719
814
|
},
|
|
720
815
|
planMainConfig(canonical, ctx) {
|
|
721
|
-
const file =
|
|
816
|
+
const file = path6.join(ctx.configDir, "opencode.json");
|
|
722
817
|
const { pluginsDir } = this.paths(ctx.configDir);
|
|
723
818
|
const original = readTextIfExists(file);
|
|
724
819
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
@@ -830,7 +925,7 @@ ${agent.body}`,
|
|
|
830
925
|
content = removeMarkdownSection(content, "browser");
|
|
831
926
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
832
927
|
}
|
|
833
|
-
const configFile =
|
|
928
|
+
const configFile = path6.join(ctx.configDir, "opencode.json");
|
|
834
929
|
const config = readTextIfExists(configFile);
|
|
835
930
|
if (config !== null) {
|
|
836
931
|
const mcpOwnership = [];
|
|
@@ -898,7 +993,7 @@ ${agent.body}`,
|
|
|
898
993
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
899
994
|
});
|
|
900
995
|
}
|
|
901
|
-
const hooksFile =
|
|
996
|
+
const hooksFile = path6.join(ctx.configDir, "hooks.json");
|
|
902
997
|
const hooksJson = readTextIfExists(hooksFile);
|
|
903
998
|
if (hooksJson !== null) {
|
|
904
999
|
const ourScripts = hookScriptNames(hooks);
|
|
@@ -924,8 +1019,9 @@ ${agent.body}`,
|
|
|
924
1019
|
};
|
|
925
1020
|
|
|
926
1021
|
// src/adapters/claude-code.ts
|
|
927
|
-
import
|
|
1022
|
+
import path7 from "path";
|
|
928
1023
|
import fs4 from "fs";
|
|
1024
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
929
1025
|
function yamlString2(value) {
|
|
930
1026
|
return JSON.stringify(value);
|
|
931
1027
|
}
|
|
@@ -936,6 +1032,21 @@ var ENGRAM_AGENT_TOOLS = [
|
|
|
936
1032
|
"Skill",
|
|
937
1033
|
...memoryTools(["mem_context", "mem_search", "mem_get_observation", "mem_timeline", "mem_current_project"])
|
|
938
1034
|
];
|
|
1035
|
+
function isRecord(value) {
|
|
1036
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1037
|
+
}
|
|
1038
|
+
function hasClaudeManualApproval(configDir) {
|
|
1039
|
+
const content = readTextIfExists(path7.join(configDir, "settings.json"));
|
|
1040
|
+
if (content === null) return false;
|
|
1041
|
+
try {
|
|
1042
|
+
const root = JSON.parse(content);
|
|
1043
|
+
const permissions = isRecord(root) ? root.permissions : void 0;
|
|
1044
|
+
const expected = loadCanonicalDefaults(stackRoot())["claude-code"]?.["permissions"];
|
|
1045
|
+
return isRecord(permissions) && expected !== void 0 && isDeepStrictEqual2(permissions, expected);
|
|
1046
|
+
} catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
939
1050
|
function toolsFor(agent) {
|
|
940
1051
|
if (agent.name === "engram") return ENGRAM_AGENT_TOOLS.join(", ");
|
|
941
1052
|
if (!agent.readonly) return null;
|
|
@@ -945,8 +1056,8 @@ function toolsFor(agent) {
|
|
|
945
1056
|
return tools.join(", ");
|
|
946
1057
|
}
|
|
947
1058
|
function hasEngramPlugin(configDir) {
|
|
948
|
-
if (fs4.existsSync(
|
|
949
|
-
const registry = readTextIfExists(
|
|
1059
|
+
if (fs4.existsSync(path7.join(configDir, "plugins", "marketplaces", "engram"))) return true;
|
|
1060
|
+
const registry = readTextIfExists(path7.join(configDir, "plugins", "installed_plugins.json"));
|
|
950
1061
|
if (registry === null) return false;
|
|
951
1062
|
try {
|
|
952
1063
|
const parsed = JSON.parse(registry);
|
|
@@ -968,18 +1079,35 @@ var claudeCodeAdapter = {
|
|
|
968
1079
|
id: "claude-code",
|
|
969
1080
|
name: "Claude Code",
|
|
970
1081
|
detect: detectClaudeCode,
|
|
1082
|
+
reportCapabilities(configDir) {
|
|
1083
|
+
const prompt = readTextIfExists(path7.join(configDir, "CLAUDE.md"));
|
|
1084
|
+
return createLocalCapabilityReport("claude-code", [
|
|
1085
|
+
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
1086
|
+
id: "policy-guidance",
|
|
1087
|
+
state: "prompt-only",
|
|
1088
|
+
reason: "The managed policy prompt is advisory and cannot enforce the policy",
|
|
1089
|
+
evidence: { source: "jorgex-stack-system-prompt", version: "1" }
|
|
1090
|
+
}] : [],
|
|
1091
|
+
...hasClaudeManualApproval(configDir) ? [{
|
|
1092
|
+
id: "tool-approval",
|
|
1093
|
+
state: "manual",
|
|
1094
|
+
reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
|
|
1095
|
+
evidence: { source: "jorgex-stack-claude-approval-policy", version: "1" }
|
|
1096
|
+
}] : []
|
|
1097
|
+
]);
|
|
1098
|
+
},
|
|
971
1099
|
injectEngramProtocol(ctx) {
|
|
972
1100
|
return !hasEngramPlugin(ctx.configDir);
|
|
973
1101
|
},
|
|
974
1102
|
paths(configDir) {
|
|
975
1103
|
return {
|
|
976
|
-
systemPromptFile:
|
|
977
|
-
agentsDir:
|
|
978
|
-
skillsDir:
|
|
979
|
-
commandsDir:
|
|
1104
|
+
systemPromptFile: path7.join(configDir, "CLAUDE.md"),
|
|
1105
|
+
agentsDir: path7.join(configDir, "agents"),
|
|
1106
|
+
skillsDir: path7.join(configDir, "skills"),
|
|
1107
|
+
commandsDir: path7.join(configDir, "commands"),
|
|
980
1108
|
pluginsDir: null,
|
|
981
|
-
scriptsDir:
|
|
982
|
-
outputStylesDir:
|
|
1109
|
+
scriptsDir: path7.join(configDir, "scripts"),
|
|
1110
|
+
outputStylesDir: path7.join(configDir, "output-styles"),
|
|
983
1111
|
profilesDir: null
|
|
984
1112
|
};
|
|
985
1113
|
},
|
|
@@ -1025,9 +1153,9 @@ ${agent.body}`,
|
|
|
1025
1153
|
planHooks(canonical, ctx) {
|
|
1026
1154
|
const actions = [];
|
|
1027
1155
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1028
|
-
const original = readTextIfExists(
|
|
1156
|
+
const original = readTextIfExists(path7.join(ctx.configDir, "settings.json"));
|
|
1029
1157
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1030
|
-
const settingsFile =
|
|
1158
|
+
const settingsFile = path7.join(ctx.configDir, "settings.json");
|
|
1031
1159
|
let content = upsertNativeHooks(contentSource, canonical, scriptsDir);
|
|
1032
1160
|
const defaults = loadCanonicalDefaults(ctx.stackDir)["claude-code"];
|
|
1033
1161
|
if (contentSource === null && defaults?.["permissions"] !== void 0) {
|
|
@@ -1041,16 +1169,16 @@ ${agent.body}`,
|
|
|
1041
1169
|
});
|
|
1042
1170
|
}
|
|
1043
1171
|
actions.push({ kind: "write", target: settingsFile, content });
|
|
1044
|
-
const scriptsSource =
|
|
1172
|
+
const scriptsSource = path7.join(ctx.stackDir, "scripts");
|
|
1045
1173
|
if (fs4.existsSync(scriptsSource)) {
|
|
1046
1174
|
for (const f of fs4.readdirSync(scriptsSource)) {
|
|
1047
|
-
actions.push({ kind: "copy", source:
|
|
1175
|
+
actions.push({ kind: "copy", source: path7.join(scriptsSource, f), target: path7.join(scriptsDir, f) });
|
|
1048
1176
|
}
|
|
1049
1177
|
}
|
|
1050
1178
|
return actions;
|
|
1051
1179
|
},
|
|
1052
1180
|
planMainConfig(canonical, ctx) {
|
|
1053
|
-
const file =
|
|
1181
|
+
const file = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
|
|
1054
1182
|
const mcpOwnership = [];
|
|
1055
1183
|
const content = upsertJson(readTextIfExists(file), (root) => {
|
|
1056
1184
|
const servers = root["mcpServers"] ??= {};
|
|
@@ -1115,7 +1243,7 @@ ${agent.body}`,
|
|
|
1115
1243
|
content = removeMarkdownSection(content, "browser");
|
|
1116
1244
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1117
1245
|
}
|
|
1118
|
-
const settingsFile =
|
|
1246
|
+
const settingsFile = path7.join(ctx.configDir, "settings.json");
|
|
1119
1247
|
const settings = readTextIfExists(settingsFile);
|
|
1120
1248
|
if (settings !== null) {
|
|
1121
1249
|
const content = removeNativeHooks(settings, hooks);
|
|
@@ -1123,7 +1251,7 @@ ${agent.body}`,
|
|
|
1123
1251
|
actions.push({ kind: "write", target: settingsFile, content: content.trim() === "{}" ? "" : content });
|
|
1124
1252
|
}
|
|
1125
1253
|
}
|
|
1126
|
-
const mainFile =
|
|
1254
|
+
const mainFile = path7.join(path7.dirname(ctx.configDir), `${path7.basename(ctx.configDir)}.json`);
|
|
1127
1255
|
const main2 = readTextIfExists(mainFile);
|
|
1128
1256
|
if (main2 !== null) {
|
|
1129
1257
|
const mcpOwnership = [];
|
|
@@ -1149,7 +1277,7 @@ ${agent.body}`,
|
|
|
1149
1277
|
};
|
|
1150
1278
|
|
|
1151
1279
|
// src/adapters/codex.ts
|
|
1152
|
-
import
|
|
1280
|
+
import path8 from "path";
|
|
1153
1281
|
import fs5 from "fs";
|
|
1154
1282
|
function tomlString(value) {
|
|
1155
1283
|
return JSON.stringify(value);
|
|
@@ -1173,37 +1301,194 @@ function isManagedOptionalStdioServer3(server, section) {
|
|
|
1173
1301
|
return server.optional === true && server.transport === "stdio" && section?.trim() === stdioMcpSection(server);
|
|
1174
1302
|
}
|
|
1175
1303
|
function hasActiveEngramPlugin(configDir) {
|
|
1176
|
-
const config = readTextIfExists(
|
|
1304
|
+
const config = readTextIfExists(path8.join(configDir, "config.toml"));
|
|
1177
1305
|
if (config === null) return false;
|
|
1178
1306
|
const match = /\[plugins\."engram@[^"]*"\]([^[]*)/.exec(config);
|
|
1179
1307
|
return match !== null && !/enabled\s*=\s*false/.test(match[1]);
|
|
1180
1308
|
}
|
|
1181
1309
|
function hasEngramProtocol(configDir) {
|
|
1182
1310
|
if (hasActiveEngramPlugin(configDir)) return true;
|
|
1183
|
-
if (fs5.existsSync(
|
|
1184
|
-
const config = readTextIfExists(
|
|
1311
|
+
if (fs5.existsSync(path8.join(configDir, "engram-instructions.md"))) return true;
|
|
1312
|
+
const config = readTextIfExists(path8.join(configDir, "config.toml"));
|
|
1185
1313
|
return config !== null && /engram-instructions\.md/.test(config);
|
|
1186
1314
|
}
|
|
1315
|
+
var CODEX_JSON_STRING = String.raw`"(?:\\.|[^"\\\r\n])*"`;
|
|
1316
|
+
var CODEX_JSON_VALUE = String.raw`(?:${CODEX_JSON_STRING}|-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|true|false)`;
|
|
1317
|
+
var CODEX_JSON_STRING_ARRAY = String.raw`\[(?:\s*${CODEX_JSON_STRING}(?:\s*,\s*${CODEX_JSON_STRING})*\s*)?\]`;
|
|
1318
|
+
var CODEX_KEY = String.raw`(?:[A-Za-z0-9_-]+|${CODEX_JSON_STRING})`;
|
|
1319
|
+
var CODEX_ASSIGNMENT = new RegExp(String.raw`^\s*(${CODEX_KEY})\s*=\s*(${CODEX_JSON_STRING_ARRAY}|${CODEX_JSON_VALUE})\s*(?:#.*)?$`);
|
|
1320
|
+
var CODEX_HEADER = new RegExp(String.raw`^\[\s*(${CODEX_KEY}(?:\s*\.\s*${CODEX_KEY})*)\s*\]\s*(?:#.*)?$`);
|
|
1321
|
+
var CODEX_PERMISSION_HEADERS = {
|
|
1322
|
+
base: "permissions.jorgex-read-anywhere",
|
|
1323
|
+
filesystem: "permissions.jorgex-read-anywhere.filesystem",
|
|
1324
|
+
workspaceRoots: 'permissions.jorgex-read-anywhere.filesystem.":workspace_roots"'
|
|
1325
|
+
};
|
|
1326
|
+
var CODEX_PERMISSION_PROFILE = {
|
|
1327
|
+
base: [["extends", ":workspace"]],
|
|
1328
|
+
filesystem: [
|
|
1329
|
+
[":root", "read"],
|
|
1330
|
+
["*.env", "deny"],
|
|
1331
|
+
["*.env.*", "deny"],
|
|
1332
|
+
["~/.ssh/**", "deny"],
|
|
1333
|
+
["~/.aws/credentials", "deny"],
|
|
1334
|
+
["~/.npmrc", "deny"],
|
|
1335
|
+
["~/.git-credentials", "deny"],
|
|
1336
|
+
["**/id_rsa", "deny"],
|
|
1337
|
+
["**/id_ed25519", "deny"],
|
|
1338
|
+
["**/*.pem", "deny"],
|
|
1339
|
+
["**/*.key", "deny"]
|
|
1340
|
+
],
|
|
1341
|
+
workspaceRoots: [
|
|
1342
|
+
[".", "write"],
|
|
1343
|
+
["*.env", "deny"],
|
|
1344
|
+
["*.env.*", "deny"],
|
|
1345
|
+
[".ssh/**", "deny"],
|
|
1346
|
+
[".aws/credentials", "deny"],
|
|
1347
|
+
[".npmrc", "deny"],
|
|
1348
|
+
[".git-credentials", "deny"],
|
|
1349
|
+
["**/id_rsa", "deny"],
|
|
1350
|
+
["**/id_ed25519", "deny"],
|
|
1351
|
+
["**/*.pem", "deny"],
|
|
1352
|
+
["**/*.key", "deny"]
|
|
1353
|
+
]
|
|
1354
|
+
};
|
|
1355
|
+
var CODEX_PERMISSION_SECTIONS = [
|
|
1356
|
+
{ header: CODEX_PERMISSION_HEADERS.base, entries: CODEX_PERMISSION_PROFILE.base, quoteKeys: false },
|
|
1357
|
+
{ header: CODEX_PERMISSION_HEADERS.filesystem, entries: CODEX_PERMISSION_PROFILE.filesystem, quoteKeys: true },
|
|
1358
|
+
{ header: CODEX_PERMISSION_HEADERS.workspaceRoots, entries: CODEX_PERMISSION_PROFILE.workspaceRoots, quoteKeys: true }
|
|
1359
|
+
];
|
|
1360
|
+
function renderCodexPermissionEntries(entries, quoteKeys) {
|
|
1361
|
+
return entries.map(([key, value]) => `${quoteKeys ? JSON.stringify(key) : key} = ${JSON.stringify(value)}`).join("\n");
|
|
1362
|
+
}
|
|
1363
|
+
function parseCodexKey(raw) {
|
|
1364
|
+
if (!raw.startsWith('"')) return raw;
|
|
1365
|
+
try {
|
|
1366
|
+
const value = JSON.parse(raw);
|
|
1367
|
+
return typeof value === "string" ? value : null;
|
|
1368
|
+
} catch {
|
|
1369
|
+
return null;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
function parseCodexValue(raw) {
|
|
1373
|
+
try {
|
|
1374
|
+
const value = JSON.parse(raw);
|
|
1375
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") || !Array.isArray(value) && (value === null || typeof value !== "object") ? value : void 0;
|
|
1376
|
+
} catch {
|
|
1377
|
+
return void 0;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function parseCodexHeader(line) {
|
|
1381
|
+
const match = CODEX_HEADER.exec(line.trim());
|
|
1382
|
+
if (match === null) return null;
|
|
1383
|
+
for (const quoted of match[1].match(/"(?:\\.|[^"\\\r\n])*"/g) ?? []) {
|
|
1384
|
+
if (parseCodexKey(quoted) === null) return null;
|
|
1385
|
+
}
|
|
1386
|
+
return match[1].replace(/\s*\.\s*/g, ".");
|
|
1387
|
+
}
|
|
1388
|
+
function scanCodexToml(config) {
|
|
1389
|
+
if (config.includes("'''") || config.includes('"""')) return null;
|
|
1390
|
+
const root = /* @__PURE__ */ new Map();
|
|
1391
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1392
|
+
const headers = /* @__PURE__ */ new Set();
|
|
1393
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1394
|
+
let section = null;
|
|
1395
|
+
for (const line of config.replace(/\r\n/g, "\n").split("\n")) {
|
|
1396
|
+
const trimmed = line.trim();
|
|
1397
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
1398
|
+
if (trimmed.startsWith("[")) {
|
|
1399
|
+
const header = parseCodexHeader(trimmed);
|
|
1400
|
+
if (header === null || headers.has(header)) return null;
|
|
1401
|
+
headers.add(header);
|
|
1402
|
+
if (header.startsWith(`${CODEX_PERMISSION_HEADERS.base}.`) && !CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) {
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
section = header;
|
|
1406
|
+
if (CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === header)) sections.set(header, /* @__PURE__ */ new Map());
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
const relevant = section === null || CODEX_PERMISSION_SECTIONS.some((entry) => entry.header === section);
|
|
1410
|
+
const match = CODEX_ASSIGNMENT.exec(trimmed);
|
|
1411
|
+
if (!relevant && match === null) continue;
|
|
1412
|
+
if (match === null) return null;
|
|
1413
|
+
const key = parseCodexKey(match[1]);
|
|
1414
|
+
const value = parseCodexValue(match[2]);
|
|
1415
|
+
if (key === null || value === void 0) return null;
|
|
1416
|
+
if (section === null && (key === "profile" || key === "sandbox_mode")) return null;
|
|
1417
|
+
const scope = section ?? "<root>";
|
|
1418
|
+
const declaration = `${scope}\0${key}`;
|
|
1419
|
+
if (keys.has(declaration)) return null;
|
|
1420
|
+
keys.add(declaration);
|
|
1421
|
+
if (section === null) root.set(key, value);
|
|
1422
|
+
else if (sections.has(section)) sections.get(section).set(key, value);
|
|
1423
|
+
}
|
|
1424
|
+
return { root, sections };
|
|
1425
|
+
}
|
|
1426
|
+
function hasCanonicalCodexPermissionProfile(scan) {
|
|
1427
|
+
for (const { header, entries } of CODEX_PERMISSION_SECTIONS) {
|
|
1428
|
+
const actual = scan.sections.get(header);
|
|
1429
|
+
if (actual === void 0 || actual.size !== entries.length) return false;
|
|
1430
|
+
for (const [key, expected] of entries) {
|
|
1431
|
+
if (actual.get(key) !== expected) return false;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
function hasCodexManualApproval(configDir) {
|
|
1437
|
+
const config = readTextIfExists(path8.join(configDir, "config.toml"));
|
|
1438
|
+
if (config === null) return false;
|
|
1439
|
+
const scan = scanCodexToml(config);
|
|
1440
|
+
if (scan === null) return false;
|
|
1441
|
+
try {
|
|
1442
|
+
const defaults = loadCanonicalDefaults(stackRoot())["codex"];
|
|
1443
|
+
const expectedApproval = defaults?.["approval_policy"];
|
|
1444
|
+
const expectedPermissions = defaults?.["default_permissions"];
|
|
1445
|
+
return typeof expectedApproval === "string" && typeof expectedPermissions === "string" && scan.root.get("approval_policy") === expectedApproval && scan.root.get("default_permissions") === expectedPermissions && hasCanonicalCodexPermissionProfile(scan);
|
|
1446
|
+
} catch {
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1187
1450
|
var codexAdapter = {
|
|
1188
1451
|
id: "codex",
|
|
1189
1452
|
name: "Codex CLI",
|
|
1190
1453
|
detect: detectCodex,
|
|
1454
|
+
reportCapabilities(configDir) {
|
|
1455
|
+
const prompt = readTextIfExists(path8.join(configDir, "AGENTS.md"));
|
|
1456
|
+
return createLocalCapabilityReport("codex", [
|
|
1457
|
+
...hasManagedMarkdownSection(prompt, "system-prompt") ? [{
|
|
1458
|
+
id: "policy-guidance",
|
|
1459
|
+
state: "prompt-only",
|
|
1460
|
+
reason: "The managed policy prompt is advisory and cannot enforce the policy",
|
|
1461
|
+
evidence: { source: "jorgex-stack-system-prompt", version: "1" }
|
|
1462
|
+
}] : [],
|
|
1463
|
+
...hasCodexManualApproval(configDir) ? [{
|
|
1464
|
+
id: "tool-approval",
|
|
1465
|
+
state: "manual",
|
|
1466
|
+
reason: "Canonical approval declarations require a human decision; runtime activation is not certified",
|
|
1467
|
+
evidence: { source: "jorgex-stack-codex-approval-policy", version: "1" }
|
|
1468
|
+
}] : [],
|
|
1469
|
+
{
|
|
1470
|
+
id: "external-verification",
|
|
1471
|
+
state: "unavailable",
|
|
1472
|
+
reason: "External verification is available only through the external verifier"
|
|
1473
|
+
}
|
|
1474
|
+
]);
|
|
1475
|
+
},
|
|
1191
1476
|
injectEngramProtocol(ctx) {
|
|
1192
1477
|
return !hasEngramProtocol(ctx.configDir);
|
|
1193
1478
|
},
|
|
1194
1479
|
paths(configDir) {
|
|
1195
|
-
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ??
|
|
1196
|
-
const agentsHome = isRealConfigDir ? HOME :
|
|
1197
|
-
const skillsDir =
|
|
1480
|
+
const isRealConfigDir = samePath(configDir, process.env.CODEX_HOME ?? path8.join(HOME, ".codex"));
|
|
1481
|
+
const agentsHome = isRealConfigDir ? HOME : path8.dirname(configDir);
|
|
1482
|
+
const skillsDir = path8.join(agentsHome, ".agents", "skills");
|
|
1198
1483
|
return {
|
|
1199
|
-
systemPromptFile:
|
|
1200
|
-
agentsDir:
|
|
1484
|
+
systemPromptFile: path8.join(configDir, "AGENTS.md"),
|
|
1485
|
+
agentsDir: path8.join(configDir, "agents"),
|
|
1201
1486
|
skillsDir,
|
|
1202
1487
|
// Los custom prompts de Codex están deprecados: los commands se
|
|
1203
1488
|
// instalan como skills (renderCommand produce <nombre>/SKILL.md).
|
|
1204
1489
|
commandsDir: skillsDir,
|
|
1205
1490
|
pluginsDir: null,
|
|
1206
|
-
scriptsDir:
|
|
1491
|
+
scriptsDir: path8.join(configDir, "scripts"),
|
|
1207
1492
|
outputStylesDir: null,
|
|
1208
1493
|
profilesDir: configDir
|
|
1209
1494
|
};
|
|
@@ -1251,7 +1536,7 @@ ${body}`
|
|
|
1251
1536
|
planHooks(canonical, ctx) {
|
|
1252
1537
|
const actions = [];
|
|
1253
1538
|
const { scriptsDir } = this.paths(ctx.configDir);
|
|
1254
|
-
const hooksFile =
|
|
1539
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
1255
1540
|
const content = upsertNativeHooks(
|
|
1256
1541
|
readTextIfExists(hooksFile),
|
|
1257
1542
|
canonical,
|
|
@@ -1262,16 +1547,16 @@ ${body}`
|
|
|
1262
1547
|
ctx.warnings.push(
|
|
1263
1548
|
"Codex: los hooks no-managed requieren aprobaci\xF3n manual \u2014 ejecuta /hooks dentro de codex para activarlos."
|
|
1264
1549
|
);
|
|
1265
|
-
const scriptsSource =
|
|
1550
|
+
const scriptsSource = path8.join(ctx.stackDir, "scripts");
|
|
1266
1551
|
if (fs5.existsSync(scriptsSource)) {
|
|
1267
1552
|
for (const f of fs5.readdirSync(scriptsSource)) {
|
|
1268
|
-
actions.push({ kind: "copy", source:
|
|
1553
|
+
actions.push({ kind: "copy", source: path8.join(scriptsSource, f), target: path8.join(scriptsDir, f) });
|
|
1269
1554
|
}
|
|
1270
1555
|
}
|
|
1271
1556
|
return actions;
|
|
1272
1557
|
},
|
|
1273
1558
|
planMainConfig(canonical, ctx) {
|
|
1274
|
-
const file =
|
|
1559
|
+
const file = path8.join(ctx.configDir, "config.toml");
|
|
1275
1560
|
const original = readTextIfExists(file);
|
|
1276
1561
|
const contentSource = original === null || original.trim() === "" ? null : original;
|
|
1277
1562
|
let content = contentSource;
|
|
@@ -1287,37 +1572,20 @@ ${body}`
|
|
|
1287
1572
|
ctx.warnings.push(
|
|
1288
1573
|
"Codex: fresh config enables read-anywhere via the jorgex-read-anywhere permission profile; broad local reads can expose secrets not covered by deny rules."
|
|
1289
1574
|
);
|
|
1290
|
-
content = upsertTomlSection(content, "permissions.jorgex-read-anywhere", 'extends = ":workspace"');
|
|
1291
1575
|
content = upsertTomlSection(
|
|
1292
1576
|
content,
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
'"~/.npmrc" = "deny"',
|
|
1301
|
-
'"~/.git-credentials" = "deny"',
|
|
1302
|
-
'"**/id_rsa" = "deny"',
|
|
1303
|
-
'"**/id_ed25519" = "deny"',
|
|
1304
|
-
'"**/*.pem" = "deny"',
|
|
1305
|
-
'"**/*.key" = "deny"'
|
|
1306
|
-
].join("\n")
|
|
1577
|
+
CODEX_PERMISSION_HEADERS.base,
|
|
1578
|
+
renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.base, false)
|
|
1579
|
+
);
|
|
1580
|
+
content = upsertTomlSection(
|
|
1581
|
+
content,
|
|
1582
|
+
CODEX_PERMISSION_HEADERS.filesystem,
|
|
1583
|
+
renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.filesystem, true)
|
|
1307
1584
|
);
|
|
1308
1585
|
content += [
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
'"*.env.*" = "deny"',
|
|
1313
|
-
'".ssh/**" = "deny"',
|
|
1314
|
-
'".aws/credentials" = "deny"',
|
|
1315
|
-
'".npmrc" = "deny"',
|
|
1316
|
-
'".git-credentials" = "deny"',
|
|
1317
|
-
'"**/id_rsa" = "deny"',
|
|
1318
|
-
'"**/id_ed25519" = "deny"',
|
|
1319
|
-
'"**/*.pem" = "deny"',
|
|
1320
|
-
'"**/*.key" = "deny"',
|
|
1586
|
+
`
|
|
1587
|
+
[${CODEX_PERMISSION_HEADERS.workspaceRoots}]`,
|
|
1588
|
+
renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.workspaceRoots, true),
|
|
1321
1589
|
""
|
|
1322
1590
|
].join("\n");
|
|
1323
1591
|
}
|
|
@@ -1407,7 +1675,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1407
1675
|
content = removeMarkdownSection(content, "browser");
|
|
1408
1676
|
actions.push({ kind: "write", target: systemPromptFile, content });
|
|
1409
1677
|
}
|
|
1410
|
-
const configFile =
|
|
1678
|
+
const configFile = path8.join(ctx.configDir, "config.toml");
|
|
1411
1679
|
const config = readTextIfExists(configFile);
|
|
1412
1680
|
if (config !== null) {
|
|
1413
1681
|
let content = config;
|
|
@@ -1442,7 +1710,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1442
1710
|
...primaryModelOwnership.length > 0 ? { primaryModelOwnership } : {}
|
|
1443
1711
|
});
|
|
1444
1712
|
}
|
|
1445
|
-
const hooksFile =
|
|
1713
|
+
const hooksFile = path8.join(ctx.configDir, "hooks.json");
|
|
1446
1714
|
const hooksJson = readTextIfExists(hooksFile);
|
|
1447
1715
|
if (hooksJson !== null) {
|
|
1448
1716
|
const content = removeNativeHooks(hooksJson, hooks);
|
|
@@ -1456,7 +1724,7 @@ args = [${(server.args ?? []).map(tomlString).join(", ")}]`);
|
|
|
1456
1724
|
|
|
1457
1725
|
// src/lib/install-mode.ts
|
|
1458
1726
|
import fs6 from "fs";
|
|
1459
|
-
import
|
|
1727
|
+
import path9 from "path";
|
|
1460
1728
|
var INSTALL_MODES = /* @__PURE__ */ new Set(["human", "programmatic"]);
|
|
1461
1729
|
var SUBAGENT_CONCURRENCIES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
1462
1730
|
var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
@@ -1464,7 +1732,7 @@ var DEFAULT_INSTALL_MODE_PREFERENCE = {
|
|
|
1464
1732
|
subagentConcurrency: "serial"
|
|
1465
1733
|
};
|
|
1466
1734
|
function installModePreferenceFile() {
|
|
1467
|
-
return
|
|
1735
|
+
return path9.join(dataDir(), "install-mode.json");
|
|
1468
1736
|
}
|
|
1469
1737
|
function hasInstallModePreference(file = installModePreferenceFile()) {
|
|
1470
1738
|
return fs6.existsSync(file);
|
|
@@ -1539,11 +1807,11 @@ function parseInstallModePreferenceFlags(mode, subagentConcurrency) {
|
|
|
1539
1807
|
|
|
1540
1808
|
// src/lib/backup.ts
|
|
1541
1809
|
import fs7 from "fs";
|
|
1542
|
-
import
|
|
1810
|
+
import path10 from "path";
|
|
1543
1811
|
import crypto from "crypto";
|
|
1544
1812
|
var KEEP_BACKUPS = 10;
|
|
1545
1813
|
function backupsRoot() {
|
|
1546
|
-
return
|
|
1814
|
+
return path10.join(dataDir(), "backups");
|
|
1547
1815
|
}
|
|
1548
1816
|
function compositeChecksum(files) {
|
|
1549
1817
|
const hash = crypto.createHash("sha256");
|
|
@@ -1562,19 +1830,19 @@ function createBackup(files, label, root = backupsRoot()) {
|
|
|
1562
1830
|
if (latest?.checksum === checksum) return latest;
|
|
1563
1831
|
const base = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${label}`;
|
|
1564
1832
|
let id = base;
|
|
1565
|
-
let dir =
|
|
1833
|
+
let dir = path10.join(root, id);
|
|
1566
1834
|
for (let n = 1; fs7.existsSync(dir); n++) {
|
|
1567
1835
|
id = `${base}-${n}`;
|
|
1568
|
-
dir =
|
|
1836
|
+
dir = path10.join(root, id);
|
|
1569
1837
|
}
|
|
1570
|
-
ensureDir(
|
|
1838
|
+
ensureDir(path10.join(dir, "files"));
|
|
1571
1839
|
const entries = existing.map((original, i) => {
|
|
1572
|
-
const stored =
|
|
1840
|
+
const stored = path10.join(dir, "files", `${String(i).padStart(4, "0")}-${path10.basename(original)}`);
|
|
1573
1841
|
fs7.copyFileSync(original, stored);
|
|
1574
1842
|
return { original, stored };
|
|
1575
1843
|
});
|
|
1576
1844
|
const info = { id, label, createdAt: (/* @__PURE__ */ new Date()).toISOString(), files: entries, checksum };
|
|
1577
|
-
writeText(
|
|
1845
|
+
writeText(path10.join(dir, "manifest.json"), JSON.stringify(info, null, 2) + "\n");
|
|
1578
1846
|
pruneBackups(root);
|
|
1579
1847
|
return info;
|
|
1580
1848
|
}
|
|
@@ -1583,7 +1851,7 @@ function listBackups(root = backupsRoot()) {
|
|
|
1583
1851
|
const infos = [];
|
|
1584
1852
|
for (const entry of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
1585
1853
|
if (!entry.isDirectory()) continue;
|
|
1586
|
-
const manifest = readTextIfExists(
|
|
1854
|
+
const manifest = readTextIfExists(path10.join(root, entry.name, "manifest.json"));
|
|
1587
1855
|
if (manifest === null) continue;
|
|
1588
1856
|
try {
|
|
1589
1857
|
infos.push(JSON.parse(manifest));
|
|
@@ -1600,7 +1868,7 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
|
1600
1868
|
for (const { original, stored } of info.files) {
|
|
1601
1869
|
if (!fs7.existsSync(stored)) continue;
|
|
1602
1870
|
if (!isContainedIn(original, boundary)) continue;
|
|
1603
|
-
ensureDir(
|
|
1871
|
+
ensureDir(path10.dirname(original));
|
|
1604
1872
|
fs7.copyFileSync(stored, original);
|
|
1605
1873
|
restored++;
|
|
1606
1874
|
}
|
|
@@ -1609,15 +1877,15 @@ function restoreBackup(id, root = backupsRoot(), boundary = HOME) {
|
|
|
1609
1877
|
function pruneBackups(root) {
|
|
1610
1878
|
const all = listBackups(root);
|
|
1611
1879
|
for (const old of all.slice(KEEP_BACKUPS)) {
|
|
1612
|
-
fs7.rmSync(
|
|
1880
|
+
fs7.rmSync(path10.join(root, old.id), { recursive: true, force: true });
|
|
1613
1881
|
}
|
|
1614
1882
|
}
|
|
1615
1883
|
|
|
1616
1884
|
// src/lib/manifest.ts
|
|
1617
1885
|
import fs8 from "fs";
|
|
1618
|
-
import
|
|
1886
|
+
import path11 from "path";
|
|
1619
1887
|
function manifestFile() {
|
|
1620
|
-
return
|
|
1888
|
+
return path11.join(dataDir(), "manifest.json");
|
|
1621
1889
|
}
|
|
1622
1890
|
function readManifest(file = manifestFile()) {
|
|
1623
1891
|
const raw = readTextIfExists(file);
|
|
@@ -1641,24 +1909,24 @@ function removeRuntimeManifest(id, file = manifestFile()) {
|
|
|
1641
1909
|
writeText(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
1642
1910
|
}
|
|
1643
1911
|
function findOrphans(prevOwned, currentTargets, root = HOME) {
|
|
1644
|
-
return prevOwned.map((f) =>
|
|
1645
|
-
(f) => !currentTargets.has(f) &&
|
|
1912
|
+
return prevOwned.map((f) => path11.resolve(f)).filter(
|
|
1913
|
+
(f) => !currentTargets.has(f) && path11.basename(f) !== "engram.ts" && isContainedIn(f, root) && fs8.existsSync(f)
|
|
1646
1914
|
);
|
|
1647
1915
|
}
|
|
1648
1916
|
|
|
1649
1917
|
// src/components/system-prompt.ts
|
|
1650
|
-
import
|
|
1918
|
+
import path13 from "path";
|
|
1651
1919
|
import fs10 from "fs";
|
|
1652
1920
|
|
|
1653
1921
|
// src/lib/mode-composition.ts
|
|
1654
1922
|
import fs9 from "fs";
|
|
1655
|
-
import
|
|
1923
|
+
import path12 from "path";
|
|
1656
1924
|
var PROGRAMMATIC_ROOT = ["modes", "programmatic"];
|
|
1657
1925
|
var PROGRAMMATIC_MARKER = "<!-- jorgex:programmatic-mode -->";
|
|
1658
1926
|
var LEGACY_RESULT_CONTRACT_SECTION = /\n?##\s+Result contract[\s\S]*$/;
|
|
1659
1927
|
var normalize = (value) => value.replace(/\r\n/g, "\n");
|
|
1660
1928
|
function loadProgrammaticAddendum(stackDir, fileName) {
|
|
1661
|
-
return normalize(fs9.readFileSync(
|
|
1929
|
+
return normalize(fs9.readFileSync(path12.join(stackDir, ...PROGRAMMATIC_ROOT, fileName), "utf8")).trim();
|
|
1662
1930
|
}
|
|
1663
1931
|
function appendAddendum(base, addendum) {
|
|
1664
1932
|
const normalizedBase = normalize(base);
|
|
@@ -1701,15 +1969,15 @@ function composeProgrammaticAgentBody(stackDir, agent, mode, concurrency) {
|
|
|
1701
1969
|
var normalize2 = (s) => s.replace(/\r\n/g, "\n");
|
|
1702
1970
|
function planSystemPrompt(adapter, ctx) {
|
|
1703
1971
|
const target = adapter.paths(ctx.configDir).systemPromptFile;
|
|
1704
|
-
const agentsMd = normalize2(fs10.readFileSync(
|
|
1972
|
+
const agentsMd = normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "AGENTS.md"), "utf8"));
|
|
1705
1973
|
const protocol = stripLeadingHtmlComments(
|
|
1706
|
-
normalize2(fs10.readFileSync(
|
|
1974
|
+
normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8"))
|
|
1707
1975
|
);
|
|
1708
1976
|
const composedAgentsMd = composeProgrammaticSystemPrompt(ctx.stackDir, agentsMd, ctx.mode);
|
|
1709
1977
|
const browser = [
|
|
1710
1978
|
ctx.playwrightCliEnabled ? "browser-playwright.md" : null,
|
|
1711
1979
|
ctx.enabledMcpServers?.has(DEVTOOLS_MCP_SERVER) ? "browser-chrome-devtools.md" : null
|
|
1712
|
-
].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(
|
|
1980
|
+
].filter((file) => file !== null).map((file) => normalize2(fs10.readFileSync(path13.join(ctx.stackDir, "system-prompt", file), "utf8"))).join("\n\n");
|
|
1713
1981
|
let content = readTextIfExists(target);
|
|
1714
1982
|
content = upsertMarkdownSection(content, "system-prompt", composedAgentsMd);
|
|
1715
1983
|
if (adapter.injectEngramProtocol(ctx)) {
|
|
@@ -1722,7 +1990,7 @@ function planSystemPrompt(adapter, ctx) {
|
|
|
1722
1990
|
}
|
|
1723
1991
|
|
|
1724
1992
|
// src/components/agents.ts
|
|
1725
|
-
import
|
|
1993
|
+
import path14 from "path";
|
|
1726
1994
|
function planAgents(adapter, ctx) {
|
|
1727
1995
|
const { agentsDir, commandsDir, outputStylesDir, profilesDir, scriptsDir } = adapter.paths(ctx.configDir);
|
|
1728
1996
|
const dirFor = {
|
|
@@ -1732,7 +2000,7 @@ function planAgents(adapter, ctx) {
|
|
|
1732
2000
|
profile: profilesDir
|
|
1733
2001
|
};
|
|
1734
2002
|
const scriptsBase = scriptsDir.replace(/\\/g, "/");
|
|
1735
|
-
return loadCanonicalAgents(
|
|
2003
|
+
return loadCanonicalAgents(path14.join(ctx.stackDir, "agents")).flatMap((agent) => {
|
|
1736
2004
|
const composedAgent = {
|
|
1737
2005
|
...agent,
|
|
1738
2006
|
body: composeProgrammaticAgentBody(ctx.stackDir, agent, ctx.mode, ctx.subagentConcurrency)
|
|
@@ -1744,42 +2012,42 @@ function planAgents(adapter, ctx) {
|
|
|
1744
2012
|
return [];
|
|
1745
2013
|
}
|
|
1746
2014
|
const content = rendered.content.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsBase);
|
|
1747
|
-
return [{ kind: "write", target:
|
|
2015
|
+
return [{ kind: "write", target: path14.join(dir, rendered.file), content }];
|
|
1748
2016
|
});
|
|
1749
2017
|
});
|
|
1750
2018
|
}
|
|
1751
2019
|
|
|
1752
2020
|
// src/components/skills.ts
|
|
1753
|
-
import
|
|
2021
|
+
import path15 from "path";
|
|
1754
2022
|
function planSkills(adapter, ctx) {
|
|
1755
2023
|
const { skillsDir } = adapter.paths(ctx.configDir);
|
|
1756
|
-
const source =
|
|
2024
|
+
const source = path15.join(ctx.stackDir, "skills");
|
|
1757
2025
|
return listFilesRecursive(source).map((file) => {
|
|
1758
|
-
const relative =
|
|
1759
|
-
return { kind: "copy", source: file, target:
|
|
2026
|
+
const relative = path15.relative(source, file);
|
|
2027
|
+
return { kind: "copy", source: file, target: path15.join(skillsDir, relative) };
|
|
1760
2028
|
});
|
|
1761
2029
|
}
|
|
1762
2030
|
|
|
1763
2031
|
// src/components/commands.ts
|
|
1764
|
-
import
|
|
2032
|
+
import path16 from "path";
|
|
1765
2033
|
import fs11 from "fs";
|
|
1766
2034
|
function planCommands(adapter, ctx) {
|
|
1767
2035
|
const { commandsDir } = adapter.paths(ctx.configDir);
|
|
1768
|
-
const source =
|
|
2036
|
+
const source = path16.join(ctx.stackDir, "commands");
|
|
1769
2037
|
if (!fs11.existsSync(source)) return [];
|
|
1770
2038
|
const commandFiles = [
|
|
1771
2039
|
...listMarkdownFiles(source),
|
|
1772
|
-
...listMarkdownFiles(
|
|
2040
|
+
...listMarkdownFiles(path16.join(source, adapter.id))
|
|
1773
2041
|
];
|
|
1774
2042
|
return commandFiles.map(({ file, fullPath }) => {
|
|
1775
2043
|
const raw = fs11.readFileSync(fullPath, "utf8").replace(/\r\n/g, "\n");
|
|
1776
2044
|
const rendered = adapter.renderCommand(file, raw);
|
|
1777
|
-
return { kind: "write", target:
|
|
2045
|
+
return { kind: "write", target: path16.join(commandsDir, rendered.file), content: rendered.content };
|
|
1778
2046
|
});
|
|
1779
2047
|
}
|
|
1780
2048
|
function listMarkdownFiles(dir) {
|
|
1781
2049
|
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:
|
|
2050
|
+
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
2051
|
}
|
|
1784
2052
|
|
|
1785
2053
|
// src/components/hooks.ts
|
|
@@ -1793,15 +2061,15 @@ function planMcp(adapter, ctx) {
|
|
|
1793
2061
|
}
|
|
1794
2062
|
|
|
1795
2063
|
// src/components/plugins.ts
|
|
1796
|
-
import
|
|
2064
|
+
import path17 from "path";
|
|
1797
2065
|
import fs12 from "fs";
|
|
1798
2066
|
function planPlugins(adapter, ctx) {
|
|
1799
2067
|
const { pluginsDir } = adapter.paths(ctx.configDir);
|
|
1800
2068
|
if (pluginsDir === null) return [];
|
|
1801
|
-
const source =
|
|
2069
|
+
const source = path17.join(ctx.stackDir, "plugins", adapter.id);
|
|
1802
2070
|
if (!fs12.existsSync(source)) return [];
|
|
1803
2071
|
return listFilesRecursive(source).filter((f) => f.endsWith(".ts")).map((sourceFile) => {
|
|
1804
|
-
const target =
|
|
2072
|
+
const target = path17.join(pluginsDir, path17.relative(source, sourceFile));
|
|
1805
2073
|
const raw = fs12.readFileSync(sourceFile, "utf8");
|
|
1806
2074
|
let content = raw;
|
|
1807
2075
|
if (content.includes('"{{ENGRAM_BIN}}"')) {
|
|
@@ -1809,7 +2077,7 @@ function planPlugins(adapter, ctx) {
|
|
|
1809
2077
|
}
|
|
1810
2078
|
if (content.includes('"{{ENGRAM_PROTOCOL}}"')) {
|
|
1811
2079
|
const protocol = stripLeadingHtmlComments(
|
|
1812
|
-
fs12.readFileSync(
|
|
2080
|
+
fs12.readFileSync(path17.join(ctx.stackDir, "system-prompt", "engram-protocol.md"), "utf8").replace(/\r\n/g, "\n")
|
|
1813
2081
|
);
|
|
1814
2082
|
content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
|
|
1815
2083
|
}
|
|
@@ -1821,8 +2089,8 @@ function planPlugins(adapter, ctx) {
|
|
|
1821
2089
|
|
|
1822
2090
|
// src/lib/external-tools.ts
|
|
1823
2091
|
import fs13 from "fs";
|
|
1824
|
-
import
|
|
1825
|
-
import
|
|
2092
|
+
import os from "os";
|
|
2093
|
+
import path18 from "path";
|
|
1826
2094
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1827
2095
|
var PLAYWRIGHT_CLI = {
|
|
1828
2096
|
packageName: "@playwright/cli",
|
|
@@ -1873,9 +2141,9 @@ function resolvePnpmBin() {
|
|
|
1873
2141
|
const pnpmCmd = lookPath("pnpm.cmd");
|
|
1874
2142
|
return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
|
|
1875
2143
|
}
|
|
1876
|
-
function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir =
|
|
2144
|
+
function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
|
|
1877
2145
|
const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
|
|
1878
|
-
const cacheDir = configuredPath ?? (platform === "win32" ?
|
|
2146
|
+
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
2147
|
if (configuredPath === "0") return { status: "missing", path: cacheDir, errorCode: "DISABLED" };
|
|
1880
2148
|
try {
|
|
1881
2149
|
const ready = fs13.readdirSync(cacheDir, { withFileTypes: true }).some(
|
|
@@ -1923,7 +2191,7 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin()) {
|
|
|
1923
2191
|
|
|
1924
2192
|
// src/lib/tool-preferences.ts
|
|
1925
2193
|
import fs14 from "fs";
|
|
1926
|
-
import
|
|
2194
|
+
import path19 from "path";
|
|
1927
2195
|
var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
|
|
1928
2196
|
var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
|
|
1929
2197
|
var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
|
|
@@ -1936,7 +2204,7 @@ function readPreference(file) {
|
|
|
1936
2204
|
}
|
|
1937
2205
|
}
|
|
1938
2206
|
function playwrightCliPreferenceFile(stateDir = dataDir()) {
|
|
1939
|
-
return
|
|
2207
|
+
return path19.join(stateDir, "playwright-cli.json");
|
|
1940
2208
|
}
|
|
1941
2209
|
function parsePlaywrightCliPreference(raw) {
|
|
1942
2210
|
try {
|
|
@@ -1966,12 +2234,12 @@ function savePlaywrightCliPreference(file, enabled) {
|
|
|
1966
2234
|
writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
|
|
1967
2235
|
}
|
|
1968
2236
|
function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
|
|
1969
|
-
return
|
|
2237
|
+
return path19.join(stateDir, "devtools-mcp.json");
|
|
1970
2238
|
}
|
|
1971
2239
|
function primaryModelOwnershipFile(stateDir = dataDir()) {
|
|
1972
|
-
return
|
|
2240
|
+
return path19.join(stateDir, "primary-model.json");
|
|
1973
2241
|
}
|
|
1974
|
-
function
|
|
2242
|
+
function isRecord2(value) {
|
|
1975
2243
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1976
2244
|
}
|
|
1977
2245
|
function isRuntimeId(value) {
|
|
@@ -1980,7 +2248,7 @@ function isRuntimeId(value) {
|
|
|
1980
2248
|
function parseDevtoolsMcpState(raw) {
|
|
1981
2249
|
try {
|
|
1982
2250
|
const value = JSON.parse(raw);
|
|
1983
|
-
if (!
|
|
2251
|
+
if (!isRecord2(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord2(value.enabled) || !isRecord2(value.owned)) return null;
|
|
1984
2252
|
const enabled = {};
|
|
1985
2253
|
for (const [runtime, selected] of Object.entries(value.enabled)) {
|
|
1986
2254
|
if (!isRuntimeId(runtime) || typeof selected !== "boolean") return null;
|
|
@@ -1988,7 +2256,7 @@ function parseDevtoolsMcpState(raw) {
|
|
|
1988
2256
|
}
|
|
1989
2257
|
const owned = {};
|
|
1990
2258
|
for (const [runtime, servers] of Object.entries(value.owned)) {
|
|
1991
|
-
if (!isRuntimeId(runtime) || !
|
|
2259
|
+
if (!isRuntimeId(runtime) || !isRecord2(servers)) return null;
|
|
1992
2260
|
const managed = {};
|
|
1993
2261
|
for (const [server, marked] of Object.entries(servers)) {
|
|
1994
2262
|
if (marked !== true) return null;
|
|
@@ -2044,13 +2312,13 @@ function saveDevtoolsMcpOwnership(file, runtime, server, owned) {
|
|
|
2044
2312
|
function parsePrimaryModelOwnership(raw) {
|
|
2045
2313
|
try {
|
|
2046
2314
|
const value = JSON.parse(raw);
|
|
2047
|
-
if (!
|
|
2315
|
+
if (!isRecord2(value) || value.version !== PRIMARY_MODEL_OWNERSHIP_VERSION || !isRecord2(value.owned)) return null;
|
|
2048
2316
|
const owned = {};
|
|
2049
2317
|
for (const [runtime, configs] of Object.entries(value.owned)) {
|
|
2050
|
-
if (!isRuntimeId(runtime) || !
|
|
2318
|
+
if (!isRuntimeId(runtime) || !isRecord2(configs)) return null;
|
|
2051
2319
|
const markedConfigs = {};
|
|
2052
2320
|
for (const [configDir, fields] of Object.entries(configs)) {
|
|
2053
|
-
if (configDir === "" || !
|
|
2321
|
+
if (configDir === "" || !isRecord2(fields)) return null;
|
|
2054
2322
|
const markedFields = {};
|
|
2055
2323
|
for (const [field, state] of Object.entries(fields)) {
|
|
2056
2324
|
if (field === "" || state !== true) return null;
|
|
@@ -2079,7 +2347,7 @@ function primaryModelOwnershipError(file = primaryModelOwnershipFile()) {
|
|
|
2079
2347
|
return `Primary model: ownership inv\xE1lido en ${file}. Corrige o borra ese archivo antes de reintentar.`;
|
|
2080
2348
|
}
|
|
2081
2349
|
function primaryModelConfigKey(configDir) {
|
|
2082
|
-
const resolved =
|
|
2350
|
+
const resolved = path19.resolve(configDir);
|
|
2083
2351
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
2084
2352
|
}
|
|
2085
2353
|
function loadPrimaryModelOwnership(file, runtime, configDir) {
|
|
@@ -2225,7 +2493,7 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
|
|
|
2225
2493
|
continue;
|
|
2226
2494
|
}
|
|
2227
2495
|
try {
|
|
2228
|
-
for (const action of buildPlan(adapter, ctx)) targets.add(
|
|
2496
|
+
for (const action of buildPlan(adapter, ctx)) targets.add(path20.resolve(action.target));
|
|
2229
2497
|
} catch (error) {
|
|
2230
2498
|
complete = false;
|
|
2231
2499
|
warnings.push(
|
|
@@ -2327,10 +2595,10 @@ async function runInstall(opts) {
|
|
|
2327
2595
|
}
|
|
2328
2596
|
const writeManifest = () => {
|
|
2329
2597
|
if (!useManifest) return;
|
|
2330
|
-
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) =>
|
|
2598
|
+
const unmergeTargets = new Set(adapter.planUnmerge(canonicalMcp, canonicalHooks, ctx).map((a) => path20.resolve(a.target)));
|
|
2331
2599
|
const keepTarget = (target) => !unmergeTargets.has(target);
|
|
2332
|
-
const liveOwned = plan.map((a) =>
|
|
2333
|
-
const previousOwned = (prevManifest?.owned ?? []).map((target) =>
|
|
2600
|
+
const liveOwned = plan.map((a) => path20.resolve(a.target)).filter(keepTarget);
|
|
2601
|
+
const previousOwned = (prevManifest?.owned ?? []).map((target) => path20.resolve(target)).filter(keepTarget);
|
|
2334
2602
|
const owned = canOrphan ? liveOwned : [.../* @__PURE__ */ new Set([...previousOwned, ...liveOwned])];
|
|
2335
2603
|
writeRuntimeManifest(id, { configDir, owned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2336
2604
|
};
|
|
@@ -2359,7 +2627,7 @@ async function runInstall(opts) {
|
|
|
2359
2627
|
const backup = useManifest ? createBackup([...updates.map((c) => c.action.target), ...orphans], `install-${id}`) : null;
|
|
2360
2628
|
if (backup) p.log.info(`Backup: ${backup.id} (${backup.files.length} archivos)`);
|
|
2361
2629
|
applyChanges(changes, useManifest ? (action) => persistConfigurationOwnershipChanges(id, configDir, [action]) : void 0);
|
|
2362
|
-
const pruneRoot = useManifest ? HOME :
|
|
2630
|
+
const pruneRoot = useManifest ? HOME : path20.dirname(configDir);
|
|
2363
2631
|
for (const orphan of orphans) {
|
|
2364
2632
|
fs15.rmSync(orphan, { force: true });
|
|
2365
2633
|
pruneEmptyDirs(orphan, pruneRoot);
|
|
@@ -2444,27 +2712,27 @@ async function runInstall(opts) {
|
|
|
2444
2712
|
|
|
2445
2713
|
// src/uninstall.ts
|
|
2446
2714
|
import fs17 from "fs";
|
|
2447
|
-
import
|
|
2715
|
+
import path24 from "path";
|
|
2448
2716
|
import * as p2 from "@clack/prompts";
|
|
2449
2717
|
|
|
2450
2718
|
// src/lib/pi-projection-lifecycle.ts
|
|
2451
2719
|
import fs16 from "fs";
|
|
2452
|
-
import
|
|
2720
|
+
import path23 from "path";
|
|
2453
2721
|
|
|
2454
2722
|
// src/adapters/pi.ts
|
|
2455
|
-
import
|
|
2723
|
+
import path21 from "path";
|
|
2456
2724
|
var piAdapter = {
|
|
2457
2725
|
id: "pi",
|
|
2458
2726
|
paths(configDir) {
|
|
2459
|
-
const piConfigDir = process.env.PI_CODING_AGENT_DIR ??
|
|
2460
|
-
const agentsHome = samePath(configDir, piConfigDir) ? HOME :
|
|
2727
|
+
const piConfigDir = process.env.PI_CODING_AGENT_DIR ?? path21.join(HOME, ".pi", "agent");
|
|
2728
|
+
const agentsHome = samePath(configDir, piConfigDir) ? HOME : path21.join(path21.dirname(configDir), "home");
|
|
2461
2729
|
return {
|
|
2462
|
-
systemPromptFile:
|
|
2463
|
-
agentsDir:
|
|
2464
|
-
skillsDir:
|
|
2465
|
-
commandsDir:
|
|
2730
|
+
systemPromptFile: path21.join(configDir, "AGENTS.md"),
|
|
2731
|
+
agentsDir: path21.join(configDir, "agents"),
|
|
2732
|
+
skillsDir: path21.join(agentsHome, ".agents", "skills"),
|
|
2733
|
+
commandsDir: path21.join(configDir, "prompts"),
|
|
2466
2734
|
pluginsDir: null,
|
|
2467
|
-
scriptsDir:
|
|
2735
|
+
scriptsDir: path21.join(configDir, "scripts"),
|
|
2468
2736
|
outputStylesDir: null,
|
|
2469
2737
|
profilesDir: null
|
|
2470
2738
|
};
|
|
@@ -2478,7 +2746,7 @@ var piAdapter = {
|
|
|
2478
2746
|
};
|
|
2479
2747
|
|
|
2480
2748
|
// src/lib/pi-package-lifecycle.ts
|
|
2481
|
-
import
|
|
2749
|
+
import path22 from "path";
|
|
2482
2750
|
var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
2483
2751
|
"foundation-contract-v1",
|
|
2484
2752
|
"runner-json-v1",
|
|
@@ -2610,7 +2878,7 @@ function parseReceiptShape(receiptJson) {
|
|
|
2610
2878
|
return null;
|
|
2611
2879
|
}
|
|
2612
2880
|
if (engram === void 0) return "upgrade-required";
|
|
2613
|
-
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !
|
|
2881
|
+
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path22.isAbsolute(Reflect.get(engram, "binary"))) {
|
|
2614
2882
|
return null;
|
|
2615
2883
|
}
|
|
2616
2884
|
return parsed;
|
|
@@ -2647,7 +2915,7 @@ function planPiPackageLifecycle(input) {
|
|
|
2647
2915
|
if (input.receiptJson !== null) {
|
|
2648
2916
|
const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
|
|
2649
2917
|
kind: input.scope.kind,
|
|
2650
|
-
codingAgentDir:
|
|
2918
|
+
codingAgentDir: path22.resolve(input.scope.codingAgentDir)
|
|
2651
2919
|
}, input.engramBin);
|
|
2652
2920
|
if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
|
|
2653
2921
|
if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
|
|
@@ -2682,7 +2950,7 @@ function planPiPackageLifecycle(input) {
|
|
|
2682
2950
|
},
|
|
2683
2951
|
receipt: expectedReceipt(input.candidate, "installing", {
|
|
2684
2952
|
kind: input.scope.kind,
|
|
2685
|
-
codingAgentDir:
|
|
2953
|
+
codingAgentDir: path22.resolve(input.scope.codingAgentDir)
|
|
2686
2954
|
}, input.engramBin),
|
|
2687
2955
|
ownership: ownership(true)
|
|
2688
2956
|
};
|
|
@@ -2697,7 +2965,7 @@ function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
|
|
|
2697
2965
|
const parsed = JSON.parse(body);
|
|
2698
2966
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2699
2967
|
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" || !
|
|
2968
|
+
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
2969
|
return null;
|
|
2702
2970
|
}
|
|
2703
2971
|
return record;
|
|
@@ -2783,10 +3051,10 @@ function validateOwnedOperationState(input) {
|
|
|
2783
3051
|
}))) {
|
|
2784
3052
|
return { kind: "blocked", reason: "receipt-untrusted" };
|
|
2785
3053
|
}
|
|
2786
|
-
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") ||
|
|
3054
|
+
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path22.resolve(receipt.scope.codingAgentDir) !== path22.resolve(input.paths.codingAgentDir)) {
|
|
2787
3055
|
return { kind: "blocked", reason: "source-divergent" };
|
|
2788
3056
|
}
|
|
2789
|
-
if (input.engramBin !== null &&
|
|
3057
|
+
if (input.engramBin !== null && path22.resolve(receipt.engram.binary) !== path22.resolve(input.engramBin)) {
|
|
2790
3058
|
return { kind: "blocked", reason: "receipt-corrupt" };
|
|
2791
3059
|
}
|
|
2792
3060
|
const source = receipt.candidate.package.source;
|
|
@@ -2883,18 +3151,18 @@ var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
|
|
|
2883
3151
|
function projectionScope(scope) {
|
|
2884
3152
|
return {
|
|
2885
3153
|
...scope,
|
|
2886
|
-
settingsFile:
|
|
3154
|
+
settingsFile: path23.join(scope.codingAgentDir, "settings.json")
|
|
2887
3155
|
};
|
|
2888
3156
|
}
|
|
2889
3157
|
function isInside(root, file) {
|
|
2890
|
-
const relative =
|
|
2891
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
3158
|
+
const relative = path23.relative(path23.resolve(root), path23.resolve(file));
|
|
3159
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path23.sep}`) && !path23.isAbsolute(relative);
|
|
2892
3160
|
}
|
|
2893
3161
|
function isManagedPath(scope, file) {
|
|
2894
3162
|
return isInside(scope.home, file) || isInside(scope.codingAgentDir, file);
|
|
2895
3163
|
}
|
|
2896
3164
|
function isAllowedPath(scope, file) {
|
|
2897
|
-
return isManagedPath(scope, file) ||
|
|
3165
|
+
return isManagedPath(scope, file) || path23.resolve(file) === path23.resolve(scope.receiptFile);
|
|
2898
3166
|
}
|
|
2899
3167
|
function projectedPiAdapter(scope) {
|
|
2900
3168
|
const paths = piAdapter.paths(scope.codingAgentDir);
|
|
@@ -2902,7 +3170,7 @@ function projectedPiAdapter(scope) {
|
|
|
2902
3170
|
...piAdapter,
|
|
2903
3171
|
paths: () => ({
|
|
2904
3172
|
...paths,
|
|
2905
|
-
skillsDir:
|
|
3173
|
+
skillsDir: path23.join(scope.home, ".agents", "skills")
|
|
2906
3174
|
})
|
|
2907
3175
|
};
|
|
2908
3176
|
}
|
|
@@ -2942,9 +3210,9 @@ function assertPlanContained(plan, scope) {
|
|
|
2942
3210
|
}
|
|
2943
3211
|
}
|
|
2944
3212
|
function receiptFor(plan, scope) {
|
|
2945
|
-
const systemPrompt =
|
|
3213
|
+
const systemPrompt = path23.join(scope.codingAgentDir, "AGENTS.md");
|
|
2946
3214
|
const owned = [...new Set(
|
|
2947
|
-
plan.map((action) =>
|
|
3215
|
+
plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
|
|
2948
3216
|
)];
|
|
2949
3217
|
return {
|
|
2950
3218
|
schemaVersion: 1,
|
|
@@ -2996,8 +3264,8 @@ function realPiProjectionScope() {
|
|
|
2996
3264
|
return {
|
|
2997
3265
|
kind: "real",
|
|
2998
3266
|
home: HOME,
|
|
2999
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
3000
|
-
receiptFile:
|
|
3267
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path23.join(HOME, ".pi", "agent"),
|
|
3268
|
+
receiptFile: path23.join(dataDir(), "pi-projection-receipt.json")
|
|
3001
3269
|
};
|
|
3002
3270
|
}
|
|
3003
3271
|
function readTextOnlyIfMissing(file) {
|
|
@@ -3035,13 +3303,13 @@ function manifestOwned(manifest) {
|
|
|
3035
3303
|
return new Set([
|
|
3036
3304
|
...manifest.runtimes.codex?.owned ?? [],
|
|
3037
3305
|
...manifest.runtimes.opencode?.owned ?? []
|
|
3038
|
-
].map((file) =>
|
|
3306
|
+
].map((file) => path23.resolve(file)));
|
|
3039
3307
|
}
|
|
3040
3308
|
function withoutManagedPromptSections(content) {
|
|
3041
3309
|
return ["system-prompt", "engram-protocol", "browser"].reduce((current, section) => removeMarkdownSection(current, section), content);
|
|
3042
3310
|
}
|
|
3043
3311
|
function uniquePaths(paths) {
|
|
3044
|
-
return [...new Set(paths.map((file) =>
|
|
3312
|
+
return [...new Set(paths.map((file) => path23.resolve(file)))];
|
|
3045
3313
|
}
|
|
3046
3314
|
function blocked2(reason, paths, remedy) {
|
|
3047
3315
|
return { kind: "blocked", reason, paths: uniquePaths(paths), remedy };
|
|
@@ -3114,7 +3382,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3114
3382
|
}
|
|
3115
3383
|
const scope = projectionScope(input.scope);
|
|
3116
3384
|
const expectedReceipt2 = expectedProjectionReceipt(input, scope);
|
|
3117
|
-
const prompt =
|
|
3385
|
+
const prompt = path23.join(scope.codingAgentDir, "AGENTS.md");
|
|
3118
3386
|
let existingPrompt;
|
|
3119
3387
|
try {
|
|
3120
3388
|
existingPrompt = deps.readText(prompt);
|
|
@@ -3122,7 +3390,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3122
3390
|
return cleanupFailure([prompt]);
|
|
3123
3391
|
}
|
|
3124
3392
|
const promptContent = existingPrompt === null ? null : withoutManagedPromptSections(existingPrompt);
|
|
3125
|
-
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file:
|
|
3393
|
+
const promptUpdate = promptContent === null || promptContent === existingPrompt ? null : { file: path23.resolve(prompt), content: promptContent };
|
|
3126
3394
|
const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
|
|
3127
3395
|
if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
|
|
3128
3396
|
let ownedToRemove = [];
|
|
@@ -3138,7 +3406,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3138
3406
|
return cleanupFailure([scope.receiptFile]);
|
|
3139
3407
|
}
|
|
3140
3408
|
ownedToRemove = receipt.owned.filter((owned) => !retained.has(owned));
|
|
3141
|
-
receiptFile =
|
|
3409
|
+
receiptFile = path23.resolve(scope.receiptFile);
|
|
3142
3410
|
backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
|
|
3143
3411
|
}
|
|
3144
3412
|
const backup = backupExisting(backupTargets, deps);
|
|
@@ -3194,7 +3462,7 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3194
3462
|
const expectedReceipt2 = receiptContent(receipt);
|
|
3195
3463
|
const drifted = plan.filter((action) => hasActionDrift(action, deps));
|
|
3196
3464
|
if (input.operation === "doctor") {
|
|
3197
|
-
const paths = drifted.map((action) =>
|
|
3465
|
+
const paths = drifted.map((action) => path23.resolve(action.target));
|
|
3198
3466
|
const currentSettings2 = deps.readText(scope.settingsFile);
|
|
3199
3467
|
if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
|
|
3200
3468
|
paths.push(scope.settingsFile);
|
|
@@ -3225,12 +3493,12 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3225
3493
|
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged };
|
|
3226
3494
|
}
|
|
3227
3495
|
function systemProjectionLifecycle(input) {
|
|
3228
|
-
const targetRoot = input.targetDir === void 0 ? null :
|
|
3496
|
+
const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
|
|
3229
3497
|
const scope = targetRoot === null ? realPiProjectionScope() : {
|
|
3230
3498
|
kind: "target-dir",
|
|
3231
|
-
home:
|
|
3232
|
-
codingAgentDir:
|
|
3233
|
-
receiptFile:
|
|
3499
|
+
home: path23.join(targetRoot, "home"),
|
|
3500
|
+
codingAgentDir: path23.join(targetRoot, "pi-agent"),
|
|
3501
|
+
receiptFile: path23.join(targetRoot, "state", "pi-projection-receipt.json")
|
|
3234
3502
|
};
|
|
3235
3503
|
return {
|
|
3236
3504
|
input: {
|
|
@@ -3246,7 +3514,7 @@ function systemProjectionLifecycle(input) {
|
|
|
3246
3514
|
backup: (paths) => createBackup(
|
|
3247
3515
|
paths,
|
|
3248
3516
|
`pi-projection-${input.operation}`,
|
|
3249
|
-
targetRoot === null ? void 0 :
|
|
3517
|
+
targetRoot === null ? void 0 : path23.join(targetRoot, "backups")
|
|
3250
3518
|
),
|
|
3251
3519
|
writeText,
|
|
3252
3520
|
copyFile,
|
|
@@ -3327,7 +3595,7 @@ async function runUninstall(opts) {
|
|
|
3327
3595
|
if (!detection.installed) continue;
|
|
3328
3596
|
const keepCtx = makeContext(keep, detection.configDir);
|
|
3329
3597
|
if (!keepCtx) continue;
|
|
3330
|
-
for (const action of buildPlan(keep, keepCtx)) retained.add(
|
|
3598
|
+
for (const action of buildPlan(keep, keepCtx)) retained.add(path24.resolve(action.target));
|
|
3331
3599
|
}
|
|
3332
3600
|
}
|
|
3333
3601
|
for (const id of opts.runtimes) {
|
|
@@ -3346,15 +3614,15 @@ async function runUninstall(opts) {
|
|
|
3346
3614
|
if (!ctx) continue;
|
|
3347
3615
|
ctx.preserveEngram = !removeEngram;
|
|
3348
3616
|
const unmerge = adapter.planUnmerge(mcpForUnmerge, hooks, ctx);
|
|
3349
|
-
const mergedTargets = new Set(unmerge.map((a) =>
|
|
3617
|
+
const mergedTargets = new Set(unmerge.map((a) => path24.resolve(a.target)));
|
|
3350
3618
|
const usingRealConfig = opts.targetDir === void 0;
|
|
3351
3619
|
const prevOwned = usingRealConfig ? readManifest().runtimes[id]?.owned ?? [] : [];
|
|
3352
|
-
const pruneRoot = usingRealConfig ? HOME :
|
|
3620
|
+
const pruneRoot = usingRealConfig ? HOME : path24.dirname(configDir);
|
|
3353
3621
|
const planTargets = [
|
|
3354
|
-
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) =>
|
|
3622
|
+
.../* @__PURE__ */ new Set([...buildPlan(adapter, ctx).map((a) => path24.resolve(a.target)), ...prevOwned.map((t) => path24.resolve(t))])
|
|
3355
3623
|
].filter((t) => !mergedTargets.has(t) && fs17.existsSync(t));
|
|
3356
3624
|
const deleteTargets = planTargets.filter(
|
|
3357
|
-
(t) => !retained.has(t) && !(ctx.preserveEngram &&
|
|
3625
|
+
(t) => !retained.has(t) && !(ctx.preserveEngram && path24.basename(t) === "engram.ts") && isContainedIn(t, pruneRoot)
|
|
3358
3626
|
);
|
|
3359
3627
|
const sharedKept = planTargets.length - deleteTargets.length;
|
|
3360
3628
|
p2.log.step(`${adapter.name} \u2192 ${configDir}`);
|
|
@@ -3434,7 +3702,7 @@ async function runUninstall(opts) {
|
|
|
3434
3702
|
}
|
|
3435
3703
|
|
|
3436
3704
|
// src/doctor.ts
|
|
3437
|
-
import
|
|
3705
|
+
import path25 from "path";
|
|
3438
3706
|
import fs18 from "fs";
|
|
3439
3707
|
import * as p3 from "@clack/prompts";
|
|
3440
3708
|
function engramVersion(bin) {
|
|
@@ -3454,7 +3722,7 @@ function resolvePlaywrightDoctorState(input) {
|
|
|
3454
3722
|
return { status: "healthy" };
|
|
3455
3723
|
}
|
|
3456
3724
|
function context7KeyConfigured(id, configDir) {
|
|
3457
|
-
const file = id === "codex" ?
|
|
3725
|
+
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
3726
|
const content = readTextIfExists(file);
|
|
3459
3727
|
if (content === null) return null;
|
|
3460
3728
|
const match = /CONTEXT7_API_KEY"?\s*[:=]\s*"([^"]*)"/.exec(content);
|
|
@@ -3477,8 +3745,8 @@ async function runDoctor() {
|
|
|
3477
3745
|
p3.log.success(`Engram: ${version} (${engramBin})`);
|
|
3478
3746
|
}
|
|
3479
3747
|
}
|
|
3480
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
3481
|
-
const engramDb =
|
|
3748
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path25.join(HOME, ".engram");
|
|
3749
|
+
const engramDb = path25.join(engramDataDir, "engram.db");
|
|
3482
3750
|
if (fs18.existsSync(engramDb)) {
|
|
3483
3751
|
const sizeMb = (fs18.statSync(engramDb).size / 1024 / 1024).toFixed(1);
|
|
3484
3752
|
p3.log.info(`Engram DB: ${engramDb} (${sizeMb} MB de memorias \u2014 el stack no la toca JAM\xC1S).`);
|
|
@@ -3534,6 +3802,9 @@ async function runDoctor() {
|
|
|
3534
3802
|
p3.log.warn(`${adapter.name}: no instalado en esta m\xE1quina.`);
|
|
3535
3803
|
continue;
|
|
3536
3804
|
}
|
|
3805
|
+
const capabilityReport = adapter.reportCapabilities(detection.configDir);
|
|
3806
|
+
const capabilitySummary = capabilityReport.capabilities.map((capability) => `${capability.id}=${capability.state}`).join(", ");
|
|
3807
|
+
p3.log.info(`${adapter.name}: capabilities diagnostic (${capabilitySummary}); no certifica enforcement local.`);
|
|
3537
3808
|
const ctx = makeContext(adapter, detection.configDir, modePreference);
|
|
3538
3809
|
if (!ctx) continue;
|
|
3539
3810
|
let pending;
|
|
@@ -3558,10 +3829,10 @@ async function runDoctor() {
|
|
|
3558
3829
|
p3.log.warn(`${adapter.name}: ${orphans.length} archivos hu\xE9rfanos de versiones previas \u2192 ejecuta 'sync'.`);
|
|
3559
3830
|
problems++;
|
|
3560
3831
|
}
|
|
3561
|
-
if (adapter.id === "codex" && fs18.existsSync(
|
|
3832
|
+
if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "hooks.json"))) {
|
|
3562
3833
|
p3.log.info("Codex: recuerda que los hooks requieren aprobaci\xF3n manual \u2014 verifica con /hooks dentro de codex.");
|
|
3563
3834
|
}
|
|
3564
|
-
if (adapter.id === "codex" && fs18.existsSync(
|
|
3835
|
+
if (adapter.id === "codex" && fs18.existsSync(path25.join(detection.configDir, "AGENTS.override.md"))) {
|
|
3565
3836
|
p3.log.warn(
|
|
3566
3837
|
"Codex: existe ~/.codex/AGENTS.override.md \u2014 tiene prioridad ABSOLUTA y tapa el AGENTS.md gestionado por el stack."
|
|
3567
3838
|
);
|
|
@@ -3576,16 +3847,16 @@ async function runDoctor() {
|
|
|
3576
3847
|
|
|
3577
3848
|
// src/update.ts
|
|
3578
3849
|
import fs21 from "fs";
|
|
3579
|
-
import
|
|
3580
|
-
import
|
|
3850
|
+
import path28 from "path";
|
|
3851
|
+
import os3 from "os";
|
|
3581
3852
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3582
3853
|
import * as p4 from "@clack/prompts";
|
|
3583
3854
|
|
|
3584
3855
|
// src/lib/github.ts
|
|
3585
3856
|
import fs19 from "fs";
|
|
3586
|
-
import
|
|
3857
|
+
import path26 from "path";
|
|
3587
3858
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3588
|
-
import
|
|
3859
|
+
import os2 from "os";
|
|
3589
3860
|
import { Readable } from "stream";
|
|
3590
3861
|
import { pipeline } from "stream/promises";
|
|
3591
3862
|
var cachedToken;
|
|
@@ -3653,7 +3924,7 @@ async function latestGithubCommit(repo) {
|
|
|
3653
3924
|
}
|
|
3654
3925
|
}
|
|
3655
3926
|
function validateExtractedTree(destDir) {
|
|
3656
|
-
const resolved =
|
|
3927
|
+
const resolved = path26.resolve(destDir);
|
|
3657
3928
|
const walk = (dir) => {
|
|
3658
3929
|
let entries;
|
|
3659
3930
|
try {
|
|
@@ -3662,7 +3933,7 @@ function validateExtractedTree(destDir) {
|
|
|
3662
3933
|
return false;
|
|
3663
3934
|
}
|
|
3664
3935
|
for (const entry of entries) {
|
|
3665
|
-
const full =
|
|
3936
|
+
const full = path26.join(dir, entry.name);
|
|
3666
3937
|
let stat;
|
|
3667
3938
|
try {
|
|
3668
3939
|
stat = fs19.lstatSync(full);
|
|
@@ -3681,12 +3952,12 @@ function validateExtractedTree(destDir) {
|
|
|
3681
3952
|
}
|
|
3682
3953
|
function resolveTarBin() {
|
|
3683
3954
|
if (process.platform !== "win32") return "tar";
|
|
3684
|
-
const winTar =
|
|
3955
|
+
const winTar = path26.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "tar.exe");
|
|
3685
3956
|
return fs19.existsSync(winTar) ? winTar : "tar";
|
|
3686
3957
|
}
|
|
3687
3958
|
async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
3688
3959
|
const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;
|
|
3689
|
-
const tmp =
|
|
3960
|
+
const tmp = path26.join(os2.tmpdir(), `jorgex-tarball-${Date.now()}.tar.gz`);
|
|
3690
3961
|
const fail = (reason) => {
|
|
3691
3962
|
try {
|
|
3692
3963
|
fs19.rmSync(destDir, { recursive: true, force: true });
|
|
@@ -3719,8 +3990,8 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
3719
3990
|
const detail = (e.stderr?.toString().trim() || e.message || "").split("\n")[0];
|
|
3720
3991
|
return fail(detail ? `tar fall\xF3: ${detail}` : "tar no disponible o fall\xF3 la extracci\xF3n");
|
|
3721
3992
|
}
|
|
3722
|
-
const resolvedDest =
|
|
3723
|
-
const validateRoot = validateSubdir ?
|
|
3993
|
+
const resolvedDest = path26.resolve(destDir);
|
|
3994
|
+
const validateRoot = validateSubdir ? path26.resolve(resolvedDest, validateSubdir) : resolvedDest;
|
|
3724
3995
|
if (validateRoot !== resolvedDest && !isContainedIn(validateRoot, resolvedDest)) {
|
|
3725
3996
|
return fail(`la ruta de validaci\xF3n "${validateSubdir}" escapa del destino`);
|
|
3726
3997
|
}
|
|
@@ -3742,7 +4013,7 @@ async function downloadRepoTarball(repo, sha, destDir, validateSubdir) {
|
|
|
3742
4013
|
|
|
3743
4014
|
// src/lib/skill-update.ts
|
|
3744
4015
|
import fs20 from "fs";
|
|
3745
|
-
import
|
|
4016
|
+
import path27 from "path";
|
|
3746
4017
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3747
4018
|
var PROTECTED_SKILLS = /* @__PURE__ */ new Set([
|
|
3748
4019
|
"agent-delegation",
|
|
@@ -3761,10 +4032,10 @@ function sameTextContentNormalized(a, b) {
|
|
|
3761
4032
|
}
|
|
3762
4033
|
function diffSkillDirs(upstreamDir, localDir) {
|
|
3763
4034
|
const upstreamFiles = new Set(
|
|
3764
|
-
listFilesRecursive(upstreamDir).map((f) =>
|
|
4035
|
+
listFilesRecursive(upstreamDir).map((f) => path27.relative(upstreamDir, f))
|
|
3765
4036
|
);
|
|
3766
4037
|
const localFiles = new Set(
|
|
3767
|
-
listFilesRecursive(localDir).map((f) =>
|
|
4038
|
+
listFilesRecursive(localDir).map((f) => path27.relative(localDir, f))
|
|
3768
4039
|
);
|
|
3769
4040
|
const added = [];
|
|
3770
4041
|
const modified = [];
|
|
@@ -3772,7 +4043,7 @@ function diffSkillDirs(upstreamDir, localDir) {
|
|
|
3772
4043
|
for (const rel of upstreamFiles) {
|
|
3773
4044
|
if (!localFiles.has(rel)) {
|
|
3774
4045
|
added.push(rel);
|
|
3775
|
-
} else if (!sameTextContentNormalized(
|
|
4046
|
+
} else if (!sameTextContentNormalized(path27.join(upstreamDir, rel), path27.join(localDir, rel))) {
|
|
3776
4047
|
modified.push(rel);
|
|
3777
4048
|
}
|
|
3778
4049
|
}
|
|
@@ -3834,7 +4105,7 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3834
4105
|
if (PROTECTED_SKILLS.has(name)) {
|
|
3835
4106
|
throw new Error(`La skill "${name}" es propia del stack y no se actualiza desde upstream.`);
|
|
3836
4107
|
}
|
|
3837
|
-
const upstreamsFile = upstreamsFilePath ??
|
|
4108
|
+
const upstreamsFile = upstreamsFilePath ?? path27.join(path27.dirname(stackRoot()), "upstreams.json");
|
|
3838
4109
|
const raw = fs20.readFileSync(upstreamsFile, "utf8");
|
|
3839
4110
|
const data = JSON.parse(raw);
|
|
3840
4111
|
const skillEntry = data?.skills?.[name];
|
|
@@ -3844,8 +4115,8 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3844
4115
|
if (skillEntry.kind === "release") {
|
|
3845
4116
|
throw new Error(`La skill "${name}" es de tipo release y no se actualiza con replaceSkill.`);
|
|
3846
4117
|
}
|
|
3847
|
-
const skillsRoot = localSkillsRoot ??
|
|
3848
|
-
const localSkillDir =
|
|
4118
|
+
const skillsRoot = localSkillsRoot ?? path27.join(stackRoot(), "skills");
|
|
4119
|
+
const localSkillDir = path27.join(skillsRoot, name);
|
|
3849
4120
|
const localFiles = listFilesRecursive(localSkillDir);
|
|
3850
4121
|
if (localFiles.length > 0) {
|
|
3851
4122
|
createBackup(localFiles, `skill-update-${name}`, backupsRoot2);
|
|
@@ -3858,9 +4129,9 @@ function replaceSkill(name, upstreamSkillDir, newCommit, opts) {
|
|
|
3858
4129
|
if (st.isSymbolicLink()) {
|
|
3859
4130
|
throw new Error(`Symlink rechazado en upstream de skill "${name}": ${src}`);
|
|
3860
4131
|
}
|
|
3861
|
-
const rel =
|
|
3862
|
-
const dest =
|
|
3863
|
-
ensureDir(
|
|
4132
|
+
const rel = path27.relative(upstreamSkillDir, src);
|
|
4133
|
+
const dest = path27.join(stagingDir, rel);
|
|
4134
|
+
ensureDir(path27.dirname(dest));
|
|
3864
4135
|
copyFile(src, dest);
|
|
3865
4136
|
}
|
|
3866
4137
|
const oldDir = `${localSkillDir}.old-${process.pid}`;
|
|
@@ -3885,7 +4156,7 @@ function rateLimitHint(prefix) {
|
|
|
3885
4156
|
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
4157
|
}
|
|
3887
4158
|
function loadUpstreams() {
|
|
3888
|
-
const file =
|
|
4159
|
+
const file = path28.join(path28.dirname(stackRoot()), "upstreams.json");
|
|
3889
4160
|
return JSON.parse(fs21.readFileSync(file, "utf8"));
|
|
3890
4161
|
}
|
|
3891
4162
|
function skillsToScan(maintainer, upstreams) {
|
|
@@ -4035,8 +4306,8 @@ function isEngramRunning() {
|
|
|
4035
4306
|
return null;
|
|
4036
4307
|
}
|
|
4037
4308
|
}
|
|
4038
|
-
function isGitClone(projectRoot =
|
|
4039
|
-
return fs21.existsSync(
|
|
4309
|
+
function isGitClone(projectRoot = path28.dirname(stackRoot())) {
|
|
4310
|
+
return fs21.existsSync(path28.join(projectRoot, ".git"));
|
|
4040
4311
|
}
|
|
4041
4312
|
var STACK_METHOD_CLONE = "git pull + pnpm install + pnpm build";
|
|
4042
4313
|
function resolvePnpm() {
|
|
@@ -4051,7 +4322,7 @@ function cleanupTmp(dir) {
|
|
|
4051
4322
|
}
|
|
4052
4323
|
}
|
|
4053
4324
|
function updateStackGitClone() {
|
|
4054
|
-
const projectRoot =
|
|
4325
|
+
const projectRoot = path28.dirname(stackRoot());
|
|
4055
4326
|
const git = lookPath("git");
|
|
4056
4327
|
if (!git) throw new Error("git no encontrado en PATH.");
|
|
4057
4328
|
const pnpm = resolvePnpm();
|
|
@@ -4068,7 +4339,7 @@ function updateStackGlobal() {
|
|
|
4068
4339
|
execFileSync5(pnpm, ["add", "-g", "jorgex-stack@latest"], { stdio: "inherit" });
|
|
4069
4340
|
}
|
|
4070
4341
|
async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
4071
|
-
const root = fs21.mkdtempSync(
|
|
4342
|
+
const root = fs21.mkdtempSync(path28.join(os3.tmpdir(), "jorgex-skill-"));
|
|
4072
4343
|
try {
|
|
4073
4344
|
const result = await downloadRepoTarball(repo, sha, root, skillPath);
|
|
4074
4345
|
if (!result.ok) {
|
|
@@ -4076,14 +4347,14 @@ async function downloadSkillToTemp(repo, sha, skillPath) {
|
|
|
4076
4347
|
return { error: result.reason };
|
|
4077
4348
|
}
|
|
4078
4349
|
if (skillPath) {
|
|
4079
|
-
const sub =
|
|
4350
|
+
const sub = path28.resolve(path28.join(root, skillPath));
|
|
4080
4351
|
if (!isContainedIn(sub, root)) {
|
|
4081
4352
|
cleanupTmp(root);
|
|
4082
4353
|
return { error: `la ruta "${skillPath}" escapa del directorio temporal` };
|
|
4083
4354
|
}
|
|
4084
4355
|
if (fs21.existsSync(sub)) return { dir: sub, root };
|
|
4085
4356
|
const lastSeg = skillPath.split("/").pop();
|
|
4086
|
-
const sub2 =
|
|
4357
|
+
const sub2 = path28.resolve(path28.join(root, lastSeg));
|
|
4087
4358
|
if (isContainedIn(sub2, root) && fs21.existsSync(sub2)) {
|
|
4088
4359
|
if (!validateExtractedTree(sub2)) {
|
|
4089
4360
|
cleanupTmp(root);
|
|
@@ -4104,10 +4375,10 @@ function pruneEngramDbBackups() {
|
|
|
4104
4375
|
try {
|
|
4105
4376
|
const dir = dataDir();
|
|
4106
4377
|
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(
|
|
4378
|
+
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
4379
|
for (const old of backups.slice(3)) {
|
|
4109
4380
|
try {
|
|
4110
|
-
fs21.rmSync(
|
|
4381
|
+
fs21.rmSync(path28.join(dir, old.name));
|
|
4111
4382
|
} catch {
|
|
4112
4383
|
}
|
|
4113
4384
|
}
|
|
@@ -4116,17 +4387,17 @@ function pruneEngramDbBackups() {
|
|
|
4116
4387
|
}
|
|
4117
4388
|
function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
4118
4389
|
if (!fs21.existsSync(binPath)) return null;
|
|
4119
|
-
const dir =
|
|
4120
|
-
const base =
|
|
4390
|
+
const dir = path28.dirname(binPath);
|
|
4391
|
+
const base = path28.basename(binPath);
|
|
4121
4392
|
const escapedBase = base.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&");
|
|
4122
4393
|
const oldPattern = new RegExp("^" + escapedBase + "\\.old-\\d+$");
|
|
4123
|
-
const resolvedDir =
|
|
4124
|
-
if (resolvedDir ===
|
|
4394
|
+
const resolvedDir = path28.resolve(dir);
|
|
4395
|
+
if (resolvedDir === path28.resolve(sweepRoot) || isContainedIn(resolvedDir, sweepRoot)) {
|
|
4125
4396
|
try {
|
|
4126
4397
|
for (const entry of fs21.readdirSync(dir)) {
|
|
4127
4398
|
if (oldPattern.test(entry)) {
|
|
4128
4399
|
try {
|
|
4129
|
-
fs21.rmSync(
|
|
4400
|
+
fs21.rmSync(path28.join(dir, entry), { force: true });
|
|
4130
4401
|
} catch {
|
|
4131
4402
|
}
|
|
4132
4403
|
}
|
|
@@ -4134,7 +4405,7 @@ function rotateLockedBinary(binPath, sweepRoot = HOME) {
|
|
|
4134
4405
|
} catch {
|
|
4135
4406
|
}
|
|
4136
4407
|
}
|
|
4137
|
-
const rotated =
|
|
4408
|
+
const rotated = path28.join(dir, `${base}.old-${Date.now()}`);
|
|
4138
4409
|
fs21.renameSync(binPath, rotated);
|
|
4139
4410
|
return rotated;
|
|
4140
4411
|
}
|
|
@@ -4144,8 +4415,8 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4144
4415
|
"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
4416
|
);
|
|
4146
4417
|
}
|
|
4147
|
-
const engramDataDir = process.env.ENGRAM_DATA_DIR ??
|
|
4148
|
-
const engramDb =
|
|
4418
|
+
const engramDataDir = process.env.ENGRAM_DATA_DIR ?? path28.join(HOME, ".engram");
|
|
4419
|
+
const engramDb = path28.join(engramDataDir, "engram.db");
|
|
4149
4420
|
if (fs21.existsSync(engramDb)) {
|
|
4150
4421
|
const doBackup = await p4.confirm({
|
|
4151
4422
|
message: `\xBFHacer backup de la DB de Engram antes de actualizar? (${engramDb})`,
|
|
@@ -4153,7 +4424,7 @@ async function updateEngram(engramRepo, latestVersion) {
|
|
|
4153
4424
|
});
|
|
4154
4425
|
if (!p4.isCancel(doBackup) && doBackup) {
|
|
4155
4426
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4156
|
-
const dest =
|
|
4427
|
+
const dest = path28.join(dataDir(), `engram-db-backup-${ts}.db`);
|
|
4157
4428
|
try {
|
|
4158
4429
|
if (!fs21.existsSync(dataDir())) fs21.mkdirSync(dataDir(), { recursive: true });
|
|
4159
4430
|
fs21.copyFileSync(engramDb, dest);
|
|
@@ -4490,7 +4761,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
|
|
|
4490
4761
|
continue;
|
|
4491
4762
|
}
|
|
4492
4763
|
const { dir: tmpDir, root: tmpRoot } = tmpResult;
|
|
4493
|
-
const localSkillDir =
|
|
4764
|
+
const localSkillDir = path28.join(stackRoot(), "skills", skillInfo.name);
|
|
4494
4765
|
const diff = renderSkillDiff(tmpDir, localSkillDir);
|
|
4495
4766
|
if (diff) {
|
|
4496
4767
|
p4.log.info(`Diff de ${skillInfo.name}:
|
|
@@ -4576,7 +4847,7 @@ ${diff}`);
|
|
|
4576
4847
|
}
|
|
4577
4848
|
|
|
4578
4849
|
// src/models-picker.ts
|
|
4579
|
-
import
|
|
4850
|
+
import path29 from "path";
|
|
4580
4851
|
import * as p5 from "@clack/prompts";
|
|
4581
4852
|
var TIERS = ["strong", "standard", "cheap"];
|
|
4582
4853
|
var EFFORTS = ["low", "medium", "high", "xhigh"];
|
|
@@ -4600,7 +4871,7 @@ function opencodeLiveModels(binPath) {
|
|
|
4600
4871
|
}
|
|
4601
4872
|
function agentsByTier() {
|
|
4602
4873
|
const grouped = { strong: [], standard: [], cheap: [] };
|
|
4603
|
-
for (const agent of loadCanonicalAgents(
|
|
4874
|
+
for (const agent of loadCanonicalAgents(path29.join(stackRoot(), "agents"))) {
|
|
4604
4875
|
if (agent.mode === "subagent") grouped[agent.tier].push(agent.name);
|
|
4605
4876
|
}
|
|
4606
4877
|
return grouped;
|
|
@@ -4781,15 +5052,15 @@ function cancelled() {
|
|
|
4781
5052
|
|
|
4782
5053
|
// src/lib/release.ts
|
|
4783
5054
|
import fs22 from "fs";
|
|
4784
|
-
import
|
|
5055
|
+
import path30 from "path";
|
|
4785
5056
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
4786
|
-
import { fileURLToPath
|
|
5057
|
+
import { fileURLToPath } from "url";
|
|
4787
5058
|
function findPackageJson() {
|
|
4788
|
-
let dir =
|
|
5059
|
+
let dir = path30.dirname(fileURLToPath(import.meta.url));
|
|
4789
5060
|
for (let i = 0; i < 6; i++) {
|
|
4790
|
-
const candidate =
|
|
5061
|
+
const candidate = path30.join(dir, "package.json");
|
|
4791
5062
|
if (fs22.existsSync(candidate)) return candidate;
|
|
4792
|
-
dir =
|
|
5063
|
+
dir = path30.dirname(dir);
|
|
4793
5064
|
}
|
|
4794
5065
|
throw new Error("No se encontr\xF3 package.json cerca del CLI.");
|
|
4795
5066
|
}
|
|
@@ -4809,8 +5080,8 @@ function readPackageMetadata() {
|
|
|
4809
5080
|
}
|
|
4810
5081
|
|
|
4811
5082
|
// src/lib/pi-runtime.ts
|
|
4812
|
-
import
|
|
4813
|
-
import
|
|
5083
|
+
import os4 from "os";
|
|
5084
|
+
import path31 from "path";
|
|
4814
5085
|
import fs23 from "fs";
|
|
4815
5086
|
import { spawnSync } from "child_process";
|
|
4816
5087
|
import { createHash } from "crypto";
|
|
@@ -4964,14 +5235,14 @@ function healthyDoctor(stdout, stderr, packageRunner, candidate) {
|
|
|
4964
5235
|
if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
|
|
4965
5236
|
const packageValue = Reflect.get(record, "package");
|
|
4966
5237
|
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]) &&
|
|
5238
|
+
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
5239
|
} catch {
|
|
4969
5240
|
return false;
|
|
4970
5241
|
}
|
|
4971
5242
|
}
|
|
4972
5243
|
function installPiFromVerifiedTarball(input, deps) {
|
|
4973
5244
|
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 ?
|
|
5245
|
+
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
5246
|
const artifact = deps.download(destination);
|
|
4976
5247
|
if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
|
|
4977
5248
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
@@ -4979,7 +5250,7 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
4979
5250
|
deps.backupSettings();
|
|
4980
5251
|
const scope = {
|
|
4981
5252
|
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4982
|
-
codingAgentDir:
|
|
5253
|
+
codingAgentDir: path31.resolve(paths.codingAgentDir)
|
|
4983
5254
|
};
|
|
4984
5255
|
const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
|
|
4985
5256
|
deps.writeReceiptAtomic(`${JSON.stringify(installing)}
|
|
@@ -5008,30 +5279,30 @@ function installPiFromVerifiedTarball(input, deps) {
|
|
|
5008
5279
|
return { kind: "installed", receipt };
|
|
5009
5280
|
}
|
|
5010
5281
|
function runtimePath(piExecutable) {
|
|
5011
|
-
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null :
|
|
5012
|
-
return [...new Set(entries.filter((entry) => entry !== null))].join(
|
|
5282
|
+
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"];
|
|
5283
|
+
return [...new Set(entries.filter((entry) => entry !== null))].join(path31.delimiter);
|
|
5013
5284
|
}
|
|
5014
5285
|
function targetPaths(targetDir, engramBin, piExecutable) {
|
|
5015
|
-
const root =
|
|
5016
|
-
const codingAgentDir =
|
|
5017
|
-
const home =
|
|
5018
|
-
const temporary =
|
|
5286
|
+
const root = path31.resolve(targetDir);
|
|
5287
|
+
const codingAgentDir = path31.join(root, "pi-agent");
|
|
5288
|
+
const home = path31.join(root, "home");
|
|
5289
|
+
const temporary = path31.join(root, "tmp");
|
|
5019
5290
|
return {
|
|
5020
5291
|
codingAgentDir,
|
|
5021
|
-
receiptPath:
|
|
5022
|
-
packageRunner:
|
|
5292
|
+
receiptPath: path31.join(root, "state", "pi-receipt.json"),
|
|
5293
|
+
packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5023
5294
|
environment: {
|
|
5024
5295
|
HOME: home,
|
|
5025
5296
|
USERPROFILE: home,
|
|
5026
|
-
APPDATA:
|
|
5027
|
-
LOCALAPPDATA:
|
|
5028
|
-
XDG_CONFIG_HOME:
|
|
5029
|
-
XDG_DATA_HOME:
|
|
5030
|
-
XDG_CACHE_HOME:
|
|
5297
|
+
APPDATA: path31.join(root, "appdata"),
|
|
5298
|
+
LOCALAPPDATA: path31.join(root, "localappdata"),
|
|
5299
|
+
XDG_CONFIG_HOME: path31.join(root, "xdg-config"),
|
|
5300
|
+
XDG_DATA_HOME: path31.join(root, "xdg-data"),
|
|
5301
|
+
XDG_CACHE_HOME: path31.join(root, "xdg-cache"),
|
|
5031
5302
|
TEMP: temporary,
|
|
5032
5303
|
TMP: temporary,
|
|
5033
5304
|
TMPDIR: temporary,
|
|
5034
|
-
npm_config_cache:
|
|
5305
|
+
npm_config_cache: path31.join(root, "npm-cache"),
|
|
5035
5306
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5036
5307
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
5037
5308
|
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
@@ -5041,17 +5312,17 @@ function targetPaths(targetDir, engramBin, piExecutable) {
|
|
|
5041
5312
|
};
|
|
5042
5313
|
}
|
|
5043
5314
|
function userPaths(engramBin, piExecutable) {
|
|
5044
|
-
const home =
|
|
5045
|
-
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ??
|
|
5315
|
+
const home = os4.homedir();
|
|
5316
|
+
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent");
|
|
5046
5317
|
return {
|
|
5047
5318
|
codingAgentDir,
|
|
5048
|
-
receiptPath:
|
|
5049
|
-
packageRunner:
|
|
5319
|
+
receiptPath: path31.join(dataDir(), "pi-receipt.json"),
|
|
5320
|
+
packageRunner: path31.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
5050
5321
|
environment: {
|
|
5051
5322
|
HOME: home,
|
|
5052
|
-
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ??
|
|
5053
|
-
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ??
|
|
5054
|
-
TMPDIR:
|
|
5323
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path31.join(home, ".config"),
|
|
5324
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path31.join(home, ".cache"),
|
|
5325
|
+
TMPDIR: os4.tmpdir(),
|
|
5055
5326
|
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
5056
5327
|
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
5057
5328
|
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
@@ -5078,7 +5349,7 @@ function runPiRuntime(input, deps) {
|
|
|
5078
5349
|
return { kind: "blocked", reason: "tarball-integrity" };
|
|
5079
5350
|
}
|
|
5080
5351
|
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(
|
|
5352
|
+
const settingsJson = deps.readSettings(path31.join(paths.codingAgentDir, "settings.json"));
|
|
5082
5353
|
const receiptJson = deps.readReceipt(paths.receiptPath);
|
|
5083
5354
|
const lifecycleInput = {
|
|
5084
5355
|
candidate: PI_RUNTIME_CANDIDATE,
|
|
@@ -5138,14 +5409,14 @@ function readJsonFile(file) {
|
|
|
5138
5409
|
function packageVersionFromExecutable(executable) {
|
|
5139
5410
|
let current;
|
|
5140
5411
|
try {
|
|
5141
|
-
current =
|
|
5412
|
+
current = path31.dirname(fs23.realpathSync(executable));
|
|
5142
5413
|
} catch {
|
|
5143
5414
|
return null;
|
|
5144
5415
|
}
|
|
5145
5416
|
for (let depth = 0; depth < 8; depth++) {
|
|
5146
5417
|
const manifests = [
|
|
5147
|
-
|
|
5148
|
-
|
|
5418
|
+
path31.join(current, "package.json"),
|
|
5419
|
+
path31.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
|
|
5149
5420
|
];
|
|
5150
5421
|
for (const manifest of manifests) {
|
|
5151
5422
|
try {
|
|
@@ -5156,7 +5427,7 @@ function packageVersionFromExecutable(executable) {
|
|
|
5156
5427
|
} catch {
|
|
5157
5428
|
}
|
|
5158
5429
|
}
|
|
5159
|
-
const parent =
|
|
5430
|
+
const parent = path31.dirname(current);
|
|
5160
5431
|
if (parent === current) break;
|
|
5161
5432
|
current = parent;
|
|
5162
5433
|
}
|
|
@@ -5164,23 +5435,23 @@ function packageVersionFromExecutable(executable) {
|
|
|
5164
5435
|
}
|
|
5165
5436
|
function detectPiRuntime() {
|
|
5166
5437
|
const executable = lookPath("pi");
|
|
5167
|
-
const home =
|
|
5438
|
+
const home = os4.homedir();
|
|
5168
5439
|
return {
|
|
5169
5440
|
id: "pi",
|
|
5170
5441
|
name: "Pi",
|
|
5171
5442
|
installed: executable !== null,
|
|
5172
5443
|
executable,
|
|
5173
5444
|
version: executable === null ? null : packageVersionFromExecutable(executable),
|
|
5174
|
-
codingAgentDir: process.env.PI_CODING_AGENT_DIR ??
|
|
5445
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path31.join(home, ".pi", "agent")
|
|
5175
5446
|
};
|
|
5176
5447
|
}
|
|
5177
5448
|
function hasManagedPiRuntime(targetDir) {
|
|
5178
|
-
const receipt = targetDir === void 0 ?
|
|
5449
|
+
const receipt = targetDir === void 0 ? path31.join(dataDir(), "pi-receipt.json") : path31.join(path31.resolve(targetDir), "state", "pi-receipt.json");
|
|
5179
5450
|
return fs23.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
|
|
5180
5451
|
}
|
|
5181
5452
|
function resolvePiEngramBin(targetDir) {
|
|
5182
5453
|
if (targetDir === void 0) return detectEngram();
|
|
5183
|
-
const candidate =
|
|
5454
|
+
const candidate = path31.join(path31.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
|
|
5184
5455
|
return fs23.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
|
|
5185
5456
|
}
|
|
5186
5457
|
function readOptional(file, fallback) {
|
|
@@ -5220,7 +5491,7 @@ async function acquirePiTarball(destination) {
|
|
|
5220
5491
|
}
|
|
5221
5492
|
fs23.rmSync(destination, { force: true });
|
|
5222
5493
|
}
|
|
5223
|
-
fs23.mkdirSync(
|
|
5494
|
+
fs23.mkdirSync(path31.dirname(destination), { recursive: true });
|
|
5224
5495
|
const partial = `${destination}.partial-${process.pid}`;
|
|
5225
5496
|
const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
|
|
5226
5497
|
redirect: "error",
|
|
@@ -5293,7 +5564,7 @@ async function runPiRuntimeSystem(input) {
|
|
|
5293
5564
|
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
5294
5565
|
};
|
|
5295
5566
|
}
|
|
5296
|
-
const destination = input.targetDir === void 0 ?
|
|
5567
|
+
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
5568
|
let artifact;
|
|
5298
5569
|
try {
|
|
5299
5570
|
artifact = await acquirePiTarball(destination);
|
|
@@ -5313,18 +5584,18 @@ async function runPiRuntimeSystem(input) {
|
|
|
5313
5584
|
}, {
|
|
5314
5585
|
download: () => artifact,
|
|
5315
5586
|
backupSettings: () => createBackup(
|
|
5316
|
-
[
|
|
5587
|
+
[path31.join(paths.codingAgentDir, "settings.json")],
|
|
5317
5588
|
"pi-package-install",
|
|
5318
|
-
input.targetDir === void 0 ? void 0 :
|
|
5589
|
+
input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
|
|
5319
5590
|
),
|
|
5320
5591
|
run: runProcess,
|
|
5321
|
-
readSettings: () => readOptional(
|
|
5322
|
-
rewriteSettings: (content) => writeText(
|
|
5592
|
+
readSettings: () => readOptional(path31.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
|
|
5593
|
+
rewriteSettings: (content) => writeText(path31.join(paths.codingAgentDir, "settings.json"), `${content}
|
|
5323
5594
|
`),
|
|
5324
5595
|
writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
|
|
5325
5596
|
});
|
|
5326
5597
|
}
|
|
5327
|
-
const packageRoot =
|
|
5598
|
+
const packageRoot = path31.dirname(path31.dirname(paths.packageRunner));
|
|
5328
5599
|
const writeReceipt = (receipt) => {
|
|
5329
5600
|
writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
|
|
5330
5601
|
`);
|
|
@@ -5342,9 +5613,9 @@ async function runPiRuntimeSystem(input) {
|
|
|
5342
5613
|
value,
|
|
5343
5614
|
{
|
|
5344
5615
|
backupSettings: () => createBackup(
|
|
5345
|
-
[
|
|
5616
|
+
[path31.join(paths.codingAgentDir, "settings.json")],
|
|
5346
5617
|
"pi-package-uninstall",
|
|
5347
|
-
input.targetDir === void 0 ? void 0 :
|
|
5618
|
+
input.targetDir === void 0 ? void 0 : path31.join(path31.resolve(input.targetDir), "backups")
|
|
5348
5619
|
),
|
|
5349
5620
|
run: runProcess,
|
|
5350
5621
|
isPackageAbsent: () => !fs23.existsSync(packageRoot),
|
|
@@ -5445,466 +5716,8 @@ async function runManagedPiSystem(input) {
|
|
|
5445
5716
|
}
|
|
5446
5717
|
|
|
5447
5718
|
// src/lib/quality-runner.ts
|
|
5448
|
-
import
|
|
5719
|
+
import path32 from "path";
|
|
5449
5720
|
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
5721
|
function normalizeLimit(value, name) {
|
|
5909
5722
|
if (value === void 0) return void 0;
|
|
5910
5723
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
@@ -5922,14 +5735,14 @@ function normalizeTimeout(value) {
|
|
|
5922
5735
|
}
|
|
5923
5736
|
return timeoutMs;
|
|
5924
5737
|
}
|
|
5925
|
-
var
|
|
5738
|
+
var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
5926
5739
|
function isRecord3(value) {
|
|
5927
5740
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5928
5741
|
}
|
|
5929
5742
|
function hasText2(value) {
|
|
5930
5743
|
return typeof value === "string" && value.trim() !== "";
|
|
5931
5744
|
}
|
|
5932
|
-
function
|
|
5745
|
+
function isDenseStringArray(value) {
|
|
5933
5746
|
if (!Array.isArray(value)) return false;
|
|
5934
5747
|
for (let index = 0; index < value.length; index += 1) {
|
|
5935
5748
|
if (!Object.prototype.hasOwnProperty.call(value, String(index)) || typeof value[index] !== "string") return false;
|
|
@@ -5939,7 +5752,7 @@ function isDenseStringArray2(value) {
|
|
|
5939
5752
|
function isNonNegativeSafeInteger(value) {
|
|
5940
5753
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
5941
5754
|
}
|
|
5942
|
-
function
|
|
5755
|
+
function isQualityProfile(value) {
|
|
5943
5756
|
return typeof value === "string" && QUALITY_PROFILES.includes(value);
|
|
5944
5757
|
}
|
|
5945
5758
|
function assertValidEnvironment(value, label) {
|
|
@@ -5951,13 +5764,13 @@ function assertValidEnvironment(value, label) {
|
|
|
5951
5764
|
function assertQualityPlanInput(value) {
|
|
5952
5765
|
if (!isRecord3(value)) throw new Error("Invalid quality plan");
|
|
5953
5766
|
if (!isRecord3(value.identity)) throw new Error("Invalid quality plan identity");
|
|
5954
|
-
if (typeof value.identity.baseSha !== "string" || !
|
|
5767
|
+
if (typeof value.identity.baseSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.baseSha)) {
|
|
5955
5768
|
throw new Error("Invalid quality plan identity.baseSha");
|
|
5956
5769
|
}
|
|
5957
|
-
if (typeof value.identity.headSha !== "string" || !
|
|
5770
|
+
if (typeof value.identity.headSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.headSha)) {
|
|
5958
5771
|
throw new Error("Invalid quality plan identity.headSha");
|
|
5959
5772
|
}
|
|
5960
|
-
if (!
|
|
5773
|
+
if (!isQualityProfile(value.profile)) throw new Error("Invalid quality plan profile");
|
|
5961
5774
|
if (!Array.isArray(value.controls)) throw new Error("Invalid quality plan controls");
|
|
5962
5775
|
const controlIds = /* @__PURE__ */ new Set();
|
|
5963
5776
|
for (let index = 0; index < value.controls.length; index += 1) {
|
|
@@ -5980,7 +5793,7 @@ function assertQualityPlanInput(value) {
|
|
|
5980
5793
|
const command = value.commands[index];
|
|
5981
5794
|
if (!isRecord3(command)) throw new Error(`Invalid quality plan commands[${index}]`);
|
|
5982
5795
|
const timeoutMs = command.timeoutMs;
|
|
5983
|
-
if (!hasText2(command.controlId) || !hasText2(command.commandId) || !hasText2(command.executable) || !
|
|
5796
|
+
if (!hasText2(command.controlId) || !hasText2(command.commandId) || !hasText2(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
|
|
5984
5797
|
throw new Error(`Invalid quality plan commands[${index}]`);
|
|
5985
5798
|
}
|
|
5986
5799
|
if (timeoutMs > MAX_NODE_TIMEOUT_MS) {
|
|
@@ -6035,7 +5848,7 @@ function killProcessTree(child) {
|
|
|
6035
5848
|
if (pid === void 0) return;
|
|
6036
5849
|
if (process.platform === "win32") {
|
|
6037
5850
|
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR ?? "C:\\Windows";
|
|
6038
|
-
const taskkill =
|
|
5851
|
+
const taskkill = path32.join(systemRoot, "System32", "taskkill.exe");
|
|
6039
5852
|
const result = spawnSync2(taskkill, ["/pid", String(pid), "/t", "/f"], {
|
|
6040
5853
|
shell: false,
|
|
6041
5854
|
stdio: "ignore",
|
|
@@ -6261,11 +6074,15 @@ async function runQualityPlan(input) {
|
|
|
6261
6074
|
results.push(receiptResultFor(command.controlId, result));
|
|
6262
6075
|
}
|
|
6263
6076
|
results.push(...missingRequiredResults(input.controls, input.commands));
|
|
6264
|
-
const
|
|
6077
|
+
const policyEvaluation = evaluateQualityPolicy({
|
|
6265
6078
|
profile: input.profile,
|
|
6266
6079
|
controls: input.controls,
|
|
6267
6080
|
results
|
|
6268
6081
|
});
|
|
6082
|
+
const evaluation = {
|
|
6083
|
+
...policyEvaluation,
|
|
6084
|
+
status: localQualityStatus(input.profile, policyEvaluation.status)
|
|
6085
|
+
};
|
|
6269
6086
|
const receipt = createQualityReceipt({
|
|
6270
6087
|
authority: "local",
|
|
6271
6088
|
identity,
|