@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.
Files changed (83) 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-DRhdpvew.css +10 -0
  14. package/dist/console-assets/assets/{index-QBai48VV.js → index-WUGCBcyb.js} +3 -3
  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
  83. package/dist/console-assets/assets/index-BgOHCIG1.css +0 -10
@@ -0,0 +1,180 @@
1
+ /**
2
+ * @module core/wiki/manifest
3
+ * @description Wiki 增量索引:源文件 hash 跟踪表(.manifest.json)
4
+ *
5
+ * @layer core
6
+ *
7
+ * 作用:每次 `sps wiki update` 时比 hash,决定哪些 source 需要重新 ingest——
8
+ * 避免每次都让 Worker 重读全部源文件。
9
+ *
10
+ * 设计要点(doc-28 §5 + §8):
11
+ * - 文件位置:`<repo>/wiki/.manifest.json`(gitignored)
12
+ * - 每台机一份;不同 dev 的 manifest 不互相覆盖
13
+ * - 源路径用相对 repo 根目录("src/X.ts" / ".raw/pdfs/y.pdf")
14
+ * - hash 用 sha256,文件级原子单位(不切分到行/段)
15
+ * - 跟 zod schema 校验过——损坏的 manifest 当作 EMPTY 重建,不阻塞 update
16
+ */
17
+ import { createHash } from 'node:crypto';
18
+ import { existsSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
19
+ import { EMPTY_MANIFEST, ManifestSchema, } from './types.js';
20
+ // ─── 文件 I/O ─────────────────────────────────────────────────────
21
+ /**
22
+ * 读 manifest 文件。
23
+ * 文件不存在 / 损坏 / schema 不合法 → 返 EMPTY_MANIFEST(同时调 onWarn 报告)。
24
+ *
25
+ * 不 throw —— manifest 只是性能优化,损坏时退化为全量重 ingest 即可。
26
+ */
27
+ export function readManifest(manifestPath, onWarn) {
28
+ if (!existsSync(manifestPath))
29
+ return EMPTY_MANIFEST;
30
+ let raw;
31
+ try {
32
+ raw = readFileSync(manifestPath, 'utf-8');
33
+ }
34
+ catch (err) {
35
+ onWarn?.(`manifest read failed (${manifestPath}): ${errMsg(err)}`);
36
+ return EMPTY_MANIFEST;
37
+ }
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(raw);
41
+ }
42
+ catch (err) {
43
+ onWarn?.(`manifest JSON parse failed (${manifestPath}): ${errMsg(err)} — treating as empty`);
44
+ return EMPTY_MANIFEST;
45
+ }
46
+ const result = ManifestSchema.safeParse(parsed);
47
+ if (!result.success) {
48
+ onWarn?.(`manifest schema invalid (${manifestPath}): ${result.error.issues
49
+ .map((i) => `${i.path.join('.')}:${i.message}`)
50
+ .join('; ')} — treating as empty`);
51
+ return EMPTY_MANIFEST;
52
+ }
53
+ return result.data;
54
+ }
55
+ /**
56
+ * 写 manifest 文件。原子替换(temp + rename),避免读到半截。
57
+ */
58
+ export function writeManifest(manifestPath, manifest) {
59
+ const validated = ManifestSchema.parse(manifest); // 写入前最后一道校验
60
+ const text = JSON.stringify(validated, null, 2) + '\n';
61
+ // node:fs writeFileSync 已经是原子的(POSIX rename semantics)—— 但为防止 partial
62
+ // write 在 crash 场景留半截文件,用 .tmp + rename。
63
+ const tmpPath = manifestPath + '.tmp';
64
+ writeFileSync(tmpPath, text, { encoding: 'utf-8', mode: 0o644 });
65
+ renameSync(tmpPath, manifestPath);
66
+ }
67
+ // ─── Hash 计算 ────────────────────────────────────────────────────
68
+ /**
69
+ * 计算文件 sha256 hex。文件不存在或读失败 → throw(调用方决定是否兜底)。
70
+ *
71
+ * 大文件友好:用 buffer reads 而不是一次性读到内存——但 wiki 源文件
72
+ * 一般 < 1 MB,readFileSync 已经够。如果将来要 ingest 大 PDF,再换 streaming。
73
+ */
74
+ export function hashFile(filePath) {
75
+ const buf = readFileSync(filePath);
76
+ return createHash('sha256').update(buf).digest('hex');
77
+ }
78
+ /**
79
+ * 同上但是宽松:失败时返 null(用于 lint / 增量扫描)。
80
+ */
81
+ export function tryHashFile(filePath) {
82
+ try {
83
+ return hashFile(filePath);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * 比较"当前发现的源 + 它们的 hash"和 manifest 里记录的状态。
91
+ *
92
+ * - added:current 里有,manifest 里没
93
+ * - changed:两边都有但 hash 不同
94
+ * - removed:manifest 里有,current 里没(源被删了)
95
+ * - unchanged:两边都有且 hash 相同
96
+ *
97
+ * 纯函数,无 I/O。
98
+ */
99
+ export function diffSources(current, manifest) {
100
+ const currentMap = new Map(current.map((s) => [s.path, s.hash]));
101
+ const manifestPaths = new Set(Object.keys(manifest.sources));
102
+ const added = [];
103
+ const changed = [];
104
+ const unchanged = [];
105
+ for (const [path, hash] of currentMap) {
106
+ const entry = manifest.sources[path];
107
+ if (!entry) {
108
+ added.push(path);
109
+ }
110
+ else if (entry.sha256 !== hash) {
111
+ changed.push(path);
112
+ }
113
+ else {
114
+ unchanged.push(path);
115
+ }
116
+ }
117
+ const removed = [];
118
+ for (const path of manifestPaths) {
119
+ if (!currentMap.has(path))
120
+ removed.push(path);
121
+ }
122
+ return {
123
+ added: added.sort(),
124
+ changed: changed.sort(),
125
+ removed: removed.sort(),
126
+ unchanged: unchanged.sort(),
127
+ };
128
+ }
129
+ // ─── 更新 manifest(in-place pure,返回新 manifest) ───────────────
130
+ /**
131
+ * 标记一个 source 已 ingest 过。返回新 manifest(不修改原对象)。
132
+ *
133
+ * 用途:每次 ingest 一个 source 后调一次。如果 path 已存在,覆盖;否则添加。
134
+ */
135
+ export function recordIngest(manifest, path, entry) {
136
+ const sources = { ...manifest.sources, [path]: entry };
137
+ return {
138
+ ...manifest,
139
+ updated_at: new Date().toISOString(),
140
+ sources,
141
+ };
142
+ }
143
+ /**
144
+ * 删除一个 source 的记录(被删除时用)。返回新 manifest。
145
+ */
146
+ export function removeFromManifest(manifest, path) {
147
+ if (!(path in manifest.sources))
148
+ return manifest;
149
+ const { [path]: _removed, ...rest } = manifest.sources;
150
+ void _removed;
151
+ return {
152
+ ...manifest,
153
+ updated_at: new Date().toISOString(),
154
+ sources: rest,
155
+ };
156
+ }
157
+ /**
158
+ * 检查 source 文件 mtime > 它所派生的 page 中任何一个的 updated 字段。
159
+ * 用于 lint:标 stale page。
160
+ *
161
+ * 这是 best-effort —— 跨文件系统 mtime 精度不一致(macOS HFS+ 1s,
162
+ * Linux ext4 ns)。差几秒不算 stale。
163
+ */
164
+ export function isSourceStale(sourcePath, pageMtime) {
165
+ if (!pageMtime)
166
+ return false;
167
+ try {
168
+ const sourceMtime = statSync(sourcePath).mtime;
169
+ // 超过 60s 才算改过——避免精度问题误报
170
+ return sourceMtime.getTime() - pageMtime.getTime() > 60_000;
171
+ }
172
+ catch {
173
+ return false; // source 不存在 = 不算 stale,归 lint 别的检查
174
+ }
175
+ }
176
+ // ─── helpers ──────────────────────────────────────────────────────
177
+ function errMsg(err) {
178
+ return err instanceof Error ? err.message : String(err);
179
+ }
180
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/core/wiki/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { FrontmatterError } from './frontmatter.js';
2
+ import type { Frontmatter, Page, PageType } from './types.js';
3
+ /**
4
+ * 读单页。文件不存在返 null。frontmatter 损坏抛 FrontmatterError。
5
+ */
6
+ export declare function readPage(filePath: string): Page | null;
7
+ /**
8
+ * 读单页(宽松版本):损坏不抛,返 ParseFailure 标记。
9
+ * 用于 list 扫描时一页坏不阻塞整体。
10
+ */
11
+ export type ReadResult = {
12
+ ok: true;
13
+ page: Page;
14
+ } | {
15
+ ok: false;
16
+ filePath: string;
17
+ error: FrontmatterError;
18
+ };
19
+ export declare function tryReadPage(filePath: string): ReadResult | null;
20
+ /**
21
+ * 写一页。父目录不存在自动建。frontmatter 写前 zod 校验(serializeFrontmatter 内部
22
+ * 假设已校验,这里通过 page.frontmatter 的类型推断保证)。
23
+ *
24
+ * - 已存在:覆盖
25
+ * - 不存在:创建
26
+ *
27
+ * 返回写入的最终 filePath(绝对路径)。
28
+ */
29
+ export declare function writePage(repoDir: string, type: PageType, title: string, frontmatter: Frontmatter, body: string): string;
30
+ /**
31
+ * 删除一页。文件不存在 = no-op。
32
+ */
33
+ export declare function deletePage(repoDir: string, type: PageType, title: string): boolean;
34
+ export interface ListOptions {
35
+ /** 限定类型(默认全部 5 类) */
36
+ readonly types?: readonly PageType[];
37
+ /** 是否包含损坏的页(带 ok=false 的 entry);默认 false(只返成功的) */
38
+ readonly includeFailures?: boolean;
39
+ }
40
+ /**
41
+ * 列出指定项目下所有 wiki page。
42
+ *
43
+ * 类型 dir 不存在 → 跳过(不算错误,project 可能没创建那种类型)。
44
+ * 单页损坏 → 默认跳过;includeFailures=true 时保留。
45
+ */
46
+ export declare function listPages(repoDir: string, opts?: ListOptions): ReadResult[];
47
+ /**
48
+ * 仅返成功读取的页。语法糖(list 90% 场景)。
49
+ */
50
+ export declare function listValidPages(repoDir: string, opts?: ListOptions): Page[];
51
+ /**
52
+ * 按 pageId(如 "modules/PipelineService")查找 page。
53
+ * 自动从 id 拆出 type 和 title。
54
+ */
55
+ export declare function getPageById(repoDir: string, pageId: string): Page | null;
56
+ /**
57
+ * 按 wikilink "[[Page Name]]" 查找 page。会扫所有 type 直到找到第一个 title 匹配。
58
+ * 用于解析 frontmatter related 字段或 body 里的 wikilink。
59
+ *
60
+ * Wikilink 可以带 type 前缀 "[[modules/PipelineService]]" —— 这种走 getPageById。
61
+ */
62
+ export declare function findPageByWikilink(repoDir: string, wikilink: string): Page | null;
63
+ /**
64
+ * 给定文件绝对路径,反推 pageId(type + title)。
65
+ * 无效路径返 null。
66
+ */
67
+ export declare function resolvePageId(repoDir: string, filePath: string): {
68
+ type: PageType;
69
+ title: string;
70
+ pageId: string;
71
+ } | null;
72
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/page.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,gBAAgB,EAA0C,MAAM,kBAAkB,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI9D;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAWtD;AAWD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GACxB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE7D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CA0B/D;AAID;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,MAAM,GACX,MAAM,CAcR;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKlF;AAID,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,mDAAmD;IACnD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAUD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,UAAU,EAAE,CAgC/E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,IAAI,EAAE,CAI9E;AAID;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAYxE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAajF;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAE1D"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @module core/wiki/page
3
+ * @description Wiki page CRUD:读 / 写 / 列表 / 取
4
+ *
5
+ * @layer core
6
+ *
7
+ * 这层负责文件系统操作 + frontmatter 解析。**不做** schema 推断、不做内容生成、
8
+ * 不做检索——那些是上层 reader/searcher 的事。
9
+ *
10
+ * 错误策略:
11
+ * - 不存在的 page:返 null,调用方决定怎么处理
12
+ * - 损坏的 page(frontmatter 解析失败):返 ParseFailure,**不抛**——避免一页坏阻塞 list
13
+ * - 写入:写前校验 frontmatter,失败时抛 FrontmatterError
14
+ *
15
+ * 测试要点:
16
+ * - round-trip:write → read → frontmatter / body 等价
17
+ * - 损坏 page 不影响 list 其他页
18
+ * - parseWikiPageId 反推一致
19
+ */
20
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
21
+ import { dirname, resolve } from 'node:path';
22
+ import { parseWikiPageId, wikiPageDir, wikiPageFile, wikiPageId, } from '../../shared/wikiPaths.js';
23
+ import { FrontmatterError, parseFrontmatter, serializeFrontmatter } from './frontmatter.js';
24
+ // ─── 单页读取 ─────────────────────────────────────────────────────
25
+ /**
26
+ * 读单页。文件不存在返 null。frontmatter 损坏抛 FrontmatterError。
27
+ */
28
+ export function readPage(filePath) {
29
+ if (!existsSync(filePath))
30
+ return null;
31
+ const content = readFileSync(filePath, 'utf-8');
32
+ const { frontmatter, body } = parseFrontmatter(content);
33
+ // 文件路径反推 pageId
34
+ // 文件路径形如 .../wiki/<type>s/<title>.md ——但我们这里只有绝对 filePath,
35
+ // 没有 repoDir 上下文。所以 pageId 通过路径基础名推:
36
+ const pageId = inferPageIdFromPath(filePath, frontmatter);
37
+ return { pageId, filePath, frontmatter, body };
38
+ }
39
+ /**
40
+ * 从绝对 filePath + frontmatter.type 推 pageId。
41
+ * 假设:filePath 形如 `<...>/<type>s/<title>.md`。
42
+ */
43
+ function inferPageIdFromPath(filePath, fm) {
44
+ const base = filePath.replace(/\.md$/, '').split('/').pop() ?? fm.title;
45
+ return wikiPageId(fm.type, base);
46
+ }
47
+ export function tryReadPage(filePath) {
48
+ if (!existsSync(filePath))
49
+ return null;
50
+ let content;
51
+ try {
52
+ content = readFileSync(filePath, 'utf-8');
53
+ }
54
+ catch (err) {
55
+ return {
56
+ ok: false,
57
+ filePath,
58
+ error: new FrontmatterError(`read failed: ${errMsg(err)}`, err),
59
+ };
60
+ }
61
+ try {
62
+ const { frontmatter, body } = parseFrontmatter(content);
63
+ const pageId = inferPageIdFromPath(filePath, frontmatter);
64
+ return { ok: true, page: { pageId, filePath, frontmatter, body } };
65
+ }
66
+ catch (err) {
67
+ return {
68
+ ok: false,
69
+ filePath,
70
+ error: err instanceof FrontmatterError
71
+ ? err
72
+ : new FrontmatterError(`parse failed: ${errMsg(err)}`, err),
73
+ };
74
+ }
75
+ }
76
+ // ─── 单页写入 ─────────────────────────────────────────────────────
77
+ /**
78
+ * 写一页。父目录不存在自动建。frontmatter 写前 zod 校验(serializeFrontmatter 内部
79
+ * 假设已校验,这里通过 page.frontmatter 的类型推断保证)。
80
+ *
81
+ * - 已存在:覆盖
82
+ * - 不存在:创建
83
+ *
84
+ * 返回写入的最终 filePath(绝对路径)。
85
+ */
86
+ export function writePage(repoDir, type, title, frontmatter, body) {
87
+ if (frontmatter.type !== type) {
88
+ throw new Error(`Page type mismatch: dir says "${type}" but frontmatter says "${frontmatter.type}"`);
89
+ }
90
+ const filePath = wikiPageFile(repoDir, type, title);
91
+ const dir = dirname(filePath);
92
+ if (!existsSync(dir)) {
93
+ mkdirSync(dir, { recursive: true });
94
+ }
95
+ const content = serializeFrontmatter(frontmatter, body);
96
+ writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o644 });
97
+ return filePath;
98
+ }
99
+ /**
100
+ * 删除一页。文件不存在 = no-op。
101
+ */
102
+ export function deletePage(repoDir, type, title) {
103
+ const filePath = wikiPageFile(repoDir, type, title);
104
+ if (!existsSync(filePath))
105
+ return false;
106
+ rmSync(filePath, { force: true });
107
+ return true;
108
+ }
109
+ const ALL_PAGE_TYPES = [
110
+ 'module',
111
+ 'concept',
112
+ 'decision',
113
+ 'lesson',
114
+ 'source',
115
+ ];
116
+ /**
117
+ * 列出指定项目下所有 wiki page。
118
+ *
119
+ * 类型 dir 不存在 → 跳过(不算错误,project 可能没创建那种类型)。
120
+ * 单页损坏 → 默认跳过;includeFailures=true 时保留。
121
+ */
122
+ export function listPages(repoDir, opts = {}) {
123
+ const types = opts.types ?? ALL_PAGE_TYPES;
124
+ const out = [];
125
+ for (const t of types) {
126
+ const dir = wikiPageDir(repoDir, t);
127
+ if (!existsSync(dir))
128
+ continue;
129
+ let entries;
130
+ try {
131
+ entries = readdirSync(dir);
132
+ }
133
+ catch {
134
+ continue;
135
+ }
136
+ for (const name of entries) {
137
+ if (!name.endsWith('.md'))
138
+ continue;
139
+ // _index.md 是辅助索引,不算 page
140
+ if (name.startsWith('_'))
141
+ continue;
142
+ const filePath = resolve(dir, name);
143
+ try {
144
+ const stat = statSync(filePath);
145
+ if (!stat.isFile())
146
+ continue;
147
+ }
148
+ catch {
149
+ continue;
150
+ }
151
+ const result = tryReadPage(filePath);
152
+ if (!result)
153
+ continue;
154
+ if (!result.ok && !opts.includeFailures)
155
+ continue;
156
+ out.push(result);
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+ /**
162
+ * 仅返成功读取的页。语法糖(list 90% 场景)。
163
+ */
164
+ export function listValidPages(repoDir, opts = {}) {
165
+ return listPages(repoDir, { ...opts, includeFailures: false })
166
+ .filter((r) => r.ok)
167
+ .map((r) => r.page);
168
+ }
169
+ // ─── 按 id 取 ─────────────────────────────────────────────────────
170
+ /**
171
+ * 按 pageId(如 "modules/PipelineService")查找 page。
172
+ * 自动从 id 拆出 type 和 title。
173
+ */
174
+ export function getPageById(repoDir, pageId) {
175
+ const slashIdx = pageId.indexOf('/');
176
+ if (slashIdx === -1)
177
+ return null;
178
+ const typeWithS = pageId.slice(0, slashIdx);
179
+ const title = pageId.slice(slashIdx + 1);
180
+ // typeWithS 是复数("modules"),去 s 得到 type
181
+ if (!typeWithS.endsWith('s'))
182
+ return null;
183
+ const type = typeWithS.slice(0, -1);
184
+ if (!ALL_PAGE_TYPES.includes(type))
185
+ return null;
186
+ const filePath = wikiPageFile(repoDir, type, title);
187
+ return readPage(filePath);
188
+ }
189
+ /**
190
+ * 按 wikilink "[[Page Name]]" 查找 page。会扫所有 type 直到找到第一个 title 匹配。
191
+ * 用于解析 frontmatter related 字段或 body 里的 wikilink。
192
+ *
193
+ * Wikilink 可以带 type 前缀 "[[modules/PipelineService]]" —— 这种走 getPageById。
194
+ */
195
+ export function findPageByWikilink(repoDir, wikilink) {
196
+ const stripped = wikilink.replace(/^\[\[/, '').replace(/\]\]$/, '');
197
+ if (stripped.includes('/')) {
198
+ return getPageById(repoDir, stripped);
199
+ }
200
+ // 无前缀:扫所有 type 找 title 匹配(注意可能多个 type 同名,按数组顺序取第一个)
201
+ for (const type of ALL_PAGE_TYPES) {
202
+ const filePath = wikiPageFile(repoDir, type, stripped);
203
+ if (existsSync(filePath)) {
204
+ return readPage(filePath);
205
+ }
206
+ }
207
+ return null;
208
+ }
209
+ // ─── 反向查询:路径 → page meta ───────────────────────────────────
210
+ /**
211
+ * 给定文件绝对路径,反推 pageId(type + title)。
212
+ * 无效路径返 null。
213
+ */
214
+ export function resolvePageId(repoDir, filePath) {
215
+ return parseWikiPageId(repoDir, filePath);
216
+ }
217
+ // ─── helpers ──────────────────────────────────────────────────────
218
+ function errMsg(err) {
219
+ return err instanceof Error ? err.message : String(err);
220
+ }
221
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/core/wiki/page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
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 type { PageType } from './types.js';
28
+ export interface ReadInput {
29
+ /** Repo 根目录(用于查 wiki/) */
30
+ readonly repoDir: string;
31
+ /** 当前卡片 title + description(用作 BM25 查询) */
32
+ readonly cardTitle: string;
33
+ readonly cardDesc: string;
34
+ /** 卡 frontmatter 的 skills(用作 tag 匹配) */
35
+ readonly cardSkills: readonly string[];
36
+ /** 卡 labels(暂未用,预留扩展) */
37
+ readonly cardLabels?: readonly string[];
38
+ /** 卡 frontmatter wiki_pages 显式 pin 的 page ids */
39
+ readonly pinnedPages?: readonly string[];
40
+ }
41
+ export interface PageContextEntry {
42
+ readonly pageId: string;
43
+ readonly title: string;
44
+ readonly type: PageType;
45
+ readonly tldr: string;
46
+ /** 命中来源——用于 prompt 渲染时分组显示 */
47
+ readonly source: 'pinned' | 'skill' | 'keyword';
48
+ /** 内部排序权重(debug) */
49
+ readonly priority: number;
50
+ }
51
+ export interface WikiContext {
52
+ /** L1: hot.md 全文 */
53
+ readonly hot: string;
54
+ /** L2: index.md 节选 top-N 行 */
55
+ readonly indexSummary: string;
56
+ /** L3+L4+L5 合并去重 + 预算后的 page 列表 */
57
+ readonly pages: readonly PageContextEntry[];
58
+ /** 可观察性:本次注入的 token 预估 */
59
+ readonly tokensEstimate: number;
60
+ }
61
+ export interface ReadOptions {
62
+ /** Layer 2 节选行数(默认 30) */
63
+ readonly indexLines?: number;
64
+ /** Layer 4 skill 命中 top-N(默认 3) */
65
+ readonly skillTopN?: number;
66
+ /** Layer 5 keyword 命中 top-N(默认 3) */
67
+ readonly keywordTopN?: number;
68
+ /** Token 预算硬上限(默认 2000) */
69
+ readonly budgetTokens?: number;
70
+ }
71
+ /**
72
+ * 读取 wiki 注入到 Worker prompt 的上下文。
73
+ *
74
+ * 步骤:
75
+ * 1. Layer 1: hot.md 全文
76
+ * 2. Layer 2: index.md 节选
77
+ * 3. Layer 3: pinned pages(按 id 取)
78
+ * 4. 列所有 page 建临时 searcher
79
+ * 5. Layer 4: skill 匹配
80
+ * 6. Layer 5: BM25 关键词
81
+ * 7. 合并去重 → 类型优先级排序 → 预算截断
82
+ * 8. 装载 TL;DR 入 PageContextEntry
83
+ *
84
+ * 失败模式:任何 layer 失败(文件丢/解析错)单独 swallow,不阻塞其他 layer。
85
+ */
86
+ export declare function wikiRead(input: ReadInput, opts?: ReadOptions): WikiContext;
87
+ /**
88
+ * WikiContext → prompt 注入 markdown。
89
+ *
90
+ * 格式(doc-28 §10):
91
+ * # 项目知识 - 当前状态
92
+ * <hot.md 全文>
93
+ * ---
94
+ * # 知识地图(节选)
95
+ * <index summary>
96
+ * ---
97
+ * # 与本任务相关的页
98
+ * ## [[id]] (type) [via source]
99
+ * TL;DR: ...
100
+ */
101
+ export declare function formatWikiContext(ctx: WikiContext): string;
102
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAMH,OAAO,KAAK,EAAQ,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIjD,MAAM,WAAW,SAAS;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,yBAAyB;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAChD,oBAAoB;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,oBAAoB;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,8BAA8B;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,0BAA0B;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAID,MAAM,WAAW,WAAW;IAC1B,0BAA0B;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2BAA2B;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAuBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,WAAgB,GACrB,WAAW,CAyEb;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CA0B1D"}