rbxstudio-mcp 2.3.2 → 2.4.0
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/README.md +67 -14
- package/dist/__tests__/bridge-service.test.js +25 -13
- package/dist/__tests__/bridge-service.test.js.map +1 -1
- package/dist/__tests__/bridge-session.test.d.ts +2 -0
- package/dist/__tests__/bridge-session.test.d.ts.map +1 -0
- package/dist/__tests__/bridge-session.test.js +171 -0
- package/dist/__tests__/bridge-session.test.js.map +1 -0
- package/dist/__tests__/chunker.test.d.ts +2 -0
- package/dist/__tests__/chunker.test.d.ts.map +1 -0
- package/dist/__tests__/chunker.test.js +201 -0
- package/dist/__tests__/chunker.test.js.map +1 -0
- package/dist/__tests__/docs-core.test.d.ts +2 -0
- package/dist/__tests__/docs-core.test.d.ts.map +1 -0
- package/dist/__tests__/docs-core.test.js +137 -0
- package/dist/__tests__/docs-core.test.js.map +1 -0
- package/dist/__tests__/docs-fetcher.test.d.ts +2 -0
- package/dist/__tests__/docs-fetcher.test.d.ts.map +1 -0
- package/dist/__tests__/docs-fetcher.test.js +173 -0
- package/dist/__tests__/docs-fetcher.test.js.map +1 -0
- package/dist/__tests__/helpers.d.ts +8 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +23 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/__tests__/http-routes.test.d.ts +2 -0
- package/dist/__tests__/http-routes.test.d.ts.map +1 -0
- package/dist/__tests__/http-routes.test.js +233 -0
- package/dist/__tests__/http-routes.test.js.map +1 -0
- package/dist/__tests__/http-server.test.js +13 -6
- package/dist/__tests__/http-server.test.js.map +1 -1
- package/dist/__tests__/integration.test.js +9 -4
- package/dist/__tests__/integration.test.js.map +1 -1
- package/dist/__tests__/semantic-search.test.d.ts +2 -0
- package/dist/__tests__/semantic-search.test.d.ts.map +1 -0
- package/dist/__tests__/semantic-search.test.js +202 -0
- package/dist/__tests__/semantic-search.test.js.map +1 -0
- package/dist/__tests__/smoke.test.js +7 -3
- package/dist/__tests__/smoke.test.js.map +1 -1
- package/dist/__tests__/studio-client.test.d.ts +2 -0
- package/dist/__tests__/studio-client.test.d.ts.map +1 -0
- package/dist/__tests__/studio-client.test.js +25 -0
- package/dist/__tests__/studio-client.test.js.map +1 -0
- package/dist/__tests__/tool-nudges.test.d.ts +2 -0
- package/dist/__tests__/tool-nudges.test.d.ts.map +1 -0
- package/dist/__tests__/tool-nudges.test.js +60 -0
- package/dist/__tests__/tool-nudges.test.js.map +1 -0
- package/dist/__tests__/tool-registry.test.d.ts +2 -0
- package/dist/__tests__/tool-registry.test.d.ts.map +1 -0
- package/dist/__tests__/tool-registry.test.js +365 -0
- package/dist/__tests__/tool-registry.test.js.map +1 -0
- package/dist/__tests__/tools-bridge.test.d.ts +2 -0
- package/dist/__tests__/tools-bridge.test.d.ts.map +1 -0
- package/dist/__tests__/tools-bridge.test.js +396 -0
- package/dist/__tests__/tools-bridge.test.js.map +1 -0
- package/dist/__tests__/tools-docs.test.d.ts +2 -0
- package/dist/__tests__/tools-docs.test.d.ts.map +1 -0
- package/dist/__tests__/tools-docs.test.js +112 -0
- package/dist/__tests__/tools-docs.test.js.map +1 -0
- package/dist/__tests__/tools-guards.test.d.ts +2 -0
- package/dist/__tests__/tools-guards.test.d.ts.map +1 -0
- package/dist/__tests__/tools-guards.test.js +131 -0
- package/dist/__tests__/tools-guards.test.js.map +1 -0
- package/dist/__tests__/tools-runtime.test.d.ts +2 -0
- package/dist/__tests__/tools-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/tools-runtime.test.js +214 -0
- package/dist/__tests__/tools-runtime.test.js.map +1 -0
- package/dist/__tests__/tools-visual.test.d.ts +2 -0
- package/dist/__tests__/tools-visual.test.d.ts.map +1 -0
- package/dist/__tests__/tools-visual.test.js +149 -0
- package/dist/__tests__/tools-visual.test.js.map +1 -0
- package/dist/bridge-service.d.ts +99 -12
- package/dist/bridge-service.d.ts.map +1 -1
- package/dist/bridge-service.js +238 -21
- package/dist/bridge-service.js.map +1 -1
- package/dist/docs/cache.d.ts +50 -0
- package/dist/docs/cache.d.ts.map +1 -0
- package/dist/docs/cache.js +123 -0
- package/dist/docs/cache.js.map +1 -0
- package/dist/docs/embeddings/chunker.d.ts +120 -0
- package/dist/docs/embeddings/chunker.d.ts.map +1 -0
- package/dist/docs/embeddings/chunker.js +395 -0
- package/dist/docs/embeddings/chunker.js.map +1 -0
- package/dist/docs/embeddings/embedder.d.ts +41 -0
- package/dist/docs/embeddings/embedder.d.ts.map +1 -0
- package/dist/docs/embeddings/embedder.js +113 -0
- package/dist/docs/embeddings/embedder.js.map +1 -0
- package/dist/docs/embeddings/index.d.ts +102 -0
- package/dist/docs/embeddings/index.d.ts.map +1 -0
- package/dist/docs/embeddings/index.js +250 -0
- package/dist/docs/embeddings/index.js.map +1 -0
- package/dist/docs/embeddings/manager.d.ts +68 -0
- package/dist/docs/embeddings/manager.d.ts.map +1 -0
- package/dist/docs/embeddings/manager.js +97 -0
- package/dist/docs/embeddings/manager.js.map +1 -0
- package/dist/docs/fetcher.d.ts +29 -0
- package/dist/docs/fetcher.d.ts.map +1 -0
- package/dist/docs/fetcher.js +244 -0
- package/dist/docs/fetcher.js.map +1 -0
- package/dist/docs/reference.d.ts +37 -0
- package/dist/docs/reference.d.ts.map +1 -0
- package/dist/docs/reference.js +108 -0
- package/dist/docs/reference.js.map +1 -0
- package/dist/docs/search.d.ts +194 -0
- package/dist/docs/search.d.ts.map +1 -0
- package/dist/docs/search.js +733 -0
- package/dist/docs/search.js.map +1 -0
- package/dist/http-server.d.ts.map +1 -1
- package/dist/http-server.js +52 -5
- package/dist/http-server.js.map +1 -1
- package/dist/index.d.ts +8 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -1035
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +15 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +26 -0
- package/dist/instructions.js.map +1 -0
- package/dist/tools/defs/attributes.d.ts +6 -0
- package/dist/tools/defs/attributes.d.ts.map +1 -0
- package/dist/tools/defs/attributes.js +85 -0
- package/dist/tools/defs/attributes.js.map +1 -0
- package/dist/tools/defs/docs.d.ts +17 -0
- package/dist/tools/defs/docs.d.ts.map +1 -0
- package/dist/tools/defs/docs.js +151 -0
- package/dist/tools/defs/docs.js.map +1 -0
- package/dist/tools/defs/execute.d.ts +6 -0
- package/dist/tools/defs/execute.d.ts.map +1 -0
- package/dist/tools/defs/execute.js +21 -0
- package/dist/tools/defs/execute.js.map +1 -0
- package/dist/tools/defs/inspection.d.ts +7 -0
- package/dist/tools/defs/inspection.d.ts.map +1 -0
- package/dist/tools/defs/inspection.js +202 -0
- package/dist/tools/defs/inspection.js.map +1 -0
- package/dist/tools/defs/objects.d.ts +6 -0
- package/dist/tools/defs/objects.d.ts.map +1 -0
- package/dist/tools/defs/objects.js +111 -0
- package/dist/tools/defs/objects.js.map +1 -0
- package/dist/tools/defs/properties.d.ts +6 -0
- package/dist/tools/defs/properties.d.ts.map +1 -0
- package/dist/tools/defs/properties.js +71 -0
- package/dist/tools/defs/properties.js.map +1 -0
- package/dist/tools/defs/runtime.d.ts +6 -0
- package/dist/tools/defs/runtime.d.ts.map +1 -0
- package/dist/tools/defs/runtime.js +145 -0
- package/dist/tools/defs/runtime.js.map +1 -0
- package/dist/tools/defs/scripts.d.ts +18 -0
- package/dist/tools/defs/scripts.d.ts.map +1 -0
- package/dist/tools/defs/scripts.js +163 -0
- package/dist/tools/defs/scripts.js.map +1 -0
- package/dist/tools/defs/tags.d.ts +6 -0
- package/dist/tools/defs/tags.d.ts.map +1 -0
- package/dist/tools/defs/tags.js +74 -0
- package/dist/tools/defs/tags.js.map +1 -0
- package/dist/tools/defs/visual.d.ts +7 -0
- package/dist/tools/defs/visual.d.ts.map +1 -0
- package/dist/tools/defs/visual.js +208 -0
- package/dist/tools/defs/visual.js.map +1 -0
- package/dist/tools/index.d.ts +101 -25
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +580 -63
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/nudges.d.ts +25 -0
- package/dist/tools/nudges.d.ts.map +1 -0
- package/dist/tools/nudges.js +34 -0
- package/dist/tools/nudges.js.map +1 -0
- package/dist/tools/registry.d.ts +20 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +65 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +24 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/package.json +7 -6
- package/studio-plugin/MCPPlugin.rbxmx +3 -238
- package/studio-plugin/plugin.luau +2041 -365
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { buildIndex, loadIndex } from './index.js';
|
|
2
|
+
import { EMBED_MODEL } from './embedder.js';
|
|
3
|
+
let cached = null;
|
|
4
|
+
/** In-flight load/build promise for de-duplication of concurrent calls. */
|
|
5
|
+
let inFlight = null;
|
|
6
|
+
/**
|
|
7
|
+
* Test seam: lets unit tests inject a precanned index and skip the
|
|
8
|
+
* load/build path entirely. The keyword + hybrid rerank machinery
|
|
9
|
+
* uses `getOrBuild` directly, so injecting here is enough to
|
|
10
|
+
* deterministically exercise reranking.
|
|
11
|
+
*/
|
|
12
|
+
export function __setIndexForTests(entry) {
|
|
13
|
+
cached = entry;
|
|
14
|
+
inFlight = null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Return a usable in-memory index for `cacheDir`@`docsSha`, building
|
|
18
|
+
* it if necessary. Returns null when the index can't be obtained — the
|
|
19
|
+
* caller should fall back to keyword-only mode in that case.
|
|
20
|
+
*
|
|
21
|
+
* Decision tree:
|
|
22
|
+
* 1. We already have a hot index for this (cacheDir, sha) → return it.
|
|
23
|
+
* 2. Otherwise try to loadIndex() from disk — if it matches the sha,
|
|
24
|
+
* cache + return it.
|
|
25
|
+
* 3. Otherwise, if loadOnly: return null.
|
|
26
|
+
* 4. Otherwise build a fresh index. Heavy: ~25MB model download +
|
|
27
|
+
* embedding ~19k chunks. Measured at ~8 minutes on a single CPU
|
|
28
|
+
* box; faster on multi-core / GPU. Subsequent process restarts
|
|
29
|
+
* load from disk in <100ms.
|
|
30
|
+
*
|
|
31
|
+
* Errors during build are swallowed (returned as null) because we
|
|
32
|
+
* don't want a semantic-index failure to break the keyword search.
|
|
33
|
+
*/
|
|
34
|
+
export async function getOrBuild(cacheDir, docsSha, options = {}) {
|
|
35
|
+
if (cached && cached.cacheDir === cacheDir && cached.sha === docsSha) {
|
|
36
|
+
return cached.index;
|
|
37
|
+
}
|
|
38
|
+
if (inFlight)
|
|
39
|
+
return inFlight;
|
|
40
|
+
inFlight = (async () => {
|
|
41
|
+
try {
|
|
42
|
+
// 1. Try disk first — if the on-disk index matches our expected
|
|
43
|
+
// sha+model, just memo it.
|
|
44
|
+
const loaded = await loadIndex(cacheDir, docsSha, EMBED_MODEL);
|
|
45
|
+
if (loaded) {
|
|
46
|
+
cached = { cacheDir, sha: docsSha, index: loaded };
|
|
47
|
+
return loaded;
|
|
48
|
+
}
|
|
49
|
+
if (options.loadOnly)
|
|
50
|
+
return null;
|
|
51
|
+
// 2. Build from scratch. Heavy: model download + embed every
|
|
52
|
+
// chunk. Subsequent process restarts hit the disk cache.
|
|
53
|
+
const meta = await buildIndex(cacheDir, docsSha, {
|
|
54
|
+
model: EMBED_MODEL,
|
|
55
|
+
onProgress: options.onProgress,
|
|
56
|
+
});
|
|
57
|
+
// Reload from disk after building — keeps the load/build paths
|
|
58
|
+
// exercising the same code (so a bug in load doesn't only
|
|
59
|
+
// manifest after a process restart).
|
|
60
|
+
const built = await loadIndex(cacheDir, docsSha, EMBED_MODEL);
|
|
61
|
+
if (!built) {
|
|
62
|
+
// Shouldn't happen — we just wrote it. Log via stderr so a
|
|
63
|
+
// partial build is at least visible.
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.error(`[rbxstudio-mcp] built docs index reported ${meta.chunkCount} chunks but reload failed`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
cached = { cacheDir, sha: docsSha, index: built };
|
|
69
|
+
return built;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.error(`[rbxstudio-mcp] semantic index unavailable (${err?.message ?? err}); ` +
|
|
74
|
+
`falling back to keyword-only search`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
inFlight = null;
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
return inFlight;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Drop the in-memory cache. Used when the docs cache itself is
|
|
85
|
+
* re-fetched and we know the on-disk vectors are now stale. The
|
|
86
|
+
* fetcher (or its caller) is expected to nuke the index directory
|
|
87
|
+
* separately if it wants the on-disk vectors gone too — typically
|
|
88
|
+
* `buildIndex` will overwrite atomically on the next call anyway.
|
|
89
|
+
*/
|
|
90
|
+
export function invalidate() {
|
|
91
|
+
cached = null;
|
|
92
|
+
}
|
|
93
|
+
/** Diagnostic: is anything cached right now? */
|
|
94
|
+
export function currentMeta() {
|
|
95
|
+
return cached?.index.meta ?? null;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/docs/embeddings/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAkC,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA2B5C,IAAI,MAAM,GAAuB,IAAI,CAAC;AACtC,2EAA2E;AAC3E,IAAI,QAAQ,GAAqC,IAAI,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAyB;IAC1D,MAAM,GAAG,KAAK,CAAC;IACf,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,OAAe,EACf,UAA6B,EAAE;IAE/B,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;QACrE,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IACD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE;QACrB,IAAI,CAAC;YACH,gEAAgE;YAChE,8BAA8B;YAC9B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;YAC/D,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;gBACnD,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,IAAI,OAAO,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAElC,6DAA6D;YAC7D,4DAA4D;YAC5D,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE;gBAC/C,KAAK,EAAE,WAAW;gBAClB,UAAU,EAAE,OAAO,CAAC,UAAU;aAC/B,CAAC,CAAC;YACH,+DAA+D;YAC/D,0DAA0D;YAC1D,qCAAqC;YACrC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;YAC9D,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,2DAA2D;gBAC3D,qCAAqC;gBACrC,sCAAsC;gBACtC,OAAO,CAAC,KAAK,CACX,6CAA6C,IAAI,CAAC,UAAU,2BAA2B,CACxF,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,sCAAsC;YACtC,OAAO,CAAC,KAAK,CACX,+CAA+C,GAAG,EAAE,OAAO,IAAI,GAAG,KAAK;gBACrE,qCAAqC,CACxC,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type DocsMeta } from './cache.js';
|
|
2
|
+
export interface EnsureDocsResult {
|
|
3
|
+
cacheDir: string;
|
|
4
|
+
meta: DocsMeta;
|
|
5
|
+
/** What did this call actually do? */
|
|
6
|
+
action: 'fresh' | 'sha-short-circuit' | 'redownloaded' | 'first-download';
|
|
7
|
+
/** Wall-clock duration of the operation in ms. */
|
|
8
|
+
durationMs: number;
|
|
9
|
+
}
|
|
10
|
+
export interface EnsureDocsOptions {
|
|
11
|
+
/** Force a redownload regardless of TTL/SHA. */
|
|
12
|
+
force?: boolean;
|
|
13
|
+
/** Override the TTL window (ms). Default 24h. */
|
|
14
|
+
ttlMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Public entry point used by all docs tools. Guarantees that, by the
|
|
18
|
+
* time it returns, `cacheDir/content/...` contains a usable mirror of
|
|
19
|
+
* the docs (or throws).
|
|
20
|
+
*
|
|
21
|
+
* Decision tree:
|
|
22
|
+
* 1. No cache on disk → first-download
|
|
23
|
+
* 2. force=true → redownloaded
|
|
24
|
+
* 3. cache age <= TTL → fresh (no network call)
|
|
25
|
+
* 4. cache age > TTL && SHA same → sha-short-circuit (network: SHA only)
|
|
26
|
+
* 5. cache age > TTL && SHA different → redownloaded
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureDocsCache(options?: EnsureDocsOptions): Promise<EnsureDocsResult>;
|
|
29
|
+
//# sourceMappingURL=fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../../src/docs/fetcher.ts"],"names":[],"mappings":"AAKA,OAAO,EAQL,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AA0DpB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,gBAAgB,CAAC;IAC1E,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8FD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgDhG"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
|
+
import { pipeline } from 'stream/promises';
|
|
5
|
+
import * as tar from 'tar';
|
|
6
|
+
import { approxSizeOnDisk, clearContent, contentRoot, ensureCacheDir, readMeta, resolveCacheDir, writeMeta, } from './cache.js';
|
|
7
|
+
import { clearIndex } from './embeddings/index.js';
|
|
8
|
+
import { invalidate as invalidateSemanticIndex } from './embeddings/manager.js';
|
|
9
|
+
/**
|
|
10
|
+
* Mirror Roblox/creator-docs locally as a flat file tree, refreshed on
|
|
11
|
+
* demand.
|
|
12
|
+
*
|
|
13
|
+
* Why tarball instead of `git clone`?
|
|
14
|
+
* 1. No git binary requirement — works on any Node install.
|
|
15
|
+
* 2. ~30MB filtered tarball downloads in ~3s on a normal connection.
|
|
16
|
+
* 3. We don't need history, only the current tree.
|
|
17
|
+
*
|
|
18
|
+
* Why scope-filter to a subset of `content/en-us/`?
|
|
19
|
+
* The full creator-docs repo is ~200MB. The AI overwhelmingly needs
|
|
20
|
+
* the API reference (`reference/engine`) plus the long-form guides
|
|
21
|
+
* for things it tends to struggle with (animation, characters, ui,
|
|
22
|
+
* workspace). Filtering during extract keeps the on-disk footprint
|
|
23
|
+
* to ~30MB.
|
|
24
|
+
*/
|
|
25
|
+
const REPO_OWNER = 'Roblox';
|
|
26
|
+
const REPO_NAME = 'creator-docs';
|
|
27
|
+
const REPO_BRANCH = 'main';
|
|
28
|
+
/**
|
|
29
|
+
* Path prefixes (relative to repo root) we keep on disk.
|
|
30
|
+
*
|
|
31
|
+
* After the tarball is extracted with `strip: 2`, the leading
|
|
32
|
+
* `<repo>-<branch>/content/` is removed, so on-disk these become
|
|
33
|
+
* `<cacheDir>/content/en-us/...`. The filter however receives the
|
|
34
|
+
* pre-strip path, so we match the `content/en-us/...` form here.
|
|
35
|
+
*/
|
|
36
|
+
const KEEP_PREFIXES = [
|
|
37
|
+
'content/en-us/reference/',
|
|
38
|
+
'content/en-us/animation/',
|
|
39
|
+
'content/en-us/characters/',
|
|
40
|
+
'content/en-us/ui/',
|
|
41
|
+
'content/en-us/scripting/',
|
|
42
|
+
'content/en-us/physics/',
|
|
43
|
+
'content/en-us/workspace/',
|
|
44
|
+
'content/en-us/players/',
|
|
45
|
+
'content/en-us/input/',
|
|
46
|
+
'content/en-us/cloud-services/',
|
|
47
|
+
'content/en-us/sound/',
|
|
48
|
+
];
|
|
49
|
+
/** File extensions we keep within the prefixes above. */
|
|
50
|
+
const KEEP_EXTENSIONS = new Set(['.md', '.yaml', '.yml']);
|
|
51
|
+
/** GitHub API: latest commit SHA on a branch. */
|
|
52
|
+
const REFS_URL = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/commits/${REPO_BRANCH}`;
|
|
53
|
+
/** codeload.github.com tarball — much faster + no auth required. */
|
|
54
|
+
const TARBALL_URL = `https://codeload.github.com/${REPO_OWNER}/${REPO_NAME}/tar.gz/refs/heads/${REPO_BRANCH}`;
|
|
55
|
+
/** How long a cache entry is "fresh enough" before we re-check upstream. */
|
|
56
|
+
const TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
57
|
+
function commonHeaders() {
|
|
58
|
+
// Identify ourselves to GitHub. They rate-limit unauthenticated
|
|
59
|
+
// requests but the daily SHA check is well under the limit.
|
|
60
|
+
return {
|
|
61
|
+
'User-Agent': 'rbxstudio-mcp (Roblox docs cache, https://github.com/boshyxd/robloxstudio-mcp)',
|
|
62
|
+
Accept: 'application/vnd.github+json',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Hit GitHub's commits API to get the current head SHA on `main`. */
|
|
66
|
+
async function fetchHeadSha() {
|
|
67
|
+
const res = await fetch(REFS_URL, { headers: commonHeaders() });
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
throw new Error(`GitHub API ${res.status} ${res.statusText} when fetching ${REFS_URL}`);
|
|
70
|
+
}
|
|
71
|
+
const body = await res.json();
|
|
72
|
+
if (typeof body?.sha !== 'string') {
|
|
73
|
+
throw new Error('GitHub API returned no sha for HEAD commit');
|
|
74
|
+
}
|
|
75
|
+
return body.sha;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Strip the leading `<repo>-<branch>/` directory that codeload tarballs
|
|
79
|
+
* wrap everything in, returning the path relative to the repo root.
|
|
80
|
+
* Returns null for the wrapper directory entry itself.
|
|
81
|
+
*/
|
|
82
|
+
function relativizeTarPath(tarPath) {
|
|
83
|
+
const slash = tarPath.indexOf('/');
|
|
84
|
+
if (slash < 0)
|
|
85
|
+
return null;
|
|
86
|
+
const rel = tarPath.slice(slash + 1);
|
|
87
|
+
if (!rel || rel === '/')
|
|
88
|
+
return null;
|
|
89
|
+
return rel;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Download the tarball and stream it through tar's extractor with a
|
|
93
|
+
* filter so only matching paths are written to disk.
|
|
94
|
+
*
|
|
95
|
+
* Note on tar v7 behavior: the `filter` callback receives the ORIGINAL
|
|
96
|
+
* (pre-strip) path, not the post-strip path. `strip:1` only affects the
|
|
97
|
+
* on-disk output path. So we relativize here just to make the filter
|
|
98
|
+
* comparison readable.
|
|
99
|
+
*/
|
|
100
|
+
async function downloadAndExtract(cacheDir) {
|
|
101
|
+
const dest = contentRoot(cacheDir);
|
|
102
|
+
// Wipe before extract so deleted upstream files don't linger.
|
|
103
|
+
await clearContent(cacheDir);
|
|
104
|
+
await fs.mkdir(dest, { recursive: true });
|
|
105
|
+
const res = await fetch(TARBALL_URL, { headers: commonHeaders() });
|
|
106
|
+
if (!res.ok || !res.body) {
|
|
107
|
+
throw new Error(`Tarball download failed: ${res.status} ${res.statusText} from ${TARBALL_URL}`);
|
|
108
|
+
}
|
|
109
|
+
let fileCount = 0;
|
|
110
|
+
const extractor = tar.x({
|
|
111
|
+
cwd: dest,
|
|
112
|
+
// `strip: 2` drops both the "<repo>-<branch>/" wrapper directory AND
|
|
113
|
+
// the upstream "content/" directory, so on-disk we end up with
|
|
114
|
+
// <cacheDir>/content/en-us/reference/engine/classes/Motor6D.yaml
|
|
115
|
+
// matching what `contentRoot()` expects.
|
|
116
|
+
//
|
|
117
|
+
// Filter still sees the ORIGINAL pre-strip path
|
|
118
|
+
// (`creator-docs-main/content/en-us/…`), which is why we relativize
|
|
119
|
+
// it manually before checking against KEEP_PREFIXES.
|
|
120
|
+
strip: 2,
|
|
121
|
+
filter: (tarPath) => {
|
|
122
|
+
const rel = relativizeTarPath(tarPath);
|
|
123
|
+
if (!rel)
|
|
124
|
+
return false;
|
|
125
|
+
// Directory entries end in `/` — let tar create them if needed.
|
|
126
|
+
// We only care about counting / filtering files.
|
|
127
|
+
if (rel.endsWith('/')) {
|
|
128
|
+
return KEEP_PREFIXES.some((p) => p.startsWith(rel) || rel.startsWith(p));
|
|
129
|
+
}
|
|
130
|
+
if (!KEEP_PREFIXES.some((p) => rel.startsWith(p)))
|
|
131
|
+
return false;
|
|
132
|
+
const ext = path.extname(rel).toLowerCase();
|
|
133
|
+
if (!KEEP_EXTENSIONS.has(ext))
|
|
134
|
+
return false;
|
|
135
|
+
fileCount++;
|
|
136
|
+
return true;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
// node-fetch / undici body is a Web ReadableStream. Convert to a
|
|
140
|
+
// Node Readable and pipe into tar.
|
|
141
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
142
|
+
await pipeline(nodeStream, extractor);
|
|
143
|
+
return { fileCount };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Public entry point used by all docs tools. Guarantees that, by the
|
|
147
|
+
* time it returns, `cacheDir/content/...` contains a usable mirror of
|
|
148
|
+
* the docs (or throws).
|
|
149
|
+
*
|
|
150
|
+
* Decision tree:
|
|
151
|
+
* 1. No cache on disk → first-download
|
|
152
|
+
* 2. force=true → redownloaded
|
|
153
|
+
* 3. cache age <= TTL → fresh (no network call)
|
|
154
|
+
* 4. cache age > TTL && SHA same → sha-short-circuit (network: SHA only)
|
|
155
|
+
* 5. cache age > TTL && SHA different → redownloaded
|
|
156
|
+
*/
|
|
157
|
+
export async function ensureDocsCache(options = {}) {
|
|
158
|
+
const cacheDir = resolveCacheDir();
|
|
159
|
+
const ttlMs = options.ttlMs ?? TTL_MS;
|
|
160
|
+
const t0 = Date.now();
|
|
161
|
+
await ensureCacheDir(cacheDir);
|
|
162
|
+
const existing = await readMeta(cacheDir);
|
|
163
|
+
// 1. No cache yet.
|
|
164
|
+
if (!existing || !existing.sha) {
|
|
165
|
+
return await doDownload(cacheDir, t0, 'first-download');
|
|
166
|
+
}
|
|
167
|
+
// 2. Forced.
|
|
168
|
+
if (options.force) {
|
|
169
|
+
return await doDownload(cacheDir, t0, 'redownloaded');
|
|
170
|
+
}
|
|
171
|
+
// 3. Within TTL window — trust the cache.
|
|
172
|
+
const downloadedAt = Date.parse(existing.downloadedAt);
|
|
173
|
+
const fresh = Number.isFinite(downloadedAt) && Date.now() - downloadedAt <= ttlMs;
|
|
174
|
+
if (fresh) {
|
|
175
|
+
return {
|
|
176
|
+
cacheDir,
|
|
177
|
+
meta: existing,
|
|
178
|
+
action: 'fresh',
|
|
179
|
+
durationMs: Date.now() - t0,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// 4 / 5. TTL expired — ask GitHub if anything changed.
|
|
183
|
+
const upstreamSha = await fetchHeadSha();
|
|
184
|
+
if (upstreamSha === existing.sha) {
|
|
185
|
+
// Just bump lastCheckedAt so the next call hits TTL again.
|
|
186
|
+
const updated = {
|
|
187
|
+
...existing,
|
|
188
|
+
lastCheckedAt: new Date().toISOString(),
|
|
189
|
+
};
|
|
190
|
+
await writeMeta(cacheDir, updated);
|
|
191
|
+
return {
|
|
192
|
+
cacheDir,
|
|
193
|
+
meta: { ...updated, schemaVersion: 1 },
|
|
194
|
+
action: 'sha-short-circuit',
|
|
195
|
+
durationMs: Date.now() - t0,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return await doDownload(cacheDir, t0, 'redownloaded');
|
|
199
|
+
}
|
|
200
|
+
async function doDownload(cacheDir, t0, action) {
|
|
201
|
+
// Get the SHA we're locking to. If this fails, we can't sensibly
|
|
202
|
+
// record provenance, but the tarball itself is the source of truth.
|
|
203
|
+
let sha = '';
|
|
204
|
+
try {
|
|
205
|
+
sha = await fetchHeadSha();
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Network blip on the SHA endpoint shouldn't block the download.
|
|
209
|
+
sha = '';
|
|
210
|
+
}
|
|
211
|
+
const { fileCount } = await downloadAndExtract(cacheDir);
|
|
212
|
+
const bytesOnDisk = await approxSizeOnDisk(contentRoot(cacheDir));
|
|
213
|
+
const now = new Date().toISOString();
|
|
214
|
+
const meta = {
|
|
215
|
+
repo: `${REPO_OWNER}/${REPO_NAME}`,
|
|
216
|
+
branch: REPO_BRANCH,
|
|
217
|
+
sha,
|
|
218
|
+
downloadedAt: now,
|
|
219
|
+
lastCheckedAt: now,
|
|
220
|
+
fileCount,
|
|
221
|
+
bytesOnDisk,
|
|
222
|
+
};
|
|
223
|
+
await writeMeta(cacheDir, meta);
|
|
224
|
+
// Vectors are tied 1:1 to the file tree we just blew away. Drop the
|
|
225
|
+
// in-memory cache and the on-disk index so the next semantic query
|
|
226
|
+
// rebuilds from the fresh docs. (We don't proactively rebuild here
|
|
227
|
+
// — that would block this call for ~60s for a feature the caller
|
|
228
|
+
// might not even use.)
|
|
229
|
+
invalidateSemanticIndex();
|
|
230
|
+
try {
|
|
231
|
+
await clearIndex(cacheDir);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Best-effort — a stale index is annoying but not fatal; the
|
|
235
|
+
// schema/sha check inside loadIndex() will reject it next time.
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
cacheDir,
|
|
239
|
+
meta: { ...meta, schemaVersion: 1 },
|
|
240
|
+
action,
|
|
241
|
+
durationMs: Date.now() - t0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/docs/fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,eAAe,EACf,SAAS,GAEV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,UAAU,IAAI,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEhF;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,UAAU,GAAG,QAAQ,CAAC;AAC5B,MAAM,SAAS,GAAG,cAAc,CAAC;AACjC,MAAM,WAAW,GAAG,MAAM,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG;IACpB,0BAA0B;IAC1B,0BAA0B;IAC1B,2BAA2B;IAC3B,mBAAmB;IACnB,0BAA0B;IAC1B,wBAAwB;IACxB,0BAA0B;IAC1B,wBAAwB;IACxB,sBAAsB;IACtB,+BAA+B;IAC/B,sBAAsB;CACvB,CAAC;AAEF,yDAAyD;AACzD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1D,iDAAiD;AACjD,MAAM,QAAQ,GAAG,gCAAgC,UAAU,IAAI,SAAS,YAAY,WAAW,EAAE,CAAC;AAClG,oEAAoE;AACpE,MAAM,WAAW,GAAG,+BAA+B,UAAU,IAAI,SAAS,sBAAsB,WAAW,EAAE,CAAC;AAE9G,4EAA4E;AAC5E,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM;AAkB1C,SAAS,aAAa;IACpB,gEAAgE;IAChE,4DAA4D;IAC5D,OAAO;QACL,YAAY,EAAE,gFAAgF;QAC9F,MAAM,EAAE,6BAA6B;KACtC,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,KAAK,UAAU,YAAY;IACzB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,kBAAkB,QAAQ,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,IAAI,GAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,OAAO,IAAI,EAAE,GAAG,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,IAAI,CAAC,GAAa,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,OAAe;IACxC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACrC,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IAChD,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnC,8DAA8D;IAC9D,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC7B,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,SAAS,WAAW,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC;QACtB,GAAG,EAAE,IAAI;QACT,qEAAqE;QACrE,+DAA+D;QAC/D,mEAAmE;QACnE,yCAAyC;QACzC,EAAE;QACF,gDAAgD;QAChD,oEAAoE;QACpE,qDAAqD;QACrD,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;YAClB,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACvC,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,gEAAgE;YAChE,iDAAiD;YACjD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC5C,SAAS,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;IAEH,iEAAiE;IACjE,mCAAmC;IACnC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAW,CAAC,CAAC;IACrD,MAAM,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAEtC,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAA6B,EAAE;IACnE,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC;IACtC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEtB,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAE1C,mBAAmB;IACnB,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QAC/B,OAAO,MAAM,UAAU,CAAC,QAAQ,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC1D,CAAC;IAED,aAAa;IACb,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,MAAM,UAAU,CAAC,QAAQ,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;IACxD,CAAC;IAED,0CAA0C;IAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,IAAI,KAAK,CAAC;IAClF,IAAI,KAAK,EAAE,CAAC;QACV,OAAO;YACL,QAAQ;YACR,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,OAAO;YACf,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;SAC5B,CAAC;IACJ,CAAC;IAED,uDAAuD;IACvD,MAAM,WAAW,GAAG,MAAM,YAAY,EAAE,CAAC;IACzC,IAAI,WAAW,KAAK,QAAQ,CAAC,GAAG,EAAE,CAAC;QACjC,2DAA2D;QAC3D,MAAM,OAAO,GAAoC;YAC/C,GAAG,QAAQ;YACX,aAAa,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACxC,CAAC;QACF,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnC,OAAO;YACL,QAAQ;YACR,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE;YACtC,MAAM,EAAE,mBAAmB;YAC3B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;SAC5B,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,UAAU,CAAC,QAAQ,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,QAAgB,EAChB,EAAU,EACV,MAAkC;IAElC,iEAAiE;IACjE,oEAAoE;IACpE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;QACjE,GAAG,GAAG,EAAE,CAAC;IACX,CAAC;IAED,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,MAAM,gBAAgB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,IAAI,GAAoC;QAC5C,IAAI,EAAE,GAAG,UAAU,IAAI,SAAS,EAAE;QAClC,MAAM,EAAE,WAAW;QACnB,GAAG;QACH,YAAY,EAAE,GAAG;QACjB,aAAa,EAAE,GAAG;QAClB,SAAS;QACT,WAAW;KACZ,CAAC;IACF,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAEhC,oEAAoE;IACpE,mEAAmE;IACnE,mEAAmE;IACnE,iEAAiE;IACjE,uBAAuB;IACvB,uBAAuB,EAAE,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;QAC7D,gEAAgE;IAClE,CAAC;IAED,OAAO;QACL,QAAQ;QACR,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE;QACnC,MAAM;QACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
declare const CATEGORY_DIRS: {
|
|
2
|
+
readonly class: "classes";
|
|
3
|
+
readonly datatype: "datatypes";
|
|
4
|
+
readonly enum: "enums";
|
|
5
|
+
readonly global: "globals";
|
|
6
|
+
readonly library: "libraries";
|
|
7
|
+
};
|
|
8
|
+
export type ReferenceCategory = keyof typeof CATEGORY_DIRS;
|
|
9
|
+
export interface ReferenceLookupResult {
|
|
10
|
+
/** Where it was found, e.g. "classes" or "datatypes". */
|
|
11
|
+
category: ReferenceCategory;
|
|
12
|
+
/** The canonical name as it appears on disk (e.g. "Motor6D"). */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Path relative to the content root. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** Parsed YAML body. Shape mirrors creator-docs' API schema. */
|
|
17
|
+
data: unknown;
|
|
18
|
+
/** Raw YAML source — handy for grep-style follow-ups. */
|
|
19
|
+
raw: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a bare name like "Motor6D" or "CFrame" to a parsed YAML doc.
|
|
23
|
+
*
|
|
24
|
+
* Resolution order (when `category` is omitted):
|
|
25
|
+
* 1. classes
|
|
26
|
+
* 2. datatypes
|
|
27
|
+
* 3. enums
|
|
28
|
+
* 4. globals
|
|
29
|
+
* 5. libraries
|
|
30
|
+
*
|
|
31
|
+
* The first match wins. Roblox's namespace doesn't currently have name
|
|
32
|
+
* collisions across these categories that I know of, but if it ever
|
|
33
|
+
* does the caller can disambiguate via `category`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveReference(cacheDir: string, name: string, category?: ReferenceCategory): Promise<ReferenceLookupResult | null>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=reference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../src/docs/reference.ts"],"names":[],"mappings":"AAwBA,QAAA,MAAM,aAAa;;;;;;CAMT,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,aAAa,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,OAAO,CAAC;IACd,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;CACb;AAmCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAgCvC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as yaml from 'js-yaml';
|
|
4
|
+
import { contentRoot } from './cache.js';
|
|
5
|
+
/**
|
|
6
|
+
* `get_roblox_api_reference` resolver.
|
|
7
|
+
*
|
|
8
|
+
* The creator-docs repo stores structured API reference under:
|
|
9
|
+
*
|
|
10
|
+
* content/en-us/reference/engine/
|
|
11
|
+
* classes/<ClassName>.yaml (e.g. Motor6D.yaml, Part.yaml)
|
|
12
|
+
* datatypes/<TypeName>.yaml (e.g. CFrame.yaml, Vector3.yaml)
|
|
13
|
+
* enums/<EnumName>.yaml (e.g. Material.yaml, KeyCode.yaml)
|
|
14
|
+
* globals/<API>.yaml (e.g. RobloxGlobals.yaml)
|
|
15
|
+
* libraries/<Lib>.yaml (e.g. math.yaml, string.yaml)
|
|
16
|
+
*
|
|
17
|
+
* The model usually knows what it's looking for ("Motor6D", "CFrame",
|
|
18
|
+
* "Material") but doesn't know which subdirectory to look in. This
|
|
19
|
+
* module resolves a name to the correct file and returns parsed YAML.
|
|
20
|
+
*/
|
|
21
|
+
const ENGINE_REL = path.join('en-us', 'reference', 'engine');
|
|
22
|
+
const CATEGORY_DIRS = {
|
|
23
|
+
class: 'classes',
|
|
24
|
+
datatype: 'datatypes',
|
|
25
|
+
enum: 'enums',
|
|
26
|
+
global: 'globals',
|
|
27
|
+
library: 'libraries',
|
|
28
|
+
};
|
|
29
|
+
async function exists(p) {
|
|
30
|
+
try {
|
|
31
|
+
await fs.access(p);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function findInDir(dir, name) {
|
|
39
|
+
// Try the obvious filename first (cheap, no readdir).
|
|
40
|
+
const direct = path.join(dir, `${name}.yaml`);
|
|
41
|
+
if (await exists(direct))
|
|
42
|
+
return direct;
|
|
43
|
+
// Fall back to a case-insensitive scan. Roblox YAML names are
|
|
44
|
+
// PascalCase, but the model sometimes lowercases them ("part",
|
|
45
|
+
// "cframe"). Scanning the directory once is fine — there are
|
|
46
|
+
// <2k files total across all categories.
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = await fs.readdir(dir);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const target = `${name.toLowerCase()}.yaml`;
|
|
55
|
+
for (const f of entries) {
|
|
56
|
+
if (f.toLowerCase() === target) {
|
|
57
|
+
return path.join(dir, f);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a bare name like "Motor6D" or "CFrame" to a parsed YAML doc.
|
|
64
|
+
*
|
|
65
|
+
* Resolution order (when `category` is omitted):
|
|
66
|
+
* 1. classes
|
|
67
|
+
* 2. datatypes
|
|
68
|
+
* 3. enums
|
|
69
|
+
* 4. globals
|
|
70
|
+
* 5. libraries
|
|
71
|
+
*
|
|
72
|
+
* The first match wins. Roblox's namespace doesn't currently have name
|
|
73
|
+
* collisions across these categories that I know of, but if it ever
|
|
74
|
+
* does the caller can disambiguate via `category`.
|
|
75
|
+
*/
|
|
76
|
+
export async function resolveReference(cacheDir, name, category) {
|
|
77
|
+
if (!name)
|
|
78
|
+
return null;
|
|
79
|
+
const root = path.join(contentRoot(cacheDir), ENGINE_REL);
|
|
80
|
+
const order = category
|
|
81
|
+
? [category]
|
|
82
|
+
: ['class', 'datatype', 'enum', 'global', 'library'];
|
|
83
|
+
for (const cat of order) {
|
|
84
|
+
const dir = path.join(root, CATEGORY_DIRS[cat]);
|
|
85
|
+
const hit = await findInDir(dir, name);
|
|
86
|
+
if (!hit)
|
|
87
|
+
continue;
|
|
88
|
+
const raw = await fs.readFile(hit, 'utf8');
|
|
89
|
+
let data = null;
|
|
90
|
+
try {
|
|
91
|
+
data = yaml.load(raw, { filename: hit, schema: yaml.JSON_SCHEMA });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Don't fail the tool call just because YAML parse went sideways
|
|
95
|
+
// — return the raw text so the model can still grep through it.
|
|
96
|
+
data = { __parseError: err?.message ?? String(err) };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
category: cat,
|
|
100
|
+
name: path.basename(hit, '.yaml'),
|
|
101
|
+
path: path.relative(contentRoot(cacheDir), hit),
|
|
102
|
+
data,
|
|
103
|
+
raw,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/docs/reference.ts"],"names":[],"mappings":"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"}
|