@coralai/sps-cli 0.50.24 → 0.51.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.
Files changed (82) hide show
  1. package/README.md +18 -1
  2. package/dist/commands/projectInit.d.ts +15 -0
  3. package/dist/commands/projectInit.d.ts.map +1 -1
  4. package/dist/commands/projectInit.js +191 -3
  5. package/dist/commands/projectInit.js.map +1 -1
  6. package/dist/commands/wikiCommand.d.ts +77 -0
  7. package/dist/commands/wikiCommand.d.ts.map +1 -0
  8. package/dist/commands/wikiCommand.js +489 -0
  9. package/dist/commands/wikiCommand.js.map +1 -0
  10. package/dist/console/routes/projects.d.ts.map +1 -1
  11. package/dist/console/routes/projects.js +1 -0
  12. package/dist/console/routes/projects.js.map +1 -1
  13. package/dist/console-assets/assets/{index-BgOHCIG1.css → index-DlwaKe2l.css} +1 -1
  14. package/dist/console-assets/assets/{index-QBai48VV.js → index-Gjim492C.js} +1 -1
  15. package/dist/console-assets/index.html +2 -2
  16. package/dist/core/taskPrompts.d.ts +12 -0
  17. package/dist/core/taskPrompts.d.ts.map +1 -1
  18. package/dist/core/taskPrompts.js +14 -0
  19. package/dist/core/taskPrompts.js.map +1 -1
  20. package/dist/core/wiki/frontmatter.d.ts +55 -0
  21. package/dist/core/wiki/frontmatter.d.ts.map +1 -0
  22. package/dist/core/wiki/frontmatter.js +109 -0
  23. package/dist/core/wiki/frontmatter.js.map +1 -0
  24. package/dist/core/wiki/hot.d.ts +27 -0
  25. package/dist/core/wiki/hot.d.ts.map +1 -0
  26. package/dist/core/wiki/hot.js +124 -0
  27. package/dist/core/wiki/hot.js.map +1 -0
  28. package/dist/core/wiki/index-builder.d.ts +37 -0
  29. package/dist/core/wiki/index-builder.d.ts.map +1 -0
  30. package/dist/core/wiki/index-builder.js +130 -0
  31. package/dist/core/wiki/index-builder.js.map +1 -0
  32. package/dist/core/wiki/linter.d.ts +76 -0
  33. package/dist/core/wiki/linter.d.ts.map +1 -0
  34. package/dist/core/wiki/linter.js +280 -0
  35. package/dist/core/wiki/linter.js.map +1 -0
  36. package/dist/core/wiki/log.d.ts +24 -0
  37. package/dist/core/wiki/log.d.ts.map +1 -0
  38. package/dist/core/wiki/log.js +107 -0
  39. package/dist/core/wiki/log.js.map +1 -0
  40. package/dist/core/wiki/manifest.d.ts +59 -0
  41. package/dist/core/wiki/manifest.d.ts.map +1 -0
  42. package/dist/core/wiki/manifest.js +180 -0
  43. package/dist/core/wiki/manifest.js.map +1 -0
  44. package/dist/core/wiki/page.d.ts +72 -0
  45. package/dist/core/wiki/page.d.ts.map +1 -0
  46. package/dist/core/wiki/page.js +221 -0
  47. package/dist/core/wiki/page.js.map +1 -0
  48. package/dist/core/wiki/reader.d.ts +102 -0
  49. package/dist/core/wiki/reader.d.ts.map +1 -0
  50. package/dist/core/wiki/reader.js +225 -0
  51. package/dist/core/wiki/reader.js.map +1 -0
  52. package/dist/core/wiki/scaffold.d.ts +42 -0
  53. package/dist/core/wiki/scaffold.d.ts.map +1 -0
  54. package/dist/core/wiki/scaffold.js +223 -0
  55. package/dist/core/wiki/scaffold.js.map +1 -0
  56. package/dist/core/wiki/searcher.d.ts +73 -0
  57. package/dist/core/wiki/searcher.d.ts.map +1 -0
  58. package/dist/core/wiki/searcher.js +216 -0
  59. package/dist/core/wiki/searcher.js.map +1 -0
  60. package/dist/core/wiki/sources.d.ts +84 -0
  61. package/dist/core/wiki/sources.d.ts.map +1 -0
  62. package/dist/core/wiki/sources.js +261 -0
  63. package/dist/core/wiki/sources.js.map +1 -0
  64. package/dist/core/wiki/types.d.ts +904 -0
  65. package/dist/core/wiki/types.d.ts.map +1 -0
  66. package/dist/core/wiki/types.js +109 -0
  67. package/dist/core/wiki/types.js.map +1 -0
  68. package/dist/engines/StageEngine.d.ts +17 -1
  69. package/dist/engines/StageEngine.d.ts.map +1 -1
  70. package/dist/engines/StageEngine.js +85 -0
  71. package/dist/engines/StageEngine.js.map +1 -1
  72. package/dist/main.js +78 -1
  73. package/dist/main.js.map +1 -1
  74. package/dist/services/ProjectService.d.ts +2 -0
  75. package/dist/services/ProjectService.d.ts.map +1 -1
  76. package/dist/services/ProjectService.js.map +1 -1
  77. package/dist/shared/wikiPaths.d.ts +38 -0
  78. package/dist/shared/wikiPaths.d.ts.map +1 -0
  79. package/dist/shared/wikiPaths.js +89 -0
  80. package/dist/shared/wikiPaths.js.map +1 -0
  81. package/package.json +1 -1
  82. package/skills/wiki-update/SKILL.md +300 -0
@@ -0,0 +1,42 @@
1
+ export interface InitWikiOptions {
2
+ /** Project name used in templates */
3
+ readonly projectName: string;
4
+ /** ISO date for created/updated fields (default: today) */
5
+ readonly today?: string;
6
+ /** Skip writing .gitignore (default: false) */
7
+ readonly skipGitignore?: boolean;
8
+ }
9
+ export interface InitWikiReport {
10
+ /** Wiki dir absolute path */
11
+ readonly wikiDir: string;
12
+ /** Subdirs created (already-existing skipped) */
13
+ readonly created: string[];
14
+ /** Files written (already-existing skipped) */
15
+ readonly filesWritten: string[];
16
+ /** Files left untouched because they already existed */
17
+ readonly filesSkipped: string[];
18
+ /** Was .gitignore touched? */
19
+ readonly gitignoreUpdated: boolean;
20
+ }
21
+ /**
22
+ * Idempotently scaffold wiki/ in the given repo.
23
+ *
24
+ * Creates:
25
+ * - wiki/, wiki/{modules,concepts,decisions,lessons,sources}/, wiki/.raw/, wiki/_attachments/
26
+ * - wiki/WIKI.md, wiki/index.md, wiki/overview.md, wiki/.hot.md
27
+ * - .gitignore: appends per-instance drift entries if missing
28
+ *
29
+ * Does NOT write .manifest.json — that's owned by `sps wiki update` and is empty
30
+ * until first ingest.
31
+ *
32
+ * Re-runnable safely: existing files are preserved.
33
+ */
34
+ export declare function initWiki(repoDir: string, opts: InitWikiOptions): InitWikiReport;
35
+ /**
36
+ * Append wiki drift entries to .gitignore if missing.
37
+ *
38
+ * Returns true when at least one line was appended.
39
+ * Creates .gitignore if not present.
40
+ */
41
+ export declare function ensureGitignoreEntries(repoDir: string): boolean;
42
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/scaffold.ts"],"names":[],"mappings":"AAyIA,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,8BAA8B;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,cAAc,CAiD/E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAsC/D"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @module core/wiki/scaffold
3
+ * @description `sps wiki init` 的物理脚手架:建目录、写模板、安装 .gitignore 条目
4
+ *
5
+ * @layer core
6
+ *
7
+ * doc-28 §4 目录结构 + §3 三层架构(per-repo)的实施面。
8
+ *
9
+ * 设计原则:
10
+ * - **幂等**:重复跑不破坏现有内容
11
+ * - **不覆盖**:所有写入前先 existsSync 检查
12
+ * - **单向同步 .gitignore**:只 append 缺失行,不重排已有内容
13
+ *
14
+ * 失败模式:mkdir 失败 → throw(让命令层报错)。
15
+ */
16
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { resolve } from 'node:path';
18
+ import { wikiAttachmentsDir, wikiDir, wikiHotFile, wikiIndexFile, wikiMetaFile, wikiOverviewFile, wikiPageDir, wikiRawDir, } from '../../shared/wikiPaths.js';
19
+ // ─── Constants ────────────────────────────────────────────────────
20
+ const ALL_PAGE_TYPES = [
21
+ 'module',
22
+ 'concept',
23
+ 'decision',
24
+ 'lesson',
25
+ 'source',
26
+ ];
27
+ /** Lines that wiki ownership requires in .gitignore */
28
+ const GITIGNORE_LINES = [
29
+ '# Wiki per-instance drift (added by sps wiki init)',
30
+ 'wiki/.hot.md',
31
+ 'wiki/.log.md',
32
+ 'wiki/.manifest.json',
33
+ ];
34
+ /**
35
+ * Default WIKI.md template. Contains schema version + sources config.
36
+ * Worker reads `sources:` to know what to ingest.
37
+ */
38
+ function wikiMetaTemplate(projectName, today) {
39
+ return `---
40
+ type: meta
41
+ title: ${projectName} Wiki
42
+ version: 1
43
+ created: ${today}
44
+ updated: ${today}
45
+ sources:
46
+ code:
47
+ - "src/**/*.ts"
48
+ doc:
49
+ - "docs/**/*.md"
50
+ - "README.md"
51
+ raw:
52
+ - "wiki/.raw/**/*"
53
+ ---
54
+
55
+ # ${projectName} Wiki
56
+
57
+ Project knowledge base. See [doc-28](../docs/design/28-wiki-system.md) for design.
58
+
59
+ ## Page Types
60
+
61
+ - **modules/** — Code modules / services (auto-derived from \`src/\`)
62
+ - **concepts/** — Domain concepts and patterns
63
+ - **decisions/** — Architecture decisions
64
+ - **lessons/** — Bug post-mortems and gotchas
65
+ - **sources/** — External references (PDFs, articles, transcripts)
66
+
67
+ ## Workflow
68
+
69
+ \`\`\`bash
70
+ sps wiki update <project> # ingest source diff via Worker + skill
71
+ sps wiki read <project> "query" # 5-layer retrieval for prompt injection
72
+ \`\`\`
73
+ `;
74
+ }
75
+ const INDEX_PLACEHOLDER = `---
76
+ type: meta
77
+ title: Wiki Index
78
+ updated: 1970-01-01T00:00:00Z
79
+ ---
80
+
81
+ # Wiki Index
82
+
83
+ (empty — run \`sps wiki update\` to ingest sources)
84
+ `;
85
+ const OVERVIEW_TEMPLATE = (projectName, today) => `---
86
+ type: meta
87
+ title: ${projectName} Overview
88
+ created: ${today}
89
+ updated: ${today}
90
+ ---
91
+
92
+ # ${projectName} Overview
93
+
94
+ (autogenerated summary — first \`sps wiki update\` will fill this in)
95
+ `;
96
+ const HOT_PLACEHOLDER = `---
97
+ type: meta
98
+ title: Hot Cache
99
+ updated: 1970-01-01T00:00:00Z
100
+ ---
101
+
102
+ # Recent Context
103
+
104
+ ## Last Updated
105
+ (尚无活动。第一次 \`sps wiki update\` 或卡片完成后会自动填充。)
106
+
107
+ ## Key Recent Facts
108
+ (none yet)
109
+
110
+ ## Recent Changes
111
+ (none yet)
112
+
113
+ ## Active Threads
114
+ (none yet)
115
+ `;
116
+ /**
117
+ * Idempotently scaffold wiki/ in the given repo.
118
+ *
119
+ * Creates:
120
+ * - wiki/, wiki/{modules,concepts,decisions,lessons,sources}/, wiki/.raw/, wiki/_attachments/
121
+ * - wiki/WIKI.md, wiki/index.md, wiki/overview.md, wiki/.hot.md
122
+ * - .gitignore: appends per-instance drift entries if missing
123
+ *
124
+ * Does NOT write .manifest.json — that's owned by `sps wiki update` and is empty
125
+ * until first ingest.
126
+ *
127
+ * Re-runnable safely: existing files are preserved.
128
+ */
129
+ export function initWiki(repoDir, opts) {
130
+ const today = opts.today ?? new Date().toISOString().slice(0, 10);
131
+ const created = [];
132
+ const filesWritten = [];
133
+ const filesSkipped = [];
134
+ // 1) Subdirs
135
+ const dirs = [
136
+ wikiDir(repoDir),
137
+ ...ALL_PAGE_TYPES.map((t) => wikiPageDir(repoDir, t)),
138
+ wikiRawDir(repoDir),
139
+ wikiAttachmentsDir(repoDir),
140
+ ];
141
+ for (const d of dirs) {
142
+ if (!existsSync(d)) {
143
+ mkdirSync(d, { recursive: true });
144
+ created.push(d);
145
+ }
146
+ }
147
+ // 2) Templated files (don't overwrite existing user content)
148
+ const templated = [
149
+ [wikiMetaFile(repoDir), wikiMetaTemplate(opts.projectName, today)],
150
+ [wikiIndexFile(repoDir), INDEX_PLACEHOLDER],
151
+ [wikiOverviewFile(repoDir), OVERVIEW_TEMPLATE(opts.projectName, today)],
152
+ [wikiHotFile(repoDir), HOT_PLACEHOLDER],
153
+ ];
154
+ for (const [path, body] of templated) {
155
+ if (existsSync(path)) {
156
+ filesSkipped.push(path);
157
+ }
158
+ else {
159
+ writeFileSync(path, body, { encoding: 'utf-8', mode: 0o644 });
160
+ filesWritten.push(path);
161
+ }
162
+ }
163
+ // 3) .gitignore — only append missing lines
164
+ let gitignoreUpdated = false;
165
+ if (!opts.skipGitignore) {
166
+ gitignoreUpdated = ensureGitignoreEntries(repoDir);
167
+ }
168
+ return {
169
+ wikiDir: wikiDir(repoDir),
170
+ created,
171
+ filesWritten,
172
+ filesSkipped,
173
+ gitignoreUpdated,
174
+ };
175
+ }
176
+ /**
177
+ * Append wiki drift entries to .gitignore if missing.
178
+ *
179
+ * Returns true when at least one line was appended.
180
+ * Creates .gitignore if not present.
181
+ */
182
+ export function ensureGitignoreEntries(repoDir) {
183
+ const gitignorePath = resolve(repoDir, '.gitignore');
184
+ let existing = '';
185
+ if (existsSync(gitignorePath)) {
186
+ existing = readFileSync(gitignorePath, 'utf-8');
187
+ }
188
+ const existingLines = new Set(existing.split('\n').map((l) => l.trim()));
189
+ const toAppend = [];
190
+ for (const line of GITIGNORE_LINES) {
191
+ // skip comment header if any drift line already present (we don't want to
192
+ // re-add the comment if user organized their .gitignore)
193
+ if (line.startsWith('#'))
194
+ continue;
195
+ if (!existingLines.has(line)) {
196
+ toAppend.push(line);
197
+ }
198
+ }
199
+ if (toAppend.length === 0)
200
+ return false;
201
+ // If .gitignore is brand new or empty, include the comment header.
202
+ const block = [];
203
+ const needsHeader = !existing.includes('# Wiki per-instance drift');
204
+ if (existing.length > 0 && !existing.endsWith('\n'))
205
+ block.push('');
206
+ if (existing.length > 0)
207
+ block.push('');
208
+ if (needsHeader)
209
+ block.push(GITIGNORE_LINES[0]);
210
+ block.push(...toAppend);
211
+ block.push(''); // trailing newline
212
+ if (existsSync(gitignorePath)) {
213
+ appendFileSync(gitignorePath, block.join('\n'), { encoding: 'utf-8' });
214
+ }
215
+ else {
216
+ writeFileSync(gitignorePath, block.join('\n').replace(/^\n+/, ''), {
217
+ encoding: 'utf-8',
218
+ mode: 0o644,
219
+ });
220
+ }
221
+ return true;
222
+ }
223
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/core/wiki/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @module core/wiki/searcher
3
+ * @description Wiki BM25F 全文检索(field-weighted Best Matching 25)
4
+ *
5
+ * @layer core
6
+ *
7
+ * 实现选择:
8
+ * - **不**用 lunr.js / minisearch / fuse.js —— 多一个 50KB 依赖不值得
9
+ * - **不**做词干提取(stemming)—— SPS 是技术文档库,词形变化少;stemming
10
+ * 反而把 "Pipeline" 和 "PipelineService" 合并成同一 token,丢辨别力
11
+ * - **不**做向量 embedding —— v0 BM25 够用;v1 视召回率决定
12
+ *
13
+ * BM25F 通过 field tiling 实现:title 3x / tags 2x / tldr 2x / body 1x
14
+ * 加权方式 = 该 field 内 token 重复 n 次(等价于 BM25F 的 boosting)。
15
+ *
16
+ * 中文支持:ASCII 按词切;中文按字切(朴素 unigram)。够用且无外部依赖。
17
+ *
18
+ * 数据规模:项目 wiki 一般 < 1000 page,~MB 级 corpus。in-memory 索引
19
+ * 占用几 MB,构建 < 100ms,查询 < 10ms。不需要持久化(每次进程启动重建)。
20
+ */
21
+ import type { Page, PageType } from './types.js';
22
+ export interface BM25Options {
23
+ /** 词频饱和参数;标准值 1.2-2.0 */
24
+ k1?: number;
25
+ /** 长度归一化;0=关 / 0.75=标准 / 1=最大归一 */
26
+ b?: number;
27
+ /** 各字段权重 */
28
+ fieldWeights?: {
29
+ title?: number;
30
+ tags?: number;
31
+ tldr?: number;
32
+ body?: number;
33
+ };
34
+ }
35
+ export declare function tokenize(text: string): string[];
36
+ export interface IndexedDoc {
37
+ readonly pageId: string;
38
+ readonly title: string;
39
+ readonly tags: readonly string[];
40
+ /** 第一段(## TL;DR 之后到下个 ## 之前);用作字段权重 + 返回值预览 */
41
+ readonly tldr: string;
42
+ /** Body 剩余部分(除 TL;DR 之外) */
43
+ readonly body: string;
44
+ readonly type: PageType;
45
+ }
46
+ /**
47
+ * 从 Page 对象抽 IndexedDoc。
48
+ * TL;DR 提取规则:找 `## TL;DR\n...\n##` 之间的内容;找不到取 body 前 200 字符。
49
+ */
50
+ export declare function pageToIndexed(page: Page): IndexedDoc;
51
+ export declare function extractTLDR(body: string): string;
52
+ export interface SearchResult {
53
+ readonly pageId: string;
54
+ readonly score: number;
55
+ }
56
+ export declare class WikiSearcher {
57
+ readonly docs: readonly IndexedDoc[];
58
+ private readonly opts;
59
+ private readonly postings;
60
+ private readonly docLengths;
61
+ private readonly avgDocLength;
62
+ constructor(docs: readonly IndexedDoc[], opts?: BM25Options);
63
+ /**
64
+ * 全字段加权的 token stream。同一个 token 在 title 出现一次 = title weight 个副本。
65
+ */
66
+ private tokenizeDoc;
67
+ search(query: string, limit?: number): SearchResult[];
68
+ /** 按 tag 集合过滤(OR 语义)—— 用于 reader.ts 的 skill-match layer */
69
+ searchByTags(tags: readonly string[], limit?: number): SearchResult[];
70
+ /** 按 type 列出(不打分;reader.ts 排序用) */
71
+ byType(type: PageType): IndexedDoc[];
72
+ }
73
+ //# sourceMappingURL=searcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searcher.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/searcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIjD,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,YAAY;IACZ,YAAY,CAAC,EAAE;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAqED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW/C;AAID,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CASpD;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhD;AAID,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAID,qBAAa,YAAY;aASL,IAAI,EAAE,SAAS,UAAU,EAAE;IAR7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAEnB;IACF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAGpB,IAAI,EAAE,SAAS,UAAU,EAAE,EAC3C,IAAI,GAAE,WAAgB;IAqBxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,YAAY,EAAE;IA2BjD,2DAA2D;IAC3D,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,SAAK,GAAG,YAAY,EAAE;IAejE,mCAAmC;IACnC,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,EAAE;CAGrC"}
@@ -0,0 +1,216 @@
1
+ const DEFAULT_OPTS = {
2
+ k1: 1.5,
3
+ b: 0.75,
4
+ fieldWeights: { title: 3, tags: 2, tldr: 2, body: 1 },
5
+ };
6
+ // ─── Tokenization ─────────────────────────────────────────────────
7
+ /**
8
+ * 把文本切成 token:
9
+ * - ASCII 单词组(含数字、`_`、`-`),≥ 2 字符
10
+ * - 单个汉字
11
+ * - lowercase 归一化
12
+ * - 去 stop words
13
+ *
14
+ * **不**做 stemming(pipelining → pipeline)—— SPS 文档技术词多,stemming
15
+ * 引入更多噪声而不是信号。
16
+ */
17
+ const STOP_WORDS = new Set([
18
+ // English stop words
19
+ 'a',
20
+ 'an',
21
+ 'and',
22
+ 'are',
23
+ 'as',
24
+ 'at',
25
+ 'be',
26
+ 'but',
27
+ 'by',
28
+ 'do',
29
+ 'for',
30
+ 'has',
31
+ 'have',
32
+ 'in',
33
+ 'is',
34
+ 'it',
35
+ 'its',
36
+ 'of',
37
+ 'on',
38
+ 'or',
39
+ 'that',
40
+ 'the',
41
+ 'this',
42
+ 'to',
43
+ 'was',
44
+ 'were',
45
+ 'will',
46
+ 'with',
47
+ // Chinese stop words (常见高频虚词)
48
+ '的',
49
+ '了',
50
+ '是',
51
+ '和',
52
+ '或',
53
+ '在',
54
+ '有',
55
+ '我',
56
+ '你',
57
+ '他',
58
+ '它',
59
+ '这',
60
+ '那',
61
+ '与',
62
+ ]);
63
+ const TOKEN_RE = /[a-z0-9_-]{2,}|[一-鿿]/g;
64
+ export function tokenize(text) {
65
+ if (!text)
66
+ return [];
67
+ const lower = text.toLowerCase();
68
+ const tokens = [];
69
+ const matches = lower.matchAll(TOKEN_RE);
70
+ for (const m of matches) {
71
+ const t = m[0];
72
+ if (STOP_WORDS.has(t))
73
+ continue;
74
+ tokens.push(t);
75
+ }
76
+ return tokens;
77
+ }
78
+ /**
79
+ * 从 Page 对象抽 IndexedDoc。
80
+ * TL;DR 提取规则:找 `## TL;DR\n...\n##` 之间的内容;找不到取 body 前 200 字符。
81
+ */
82
+ export function pageToIndexed(page) {
83
+ return {
84
+ pageId: page.pageId,
85
+ title: page.frontmatter.title,
86
+ tags: page.frontmatter.tags,
87
+ tldr: extractTLDR(page.body),
88
+ body: page.body,
89
+ type: page.frontmatter.type,
90
+ };
91
+ }
92
+ const TLDR_RE = /^##\s+TL;DR\s*\r?\n([\s\S]*?)(?=\r?\n##\s+|\s*$)/m;
93
+ export function extractTLDR(body) {
94
+ const m = body.match(TLDR_RE);
95
+ if (m)
96
+ return m[1].trim();
97
+ // Fallback:第一个段落或前 200 字
98
+ const firstPara = body.split(/\n\s*\n/)[0]?.trim() ?? '';
99
+ return firstPara.slice(0, 200);
100
+ }
101
+ // ─── BM25F searcher ───────────────────────────────────────────────
102
+ export class WikiSearcher {
103
+ docs;
104
+ opts;
105
+ postings = new Map();
106
+ docLengths = [];
107
+ avgDocLength;
108
+ constructor(docs, opts = {}) {
109
+ this.docs = docs;
110
+ this.opts = mergeOpts(opts);
111
+ let totalLen = 0;
112
+ for (let i = 0; i < docs.length; i++) {
113
+ const tokens = this.tokenizeDoc(docs[i]);
114
+ const tf = countTF(tokens);
115
+ for (const [term, count] of tf) {
116
+ let pl = this.postings.get(term);
117
+ if (!pl) {
118
+ pl = new Map();
119
+ this.postings.set(term, pl);
120
+ }
121
+ pl.set(i, count);
122
+ }
123
+ this.docLengths[i] = tokens.length;
124
+ totalLen += tokens.length;
125
+ }
126
+ this.avgDocLength = docs.length > 0 ? totalLen / docs.length : 1;
127
+ }
128
+ /**
129
+ * 全字段加权的 token stream。同一个 token 在 title 出现一次 = title weight 个副本。
130
+ */
131
+ tokenizeDoc(doc) {
132
+ const w = this.opts.fieldWeights;
133
+ return [
134
+ ...repeat(tokenize(doc.title), w.title),
135
+ ...repeat(tokenize(doc.tags.join(' ')), w.tags),
136
+ ...repeat(tokenize(doc.tldr), w.tldr),
137
+ ...repeat(tokenize(doc.body), w.body),
138
+ ];
139
+ }
140
+ search(query, limit = 10) {
141
+ const queryTerms = unique(tokenize(query));
142
+ if (queryTerms.length === 0 || this.docs.length === 0)
143
+ return [];
144
+ const scores = new Map();
145
+ const N = this.docs.length;
146
+ for (const term of queryTerms) {
147
+ const postings = this.postings.get(term);
148
+ if (!postings)
149
+ continue;
150
+ const df = postings.size;
151
+ const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
152
+ for (const [docIdx, tf] of postings) {
153
+ const docLen = this.docLengths[docIdx];
154
+ const norm = 1 - this.opts.b + this.opts.b * (docLen / this.avgDocLength);
155
+ const tfPart = (tf * (this.opts.k1 + 1)) / (tf + this.opts.k1 * norm);
156
+ const contribution = idf * tfPart;
157
+ scores.set(docIdx, (scores.get(docIdx) ?? 0) + contribution);
158
+ }
159
+ }
160
+ return [...scores.entries()]
161
+ .sort((a, b) => b[1] - a[1])
162
+ .slice(0, limit)
163
+ .map(([idx, score]) => ({ pageId: this.docs[idx].pageId, score }));
164
+ }
165
+ /** 按 tag 集合过滤(OR 语义)—— 用于 reader.ts 的 skill-match layer */
166
+ searchByTags(tags, limit = 10) {
167
+ if (tags.length === 0)
168
+ return [];
169
+ const tagSet = new Set(tags.map((t) => t.toLowerCase()));
170
+ const matches = [];
171
+ for (let i = 0; i < this.docs.length; i++) {
172
+ const doc = this.docs[i];
173
+ const overlap = doc.tags.filter((t) => tagSet.has(t.toLowerCase())).length;
174
+ if (overlap > 0) {
175
+ // 命中 tag 多 = 分数高
176
+ matches.push({ pageId: doc.pageId, score: overlap });
177
+ }
178
+ }
179
+ return matches.sort((a, b) => b.score - a.score).slice(0, limit);
180
+ }
181
+ /** 按 type 列出(不打分;reader.ts 排序用) */
182
+ byType(type) {
183
+ return this.docs.filter((d) => d.type === type);
184
+ }
185
+ }
186
+ // ─── Pure helpers ─────────────────────────────────────────────────
187
+ function countTF(tokens) {
188
+ const m = new Map();
189
+ for (const t of tokens)
190
+ m.set(t, (m.get(t) ?? 0) + 1);
191
+ return m;
192
+ }
193
+ function repeat(arr, n) {
194
+ if (n <= 1)
195
+ return arr.slice();
196
+ const out = [];
197
+ for (let i = 0; i < n; i++)
198
+ out.push(...arr);
199
+ return out;
200
+ }
201
+ function unique(arr) {
202
+ return [...new Set(arr)];
203
+ }
204
+ function mergeOpts(input) {
205
+ return {
206
+ k1: input.k1 ?? DEFAULT_OPTS.k1,
207
+ b: input.b ?? DEFAULT_OPTS.b,
208
+ fieldWeights: {
209
+ title: input.fieldWeights?.title ?? DEFAULT_OPTS.fieldWeights.title,
210
+ tags: input.fieldWeights?.tags ?? DEFAULT_OPTS.fieldWeights.tags,
211
+ tldr: input.fieldWeights?.tldr ?? DEFAULT_OPTS.fieldWeights.tldr,
212
+ body: input.fieldWeights?.body ?? DEFAULT_OPTS.fieldWeights.body,
213
+ },
214
+ };
215
+ }
216
+ //# sourceMappingURL=searcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searcher.js","sourceRoot":"","sources":["../../../src/core/wiki/searcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import type { Manifest, SourceDiff } from './types.js';
2
+ /** WIKI.md sources 字段定义的 4 个 bucket */
3
+ export type SourceCategory = 'code' | 'doc' | 'raw' | 'data';
4
+ export interface DiscoveredSource {
5
+ /** Path relative to repoDir, posix-style */
6
+ readonly path: string;
7
+ /** Which sources bucket the file belongs to */
8
+ readonly category: SourceCategory;
9
+ /** sha256 hex; computed lazily by callers (use hashSource) */
10
+ readonly hash?: string;
11
+ }
12
+ export interface SourcesConfig {
13
+ /** Globs per bucket; absent bucket = empty array */
14
+ readonly code: readonly string[];
15
+ readonly doc: readonly string[];
16
+ readonly raw: readonly string[];
17
+ readonly data: readonly string[];
18
+ }
19
+ /**
20
+ * 从 WIKI.md 顶部 frontmatter 抽 sources 配置。
21
+ *
22
+ * 容错:
23
+ * - WIKI.md 不存在 → 返默认(空配置)
24
+ * - frontmatter 缺失 / sources 字段缺失 → 返默认
25
+ * - 类型不对(非数组 / 非字符串)→ 跳过该 bucket
26
+ */
27
+ export declare function readSourcesConfig(repoDir: string): SourcesConfig;
28
+ interface ParsedPattern {
29
+ /** Anchor directory relative to repoDir (no trailing slash) */
30
+ readonly base: string;
31
+ /** Should match recursively? (true if pattern contains ** ) */
32
+ readonly recursive: boolean;
33
+ /** Optional file extension filter (e.g. ".ts"); '' for all files */
34
+ readonly extension: string;
35
+ /** Original pattern (for debug) */
36
+ readonly raw: string;
37
+ }
38
+ /**
39
+ * Parse a simple glob pattern into base + recursion + extension filter.
40
+ *
41
+ * Examples:
42
+ * "src/**\/*.ts" → base=src, recursive=true, ext=.ts
43
+ * "src/**" → base=src, recursive=true, ext=''
44
+ * "docs/*.md" → base=docs, recursive=false, ext=.md
45
+ * "README.md" → base=., recursive=false, ext=.md (treated as flat single)
46
+ *
47
+ * Unsupported patterns (e.g. with `{}`) are passed through but won't match anything.
48
+ */
49
+ export declare function parsePattern(pattern: string): ParsedPattern;
50
+ /**
51
+ * Walk filesystem from `repoDir/<base>` and collect files that match the pattern.
52
+ *
53
+ * - Skips dotfiles starting with `.` UNLESS the base itself starts with `.`
54
+ * (e.g. wiki/.raw/** — user wants those).
55
+ * - Always skips node_modules / .git anywhere in the tree.
56
+ *
57
+ * Returns paths relative to repoDir, posix-normalized.
58
+ */
59
+ export declare function expandPattern(repoDir: string, pattern: string): string[];
60
+ export interface DiscoverResult {
61
+ /** Discovered sources with hashes */
62
+ readonly sources: readonly DiscoveredSource[];
63
+ /** Patterns that returned 0 files (warning candidates) */
64
+ readonly emptyPatterns: readonly string[];
65
+ }
66
+ /**
67
+ * Discover all sources from WIKI.md config + hash them.
68
+ *
69
+ * Buckets are merged; if the same path is matched by multiple buckets, the first
70
+ * winning bucket (in code → doc → data → raw order) is used.
71
+ *
72
+ * Caller is responsible for I/O fault tolerance — this throws on individual hash
73
+ * failures only via `tryHashFile` semantics (skip).
74
+ */
75
+ export declare function discoverSources(repoDir: string): DiscoverResult;
76
+ /**
77
+ * Compute diff between current discovered sources and saved manifest.
78
+ *
79
+ * Wraps diffSources() with a typed input so callers don't have to remember
80
+ * the DiffInputSource shape.
81
+ */
82
+ export declare function diffAgainstManifest(sources: readonly DiscoveredSource[], manifest: Manifest): SourceDiff;
83
+ export {};
84
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/sources.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIvD,uCAAuC;AACvC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAID;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CA2BhE;AASD,UAAU,aAAa;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAgC3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAsCxE;AAqDD,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,0DAA0D;IAC1D,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAuC/D;AAID;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,QAAQ,EAAE,QAAQ,GACjB,UAAU,CAKZ"}