jorgex-stack 1.9.62 → 1.9.63

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.
@@ -1,693 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- isContainedIn,
4
- readTextIfExists,
5
- writeText
6
- } from "./chunk-B76ZJXXD.js";
7
- import {
8
- HOME,
9
- dataDir
10
- } from "./chunk-NQZVJFV2.js";
11
-
12
- // src/lib/filemerge.ts
13
- function markers(name) {
14
- return { open: `<!-- jorgex:${name} -->`, close: `<!-- /jorgex:${name} -->` };
15
- }
16
- function hasHealthyManagedMarkdownMarkers(existing, name) {
17
- const { open, close } = markers(name);
18
- const count = (marker) => existing.split(marker).length - 1;
19
- const onOwnLine = (marker) => existing.split("\n").some((line) => line.trim() === marker);
20
- return count(open) === 1 && count(close) === 1 && existing.indexOf(close) > existing.indexOf(open) && onOwnLine(open) && onOwnLine(close);
21
- }
22
- function repairOrphanMarkers(existing, name) {
23
- const { open, close } = markers(name);
24
- const count = (marker) => existing.split(marker).length - 1;
25
- const opens = count(open);
26
- const closes = count(close);
27
- if (opens === 0 && closes === 0) return existing;
28
- if (hasHealthyManagedMarkdownMarkers(existing, name)) return existing;
29
- 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");
30
- }
31
- function upsertMarkdownSection(existing, name, content) {
32
- const { open, close } = markers(name);
33
- const block = `${open}
34
- ${content.trim()}
35
- ${close}`;
36
- if (existing === null || existing.trim() === "") return block + "\n";
37
- existing = repairOrphanMarkers(existing, name);
38
- const start = existing.indexOf(open);
39
- const end = existing.indexOf(close);
40
- if (start !== -1 && end !== -1 && end > start) {
41
- return existing.slice(0, start) + block + existing.slice(end + close.length);
42
- }
43
- const sep = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
44
- return existing + sep + block + "\n";
45
- }
46
- function removeMarkdownSection(existing, name) {
47
- existing = repairOrphanMarkers(existing, name);
48
- const { open, close } = markers(name);
49
- const start = existing.indexOf(open);
50
- const end = existing.indexOf(close);
51
- if (start === -1 || end === -1 || end < start) return existing;
52
- const before = existing.slice(0, start).replace(/\n+$/, "\n");
53
- const after = existing.slice(end + close.length).replace(/^\n+/, "\n");
54
- const joined = before + after;
55
- return joined.trim() === "" ? "" : joined.replace(/^\n+/, "");
56
- }
57
- function stripLeadingHtmlComments(md) {
58
- let out = md.trimStart();
59
- while (out.startsWith("<!--")) {
60
- const end = out.indexOf("-->");
61
- if (end === -1) break;
62
- out = out.slice(end + 3).trimStart();
63
- }
64
- return out;
65
- }
66
- function upsertJson(existing, mutate) {
67
- let root = {};
68
- if (existing !== null && existing.trim() !== "") {
69
- root = JSON.parse(existing);
70
- }
71
- mutate(root);
72
- return JSON.stringify(root, null, 2) + "\n";
73
- }
74
- function tomlRootEnd(lines) {
75
- const mask = multilineStringMask(lines);
76
- const index = lines.findIndex((line, lineIndex) => !mask[lineIndex] && headerName(line) !== null);
77
- return index === -1 ? lines.length : index;
78
- }
79
- function rootKeyLineIndex(lines, key) {
80
- const end = tomlRootEnd(lines);
81
- const mask = multilineStringMask(lines);
82
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
- const pattern = new RegExp(`^\\s*(?:${escaped}|"${escaped}"|'${escaped}')\\s*=`);
84
- return lines.slice(0, end).findIndex((line, index) => !mask[index] && pattern.test(line));
85
- }
86
- function hasTomlRootKey(existing, key) {
87
- if (existing === null || existing === "") return false;
88
- return rootKeyLineIndex(existing.replace(/\r\n/g, "\n").split("\n"), key) !== -1;
89
- }
90
- function upsertTomlRootKeyIfMissing(existing, key, value) {
91
- if (hasTomlRootKey(existing, key)) return existing;
92
- const eol = existing?.includes("\r\n") ? "\r\n" : "\n";
93
- const normalized = (existing ?? "").replace(/\r\n/g, "\n");
94
- const lines = normalized === "" ? [] : normalized.split("\n");
95
- const index = tomlRootEnd(lines);
96
- lines.splice(index, 0, `${key} = ${value}`);
97
- return lines.join(eol);
98
- }
99
- function removeTomlRootKeyIfExact(existing, key, value) {
100
- const eol = existing.includes("\r\n") ? "\r\n" : "\n";
101
- const normalized = existing.replace(/\r\n/g, "\n");
102
- const lines = normalized.split("\n");
103
- const index = rootKeyLineIndex(lines, key);
104
- if (index === -1) return existing;
105
- const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
106
- const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
107
- const exact = new RegExp(`^\\s*(?:${escapedKey}|"${escapedKey}"|'${escapedKey}')\\s*=\\s*${escapedValue}\\s*(?:#.*)?$`);
108
- if (!exact.test(lines[index])) return existing;
109
- lines.splice(index, 1);
110
- return lines.join(eol);
111
- }
112
- function headerName(line) {
113
- const match = /^\s*\[\s*([^\]]+?)\s*\]\s*(#.*)?$/.exec(line);
114
- if (!match) return null;
115
- return match[1].split(".").map((part) => part.trim().replace(/^["']|["']$/g, "")).join(".");
116
- }
117
- function quoteRunLength(line, start, quote) {
118
- let end = start + 1;
119
- while (end < line.length && line[end] === quote) end++;
120
- return end - start;
121
- }
122
- function skipShortString(line, start, quote) {
123
- let index = start + 1;
124
- while (index < line.length) {
125
- if (line[index] === quote) return index + 1;
126
- if (quote === '"' && line[index] === "\\") {
127
- index += 2;
128
- continue;
129
- }
130
- index++;
131
- }
132
- return line.length;
133
- }
134
- function scanTomlLine(line, initial) {
135
- let inside = initial;
136
- let index = 0;
137
- while (index < line.length) {
138
- if (inside !== null) {
139
- const quote = inside === '"""' ? '"' : "'";
140
- if (line[index] === quote) {
141
- const run2 = quoteRunLength(line, index, quote);
142
- if (run2 >= 3) {
143
- inside = null;
144
- }
145
- index += run2;
146
- continue;
147
- }
148
- if (inside === '"""' && line[index] === "\\") {
149
- index += 2;
150
- continue;
151
- }
152
- index++;
153
- continue;
154
- }
155
- const char = line[index];
156
- if (char === "#") break;
157
- if (char !== '"' && char !== "'") {
158
- index++;
159
- continue;
160
- }
161
- const run = quoteRunLength(line, index, char);
162
- if (run >= 3) {
163
- inside = char === '"' ? '"""' : "'''";
164
- index += 3;
165
- } else {
166
- index = skipShortString(line, index, char);
167
- }
168
- }
169
- return inside;
170
- }
171
- function multilineStringMask(lines) {
172
- const mask = [];
173
- let inside = null;
174
- for (const line of lines) {
175
- mask.push(inside !== null);
176
- inside = scanTomlLine(line, inside);
177
- }
178
- return mask;
179
- }
180
- function findTomlSection(lines, section) {
181
- const mask = multilineStringMask(lines);
182
- const start = lines.findIndex((line, i) => !mask[i] && headerName(line) === section);
183
- if (start === -1) return null;
184
- let end = start + 1;
185
- while (end < lines.length && (mask[end] || headerName(lines[end]) === null)) end++;
186
- return { start, end };
187
- }
188
- function upsertTomlSection(existing, section, body) {
189
- const header = `[${section}]`;
190
- const target = headerName(header) ?? section;
191
- const block = `${header}
192
- ${body.trim()}
193
- `;
194
- if (existing === null || existing.trim() === "") return block;
195
- const rawLines = existing.split("\n");
196
- const lines = rawLines.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
197
- const lineStarts = [];
198
- let offset = 0;
199
- for (let index = 0; index < rawLines.length; index++) {
200
- lineStarts.push(offset);
201
- offset += rawLines[index].length;
202
- if (index < rawLines.length - 1) offset++;
203
- }
204
- const found = findTomlSection(lines, target);
205
- if (found === null) {
206
- const sep = existing.endsWith("\n") ? "\n" : "\n\n";
207
- return existing + sep + block;
208
- }
209
- let sectionEnd = found.end;
210
- while (sectionEnd > found.start + 1 && lines[sectionEnd - 1].trim() === "") sectionEnd--;
211
- const startOffset = lineStarts[found.start];
212
- const endOffset = lineStarts[sectionEnd] ?? existing.length;
213
- return existing.slice(0, startOffset) + block.trimEnd() + "\n" + existing.slice(endOffset);
214
- }
215
- function removeTomlSection(existing, section) {
216
- const eol = existing.includes("\r\n") ? "\r\n" : "\n";
217
- const lines = existing.split(/\r?\n/);
218
- const found = findTomlSection(lines, section);
219
- if (found === null) return existing;
220
- let realStart = found.start;
221
- while (realStart > 0 && lines[realStart - 1].trim() === "") realStart--;
222
- const result = [...lines.slice(0, realStart), ...lines.slice(found.end)].join(eol);
223
- if (result.trim() === "") return "";
224
- return result.endsWith(eol) ? result : result + eol;
225
- }
226
- function readTomlSection(existing, section) {
227
- if (existing === null) return null;
228
- const lines = existing.split(/\r?\n/);
229
- const found = findTomlSection(lines, section);
230
- if (found === null) return null;
231
- return lines.slice(found.start + 1, found.end).join("\n");
232
- }
233
- function hasTomlChildSection(existing, section) {
234
- if (existing === null) return false;
235
- const lines = existing.split(/\r?\n/);
236
- const mask = multilineStringMask(lines);
237
- return lines.some((line, index) => !mask[index] && headerName(line)?.startsWith(`${section}.`) === true);
238
- }
239
-
240
- // src/adapters/types.ts
241
- var LEGACY_SYSTEM_PROMPT_SECTIONS = ["engram-protocol"];
242
-
243
- // src/lib/system-prompt-sections.ts
244
- import fs from "fs";
245
- import path from "path";
246
- var SYSTEM_PROMPT_SECTIONS = [
247
- "system-prompt",
248
- "context7",
249
- "playwright",
250
- "chrome-devtools",
251
- "browser",
252
- "writing-style"
253
- ];
254
- var KNOWN_SECTIONS = [...SYSTEM_PROMPT_SECTIONS, ...LEGACY_SYSTEM_PROMPT_SECTIONS];
255
- function assertSystemPromptMarkers(content, target) {
256
- if (content === null) return;
257
- const ranges = [];
258
- for (const section of KNOWN_SECTIONS) {
259
- const markers2 = [...content.matchAll(new RegExp(`<!--\\s*\\/?jorgex:${section}(?=[\\s>]|-->|$)`, "g"))];
260
- if (markers2.length === 0) continue;
261
- if (markers2.length !== 2 || !hasHealthyManagedMarkdownMarkers(content, section)) {
262
- throw new Error(`Marcadores jorgex:${section} ambiguos en ${target}. Repara el bloque antes de reintentar; su contenido se conserva.`);
263
- }
264
- ranges.push({
265
- section,
266
- start: content.indexOf(`<!-- jorgex:${section} -->`),
267
- end: content.indexOf(`<!-- /jorgex:${section} -->`) + `<!-- /jorgex:${section} -->`.length
268
- });
269
- }
270
- ranges.sort((a, b) => a.start - b.start);
271
- for (let index = 1; index < ranges.length; index++) {
272
- if (ranges[index].start < ranges[index - 1].end) {
273
- throw new Error(`Marcadores jorgex:${ranges[index].section} anidados en ${target}. Separa los bloques antes de reintentar; su contenido se conserva.`);
274
- }
275
- }
276
- }
277
- function readSystemPromptFile(target, rootDir) {
278
- if (rootDir !== void 0) {
279
- const probe = fs.existsSync(target) ? target : path.dirname(target);
280
- if (fs.existsSync(probe) && fs.existsSync(rootDir)) {
281
- const resolved = fs.realpathSync(probe);
282
- const root = fs.realpathSync(rootDir);
283
- if (resolved !== root && !isContainedIn(resolved, root)) {
284
- throw new Error(`El prompt ${target} sale del destino aislado.`);
285
- }
286
- }
287
- }
288
- let bytes;
289
- try {
290
- bytes = fs.readFileSync(target);
291
- } catch (error) {
292
- if (error.code === "ENOENT") return null;
293
- throw new Error(`No se puede leer el prompt ${target}. Revisa los permisos y el tipo de archivo.`, { cause: error });
294
- }
295
- try {
296
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
297
- } catch (error) {
298
- throw new Error(`El prompt ${target} debe usar UTF-8 v\xE1lido.`, { cause: error });
299
- }
300
- }
301
- function assertSystemPromptFile(target, rootDir) {
302
- assertSystemPromptMarkers(readSystemPromptFile(target, rootDir), target);
303
- }
304
- function removeSystemPromptSections(content) {
305
- assertSystemPromptMarkers(content, "system prompt");
306
- return KNOWN_SECTIONS.reduce((current, section) => removeMarkdownSection(current, section), content);
307
- }
308
-
309
- // src/lib/canonical.ts
310
- import path2 from "path";
311
- import fs2 from "fs";
312
- function parseCanonicalAgent(source, file) {
313
- const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
314
- if (!match) throw new Error(`Agente sin frontmatter: ${file}`);
315
- const fields = {};
316
- for (const line of match[1].split(/\r?\n/)) {
317
- const sep = line.indexOf(":");
318
- if (sep === -1) continue;
319
- fields[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
320
- }
321
- const required = ["name", "description", "mode", "tier"];
322
- for (const key of required) {
323
- if (!fields[key]) throw new Error(`Agente ${file}: falta '${key}' en el frontmatter`);
324
- }
325
- return {
326
- name: fields.name,
327
- description: fields.description,
328
- mode: fields.mode === "primary" ? "primary" : "subagent",
329
- tier: fields.tier,
330
- readonly: fields.readonly === "true",
331
- bash: fields.bash ?? "full",
332
- spawn: fields.spawn !== "false",
333
- body: source.slice(match[0].length)
334
- };
335
- }
336
- function loadCanonicalAgents(agentsDir) {
337
- return fs2.readdirSync(agentsDir).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md").map((f) => {
338
- const file = path2.join(agentsDir, f);
339
- const source = fs2.readFileSync(file, "utf8").replace(/\r\n/g, "\n");
340
- return parseCanonicalAgent(source, file);
341
- });
342
- }
343
- var DEVTOOLS_MCP_SERVER = "chrome-devtools";
344
- function isCanonicalMcpServerEnabled(name, server, enabledServers) {
345
- return !server.optional || server.defaultEnabled === true || enabledServers?.has(name) === true;
346
- }
347
- function loadCanonicalMcp(stackDir) {
348
- return JSON.parse(fs2.readFileSync(path2.join(stackDir, "mcp", "servers.json"), "utf8"));
349
- }
350
- function loadCanonicalHooks(stackDir) {
351
- return JSON.parse(fs2.readFileSync(path2.join(stackDir, "hooks", "hooks.json"), "utf8"));
352
- }
353
- function loadCanonicalDefaults(stackDir) {
354
- const file = path2.join(stackDir, "config", "defaults.json");
355
- if (!fs2.existsSync(file)) return {};
356
- const parsed = JSON.parse(fs2.readFileSync(file, "utf8"));
357
- delete parsed["$comment"];
358
- return parsed;
359
- }
360
-
361
- // src/lib/model-map.ts
362
- import path3 from "path";
363
- import { existsSync } from "fs";
364
- function resolveAgentModel(models, agentName, tier) {
365
- const base = models[tier];
366
- const override = models.overrides?.[agentName];
367
- if (!override) return base;
368
- return {
369
- model: override.model || base.model,
370
- variant: "variant" in override ? override.variant || void 0 : base.variant
371
- };
372
- }
373
- var DEFAULT_MODEL_MAP = {
374
- "claude-code": {
375
- strong: { model: "fable" },
376
- standard: { model: "sonnet" },
377
- cheap: { model: "haiku" }
378
- },
379
- // El primary (orchestrator) no usa estos tiers: tanto el profile de CLI como
380
- // la skill de la app heredan el modelo elegido por el usuario. Estos defaults
381
- // son solo para subagentes; variant → model_reasoning_effort.
382
- codex: {
383
- strong: { model: "gpt-6-astra", variant: "low" },
384
- standard: { model: "gpt-5.6-luna", variant: "max" },
385
- cheap: { model: "gpt-5.6-luna", variant: "medium" }
386
- }
387
- };
388
- function modelMapFile() {
389
- return path3.join(dataDir(), "model-map.json");
390
- }
391
- function loadModelMap() {
392
- const file = modelMapFile();
393
- const raw = readTextIfExists(file);
394
- if (raw === null) return DEFAULT_MODEL_MAP;
395
- let fromDisk;
396
- try {
397
- fromDisk = JSON.parse(raw);
398
- } catch {
399
- throw new Error(`El mapa de modelos ${file} contiene JSON inv\xE1lido. Corrige el JSON o restaura una copia antes de continuar.`);
400
- }
401
- const merged = { ...fromDisk };
402
- for (const id of Object.keys(DEFAULT_MODEL_MAP)) {
403
- merged[id] = { ...DEFAULT_MODEL_MAP[id], ...fromDisk[id] ?? {} };
404
- if (fromDisk[id] && !Object.hasOwn(fromDisk[id], "overrides")) delete merged[id].overrides;
405
- }
406
- return merged;
407
- }
408
- function ensureModelMapFile() {
409
- const file = modelMapFile();
410
- if (!existsSync(file)) {
411
- writeText(file, JSON.stringify(DEFAULT_MODEL_MAP, null, 2) + "\n");
412
- }
413
- return file;
414
- }
415
-
416
- // src/lib/detect.ts
417
- import path4 from "path";
418
- import { existsSync as existsSync2, statSync } from "fs";
419
- import { execFileSync } from "child_process";
420
- function lookPath(cmd, env = process.env) {
421
- const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
422
- for (const dir of (env.PATH ?? "").split(path4.delimiter)) {
423
- if (!dir) continue;
424
- for (const ext of exts) {
425
- const candidate = path4.join(dir, cmd + ext);
426
- if (statSync(candidate, { throwIfNoEntry: false })?.isFile()) return candidate;
427
- }
428
- }
429
- return null;
430
- }
431
- function planDetectedBinCommand(bin, args) {
432
- if (process.platform !== "win32" || !/\.(cmd|bat)$/i.test(bin)) return { command: bin, args };
433
- if ([bin, ...args].some((part) => /[&|<>()^%!"\r\n]/.test(part))) return null;
434
- const quote = (part) => part === "" || /\s/.test(part) ? `"${part}"` : part;
435
- return {
436
- command: process.env.ComSpec ?? "cmd.exe",
437
- args: ["/d", "/s", "/c", [quote(bin), ...args.map(quote)].join(" ")]
438
- };
439
- }
440
- function runDetectedBin(bin, args, timeoutMs, envOverrides) {
441
- try {
442
- const command = planDetectedBinCommand(bin, args);
443
- if (command === null) return null;
444
- return execFileSync(command.command, command.args, {
445
- encoding: "utf8",
446
- timeout: timeoutMs,
447
- stdio: ["ignore", "pipe", "pipe"],
448
- ...envOverrides === void 0 ? {} : { env: { ...process.env, ...envOverrides } }
449
- });
450
- } catch {
451
- return null;
452
- }
453
- }
454
- function detectOpenCode() {
455
- const configDir = process.env.OPENCODE_CONFIG_DIR ?? path4.join(HOME, ".config", "opencode");
456
- const binPath = lookPath("opencode");
457
- return {
458
- id: "opencode",
459
- name: "OpenCode",
460
- installed: binPath !== null || existsSync2(configDir),
461
- binPath,
462
- configDir
463
- };
464
- }
465
- function detectClaudeCode() {
466
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? path4.join(HOME, ".claude");
467
- const binPath = lookPath("claude");
468
- return {
469
- id: "claude-code",
470
- name: "Claude Code",
471
- installed: binPath !== null || existsSync2(configDir),
472
- binPath,
473
- configDir
474
- };
475
- }
476
- function detectCodex() {
477
- const configDir = process.env.CODEX_HOME ?? path4.join(HOME, ".codex");
478
- const binPath = lookPath("codex");
479
- return {
480
- id: "codex",
481
- name: "Codex CLI",
482
- installed: binPath !== null || existsSync2(configDir),
483
- binPath,
484
- configDir
485
- };
486
- }
487
- function detectEngram() {
488
- const fromEnv = process.env.ENGRAM_BIN;
489
- if (fromEnv && existsSync2(fromEnv)) return fromEnv;
490
- const onPath = lookPath("engram");
491
- if (onPath) return onPath;
492
- const candidates = [
493
- path4.join(HOME, "go", "bin", "engram.exe"),
494
- path4.join(HOME, "go", "bin", "engram"),
495
- path4.join(HOME, ".local", "bin", "engram.exe"),
496
- path4.join(HOME, ".local", "bin", "engram")
497
- ];
498
- for (const c of candidates) if (existsSync2(c)) return c;
499
- return null;
500
- }
501
- function engramVersion(bin) {
502
- const out = runDetectedBin(bin, ["--version"], 5e3);
503
- if (out === null) return null;
504
- return /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(out)?.[1] ?? out.trim().split("\n")[0] ?? null;
505
- }
506
-
507
- // src/lib/hooks-format.ts
508
- import path5 from "path";
509
- function hookScriptNames(canonical) {
510
- 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));
511
- }
512
- function plainHookCommands(canonical) {
513
- return new Set(
514
- Object.values(canonical.hooks).flat().flatMap((entry) => entry.hooks).map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"))
515
- );
516
- }
517
- function removeNativeHooks(existing, canonical) {
518
- if (existing === null || existing.trim() === "") return existing;
519
- const scriptNames = hookScriptNames(canonical);
520
- const plainCommands = plainHookCommands(canonical);
521
- return upsertJson(existing, (root) => {
522
- const hooks = root["hooks"];
523
- if (!hooks) return;
524
- for (const event of Object.keys(hooks)) {
525
- const list = hooks[event];
526
- if (!Array.isArray(list)) continue;
527
- hooks[event] = list.filter((entry) => {
528
- const e = entry;
529
- if (!Array.isArray(e?.hooks)) return true;
530
- const ours = e.hooks.some((hh) => {
531
- const cmd = String(hh?.command ?? "");
532
- return scriptNames.some((s) => cmd.includes(s)) || plainCommands.has(cmd);
533
- });
534
- return !ours;
535
- });
536
- if (hooks[event].length === 0) delete hooks[event];
537
- }
538
- if (Object.keys(hooks).length === 0) delete root["hooks"];
539
- });
540
- }
541
- function upsertNativeHooks(existing, canonical, scriptsDir, mapMatcher = (m) => m) {
542
- const scriptsDirForCommand = scriptsDir.replace(/\\/g, "/");
543
- return upsertJson(existing, (root) => {
544
- const hooks = root["hooks"] ??= {};
545
- for (const [event, entries] of Object.entries(canonical.hooks)) {
546
- const list = hooks[event] ??= [];
547
- for (const entry of entries) {
548
- const matcher = entry.matcher !== void 0 ? mapMatcher(entry.matcher) : void 0;
549
- const rendered = {
550
- ...matcher !== void 0 ? { matcher } : {},
551
- hooks: entry.hooks.map((h) => ({
552
- type: h.type,
553
- command: h.command.replace(/\{\{SCRIPTS_DIR\}\}/g, scriptsDirForCommand),
554
- ...h.timeout !== void 0 ? { timeout: h.timeout } : {}
555
- }))
556
- };
557
- const scriptNames = entry.hooks.map((h) => /\{\{SCRIPTS_DIR\}\}[/\\]([\w./-]+)/.exec(h.command)?.[1]).filter((s) => s !== void 0).map((s) => path5.basename(s));
558
- const plainCommands = entry.hooks.map((h) => h.command).filter((c) => !c.includes("{{SCRIPTS_DIR}}"));
559
- const index = list.findIndex((existingEntry) => {
560
- const e = existingEntry;
561
- if (!Array.isArray(e?.hooks)) return false;
562
- return e.hooks.some((hh) => {
563
- const cmd = String(hh?.command ?? "");
564
- return scriptNames.some((s) => cmd.includes(s)) || plainCommands.includes(cmd);
565
- });
566
- });
567
- if (index >= 0) list[index] = rendered;
568
- else list.push(rendered);
569
- }
570
- }
571
- });
572
- }
573
-
574
- // src/lib/quality-capabilities.ts
575
- var QUALITY_CAPABILITY_NAMESPACE = "jorgex.quality.capabilities";
576
- var QUALITY_CAPABILITY_VERSION = 1;
577
- var QUALITY_CAPABILITY_IDS = [
578
- "policy-guidance",
579
- "tool-approval",
580
- "external-verification"
581
- ];
582
- var UNAVAILABLE_REASON = "No reviewed local declaration is available";
583
- var EXTERNAL_VERIFICATION_REASON = "External verification is available only through the external verifier";
584
- function hasText(value) {
585
- return typeof value === "string" && value.trim() !== "";
586
- }
587
- function isRuntime(value) {
588
- return value === "claude-code" || value === "codex" || value === "opencode" || value === "pi";
589
- }
590
- function hasValidEvidence(value) {
591
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
592
- const evidence = value;
593
- return hasText(evidence.source) && hasText(evidence.version);
594
- }
595
- function unavailableCapability(id, reason = UNAVAILABLE_REASON) {
596
- return { id, state: "unavailable", reason };
597
- }
598
- function declarationFor(id, declarations, runtime) {
599
- if (runtime === "unknown") {
600
- return unavailableCapability(id, "Runtime is unknown; local capability cannot be established");
601
- }
602
- if (id === "external-verification") {
603
- return unavailableCapability(id, EXTERNAL_VERIFICATION_REASON);
604
- }
605
- const matches = declarations.filter((declaration2) => declaration2?.id === id);
606
- if (matches.length === 0) return unavailableCapability(id);
607
- if (matches.length !== 1) return unavailableCapability(id, "Duplicate capability declarations are ambiguous");
608
- const declaration = matches[0];
609
- const state = declaration.state;
610
- const reason = hasText(declaration.reason) ? declaration.reason.trim() : void 0;
611
- if (!reason) return unavailableCapability(id, "Capability declaration has no reason");
612
- if (state !== "prompt-only" && state !== "manual" && state !== "unavailable") {
613
- return unavailableCapability(id, "Capability state is not a supported local state");
614
- }
615
- if (state === "unavailable") return { id, state, reason };
616
- if (!hasValidEvidence(declaration.evidence)) {
617
- return unavailableCapability(id, "Capability declaration lacks reviewed source/version evidence");
618
- }
619
- const evidence = declaration.evidence;
620
- return {
621
- id,
622
- state,
623
- reason,
624
- evidence: {
625
- source: evidence.source.trim(),
626
- version: evidence.version.trim()
627
- }
628
- };
629
- }
630
- function hasManagedMarkdownSection(content, name) {
631
- return content !== null && hasHealthyManagedMarkdownMarkers(content, name);
632
- }
633
- function createLocalCapabilityReport(runtime, declarations) {
634
- const normalizedRuntime = isRuntime(runtime) ? runtime : "unknown";
635
- return {
636
- namespace: QUALITY_CAPABILITY_NAMESPACE,
637
- version: QUALITY_CAPABILITY_VERSION,
638
- runtime: normalizedRuntime,
639
- capabilities: QUALITY_CAPABILITY_IDS.map((id) => declarationFor(id, declarations, normalizedRuntime))
640
- };
641
- }
642
- function localQualityStatus(profile, status) {
643
- if ((profile === "high" || profile === "release") && status === "pass") return "incomplete";
644
- return status;
645
- }
646
-
647
- export {
648
- hasHealthyManagedMarkdownMarkers,
649
- upsertMarkdownSection,
650
- removeMarkdownSection,
651
- stripLeadingHtmlComments,
652
- upsertJson,
653
- hasTomlRootKey,
654
- upsertTomlRootKeyIfMissing,
655
- removeTomlRootKeyIfExact,
656
- headerName,
657
- multilineStringMask,
658
- upsertTomlSection,
659
- removeTomlSection,
660
- readTomlSection,
661
- hasTomlChildSection,
662
- LEGACY_SYSTEM_PROMPT_SECTIONS,
663
- SYSTEM_PROMPT_SECTIONS,
664
- assertSystemPromptMarkers,
665
- readSystemPromptFile,
666
- assertSystemPromptFile,
667
- removeSystemPromptSections,
668
- loadCanonicalAgents,
669
- DEVTOOLS_MCP_SERVER,
670
- isCanonicalMcpServerEnabled,
671
- loadCanonicalMcp,
672
- loadCanonicalHooks,
673
- loadCanonicalDefaults,
674
- resolveAgentModel,
675
- DEFAULT_MODEL_MAP,
676
- modelMapFile,
677
- loadModelMap,
678
- ensureModelMapFile,
679
- lookPath,
680
- planDetectedBinCommand,
681
- runDetectedBin,
682
- detectOpenCode,
683
- detectClaudeCode,
684
- detectCodex,
685
- detectEngram,
686
- engramVersion,
687
- hookScriptNames,
688
- removeNativeHooks,
689
- upsertNativeHooks,
690
- hasManagedMarkdownSection,
691
- createLocalCapabilityReport,
692
- localQualityStatus
693
- };