@coralai/sps-cli 0.50.24 → 0.51.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/commands/projectInit.d.ts +15 -0
- package/dist/commands/projectInit.d.ts.map +1 -1
- package/dist/commands/projectInit.js +191 -3
- package/dist/commands/projectInit.js.map +1 -1
- package/dist/commands/wikiCommand.d.ts +77 -0
- package/dist/commands/wikiCommand.d.ts.map +1 -0
- package/dist/commands/wikiCommand.js +489 -0
- package/dist/commands/wikiCommand.js.map +1 -0
- package/dist/console/routes/projects.d.ts.map +1 -1
- package/dist/console/routes/projects.js +1 -0
- package/dist/console/routes/projects.js.map +1 -1
- package/dist/console-assets/assets/index-DRhdpvew.css +10 -0
- package/dist/console-assets/assets/{index-QBai48VV.js → index-WUGCBcyb.js} +3 -3
- package/dist/console-assets/index.html +2 -2
- package/dist/core/taskPrompts.d.ts +12 -0
- package/dist/core/taskPrompts.d.ts.map +1 -1
- package/dist/core/taskPrompts.js +14 -0
- package/dist/core/taskPrompts.js.map +1 -1
- package/dist/core/wiki/frontmatter.d.ts +55 -0
- package/dist/core/wiki/frontmatter.d.ts.map +1 -0
- package/dist/core/wiki/frontmatter.js +109 -0
- package/dist/core/wiki/frontmatter.js.map +1 -0
- package/dist/core/wiki/hot.d.ts +27 -0
- package/dist/core/wiki/hot.d.ts.map +1 -0
- package/dist/core/wiki/hot.js +124 -0
- package/dist/core/wiki/hot.js.map +1 -0
- package/dist/core/wiki/index-builder.d.ts +37 -0
- package/dist/core/wiki/index-builder.d.ts.map +1 -0
- package/dist/core/wiki/index-builder.js +130 -0
- package/dist/core/wiki/index-builder.js.map +1 -0
- package/dist/core/wiki/linter.d.ts +76 -0
- package/dist/core/wiki/linter.d.ts.map +1 -0
- package/dist/core/wiki/linter.js +280 -0
- package/dist/core/wiki/linter.js.map +1 -0
- package/dist/core/wiki/log.d.ts +24 -0
- package/dist/core/wiki/log.d.ts.map +1 -0
- package/dist/core/wiki/log.js +107 -0
- package/dist/core/wiki/log.js.map +1 -0
- package/dist/core/wiki/manifest.d.ts +59 -0
- package/dist/core/wiki/manifest.d.ts.map +1 -0
- package/dist/core/wiki/manifest.js +180 -0
- package/dist/core/wiki/manifest.js.map +1 -0
- package/dist/core/wiki/page.d.ts +72 -0
- package/dist/core/wiki/page.d.ts.map +1 -0
- package/dist/core/wiki/page.js +221 -0
- package/dist/core/wiki/page.js.map +1 -0
- package/dist/core/wiki/reader.d.ts +102 -0
- package/dist/core/wiki/reader.d.ts.map +1 -0
- package/dist/core/wiki/reader.js +225 -0
- package/dist/core/wiki/reader.js.map +1 -0
- package/dist/core/wiki/scaffold.d.ts +42 -0
- package/dist/core/wiki/scaffold.d.ts.map +1 -0
- package/dist/core/wiki/scaffold.js +223 -0
- package/dist/core/wiki/scaffold.js.map +1 -0
- package/dist/core/wiki/searcher.d.ts +73 -0
- package/dist/core/wiki/searcher.d.ts.map +1 -0
- package/dist/core/wiki/searcher.js +216 -0
- package/dist/core/wiki/searcher.js.map +1 -0
- package/dist/core/wiki/sources.d.ts +84 -0
- package/dist/core/wiki/sources.d.ts.map +1 -0
- package/dist/core/wiki/sources.js +261 -0
- package/dist/core/wiki/sources.js.map +1 -0
- package/dist/core/wiki/types.d.ts +904 -0
- package/dist/core/wiki/types.d.ts.map +1 -0
- package/dist/core/wiki/types.js +109 -0
- package/dist/core/wiki/types.js.map +1 -0
- package/dist/engines/StageEngine.d.ts +17 -1
- package/dist/engines/StageEngine.d.ts.map +1 -1
- package/dist/engines/StageEngine.js +85 -0
- package/dist/engines/StageEngine.js.map +1 -1
- package/dist/main.js +78 -1
- package/dist/main.js.map +1 -1
- package/dist/services/ProjectService.d.ts +2 -0
- package/dist/services/ProjectService.d.ts.map +1 -1
- package/dist/services/ProjectService.js.map +1 -1
- package/dist/shared/wikiPaths.d.ts +38 -0
- package/dist/shared/wikiPaths.d.ts.map +1 -0
- package/dist/shared/wikiPaths.js +89 -0
- package/dist/shared/wikiPaths.js.map +1 -0
- package/package.json +1 -1
- package/skills/wiki-update/SKILL.md +300 -0
- package/dist/console-assets/assets/index-BgOHCIG1.css +0 -10
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/wiki/reader
|
|
3
|
+
* @description wikiRead():5 层确定性检索 + 类型优先级 + token 预算
|
|
4
|
+
*
|
|
5
|
+
* @layer core
|
|
6
|
+
*
|
|
7
|
+
* doc-28 §10 Wiki 读取原则的代码实现。**这是 Worker prompt 注入的入口**。
|
|
8
|
+
*
|
|
9
|
+
* 5 层叠加:
|
|
10
|
+
* L1 永远 — hot.md 全文 ~500 字
|
|
11
|
+
* L2 永远 — index.md 节选 top-N 行 ~500 字
|
|
12
|
+
* L3 优先 — pinned wiki_pages(card frontmatter) ~50×N 字
|
|
13
|
+
* L4 按 skill — 卡 skills ∩ 页 tags → top-3 ~50×3 字
|
|
14
|
+
* L5 按关键词 — BM25(card.title + card.desc)→top-3 ~50×3 字
|
|
15
|
+
*
|
|
16
|
+
* 优先级排序(命中多页):
|
|
17
|
+
* lesson = 3 / decision = 3 / concept = 2 / module = 1 / source = 1
|
|
18
|
+
* stale page → 跳过(status=stale 或 mtime 太老)
|
|
19
|
+
*
|
|
20
|
+
* Token 预算硬上限 1500 字(~2000 token),超出砍 L5 keyword 命中。
|
|
21
|
+
*
|
|
22
|
+
* 设计原则(Karpathy):
|
|
23
|
+
* - **确定性**——同输入同输出,纯函数(除文件 I/O)
|
|
24
|
+
* - **Push 而非 Pull**——Worker 不需要学怎么查,结果已经摆好
|
|
25
|
+
* - **TL;DR 而非全文**——让 Worker 自己决定要不要 Read 完整 page
|
|
26
|
+
*/
|
|
27
|
+
import { readHot } from './hot.js';
|
|
28
|
+
import { readIndexSummary } from './index-builder.js';
|
|
29
|
+
import { getPageById, listValidPages } from './page.js';
|
|
30
|
+
import { extractTLDR, WikiSearcher } from './searcher.js';
|
|
31
|
+
const DEFAULT_OPTS = {
|
|
32
|
+
indexLines: 30,
|
|
33
|
+
skillTopN: 3,
|
|
34
|
+
keywordTopN: 3,
|
|
35
|
+
budgetTokens: 2000,
|
|
36
|
+
};
|
|
37
|
+
// 类型优先级(数字越大越靠前)
|
|
38
|
+
const TYPE_PRIORITY = {
|
|
39
|
+
lesson: 3,
|
|
40
|
+
decision: 3,
|
|
41
|
+
concept: 2,
|
|
42
|
+
module: 1,
|
|
43
|
+
source: 1,
|
|
44
|
+
};
|
|
45
|
+
// 启发式:1 个汉字/英文单词 ≈ 1.5 token(粗估)
|
|
46
|
+
const CHARS_PER_TOKEN = 1.5;
|
|
47
|
+
// ─── 主入口 ───────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* 读取 wiki 注入到 Worker prompt 的上下文。
|
|
50
|
+
*
|
|
51
|
+
* 步骤:
|
|
52
|
+
* 1. Layer 1: hot.md 全文
|
|
53
|
+
* 2. Layer 2: index.md 节选
|
|
54
|
+
* 3. Layer 3: pinned pages(按 id 取)
|
|
55
|
+
* 4. 列所有 page 建临时 searcher
|
|
56
|
+
* 5. Layer 4: skill 匹配
|
|
57
|
+
* 6. Layer 5: BM25 关键词
|
|
58
|
+
* 7. 合并去重 → 类型优先级排序 → 预算截断
|
|
59
|
+
* 8. 装载 TL;DR 入 PageContextEntry
|
|
60
|
+
*
|
|
61
|
+
* 失败模式:任何 layer 失败(文件丢/解析错)单独 swallow,不阻塞其他 layer。
|
|
62
|
+
*/
|
|
63
|
+
export function wikiRead(input, opts = {}) {
|
|
64
|
+
const cfg = { ...DEFAULT_OPTS, ...opts };
|
|
65
|
+
// L1
|
|
66
|
+
const hot = safeRead(() => readHot(input.repoDir), '');
|
|
67
|
+
// L2
|
|
68
|
+
const indexSummary = safeRead(() => readIndexSummary(input.repoDir, cfg.indexLines), '');
|
|
69
|
+
// 拉所有 page 一次(reader.ts 调用频率低 = 卡启动一次;现读 OK)
|
|
70
|
+
const pages = safeRead(() => listValidPages(input.repoDir), []);
|
|
71
|
+
// L3: pinned
|
|
72
|
+
const pinned = (input.pinnedPages ?? [])
|
|
73
|
+
.map((id) => getPageById(input.repoDir, id))
|
|
74
|
+
.filter((p) => p !== null);
|
|
75
|
+
// 临时 searcher 用 IndexedDoc(pageToIndexed 来自 searcher.ts)
|
|
76
|
+
const searcher = new WikiSearcher(pages.map(pageToIndexedAdapter));
|
|
77
|
+
// L4: skill
|
|
78
|
+
const bySkill = searcher
|
|
79
|
+
.searchByTags(input.cardSkills, cfg.skillTopN)
|
|
80
|
+
.map((r) => ({ pageId: r.pageId, source: 'skill' }));
|
|
81
|
+
// L5: keyword
|
|
82
|
+
const byKeyword = searcher
|
|
83
|
+
.search(`${input.cardTitle} ${input.cardDesc}`, cfg.keywordTopN)
|
|
84
|
+
.map((r) => ({ pageId: r.pageId, source: 'keyword' }));
|
|
85
|
+
// 合并去重(按 source 优先级:pinned > skill > keyword)
|
|
86
|
+
const dedup = new Map();
|
|
87
|
+
for (const p of pinned)
|
|
88
|
+
dedup.set(p.pageId, { source: 'pinned' });
|
|
89
|
+
for (const r of bySkill) {
|
|
90
|
+
if (!dedup.has(r.pageId))
|
|
91
|
+
dedup.set(r.pageId, { source: r.source });
|
|
92
|
+
}
|
|
93
|
+
for (const r of byKeyword) {
|
|
94
|
+
if (!dedup.has(r.pageId))
|
|
95
|
+
dedup.set(r.pageId, { source: r.source });
|
|
96
|
+
}
|
|
97
|
+
// 把每个 page 加载完整 + 排序
|
|
98
|
+
const entries = [];
|
|
99
|
+
for (const [pageId, meta] of dedup) {
|
|
100
|
+
const page = pages.find((p) => p.pageId === pageId);
|
|
101
|
+
if (!page)
|
|
102
|
+
continue;
|
|
103
|
+
if (isStalePage(page))
|
|
104
|
+
continue;
|
|
105
|
+
entries.push({
|
|
106
|
+
pageId,
|
|
107
|
+
title: page.frontmatter.title,
|
|
108
|
+
type: page.frontmatter.type,
|
|
109
|
+
tldr: extractTLDR(page.body),
|
|
110
|
+
source: meta.source,
|
|
111
|
+
priority: priorityOf(page.frontmatter.type, meta.source),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// 优先级排序
|
|
115
|
+
entries.sort((a, b) => b.priority - a.priority);
|
|
116
|
+
// 预算截断:估算 token,超出从尾部砍 keyword 命中(保 pinned + skill + 高优类型)
|
|
117
|
+
const trimmed = applyBudget(entries, hot, indexSummary, cfg.budgetTokens);
|
|
118
|
+
const tokensEstimate = estimateTokens(hot, indexSummary, trimmed);
|
|
119
|
+
return {
|
|
120
|
+
hot,
|
|
121
|
+
indexSummary,
|
|
122
|
+
pages: trimmed,
|
|
123
|
+
tokensEstimate,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// ─── Prompt 渲染 ──────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* WikiContext → prompt 注入 markdown。
|
|
129
|
+
*
|
|
130
|
+
* 格式(doc-28 §10):
|
|
131
|
+
* # 项目知识 - 当前状态
|
|
132
|
+
* <hot.md 全文>
|
|
133
|
+
* ---
|
|
134
|
+
* # 知识地图(节选)
|
|
135
|
+
* <index summary>
|
|
136
|
+
* ---
|
|
137
|
+
* # 与本任务相关的页
|
|
138
|
+
* ## [[id]] (type) [via source]
|
|
139
|
+
* TL;DR: ...
|
|
140
|
+
*/
|
|
141
|
+
export function formatWikiContext(ctx) {
|
|
142
|
+
const sections = [];
|
|
143
|
+
if (ctx.hot.trim().length > 0) {
|
|
144
|
+
// hot 已经是带 frontmatter 的完整文档;展示时去掉 frontmatter 块
|
|
145
|
+
const hotBody = stripFrontmatter(ctx.hot).trim();
|
|
146
|
+
sections.push('# 项目知识 - 当前状态\n\n' + hotBody);
|
|
147
|
+
}
|
|
148
|
+
if (ctx.indexSummary.trim().length > 0) {
|
|
149
|
+
sections.push('# 知识地图(节选)\n\n' + ctx.indexSummary.trim());
|
|
150
|
+
}
|
|
151
|
+
if (ctx.pages.length > 0) {
|
|
152
|
+
const lines = ['# 与本任务相关的页', ''];
|
|
153
|
+
for (const p of ctx.pages) {
|
|
154
|
+
const tag = p.source === 'pinned' ? '📌 pinned' : p.source === 'skill' ? 'via skill' : 'via keyword';
|
|
155
|
+
lines.push(`## [[${p.pageId}]] (${p.type}, ${tag})`);
|
|
156
|
+
lines.push(`TL;DR: ${p.tldr.replace(/\s+/g, ' ').trim().slice(0, 300)}`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
lines.push('完整内容:直接 Read 文件,或 `sps wiki read "<keyword>"` 找更多。');
|
|
160
|
+
sections.push(lines.join('\n'));
|
|
161
|
+
}
|
|
162
|
+
return sections.join('\n\n---\n\n');
|
|
163
|
+
}
|
|
164
|
+
// ─── Helpers (private) ────────────────────────────────────────────
|
|
165
|
+
function pageToIndexedAdapter(p) {
|
|
166
|
+
// 复用 searcher 的工厂;这里手写一份避免循环引用复杂度
|
|
167
|
+
return {
|
|
168
|
+
pageId: p.pageId,
|
|
169
|
+
title: p.frontmatter.title,
|
|
170
|
+
tags: p.frontmatter.tags,
|
|
171
|
+
tldr: extractTLDR(p.body),
|
|
172
|
+
body: p.body,
|
|
173
|
+
type: p.frontmatter.type,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function priorityOf(type, source) {
|
|
177
|
+
// pinned 永远最高(用户/Worker 显式指定)
|
|
178
|
+
// 其他按类型权重
|
|
179
|
+
const sourceBonus = source === 'pinned' ? 100 : source === 'skill' ? 10 : 0;
|
|
180
|
+
return sourceBonus + (TYPE_PRIORITY[type] ?? 1);
|
|
181
|
+
}
|
|
182
|
+
function isStalePage(page) {
|
|
183
|
+
return page.frontmatter.status === 'stale';
|
|
184
|
+
}
|
|
185
|
+
function applyBudget(entries, hot, indexSummary, budgetTokens) {
|
|
186
|
+
const baseTokens = estimateChars(hot) + estimateChars(indexSummary);
|
|
187
|
+
const baseTok = baseTokens / CHARS_PER_TOKEN;
|
|
188
|
+
if (baseTok >= budgetTokens) {
|
|
189
|
+
// hot+index 已超预算 → 不加任何 page
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const remaining = budgetTokens - baseTok;
|
|
193
|
+
// 每页 TL;DR 估 ~80 token(300 字符 / 1.5 + 一些 metadata)
|
|
194
|
+
const PER_PAGE_TOKENS = 80;
|
|
195
|
+
const maxPages = Math.max(0, Math.floor(remaining / PER_PAGE_TOKENS));
|
|
196
|
+
if (entries.length <= maxPages)
|
|
197
|
+
return entries.slice();
|
|
198
|
+
// 砍法:保留 pinned + skill,只砍 keyword 末尾
|
|
199
|
+
const pinned = entries.filter((e) => e.source === 'pinned');
|
|
200
|
+
const skill = entries.filter((e) => e.source === 'skill');
|
|
201
|
+
const keyword = entries.filter((e) => e.source === 'keyword');
|
|
202
|
+
const need = Math.max(0, maxPages - pinned.length - skill.length);
|
|
203
|
+
return [...pinned, ...skill, ...keyword.slice(0, need)];
|
|
204
|
+
}
|
|
205
|
+
function estimateTokens(hot, indexSummary, entries) {
|
|
206
|
+
const charCount = estimateChars(hot) +
|
|
207
|
+
estimateChars(indexSummary) +
|
|
208
|
+
entries.reduce((sum, e) => sum + e.title.length + e.tldr.length + 30, 0);
|
|
209
|
+
return Math.ceil(charCount / CHARS_PER_TOKEN);
|
|
210
|
+
}
|
|
211
|
+
function estimateChars(s) {
|
|
212
|
+
return s.length;
|
|
213
|
+
}
|
|
214
|
+
function stripFrontmatter(content) {
|
|
215
|
+
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
216
|
+
}
|
|
217
|
+
function safeRead(fn, fallback) {
|
|
218
|
+
try {
|
|
219
|
+
return fn();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return fallback;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../../../src/core/wiki/reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|