@oneentry/mcp-platform-server 0.1.0 → 0.1.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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -92
  3. package/data/permissions.json +411 -0
  4. package/dist/api/audit.d.ts +0 -11
  5. package/dist/api/audit.js +0 -10
  6. package/dist/api/auth.d.ts +0 -18
  7. package/dist/api/auth.js +0 -20
  8. package/dist/api/build-catalog.d.ts +10 -0
  9. package/dist/api/build-catalog.js +187 -0
  10. package/dist/api/catalog.d.ts +8 -15
  11. package/dist/api/catalog.js +54 -16
  12. package/dist/api/client.d.ts +0 -24
  13. package/dist/api/client.js +6 -28
  14. package/dist/api/normalize-schema.d.ts +3 -0
  15. package/dist/api/normalize-schema.js +133 -0
  16. package/dist/api/policy.d.ts +0 -23
  17. package/dist/api/policy.js +0 -0
  18. package/dist/api/shape.d.ts +0 -13
  19. package/dist/api/shape.js +0 -21
  20. package/dist/api/swagger-source.d.ts +11 -0
  21. package/dist/api/swagger-source.js +58 -0
  22. package/dist/api/types.d.ts +0 -34
  23. package/dist/api/types.js +0 -1
  24. package/dist/bin/cli.d.ts +0 -1
  25. package/dist/bin/cli.js +11 -7
  26. package/dist/config/config.d.ts +66 -46
  27. package/dist/config/config.js +47 -44
  28. package/dist/index.d.ts +9 -7
  29. package/dist/index.js +5 -6
  30. package/dist/knowledge/chunk.d.ts +0 -15
  31. package/dist/knowledge/chunk.js +0 -16
  32. package/dist/knowledge/github.d.ts +21 -0
  33. package/dist/knowledge/github.js +174 -0
  34. package/dist/knowledge/loader.d.ts +23 -24
  35. package/dist/knowledge/loader.js +69 -108
  36. package/dist/knowledge/search.d.ts +1 -12
  37. package/dist/knowledge/search.js +19 -20
  38. package/dist/knowledge/tar.d.ts +7 -0
  39. package/dist/knowledge/tar.js +87 -0
  40. package/dist/knowledge/types.d.ts +8 -22
  41. package/dist/knowledge/types.js +0 -1
  42. package/dist/server.d.ts +1 -12
  43. package/dist/server.js +21 -18
  44. package/dist/session.d.ts +1 -18
  45. package/dist/session.js +3 -13
  46. package/dist/tools/api-call.d.ts +0 -2
  47. package/dist/tools/api-call.js +0 -23
  48. package/dist/tools/api-discovery.d.ts +0 -2
  49. package/dist/tools/api-discovery.js +2 -5
  50. package/dist/tools/docs.d.ts +0 -2
  51. package/dist/tools/docs.js +1 -5
  52. package/dist/tools/guide.d.ts +0 -7
  53. package/dist/tools/guide.js +11 -11
  54. package/dist/tools/result.d.ts +0 -8
  55. package/dist/tools/result.js +0 -7
  56. package/dist/tools/whoami.d.ts +0 -2
  57. package/dist/tools/whoami.js +5 -3
  58. package/dist/transports/http.d.ts +0 -6
  59. package/dist/transports/http.js +17 -18
  60. package/dist/transports/stdio.d.ts +0 -6
  61. package/dist/transports/stdio.js +1 -7
  62. package/knowledge/operating-rules.md +121 -0
  63. package/package.json +20 -7
  64. package/data/catalog.json +0 -1
  65. package/data/knowledge.json +0 -1
  66. package/dist/api/audit.d.ts.map +0 -1
  67. package/dist/api/audit.js.map +0 -1
  68. package/dist/api/auth.d.ts.map +0 -1
  69. package/dist/api/auth.js.map +0 -1
  70. package/dist/api/catalog.d.ts.map +0 -1
  71. package/dist/api/catalog.js.map +0 -1
  72. package/dist/api/client.d.ts.map +0 -1
  73. package/dist/api/client.js.map +0 -1
  74. package/dist/api/policy.d.ts.map +0 -1
  75. package/dist/api/policy.js.map +0 -1
  76. package/dist/api/shape.d.ts.map +0 -1
  77. package/dist/api/shape.js.map +0 -1
  78. package/dist/api/types.d.ts.map +0 -1
  79. package/dist/api/types.js.map +0 -1
  80. package/dist/bin/cli.d.ts.map +0 -1
  81. package/dist/bin/cli.js.map +0 -1
  82. package/dist/config/config.d.ts.map +0 -1
  83. package/dist/config/config.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/knowledge/chunk.d.ts.map +0 -1
  87. package/dist/knowledge/chunk.js.map +0 -1
  88. package/dist/knowledge/loader.d.ts.map +0 -1
  89. package/dist/knowledge/loader.js.map +0 -1
  90. package/dist/knowledge/search.d.ts.map +0 -1
  91. package/dist/knowledge/search.js.map +0 -1
  92. package/dist/knowledge/types.d.ts.map +0 -1
  93. package/dist/knowledge/types.js.map +0 -1
  94. package/dist/server.d.ts.map +0 -1
  95. package/dist/server.js.map +0 -1
  96. package/dist/session.d.ts.map +0 -1
  97. package/dist/session.js.map +0 -1
  98. package/dist/tools/api-call.d.ts.map +0 -1
  99. package/dist/tools/api-call.js.map +0 -1
  100. package/dist/tools/api-discovery.d.ts.map +0 -1
  101. package/dist/tools/api-discovery.js.map +0 -1
  102. package/dist/tools/docs.d.ts.map +0 -1
  103. package/dist/tools/docs.js.map +0 -1
  104. package/dist/tools/guide.d.ts.map +0 -1
  105. package/dist/tools/guide.js.map +0 -1
  106. package/dist/tools/result.d.ts.map +0 -1
  107. package/dist/tools/result.js.map +0 -1
  108. package/dist/tools/whoami.d.ts.map +0 -1
  109. package/dist/tools/whoami.js.map +0 -1
  110. package/dist/transports/http.d.ts.map +0 -1
  111. package/dist/transports/http.js.map +0 -1
  112. package/dist/transports/stdio.d.ts.map +0 -1
  113. package/dist/transports/stdio.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * Публичный API пакета: всё, что нужно, чтобы встроить сервер в свой процесс
3
- * или собрать его иначе, чем это делает CLI.
4
- */
5
1
  export { loadConfig, parseFlags, normalizeBaseUrl, ConfigSchema } from './config/config.js';
6
2
  export { createServer, createSharedDeps } from './server.js';
7
3
  export { Session } from './session.js';
@@ -12,7 +8,10 @@ export { shapeResponse } from './api/shape.js';
12
8
  export { AuditLog } from './api/audit.js';
13
9
  export { TokenStore, AuthError } from './api/auth.js';
14
10
  export { KnowledgeIndex } from './knowledge/search.js';
15
- export { buildPack, loadSnapshot, resolveKnowledge } from './knowledge/loader.js';
11
+ export { packFromFiles, resolveKnowledge, seedPack } from './knowledge/loader.js';
12
+ export { fetchKnowledgeTree, readLocalTree } from './knowledge/github.js';
13
+ export { readTar, readTarGz } from './knowledge/tar.js';
14
+ export { buildCatalog } from './api/build-catalog.js';
15
+ export { fetchSwagger } from './api/swagger-source.js';
16
16
  export { runStdio } from './transports/stdio.js';
17
17
  export { runHttp } from './transports/http.js';
18
- //# sourceMappingURL=index.js.map
@@ -1,27 +1,13 @@
1
1
  import type { KnowledgeChunk, KnowledgeDoc } from './types.js';
2
- /**
3
- * Слаг заголовка в стиле GitHub: нижний регистр, пробелы в дефисы,
4
- * пунктуация выброшена. Кириллица сохраняется — документация в проекте русскоязычная.
5
- */
6
2
  export declare const slugify: (heading: string) => string;
7
3
  interface Section {
8
4
  title: string;
9
5
  lines: string[];
10
6
  }
11
- /**
12
- * Режет markdown по заголовкам `##` и `###`.
13
- * Преамбула до первого `##` становится разделом с пустым anchor — в ней обычно
14
- * лежит самое важное описание модуля, терять его нельзя.
15
- */
16
7
  export declare const splitSections: (markdown: string) => {
17
8
  docTitle: string;
18
9
  sections: Section[];
19
10
  };
20
- /**
21
- * Превращает один markdown-файл в документ + его разделы.
22
- * Anchor'ы дедуплицируются суффиксом, иначе два одинаковых `## Схема`
23
- * в одном файле стали бы неразличимы для cms_docs_read.
24
- */
25
11
  export declare const chunkDocument: (params: {
26
12
  docId: string;
27
13
  repo: KnowledgeChunk["repo"];
@@ -32,4 +18,3 @@ export declare const chunkDocument: (params: {
32
18
  chunks: KnowledgeChunk[];
33
19
  };
34
20
  export {};
35
- //# sourceMappingURL=chunk.d.ts.map
@@ -1,20 +1,10 @@
1
- /**
2
- * Слаг заголовка в стиле GitHub: нижний регистр, пробелы в дефисы,
3
- * пунктуация выброшена. Кириллица сохраняется — документация в проекте русскоязычная.
4
- */
5
1
  export const slugify = (heading) => heading
6
2
  .toLowerCase()
7
3
  .replace(/`|\*|_|\[|\]|\(|\)|:|,|\.|\/|\\|«|»|"|'|\?|!|;/g, '')
8
4
  .trim()
9
5
  .replace(/\s+/g, '-')
10
6
  .slice(0, 80);
11
- /** Строки внутри огороженных блоков кода не считаются заголовками. */
12
7
  const isFence = (line) => line.startsWith('```') || line.startsWith('~~~');
13
- /**
14
- * Режет markdown по заголовкам `##` и `###`.
15
- * Преамбула до первого `##` становится разделом с пустым anchor — в ней обычно
16
- * лежит самое важное описание модуля, терять его нельзя.
17
- */
18
8
  export const splitSections = (markdown) => {
19
9
  const lines = markdown.split('\n');
20
10
  let docTitle = '';
@@ -43,11 +33,6 @@ export const splitSections = (markdown) => {
43
33
  sections: sections.filter((s) => s.title !== '' || s.lines.join('').trim() !== ''),
44
34
  };
45
35
  };
46
- /**
47
- * Превращает один markdown-файл в документ + его разделы.
48
- * Anchor'ы дедуплицируются суффиксом, иначе два одинаковых `## Схема`
49
- * в одном файле стали бы неразличимы для cms_docs_read.
50
- */
51
36
  export const chunkDocument = (params) => {
52
37
  const { docTitle, sections } = splitSections(params.markdown);
53
38
  const seen = new Map();
@@ -84,4 +69,3 @@ export const chunkDocument = (params) => {
84
69
  chunks,
85
70
  };
86
71
  };
87
- //# sourceMappingURL=chunk.js.map
@@ -0,0 +1,21 @@
1
+ export interface KnowledgeSource {
2
+ repo: string;
3
+ ref: string;
4
+ cacheDir: string;
5
+ refreshTtlMs: number;
6
+ offline: boolean;
7
+ token?: string;
8
+ timeoutMs: number;
9
+ }
10
+ export interface KnowledgeFile {
11
+ path: string;
12
+ content: string;
13
+ }
14
+ export interface KnowledgeTree {
15
+ files: KnowledgeFile[];
16
+ sha: string;
17
+ source: 'github' | 'cache';
18
+ }
19
+ export declare const walkMarkdown: (root: string) => string[];
20
+ export declare const fetchKnowledgeTree: (source: KnowledgeSource) => Promise<KnowledgeTree>;
21
+ export declare const readLocalTree: (path: string) => KnowledgeFile[];
@@ -0,0 +1,174 @@
1
+ import { mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ import { readTarGz } from './tar.js';
4
+ const ROOT = 'knowledge';
5
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ const repoCacheDir = (source) => resolve(source.cacheDir, source.repo.replace('/', '--'));
7
+ const stateFile = (source) => join(repoCacheDir(source), 'current.json');
8
+ const readState = (source) => {
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(stateFile(source), 'utf8'));
11
+ if (!isRecord(parsed) ||
12
+ typeof parsed['sha'] !== 'string' ||
13
+ typeof parsed['ref'] !== 'string' ||
14
+ typeof parsed['checkedAt'] !== 'number') {
15
+ return undefined;
16
+ }
17
+ return { ref: parsed['ref'], sha: parsed['sha'], checkedAt: parsed['checkedAt'] };
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ };
23
+ const writeState = (source, state) => {
24
+ mkdirSync(repoCacheDir(source), { recursive: true });
25
+ writeFileSync(stateFile(source), `${JSON.stringify(state)}\n`);
26
+ };
27
+ export const walkMarkdown = (root) => {
28
+ const out = [];
29
+ const visit = (dir) => {
30
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
31
+ const full = join(dir, entry.name);
32
+ if (entry.isDirectory()) {
33
+ visit(full);
34
+ }
35
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
36
+ out.push(full);
37
+ }
38
+ }
39
+ };
40
+ visit(root);
41
+ return out.sort();
42
+ };
43
+ const exists = (path) => {
44
+ try {
45
+ statSync(path);
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ };
52
+ const readCached = (source, sha) => {
53
+ const dir = join(repoCacheDir(source), sha, ROOT);
54
+ if (!exists(dir)) {
55
+ return undefined;
56
+ }
57
+ return walkMarkdown(dir).map((file) => ({
58
+ path: relative(dir, file).split(sep).join('/'),
59
+ content: readFileSync(file, 'utf8'),
60
+ }));
61
+ };
62
+ const writeCache = (source, sha, files) => {
63
+ const base = repoCacheDir(source);
64
+ const target = join(base, sha, ROOT);
65
+ for (const file of files) {
66
+ const full = join(target, file.path);
67
+ mkdirSync(dirname(full), { recursive: true });
68
+ writeFileSync(full, file.content);
69
+ }
70
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
71
+ if (entry.isDirectory() && entry.name !== sha) {
72
+ rmSync(join(base, entry.name), { recursive: true, force: true });
73
+ }
74
+ }
75
+ };
76
+ const headers = (source, accept) => ({
77
+ accept,
78
+ 'user-agent': 'oneentry-mcp-platform',
79
+ ...(source.token ? { authorization: `Bearer ${source.token}` } : {}),
80
+ });
81
+ const shaFailureHint = (status) => {
82
+ switch (status) {
83
+ case 403: {
84
+ return ' (rate limit — set ONEENTRY_GITHUB_TOKEN)';
85
+ }
86
+ case 404: {
87
+ return ' (no such repository or ref — check ONEENTRY_MCP_KNOWLEDGE_REPO, and set ONEENTRY_GITHUB_TOKEN if it is private)';
88
+ }
89
+ case 409: {
90
+ return ' (the repository is empty — it has no commits yet)';
91
+ }
92
+ default: {
93
+ return '';
94
+ }
95
+ }
96
+ };
97
+ const resolveSha = async (source) => {
98
+ const url = `https://api.github.com/repos/${source.repo}/commits/${encodeURIComponent(source.ref)}`;
99
+ const response = await fetch(url, {
100
+ headers: headers(source, 'application/vnd.github.sha'),
101
+ signal: AbortSignal.timeout(source.timeoutMs),
102
+ });
103
+ if (!response.ok) {
104
+ throw new Error(`GitHub answered ${String(response.status)} for ${source.repo}@${source.ref}` +
105
+ shaFailureHint(response.status));
106
+ }
107
+ const sha = (await response.text()).trim();
108
+ if (!/^[0-9a-f]{7,40}$/.test(sha)) {
109
+ throw new Error(`GitHub returned an unexpected commit id for ${source.repo}@${source.ref}`);
110
+ }
111
+ return sha;
112
+ };
113
+ const selectDocs = (entries) => {
114
+ const prefix = `${ROOT}/`;
115
+ return entries
116
+ .filter((entry) => entry.path.startsWith(prefix) &&
117
+ entry.path.endsWith('.md') &&
118
+ !entry.path.split('/').includes('..'))
119
+ .map((entry) => ({ path: entry.path.slice(prefix.length), content: entry.content }));
120
+ };
121
+ const download = async (source, sha) => {
122
+ const url = `https://codeload.github.com/${source.repo}/tar.gz/${sha}`;
123
+ const response = await fetch(url, {
124
+ headers: headers(source, 'application/octet-stream'),
125
+ signal: AbortSignal.timeout(source.timeoutMs),
126
+ });
127
+ if (!response.ok) {
128
+ throw new Error(`GitHub answered ${String(response.status)} for the ${source.repo} archive`);
129
+ }
130
+ const files = selectDocs(readTarGz(Buffer.from(await response.arrayBuffer())));
131
+ if (files.length === 0) {
132
+ throw new Error(`${source.repo}@${sha} has no ${ROOT}/**/*.md — wrong repository or layout?`);
133
+ }
134
+ return files;
135
+ };
136
+ export const fetchKnowledgeTree = async (source) => {
137
+ const state = readState(source);
138
+ const files = state && state.ref === source.ref ? readCached(source, state.sha) : undefined;
139
+ const cache = state && files ? { state, files } : undefined;
140
+ if (cache && (source.offline || Date.now() - cache.state.checkedAt < source.refreshTtlMs)) {
141
+ return { files: cache.files, sha: cache.state.sha, source: 'cache' };
142
+ }
143
+ if (source.offline) {
144
+ throw new Error(`offline: no cached copy of ${source.repo}@${source.ref}`);
145
+ }
146
+ let sha;
147
+ try {
148
+ sha = await resolveSha(source);
149
+ }
150
+ catch (error) {
151
+ if (cache) {
152
+ return { files: cache.files, sha: cache.state.sha, source: 'cache' };
153
+ }
154
+ throw error;
155
+ }
156
+ if (cache && cache.state.sha === sha) {
157
+ writeState(source, { ref: source.ref, sha, checkedAt: Date.now() });
158
+ return { files: cache.files, sha, source: 'cache' };
159
+ }
160
+ const downloaded = await download(source, sha);
161
+ writeCache(source, sha, downloaded);
162
+ writeState(source, { ref: source.ref, sha, checkedAt: Date.now() });
163
+ return { files: downloaded, sha, source: 'github' };
164
+ };
165
+ export const readLocalTree = (path) => {
166
+ const root = exists(join(path, ROOT)) ? join(path, ROOT) : path;
167
+ if (!exists(root)) {
168
+ throw new Error(`Knowledge path "${path}" does not exist`);
169
+ }
170
+ return walkMarkdown(root).map((file) => ({
171
+ path: relative(root, file).split(sep).join('/'),
172
+ content: readFileSync(file, 'utf8'),
173
+ }));
174
+ };
@@ -1,27 +1,26 @@
1
- import type { KnowledgePack } from './types.js';
2
- /** Каталог со собственными знаниями пакета (operating-rules.md и т.п.). */
3
- export declare const ownKnowledgeDir: () => string;
4
- /** Каталог со собранными данными пакета (knowledge.json, catalog.json). */
1
+ import { type KnowledgeFile } from './github.js';
2
+ import type { KnowledgeOrigin, KnowledgePack } from './types.js';
3
+ export interface KnowledgeConfig {
4
+ repo: string;
5
+ ref: string;
6
+ path?: string;
7
+ cacheDir: string;
8
+ refreshTtlMs: number;
9
+ offline: boolean;
10
+ token?: string;
11
+ timeoutMs: number;
12
+ }
13
+ export declare const seedDir: () => string;
5
14
  export declare const dataDir: () => string;
6
- /**
7
- * Собирает пакет знаний из чекаутов. Используется и сборкой снапшота,
8
- * и live-режимом, поэтому результат в обоих случаях идентичен по структуре.
9
- */
10
- export declare const buildPack: (params: {
11
- backPath?: string;
12
- frontPath?: string;
13
- source: KnowledgePack["source"];
15
+ export declare const docIdFromPath: (path: string) => string;
16
+ export declare const packFromFiles: (params: {
17
+ files: readonly KnowledgeFile[];
18
+ source: KnowledgeOrigin;
14
19
  builtAt: string;
20
+ repo?: string;
21
+ ref?: string;
22
+ sha?: string;
23
+ warning?: string;
15
24
  }) => KnowledgePack;
16
- /** Читает собранный снапшот из data/knowledge.json. */
17
- export declare const loadSnapshot: () => KnowledgePack;
18
- /**
19
- * Выбирает источник знаний: живой чекаут, если он указан, иначе снапшот пакета.
20
- * Live-режим нужен, чтобы правка документации в dnk-back действовала сразу,
21
- * без переиздания npm-пакета.
22
- */
23
- export declare const resolveKnowledge: (params: {
24
- backPath?: string;
25
- frontPath?: string;
26
- }) => KnowledgePack;
27
- //# sourceMappingURL=loader.d.ts.map
25
+ export declare const seedPack: (warning?: string) => KnowledgePack;
26
+ export declare const resolveKnowledge: (config: KnowledgeConfig) => Promise<KnowledgePack>;
@@ -1,125 +1,86 @@
1
- import { readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { join, relative, resolve, sep } from 'node:path';
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { chunkDocument } from './chunk.js';
5
- const BACK_SOURCES = [
6
- { repo: 'back', subPath: '.claude/docs', docPrefix: 'back/docs' },
7
- { repo: 'back', subPath: '.claude/tests', docPrefix: 'back/tests' },
8
- { repo: 'back', subPath: '.claude/rules', docPrefix: 'back/rules' },
9
- { repo: 'back', subPath: '.claude/fixes', docPrefix: 'back/fixes' },
10
- { repo: 'back', subPath: '.claude/CLAUDE.md', docPrefix: 'back', singleFile: true },
11
- ];
12
- const FRONT_SOURCES = [
13
- { repo: 'front', subPath: '.claude/docs', docPrefix: 'front/docs' },
14
- { repo: 'front', subPath: '.claude/rules', docPrefix: 'front/rules' },
15
- { repo: 'front', subPath: '.claude/fixes', docPrefix: 'front/fixes' },
16
- ];
17
- /** Рекурсивно собирает пути всех `*.md` внутри каталога. */
18
- const walkMarkdown = (root) => {
19
- const out = [];
20
- const visit = (dir) => {
21
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
22
- const full = join(dir, entry.name);
23
- if (entry.isDirectory()) {
24
- visit(full);
25
- }
26
- else if (entry.isFile() && entry.name.endsWith('.md')) {
27
- out.push(full);
28
- }
29
- }
30
- };
31
- visit(root);
32
- return out.sort();
33
- };
34
- const exists = (path) => {
35
- try {
36
- statSync(path);
37
- return true;
38
- }
39
- catch {
40
- return false;
41
- }
42
- };
43
- const collectSource = (repoRoot, spec) => {
44
- const target = resolve(repoRoot, spec.subPath);
5
+ import { fetchKnowledgeTree, readLocalTree } from './github.js';
6
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
7
+ export const seedDir = () => resolve(fileURLToPath(new URL('.', import.meta.url)), '../../knowledge');
8
+ export const dataDir = () => resolve(fileURLToPath(new URL('.', import.meta.url)), '../../data');
9
+ export const docIdFromPath = (path) => path.replace(/\.md$/, '').replace(/\/index$/, '');
10
+ export const packFromFiles = (params) => {
45
11
  const docs = [];
46
12
  const chunks = [];
47
- if (!exists(target)) {
48
- return { docs, chunks };
49
- }
50
- const files = spec.singleFile ? [target] : walkMarkdown(target);
51
- for (const file of files) {
52
- const rel = spec.singleFile
53
- ? file.split(sep).slice(-1).join('')
54
- : relative(target, file);
55
- const slug = rel.replace(/\.md$/, '').split(sep).join('/');
56
- const docId = spec.docPrefix ? `${spec.docPrefix}/${slug}` : slug;
13
+ for (const file of [...params.files].sort((a, b) => a.path.localeCompare(b.path))) {
14
+ const docId = docIdFromPath(file.path);
57
15
  const result = chunkDocument({
58
16
  docId,
59
- repo: spec.repo,
60
- path: relative(repoRoot, file).split(sep).join('/'),
61
- markdown: readFileSync(file, 'utf8'),
17
+ repo: docId.split('/')[0] ?? 'mcp',
18
+ path: `knowledge/${file.path}`,
19
+ markdown: file.content,
62
20
  });
63
21
  docs.push(result.doc);
64
22
  chunks.push(...result.chunks);
65
23
  }
66
- return { docs, chunks };
24
+ return {
25
+ version: 1,
26
+ builtAt: params.builtAt,
27
+ source: params.source,
28
+ ...(params.repo ? { repo: params.repo } : {}),
29
+ ...(params.ref ? { ref: params.ref } : {}),
30
+ ...(params.sha ? { sha: params.sha } : {}),
31
+ ...(params.warning ? { warning: params.warning } : {}),
32
+ docs,
33
+ chunks,
34
+ };
67
35
  };
68
- /** Каталог со собственными знаниями пакета (operating-rules.md и т.п.). */
69
- export const ownKnowledgeDir = () => resolve(fileURLToPath(new URL('.', import.meta.url)), '../../knowledge');
70
- /** Каталог со собранными данными пакета (knowledge.json, catalog.json). */
71
- export const dataDir = () => resolve(fileURLToPath(new URL('.', import.meta.url)), '../../data');
72
- /**
73
- * Собирает пакет знаний из чекаутов. Используется и сборкой снапшота,
74
- * и live-режимом, поэтому результат в обоих случаях идентичен по структуре.
75
- */
76
- export const buildPack = (params) => {
77
- const docs = [];
78
- const chunks = [];
79
- const own = collectSource(ownKnowledgeDir(), {
80
- repo: 'mcp',
81
- subPath: '.',
82
- docPrefix: 'mcp',
83
- });
84
- docs.push(...own.docs);
85
- chunks.push(...own.chunks);
86
- const add = (root, specs) => {
87
- if (!root) {
88
- return;
36
+ export const seedPack = (warning) => packFromFiles({
37
+ files: [
38
+ {
39
+ path: 'mcp/operating-rules.md',
40
+ content: readFileSync(resolve(seedDir(), 'operating-rules.md'), 'utf8'),
41
+ },
42
+ ],
43
+ source: 'seed',
44
+ builtAt: new Date().toISOString(),
45
+ ...(warning ? { warning } : {}),
46
+ });
47
+ export const resolveKnowledge = async (config) => {
48
+ const builtAt = new Date().toISOString();
49
+ if (config.path) {
50
+ try {
51
+ return packFromFiles({
52
+ files: readLocalTree(config.path),
53
+ source: 'local',
54
+ builtAt,
55
+ repo: config.path,
56
+ });
89
57
  }
90
- for (const spec of specs) {
91
- const result = collectSource(root, spec);
92
- docs.push(...result.docs);
93
- chunks.push(...result.chunks);
58
+ catch (error) {
59
+ throw new Error(`Local knowledge path "${config.path}" could not be read (${messageOf(error)}). ` +
60
+ 'Fix the path, or unset it to read the knowledge repository from GitHub.');
94
61
  }
95
- };
96
- add(params.backPath, BACK_SOURCES);
97
- add(params.frontPath, FRONT_SOURCES);
98
- return { version: 1, builtAt: params.builtAt, source: params.source, docs, chunks };
99
- };
100
- /** Читает собранный снапшот из data/knowledge.json. */
101
- export const loadSnapshot = () => {
102
- const file = resolve(dataDir(), 'knowledge.json');
103
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
104
- if (parsed.version !== 1) {
105
- throw new Error(`Unsupported knowledge pack version ${String(parsed.version)}`);
106
62
  }
107
- return parsed;
108
- };
109
- /**
110
- * Выбирает источник знаний: живой чекаут, если он указан, иначе снапшот пакета.
111
- * Live-режим нужен, чтобы правка документации в dnk-back действовала сразу,
112
- * без переиздания npm-пакета.
113
- */
114
- export const resolveKnowledge = (params) => {
115
- if (params.backPath) {
116
- return buildPack({
117
- backPath: params.backPath,
118
- frontPath: params.frontPath,
119
- source: 'live',
120
- builtAt: new Date().toISOString(),
63
+ try {
64
+ const tree = await fetchKnowledgeTree({
65
+ repo: config.repo,
66
+ ref: config.ref,
67
+ cacheDir: config.cacheDir,
68
+ refreshTtlMs: config.refreshTtlMs,
69
+ offline: config.offline,
70
+ ...(config.token ? { token: config.token } : {}),
71
+ timeoutMs: config.timeoutMs,
121
72
  });
73
+ return packFromFiles({
74
+ files: tree.files,
75
+ source: tree.source,
76
+ builtAt,
77
+ repo: config.repo,
78
+ ref: config.ref,
79
+ sha: tree.sha,
80
+ });
81
+ }
82
+ catch (error) {
83
+ return seedPack(`Knowledge repository ${config.repo}@${config.ref} is unavailable (${messageOf(error)}). ` +
84
+ 'Only the bundled operating rules are available — module reference docs will not be found.');
122
85
  }
123
- return loadSnapshot();
124
86
  };
125
- //# sourceMappingURL=loader.js.map
@@ -1,5 +1,4 @@
1
1
  import type { KnowledgeChunk, KnowledgeDoc, KnowledgePack } from './types.js';
2
- /** Найденный раздел с оценкой и вырезкой вокруг совпадения. */
3
2
  export interface KnowledgeHit {
4
3
  id: string;
5
4
  docId: string;
@@ -10,25 +9,15 @@ export interface KnowledgeHit {
10
9
  score: number;
11
10
  snippet: string;
12
11
  }
13
- /**
14
- * Поисковый индекс по разделам документации.
15
- * Хранит только идентификаторы в индексе, а тексты — в отдельной карте:
16
- * так `cms_docs_search` отдаёт вырезки, не поднимая в память дубли корпуса.
17
- */
12
+ export declare const meaningfulQuery: (query: string) => string;
18
13
  export declare class KnowledgeIndex {
19
14
  private readonly mini;
20
15
  private readonly byId;
21
16
  private readonly byDocId;
22
17
  readonly pack: KnowledgePack;
23
18
  constructor(pack: KnowledgePack);
24
- /** Разделы документа в порядке следования — для листания в cms_docs_read. */
25
19
  doc(docId: string): KnowledgeDoc | undefined;
26
20
  chunk(id: string): KnowledgeChunk | undefined;
27
21
  docs(): readonly KnowledgeDoc[];
28
- /**
29
- * Ищет разделы по запросу. Оценка minisearch домножается на веса источника и вида
30
- * документа, поэтому правила эксплуатации всегда всплывают выше истории фиксов.
31
- */
32
22
  search(query: string, limit?: number): KnowledgeHit[];
33
23
  }
34
- //# sourceMappingURL=search.d.ts.map
@@ -1,14 +1,24 @@
1
1
  import MiniSearch from 'minisearch';
2
- /**
3
- * Веса источников. `mcp` — собственные правила эксплуатации, они всегда должны
4
- * побеждать первичную документацию; `fixes` — история багов, полезна, но шумна,
5
- * поэтому опускается вниз, чтобы не вытеснять описание модуля.
6
- */
7
2
  const REPO_WEIGHT = { mcp: 1.6, back: 1, front: 0.9 };
8
- const KIND_WEIGHT = { docs: 1.15, tests: 1, rules: 1, fixes: 0.6 };
9
- const kindOf = (docId) => docId.split('/')[1] ?? 'docs';
3
+ const KIND_WEIGHT = {
4
+ docs: 1.15,
5
+ tests: 1,
6
+ rules: 1,
7
+ fixes: 0.6,
8
+ specs: 0.6,
9
+ };
10
+ const kindOf = (docId) => {
11
+ const parts = docId.split('/');
12
+ if (parts[2] === 'specs') {
13
+ return 'specs';
14
+ }
15
+ return parts[1] ?? 'docs';
16
+ };
10
17
  const SNIPPET_RADIUS = 180;
11
- /** Вырезка вокруг первого совпавшего терма; при отсутствии — начало раздела. */
18
+ export const meaningfulQuery = (query) => {
19
+ const kept = query.split(/\s+/).filter((word) => word.replace(/[^\p{L}\p{N}]/gu, '').length > 2);
20
+ return kept.length > 0 ? kept.join(' ') : query;
21
+ };
12
22
  const makeSnippet = (text, terms) => {
13
23
  const flat = text.replace(/\s+/g, ' ').trim();
14
24
  const lower = flat.toLowerCase();
@@ -26,11 +36,6 @@ const makeSnippet = (text, terms) => {
26
36
  const end = Math.min(flat.length, at + SNIPPET_RADIUS);
27
37
  return `${start > 0 ? '…' : ''}${flat.slice(start, end)}${end < flat.length ? '…' : ''}`;
28
38
  };
29
- /**
30
- * Поисковый индекс по разделам документации.
31
- * Хранит только идентификаторы в индексе, а тексты — в отдельной карте:
32
- * так `cms_docs_search` отдаёт вырезки, не поднимая в память дубли корпуса.
33
- */
34
39
  export class KnowledgeIndex {
35
40
  mini;
36
41
  byId;
@@ -53,7 +58,6 @@ export class KnowledgeIndex {
53
58
  });
54
59
  this.mini.addAll(pack.chunks);
55
60
  }
56
- /** Разделы документа в порядке следования — для листания в cms_docs_read. */
57
61
  doc(docId) {
58
62
  return this.byDocId.get(docId);
59
63
  }
@@ -63,13 +67,9 @@ export class KnowledgeIndex {
63
67
  docs() {
64
68
  return this.pack.docs;
65
69
  }
66
- /**
67
- * Ищет разделы по запросу. Оценка minisearch домножается на веса источника и вида
68
- * документа, поэтому правила эксплуатации всегда всплывают выше истории фиксов.
69
- */
70
70
  search(query, limit = 8) {
71
71
  const terms = query.split(/\s+/).filter((t) => t.length > 1);
72
- const raw = this.mini.search(query);
72
+ const raw = this.mini.search(meaningfulQuery(query));
73
73
  const hits = [];
74
74
  for (const result of raw) {
75
75
  const chunk = this.byId.get(String(result.id));
@@ -92,4 +92,3 @@ export class KnowledgeIndex {
92
92
  return hits.slice(0, limit);
93
93
  }
94
94
  }
95
- //# sourceMappingURL=search.js.map
@@ -0,0 +1,7 @@
1
+ export interface TarEntry {
2
+ path: string;
3
+ content: string;
4
+ }
5
+ export declare const readTar: (buffer: Buffer) => TarEntry[];
6
+ export declare const stripRoot: (entries: readonly TarEntry[]) => TarEntry[];
7
+ export declare const readTarGz: (buffer: Buffer) => TarEntry[];