docdex 0.2.18 → 0.2.19

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.19
4
+ - Playwright issue fix
5
+ - Agents md adding command manually
6
+ - Agents md append repeat fix
7
+
3
8
  ## 0.2.16
4
9
  - Repo memory now tags items with `repoId` and filters recalls to prevent cross-repo leakage in multi-repo daemons.
5
10
  - MCP HTTP requires explicit repo selection when multiple repos are active.
@@ -20,6 +20,8 @@ const DEFAULT_OLLAMA_CHAT_MODEL = "phi3.5:3.8b";
20
20
  const DEFAULT_OLLAMA_CHAT_MODEL_SIZE_GIB = 2.2;
21
21
  const SETUP_PENDING_MARKER = "setup_pending.json";
22
22
  const AGENTS_DOC_FILENAME = "agents.md";
23
+ const DOCDEX_INFO_START_PREFIX = "---- START OF DOCDEX INFO V";
24
+ const DOCDEX_INFO_END = "---- END OF DOCDEX INFO -----";
23
25
 
24
26
  function defaultConfigPath() {
25
27
  return path.join(os.homedir(), ".docdex", "config.toml");
@@ -158,6 +160,18 @@ function agentsDocSourcePath() {
158
160
  return path.join(__dirname, "..", "assets", AGENTS_DOC_FILENAME);
159
161
  }
160
162
 
163
+ function resolvePackageVersion() {
164
+ const packagePath = path.join(__dirname, "..", "package.json");
165
+ if (!fs.existsSync(packagePath)) return "unknown";
166
+ try {
167
+ const raw = fs.readFileSync(packagePath, "utf8");
168
+ const parsed = JSON.parse(raw);
169
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : "unknown";
170
+ } catch {
171
+ return "unknown";
172
+ }
173
+ }
174
+
161
175
  function loadAgentInstructions() {
162
176
  const sourcePath = agentsDocSourcePath();
163
177
  if (!fs.existsSync(sourcePath)) return "";
@@ -172,13 +186,123 @@ function normalizeInstructionText(value) {
172
186
  return String(value || "").trim();
173
187
  }
174
188
 
175
- function mergeInstructionText(existing, instructions) {
189
+ function docdexBlockStart(version) {
190
+ return `${DOCDEX_INFO_START_PREFIX}${version} ----`;
191
+ }
192
+
193
+ function buildDocdexInstructionBlock(instructions) {
194
+ const next = normalizeInstructionText(instructions);
195
+ if (!next) return "";
196
+ const version = resolvePackageVersion();
197
+ return `${docdexBlockStart(version)}\n${next}\n${DOCDEX_INFO_END}`;
198
+ }
199
+
200
+ function extractDocdexBlockBody(text) {
201
+ const match = String(text || "").match(
202
+ new RegExp(
203
+ `${escapeRegExp(DOCDEX_INFO_START_PREFIX)}[^\\r\\n]* ----\\r?\\n([\\s\\S]*?)\\r?\\n${escapeRegExp(
204
+ DOCDEX_INFO_END
205
+ )}`
206
+ )
207
+ );
208
+ return match ? normalizeInstructionText(match[1]) : "";
209
+ }
210
+
211
+ function extractDocdexBlockVersion(text) {
212
+ const match = String(text || "").match(
213
+ new RegExp(`${escapeRegExp(DOCDEX_INFO_START_PREFIX)}([^\\s]+) ----`)
214
+ );
215
+ return match ? match[1] : null;
216
+ }
217
+
218
+ function hasDocdexBlockVersion(text, version) {
219
+ if (!version) return false;
220
+ return String(text || "").includes(docdexBlockStart(version));
221
+ }
222
+
223
+ function stripDocdexBlocks(text) {
224
+ const re = new RegExp(
225
+ `${escapeRegExp(DOCDEX_INFO_START_PREFIX)}[^\\r\\n]* ----\\r?\\n[\\s\\S]*?\\r?\\n${escapeRegExp(
226
+ DOCDEX_INFO_END
227
+ )}\\r?\\n?`,
228
+ "g"
229
+ );
230
+ return String(text || "").replace(re, "").trim();
231
+ }
232
+
233
+ function stripDocdexBlocksExcept(text, version) {
234
+ if (!version) return stripDocdexBlocks(text);
235
+ const source = String(text || "");
236
+ const re = new RegExp(
237
+ `${escapeRegExp(DOCDEX_INFO_START_PREFIX)}[^\\r\\n]* ----\\r?\\n[\\s\\S]*?\\r?\\n${escapeRegExp(
238
+ DOCDEX_INFO_END
239
+ )}\\r?\\n?`,
240
+ "g"
241
+ );
242
+ let result = "";
243
+ let lastIndex = 0;
244
+ let match;
245
+ while ((match = re.exec(source))) {
246
+ const before = source.slice(lastIndex, match.index);
247
+ result += before;
248
+ const block = match[0];
249
+ const blockVersion = extractDocdexBlockVersion(block);
250
+ if (blockVersion === version) {
251
+ result += block;
252
+ }
253
+ lastIndex = match.index + block.length;
254
+ }
255
+ result += source.slice(lastIndex);
256
+ return result;
257
+ }
258
+
259
+ function stripLegacyDocdexBodySegment(segment, body) {
260
+ if (!body) return String(segment || "");
261
+ const normalizedSegment = String(segment || "").replace(/\r\n/g, "\n");
262
+ const normalizedBody = String(body || "").replace(/\r\n/g, "\n");
263
+ if (!normalizedBody.trim()) return normalizedSegment;
264
+ const re = new RegExp(`\\n?${escapeRegExp(normalizedBody)}\\n?`, "g");
265
+ return normalizedSegment.replace(re, "\n").replace(/\n{3,}/g, "\n\n");
266
+ }
267
+
268
+ function stripLegacyDocdexBody(text, body) {
269
+ if (!body) return String(text || "");
270
+ const source = String(text || "").replace(/\r\n/g, "\n");
271
+ const re = new RegExp(
272
+ `${escapeRegExp(DOCDEX_INFO_START_PREFIX)}[^\\n]* ----\\n[\\s\\S]*?\\n${escapeRegExp(DOCDEX_INFO_END)}\\n?`,
273
+ "g"
274
+ );
275
+ let result = "";
276
+ let lastIndex = 0;
277
+ let match;
278
+ while ((match = re.exec(source))) {
279
+ const before = source.slice(lastIndex, match.index);
280
+ result += stripLegacyDocdexBodySegment(before, body);
281
+ result += match[0];
282
+ lastIndex = match.index + match[0].length;
283
+ }
284
+ result += stripLegacyDocdexBodySegment(source.slice(lastIndex), body);
285
+ return result;
286
+ }
287
+
288
+ function mergeInstructionText(existing, instructions, { prepend = false } = {}) {
176
289
  const next = normalizeInstructionText(instructions);
177
290
  if (!next) return normalizeInstructionText(existing);
178
- const current = normalizeInstructionText(existing);
291
+ const existingText = String(existing || "");
292
+ const current = normalizeInstructionText(existingText);
179
293
  if (!current) return next;
180
- if (current.includes(next)) return current;
181
- return `${current}\n\n${next}`;
294
+ const version = extractDocdexBlockVersion(next);
295
+ if (version) {
296
+ const body = extractDocdexBlockBody(next);
297
+ const cleaned = stripLegacyDocdexBody(existingText, body);
298
+ const withoutOldBlocks = stripDocdexBlocksExcept(cleaned, version);
299
+ if (hasDocdexBlockVersion(withoutOldBlocks, version)) return withoutOldBlocks;
300
+ const remainder = normalizeInstructionText(stripDocdexBlocks(withoutOldBlocks));
301
+ if (!remainder) return next;
302
+ return prepend ? `${next}\n\n${remainder}` : `${remainder}\n\n${next}`;
303
+ }
304
+ if (existingText.includes(next)) return existingText;
305
+ return prepend ? `${next}\n\n${current}` : `${current}\n\n${next}`;
182
306
  }
183
307
 
184
308
  function writeTextFile(pathname, contents) {
@@ -199,13 +323,10 @@ function upsertPromptFile(pathname, instructions, { prepend = false } = {}) {
199
323
  let current = "";
200
324
  if (fs.existsSync(pathname)) {
201
325
  current = fs.readFileSync(pathname, "utf8");
202
- if (current.includes(next)) return false;
203
- }
204
- const currentTrimmed = normalizeInstructionText(current);
205
- let merged = next;
206
- if (currentTrimmed) {
207
- merged = prepend ? `${next}\n\n${currentTrimmed}` : `${currentTrimmed}\n\n${next}`;
208
326
  }
327
+ const merged = mergeInstructionText(current, instructions, { prepend });
328
+ if (!merged) return false;
329
+ if (merged === current) return false;
209
330
  return writeTextFile(pathname, merged);
210
331
  }
211
332
 
@@ -220,13 +341,51 @@ function upsertYamlInstruction(pathname, key, instructions) {
220
341
  if (fs.existsSync(pathname)) {
221
342
  current = fs.readFileSync(pathname, "utf8");
222
343
  }
223
- const keyRe = new RegExp(`^\\s*${escapeRegExp(key)}\\s*:`, "m");
224
- if (keyRe.test(current)) {
225
- if (current.includes(next)) return false;
226
- return false;
344
+ const lines = current.split(/\r?\n/);
345
+ const blockRe = new RegExp(`^(\\s*)${escapeRegExp(key)}\\s*:\\s*(\\|[+-]?)?\\s*$`);
346
+ for (let idx = 0; idx < lines.length; idx += 1) {
347
+ const match = lines[idx].match(blockRe);
348
+ if (!match) continue;
349
+ const indent = match[1] || "";
350
+ const blockIndent = `${indent} `;
351
+ let existingBlock = "";
352
+ let blockEnd = idx + 1;
353
+ if (match[2]) {
354
+ for (let j = idx + 1; j < lines.length; j += 1) {
355
+ const line = lines[j];
356
+ if (!line.trim()) {
357
+ blockEnd = j + 1;
358
+ continue;
359
+ }
360
+ const leading = line.match(/^\s*/)[0].length;
361
+ if (leading <= indent.length) break;
362
+ blockEnd = j + 1;
363
+ }
364
+ const blockLines = lines.slice(idx + 1, blockEnd);
365
+ existingBlock = blockLines
366
+ .map((line) => {
367
+ if (!line.trim()) return "";
368
+ return line.startsWith(blockIndent) ? line.slice(blockIndent.length) : line.trimStart();
369
+ })
370
+ .join("\n");
371
+ } else {
372
+ const inlineMatch = lines[idx].match(new RegExp(`^(\\s*)${escapeRegExp(key)}\\s*:\\s*(.*)$`));
373
+ existingBlock = inlineMatch ? inlineMatch[2].trim() : "";
374
+ }
375
+ const merged = mergeInstructionText(existingBlock, instructions);
376
+ if (!merged) return false;
377
+ if (normalizeInstructionText(merged) === normalizeInstructionText(existingBlock) && match[2]) return false;
378
+ const mergedLines = merged.split(/\r?\n/).map((line) => `${blockIndent}${line}`);
379
+ const updatedLines = [
380
+ ...lines.slice(0, idx),
381
+ `${indent}${key}: |`,
382
+ ...mergedLines,
383
+ ...lines.slice(match[2] ? blockEnd : idx + 1)
384
+ ];
385
+ return writeTextFile(pathname, updatedLines.join("\n").trimEnd());
227
386
  }
228
- const lines = next.split(/\r?\n/).map((line) => ` ${line}`);
229
- const block = `${key}: |\n${lines.join("\n")}`;
387
+ const contentLines = next.split(/\r?\n/).map((line) => ` ${line}`);
388
+ const block = `${key}: |\n${contentLines.join("\n")}`;
230
389
  const merged = current.trim() ? `${current.trim()}\n\n${block}` : block;
231
390
  return writeTextFile(pathname, merged);
232
391
  }
@@ -668,7 +827,7 @@ function resolveBinaryPath({ binaryPath } = {}) {
668
827
  }
669
828
 
670
829
  function applyAgentInstructions({ logger } = {}) {
671
- const instructions = loadAgentInstructions();
830
+ const instructions = buildDocdexInstructionBlock(loadAgentInstructions());
672
831
  if (!normalizeInstructionText(instructions)) return { ok: false, reason: "missing_instructions" };
673
832
  const paths = clientInstructionPaths();
674
833
  let updated = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
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": {