@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,109 @@
1
+ /**
2
+ * @module core/wiki/frontmatter
3
+ * @description Frontmatter 解析、序列化、校验
4
+ *
5
+ * @layer core
6
+ *
7
+ * 输入:raw markdown 文件内容(开头是 YAML 块 + body)。
8
+ * 输出:分离的 frontmatter(zod 校验过)+ body。
9
+ *
10
+ * 关键约束:
11
+ * - 扁平 YAML(doc-28 §6 Page Schema)—— Obsidian Properties UI 不支持嵌套
12
+ * - 缺 frontmatter / 不合法 → 返 error,**不 silently 接受残缺**
13
+ * wiki page 来自 LLM 写入,松散 schema 会让格式漂移失控
14
+ */
15
+ import YAML from 'yaml';
16
+ import { FrontmatterSchema } from './types.js';
17
+ // ─── 解析 ─────────────────────────────────────────────────────────
18
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
19
+ export class FrontmatterError extends Error {
20
+ cause;
21
+ issues;
22
+ constructor(message, cause, issues) {
23
+ super(message);
24
+ this.cause = cause;
25
+ this.issues = issues;
26
+ this.name = 'FrontmatterError';
27
+ }
28
+ }
29
+ /**
30
+ * 从完整 markdown 文件内容解析 frontmatter + body。
31
+ * 严格模式:
32
+ * - 没 `---\n...\n---\n` 块 → throw
33
+ * - YAML 解析失败 → throw
34
+ * - zod 校验失败 → throw(issues 字段带详细路径)
35
+ */
36
+ export function parseFrontmatter(content) {
37
+ const match = content.match(FRONTMATTER_RE);
38
+ if (!match) {
39
+ throw new FrontmatterError('No frontmatter block found. Page must start with `---\\n<yaml>\\n---\\n`.');
40
+ }
41
+ const yamlBlock = match[1] ?? '';
42
+ const body = (match[2] ?? '').replace(/^\n+/, '');
43
+ let parsed;
44
+ try {
45
+ parsed = YAML.parse(yamlBlock);
46
+ }
47
+ catch (err) {
48
+ throw new FrontmatterError('YAML parse error in frontmatter', err);
49
+ }
50
+ if (parsed === null || typeof parsed !== 'object') {
51
+ throw new FrontmatterError('Frontmatter must be a YAML object');
52
+ }
53
+ const result = FrontmatterSchema.safeParse(parsed);
54
+ if (!result.success) {
55
+ throw new FrontmatterError('Frontmatter schema validation failed', result.error, result.error.issues.map((i) => ({
56
+ path: i.path.join('.'),
57
+ message: i.message,
58
+ })));
59
+ }
60
+ return { frontmatter: result.data, body };
61
+ }
62
+ /**
63
+ * 宽松解析:失败时返 null + 错误,不抛。
64
+ * 用途:lint 扫描整个 wiki 时,单页坏不该终止整个 lint。
65
+ */
66
+ export function tryParseFrontmatter(content) {
67
+ try {
68
+ return { ok: true, value: parseFrontmatter(content) };
69
+ }
70
+ catch (err) {
71
+ return {
72
+ ok: false,
73
+ error: err instanceof FrontmatterError ? err : new FrontmatterError(String(err), err),
74
+ };
75
+ }
76
+ }
77
+ // ─── 序列化 ───────────────────────────────────────────────────────
78
+ /**
79
+ * Frontmatter + body → 完整 markdown 文件内容。
80
+ *
81
+ * - YAML 输出走 yaml lib 默认行为(块式、整齐排版、字符串可选引号)
82
+ * - body 末尾保证一个换行(POSIX 文件尾约定)
83
+ * - 校验过 frontmatter 才能进来——这里不再校验,调用方传合法对象
84
+ */
85
+ export function serializeFrontmatter(frontmatter, body) {
86
+ // 用 yaml lib 控制输出风格
87
+ const yamlText = YAML.stringify(frontmatter, {
88
+ indent: 2,
89
+ lineWidth: 0, // 不自动折行(避免 wikilink 被切到下一行)
90
+ defaultStringType: 'PLAIN',
91
+ defaultKeyType: 'PLAIN',
92
+ }).replace(/\n$/, ''); // YAML.stringify 末尾会带 \n,去掉留我们自己控
93
+ const bodyTrimmed = body.replace(/^\n+/, '').replace(/\n+$/, '');
94
+ return `---\n${yamlText}\n---\n\n${bodyTrimmed}\n`;
95
+ }
96
+ /**
97
+ * 校验 frontmatter 对象(不需要先序列化)。
98
+ * 用于:内存里组装 page 后写盘前快速检查。
99
+ */
100
+ export function validateFrontmatter(fm) {
101
+ const result = FrontmatterSchema.safeParse(fm);
102
+ if (result.success)
103
+ return { ok: true, value: result.data };
104
+ return {
105
+ ok: false,
106
+ error: new FrontmatterError('Frontmatter validation failed', result.error, result.error.issues.map((i) => ({ path: i.path.join('.'), message: i.message }))),
107
+ };
108
+ }
109
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../../src/core/wiki/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAoB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEjE,mEAAmE;AAEnE,MAAM,cAAc,GAAG,6CAA6C,CAAC;AAOrE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAG9B;IACA;IAHX,YACE,OAAe,EACN,KAAe,EACf,MAAqD;QAE9D,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,UAAK,GAAL,KAAK,CAAU;QACf,WAAM,GAAN,MAAM,CAA+C;QAG9D,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,gBAAgB,CACxB,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAElD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,gBAAgB,CAAC,iCAAiC,EAAE,GAAG,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,gBAAgB,CAAC,mCAAmC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,gBAAgB,CACxB,sCAAsC,EACtC,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YACtB,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe;IAEf,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,GAAG,YAAY,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;SACtF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,kEAAkE;AAElE;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAwB,EAAE,IAAY;IACzE,oBAAoB;IACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE;QAC3C,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,CAAC,EAAE,4BAA4B;QAC1C,iBAAiB,EAAE,OAAO;QAC1B,cAAc,EAAE,OAAO;KACxB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,kCAAkC;IAEzD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjE,OAAO,QAAQ,QAAQ,YAAY,WAAW,IAAI,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,EAAW;IAEX,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI,gBAAgB,CACzB,+BAA+B,EAC/B,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CACjF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 读 hot.md。文件不存在或损坏 → 返默认骨架(不阻塞 Worker prompt 注入)。
3
+ */
4
+ export declare function readHot(repoDir: string): string;
5
+ export interface HotCacheUpdate {
6
+ /** 时间戳(ISO 8601);缺省 = now */
7
+ updatedAt?: string;
8
+ /** Last Updated 段的描述(一句话:"完成卡 #18,加了 race-recovery") */
9
+ lastUpdate: string;
10
+ /** Key Recent Facts 列表(每条一行) */
11
+ keyFacts?: readonly string[];
12
+ /** Recent Changes 列表(每条一行,建议带 [[wikilink]]) */
13
+ recentChanges?: readonly string[];
14
+ /** Active Threads 列表 */
15
+ activeThreads?: readonly string[];
16
+ }
17
+ /**
18
+ * 用结构化数据生成 hot.md 完整内容(覆盖式更新——hot 是 cache,不是 journal)。
19
+ *
20
+ * 长度控制:超过 SOFT_LIMIT 在末尾加 truncation 提示;超过 HARD_LIMIT 截断。
21
+ */
22
+ export declare function renderHot(update: HotCacheUpdate): string;
23
+ /**
24
+ * 渲染 + 原子写入 hot.md。
25
+ */
26
+ export declare function writeHot(repoDir: string, update: HotCacheUpdate): void;
27
+ //# sourceMappingURL=hot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/hot.ts"],"names":[],"mappings":"AA+CA;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAS/C;AAID,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,+CAA+C;IAC/C,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,wBAAwB;IACxB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA+CxD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAMtE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @module core/wiki/hot
3
+ * @description Wiki hot.md 缓存:~500 字最近上下文(per-instance,gitignored)
4
+ *
5
+ * @layer core
6
+ *
7
+ * doc-28 §10 / claude-obsidian "hot cache" 机制:每次 ingest / 卡完成后更新
8
+ * 一份"最近发生了什么"摘要,下次 Worker 启动瞬间 prime。
9
+ *
10
+ * 实现要点:
11
+ * - 文件位置:`<repo>/wiki/.hot.md`(gitignored,per-instance 飘移)
12
+ * - 长度软上限 500 字(hard cap 1000 字截断)
13
+ * - 不存在 → 返默认骨架(让 Worker 不会因缺文件就出错)
14
+ * - 写入时校验 frontmatter 是 valid meta page
15
+ */
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { dirname } from 'node:path';
18
+ import { wikiHotFile } from '../../shared/wikiPaths.js';
19
+ // ─── Defaults ─────────────────────────────────────────────────────
20
+ const DEFAULT_HOT_TEMPLATE = `---
21
+ type: meta
22
+ title: Hot Cache
23
+ updated: 1970-01-01T00:00:00Z
24
+ ---
25
+
26
+ # Recent Context
27
+
28
+ ## Last Updated
29
+ (尚无活动。第一次 \`sps wiki update\` 或卡片完成后会自动填充。)
30
+
31
+ ## Key Recent Facts
32
+ (none yet)
33
+
34
+ ## Recent Changes
35
+ (none yet)
36
+
37
+ ## Active Threads
38
+ (none yet)
39
+ `;
40
+ const SOFT_LIMIT_CHARS = 4000; // ~500 中文字 / ~1000 英文字
41
+ const HARD_LIMIT_CHARS = 8000;
42
+ // ─── Read ─────────────────────────────────────────────────────────
43
+ /**
44
+ * 读 hot.md。文件不存在或损坏 → 返默认骨架(不阻塞 Worker prompt 注入)。
45
+ */
46
+ export function readHot(repoDir) {
47
+ const path = wikiHotFile(repoDir);
48
+ if (!existsSync(path))
49
+ return DEFAULT_HOT_TEMPLATE;
50
+ try {
51
+ const content = readFileSync(path, 'utf-8');
52
+ return content;
53
+ }
54
+ catch {
55
+ return DEFAULT_HOT_TEMPLATE;
56
+ }
57
+ }
58
+ /**
59
+ * 用结构化数据生成 hot.md 完整内容(覆盖式更新——hot 是 cache,不是 journal)。
60
+ *
61
+ * 长度控制:超过 SOFT_LIMIT 在末尾加 truncation 提示;超过 HARD_LIMIT 截断。
62
+ */
63
+ export function renderHot(update) {
64
+ const ts = update.updatedAt ?? new Date().toISOString();
65
+ const lines = [
66
+ '---',
67
+ 'type: meta',
68
+ 'title: Hot Cache',
69
+ `updated: ${ts}`,
70
+ '---',
71
+ '',
72
+ '# Recent Context',
73
+ '',
74
+ '## Last Updated',
75
+ update.lastUpdate.trim(),
76
+ '',
77
+ '## Key Recent Facts',
78
+ ];
79
+ if (update.keyFacts && update.keyFacts.length > 0) {
80
+ for (const f of update.keyFacts)
81
+ lines.push(`- ${f}`);
82
+ }
83
+ else {
84
+ lines.push('(none)');
85
+ }
86
+ lines.push('', '## Recent Changes');
87
+ if (update.recentChanges && update.recentChanges.length > 0) {
88
+ for (const c of update.recentChanges)
89
+ lines.push(`- ${c}`);
90
+ }
91
+ else {
92
+ lines.push('(none)');
93
+ }
94
+ lines.push('', '## Active Threads');
95
+ if (update.activeThreads && update.activeThreads.length > 0) {
96
+ for (const t of update.activeThreads)
97
+ lines.push(`- ${t}`);
98
+ }
99
+ else {
100
+ lines.push('(none)');
101
+ }
102
+ let content = lines.join('\n') + '\n';
103
+ if (content.length > SOFT_LIMIT_CHARS) {
104
+ if (content.length > HARD_LIMIT_CHARS) {
105
+ content = content.slice(0, HARD_LIMIT_CHARS) + '\n…(truncated to hard cap; review hot.md and trim)\n';
106
+ }
107
+ else {
108
+ content += '\n> ⚠ Hot cache exceeds soft limit (~500 字). Trim if needed.\n';
109
+ }
110
+ }
111
+ return content;
112
+ }
113
+ /**
114
+ * 渲染 + 原子写入 hot.md。
115
+ */
116
+ export function writeHot(repoDir, update) {
117
+ const path = wikiHotFile(repoDir);
118
+ const dir = dirname(path);
119
+ if (!existsSync(dir))
120
+ mkdirSync(dir, { recursive: true });
121
+ const content = renderHot(update);
122
+ writeFileSync(path, content, { encoding: 'utf-8', mode: 0o644 });
123
+ }
124
+ //# sourceMappingURL=hot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hot.js","sourceRoot":"","sources":["../../../src/core/wiki/hot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Page } from './types.js';
2
+ /**
3
+ * 从 page 列表渲染 index.md 完整内容。
4
+ *
5
+ * 输出形如:
6
+ * ```md
7
+ * ---
8
+ * type: meta
9
+ * title: Wiki Index
10
+ * updated: 2026-04-27
11
+ * ---
12
+ *
13
+ * # Wiki Index
14
+ *
15
+ * Pages: 12 · Updated 2026-04-27
16
+ *
17
+ * ## Modules (3)
18
+ * - [[modules/PipelineService]]: TL;DR 摘要短句...
19
+ * - ...
20
+ *
21
+ * ## Lessons (4)
22
+ * - [[lessons/Stop Hook Race]]: ...
23
+ * ```
24
+ */
25
+ export declare function renderIndex(pages: readonly Page[], opts?: {
26
+ updatedAt?: string;
27
+ }): string;
28
+ /**
29
+ * 写 index.md(全量重建)。
30
+ */
31
+ export declare function writeIndex(repoDir: string, pages: readonly Page[]): void;
32
+ /**
33
+ * 取 index.md 节选 top-N 行(不含 frontmatter)。
34
+ * 给 reader.ts 的 Layer 2 用——总是注入这一段。
35
+ */
36
+ export declare function readIndexSummary(repoDir: string, maxLines?: number): string;
37
+ //# sourceMappingURL=index-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-builder.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/index-builder.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,YAAY,CAAC;AAYjD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAuC7F;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,IAAI,CAMxE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,MAAM,CAgBvE"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @module core/wiki/index-builder
3
+ * @description 渲染 wiki/index.md:全部 page 的 catalog,按类型分组
4
+ *
5
+ * @layer core
6
+ *
7
+ * doc-28 §10:reader 5 层注入策略里"index 节选 top-30 行"是 Worker prompt
8
+ * 知识地图来源——所以 index.md 必须**密集且 LLM-friendly**:
9
+ * - 每条一行:`- [[type/Title]]: TL;DR 摘要`
10
+ * - 按类型分组(### Modules / ### Concepts / ### Lessons / ...)
11
+ * - 不写废话头(无引子、无 stats 表格之类)
12
+ *
13
+ * 重建时机:每次 ingest / write 后调用一次(cheap,纯函数+1 次 writeFile)。
14
+ */
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
16
+ import { dirname } from 'node:path';
17
+ import { wikiIndexFile } from '../../shared/wikiPaths.js';
18
+ import { extractTLDR } from './searcher.js';
19
+ // ─── Render ───────────────────────────────────────────────────────
20
+ const SECTIONS = [
21
+ { type: 'module', label: 'Modules' },
22
+ { type: 'concept', label: 'Concepts' },
23
+ { type: 'decision', label: 'Decisions' },
24
+ { type: 'lesson', label: 'Lessons' },
25
+ { type: 'source', label: 'Sources' },
26
+ ];
27
+ /**
28
+ * 从 page 列表渲染 index.md 完整内容。
29
+ *
30
+ * 输出形如:
31
+ * ```md
32
+ * ---
33
+ * type: meta
34
+ * title: Wiki Index
35
+ * updated: 2026-04-27
36
+ * ---
37
+ *
38
+ * # Wiki Index
39
+ *
40
+ * Pages: 12 · Updated 2026-04-27
41
+ *
42
+ * ## Modules (3)
43
+ * - [[modules/PipelineService]]: TL;DR 摘要短句...
44
+ * - ...
45
+ *
46
+ * ## Lessons (4)
47
+ * - [[lessons/Stop Hook Race]]: ...
48
+ * ```
49
+ */
50
+ export function renderIndex(pages, opts = {}) {
51
+ const ts = opts.updatedAt ?? new Date().toISOString().slice(0, 10);
52
+ const total = pages.length;
53
+ const lines = [
54
+ '---',
55
+ 'type: meta',
56
+ 'title: Wiki Index',
57
+ `updated: ${ts}`,
58
+ '---',
59
+ '',
60
+ '# Wiki Index',
61
+ '',
62
+ `Pages: ${total} · Updated ${ts}`,
63
+ '',
64
+ ];
65
+ for (const section of SECTIONS) {
66
+ const inSection = pages.filter((p) => p.frontmatter.type === section.type);
67
+ if (inSection.length === 0)
68
+ continue;
69
+ lines.push(`## ${section.label} (${inSection.length})`);
70
+ // 按 title 字典序排
71
+ const sorted = inSection.slice().sort((a, b) => a.frontmatter.title.localeCompare(b.frontmatter.title, 'en'));
72
+ for (const page of sorted) {
73
+ const tldr = extractTLDR(page.body);
74
+ const oneliner = squashToOneLine(tldr, 120);
75
+ lines.push(`- [[${page.pageId}]]: ${oneliner}`);
76
+ }
77
+ lines.push('');
78
+ }
79
+ if (total === 0) {
80
+ lines.push('(empty—— run `sps wiki update` to populate)');
81
+ lines.push('');
82
+ }
83
+ return lines.join('\n');
84
+ }
85
+ /**
86
+ * 写 index.md(全量重建)。
87
+ */
88
+ export function writeIndex(repoDir, pages) {
89
+ const path = wikiIndexFile(repoDir);
90
+ const dir = dirname(path);
91
+ if (!existsSync(dir))
92
+ mkdirSync(dir, { recursive: true });
93
+ const content = renderIndex(pages);
94
+ writeFileSync(path, content, { encoding: 'utf-8', mode: 0o644 });
95
+ }
96
+ /**
97
+ * 取 index.md 节选 top-N 行(不含 frontmatter)。
98
+ * 给 reader.ts 的 Layer 2 用——总是注入这一段。
99
+ */
100
+ export function readIndexSummary(repoDir, maxLines = 30) {
101
+ const path = wikiIndexFile(repoDir);
102
+ if (!existsSync(path))
103
+ return '';
104
+ let content;
105
+ try {
106
+ content = readFileSync(path, 'utf-8');
107
+ }
108
+ catch {
109
+ return '';
110
+ }
111
+ // strip frontmatter
112
+ const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim();
113
+ const lines = body.split('\n');
114
+ // 跳过 # Wiki Index 标题 + 空行
115
+ const start = lines.findIndex((l) => l.startsWith('## '));
116
+ if (start === -1)
117
+ return body.split('\n').slice(0, maxLines).join('\n');
118
+ return lines.slice(start, start + maxLines).join('\n');
119
+ }
120
+ // ─── helpers ──────────────────────────────────────────────────────
121
+ /**
122
+ * 把 TL;DR 拍扁成一行 + 截短。给 Worker prompt 注入用——不能多行。
123
+ */
124
+ function squashToOneLine(text, maxLen) {
125
+ const flat = text.replace(/\s+/g, ' ').trim();
126
+ if (flat.length <= maxLen)
127
+ return flat;
128
+ return flat.slice(0, maxLen - 1) + '…';
129
+ }
130
+ //# sourceMappingURL=index-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-builder.js","sourceRoot":"","sources":["../../../src/core/wiki/index-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import type { Manifest, Page } from './types.js';
2
+ export type IssueKind = 'orphan' | 'dead-link' | 'fm-gap' | 'stale';
3
+ export type IssueSeverity = 'warn' | 'error';
4
+ export interface LintIssue {
5
+ readonly kind: IssueKind;
6
+ readonly severity: IssueSeverity;
7
+ /** Page id where issue was found; null when issue is repo-level (e.g. stale source) */
8
+ readonly pageId: string | null;
9
+ /** One-line message */
10
+ readonly message: string;
11
+ /** Optional context: target page id / source path / field name */
12
+ readonly target?: string;
13
+ }
14
+ export interface LintReport {
15
+ readonly issues: readonly LintIssue[];
16
+ /** Counts grouped by kind for fast summary */
17
+ readonly counts: Record<IssueKind, number>;
18
+ readonly errorCount: number;
19
+ readonly warnCount: number;
20
+ }
21
+ export interface LintInput {
22
+ readonly pages: readonly Page[];
23
+ readonly manifest: Manifest;
24
+ readonly repoDir: string;
25
+ }
26
+ export declare function lintWiki(input: LintInput): LintReport;
27
+ /**
28
+ * Orphan = page that nobody links to (via frontmatter related[] OR body wikilink).
29
+ *
30
+ * A solo page can still be valuable (e.g. just-written lesson) — orphan is **warn**,
31
+ * not error.
32
+ */
33
+ export declare function checkOrphans(pages: readonly Page[]): LintIssue[];
34
+ /**
35
+ * Dead link = wikilink "[[X]]" where X (resolved) is not in the page set.
36
+ *
37
+ * Errors for related[] (frontmatter); warnings for body links (in-flight writing).
38
+ */
39
+ export declare function checkDeadLinks(pages: readonly Page[]): LintIssue[];
40
+ /**
41
+ * Catches missing soft-required fields the zod schema can't enforce
42
+ * (e.g. empty title, empty TL;DR, all default tags). Schema-level errors
43
+ * are caught at parse time — this is a content sniff.
44
+ */
45
+ export declare function checkFrontmatterGaps(pages: readonly Page[]): LintIssue[];
46
+ /**
47
+ * Compare manifest's stored sha256 against current file hash on disk.
48
+ * Mismatch = source has drifted since last ingest → its derived pages may be stale.
49
+ *
50
+ * Files that don't exist on disk anymore are flagged as 'stale' too (they should
51
+ * be removed from manifest via finalize).
52
+ */
53
+ export declare function checkStaleSources(manifest: Manifest, repoDir: string): LintIssue[];
54
+ /**
55
+ * Resolve a "[[X]]" or "[[type/X]]" wikilink against the known pageId set.
56
+ *
57
+ * Strategy:
58
+ * - "[[type/X]]" → look up exact pageId
59
+ * - "[[X]]" → look up any pageId ending with "/X" (any type matches)
60
+ *
61
+ * Returns the resolved pageId, or null if unresolvable.
62
+ */
63
+ export declare function resolveLinkToId(wikilink: string, ids: ReadonlySet<string>): string | null;
64
+ export declare function extractBodyWikilinks(body: string): string[];
65
+ /**
66
+ * Compare source mtime vs page mtime. Returns paths whose source is newer.
67
+ *
68
+ * Best-effort — file mtime precision varies (ext4 ns vs HFS+ 1s); we use a
69
+ * 60s threshold to avoid false positives.
70
+ */
71
+ export declare function findOutdatedPages(manifest: Manifest, pages: readonly Page[], repoDir: string, thresholdMs?: number): {
72
+ sourcePath: string;
73
+ pageIds: string[];
74
+ sourceMtime: Date;
75
+ }[];
76
+ //# sourceMappingURL=linter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/linter.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIjD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AACpE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uBAAuB;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAID,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAOrD;AAID;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,EAAE,CA+BhE;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,EAAE,CA4BlE;AAID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,EAAE,CAiCxE;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CA4BlF;AAID;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GACvB,MAAM,GAAG,IAAI,CAaf;AAID,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM3D;AA0BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,OAAO,EAAE,MAAM,EACf,WAAW,SAAS,GACnB;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,IAAI,CAAA;CAAE,EAAE,CA4BhE"}