@agentskit/doc-bridge 0.1.0-alpha.2 → 0.1.0-alpha.3
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 +13 -0
- package/README.md +1 -1
- package/dist/cli/program.js +137 -47
- package/dist/cli/program.js.map +1 -1
- package/dist/index.d.ts +21 -3
- package/dist/index.js +124 -36
- package/dist/index.js.map +1 -1
- package/docs/DOGFOOD-ROUND2.md +142 -0
- package/package.json +8 -8
- package/src/cli/program.ts +28 -14
- package/src/federation/llms.ts +20 -11
- package/src/index-builder/build-handoffs.ts +16 -1
- package/src/index-builder/scan-corpus.ts +5 -1
- package/src/lib/markdown.ts +31 -4
- package/src/query/search.ts +85 -17
- package/src/schemas/doc-bridge-index.ts +3 -1
- package/src/schemas/json-schemas.ts +2 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.3
|
|
4
|
+
|
|
5
|
+
Dogfood round-2 fixes (search ranking, full-text body, peers, federation soft-fail).
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
- **Search ranking:** exact id / basename boost; ownership preferred for routing questions; path dedupe
|
|
9
|
+
- **Full-text search:** knowledge entries store `body` excerpt; descriptions prefer frontmatter `purpose` and complete sentences
|
|
10
|
+
- **ask:** next command prefers ownership match over knowledge-only
|
|
11
|
+
- **Text UX:** multi-line search/ask matches (`[type] id`, path, summary)
|
|
12
|
+
- **Federation:** missing/404 remote `llms.txt` soft-skipped (no hard fail)
|
|
13
|
+
- **Peers:** optional peer ranges widened (`@agentskit/core` `>=1.0`, adapters `>=0.12`) so Layer 0 install is not blocked
|
|
14
|
+
- **humanDoc:** more aliases (`packages/id`, `reference/packages/id`, path suffixes)
|
|
15
|
+
|
|
3
16
|
## 0.1.0-alpha.2
|
|
4
17
|
|
|
5
18
|
Dogfood-driven polish after ecosystem install on agentskit, agentskit-os, playbook, and registry.
|
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ Contract: [`docs/spec/config-v1.md`](docs/spec/config-v1.md) · CLI: [`docs/spec
|
|
|
121
121
|
|
|
122
122
|
## Status
|
|
123
123
|
|
|
124
|
-
**v0.1.0-alpha.
|
|
124
|
+
**v0.1.0-alpha.3** — Dogfood polish (pnpm-aware checks, corpus ownership inference, playbook gate preset, git prepare build). Layer 1 RAG/chat via optional peers.
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
127
|
pnpm install && pnpm build && pnpm test
|
package/dist/cli/program.js
CHANGED
|
@@ -469,7 +469,7 @@ var firstHeading = (markdown) => {
|
|
|
469
469
|
}
|
|
470
470
|
return void 0;
|
|
471
471
|
};
|
|
472
|
-
var firstParagraph = (markdown) => {
|
|
472
|
+
var firstParagraph = (markdown, maxLen = 400) => {
|
|
473
473
|
const { body } = parseFrontmatter(markdown);
|
|
474
474
|
const lines = body.split("\n");
|
|
475
475
|
const buf = [];
|
|
@@ -481,11 +481,27 @@ var firstParagraph = (markdown) => {
|
|
|
481
481
|
}
|
|
482
482
|
if (t.startsWith("#")) continue;
|
|
483
483
|
if (t.startsWith("---")) continue;
|
|
484
|
+
if (t.startsWith("```")) break;
|
|
485
|
+
if (t.startsWith("|") || t.startsWith("- [") || t.startsWith("* [")) {
|
|
486
|
+
if (buf.length) break;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
484
489
|
buf.push(t);
|
|
485
|
-
if (buf.join(" ").length
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
490
|
+
if (buf.join(" ").length >= maxLen) break;
|
|
491
|
+
}
|
|
492
|
+
let text = buf.join(" ").replace(/\s+/g, " ").trim();
|
|
493
|
+
if (!text) return void 0;
|
|
494
|
+
if (text.length <= maxLen) return text;
|
|
495
|
+
const sliced = text.slice(0, maxLen);
|
|
496
|
+
const sentenceEnd = Math.max(sliced.lastIndexOf(". "), sliced.lastIndexOf("! "), sliced.lastIndexOf("? "));
|
|
497
|
+
if (sentenceEnd > maxLen * 0.4) return sliced.slice(0, sentenceEnd + 1).trim();
|
|
498
|
+
const wordEnd = sliced.lastIndexOf(" ");
|
|
499
|
+
return (wordEnd > 0 ? sliced.slice(0, wordEnd) : sliced).trim();
|
|
500
|
+
};
|
|
501
|
+
var extractSearchBody = (markdown, maxLen = 6e3) => {
|
|
502
|
+
const { body } = parseFrontmatter(markdown);
|
|
503
|
+
const text = body.replace(/```[\s\S]*?```/g, " ").replace(/!\[[^\]]*\]\([^)]+\)/g, " ").replace(/\[[^\]]*\]\([^)]+\)/g, " ").replace(/^#+\s+/gm, "").replace(/[|>*_`#]/g, " ").replace(/\s+/g, " ").trim();
|
|
504
|
+
return text.length > maxLen ? text.slice(0, maxLen) : text;
|
|
489
505
|
};
|
|
490
506
|
var slugFromPath = (relPath) => {
|
|
491
507
|
const base = relPath.replace(/\.mdx?$/, "");
|
|
@@ -543,7 +559,9 @@ var scanAgentCorpus = (root, config) => {
|
|
|
543
559
|
const { data: frontmatter } = parseFrontmatter(raw);
|
|
544
560
|
const id = frontmatterString(frontmatter, "id") ?? frontmatterString(frontmatter, "package") ?? slugFromPath(relToCorpus);
|
|
545
561
|
const title = firstHeading(raw) ?? id;
|
|
546
|
-
const
|
|
562
|
+
const purpose = frontmatterString(frontmatter, "purpose");
|
|
563
|
+
const description = purpose ?? firstParagraph(raw, 400);
|
|
564
|
+
const body = extractSearchBody(raw);
|
|
547
565
|
return {
|
|
548
566
|
id,
|
|
549
567
|
type: "agent-doc",
|
|
@@ -552,7 +570,8 @@ var scanAgentCorpus = (root, config) => {
|
|
|
552
570
|
absPath: abs,
|
|
553
571
|
relPath,
|
|
554
572
|
frontmatter,
|
|
555
|
-
...description ? { description } : {}
|
|
573
|
+
...description ? { description } : {},
|
|
574
|
+
...body ? { body } : {}
|
|
556
575
|
};
|
|
557
576
|
});
|
|
558
577
|
};
|
|
@@ -685,11 +704,19 @@ var resolveHumanDoc = (packageId, override, fmHuman, humanDocs = {}) => {
|
|
|
685
704
|
packageId,
|
|
686
705
|
packageId.replace(/^@[^/]+\//, ""),
|
|
687
706
|
packageId.replace(/^os-/, ""),
|
|
688
|
-
packageId.replace(/-pattern$/, "")
|
|
707
|
+
packageId.replace(/-pattern$/, ""),
|
|
708
|
+
`packages/${packageId}`,
|
|
709
|
+
`reference/packages/${packageId}`,
|
|
710
|
+
`packages/${packageId}/index`
|
|
689
711
|
];
|
|
690
712
|
for (const alias of aliases) {
|
|
691
713
|
if (humanDocs[alias]) return humanDocs[alias];
|
|
692
714
|
}
|
|
715
|
+
for (const [key, url] of Object.entries(humanDocs)) {
|
|
716
|
+
if (key === packageId || key.endsWith(`/${packageId}`) || key.endsWith(`/${packageId}/index`) || key.endsWith(`-${packageId}`)) {
|
|
717
|
+
return url;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
693
720
|
return void 0;
|
|
694
721
|
};
|
|
695
722
|
var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root = process.cwd()) => {
|
|
@@ -775,7 +802,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
775
802
|
};
|
|
776
803
|
|
|
777
804
|
// src/version.ts
|
|
778
|
-
var PACKAGE_VERSION = "0.1.0-alpha.
|
|
805
|
+
var PACKAGE_VERSION = "0.1.0-alpha.3";
|
|
779
806
|
|
|
780
807
|
// src/index-builder/capabilities.ts
|
|
781
808
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1252,19 +1279,58 @@ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
|
1252
1279
|
import { resolve as resolve3 } from "path";
|
|
1253
1280
|
|
|
1254
1281
|
// src/query/search.ts
|
|
1255
|
-
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2);
|
|
1282
|
+
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
1283
|
+
var PACKAGE_INTENT = /\b(package|module|pkg|edit|change|where|owns?|ownership|handoff|start)\b/i;
|
|
1284
|
+
var scoreHay = (tokens, hay, weight = 1) => {
|
|
1285
|
+
let score = 0;
|
|
1286
|
+
for (const token of tokens) {
|
|
1287
|
+
if (!hay.includes(token)) continue;
|
|
1288
|
+
score += token.length * weight;
|
|
1289
|
+
if (new RegExp(`(?:^|[^a-z0-9])${token}(?:[^a-z0-9]|$)`).test(hay)) {
|
|
1290
|
+
score += token.length;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return score;
|
|
1294
|
+
};
|
|
1295
|
+
var identityBoost = (id, path, tokens, term) => {
|
|
1296
|
+
const idLower = id.toLowerCase();
|
|
1297
|
+
const termLower = term.toLowerCase().trim();
|
|
1298
|
+
const base = path.split("/").pop()?.replace(/\.mdx?$/i, "").toLowerCase() ?? "";
|
|
1299
|
+
let boost = 0;
|
|
1300
|
+
if (idLower === termLower || base === termLower) boost += 200;
|
|
1301
|
+
if (tokens.length === 1 && (idLower === tokens[0] || base === tokens[0])) boost += 200;
|
|
1302
|
+
for (const token of tokens) {
|
|
1303
|
+
if (idLower === token) boost += 120;
|
|
1304
|
+
else if (idLower.startsWith(`${token}-`) || idLower.endsWith(`-${token}`)) boost += 40;
|
|
1305
|
+
else if (idLower.includes(token) && idLower.length <= token.length + 4) boost += 30;
|
|
1306
|
+
if (base === token) boost += 100;
|
|
1307
|
+
}
|
|
1308
|
+
if (tokens.includes(idLower)) boost += Math.max(0, 40 - idLower.length);
|
|
1309
|
+
return boost;
|
|
1310
|
+
};
|
|
1311
|
+
var preferOwnership = (term) => PACKAGE_INTENT.test(term) || /^(where|how).*(edit|change|package|module)/i.test(term);
|
|
1256
1312
|
var searchIndex = (index, term, limit = 20) => {
|
|
1257
1313
|
const tokens = tokenize(term);
|
|
1258
1314
|
if (!tokens.length) return [];
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1315
|
+
const wantOwnership = preferOwnership(term);
|
|
1316
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1317
|
+
const consider = (match) => {
|
|
1318
|
+
const key = match.path;
|
|
1319
|
+
const existing = byPath.get(key);
|
|
1320
|
+
if (!existing) {
|
|
1321
|
+
byPath.set(key, match);
|
|
1322
|
+
return;
|
|
1265
1323
|
}
|
|
1324
|
+
const prefer = match.score > existing.score || match.score === existing.score && match.type === "ownership" && existing.type !== "ownership" || wantOwnership && match.type === "ownership" && existing.type !== "ownership" && match.score >= existing.score - 20;
|
|
1325
|
+
if (prefer) byPath.set(key, match);
|
|
1326
|
+
};
|
|
1327
|
+
for (const entry of index.knowledge) {
|
|
1328
|
+
const body = entry.body ?? "";
|
|
1329
|
+
const hay = `${entry.id} ${entry.title} ${entry.description ?? ""} ${entry.path} ${body}`.toLowerCase();
|
|
1330
|
+
let score = scoreHay(tokens, hay, 1);
|
|
1331
|
+
score += identityBoost(entry.id, entry.path, tokens, term);
|
|
1266
1332
|
if (score > 0) {
|
|
1267
|
-
|
|
1333
|
+
consider({
|
|
1268
1334
|
type: "knowledge",
|
|
1269
1335
|
id: entry.id,
|
|
1270
1336
|
path: entry.path,
|
|
@@ -1274,22 +1340,31 @@ var searchIndex = (index, term, limit = 20) => {
|
|
|
1274
1340
|
}
|
|
1275
1341
|
}
|
|
1276
1342
|
for (const [id, owner] of Object.entries(index.lookup?.ownership ?? {})) {
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1343
|
+
const path = owner.agentDoc ?? owner.path;
|
|
1344
|
+
const hay = `${id} ${owner.path} ${owner.purpose ?? ""} ${owner.group ?? ""} ${owner.agentDoc ?? ""} ${owner.humanDoc ?? ""}`.toLowerCase();
|
|
1345
|
+
let score = scoreHay(tokens, hay, 2);
|
|
1346
|
+
score += identityBoost(id, path, tokens, term);
|
|
1347
|
+
if (wantOwnership) score += 25;
|
|
1348
|
+
score += 15;
|
|
1282
1349
|
if (score > 0) {
|
|
1283
|
-
|
|
1350
|
+
consider({
|
|
1284
1351
|
type: "ownership",
|
|
1285
1352
|
id,
|
|
1286
|
-
path
|
|
1353
|
+
path,
|
|
1287
1354
|
...owner.purpose ? { summary: owner.purpose } : {},
|
|
1288
1355
|
score
|
|
1289
1356
|
});
|
|
1290
1357
|
}
|
|
1291
1358
|
}
|
|
1292
|
-
return
|
|
1359
|
+
return [...byPath.values()].sort((a, b) => {
|
|
1360
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
1361
|
+
const aExact = tokens.includes(a.id.toLowerCase()) ? 1 : 0;
|
|
1362
|
+
const bExact = tokens.includes(b.id.toLowerCase()) ? 1 : 0;
|
|
1363
|
+
if (bExact !== aExact) return bExact - aExact;
|
|
1364
|
+
if (a.type === "ownership" && b.type !== "ownership") return -1;
|
|
1365
|
+
if (b.type === "ownership" && a.type !== "ownership") return 1;
|
|
1366
|
+
return a.id.localeCompare(b.id);
|
|
1367
|
+
}).slice(0, limit);
|
|
1293
1368
|
};
|
|
1294
1369
|
|
|
1295
1370
|
// src/retriever/doc-bridge-retriever.ts
|
|
@@ -1328,10 +1403,14 @@ var defaultFetchText = async (url) => {
|
|
|
1328
1403
|
return res.text();
|
|
1329
1404
|
};
|
|
1330
1405
|
var sourceText = async (root, source, fetchText) => {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1406
|
+
try {
|
|
1407
|
+
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
1408
|
+
const path = resolve3(root, source);
|
|
1409
|
+
if (!existsSync7(path)) return null;
|
|
1410
|
+
return readFileSync9(path, "utf8");
|
|
1411
|
+
} catch {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1335
1414
|
};
|
|
1336
1415
|
var sameOrigin = (base, target) => {
|
|
1337
1416
|
if (!/^https?:\/\//.test(base) || !/^https?:\/\//.test(target)) return true;
|
|
@@ -1370,22 +1449,28 @@ var chunksFromMarkdown = (property, raw, sourceUrl) => {
|
|
|
1370
1449
|
var loadFederatedChunks = async (root, config, options = {}) => {
|
|
1371
1450
|
const fetchText = options.fetchText ?? defaultFetchText;
|
|
1372
1451
|
const chunks = [];
|
|
1452
|
+
const warnings = [];
|
|
1373
1453
|
for (const source of config.federation?.sources ?? []) {
|
|
1374
1454
|
if (source.includeInRetriever === false || !source.llmsTxt) continue;
|
|
1375
1455
|
const llms = await sourceText(root, source.llmsTxt, fetchText);
|
|
1456
|
+
if (!llms) {
|
|
1457
|
+
warnings.push(`federation source skipped (unavailable): ${source.id} \u2192 ${source.llmsTxt}`);
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1376
1460
|
chunks.push(...chunksFromMarkdown(source.id, llms, source.llmsTxt));
|
|
1377
1461
|
const links = parseLlmsTxtLinks(llms);
|
|
1378
1462
|
for (const link of links) {
|
|
1379
1463
|
const url = !/^https?:\/\//.test(link.url) && source.rawBaseUrl ? `${source.rawBaseUrl.replace(/\/$/, "")}/${link.url.replace(/^\//, "")}` : link.url;
|
|
1380
1464
|
if (!/\.(md|txt)(?:$|\?)/.test(url)) continue;
|
|
1381
1465
|
if (!sameOrigin(source.llmsTxt, url)) continue;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
chunks.push(...chunksFromMarkdown(source.id, raw, url));
|
|
1385
|
-
} catch {
|
|
1386
|
-
}
|
|
1466
|
+
const raw = await sourceText(root, url, fetchText);
|
|
1467
|
+
if (raw) chunks.push(...chunksFromMarkdown(source.id, raw, url));
|
|
1387
1468
|
}
|
|
1388
1469
|
}
|
|
1470
|
+
if (warnings.length && process.stderr.isTTY) {
|
|
1471
|
+
for (const w of warnings) process.stderr.write(`${w}
|
|
1472
|
+
`);
|
|
1473
|
+
}
|
|
1389
1474
|
return chunks;
|
|
1390
1475
|
};
|
|
1391
1476
|
var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
@@ -1485,7 +1570,9 @@ var KnowledgeEntrySchema = z3.object({
|
|
|
1485
1570
|
type: z3.string().min(1).max(128),
|
|
1486
1571
|
title: z3.string().min(1).max(256),
|
|
1487
1572
|
path: z3.string().min(1).max(512),
|
|
1488
|
-
description: z3.string().max(
|
|
1573
|
+
description: z3.string().max(2048).optional(),
|
|
1574
|
+
/** Flattened body excerpt for full-text search (not for display). */
|
|
1575
|
+
body: z3.string().max(8e3).optional(),
|
|
1489
1576
|
links: z3.array(z3.string().min(1).max(512)).max(64).optional(),
|
|
1490
1577
|
tags: z3.array(z3.string().min(1).max(64)).max(32).optional()
|
|
1491
1578
|
}).strict();
|
|
@@ -2614,35 +2701,38 @@ var writeTextQuery = (payload) => {
|
|
|
2614
2701
|
writeLines([
|
|
2615
2702
|
`Search: ${String(data.term ?? "")}`,
|
|
2616
2703
|
`Matches: ${String(data.count ?? matches.length)}`,
|
|
2617
|
-
...matches.map(
|
|
2618
|
-
(match) =>
|
|
2619
|
-
)
|
|
2704
|
+
...matches.length ? matches.map(
|
|
2705
|
+
(match) => formatSearchMatch(match)
|
|
2706
|
+
) : [" (none)"]
|
|
2620
2707
|
]);
|
|
2621
2708
|
return;
|
|
2622
2709
|
}
|
|
2623
2710
|
writeLines([textValue(result.data)]);
|
|
2624
2711
|
};
|
|
2712
|
+
var formatSearchMatch = (match) => {
|
|
2713
|
+
const summary = match.summary ? match.summary.replace(/\s+/g, " ").slice(0, 100) : "";
|
|
2714
|
+
const score = typeof match.score === "number" ? ` score=${match.score}` : "";
|
|
2715
|
+
return ` [${match.type}] ${match.id}${score}
|
|
2716
|
+
${match.path}${summary ? `
|
|
2717
|
+
${summary}` : ""}`;
|
|
2718
|
+
};
|
|
2625
2719
|
var writeTextSearch = (term, matches) => {
|
|
2626
2720
|
writeLines([
|
|
2627
2721
|
`Search: ${term}`,
|
|
2628
2722
|
`Matches: ${matches.length}`,
|
|
2629
|
-
...matches.map(
|
|
2630
|
-
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2631
|
-
)
|
|
2723
|
+
...matches.length ? matches.map(formatSearchMatch) : [" (none)"]
|
|
2632
2724
|
]);
|
|
2633
2725
|
};
|
|
2634
2726
|
var writeAsk = (question, matches, index) => {
|
|
2635
|
-
const
|
|
2636
|
-
const
|
|
2637
|
-
const bestQuery =
|
|
2727
|
+
const owner = matches.find((match) => match.type === "ownership") ?? matches.find((match) => Boolean(index.lookup?.ownership?.[match.id]));
|
|
2728
|
+
const best = owner ?? matches[0];
|
|
2729
|
+
const bestQuery = best && (best.type === "ownership" || index.lookup?.ownership?.[best.id]) ? `ak-docs query ownership ${best.id} --agent` : "ak-docs list knowledge --text";
|
|
2638
2730
|
writeLines([
|
|
2639
2731
|
`Question: ${question}`,
|
|
2640
2732
|
best ? `Best match: ${best.type} ${best.id} (${best.path})` : "Best match: none",
|
|
2641
2733
|
"",
|
|
2642
2734
|
"Matches:",
|
|
2643
|
-
...matches.length ? matches.slice(0, 5).map(
|
|
2644
|
-
(match) => [match.type, match.id, match.path, match.summary].filter(Boolean).join(" ")
|
|
2645
|
-
) : ["No local matches. Try: ak-docs search <term>"],
|
|
2735
|
+
...matches.length ? matches.slice(0, 5).map(formatSearchMatch) : [" No local matches. Try: ak-docs search <term>"],
|
|
2646
2736
|
"",
|
|
2647
2737
|
"Next commands:",
|
|
2648
2738
|
...best ? [
|