@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
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="/assets/index-QBai48VV.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-BgOHCIG1.css">
10
+ <script type="module" crossorigin src="/assets/index-WUGCBcyb.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DRhdpvew.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -27,6 +27,18 @@ interface SharedPromptContext {
27
27
  skillContent?: string;
28
28
  projectRules?: string;
29
29
  knowledge?: string;
30
+ /**
31
+ * v0.51.0: pre-rendered wiki context (hot.md + index summary + relevant pages).
32
+ * Produced by `wikiRead → formatWikiContext`. Inserted between `knowledge` and `# Task`.
33
+ * When WIKI_ENABLED=false, callers leave this undefined so nothing is appended.
34
+ */
35
+ wikiContext?: string;
36
+ /**
37
+ * v0.51.0: trailing reminder block pointing to the `wiki-update` skill. Worker
38
+ * sees it after `# How to Run` so it's the last thing read before deciding what
39
+ * to write.
40
+ */
41
+ wikiUpdateReminder?: string;
30
42
  /** v0.50.18:完成信号词,默认 "done"。项目 YAML / env 可覆盖。 */
31
43
  completionSignal?: string;
32
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"taskPrompts.d.ts","sourceRoot":"","sources":["../../src/core/taskPrompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,aAAa,CAAC;AAE5D,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAEhD,UAAU,kBAAmB,SAAQ,mBAAmB;IACtD,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAsBhE;AA+BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAiChE"}
1
+ {"version":3,"file":"taskPrompts.d.ts","sourceRoot":"","sources":["../../src/core/taskPrompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,aAAa,CAAC;AAE5D,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAEhD,UAAU,kBAAmB,SAAQ,mBAAmB;IACtD,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CA+BhE;AA+BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CA0ChE"}
@@ -14,8 +14,15 @@ export function buildPhasePrompt(ctx) {
14
14
  sections.push(ctx.knowledge.trim());
15
15
  sections.push('---');
16
16
  }
17
+ if (ctx.wikiContext?.trim()) {
18
+ sections.push(ctx.wikiContext.trim());
19
+ sections.push('---');
20
+ }
17
21
  sections.push(buildTaskSection(ctx));
18
22
  sections.push(buildPhaseInstructions(ctx));
23
+ if (ctx.wikiUpdateReminder?.trim()) {
24
+ sections.push(ctx.wikiUpdateReminder.trim());
25
+ }
19
26
  return sections.join('\n\n').trim() + '\n';
20
27
  }
21
28
  function buildTaskSection(ctx) {
@@ -63,6 +70,10 @@ export function buildTaskPrompt(ctx) {
63
70
  sections.push(ctx.knowledge.trim());
64
71
  sections.push('---');
65
72
  }
73
+ if (ctx.wikiContext?.trim()) {
74
+ sections.push(ctx.wikiContext.trim());
75
+ sections.push('---');
76
+ }
66
77
  sections.push(`# Task
67
78
 
68
79
  ${ctx.taskTitle} (seq ${ctx.taskSeq})
@@ -75,6 +86,9 @@ ${ctx.taskDescription || '(no description)'}`);
75
86
  sections.push(`# How to Run
76
87
 
77
88
  Work inside \`${ctx.worktreePath}\`. Complete the task, validate the output, then say "${signal}". Don't touch files outside this directory. If blocked, report the exact blocker.`);
89
+ if (ctx.wikiUpdateReminder?.trim()) {
90
+ sections.push(ctx.wikiUpdateReminder.trim());
91
+ }
78
92
  return sections.join('\n\n').trim() + '\n';
79
93
  }
80
94
  //# sourceMappingURL=taskPrompts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"taskPrompts.js","sourceRoot":"","sources":["../../src/core/taskPrompts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"taskPrompts.js","sourceRoot":"","sources":["../../src/core/taskPrompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type Frontmatter } from './types.js';
2
+ export interface ParseResult {
3
+ readonly frontmatter: Frontmatter;
4
+ readonly body: string;
5
+ }
6
+ export declare class FrontmatterError extends Error {
7
+ readonly cause?: unknown | undefined;
8
+ readonly issues?: readonly {
9
+ path: string;
10
+ message: string;
11
+ }[] | undefined;
12
+ constructor(message: string, cause?: unknown | undefined, issues?: readonly {
13
+ path: string;
14
+ message: string;
15
+ }[] | undefined);
16
+ }
17
+ /**
18
+ * 从完整 markdown 文件内容解析 frontmatter + body。
19
+ * 严格模式:
20
+ * - 没 `---\n...\n---\n` 块 → throw
21
+ * - YAML 解析失败 → throw
22
+ * - zod 校验失败 → throw(issues 字段带详细路径)
23
+ */
24
+ export declare function parseFrontmatter(content: string): ParseResult;
25
+ /**
26
+ * 宽松解析:失败时返 null + 错误,不抛。
27
+ * 用途:lint 扫描整个 wiki 时,单页坏不该终止整个 lint。
28
+ */
29
+ export declare function tryParseFrontmatter(content: string): {
30
+ ok: true;
31
+ value: ParseResult;
32
+ } | {
33
+ ok: false;
34
+ error: FrontmatterError;
35
+ };
36
+ /**
37
+ * Frontmatter + body → 完整 markdown 文件内容。
38
+ *
39
+ * - YAML 输出走 yaml lib 默认行为(块式、整齐排版、字符串可选引号)
40
+ * - body 末尾保证一个换行(POSIX 文件尾约定)
41
+ * - 校验过 frontmatter 才能进来——这里不再校验,调用方传合法对象
42
+ */
43
+ export declare function serializeFrontmatter(frontmatter: Frontmatter, body: string): string;
44
+ /**
45
+ * 校验 frontmatter 对象(不需要先序列化)。
46
+ * 用于:内存里组装 page 后写盘前快速检查。
47
+ */
48
+ export declare function validateFrontmatter(fm: unknown): {
49
+ ok: true;
50
+ value: Frontmatter;
51
+ } | {
52
+ ok: false;
53
+ error: FrontmatterError;
54
+ };
55
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/core/wiki/frontmatter.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,YAAY,CAAC;AAMjE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAGvC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE;gBAF9D,OAAO,EAAE,MAAM,EACN,KAAK,CAAC,EAAE,OAAO,YAAA,EACf,MAAM,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,YAAA;CAKjE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAiC7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,GACd;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAS3E;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAWnF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,OAAO,GACV;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAW3E"}
@@ -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":"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"}
@@ -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":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,qEAAqE;AAErE,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;CAmB5B,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,CAAC,uBAAuB;AACtD,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,qEAAqE;AAErE;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,oBAAoB,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,oBAAoB,CAAC;IAC9B,CAAC;AACH,CAAC;AAiBD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAsB;IAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxD,MAAM,KAAK,GAAa;QACtB,KAAK;QACL,YAAY;QACZ,kBAAkB;QAClB,YAAY,EAAE,EAAE;QAChB,KAAK;QACL,EAAE;QACF,kBAAkB;QAClB,EAAE;QACF,iBAAiB;QACjB,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE;QACxB,EAAE;QACF,qBAAqB;KACtB,CAAC;IAEF,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,mBAAmB,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,mBAAmB,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAEtC,IAAI,OAAO,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACtC,IAAI,OAAO,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;YACtC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,sDAAsD,CAAC;QACxG,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,gEAAgE,CAAC;QAC9E,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,MAAsB;IAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAClC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACnE,CAAC"}
@@ -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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"}