docdex 0.2.36 → 0.2.39

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.39
4
+ - Bump release metadata to 0.2.39.
5
+
6
+ ## 0.2.38
7
+ - Bump release metadata to 0.2.38.
8
+
9
+ ## 0.2.37
10
+ - Bump release metadata to 0.2.37.
11
+
3
12
  ## 0.2.36
4
13
  - Bump release metadata to 0.2.36.
5
14
 
package/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.36 ----
1
+ ---- START OF DOCDEX INFO V0.2.39 ----
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`).
@@ -6,6 +6,7 @@ Health check endpoint: `GET /healthz` (not `/v1/health`).
6
6
  ## Priority Directives (Mandatory)
7
7
 
8
8
  - Always use both memory lobes early: docdex_get_profile + docdex_memory_recall; save new facts via docdex_memory_save and new preferences via docdex_save_preference.
9
+ - Profile memory is always-on and global; never gate on DOCDEX_ENABLE_MEMORY or repo state. docdex_save_preference must persist (fallback if embeddings/LLM unavailable). If profile calls fail, treat it as a bug.
9
10
  - Maintain agent-specific profile memory: use your agent_id with docdex_get_profile/docdex_save_preference; store generic self-knowledge (role, tooling, preferences).
10
11
  - Use impact analysis for every code change: prefer MCP tools `docdex_impact_graph` / `docdex_dag_export` (IPC/HTTP). If shell networking is blocked, do not use curl; use MCP/IPC instead. If unavailable, state it and proceed cautiously.
11
12
  - Apply DAG reasoning for planning: prefer dependency graph facts (impact results and /v1/dag/export) to choose the right change order and scope.
@@ -250,7 +251,7 @@ Query params:
250
251
 
251
252
  - SSE: `/v1/mcp/sse` + `/v1/mcp/message`. When multiple repos are mounted, initialize with `rootUri` first.
252
253
  - HTTP: `/v1/mcp` accepts repo context in the payload or via prior initialize.
253
- - If HTTP/SSE is unreachable (sandboxed clients), fall back to local IPC: configure `transport = "ipc"` with `socket_path` (Unix) or `pipe_name` (Windows) and send MCP JSON-RPC to `/v1/mcp` over IPC.
254
+ - If HTTP/SSE is unreachable or fails (e.g., connection refused or sandboxed clients), fall back to local IPC: configure `transport = "ipc"` with `socket_path` (Unix) or `pipe_name` (Windows) and send MCP JSON-RPC to `/v1/mcp` over IPC.
254
255
  - For stdio-only clients (e.g., Smithery), use the `docdex-mcp-stdio` entrypoint to bridge stdio JSON-RPC to Docdex MCP.
255
256
  - For impact/DAG in sandboxed shells, prefer MCP/IPC tools over `curl` to `/v1/graph/impact` or `/v1/dag/export`.
256
257
  - MCP tools: `docdex_impact_graph` (impact traversal) and `docdex_dag_export` (DAG export).
@@ -304,9 +305,10 @@ When answering a complex coding query, follow this "Reasoning Trace":
304
305
  Save more memories for both lobes during the task, not just at the end.
305
306
 
306
307
  1. Repo memory: After each meaningful discovery or code change, save at least one durable fact (file location, behavior, config, gotcha) via `docdex_memory_save`.
307
- 2. Profile memory: When the user expresses a preference, constraint, or workflow correction, call `docdex_save_preference` immediately with the right category.
308
- 3. Keep it crisp: 1-3 short sentences, include file paths when relevant, avoid raw code blobs.
309
- 4. Safety: Never store secrets, tokens, or sensitive user data. Skip transient or speculative info.
308
+ 2. Memory overrides: When a new repo memory replaces older facts, include `metadata.supersedes` with the prior memory id(s). Docdex marks the superseded entries with `supersededBy`/`supersededAtMs`, down-ranks them during recall, and they can be removed via `docdex memory compact` (dry-run unless `--apply`).
309
+ 3. Profile memory: When the user expresses a preference, constraint, or workflow correction, call `docdex_save_preference` immediately with the right category.
310
+ 4. Keep it crisp: 1-3 short sentences, include file paths when relevant, avoid raw code blobs.
311
+ 5. Safety: Never store secrets, tokens, or sensitive user data. Skip transient or speculative info.
310
312
 
311
313
  ### 3. Index Health + Diff-Aware Search (Mandatory)
312
314
 
package/lib/install.js CHANGED
@@ -2258,7 +2258,7 @@ async function main() {
2258
2258
  }
2259
2259
  const result = await runInstaller({ env, distBaseDir });
2260
2260
  try {
2261
- const skipDaemon = Boolean(env?.npm_lifecycle_event);
2261
+ const skipDaemon = parseEnvBool(env?.DOCDEX_DAEMON_SKIP_SETUP);
2262
2262
  await runPostInstallSetup({ binaryPath: result?.binaryPath, env, skipDaemon, distBaseDir });
2263
2263
  } catch (err) {
2264
2264
  console.warn(`[docdex] postinstall setup failed: ${err?.message || err}`);
@@ -2286,6 +2286,14 @@ function printPostInstallBanner() {
2286
2286
  return false;
2287
2287
  }
2288
2288
  };
2289
+ const writeStderr = (message) => {
2290
+ try {
2291
+ process.stderr.write(message);
2292
+ return true;
2293
+ } catch {
2294
+ return false;
2295
+ }
2296
+ };
2289
2297
  let width = 0;
2290
2298
  const content = [
2291
2299
  "\x1b[31m _ _ \x1b[0m",
@@ -2320,7 +2328,9 @@ function printPostInstallBanner() {
2320
2328
  lines.push(bottom);
2321
2329
  const banner = `\r\x1b[2K${lines.join("\n")}\n`;
2322
2330
  if (!writeDirect(banner)) {
2323
- console.log(banner);
2331
+ if (!writeStderr(banner)) {
2332
+ console.log(banner);
2333
+ }
2324
2334
  }
2325
2335
  }
2326
2336
 
package/lib/paths.js CHANGED
@@ -102,11 +102,20 @@ function resolveWindowsRunnerPath(options = {}) {
102
102
  return pathModule.join(resolveDocdexDataDir({ ...options, pathModule }), "run-daemon.cmd");
103
103
  }
104
104
 
105
+ function resolveWindowsSetupRunnerPath(options = {}) {
106
+ const pathModule = options.pathModule || path;
107
+ if (options.distBaseDir) {
108
+ return pathModule.join(pathModule.dirname(options.distBaseDir), "run-setup.cmd");
109
+ }
110
+ return pathModule.join(resolveDocdexDataDir({ ...options, pathModule }), "run-setup.cmd");
111
+ }
112
+
105
113
  module.exports = {
106
114
  resolveUserDataDir,
107
115
  resolveDocdexDataDir,
108
116
  resolveDistBaseCandidates,
109
117
  resolveDistBaseDir,
110
118
  resolveBinDir,
111
- resolveWindowsRunnerPath
119
+ resolveWindowsRunnerPath,
120
+ resolveWindowsSetupRunnerPath
112
121
  };
@@ -15,7 +15,8 @@ const {
15
15
  resolveDistBaseDir,
16
16
  resolveDistBaseCandidates,
17
17
  resolveBinDir,
18
- resolveWindowsRunnerPath
18
+ resolveWindowsRunnerPath,
19
+ resolveWindowsSetupRunnerPath
19
20
  } = require("./paths");
20
21
 
21
22
  const DEFAULT_HOST = "127.0.0.1";
@@ -313,6 +314,73 @@ function stopDaemonService({ logger } = {}) {
313
314
  return false;
314
315
  }
315
316
 
317
+ function removeDaemonService({ logger, distBaseDir, env } = {}) {
318
+ if (process.platform === "darwin") {
319
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
320
+ const domain = uid != null ? `gui/${uid}` : null;
321
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.docdex.daemon.plist");
322
+ const systemPlistPath = "/Library/LaunchDaemons/com.docdex.daemon.plist";
323
+ if (domain) {
324
+ spawnSync("launchctl", ["bootout", domain, "com.docdex.daemon"]);
325
+ spawnSync("launchctl", ["bootout", domain, plistPath]);
326
+ } else {
327
+ spawnSync("launchctl", ["bootout", "com.docdex.daemon"]);
328
+ spawnSync("launchctl", ["bootout", plistPath]);
329
+ }
330
+ spawnSync("launchctl", ["remove", "com.docdex.daemon"]);
331
+ spawnSync("launchctl", ["unload", "-w", plistPath]);
332
+ if (fs.existsSync(plistPath)) {
333
+ try {
334
+ fs.unlinkSync(plistPath);
335
+ } catch (err) {
336
+ logger?.warn?.(`[docdex] failed to remove LaunchAgent plist: ${err?.message || err}`);
337
+ }
338
+ }
339
+ if (fs.existsSync(systemPlistPath)) {
340
+ spawnSync("launchctl", ["bootout", "system", systemPlistPath]);
341
+ spawnSync("launchctl", ["remove", "com.docdex.daemon"]);
342
+ try {
343
+ fs.unlinkSync(systemPlistPath);
344
+ } catch (err) {
345
+ logger?.warn?.(`[docdex] failed to remove LaunchDaemon plist: ${err?.message || err}`);
346
+ }
347
+ }
348
+ return true;
349
+ }
350
+ if (process.platform === "linux") {
351
+ const systemdDir = path.join(os.homedir(), ".config", "systemd", "user");
352
+ const unitPath = path.join(systemdDir, "docdexd.service");
353
+ spawnSync("systemctl", ["--user", "stop", "docdexd.service"]);
354
+ spawnSync("systemctl", ["--user", "disable", "--now", "docdexd.service"]);
355
+ spawnSync("systemctl", ["--user", "reset-failed", "docdexd.service"]);
356
+ if (fs.existsSync(unitPath)) {
357
+ try {
358
+ fs.unlinkSync(unitPath);
359
+ } catch (err) {
360
+ logger?.warn?.(`[docdex] failed to remove systemd unit: ${err?.message || err}`);
361
+ }
362
+ spawnSync("systemctl", ["--user", "daemon-reload"]);
363
+ }
364
+ return true;
365
+ }
366
+ if (process.platform === "win32") {
367
+ const taskName = "Docdex Daemon";
368
+ spawnSync("schtasks", ["/End", "/TN", taskName]);
369
+ spawnSync("schtasks", ["/Delete", "/TN", taskName, "/F"]);
370
+ const resolvedDistBaseDir = distBaseDir || resolveDistBaseDir({ env, fsModule: fs });
371
+ const runnerPath = resolveWindowsRunnerPath({ distBaseDir: resolvedDistBaseDir, pathModule: path });
372
+ if (runnerPath && fs.existsSync(runnerPath)) {
373
+ try {
374
+ fs.unlinkSync(runnerPath);
375
+ } catch (err) {
376
+ logger?.warn?.(`[docdex] failed to remove Windows runner: ${err?.message || err}`);
377
+ }
378
+ }
379
+ return true;
380
+ }
381
+ return false;
382
+ }
383
+
316
384
  function startDaemonService({ logger } = {}) {
317
385
  if (process.platform === "darwin") {
318
386
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
@@ -356,6 +424,21 @@ function stopDaemonByName({ logger } = {}) {
356
424
  return true;
357
425
  }
358
426
 
427
+ async function cleanupExistingDaemon({ logger, env, distBaseDir, host, port } = {}) {
428
+ const log = logger || console;
429
+ stopDaemonService({ logger: log });
430
+ stopDaemonFromLock({ logger: log });
431
+ stopDaemonByName({ logger: log });
432
+ removeDaemonService({ logger: log, distBaseDir, env });
433
+ clearDaemonLocks();
434
+ if (!host || !port) return true;
435
+ const released = await waitForPortAvailable({ host, port });
436
+ if (!released) {
437
+ log.warn?.(`[docdex] ${host}:${port} still in use after daemon cleanup.`);
438
+ }
439
+ return released;
440
+ }
441
+
359
442
  function clearDaemonLocks() {
360
443
  let removed = false;
361
444
  for (const lockPath of daemonLockPaths()) {
@@ -2179,6 +2262,27 @@ function writeWindowsRunner({ binaryPath, args, envPairs, workingDir, logger, di
2179
2262
  }
2180
2263
  }
2181
2264
 
2265
+ function writeWindowsSetupRunner({ binaryPath, args, logger, distBaseDir } = {}) {
2266
+ if (!binaryPath) return null;
2267
+ const runnerPath = resolveWindowsSetupRunnerPath({ distBaseDir });
2268
+ const lines = [
2269
+ "@echo off",
2270
+ "setlocal",
2271
+ "set \"DOCDEX_SETUP_AUTO=1\"",
2272
+ "set \"DOCDEX_SETUP_MODE=auto\""
2273
+ ];
2274
+ const argString = (args || []).map((arg) => escapeCmdArg(arg)).join(" ");
2275
+ lines.push(`${escapeCmdArg(binaryPath)} ${argString}`.trim());
2276
+ try {
2277
+ fs.mkdirSync(path.dirname(runnerPath), { recursive: true });
2278
+ fs.writeFileSync(runnerPath, `${lines.join("\r\n")}\r\n`);
2279
+ return runnerPath;
2280
+ } catch (err) {
2281
+ logger?.warn?.(`[docdex] failed to write Windows setup runner: ${err?.message || err}`);
2282
+ return null;
2283
+ }
2284
+ }
2285
+
2182
2286
  function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2183
2287
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
2184
2288
  stopDaemonService({ logger });
@@ -2360,7 +2464,6 @@ function isNpmLifecycle(env = process.env) {
2360
2464
 
2361
2465
  function shouldSkipDaemonSideEffects({ env = process.env, skipDaemon } = {}) {
2362
2466
  if (skipDaemon) return true;
2363
- if (isNpmLifecycle(env)) return true;
2364
2467
  if (parseEnvBool(env?.DOCDEX_DAEMON_SKIP_SETUP)) return true;
2365
2468
  return false;
2366
2469
  }
@@ -2433,7 +2536,8 @@ function launchSetupWizard({
2433
2536
  spawnFn = spawn,
2434
2537
  spawnSyncFn = spawnSync,
2435
2538
  platform = process.platform,
2436
- canPrompt = canPromptWithTty
2539
+ canPrompt = canPromptWithTty,
2540
+ distBaseDir
2437
2541
  }) {
2438
2542
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
2439
2543
  if (shouldSkipSetup(env)) return { ok: false, reason: "skipped" };
@@ -2454,9 +2558,21 @@ function launchSetupWizard({
2454
2558
  }
2455
2559
 
2456
2560
  if (platform === "win32") {
2457
- const quoted = `"${binaryPath}" ${args.map((arg) => `"${arg}"`).join(" ")}`;
2458
- const cmdline = `set DOCDEX_SETUP_AUTO=1 && set DOCDEX_SETUP_MODE=auto && ${quoted}`;
2459
- const result = spawnSyncFn("cmd", ["/c", "start", "", "cmd", "/c", cmdline]);
2561
+ const runnerPath = writeWindowsSetupRunner({
2562
+ binaryPath,
2563
+ args,
2564
+ logger,
2565
+ distBaseDir
2566
+ });
2567
+ if (!runnerPath) return { ok: false, reason: "runner_failed" };
2568
+ const result = spawnSyncFn("cmd", [
2569
+ "/c",
2570
+ "start",
2571
+ "",
2572
+ "cmd",
2573
+ "/c",
2574
+ escapeCmdArg(runnerPath)
2575
+ ]);
2460
2576
  if (result.status === 0) return { ok: true };
2461
2577
  logger?.warn?.(`[docdex] cmd start failed: ${result.stderr || "unknown error"}`);
2462
2578
  return { ok: false, reason: "terminal_launch_failed" };
@@ -2478,6 +2594,22 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2478
2594
  }
2479
2595
  const port = DEFAULT_DAEMON_PORT;
2480
2596
  let portState = { available: true, reuseExisting: false };
2597
+ if (allowDaemon) {
2598
+ const cleaned = await cleanupExistingDaemon({
2599
+ logger: log,
2600
+ env: effectiveEnv,
2601
+ distBaseDir: resolvedDistBaseDir,
2602
+ host: DEFAULT_HOST,
2603
+ port
2604
+ });
2605
+ if (!cleaned) {
2606
+ log.warn?.(
2607
+ `[docdex] ${DEFAULT_HOST}:${port} is still in use after removing the daemon service; skipping daemon startup.`
2608
+ );
2609
+ recordStartupFailure({ reason: "port_in_use", host: DEFAULT_HOST, port });
2610
+ allowDaemon = false;
2611
+ }
2612
+ }
2481
2613
  if (allowDaemon) {
2482
2614
  portState = await resolveDaemonPortState({
2483
2615
  host: DEFAULT_HOST,
@@ -2584,13 +2716,20 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2584
2716
  if (startupOk) {
2585
2717
  clearStartupFailure();
2586
2718
  }
2587
- const skipWizard = isNpmLifecycle(effectiveEnv) || shouldSkipSetup(effectiveEnv);
2719
+ const skipExplicit = shouldSkipSetup(effectiveEnv);
2720
+ const skipWizard = isNpmLifecycle(effectiveEnv) || skipExplicit;
2588
2721
  const setupLaunch = skipWizard
2589
- ? { ok: false, reason: "skipped" }
2590
- : launchSetupWizard({ binaryPath: startupBinaries.binaryPath, logger: log });
2591
- if (!setupLaunch.ok && setupLaunch.reason !== "skipped") {
2592
- log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
2593
- recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
2722
+ ? { ok: false, reason: skipExplicit ? "skipped" : "npm_lifecycle" }
2723
+ : launchSetupWizard({
2724
+ binaryPath: startupBinaries.binaryPath,
2725
+ logger: log,
2726
+ distBaseDir: resolvedDistBaseDir
2727
+ });
2728
+ if (!setupLaunch.ok) {
2729
+ if (setupLaunch.reason !== "skipped") {
2730
+ log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
2731
+ recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
2732
+ }
2594
2733
  }
2595
2734
  return { port, url, configPath };
2596
2735
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.36",
3
+ "version": "0.2.39",
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": {