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 +16 -0
- package/assets/agents.md +1 -1
- package/lib/postinstall_setup.js +100 -9
- package/package.json +2 -2
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
package/lib/postinstall_setup.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
2725
|
-
if (
|
|
2726
|
-
return { ok: true, reason: "
|
|
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
|
|
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: "
|
|
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.
|
|
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": "^
|
|
65
|
+
"tar": "^7.5.19"
|
|
66
66
|
}
|
|
67
67
|
}
|