eminiar 0.10.0 → 0.10.1
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/.claude-plugin/plugin.json +1 -1
- package/dist/cli.js +214 -50
- package/dist/hook.js +161 -41
- package/dist/server.js +247 -57
- package/package.json +1 -1
- package/skills/em-docs/SKILL.md +3 -1
package/dist/hook.js
CHANGED
|
@@ -26434,7 +26434,7 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
26434
26434
|
if (/^\s*(```|~~~)/.test(line2)) inFence = !inFence;
|
|
26435
26435
|
const m = !inFence && /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line2);
|
|
26436
26436
|
if (m) {
|
|
26437
|
-
|
|
26437
|
+
sections.push(current);
|
|
26438
26438
|
const level = m[1].length;
|
|
26439
26439
|
while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
|
|
26440
26440
|
stack.push({ level, text: m[2] });
|
|
@@ -26443,14 +26443,23 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
26443
26443
|
}
|
|
26444
26444
|
current.lines.push(line2);
|
|
26445
26445
|
});
|
|
26446
|
-
|
|
26446
|
+
sections.push(current);
|
|
26447
26447
|
const out = [];
|
|
26448
|
-
|
|
26448
|
+
sections.forEach((s, k) => {
|
|
26449
26449
|
const title = s.path.length ? `${baseTitle} > ${s.path.join(" > ")}` : baseTitle;
|
|
26450
26450
|
const body = s.lines.join("\n");
|
|
26451
|
+
if (!body.trim()) {
|
|
26452
|
+
const next = sections[k + 1];
|
|
26453
|
+
const isParent = next !== void 0 && next.path.length > s.path.length && s.path.every((p, j) => next.path[j] === p);
|
|
26454
|
+
if (s.path.length && !isParent) {
|
|
26455
|
+
const heading = s.path[s.path.length - 1];
|
|
26456
|
+
out.push({ title, body: heading, startLine: s.startLine, endLine: s.startLine });
|
|
26457
|
+
}
|
|
26458
|
+
return;
|
|
26459
|
+
}
|
|
26451
26460
|
if (body.length <= opts.maxChars) {
|
|
26452
26461
|
out.push({ title, body, startLine: s.startLine, endLine: s.startLine + s.lines.length - 1 });
|
|
26453
|
-
|
|
26462
|
+
return;
|
|
26454
26463
|
}
|
|
26455
26464
|
let fence = false;
|
|
26456
26465
|
const fenceState = s.lines.map((l) => {
|
|
@@ -26466,7 +26475,7 @@ function chunkMarkdown(text, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
26466
26475
|
s.startLine - 1
|
|
26467
26476
|
)
|
|
26468
26477
|
);
|
|
26469
|
-
}
|
|
26478
|
+
});
|
|
26470
26479
|
return out;
|
|
26471
26480
|
}
|
|
26472
26481
|
|
|
@@ -26549,7 +26558,20 @@ function chunk(content, baseTitle, kind, uri, opts = DEFAULT_CHUNKER) {
|
|
|
26549
26558
|
// src/index/indexer.ts
|
|
26550
26559
|
function indexContent(db, input2) {
|
|
26551
26560
|
const kind = input2.kind ?? (input2.contentType === "code" ? "code" : input2.contentType === "json" ? "json" : void 0);
|
|
26552
|
-
const
|
|
26561
|
+
const cut = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
|
|
26562
|
+
const k = cut.kind;
|
|
26563
|
+
const fallback = cut.chunks.length === 0 && input2.content.trim().length > 0;
|
|
26564
|
+
const chunks = fallback ? finalize2(
|
|
26565
|
+
[
|
|
26566
|
+
{
|
|
26567
|
+
title: input2.title,
|
|
26568
|
+
body: input2.content,
|
|
26569
|
+
startLine: 1,
|
|
26570
|
+
endLine: input2.content.split("\n").length
|
|
26571
|
+
}
|
|
26572
|
+
],
|
|
26573
|
+
kindToContentType(k)
|
|
26574
|
+
) : cut.chunks;
|
|
26553
26575
|
const contentHash = hashText(input2.content);
|
|
26554
26576
|
const existing = input2.uri ? db.findDocumentByUri(input2.uri) : void 0;
|
|
26555
26577
|
if (existing && existing.content_hash === contentHash) {
|
|
@@ -26559,7 +26581,8 @@ function indexContent(db, input2) {
|
|
|
26559
26581
|
created: false,
|
|
26560
26582
|
chunks: chunks.length,
|
|
26561
26583
|
chars: input2.content.length,
|
|
26562
|
-
kind: k
|
|
26584
|
+
kind: k,
|
|
26585
|
+
fallback
|
|
26563
26586
|
};
|
|
26564
26587
|
}
|
|
26565
26588
|
db.deleteDocument(existing.id);
|
|
@@ -26576,7 +26599,14 @@ function indexContent(db, input2) {
|
|
|
26576
26599
|
},
|
|
26577
26600
|
chunks
|
|
26578
26601
|
);
|
|
26579
|
-
return {
|
|
26602
|
+
return {
|
|
26603
|
+
documentId: id,
|
|
26604
|
+
created,
|
|
26605
|
+
chunks: chunks.length,
|
|
26606
|
+
chars: input2.content.length,
|
|
26607
|
+
kind: k,
|
|
26608
|
+
fallback
|
|
26609
|
+
};
|
|
26580
26610
|
}
|
|
26581
26611
|
var MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
26582
26612
|
|
|
@@ -26597,6 +26627,23 @@ function fileOf(id) {
|
|
|
26597
26627
|
return void 0;
|
|
26598
26628
|
}
|
|
26599
26629
|
|
|
26630
|
+
// src/graph/metrics.ts
|
|
26631
|
+
init_esm_shims();
|
|
26632
|
+
|
|
26633
|
+
// src/graph/clusters.ts
|
|
26634
|
+
init_esm_shims();
|
|
26635
|
+
|
|
26636
|
+
// src/graph/metrics.ts
|
|
26637
|
+
var DEPENDENCY_KINDS = [
|
|
26638
|
+
"calls",
|
|
26639
|
+
"references",
|
|
26640
|
+
"extends",
|
|
26641
|
+
"implements",
|
|
26642
|
+
"imports",
|
|
26643
|
+
"reexports",
|
|
26644
|
+
"handles"
|
|
26645
|
+
];
|
|
26646
|
+
|
|
26600
26647
|
// src/history/model.ts
|
|
26601
26648
|
init_esm_shims();
|
|
26602
26649
|
|
|
@@ -27298,6 +27345,9 @@ function memoriesForFile(db, rel, opts = {}) {
|
|
|
27298
27345
|
}
|
|
27299
27346
|
var RISKY = 0.3;
|
|
27300
27347
|
var FAN_IN = 5;
|
|
27348
|
+
function dependentFiles(db, paths2) {
|
|
27349
|
+
return db.graphDependentFileCounts(paths2, DEPENDENCY_KINDS);
|
|
27350
|
+
}
|
|
27301
27351
|
function riskLine(path2, f) {
|
|
27302
27352
|
if (f.risk < RISKY && !f.bugOrigins) return void 0;
|
|
27303
27353
|
const parts = [`${f.fixes}/${f.commits} commits were fixes`];
|
|
@@ -27325,10 +27375,10 @@ function fileContext(db, rel, opts = {}) {
|
|
|
27325
27375
|
}
|
|
27326
27376
|
const fanKey = `fanin:${rel}`;
|
|
27327
27377
|
if (opts.edit && !seen.has(fanKey)) {
|
|
27328
|
-
const
|
|
27329
|
-
if (
|
|
27378
|
+
const dependents = dependentFiles(db, [rel]).get(rel) ?? 0;
|
|
27379
|
+
if (dependents >= FAN_IN) {
|
|
27330
27380
|
lines.push(
|
|
27331
|
-
`eminiar graph: ${rel} is imported by ${
|
|
27381
|
+
`eminiar graph: ${rel} is imported by ${dependents} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
|
|
27332
27382
|
);
|
|
27333
27383
|
keys.push(fanKey);
|
|
27334
27384
|
}
|
|
@@ -27340,7 +27390,7 @@ function commitContext(db, files) {
|
|
|
27340
27390
|
if (!files.length) return "";
|
|
27341
27391
|
const table = readFileRisk(db);
|
|
27342
27392
|
const risky = files.map((p) => table[p] ? riskLine(p, table[p]) : void 0).filter((l) => !!l).slice(0, 4);
|
|
27343
|
-
const wide = db
|
|
27393
|
+
const wide = [...dependentFiles(db, files)].filter(([, n]) => n >= FAN_IN).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([p, n]) => `${p} (${n} importers)`);
|
|
27344
27394
|
if (!risky.length && !wide.length) return "";
|
|
27345
27395
|
const lines = ["eminiar history: this commit includes files that deserve a second look:"];
|
|
27346
27396
|
for (const r of risky) lines.push(`- ${r}`);
|
|
@@ -27624,6 +27674,10 @@ function topTermsOf(bodies, limit) {
|
|
|
27624
27674
|
|
|
27625
27675
|
// src/storage/sqlite/schema.ts
|
|
27626
27676
|
init_esm_shims();
|
|
27677
|
+
function hasColumn(db, table, column) {
|
|
27678
|
+
const cols = db.pragma(`table_info(${table})`);
|
|
27679
|
+
return cols.some((c) => c.name === column);
|
|
27680
|
+
}
|
|
27627
27681
|
var migrations = [
|
|
27628
27682
|
{
|
|
27629
27683
|
version: 1,
|
|
@@ -27958,7 +28012,6 @@ CREATE TABLE IF NOT EXISTS transcript_files (
|
|
|
27958
28012
|
offset INTEGER NOT NULL DEFAULT 0,
|
|
27959
28013
|
size INTEGER NOT NULL DEFAULT 0,
|
|
27960
28014
|
mtime INTEGER NOT NULL DEFAULT 0,
|
|
27961
|
-
head_hash TEXT,
|
|
27962
28015
|
ingested_at INTEGER NOT NULL
|
|
27963
28016
|
);
|
|
27964
28017
|
|
|
@@ -27986,6 +28039,16 @@ CREATE TABLE IF NOT EXISTS statusline_snapshots (
|
|
|
27986
28039
|
CREATE INDEX IF NOT EXISTS statusline_snapshots_session_ts ON statusline_snapshots(session_id, ts);
|
|
27987
28040
|
CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
|
|
27988
28041
|
`
|
|
28042
|
+
},
|
|
28043
|
+
{
|
|
28044
|
+
// transcript_files.head_hash (fingerprint of the file head, so a rewrite with the same
|
|
28045
|
+
// size is noticed) was first added to v5 after v5 had already run on real databases, so
|
|
28046
|
+
// version-5 databases exist both with and without the column. Add it only when missing.
|
|
28047
|
+
version: 6,
|
|
28048
|
+
up(db) {
|
|
28049
|
+
if (!hasColumn(db, "transcript_files", "head_hash"))
|
|
28050
|
+
db.exec("ALTER TABLE transcript_files ADD COLUMN head_hash TEXT");
|
|
28051
|
+
}
|
|
27989
28052
|
}
|
|
27990
28053
|
];
|
|
27991
28054
|
function vecTableSql(dims) {
|
|
@@ -28097,7 +28160,8 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
28097
28160
|
if (pending.length) {
|
|
28098
28161
|
this.transaction(() => {
|
|
28099
28162
|
for (const m of pending) {
|
|
28100
|
-
this.db.exec(m.sql);
|
|
28163
|
+
if (m.sql) this.db.exec(m.sql);
|
|
28164
|
+
m.up?.(this.db);
|
|
28101
28165
|
this.metaSet("schema_version", String(m.version));
|
|
28102
28166
|
}
|
|
28103
28167
|
});
|
|
@@ -28316,18 +28380,42 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
28316
28380
|
}
|
|
28317
28381
|
vecSearch(embedding, k, filters) {
|
|
28318
28382
|
if (!this.vectors) return [];
|
|
28319
|
-
const raw = this.db.prepare(
|
|
28320
|
-
"SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ORDER BY distance"
|
|
28321
|
-
).all(embedding, BigInt(filters ? k * 4 : k));
|
|
28322
|
-
if (!filters || !raw.length) return raw.map((r) => ({ chunkId: r.id, distance: r.distance }));
|
|
28323
28383
|
const { where, params } = filterSql(filters);
|
|
28324
|
-
|
|
28325
|
-
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28384
|
+
if (!where) return this.knn(embedding, k);
|
|
28385
|
+
try {
|
|
28386
|
+
return this.knn(
|
|
28387
|
+
embedding,
|
|
28388
|
+
k,
|
|
28389
|
+
`AND chunk_id IN (SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id WHERE 1=1${where})`,
|
|
28390
|
+
params
|
|
28391
|
+
);
|
|
28392
|
+
} catch {
|
|
28393
|
+
return this.vecSearchWidening(embedding, k, where, params);
|
|
28394
|
+
}
|
|
28395
|
+
}
|
|
28396
|
+
knn(embedding, k, extra = "", params = []) {
|
|
28397
|
+
return this.db.prepare(
|
|
28398
|
+
`SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ${extra} ORDER BY distance`
|
|
28399
|
+
).all(embedding, BigInt(k), ...params).map((r) => ({ chunkId: r.id, distance: r.distance }));
|
|
28400
|
+
}
|
|
28401
|
+
/** Fallback filtered KNN: fetch k*4, k*16, ... nearest until k pass the filter or all were seen. */
|
|
28402
|
+
vecSearchWidening(embedding, k, where, params) {
|
|
28403
|
+
const total = this.vecCount();
|
|
28404
|
+
for (let fetch = k * 4; ; fetch *= 4) {
|
|
28405
|
+
const raw = this.knn(embedding, Math.min(fetch, total));
|
|
28406
|
+
if (!raw.length) return [];
|
|
28407
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
28408
|
+
for (let i = 0; i < raw.length; i += 400) {
|
|
28409
|
+
const ids = raw.slice(i, i + 400).map((r) => r.chunkId);
|
|
28410
|
+
for (const r of this.db.prepare(
|
|
28411
|
+
`SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
|
|
28412
|
+
WHERE c.id IN (${ids.map(() => "?").join(",")})${where}`
|
|
28413
|
+
).all(...ids, ...params))
|
|
28414
|
+
allowed.add(r.id);
|
|
28415
|
+
}
|
|
28416
|
+
const hits = raw.filter((r) => allowed.has(r.chunkId)).slice(0, k);
|
|
28417
|
+
if (hits.length >= k || fetch >= total) return hits;
|
|
28418
|
+
}
|
|
28331
28419
|
}
|
|
28332
28420
|
unembeddedChunkIds(limit) {
|
|
28333
28421
|
return this.db.prepare("SELECT id FROM chunks WHERE embedded = 0 ORDER BY id LIMIT ?").all(limit).map((r) => r.id);
|
|
@@ -28606,6 +28694,26 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
28606
28694
|
}
|
|
28607
28695
|
return out;
|
|
28608
28696
|
}
|
|
28697
|
+
graphDependentFileCounts(paths2, kinds) {
|
|
28698
|
+
const out = /* @__PURE__ */ new Map();
|
|
28699
|
+
if (!paths2.length || !kinds.length) return out;
|
|
28700
|
+
const kph = kinds.map(() => "?").join(",");
|
|
28701
|
+
for (let i = 0; i < paths2.length; i += 400) {
|
|
28702
|
+
const batch = paths2.slice(i, i + 400);
|
|
28703
|
+
const pph = batch.map(() => "?").join(",");
|
|
28704
|
+
for (const r of this.db.prepare(
|
|
28705
|
+
`SELECT d.path p, COUNT(DISTINCT s.path) n
|
|
28706
|
+
FROM graph_nodes d
|
|
28707
|
+
JOIN graph_edges e ON e.dst = d.id AND e.kind IN (${kph})
|
|
28708
|
+
JOIN graph_nodes s ON s.id = e.src
|
|
28709
|
+
WHERE d.path IN (${pph}) AND s.path IS NOT NULL AND s.path <> d.path
|
|
28710
|
+
GROUP BY d.path`
|
|
28711
|
+
).all(...kinds, ...batch)) {
|
|
28712
|
+
out.set(r.p, r.n);
|
|
28713
|
+
}
|
|
28714
|
+
}
|
|
28715
|
+
return out;
|
|
28716
|
+
}
|
|
28609
28717
|
edgesWhere(col, ids, kinds) {
|
|
28610
28718
|
if (!ids.length) return [];
|
|
28611
28719
|
const out = [];
|
|
@@ -30755,6 +30863,18 @@ function ingestSession(db, root, sessionId, transcriptPath, opts = {}) {
|
|
|
30755
30863
|
return total;
|
|
30756
30864
|
}
|
|
30757
30865
|
|
|
30866
|
+
// src/hooks/log.ts
|
|
30867
|
+
init_esm_shims();
|
|
30868
|
+
import { appendFileSync } from "fs";
|
|
30869
|
+
function hookLog(level, msg) {
|
|
30870
|
+
try {
|
|
30871
|
+
const p = ensureDirs();
|
|
30872
|
+
appendFileSync(p.hookLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${msg}
|
|
30873
|
+
`);
|
|
30874
|
+
} catch {
|
|
30875
|
+
}
|
|
30876
|
+
}
|
|
30877
|
+
|
|
30758
30878
|
// src/hooks/capture.ts
|
|
30759
30879
|
var INDEXED = /* @__PURE__ */ new Set([
|
|
30760
30880
|
"decision",
|
|
@@ -30797,6 +30917,7 @@ function open3(input2) {
|
|
|
30797
30917
|
});
|
|
30798
30918
|
return { db, root: ctx.root, sessionId };
|
|
30799
30919
|
}
|
|
30920
|
+
var usageFailureLogged = false;
|
|
30800
30921
|
function ingestUsage(db, input2, sessionId) {
|
|
30801
30922
|
try {
|
|
30802
30923
|
const ctx = getContext(input2.cwd);
|
|
@@ -30805,7 +30926,13 @@ function ingestUsage(db, input2, sessionId) {
|
|
|
30805
30926
|
ingestSession(db, ctx.root, sessionId, input2.transcript_path, { overrides });
|
|
30806
30927
|
if (input2.agent_transcript_path)
|
|
30807
30928
|
ingestTranscript(db, input2.agent_transcript_path, { sessionId, overrides });
|
|
30808
|
-
} catch {
|
|
30929
|
+
} catch (err) {
|
|
30930
|
+
if (usageFailureLogged) return;
|
|
30931
|
+
usageFailureLogged = true;
|
|
30932
|
+
hookLog(
|
|
30933
|
+
"error",
|
|
30934
|
+
`usage ingest failed (session ${sessionId}): ${err.message}; run \`eminiar usage ingest\` to see the error`
|
|
30935
|
+
);
|
|
30809
30936
|
}
|
|
30810
30937
|
}
|
|
30811
30938
|
var userPromptSubmit = async (input2) => {
|
|
@@ -31072,6 +31199,7 @@ import { extname as extname2, join as join12, relative as relative4, resolve as
|
|
|
31072
31199
|
init_esm_shims();
|
|
31073
31200
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
31074
31201
|
import { dirname as dirname3, join as join5 } from "path";
|
|
31202
|
+
var DOCS_PREFIX = "docs:";
|
|
31075
31203
|
var NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
31076
31204
|
function isValidName(name) {
|
|
31077
31205
|
return NAME_RE.test(name);
|
|
@@ -31451,18 +31579,6 @@ function effectiveRoutingMode(config2) {
|
|
|
31451
31579
|
return contextModeInstalled() ? "annotate" : "enforce";
|
|
31452
31580
|
}
|
|
31453
31581
|
|
|
31454
|
-
// src/hooks/log.ts
|
|
31455
|
-
init_esm_shims();
|
|
31456
|
-
import { appendFileSync } from "fs";
|
|
31457
|
-
function hookLog(level, msg) {
|
|
31458
|
-
try {
|
|
31459
|
-
const p = ensureDirs();
|
|
31460
|
-
appendFileSync(p.hookLog, `${(/* @__PURE__ */ new Date()).toISOString()} ${level} ${msg}
|
|
31461
|
-
`);
|
|
31462
|
-
} catch {
|
|
31463
|
-
}
|
|
31464
|
-
}
|
|
31465
|
-
|
|
31466
31582
|
// src/hooks/routing.ts
|
|
31467
31583
|
var SYMBOL_KINDS2 = [
|
|
31468
31584
|
"function",
|
|
@@ -31573,6 +31689,7 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
31573
31689
|
".sql",
|
|
31574
31690
|
".har"
|
|
31575
31691
|
]);
|
|
31692
|
+
var VISUAL_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".pdf"]);
|
|
31576
31693
|
function withoutHeredocs(cmd) {
|
|
31577
31694
|
return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
|
|
31578
31695
|
}
|
|
@@ -31774,10 +31891,11 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
31774
31891
|
case "Read": {
|
|
31775
31892
|
const fp = String(input2.file_path ?? "");
|
|
31776
31893
|
if (!fp) return void 0;
|
|
31894
|
+
const ext = extname2(fp).toLowerCase();
|
|
31895
|
+
if (VISUAL_EXT.has(ext)) return void 0;
|
|
31777
31896
|
try {
|
|
31778
31897
|
const abs = resolve4(root, fp);
|
|
31779
31898
|
const st = statSync5(abs);
|
|
31780
|
-
const ext = extname2(fp).toLowerCase();
|
|
31781
31899
|
if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
|
|
31782
31900
|
if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
|
|
31783
31901
|
return {
|
|
@@ -32193,10 +32311,12 @@ ${guide}`);
|
|
|
32193
32311
|
parts.push(
|
|
32194
32312
|
"No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
|
|
32195
32313
|
);
|
|
32196
|
-
else if (!readManifest(ctx).sources.length)
|
|
32314
|
+
else if (!readManifest(ctx).sources.length) {
|
|
32315
|
+
const orphaned = db.documentSourceStats(DOCS_PREFIX).length;
|
|
32197
32316
|
parts.push(
|
|
32198
|
-
`No documentation sources registered yet: em_docs {action:"discover"} maps this project's libraries, services and APIs to their documentation, and {action:"add", pick:"recommended"} indexes it for em_docs lookup.`
|
|
32317
|
+
orphaned ? `No documentation sources registered (${orphaned} orphaned docs:* source(s) still in the index; em_docs {action:"list"} shows them, {action:"remove"} drops them, {action:"add", pick:"recommended"} re-registers).` : `No documentation sources registered yet: em_docs {action:"discover"} maps this project's libraries, services and APIs to their documentation, and {action:"add", pick:"recommended"} indexes it for em_docs lookup.`
|
|
32199
32318
|
);
|
|
32319
|
+
}
|
|
32200
32320
|
} catch {
|
|
32201
32321
|
}
|
|
32202
32322
|
try {
|