docdex 0.2.27 → 0.2.29

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/assets/agents.md CHANGED
@@ -1,4 +1,4 @@
1
- ---- START OF DOCDEX INFO V0.2.27 ----
1
+ ---- START OF DOCDEX INFO V0.2.29 ----
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`).
@@ -12,6 +12,11 @@ Health check endpoint: `GET /healthz` (not `/v1/health`).
12
12
  - Use Docdex tools intentionally: docdex_search/symbols/ast for repo truth; docdex_stats/files/repo_inspect/index for index health.
13
13
  - For folder structure, use docdex_tree instead of raw `rg --files`/`find` to avoid noisy folders.
14
14
  - When you do not know something, run docdex_web_research (force_web=true). Web research is encouraged by default for non-repo facts and external APIs.
15
+ - When a Docdex feature makes a task easier/safer, you MUST use it instead of ad-hoc inspection. Examples: `docdex_search` for context, `docdex_open`/`/v1/snippet` for file slices, `docdex_symbols`/`docdex_ast` for structure, `docdex_impact_graph`/`docdex_impact_diagnostics` for dependency safety, and `docdex_dag_export` to review session traces.
16
+ - For dependency/library docs, run `docdexd libs discover`/`docdexd libs fetch` and search with `include_libs` rather than web searching.
17
+ - For test execution, use `docdexd run-tests` (or `docdexd test run-node`) instead of ad-hoc commands when feasible.
18
+ - For staged-change validation, use `docdexd hook pre-commit`.
19
+ - For MCP client registration, use `docdexd mcp add` (or `--all`) instead of editing configs by hand.
15
20
 
16
21
  # Docdex Agent Usage Instructions
17
22
 
@@ -92,8 +97,23 @@ Use local delegation for low-complexity, code-generation-oriented tasks to reduc
92
97
  | docdex_local_completion | Delegate small tasks to a local model with strict output formats. |
93
98
  | HTTP /v1/delegate | HTTP endpoint for delegated completions with structured responses. |
94
99
 
95
- Required fields: `task_type`, `instruction`, `context`. Optional: `max_tokens`, `timeout_ms`, `mode` (`draft_only` or `draft_then_refine`), `agent` (local agent id/slug).
100
+ Required fields: `task_type`, `instruction`, `context`. Optional: `max_tokens`, `timeout_ms`, `mode` (`draft_only` or `draft_then_refine`), `agent` (local agent id/slug or `model:<name>` to force an Ollama model; raw model names from `docdexd delegation agents` are also accepted).
96
101
  Expensive model library: `docs/expensive_models.json` (match by `agent_id`, `agent_slug`, `model`, or adapter type; case-insensitive).
102
+ To choose a local target, run `docdexd delegation agents` (or `--json`) and prefer:
103
+ - `code_writer` for scaffolding/boilerplate/docstrings.
104
+ - `code_reviewer` for tests/format/refactors.
105
+ - `general_chat` for lightweight Q&A or fallback.
106
+ For mcoda agents, also consider:
107
+ - `max_complexity`: do not assign tasks above this ceiling.
108
+ - `rating`: prefer higher-rated agents for reliability.
109
+ - `cost_per_million`: USD per 1M tokens; prefer lower cost when ratings/complexity match.
110
+ - `usage`: best-fit role (for example `code_writer` or `code_reviewer`); use this for quick matching.
111
+ - `reasoning_rating`: reasoning score out of 10; prefer higher for complex reasoning tasks.
112
+ - `health_status`: only use agents marked `healthy` (treat `-` as unknown).
113
+ Table output shows `USAGE`, `COMPLEXITY`, `RATING`, `REASON`, `COST/$1M`, and `HEALTH` for mcoda agents (`-` means unknown).
114
+ - When `llm.delegation.re_evaluate = true` (default), Docdex reviews successful local mcoda runs using the primary agent when available and writes updated ratings to `~/.mcoda/mcoda.db` (disable with `DOCDEX_DELEGATION_REEVALUATE=0`).
115
+ Use `agent: model:<ollama-model>` to force a specific local model (for example, `model:phi3.5:3.8b`).
116
+ Avoid entries that only advertise `embedding` or `vision`.
97
117
 
98
118
  ### E. Index Health + File Access
99
119
 
@@ -125,6 +145,8 @@ Use these to verify index coverage, repo binding, and to read precise file slice
125
145
  - docdex_impact_graph: Mandatory before code changes to review inbound/outbound deps (use MCP/IPC if shell networking is blocked).
126
146
  - docdex_dag_export: Export dependency graph to plan change order.
127
147
  - HTTP /v1/initialize: Mount/bind a repo for HTTP daemon mode. Request JSON uses rootUri/root_uri (NOT repo_root).
148
+ - HTTP /v1/snippet: Fetch exact line-safe snippets for a doc_id returned by search.
149
+ - HTTP /v1/impact/diagnostics: Inspect unresolved/dynamic imports when impact graphs look incomplete.
128
150
 
129
151
  ## CLI Fallbacks (when MCP/IPC is unavailable)
130
152
  Use these only when MCP tools cannot be called (e.g., blocked sandbox networking). Prefer MCP/IPC otherwise.
@@ -133,11 +155,18 @@ Use these only when MCP tools cannot be called (e.g., blocked sandbox networking
133
155
  - `docdexd repo id --repo <path>`: compute repo fingerprint locally.
134
156
  - `docdexd repo status --repo <path>` / `docdexd repo dirty --exit-code`: git working tree status.
135
157
  - `docdexd impact-graph --repo <path> --file <rel>`: impact graph (HTTP/local).
136
- - `docdexd dag export --repo <path> <session_id>`: DAG export alias.
158
+ - `docdexd dag view --repo <path> <session_id>` / `docdexd dag export --repo <path> <session_id>`: DAG export/render.
137
159
  - `docdexd search --repo <path> --query "<q>"`: /search equivalent (HTTP/local).
160
+ - `docdexd delegation savings`: delegation telemetry (JSON: offloaded count, local/primary tokens & costs, savings).
161
+ - `docdexd delegation agents --json`: list local delegation targets and capabilities (mcoda agents include `max_complexity`, `rating`, `cost_per_million`, `usage`, `reasoning_rating`, `health_status`).
138
162
  - `docdexd open --repo <path> --file <rel>`: safe file slice read (head/start/end/clamp).
139
163
  - `docdexd file ensure-newline|write --repo <path> --file <rel>`: minimal file edits.
140
164
  - `docdexd test run-node --repo <path> --file <rel> --args "..."`: run Node scripts.
165
+ - `docdexd run-tests --repo <path> [--target <file|dir>]`: run repo tests (preferred for test execution).
166
+ - `docdexd hook pre-commit --repo <path>`: run semantic gatekeeper hooks against staged changes.
167
+ - `docdexd impact-diagnostics --repo <path> [--file <rel>]`: list unresolved import diagnostics.
168
+ - `docdexd libs discover|fetch --repo <path> [--sources <file>]`: dependency docs discovery/ingestion.
169
+ - `docdexd mcp add --agent <name> [--transport http|ipc] [--all]`: register Docdex MCP in supported clients.
141
170
 
142
171
  ## Docdex Usage Cookbook (Mandatory, Exact Schemas)
143
172
 
@@ -18,6 +18,8 @@ const DAEMON_HEALTH_TIMEOUT_MS = 8000;
18
18
  const DAEMON_HEALTH_REQUEST_TIMEOUT_MS = 1000;
19
19
  const DAEMON_HEALTH_POLL_INTERVAL_MS = 200;
20
20
  const DAEMON_HEALTH_PATH = "/healthz";
21
+ const DAEMON_INFO_PATH = "/ai-help";
22
+ const DAEMON_PORT_RELEASE_TIMEOUT_MS = 5000;
21
23
  const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
22
24
  const DEFAULT_OLLAMA_MODEL = "nomic-embed-text";
23
25
  const DEFAULT_OLLAMA_CHAT_MODEL = "phi3.5:3.8b";
@@ -120,6 +122,139 @@ async function waitForDaemonHealthy({ host, port, timeoutMs = DAEMON_HEALTH_TIME
120
122
  return false;
121
123
  }
122
124
 
125
+ async function waitForPortAvailable({
126
+ host,
127
+ port,
128
+ timeoutMs = DAEMON_PORT_RELEASE_TIMEOUT_MS
129
+ }) {
130
+ const deadline = Date.now() + timeoutMs;
131
+ while (Date.now() < deadline) {
132
+ if (await isPortAvailable(port, host)) {
133
+ return true;
134
+ }
135
+ await sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
136
+ }
137
+ return false;
138
+ }
139
+
140
+ function isPidRunning(pid) {
141
+ if (!Number.isFinite(pid) || pid <= 0) return false;
142
+ try {
143
+ process.kill(pid, 0);
144
+ return true;
145
+ } catch (err) {
146
+ return err?.code === "EPERM";
147
+ }
148
+ }
149
+
150
+ function readDaemonLockMetadataForPort(port) {
151
+ for (const lockPath of daemonLockPaths()) {
152
+ if (!lockPath || !fs.existsSync(lockPath)) continue;
153
+ try {
154
+ const raw = fs.readFileSync(lockPath, "utf8");
155
+ if (!raw.trim()) continue;
156
+ const payload = JSON.parse(raw);
157
+ const lockPort = Number(payload?.port);
158
+ const pid = Number(payload?.pid);
159
+ if (!Number.isFinite(lockPort) || lockPort !== port) continue;
160
+ return { pid, port: lockPort };
161
+ } catch {
162
+ continue;
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+
168
+ function normalizeVersion(value) {
169
+ return String(value || "")
170
+ .trim()
171
+ .replace(/^v/i, "");
172
+ }
173
+
174
+ function fetchDaemonInfo({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
175
+ return new Promise((resolve) => {
176
+ const req = http.request(
177
+ {
178
+ host,
179
+ port,
180
+ path: DAEMON_INFO_PATH,
181
+ method: "GET",
182
+ timeout: timeoutMs
183
+ },
184
+ (res) => {
185
+ let body = "";
186
+ res.setEncoding("utf8");
187
+ res.on("data", (chunk) => {
188
+ body += chunk;
189
+ });
190
+ res.on("end", () => {
191
+ if (res.statusCode !== 200) return resolve(null);
192
+ try {
193
+ const payload = JSON.parse(body);
194
+ resolve(payload);
195
+ } catch {
196
+ resolve(null);
197
+ }
198
+ });
199
+ }
200
+ );
201
+ req.on("timeout", () => {
202
+ req.destroy();
203
+ resolve(null);
204
+ });
205
+ req.on("error", () => resolve(null));
206
+ req.end();
207
+ });
208
+ }
209
+
210
+ function checkDocdexIdentity({ host, port, timeoutMs = DAEMON_HEALTH_REQUEST_TIMEOUT_MS }) {
211
+ return fetchDaemonInfo({ host, port, timeoutMs }).then((payload) => payload?.product === "Docdex");
212
+ }
213
+
214
+ async function resolveDaemonPortState({ host, port, logger, deps } = {}) {
215
+ const log = logger || console;
216
+ const helpers = {
217
+ isPortAvailable,
218
+ checkDaemonHealth,
219
+ checkDocdexIdentity,
220
+ stopDaemonService,
221
+ stopDaemonFromLock,
222
+ stopDaemonByName,
223
+ clearDaemonLocks,
224
+ sleep,
225
+ readDaemonLockMetadataForPort,
226
+ isPidRunning,
227
+ normalizeVersion
228
+ };
229
+ if (deps && typeof deps === "object") {
230
+ Object.assign(helpers, deps);
231
+ }
232
+
233
+ let available = await helpers.isPortAvailable(port, host);
234
+ if (available) return { available: true, reuseExisting: false, stopped: false };
235
+
236
+ helpers.stopDaemonService({ logger: log });
237
+ helpers.stopDaemonFromLock({ logger: log });
238
+ helpers.stopDaemonByName({ logger: log });
239
+ await helpers.sleep(DAEMON_HEALTH_POLL_INTERVAL_MS);
240
+
241
+ available = await helpers.isPortAvailable(port, host);
242
+ if (available) {
243
+ helpers.clearDaemonLocks();
244
+ return { available: true, reuseExisting: false, stopped: true };
245
+ }
246
+
247
+ const lockMeta = helpers.readDaemonLockMetadataForPort(port);
248
+ const lockRunning = lockMeta ? helpers.isPidRunning(lockMeta.pid) : false;
249
+ const healthy = await helpers.checkDaemonHealth({ host, port });
250
+ const identity = lockRunning ? true : await helpers.checkDocdexIdentity({ host, port });
251
+ const reuseExisting = Boolean(lockRunning || healthy || identity);
252
+ if (reuseExisting) {
253
+ log.warn?.(`[docdex] ${host}:${port} already in use by a running docdex daemon; reusing it.`);
254
+ }
255
+ return { available: false, reuseExisting, stopped: false };
256
+ }
257
+
123
258
  function parseServerBind(contents) {
124
259
  let inServer = false;
125
260
  const lines = contents.split(/\r?\n/);
@@ -2254,8 +2389,12 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
2254
2389
  existingConfig = fs.readFileSync(configPath, "utf8");
2255
2390
  }
2256
2391
  const port = DEFAULT_DAEMON_PORT;
2257
- const available = await isPortAvailable(port, DEFAULT_HOST);
2258
- if (!available) {
2392
+ const portState = await resolveDaemonPortState({
2393
+ host: DEFAULT_HOST,
2394
+ port,
2395
+ logger: log
2396
+ });
2397
+ if (!portState.available && !portState.reuseExisting) {
2259
2398
  log.error?.(
2260
2399
  `[docdex] ${DEFAULT_HOST}:${port} is already in use; docdex requires a fixed port. Stop the process using this port and re-run the install.`
2261
2400
  );
@@ -2268,19 +2407,42 @@ async function runPostInstallSetup({ binaryPath, logger } = {}) {
2268
2407
  binaryPath: resolvedBinary,
2269
2408
  logger: log
2270
2409
  });
2271
- stopDaemonService({ logger: log });
2272
- stopDaemonFromLock({ logger: log });
2273
- stopDaemonByName({ logger: log });
2274
- clearDaemonLocks();
2275
- const result = await startDaemonWithHealthCheck({
2276
- binaryPath: startupBinaries.binaryPath,
2277
- port,
2278
- host: DEFAULT_HOST,
2279
- logger: log
2280
- });
2281
- if (!result.ok) {
2282
- log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
2283
- throw new Error("docdex daemon failed to start");
2410
+ let reuseExisting = portState.reuseExisting;
2411
+ if (reuseExisting) {
2412
+ const daemonInfo = await fetchDaemonInfo({ host: DEFAULT_HOST, port });
2413
+ const daemonVersion = normalizeVersion(daemonInfo?.version);
2414
+ const packageVersion = normalizeVersion(resolvePackageVersion());
2415
+ if (daemonInfo?.product === "Docdex" && daemonVersion && packageVersion) {
2416
+ if (daemonVersion !== packageVersion) {
2417
+ log.warn?.(
2418
+ `[docdex] daemon version ${daemonVersion} differs from package ${packageVersion}; restarting daemon.`
2419
+ );
2420
+ stopDaemonService({ logger: log });
2421
+ stopDaemonFromLock({ logger: log });
2422
+ stopDaemonByName({ logger: log });
2423
+ clearDaemonLocks();
2424
+ const released = await waitForPortAvailable({
2425
+ host: DEFAULT_HOST,
2426
+ port
2427
+ });
2428
+ if (!released) {
2429
+ throw new Error("docdex daemon restart failed; port still in use");
2430
+ }
2431
+ reuseExisting = false;
2432
+ }
2433
+ }
2434
+ }
2435
+ if (!reuseExisting) {
2436
+ const result = await startDaemonWithHealthCheck({
2437
+ binaryPath: startupBinaries.binaryPath,
2438
+ port,
2439
+ host: DEFAULT_HOST,
2440
+ logger: log
2441
+ });
2442
+ if (!result.ok) {
2443
+ log.warn?.(`[docdex] daemon failed to start on ${DEFAULT_HOST}:${port}.`);
2444
+ throw new Error("docdex daemon failed to start");
2445
+ }
2284
2446
  }
2285
2447
 
2286
2448
  const httpBindAddr = `${DEFAULT_HOST}:${port}`;
@@ -2345,5 +2507,7 @@ module.exports = {
2345
2507
  shouldSkipSetup,
2346
2508
  launchSetupWizard,
2347
2509
  applyAgentInstructions,
2348
- buildDaemonEnv
2510
+ buildDaemonEnv,
2511
+ resolveDaemonPortState,
2512
+ normalizeVersion
2349
2513
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
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": {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "homepage": "https://docdex.org",
46
46
  "license": "MIT",
47
- "author": "bekir dağ",
47
+ "author": "bekir da\u011f",
48
48
  "keywords": [
49
49
  "docdex",
50
50
  "documentation",