docdex 0.2.59 → 0.2.60

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.60
4
+ - Deduplicate installer-managed Docdex client config on reinstall: JSON client configs now collapse stale `docdex` entries, Codex TOML converges to one canonical Docdex entry, and packaged Docdex instruction blocks replace older Codex/Gemini/Claude prompt blocks instead of duplicating them.
5
+ - Update the packaged daemon dependency set to remove the vulnerable `rustls-webpki` chain that caused the nightly security audit failure.
6
+
3
7
  ## 0.2.58
4
8
  - Export Docdex delegation savings in hourly mswarm telemetry packages and expose matching runtime/admin mswarm summaries for frontend visibility.
5
9
 
package/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.59 ----
1
+ ---- START OF DOCDEX INFO V0.2.60 ----
2
2
  Docdex URL: http://127.0.0.1:28491
3
3
  Use this base URL for Docdex HTTP endpoints.
4
4
  Health check endpoint: `GET /healthz` (not `/v1/health`).
@@ -666,8 +666,17 @@ function stripLegacyDocdexBodySegment(segment, body) {
666
666
  const normalizedSegment = String(segment || "").replace(/\r\n/g, "\n");
667
667
  const normalizedBody = String(body || "").replace(/\r\n/g, "\n");
668
668
  if (!normalizedBody.trim()) return normalizedSegment;
669
- const re = new RegExp(`\\n?${escapeRegExp(normalizedBody)}\\n?`, "g");
670
- return normalizedSegment.replace(re, "\n").replace(/\n{3,}/g, "\n\n");
669
+ let result = normalizedSegment;
670
+ let index = result.indexOf(normalizedBody);
671
+ while (index !== -1) {
672
+ let start = index;
673
+ let end = index + normalizedBody.length;
674
+ if (start > 0 && result[start - 1] === "\n") start -= 1;
675
+ if (end < result.length && result[end] === "\n") end += 1;
676
+ result = `${result.slice(0, start)}\n${result.slice(end)}`;
677
+ index = result.indexOf(normalizedBody);
678
+ }
679
+ return result.replace(/\n{3,}/g, "\n\n");
671
680
  }
672
681
 
673
682
  function stripLegacyDocdexBody(text, body) {
@@ -1082,6 +1091,7 @@ function upsertMcpServerJson(pathname, url, options = {}) {
1082
1091
  const { value } = readJson(pathname);
1083
1092
  if (typeof value !== "object" || value == null || Array.isArray(value)) return false;
1084
1093
  const root = value;
1094
+ const before = JSON.stringify(root);
1085
1095
  const extra =
1086
1096
  options &&
1087
1097
  typeof options === "object" &&
@@ -1090,9 +1100,25 @@ function upsertMcpServerJson(pathname, url, options = {}) {
1090
1100
  !Array.isArray(options.extra)
1091
1101
  ? options.extra
1092
1102
  : {};
1093
- const extraEntries = Object.entries(extra);
1094
- const matchesExtras = (entry) =>
1095
- extraEntries.every(([key, value]) => entry && entry[key] === value);
1103
+ const isPlainObject = (entry) =>
1104
+ typeof entry === "object" && entry != null && !Array.isArray(entry);
1105
+ const removeDocdexFromSection = (key) => {
1106
+ const section = root[key];
1107
+ if (Array.isArray(section)) {
1108
+ const filtered = section.filter((entry) => !(entry && entry.name === "docdex"));
1109
+ if (filtered.length !== section.length) {
1110
+ root[key] = filtered;
1111
+ }
1112
+ return;
1113
+ }
1114
+ if (!isPlainObject(section) || !Object.prototype.hasOwnProperty.call(section, "docdex")) {
1115
+ return;
1116
+ }
1117
+ delete section.docdex;
1118
+ if (Object.keys(section).length === 0) {
1119
+ delete root[key];
1120
+ }
1121
+ };
1096
1122
  const pickSection = () => {
1097
1123
  if (root.mcpServers && typeof root.mcpServers === "object" && !Array.isArray(root.mcpServers)) {
1098
1124
  return { key: "mcpServers", section: root.mcpServers };
@@ -1103,29 +1129,39 @@ function upsertMcpServerJson(pathname, url, options = {}) {
1103
1129
  return null;
1104
1130
  };
1105
1131
  if (Array.isArray(root.mcpServers)) {
1106
- const idx = root.mcpServers.findIndex((entry) => entry && entry.name === "docdex");
1107
- if (idx >= 0) {
1108
- const current = root.mcpServers[idx] || {};
1109
- if (current.url === url && matchesExtras(current)) return false;
1110
- root.mcpServers[idx] = { ...current, ...extra, url, name: "docdex" };
1111
- writeJson(pathname, root);
1112
- return true;
1132
+ const nextEntries = [];
1133
+ let insertIndex = -1;
1134
+ let current = {};
1135
+ for (const entry of root.mcpServers) {
1136
+ if (entry && entry.name === "docdex") {
1137
+ if (insertIndex === -1) {
1138
+ insertIndex = nextEntries.length;
1139
+ current = isPlainObject(entry) ? { ...entry } : {};
1140
+ }
1141
+ continue;
1142
+ }
1143
+ nextEntries.push(entry);
1113
1144
  }
1114
- root.mcpServers.push({ ...extra, url, name: "docdex" });
1115
- writeJson(pathname, root);
1116
- return true;
1117
- }
1118
-
1119
- const picked = pickSection();
1120
- if (!picked) {
1121
- root.mcpServers = {};
1145
+ const nextEntry = { ...current, ...extra, url, name: "docdex" };
1146
+ if (insertIndex === -1) {
1147
+ nextEntries.push(nextEntry);
1148
+ } else {
1149
+ nextEntries.splice(insertIndex, 0, nextEntry);
1150
+ }
1151
+ root.mcpServers = nextEntries;
1152
+ removeDocdexFromSection("mcp_servers");
1153
+ } else {
1154
+ const picked = pickSection();
1155
+ const sectionKey = picked ? picked.key : "mcpServers";
1156
+ if (!picked) {
1157
+ root[sectionKey] = {};
1158
+ }
1159
+ const section = root[sectionKey];
1160
+ const current = isPlainObject(section.docdex) ? section.docdex : {};
1161
+ section.docdex = { ...current, ...extra, url };
1162
+ removeDocdexFromSection(sectionKey === "mcpServers" ? "mcp_servers" : "mcpServers");
1122
1163
  }
1123
- const section = picked ? picked.section : root.mcpServers;
1124
- const current = section.docdex;
1125
- if (current && current.url === url && matchesExtras(current)) return false;
1126
- const base =
1127
- current && typeof current === "object" && !Array.isArray(current) ? current : {};
1128
- section.docdex = { ...base, ...extra, url };
1164
+ if (JSON.stringify(root) === before) return false;
1129
1165
  writeJson(pathname, root);
1130
1166
  return true;
1131
1167
  }
@@ -1273,19 +1309,25 @@ function upsertCodexConfig(pathname, url) {
1273
1309
  end += 1;
1274
1310
  }
1275
1311
  let updated = false;
1276
- let docdexLine = -1;
1312
+ const docdexLines = [];
1277
1313
  for (let i = start + 1; i < end; i += 1) {
1278
1314
  if (/^\s*docdex\s*=/.test(lines[i])) {
1279
- docdexLine = i;
1280
- break;
1315
+ docdexLines.push(i);
1281
1316
  }
1282
1317
  }
1283
- if (docdexLine === -1) {
1318
+ if (docdexLines.length === 0) {
1284
1319
  lines.splice(end, 0, entryLine);
1285
1320
  updated = true;
1286
- } else if (lines[docdexLine].trim() !== entryLine) {
1287
- lines[docdexLine] = entryLine;
1288
- updated = true;
1321
+ } else {
1322
+ const [firstDocdexLine, ...extraDocdexLines] = docdexLines;
1323
+ if (lines[firstDocdexLine].trim() !== entryLine) {
1324
+ lines[firstDocdexLine] = entryLine;
1325
+ updated = true;
1326
+ }
1327
+ for (let i = extraDocdexLines.length - 1; i >= 0; i -= 1) {
1328
+ lines.splice(extraDocdexLines[i], 1);
1329
+ updated = true;
1330
+ }
1289
1331
  }
1290
1332
  return { contents: lines.join("\n"), updated };
1291
1333
  };
@@ -1336,6 +1378,71 @@ function upsertCodexConfig(pathname, url) {
1336
1378
  return { contents: output.join("\n"), updated };
1337
1379
  };
1338
1380
 
1381
+ const removeRootDocdexEntries = (text) => {
1382
+ const lines = text.split(/\r?\n/);
1383
+ const output = [];
1384
+ let inRootSection = false;
1385
+ let updated = false;
1386
+ for (const line of lines) {
1387
+ const section = line.match(/^\s*\[([^\]]+)\]\s*$/);
1388
+ if (section) {
1389
+ inRootSection = section[1].trim() === "mcp_servers";
1390
+ output.push(line);
1391
+ continue;
1392
+ }
1393
+ if (inRootSection && /^\s*docdex\s*=/.test(line)) {
1394
+ updated = true;
1395
+ continue;
1396
+ }
1397
+ output.push(line);
1398
+ }
1399
+ return { contents: output.join("\n"), updated };
1400
+ };
1401
+
1402
+ const countRootDocdexEntries = (text) => {
1403
+ const lines = text.split(/\r?\n/);
1404
+ let inRootSection = false;
1405
+ let count = 0;
1406
+ for (const line of lines) {
1407
+ const section = line.match(/^\s*\[([^\]]+)\]\s*$/);
1408
+ if (section) {
1409
+ inRootSection = section[1].trim() === "mcp_servers";
1410
+ continue;
1411
+ }
1412
+ if (inRootSection && /^\s*docdex\s*=/.test(line)) {
1413
+ count += 1;
1414
+ }
1415
+ }
1416
+ return count;
1417
+ };
1418
+
1419
+ const removeNestedDocdexSections = (text) => {
1420
+ const lines = text.split(/\r?\n/);
1421
+ const output = [];
1422
+ let skipping = false;
1423
+ let updated = false;
1424
+ for (const line of lines) {
1425
+ const isSection = /^\s*\[.+\]\s*$/.test(line);
1426
+ if (skipping) {
1427
+ if (isSection) {
1428
+ skipping = false;
1429
+ } else {
1430
+ continue;
1431
+ }
1432
+ }
1433
+ if (/^\s*\[mcp_servers\.docdex\]\s*$/.test(line)) {
1434
+ skipping = true;
1435
+ updated = true;
1436
+ continue;
1437
+ }
1438
+ output.push(line);
1439
+ }
1440
+ return { contents: output.join("\n"), updated };
1441
+ };
1442
+
1443
+ const countNestedDocdexSections = (text) =>
1444
+ (text.match(/^\s*\[mcp_servers\.docdex\]\s*$/gm) || []).length;
1445
+
1339
1446
  let contents = "";
1340
1447
  if (fs.existsSync(pathname)) {
1341
1448
  contents = fs.readFileSync(pathname, "utf8");
@@ -1351,7 +1458,23 @@ function upsertCodexConfig(pathname, url) {
1351
1458
  contents = cleaned.contents;
1352
1459
  updated = updated || cleaned.updated;
1353
1460
 
1354
- if (hasNestedMcpServers(contents)) {
1461
+ const preferNested = hasNestedMcpServers(contents);
1462
+ const rootDocdexCount = countRootDocdexEntries(contents);
1463
+ const nestedDocdexCount = countNestedDocdexSections(contents);
1464
+
1465
+ if (preferNested && rootDocdexCount > 0) {
1466
+ const prunedRoot = removeRootDocdexEntries(contents);
1467
+ contents = prunedRoot.contents;
1468
+ updated = updated || prunedRoot.updated;
1469
+ }
1470
+
1471
+ if ((!preferNested && nestedDocdexCount > 0) || (preferNested && nestedDocdexCount > 1)) {
1472
+ const prunedNested = removeNestedDocdexSections(contents);
1473
+ contents = prunedNested.contents;
1474
+ updated = updated || prunedNested.updated;
1475
+ }
1476
+
1477
+ if (preferNested) {
1355
1478
  const nested = upsertDocdexNested(contents, url);
1356
1479
  contents = nested.contents;
1357
1480
  updated = updated || nested.updated;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.59",
3
+ "version": "0.2.60",
4
4
  "mcpName": "io.github.bekirdag/docdex",
5
5
  "description": "Local-first documentation and code indexer with HTTP/MCP search, AST, and agent memory.",
6
6
  "bin": {