jorgex-stack 1.6.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/cli.js CHANGED
@@ -26,6 +26,7 @@ import * as p from "@clack/prompts";
26
26
  // src/adapters/opencode.ts
27
27
  import path6 from "path";
28
28
  import fs3 from "fs";
29
+ import { isDeepStrictEqual } from "util";
29
30
  import { pathToFileURL } from "url";
30
31
 
31
32
  // src/lib/canonical.ts
@@ -300,15 +301,19 @@ function detectEngram() {
300
301
  function markers(name) {
301
302
  return { open: `<!-- jorgex:${name} -->`, close: `<!-- /jorgex:${name} -->` };
302
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
+ }
303
310
  function repairOrphanMarkers(existing, name) {
304
311
  const { open, close } = markers(name);
305
312
  const count = (marker) => existing.split(marker).length - 1;
306
313
  const opens = count(open);
307
314
  const closes = count(close);
308
315
  if (opens === 0 && closes === 0) return existing;
309
- const onOwnLine = (marker) => existing.split("\n").some((line) => line.trim() === marker);
310
- const healthy = opens === 1 && closes === 1 && existing.indexOf(close) > existing.indexOf(open) && onOwnLine(open) && onOwnLine(close);
311
- if (healthy) return existing;
316
+ if (hasHealthyManagedMarkdownMarkers(existing, name)) return existing;
312
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");
313
318
  }
314
319
  function upsertMarkdownSection(existing, name, content) {
@@ -540,6 +545,79 @@ var DESTRUCTIVE_GIT_DENY = [
540
545
  ];
541
546
  var GIT_GUARD_SCRIPT = "block-destructive-git.cjs";
542
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
+
543
621
  // src/adapters/opencode.ts
544
622
  function yamlString(value) {
545
623
  return JSON.stringify(value);
@@ -580,10 +658,39 @@ function pruneEmpty(parent, key) {
580
658
  const value = objectValue(parent[key]);
581
659
  if (value !== null && Object.keys(value).length === 0) delete parent[key];
582
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
+ }
583
673
  var opencodeAdapter = {
584
674
  id: "opencode",
585
675
  name: "OpenCode",
586
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
+ },
587
694
  injectEngramProtocol() {
588
695
  return false;
589
696
  },
@@ -914,6 +1021,7 @@ ${agent.body}`,
914
1021
  // src/adapters/claude-code.ts
915
1022
  import path7 from "path";
916
1023
  import fs4 from "fs";
1024
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
917
1025
  function yamlString2(value) {
918
1026
  return JSON.stringify(value);
919
1027
  }
@@ -924,6 +1032,21 @@ var ENGRAM_AGENT_TOOLS = [
924
1032
  "Skill",
925
1033
  ...memoryTools(["mem_context", "mem_search", "mem_get_observation", "mem_timeline", "mem_current_project"])
926
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
+ }
927
1050
  function toolsFor(agent) {
928
1051
  if (agent.name === "engram") return ENGRAM_AGENT_TOOLS.join(", ");
929
1052
  if (!agent.readonly) return null;
@@ -956,6 +1079,23 @@ var claudeCodeAdapter = {
956
1079
  id: "claude-code",
957
1080
  name: "Claude Code",
958
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
+ },
959
1099
  injectEngramProtocol(ctx) {
960
1100
  return !hasEngramPlugin(ctx.configDir);
961
1101
  },
@@ -1172,10 +1312,167 @@ function hasEngramProtocol(configDir) {
1172
1312
  const config = readTextIfExists(path8.join(configDir, "config.toml"));
1173
1313
  return config !== null && /engram-instructions\.md/.test(config);
1174
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
+ }
1175
1450
  var codexAdapter = {
1176
1451
  id: "codex",
1177
1452
  name: "Codex CLI",
1178
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
+ },
1179
1476
  injectEngramProtocol(ctx) {
1180
1477
  return !hasEngramProtocol(ctx.configDir);
1181
1478
  },
@@ -1275,37 +1572,20 @@ ${body}`
1275
1572
  ctx.warnings.push(
1276
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."
1277
1574
  );
1278
- content = upsertTomlSection(content, "permissions.jorgex-read-anywhere", 'extends = ":workspace"');
1279
1575
  content = upsertTomlSection(
1280
1576
  content,
1281
- "permissions.jorgex-read-anywhere.filesystem",
1282
- [
1283
- '":root" = "read"',
1284
- '"*.env" = "deny"',
1285
- '"*.env.*" = "deny"',
1286
- '"~/.ssh/**" = "deny"',
1287
- '"~/.aws/credentials" = "deny"',
1288
- '"~/.npmrc" = "deny"',
1289
- '"~/.git-credentials" = "deny"',
1290
- '"**/id_rsa" = "deny"',
1291
- '"**/id_ed25519" = "deny"',
1292
- '"**/*.pem" = "deny"',
1293
- '"**/*.key" = "deny"'
1294
- ].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)
1295
1584
  );
1296
1585
  content += [
1297
- '\n[permissions.jorgex-read-anywhere.filesystem.":workspace_roots"]',
1298
- '"." = "write"',
1299
- '"*.env" = "deny"',
1300
- '"*.env.*" = "deny"',
1301
- '".ssh/**" = "deny"',
1302
- '".aws/credentials" = "deny"',
1303
- '".npmrc" = "deny"',
1304
- '".git-credentials" = "deny"',
1305
- '"**/id_rsa" = "deny"',
1306
- '"**/id_ed25519" = "deny"',
1307
- '"**/*.pem" = "deny"',
1308
- '"**/*.key" = "deny"',
1586
+ `
1587
+ [${CODEX_PERMISSION_HEADERS.workspaceRoots}]`,
1588
+ renderCodexPermissionEntries(CODEX_PERMISSION_PROFILE.workspaceRoots, true),
1309
1589
  ""
1310
1590
  ].join("\n");
1311
1591
  }
@@ -1959,7 +2239,7 @@ function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
1959
2239
  function primaryModelOwnershipFile(stateDir = dataDir()) {
1960
2240
  return path19.join(stateDir, "primary-model.json");
1961
2241
  }
1962
- function isRecord(value) {
2242
+ function isRecord2(value) {
1963
2243
  return value !== null && typeof value === "object" && !Array.isArray(value);
1964
2244
  }
1965
2245
  function isRuntimeId(value) {
@@ -1968,7 +2248,7 @@ function isRuntimeId(value) {
1968
2248
  function parseDevtoolsMcpState(raw) {
1969
2249
  try {
1970
2250
  const value = JSON.parse(raw);
1971
- if (!isRecord(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord(value.enabled) || !isRecord(value.owned)) return null;
2251
+ if (!isRecord2(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord2(value.enabled) || !isRecord2(value.owned)) return null;
1972
2252
  const enabled = {};
1973
2253
  for (const [runtime, selected] of Object.entries(value.enabled)) {
1974
2254
  if (!isRuntimeId(runtime) || typeof selected !== "boolean") return null;
@@ -1976,7 +2256,7 @@ function parseDevtoolsMcpState(raw) {
1976
2256
  }
1977
2257
  const owned = {};
1978
2258
  for (const [runtime, servers] of Object.entries(value.owned)) {
1979
- if (!isRuntimeId(runtime) || !isRecord(servers)) return null;
2259
+ if (!isRuntimeId(runtime) || !isRecord2(servers)) return null;
1980
2260
  const managed = {};
1981
2261
  for (const [server, marked] of Object.entries(servers)) {
1982
2262
  if (marked !== true) return null;
@@ -2032,13 +2312,13 @@ function saveDevtoolsMcpOwnership(file, runtime, server, owned) {
2032
2312
  function parsePrimaryModelOwnership(raw) {
2033
2313
  try {
2034
2314
  const value = JSON.parse(raw);
2035
- if (!isRecord(value) || value.version !== PRIMARY_MODEL_OWNERSHIP_VERSION || !isRecord(value.owned)) return null;
2315
+ if (!isRecord2(value) || value.version !== PRIMARY_MODEL_OWNERSHIP_VERSION || !isRecord2(value.owned)) return null;
2036
2316
  const owned = {};
2037
2317
  for (const [runtime, configs] of Object.entries(value.owned)) {
2038
- if (!isRuntimeId(runtime) || !isRecord(configs)) return null;
2318
+ if (!isRuntimeId(runtime) || !isRecord2(configs)) return null;
2039
2319
  const markedConfigs = {};
2040
2320
  for (const [configDir, fields] of Object.entries(configs)) {
2041
- if (configDir === "" || !isRecord(fields)) return null;
2321
+ if (configDir === "" || !isRecord2(fields)) return null;
2042
2322
  const markedFields = {};
2043
2323
  for (const [field, state] of Object.entries(fields)) {
2044
2324
  if (field === "" || state !== true) return null;
@@ -3522,6 +3802,9 @@ async function runDoctor() {
3522
3802
  p3.log.warn(`${adapter.name}: no instalado en esta m\xE1quina.`);
3523
3803
  continue;
3524
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.`);
3525
3808
  const ctx = makeContext(adapter, detection.configDir, modePreference);
3526
3809
  if (!ctx) continue;
3527
3810
  let pending;
@@ -5453,10 +5736,10 @@ function normalizeTimeout(value) {
5453
5736
  return timeoutMs;
5454
5737
  }
5455
5738
  var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
5456
- function isRecord2(value) {
5739
+ function isRecord3(value) {
5457
5740
  return value !== null && typeof value === "object" && !Array.isArray(value);
5458
5741
  }
5459
- function hasText(value) {
5742
+ function hasText2(value) {
5460
5743
  return typeof value === "string" && value.trim() !== "";
5461
5744
  }
5462
5745
  function isDenseStringArray(value) {
@@ -5473,14 +5756,14 @@ function isQualityProfile(value) {
5473
5756
  return typeof value === "string" && QUALITY_PROFILES.includes(value);
5474
5757
  }
5475
5758
  function assertValidEnvironment(value, label) {
5476
- if (!isRecord2(value)) throw new Error(`Invalid ${label}`);
5759
+ if (!isRecord3(value)) throw new Error(`Invalid ${label}`);
5477
5760
  for (const [key, entry] of Object.entries(value)) {
5478
5761
  if (typeof entry !== "string") throw new Error(`Invalid ${label}.${key}`);
5479
5762
  }
5480
5763
  }
5481
5764
  function assertQualityPlanInput(value) {
5482
- if (!isRecord2(value)) throw new Error("Invalid quality plan");
5483
- if (!isRecord2(value.identity)) throw new Error("Invalid quality plan identity");
5765
+ if (!isRecord3(value)) throw new Error("Invalid quality plan");
5766
+ if (!isRecord3(value.identity)) throw new Error("Invalid quality plan identity");
5484
5767
  if (typeof value.identity.baseSha !== "string" || !COMMIT_SHA_PATTERN.test(value.identity.baseSha)) {
5485
5768
  throw new Error("Invalid quality plan identity.baseSha");
5486
5769
  }
@@ -5492,7 +5775,7 @@ function assertQualityPlanInput(value) {
5492
5775
  const controlIds = /* @__PURE__ */ new Set();
5493
5776
  for (let index = 0; index < value.controls.length; index += 1) {
5494
5777
  const control = value.controls[index];
5495
- if (!isRecord2(control) || !hasText(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
5778
+ if (!isRecord3(control) || !hasText2(control.id) || control.requirement !== "required" && control.requirement !== "optional") {
5496
5779
  throw new Error(`Invalid quality plan controls[${index}]`);
5497
5780
  }
5498
5781
  if (controlIds.has(control.id)) {
@@ -5508,9 +5791,9 @@ function assertQualityPlanInput(value) {
5508
5791
  const commandControlIds = /* @__PURE__ */ new Set();
5509
5792
  for (let index = 0; index < value.commands.length; index += 1) {
5510
5793
  const command = value.commands[index];
5511
- if (!isRecord2(command)) throw new Error(`Invalid quality plan commands[${index}]`);
5794
+ if (!isRecord3(command)) throw new Error(`Invalid quality plan commands[${index}]`);
5512
5795
  const timeoutMs = command.timeoutMs;
5513
- if (!hasText(command.controlId) || !hasText(command.commandId) || !hasText(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
5796
+ if (!hasText2(command.controlId) || !hasText2(command.commandId) || !hasText2(command.executable) || !isDenseStringArray(command.argv) || !isNonNegativeSafeInteger(timeoutMs)) {
5514
5797
  throw new Error(`Invalid quality plan commands[${index}]`);
5515
5798
  }
5516
5799
  if (timeoutMs > MAX_NODE_TIMEOUT_MS) {
@@ -5753,7 +6036,7 @@ function missingRequiredResults(controls, commands) {
5753
6036
  const commandControlIds = new Set(commands.map((command) => command.controlId));
5754
6037
  const missing = /* @__PURE__ */ new Set();
5755
6038
  for (const control of controls) {
5756
- if (control.requirement !== "required" || !hasText(control.id) || commandControlIds.has(control.id)) continue;
6039
+ if (control.requirement !== "required" || !hasText2(control.id) || commandControlIds.has(control.id)) continue;
5757
6040
  missing.add(control.id);
5758
6041
  }
5759
6042
  return [...missing].map((controlId) => ({
@@ -5791,11 +6074,15 @@ async function runQualityPlan(input) {
5791
6074
  results.push(receiptResultFor(command.controlId, result));
5792
6075
  }
5793
6076
  results.push(...missingRequiredResults(input.controls, input.commands));
5794
- const evaluation = evaluateQualityPolicy({
6077
+ const policyEvaluation = evaluateQualityPolicy({
5795
6078
  profile: input.profile,
5796
6079
  controls: input.controls,
5797
6080
  results
5798
6081
  });
6082
+ const evaluation = {
6083
+ ...policyEvaluation,
6084
+ status: localQualityStatus(input.profile, policyEvaluation.status)
6085
+ };
5799
6086
  const receipt = createQualityReceipt({
5800
6087
  authority: "local",
5801
6088
  identity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI, OpenCode y Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,117 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jorgex.dev/contracts/quality-capabilities.v1.schema.json",
4
+ "title": "JorgeX local quality capabilities v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["namespace", "version", "runtime", "capabilities"],
8
+ "properties": {
9
+ "namespace": {
10
+ "type": "string",
11
+ "const": "jorgex.quality.capabilities"
12
+ },
13
+ "version": {
14
+ "type": "integer",
15
+ "const": 1
16
+ },
17
+ "runtime": {
18
+ "type": "string",
19
+ "enum": ["claude-code", "codex", "opencode", "pi", "unknown"]
20
+ },
21
+ "capabilities": {
22
+ "type": "array",
23
+ "minItems": 3,
24
+ "maxItems": 3,
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["id", "state", "reason"],
29
+ "properties": {
30
+ "id": {
31
+ "type": "string",
32
+ "enum": ["policy-guidance", "tool-approval", "external-verification"]
33
+ },
34
+ "state": {
35
+ "$ref": "#/$defs/localCapabilityState"
36
+ },
37
+ "reason": {
38
+ "type": "string",
39
+ "pattern": "\\S"
40
+ },
41
+ "evidence": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": ["source", "version"],
45
+ "properties": {
46
+ "source": {
47
+ "type": "string",
48
+ "pattern": "\\S"
49
+ },
50
+ "version": {
51
+ "type": "string",
52
+ "pattern": "\\S"
53
+ }
54
+ }
55
+ }
56
+ },
57
+ "allOf": [
58
+ {
59
+ "if": {
60
+ "properties": {"id": {"const": "external-verification"}},
61
+ "required": ["id"]
62
+ },
63
+ "then": {
64
+ "properties": {"state": {"const": "unavailable"}}
65
+ }
66
+ },
67
+ {
68
+ "if": {
69
+ "properties": {"state": {"enum": ["prompt-only", "manual"]}},
70
+ "required": ["state"]
71
+ },
72
+ "then": {
73
+ "required": ["evidence"]
74
+ }
75
+ }
76
+ ]
77
+ },
78
+ "allOf": [
79
+ {
80
+ "contains": {
81
+ "type": "object",
82
+ "properties": {"id": {"const": "policy-guidance"}},
83
+ "required": ["id"]
84
+ }
85
+ },
86
+ {
87
+ "contains": {
88
+ "type": "object",
89
+ "properties": {"id": {"const": "tool-approval"}},
90
+ "required": ["id"]
91
+ }
92
+ },
93
+ {
94
+ "contains": {
95
+ "type": "object",
96
+ "properties": {"id": {"const": "external-verification"}},
97
+ "required": ["id"]
98
+ }
99
+ }
100
+ ]
101
+ }
102
+ },
103
+ "$defs": {
104
+ "capabilityState": {
105
+ "type": "string",
106
+ "enum": ["enforced", "prompt-only", "manual", "unavailable"]
107
+ },
108
+ "localCapabilityState": {
109
+ "type": "string",
110
+ "enum": ["prompt-only", "manual", "unavailable"]
111
+ },
112
+ "strictProfile": {
113
+ "type": "string",
114
+ "enum": ["high", "release"]
115
+ }
116
+ }
117
+ }