unity-agentic-tools 0.2.0 → 0.3.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.
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -2271,18 +2285,216 @@ function chunkProse(content, filePath) {
2271
2285
  }
2272
2286
  return chunks;
2273
2287
  }
2274
- function stripHtml(html) {
2288
+ function removeBalancedBlock(html, openPattern, tagName) {
2289
+ let result = html;
2290
+ let match;
2291
+ while ((match = openPattern.exec(result)) !== null) {
2292
+ const startIdx = match.index;
2293
+ let depth = 1;
2294
+ let i = startIdx + match[0].length;
2295
+ const openTag = new RegExp(`<${tagName}[\\s>]`, "gi");
2296
+ const closeTag = `</${tagName}>`;
2297
+ while (depth > 0 && i < result.length) {
2298
+ const nextClose = result.indexOf(closeTag, i);
2299
+ if (nextClose === -1)
2300
+ break;
2301
+ openTag.lastIndex = i;
2302
+ let openMatch;
2303
+ while ((openMatch = openTag.exec(result)) !== null && openMatch.index < nextClose) {
2304
+ depth++;
2305
+ }
2306
+ depth--;
2307
+ i = nextClose + closeTag.length;
2308
+ }
2309
+ result = result.substring(0, startIdx) + result.substring(i);
2310
+ openPattern.lastIndex = 0;
2311
+ }
2312
+ return result;
2313
+ }
2314
+ function decodeEntities(text) {
2315
+ return text.replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
2316
+ }
2317
+ function htmlToMarkdown(html) {
2318
+ let text = html;
2319
+ text = text.replace(/<script[\s\S]*?<\/script>/gi, "");
2320
+ text = text.replace(/<style[\s\S]*?<\/style>/gi, "");
2321
+ text = text.replace(/<form[\s\S]*?<\/form>/gi, "");
2322
+ const noisePatterns = [
2323
+ [/<div[^>]*class="[^"]*header-wrapper[^"]*"[^>]*>/gi, "div"],
2324
+ [/<div[^>]*id="sidebar"[^>]*>/gi, "div"],
2325
+ [/<div[^>]*class="[^"]*footer-wrapper[^"]*"[^>]*>/gi, "div"],
2326
+ [/<div[^>]*class="[^"]*suggest-wrap[^"]*"[^>]*>/gi, "div"],
2327
+ [/<div[^>]*class="[^"]*suggest-form[^"]*"[^>]*>/gi, "div"],
2328
+ [/<div[^>]*class="[^"]*suggest"[^>]*>/gi, "div"],
2329
+ [/<div[^>]*class="[^"]*scrollToFeedback[^"]*"[^>]*>/gi, "div"],
2330
+ [/<div[^>]*class="[^"]*nextprev[^"]*"[^>]*>/gi, "div"],
2331
+ [/<div[^>]*class="[^"]*toolbar[^"]*"[^>]*>/gi, "div"],
2332
+ [/<nav[^>]*>/gi, "nav"]
2333
+ ];
2334
+ for (const [pattern, tag] of noisePatterns) {
2335
+ text = removeBalancedBlock(text, pattern, tag);
2336
+ }
2337
+ const contentWrapMatch = text.match(/<div[^>]*id="content-wrap"[^>]*>([\s\S]*)/i);
2338
+ if (contentWrapMatch) {
2339
+ const afterOpen = contentWrapMatch.index + contentWrapMatch[0].indexOf(">") + 1;
2340
+ let depth = 1;
2341
+ let i = afterOpen;
2342
+ while (depth > 0 && i < text.length) {
2343
+ const nextOpen = text.indexOf("<div", i);
2344
+ const nextClose = text.indexOf("</div>", i);
2345
+ if (nextClose === -1)
2346
+ break;
2347
+ if (nextOpen !== -1 && nextOpen < nextClose) {
2348
+ depth++;
2349
+ i = nextOpen + 4;
2350
+ } else {
2351
+ depth--;
2352
+ if (depth === 0) {
2353
+ text = text.substring(afterOpen, nextClose);
2354
+ } else {
2355
+ i = nextClose + 6;
2356
+ }
2357
+ }
2358
+ }
2359
+ } else {
2360
+ const bodyMatch = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
2361
+ if (bodyMatch) {
2362
+ text = bodyMatch[1];
2363
+ }
2364
+ }
2365
+ text = text.replace(/<a[^>]*class=['"][^'"]*switch-link[^'"]*['"][^>]*>[\s\S]*?<\/a>/gi, "");
2366
+ text = text.replace(/<span[^>]*class="[^"]*tooltiptext[^"]*"[^>]*>[\s\S]*?<\/span>/gi, "");
2367
+ text = text.replace(/<span[^>]*class="[^"]*search-words[^"]*"[^>]*>[\s\S]*?<\/span>/gi, "");
2368
+ text = text.replace(/<div[^>]*class="[^"]*breadcrumbs[^"]*"[^>]*>([\s\S]*?)<\/div>/gi, (_match, inner) => {
2369
+ const crumbs = inner.replace(/<a[^>]*>([\s\S]*?)<\/a>/gi, "$1").replace(/<[^>]+>/g, "").split(/\s*[>\/]\s*/).map((c) => c.trim()).filter(Boolean);
2370
+ return crumbs.length > 0 ? `
2371
+ > ${crumbs.join(" > ")}
2372
+
2373
+ ` : "";
2374
+ });
2375
+ text = text.replace(/<pre[^>]*>\s*<code[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, (_m, code) => {
2376
+ const decoded = decodeEntities(code.replace(/<[^>]+>/g, "").trim());
2377
+ return `
2378
+ \`\`\`csharp
2379
+ ${decoded}
2380
+ \`\`\`
2381
+ `;
2382
+ });
2383
+ text = text.replace(/<pre[^>]*class="[^"]*codeExample[^"]*"[^>]*>([\s\S]*?)<\/pre>/gi, (_m, code) => {
2384
+ const decoded = decodeEntities(code.replace(/<[^>]+>/g, "").trim());
2385
+ return `
2386
+ \`\`\`csharp
2387
+ ${decoded}
2388
+ \`\`\`
2389
+ `;
2390
+ });
2391
+ text = text.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gi, (_m, code) => {
2392
+ const decoded = decodeEntities(code.replace(/<[^>]+>/g, "").trim());
2393
+ return `
2394
+ \`\`\`
2395
+ ${decoded}
2396
+ \`\`\`
2397
+ `;
2398
+ });
2399
+ text = text.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, (_m, t) => `
2400
+ # ${t.replace(/<[^>]+>/g, "").trim()}
2401
+
2402
+ `);
2403
+ text = text.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, (_m, t) => `
2404
+ ## ${t.replace(/<[^>]+>/g, "").trim()}
2405
+
2406
+ `);
2407
+ text = text.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, (_m, t) => `
2408
+ ### ${t.replace(/<[^>]+>/g, "").trim()}
2409
+
2410
+ `);
2411
+ text = text.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, (_m, t) => `
2412
+ #### ${t.replace(/<[^>]+>/g, "").trim()}
2413
+
2414
+ `);
2415
+ text = text.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_m, tableHtml) => {
2416
+ const rows = [];
2417
+ let hasHeader = false;
2418
+ const theadMatch = tableHtml.match(/<thead[^>]*>([\s\S]*?)<\/thead>/i);
2419
+ const headerRowMatch = theadMatch ? theadMatch[1].match(/<tr[^>]*>([\s\S]*?)<\/tr>/i) : tableHtml.match(/<tr[^>]*>([\s\S]*?)<\/tr>/i);
2420
+ if (headerRowMatch) {
2421
+ const thCells = Array.from(headerRowMatch[1].matchAll(/<th[^>]*>([\s\S]*?)<\/th>/gi));
2422
+ if (thCells.length > 0) {
2423
+ hasHeader = true;
2424
+ const headerRow = thCells.map((c) => convertInlineHtml(c[1]).trim()).join(" | ");
2425
+ rows.push(`| ${headerRow} |`);
2426
+ rows.push(`|${thCells.map(() => "---").join("|")}|`);
2427
+ }
2428
+ }
2429
+ const tbodyMatch = tableHtml.match(/<tbody[^>]*>([\s\S]*?)<\/tbody>/i);
2430
+ const bodyHtml = tbodyMatch ? tbodyMatch[1] : tableHtml;
2431
+ const trMatches = Array.from(bodyHtml.matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/gi));
2432
+ for (const tr of trMatches) {
2433
+ const tdCells = Array.from(tr[1].matchAll(/<td[^>]*>([\s\S]*?)<\/td>/gi));
2434
+ if (tdCells.length === 0)
2435
+ continue;
2436
+ if (!hasHeader && rows.length === 0) {
2437
+ const thCheck = Array.from(tr[1].matchAll(/<th[^>]*>([\s\S]*?)<\/th>/gi));
2438
+ if (thCheck.length > 0)
2439
+ continue;
2440
+ }
2441
+ const cellValues = tdCells.map((c) => convertInlineHtml(c[1]).trim());
2442
+ rows.push(`| ${cellValues.join(" | ")} |`);
2443
+ if (!hasHeader && rows.length === 1) {
2444
+ rows.unshift(`|${tdCells.map(() => "---").join("|")}|`);
2445
+ hasHeader = true;
2446
+ }
2447
+ }
2448
+ return rows.length > 0 ? `
2449
+ ${rows.join(`
2450
+ `)}
2451
+ ` : "";
2452
+ });
2453
+ text = text.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_m, href, linkText) => {
2454
+ const clean = linkText.replace(/<[^>]+>/g, "").trim();
2455
+ if (!clean)
2456
+ return "";
2457
+ return `[${clean}](${href})`;
2458
+ });
2459
+ text = text.replace(/<(?:strong|b)>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
2460
+ text = text.replace(/<(?:em|i)>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
2461
+ text = text.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`");
2462
+ text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m, content) => {
2463
+ const clean = content.replace(/<[^>]+>/g, "").trim();
2464
+ return `
2465
+ - ${clean}`;
2466
+ });
2467
+ text = text.replace(/<\/?(?:ul|ol)[^>]*>/gi, `
2468
+ `);
2469
+ text = text.replace(/<br\s*\/?>/gi, `
2470
+ `);
2471
+ text = text.replace(/<\/p>/gi, `
2472
+
2473
+ `);
2474
+ text = text.replace(/<p[^>]*>/gi, "");
2475
+ text = text.replace(/<hr[^>]*>/gi, `
2476
+ ---
2477
+ `);
2478
+ text = text.replace(/<[^>]+>/g, "");
2479
+ text = decodeEntities(text);
2480
+ text = text.replace(/[ \t]+$/gm, "");
2481
+ text = text.replace(/\n{3,}/g, `
2482
+
2483
+ `);
2484
+ text = text.trim();
2485
+ return text;
2486
+ }
2487
+ function convertInlineHtml(html) {
2275
2488
  let text = html;
2276
- text = text.replace(/<script[\s\S]*?<\/script>/gi, " ");
2277
- text = text.replace(/<style[\s\S]*?<\/style>/gi, " ");
2278
- text = text.replace(/<[^>]+>/g, " ");
2279
- text = text.replace(/&nbsp;/g, " ");
2280
- text = text.replace(/&amp;/g, "&");
2281
- text = text.replace(/&lt;/g, "<");
2282
- text = text.replace(/&gt;/g, ">");
2283
- text = text.replace(/&quot;/g, '"');
2284
- text = text.replace(/&#39;/g, "'");
2285
- text = text.replace(/\s+/g, " ").trim();
2489
+ text = text.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, (_m, href, linkText) => {
2490
+ const clean = linkText.replace(/<[^>]+>/g, "").trim();
2491
+ return clean ? `[${clean}](${href})` : "";
2492
+ });
2493
+ text = text.replace(/<(?:strong|b)>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
2494
+ text = text.replace(/<(?:em|i)>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
2495
+ text = text.replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`");
2496
+ text = text.replace(/<[^>]+>/g, "");
2497
+ text = decodeEntities(text);
2286
2498
  return text;
2287
2499
  }
2288
2500
  function walkDirectory(dir) {
@@ -2347,7 +2559,7 @@ function indexMarkdownFile(filePath, storage) {
2347
2559
  function indexHtmlFile(filePath, storage) {
2348
2560
  const startTime = Date.now();
2349
2561
  const html = import_fs.readFileSync(filePath, "utf-8");
2350
- const text = stripHtml(html);
2562
+ const text = htmlToMarkdown(html);
2351
2563
  const chunks = chunkProse(text, filePath);
2352
2564
  let embeddingsGenerated = 0;
2353
2565
  if (storage) {
@@ -2375,7 +2587,7 @@ async function indexDocsDirectory(dirPath, extensions = [".md", ".txt", ".html"]
2375
2587
  continue;
2376
2588
  const content = import_fs.readFileSync(fullPath, "utf-8");
2377
2589
  if (ext === ".html") {
2378
- const text = stripHtml(content);
2590
+ const text = htmlToMarkdown(content);
2379
2591
  allChunks.push(...chunkProse(text, fullPath));
2380
2592
  } else {
2381
2593
  allChunks.push(...extractCodeBlocks(content));
@@ -2483,7 +2695,7 @@ async function indexSource(source, storage) {
2483
2695
  const ext = file.fullPath.substring(file.fullPath.lastIndexOf("."));
2484
2696
  const fileChunks = [];
2485
2697
  if (ext === ".html") {
2486
- const text = stripHtml(content);
2698
+ const text = htmlToMarkdown(content);
2487
2699
  fileChunks.push(...chunkProse(text, file.fullPath));
2488
2700
  } else {
2489
2701
  fileChunks.push(...extractCodeBlocks(content, file.fullPath));
@@ -2617,28 +2829,37 @@ class DocStorage {
2617
2829
  await this.init();
2618
2830
  const lowerQuery = query.toLowerCase();
2619
2831
  const queryTerms = lowerQuery.split(/\s+/).filter((t) => t.length > 0);
2832
+ if (queryTerms.length === 0)
2833
+ return [];
2834
+ const minTermRatio = 0.5;
2620
2835
  const results = [];
2621
2836
  for (const [id, chunk] of this.chunks) {
2622
2837
  const lowerContent = chunk.content.toLowerCase();
2623
- if (lowerContent.includes(lowerQuery)) {
2624
- let termHits = 0;
2625
- for (const term of queryTerms) {
2626
- let idx = 0;
2627
- while ((idx = lowerContent.indexOf(term, idx)) !== -1) {
2628
- termHits++;
2629
- idx += term.length;
2630
- }
2631
- }
2632
- const wordCount = lowerContent.split(/\s+/).length;
2633
- const score = termHits / Math.max(wordCount, 1);
2634
- if (score > 0) {
2635
- results.push({
2636
- id,
2637
- content: chunk.content,
2638
- score,
2639
- metadata: chunk.metadata
2640
- });
2641
- }
2838
+ let matchedTerms = 0;
2839
+ let termHits = 0;
2840
+ for (const term of queryTerms) {
2841
+ let found = false;
2842
+ let idx = 0;
2843
+ while ((idx = lowerContent.indexOf(term, idx)) !== -1) {
2844
+ termHits++;
2845
+ found = true;
2846
+ idx += term.length;
2847
+ }
2848
+ if (found)
2849
+ matchedTerms++;
2850
+ }
2851
+ const termRatio = matchedTerms / queryTerms.length;
2852
+ if (termRatio < minTermRatio)
2853
+ continue;
2854
+ const wordCount = lowerContent.split(/\s+/).length;
2855
+ const score = termHits / Math.max(wordCount, 1);
2856
+ if (score > 0) {
2857
+ results.push({
2858
+ id,
2859
+ content: chunk.content,
2860
+ score,
2861
+ metadata: chunk.metadata
2862
+ });
2642
2863
  }
2643
2864
  }
2644
2865
  results.sort((a, b) => b.score - a.score);
@@ -2868,6 +3089,20 @@ function read_unity_version(projectRoot) {
2868
3089
  // src/cli.ts
2869
3090
  var program2 = new Command;
2870
3091
  program2.name("unity-doc-indexer").description("Fast Unity documentation indexer with local embeddings").version("1.0.0").option("--project-root <path>", "Unity project root (auto-detected if omitted)").option("--storage-path <path>", "Index storage file path (auto-resolved if omitted)");
3092
+ function extract_doc_title(filePath) {
3093
+ if (!filePath)
3094
+ return "Unknown";
3095
+ const filename = filePath.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "Unknown";
3096
+ return filename.replace(/^class-/, "").replace(/-/g, ".");
3097
+ }
3098
+ function extract_relative_source(filePath) {
3099
+ if (!filePath)
3100
+ return;
3101
+ const match = filePath.match(/((?:ScriptReference|Manual|Documentation)\/[^/]*\.html?)$/i);
3102
+ if (match)
3103
+ return match[1];
3104
+ return filePath.split("/").pop();
3105
+ }
2871
3106
  function get_storage_path(opts) {
2872
3107
  if (opts.storagePath)
2873
3108
  return opts.storagePath;
@@ -2892,7 +3127,7 @@ async function auto_index(storage, projectRoot) {
2892
3127
  }
2893
3128
  return reindexed;
2894
3129
  }
2895
- program2.command("search <query>").description("Search documentation (auto-discovers and indexes on first use)").option("-s, --summarize", "Summarize results (truncate content)").option("-c, --compress", "Compress results (minimal output)").option("-j, --json", "Output as JSON").action(async (query, options) => {
3130
+ program2.command("search <query>").description("Search documentation (auto-discovers and indexes on first use)").option("-j, --json", "Output as JSON").action(async (query, options) => {
2896
3131
  const globalOpts = program2.opts();
2897
3132
  const storagePath = get_storage_path(globalOpts);
2898
3133
  const projectRoot = globalOpts.projectRoot || find_project_root() || null;
@@ -2915,27 +3150,26 @@ program2.command("search <query>").description("Search documentation (auto-disco
2915
3150
  keyword_weight: 0.4
2916
3151
  });
2917
3152
  if (options.json) {
2918
- const output = options.summarize ? { ...results, results: results.results.map((r) => ({ ...r, content: r.content.slice(0, 200) })) } : options.compress ? { ...results, results: results.results.map(({ content, ...r }) => r) } : results;
2919
- console.log(JSON.stringify(output, null, 2));
3153
+ console.log(JSON.stringify(results, null, 2));
2920
3154
  return;
2921
3155
  }
2922
- if (options.compress) {
2923
- for (const result of results.results) {
2924
- const title = result.metadata?.section || result.metadata?.unity_class || result.metadata?.file_path;
2925
- console.log(`${title} (${result.score.toFixed(4)})`);
3156
+ const count = results.results.length;
3157
+ console.log(`# Unity Docs: "${query}" (${count} result${count !== 1 ? "s" : ""})
3158
+ `);
3159
+ for (const result of results.results) {
3160
+ const meta = result.metadata;
3161
+ const title = meta?.section || meta?.unity_class || extract_doc_title(meta?.file_path);
3162
+ const source = extract_relative_source(meta?.file_path);
3163
+ console.log(`## ${title}
3164
+ `);
3165
+ console.log(result.content);
3166
+ if (source) {
3167
+ console.log(`
3168
+ *Source: ${source}*`);
2926
3169
  }
2927
- return;
2928
- }
2929
- console.log(`Found ${results.results.length} results in ${results.elapsed_ms}ms`);
2930
- console.log(`Semantic: ${results.semantic_count}, Keyword: ${results.keyword_count}`);
2931
- for (let i = 0;i < results.results.length; i++) {
2932
- const result = results.results[i];
2933
- const title = result.metadata?.section || result.metadata?.unity_class || result.metadata?.file_path;
2934
- const content = options.summarize ? result.content.slice(0, 200) + "..." : result.content;
2935
3170
  console.log(`
2936
- [${i + 1}] ${title}`);
2937
- console.log(content);
2938
- console.log(`Score: ${result.score.toFixed(4)}`);
3171
+ ---
3172
+ `);
2939
3173
  }
2940
3174
  });
2941
3175
  program2.command("index [path]").description("Index documentation (auto-discovers sources if no path given)").action(async (path) => {