docdex 0.2.81 → 0.2.85

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
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.85
6
+ - Add a packaged disabled-by-default encrypted user-memory sync preview with config/status/dry-run support, bundle generation, and server-store register, push, feed, apply, and ack endpoints.
7
+ - Scope packaged hosted user-memory sync requests through external API-key introspection, including user-memory sync scope checks and a canonical hashed principal so multiple API keys for one user merge into one feed.
8
+ - Add packaged AES-256-GCM payload envelopes, local decrypt/AAD/hash verification, profile-memory down-sync apply with embedding rebuild and last-write-wins import, and applied/skipped ledger tracking.
9
+ - Document packaged production plan, progress, and threat model boundaries; non-profile lanes currently sync as encrypted inventory/policy events until safe importers are implemented.
10
+ - Harden packaged release validation by widening daemon health waits under full-suite load and serializing MCP local-completion daemon tests that contend for local SQLite state.
11
+ - Upgrade the packaged tar extraction dependency to `tar` 7.5.19 so production npm audit is clean before publish.
12
+ - Bump packaged release metadata to 0.2.85.
13
+
14
+ ## 0.2.84
15
+ - Distinguish packaged daemon startup-in-progress locks from healthy already-running daemons so `docdexd daemon` tells clients to wait for `/healthz` and `/v1/mcp` readiness instead of advertising an unavailable MCP endpoint.
16
+ - Harden packaged npm postinstall daemon readiness checks by requiring both `/healthz` and the streamable MCP `/v1/mcp` route before treating a started or reused daemon as ready.
17
+ - Make packaged daemon singleton and mcoda registry tests deterministic by using a bounded full health response read, disabling cloud refresh for registry-only assertions, and allowing slower daemon binds in local/CI environments with `DOCDEX_DAEMON_AUTO_START_TIMEOUT_SECS`.
18
+ - Pin packaged GitHub Actions npm upgrades to npm 11.18.0 so trusted publishing remains compatible with the workflow's Node 20 runtime.
19
+ - Bump packaged release metadata to 0.2.84.
20
+
5
21
  ## 0.2.81
6
22
  - Harden packaged repo memory storage by enabling SQLite WAL mode and a 5-second busy timeout so concurrent Docdex agents can tolerate transient `database is locked` contention during memory saves.
7
23
  - Add packaged regression coverage for repo memory connection settings.
package/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.81 ----
1
+ ---- START OF DOCDEX INFO V0.2.85 ----
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`).
@@ -25,6 +25,7 @@ const DAEMON_HEALTH_TIMEOUT_MS = 8000;
25
25
  const DAEMON_HEALTH_REQUEST_TIMEOUT_MS = 1000;
26
26
  const DAEMON_HEALTH_POLL_INTERVAL_MS = 200;
27
27
  const DAEMON_HEALTH_PATH = "/healthz";
28
+ const DAEMON_MCP_READY_PATH = "/v1/mcp";
28
29
  const DAEMON_INFO_PATH = "/ai-help";
29
30
  const DAEMON_PORT_RELEASE_TIMEOUT_MS = 5000;
30
31
  const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
@@ -128,6 +129,52 @@ function checkDaemonHealth({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEO
128
129
  });
129
130
  }
130
131
 
132
+ function checkDaemonMcpReady({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
133
+ return new Promise((resolve) => {
134
+ let settled = false;
135
+ const finish = (value) => {
136
+ if (settled) return;
137
+ settled = true;
138
+ resolve(value);
139
+ };
140
+ const payload = JSON.stringify({
141
+ jsonrpc: "2.0",
142
+ id: 1,
143
+ method: "ping",
144
+ params: {}
145
+ });
146
+ const req = http.request(
147
+ {
148
+ host,
149
+ port,
150
+ path: DAEMON_MCP_READY_PATH,
151
+ method: "POST",
152
+ timeout: timeoutMs,
153
+ headers: {
154
+ "content-type": "application/json",
155
+ "content-length": Buffer.byteLength(payload)
156
+ }
157
+ },
158
+ (res) => {
159
+ let body = "";
160
+ res.setEncoding("utf8");
161
+ res.on("data", (chunk) => {
162
+ if (body.length < 4096) body += chunk;
163
+ });
164
+ res.on("end", () => {
165
+ finish(res.statusCode === 200 && body.includes("\"jsonrpc\""));
166
+ });
167
+ }
168
+ );
169
+ req.on("timeout", () => {
170
+ req.destroy();
171
+ finish(false);
172
+ });
173
+ req.on("error", () => finish(false));
174
+ req.end(payload);
175
+ });
176
+ }
177
+
131
178
  async function waitForDaemonHealthy({ host, port, timeoutMs = DAEMON_HEALTH_TIMEOUT_MS }) {
132
179
  const deadline = Date.now() + timeoutMs;
133
180
  while (Date.now() < deadline) {
@@ -139,6 +186,33 @@ async function waitForDaemonHealthy({ host, port, timeoutMs = DAEMON_HEALTH_TIME
139
186
  return false;
140
187
  }
141
188
 
189
+ async function waitForDaemonReady({
190
+ host,
191
+ port,
192
+ timeoutMs = DAEMON_HEALTH_TIMEOUT_MS,
193
+ deps
194
+ }) {
195
+ const helpers = {
196
+ checkDaemonHealth,
197
+ checkDaemonMcpReady,
198
+ sleep
199
+ };
200
+ if (deps && typeof deps === "object") {
201
+ Object.assign(helpers, deps);
202
+ }
203
+ const deadline = Date.now() + timeoutMs;
204
+ while (Date.now() < deadline) {
205
+ if (
206
+ await helpers.checkDaemonHealth({ host, port }) &&
207
+ await helpers.checkDaemonMcpReady({ host, port })
208
+ ) {
209
+ return true;
210
+ }
211
+ await helpers.sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
212
+ }
213
+ return false;
214
+ }
215
+
142
216
  async function waitForPortAvailable({
143
217
  host,
144
218
  port,
@@ -265,11 +339,16 @@ async function resolveDaemonPortState({ host, port, logger, deps } = {}) {
265
339
  const lockRunning = lockMeta ? helpers.isPidRunning(lockMeta.pid) : false;
266
340
  const healthy = await helpers.checkDaemonHealth({ host, port });
267
341
  const identity = lockRunning ? true : await helpers.checkDocdexIdentity({ host, port });
342
+ const starting = Boolean(lockRunning && !healthy);
268
343
  const reuseExisting = Boolean(lockRunning || healthy || identity);
269
344
  if (reuseExisting) {
270
- log.warn?.(`[docdex] ${host}:${port} already in use by a running docdex daemon; reusing it.`);
345
+ if (starting) {
346
+ log.warn?.(`[docdex] ${host}:${port} has a running docdex daemon process that is still starting; waiting for readiness.`);
347
+ } else {
348
+ log.warn?.(`[docdex] ${host}:${port} already in use by a running docdex daemon; reusing it.`);
349
+ }
271
350
  }
272
- return { available: false, reuseExisting, stopped: false };
351
+ return { available: false, reuseExisting, starting, stopped: false };
273
352
  }
274
353
 
275
354
  function parseServerBind(contents) {
@@ -2690,11 +2769,12 @@ async function startDaemonWithHealthCheck({
2690
2769
  logger,
2691
2770
  distBaseDir,
2692
2771
  startNow = true,
2772
+ waitForReady = startNow,
2693
2773
  deps
2694
2774
  }) {
2695
2775
  const helpers = {
2696
2776
  registerStartup,
2697
- waitForDaemonHealthy,
2777
+ waitForDaemonReady,
2698
2778
  stopDaemonService,
2699
2779
  stopDaemonFromLock,
2700
2780
  stopDaemonByName,
@@ -2716,21 +2796,29 @@ async function startDaemonWithHealthCheck({
2716
2796
  return { ok: false, reason: "startup_failed" };
2717
2797
  }
2718
2798
  if (!startNow) {
2799
+ if (waitForReady) {
2800
+ const ready = await helpers.waitForDaemonReady({ host, port });
2801
+ if (ready) {
2802
+ return { ok: true, reason: "ready" };
2803
+ }
2804
+ logger?.warn?.(`[docdex] daemon failed readiness check on ${host}:${port}`);
2805
+ return { ok: false, reason: "readiness_failed" };
2806
+ }
2719
2807
  return { ok: true, reason: "registered" };
2720
2808
  }
2721
2809
  // `registerStartup(..., startNow: true)` already starts the service on all
2722
2810
  // supported platforms. Starting it again here can interrupt the first boot
2723
2811
  // and leave the daemon stuck behind its own lock file.
2724
- const healthy = await helpers.waitForDaemonHealthy({ host, port });
2725
- if (healthy) {
2726
- return { ok: true, reason: "healthy" };
2812
+ const ready = await helpers.waitForDaemonReady({ host, port });
2813
+ if (ready) {
2814
+ return { ok: true, reason: "ready" };
2727
2815
  }
2728
- logger?.warn?.(`[docdex] daemon failed health check on ${host}:${port}`);
2816
+ logger?.warn?.(`[docdex] daemon failed readiness check on ${host}:${port}`);
2729
2817
  helpers.stopDaemonService({ logger });
2730
2818
  helpers.stopDaemonFromLock({ logger });
2731
2819
  helpers.stopDaemonByName({ logger });
2732
2820
  helpers.clearDaemonLocks();
2733
- return { ok: false, reason: "health_failed" };
2821
+ return { ok: false, reason: "readiness_failed" };
2734
2822
  }
2735
2823
 
2736
2824
  function recordStartupFailure(details) {
@@ -2997,7 +3085,8 @@ async function runPostInstallSetup({ binaryPath, logger, env, skipDaemon, distBa
2997
3085
  host: DEFAULT_HOST,
2998
3086
  logger: log,
2999
3087
  distBaseDir: resolvedDistBaseDir,
3000
- startNow: !reuseExisting && allowStartNow
3088
+ startNow: !reuseExisting && allowStartNow,
3089
+ waitForReady: reuseExisting || (!reuseExisting && allowStartNow)
3001
3090
  });
3002
3091
  if (!result.ok) {
3003
3092
  log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
@@ -3096,6 +3185,8 @@ module.exports = {
3096
3185
  buildDaemonEnv,
3097
3186
  buildLaunchAgentPlist,
3098
3187
  startDaemonWithHealthCheck,
3188
+ checkDaemonMcpReady,
3189
+ waitForDaemonReady,
3099
3190
  resolveDaemonPortState,
3100
3191
  normalizeVersion
3101
3192
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.81",
3
+ "version": "0.2.85",
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": {
@@ -62,6 +62,6 @@
62
62
  "access": "public"
63
63
  },
64
64
  "dependencies": {
65
- "tar": "^6.2.1"
65
+ "tar": "^7.5.19"
66
66
  }
67
67
  }