docdex 0.2.37 → 0.2.40

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,11 @@
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
+
3
9
  ## 0.2.37
4
10
  - Bump release metadata to 0.2.37.
5
11
 
package/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.37 ----
1
+ ---- START OF DOCDEX INFO V0.2.40 ----
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`).
@@ -177,7 +177,7 @@ This section is the authoritative source for how to call Docdex. Do not guess fi
177
177
 
178
178
  - Default HTTP base URL: http://127.0.0.1:28491 (override with DOCDEX_HTTP_BASE_URL).
179
179
  - Single-repo HTTP daemon: `docdexd serve --repo /abs/path`. /v1/initialize is NOT used. repo_id is optional, but must match the serving repo if provided.
180
- - Multi-repo HTTP daemon: `docdexd daemon`. You MUST call /v1/initialize before repo-scoped HTTP endpoints. When multiple repos are mounted, repo_id is required on every repo-scoped request.
180
+ - Multi-repo HTTP daemon: `docdexd daemon` (alias: `docdex start`). You MUST call /v1/initialize before repo-scoped HTTP endpoints. When multiple repos are mounted, repo_id is required on every repo-scoped request.
181
181
 
182
182
  ### 1) Initialize (HTTP) - exact request payload
183
183
 
@@ -305,9 +305,10 @@ When answering a complex coding query, follow this "Reasoning Trace":
305
305
  Save more memories for both lobes during the task, not just at the end.
306
306
 
307
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`.
308
- 2. Profile memory: When the user expresses a preference, constraint, or workflow correction, call `docdex_save_preference` immediately with the right category.
309
- 3. Keep it crisp: 1-3 short sentences, include file paths when relevant, avoid raw code blobs.
310
- 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.
311
312
 
312
313
  ### 3. Index Health + Diff-Aware Search (Mandatory)
313
314
 
package/lib/install.js CHANGED
@@ -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";
@@ -2261,9 +2262,32 @@ function writeWindowsRunner({ binaryPath, args, envPairs, workingDir, logger, di
2261
2262
  }
2262
2263
  }
2263
2264
 
2264
- function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
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
+
2286
+ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir, startNow = true }) {
2265
2287
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
2266
- stopDaemonService({ logger });
2288
+ if (startNow) {
2289
+ stopDaemonService({ logger });
2290
+ }
2267
2291
  const envPairs = buildDaemonEnvPairs();
2268
2292
  const workingDir = repoRoot ? path.resolve(repoRoot) : null;
2269
2293
  const args = [
@@ -2315,6 +2339,7 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2315
2339
  `</plist>\n`;
2316
2340
  fs.mkdirSync(path.dirname(plistPath), { recursive: true });
2317
2341
  fs.writeFileSync(plistPath, plist);
2342
+ if (!startNow) return { ok: true };
2318
2343
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
2319
2344
  const bootstrap = uid != null
2320
2345
  ? spawnSync("launchctl", ["bootstrap", `gui/${uid}`, plistPath])
@@ -2349,7 +2374,11 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2349
2374
  ].filter(Boolean).join("\n");
2350
2375
  fs.writeFileSync(unitPath, unit);
2351
2376
  const reload = spawnSync("systemctl", ["--user", "daemon-reload"]);
2352
- const enable = spawnSync("systemctl", ["--user", "enable", "--now", "docdexd.service"]);
2377
+ const enableArgs = startNow
2378
+ ? ["--user", "enable", "--now", "docdexd.service"]
2379
+ : ["--user", "enable", "docdexd.service"];
2380
+ const enable = spawnSync("systemctl", enableArgs);
2381
+ ensureSystemdUserLinger({ logger });
2353
2382
  if (reload.status === 0 && enable.status === 0) return { ok: true };
2354
2383
  logger?.warn?.(`[docdex] systemd failed: ${enable.stderr || reload.stderr || "unknown error"}`);
2355
2384
  return { ok: false, reason: "systemd_failed" };
@@ -2380,7 +2409,9 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2380
2409
  taskArgs
2381
2410
  ]);
2382
2411
  if (create.status === 0) {
2383
- spawnSync("schtasks", ["/Run", "/TN", taskName]);
2412
+ if (startNow) {
2413
+ spawnSync("schtasks", ["/Run", "/TN", taskName]);
2414
+ }
2384
2415
  return { ok: true };
2385
2416
  }
2386
2417
  logger?.warn?.(`[docdex] schtasks failed: ${create.stderr || "unknown error"}`);
@@ -2390,18 +2421,22 @@ function registerStartup({ binaryPath, port, repoRoot, logger, distBaseDir }) {
2390
2421
  return { ok: false, reason: "unsupported_platform" };
2391
2422
  }
2392
2423
 
2393
- async function startDaemonWithHealthCheck({ binaryPath, port, host, logger, distBaseDir }) {
2424
+ async function startDaemonWithHealthCheck({ binaryPath, port, host, logger, distBaseDir, startNow = true }) {
2394
2425
  const startup = registerStartup({
2395
2426
  binaryPath,
2396
2427
  port,
2397
2428
  repoRoot: daemonRootPath(),
2398
2429
  logger,
2399
- distBaseDir
2430
+ distBaseDir,
2431
+ startNow
2400
2432
  });
2401
2433
  if (!startup.ok) {
2402
2434
  logger?.warn?.(`[docdex] daemon service registration failed (${startup.reason || "unknown"}).`);
2403
2435
  return { ok: false, reason: "startup_failed" };
2404
2436
  }
2437
+ if (!startNow) {
2438
+ return { ok: true, reason: "registered" };
2439
+ }
2405
2440
  startDaemonService({ logger });
2406
2441
  const healthy = await waitForDaemonHealthy({ host, port });
2407
2442
  if (healthy) {
@@ -2456,6 +2491,25 @@ function commandExists(cmd, spawnSyncFn) {
2456
2491
  return true;
2457
2492
  }
2458
2493
 
2494
+ function currentUsername() {
2495
+ try {
2496
+ const info = os.userInfo();
2497
+ if (info && info.username) return info.username;
2498
+ } catch {}
2499
+ return process.env.USER || process.env.LOGNAME || process.env.USERNAME || null;
2500
+ }
2501
+
2502
+ function ensureSystemdUserLinger({ logger } = {}) {
2503
+ if (process.platform !== "linux") return { ok: false, reason: "unsupported_platform" };
2504
+ if (!commandExists("loginctl", spawnSync)) return { ok: false, reason: "loginctl_missing" };
2505
+ const username = currentUsername();
2506
+ if (!username) return { ok: false, reason: "username_missing" };
2507
+ const result = spawnSync("loginctl", ["enable-linger", username]);
2508
+ if (result.status === 0) return { ok: true };
2509
+ logger?.warn?.(`[docdex] loginctl enable-linger failed: ${result.stderr || "unknown error"}`);
2510
+ return { ok: false, reason: "loginctl_failed" };
2511
+ }
2512
+
2459
2513
  function launchMacTerminal({ binaryPath, args, spawnSyncFn, logger }) {
2460
2514
  const command = [
2461
2515
  "DOCDEX_SETUP_AUTO=1",
@@ -2514,7 +2568,8 @@ function launchSetupWizard({
2514
2568
  spawnFn = spawn,
2515
2569
  spawnSyncFn = spawnSync,
2516
2570
  platform = process.platform,
2517
- canPrompt = canPromptWithTty
2571
+ canPrompt = canPromptWithTty,
2572
+ distBaseDir
2518
2573
  }) {
2519
2574
  if (!binaryPath) return { ok: false, reason: "missing_binary" };
2520
2575
  if (shouldSkipSetup(env)) return { ok: false, reason: "skipped" };
@@ -2535,9 +2590,21 @@ function launchSetupWizard({
2535
2590
  }
2536
2591
 
2537
2592
  if (platform === "win32") {
2538
- const quoted = `"${binaryPath}" ${args.map((arg) => `"${arg}"`).join(" ")}`;
2539
- const cmdline = `set DOCDEX_SETUP_AUTO=1 && set DOCDEX_SETUP_MODE=auto && ${quoted}`;
2540
- const result = spawnSyncFn("cmd", ["/c", "start", "", "cmd", "/c", cmdline]);
2593
+ const runnerPath = writeWindowsSetupRunner({
2594
+ binaryPath,
2595
+ args,
2596
+ logger,
2597
+ distBaseDir
2598
+ });
2599
+ if (!runnerPath) return { ok: false, reason: "runner_failed" };
2600
+ const result = spawnSyncFn("cmd", [
2601
+ "/c",
2602
+ "start",
2603
+ "",
2604
+ "cmd",
2605
+ "/c",
2606
+ escapeCmdArg(runnerPath)
2607
+ ]);
2541
2608
  if (result.status === 0) return { ok: true };
2542
2609
  logger?.warn?.(`[docdex] cmd start failed: ${result.stderr || "unknown error"}`);
2543
2610
  return { ok: false, reason: "terminal_launch_failed" };
@@ -2630,14 +2697,15 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2630
2697
  }
2631
2698
  }
2632
2699
  }
2633
- let startupOk = reuseExisting;
2634
- if (allowDaemon && !reuseExisting) {
2700
+ let startupOk = false;
2701
+ if (allowDaemon) {
2635
2702
  const result = await startDaemonWithHealthCheck({
2636
2703
  binaryPath: startupBinaries.binaryPath,
2637
2704
  port,
2638
2705
  host: DEFAULT_HOST,
2639
2706
  logger: log,
2640
- distBaseDir: resolvedDistBaseDir
2707
+ distBaseDir: resolvedDistBaseDir,
2708
+ startNow: !reuseExisting
2641
2709
  });
2642
2710
  if (!result.ok) {
2643
2711
  log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
@@ -2681,13 +2749,20 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2681
2749
  if (startupOk) {
2682
2750
  clearStartupFailure();
2683
2751
  }
2684
- const skipWizard = isNpmLifecycle(effectiveEnv) || shouldSkipSetup(effectiveEnv);
2752
+ const skipExplicit = shouldSkipSetup(effectiveEnv);
2753
+ const skipWizard = isNpmLifecycle(effectiveEnv) || skipExplicit;
2685
2754
  const setupLaunch = skipWizard
2686
- ? { ok: false, reason: "skipped" }
2687
- : launchSetupWizard({ binaryPath: startupBinaries.binaryPath, logger: log });
2688
- if (!setupLaunch.ok && setupLaunch.reason !== "skipped") {
2689
- log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
2690
- recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
2755
+ ? { ok: false, reason: skipExplicit ? "skipped" : "npm_lifecycle" }
2756
+ : launchSetupWizard({
2757
+ binaryPath: startupBinaries.binaryPath,
2758
+ logger: log,
2759
+ distBaseDir: resolvedDistBaseDir
2760
+ });
2761
+ if (!setupLaunch.ok) {
2762
+ if (setupLaunch.reason !== "skipped") {
2763
+ log.warn?.("[docdex] setup wizard did not launch. Run `docdex setup`.");
2764
+ recordSetupPending({ reason: setupLaunch.reason, port, repoRoot: daemonRoot });
2765
+ }
2691
2766
  }
2692
2767
  return { port, url, configPath };
2693
2768
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.37",
3
+ "version": "0.2.40",
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": {