@tienne/gestalt 0.107.0 → 0.108.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 (159) hide show
  1. package/CLAUDE.md +1 -0
  2. package/README.ko.md +1 -0
  3. package/README.md +1 -0
  4. package/dist/package.json +1 -1
  5. package/dist/plugin/role-agents/lens-context/AGENT.md +94 -0
  6. package/dist/plugin/role-agents/lens-link/AGENT.md +83 -0
  7. package/dist/plugin/role-agents/lens-reader/AGENT.md +230 -0
  8. package/dist/plugin/skills/_shared/proactive-routing.md +1 -0
  9. package/dist/plugin/skills/lens/SKILL.md +375 -0
  10. package/dist/schemas/gestalt.schema.json +13 -0
  11. package/dist/src/cli/commands/digest.d.ts +2 -0
  12. package/dist/src/cli/commands/digest.d.ts.map +1 -1
  13. package/dist/src/cli/commands/digest.js +6 -0
  14. package/dist/src/cli/commands/digest.js.map +1 -1
  15. package/dist/src/cli/commands/lens.d.ts +27 -0
  16. package/dist/src/cli/commands/lens.d.ts.map +1 -0
  17. package/dist/src/cli/commands/lens.js +200 -0
  18. package/dist/src/cli/commands/lens.js.map +1 -0
  19. package/dist/src/cli/index.d.ts.map +1 -1
  20. package/dist/src/cli/index.js +37 -0
  21. package/dist/src/cli/index.js.map +1 -1
  22. package/dist/src/digest/format.d.ts.map +1 -1
  23. package/dist/src/digest/format.js +1 -0
  24. package/dist/src/digest/format.js.map +1 -1
  25. package/dist/src/digest/frames.d.ts.map +1 -1
  26. package/dist/src/digest/frames.js +1 -1
  27. package/dist/src/digest/frames.js.map +1 -1
  28. package/dist/src/digest/ingest.d.ts +8 -1
  29. package/dist/src/digest/ingest.d.ts.map +1 -1
  30. package/dist/src/digest/ingest.js +2 -1
  31. package/dist/src/digest/ingest.js.map +1 -1
  32. package/dist/src/digest/lens-schema.d.ts +542 -0
  33. package/dist/src/digest/lens-schema.d.ts.map +1 -0
  34. package/dist/src/digest/lens-schema.js +108 -0
  35. package/dist/src/digest/lens-schema.js.map +1 -0
  36. package/dist/src/digest/lens-source.d.ts +260 -0
  37. package/dist/src/digest/lens-source.d.ts.map +1 -0
  38. package/dist/src/digest/lens-source.js +92 -0
  39. package/dist/src/digest/lens-source.js.map +1 -0
  40. package/dist/src/digest/lens-widget-schema.d.ts +1225 -0
  41. package/dist/src/digest/lens-widget-schema.d.ts.map +1 -0
  42. package/dist/src/digest/lens-widget-schema.js +80 -0
  43. package/dist/src/digest/lens-widget-schema.js.map +1 -0
  44. package/dist/src/digest/lens-widgets.d.ts +25 -0
  45. package/dist/src/digest/lens-widgets.d.ts.map +1 -0
  46. package/dist/src/digest/lens-widgets.js +225 -0
  47. package/dist/src/digest/lens-widgets.js.map +1 -0
  48. package/dist/src/digest/lint-lens-context.d.ts +102 -0
  49. package/dist/src/digest/lint-lens-context.d.ts.map +1 -0
  50. package/dist/src/digest/lint-lens-context.js +192 -0
  51. package/dist/src/digest/lint-lens-context.js.map +1 -0
  52. package/dist/src/digest/lint-lens.d.ts +17 -0
  53. package/dist/src/digest/lint-lens.d.ts.map +1 -0
  54. package/dist/src/digest/lint-lens.js +237 -0
  55. package/dist/src/digest/lint-lens.js.map +1 -0
  56. package/dist/src/digest/lint.d.ts +14 -0
  57. package/dist/src/digest/lint.d.ts.map +1 -1
  58. package/dist/src/digest/lint.js +156 -2
  59. package/dist/src/digest/lint.js.map +1 -1
  60. package/dist/src/digest/merge.d.ts.map +1 -1
  61. package/dist/src/digest/merge.js +11 -2
  62. package/dist/src/digest/merge.js.map +1 -1
  63. package/dist/src/digest/prose.d.ts.map +1 -1
  64. package/dist/src/digest/prose.js +8 -0
  65. package/dist/src/digest/prose.js.map +1 -1
  66. package/dist/src/digest/render-html.d.ts +5 -0
  67. package/dist/src/digest/render-html.d.ts.map +1 -1
  68. package/dist/src/digest/render-html.js +93 -18
  69. package/dist/src/digest/render-html.js.map +1 -1
  70. package/dist/src/digest/render-lens.d.ts +61 -0
  71. package/dist/src/digest/render-lens.d.ts.map +1 -0
  72. package/dist/src/digest/render-lens.js +273 -0
  73. package/dist/src/digest/render-lens.js.map +1 -0
  74. package/dist/src/digest/render-md.d.ts +5 -0
  75. package/dist/src/digest/render-md.d.ts.map +1 -1
  76. package/dist/src/digest/render-md.js +116 -7
  77. package/dist/src/digest/render-md.js.map +1 -1
  78. package/dist/src/digest/render-widgets.d.ts +13 -0
  79. package/dist/src/digest/render-widgets.d.ts.map +1 -0
  80. package/dist/src/digest/render-widgets.js +197 -0
  81. package/dist/src/digest/render-widgets.js.map +1 -0
  82. package/dist/src/digest/render.d.ts +2 -0
  83. package/dist/src/digest/render.d.ts.map +1 -1
  84. package/dist/src/digest/render.js +17 -3
  85. package/dist/src/digest/render.js.map +1 -1
  86. package/dist/src/digest/schema.d.ts +14683 -2022
  87. package/dist/src/digest/schema.d.ts.map +1 -1
  88. package/dist/src/digest/schema.js +67 -6
  89. package/dist/src/digest/schema.js.map +1 -1
  90. package/dist/src/digest/server.d.ts +2 -1
  91. package/dist/src/digest/server.d.ts.map +1 -1
  92. package/dist/src/digest/server.js +26 -11
  93. package/dist/src/digest/server.js.map +1 -1
  94. package/dist/src/digest/viewer-js.d.ts.map +1 -1
  95. package/dist/src/digest/viewer-js.js +120 -4
  96. package/dist/src/digest/viewer-js.js.map +1 -1
  97. package/dist/src/lens/attach.d.ts +436 -0
  98. package/dist/src/lens/attach.d.ts.map +1 -0
  99. package/dist/src/lens/attach.js +46 -0
  100. package/dist/src/lens/attach.js.map +1 -0
  101. package/dist/src/lens/ingest.d.ts +75 -0
  102. package/dist/src/lens/ingest.d.ts.map +1 -0
  103. package/dist/src/lens/ingest.js +558 -0
  104. package/dist/src/lens/ingest.js.map +1 -0
  105. package/dist/src/lens/kind.d.ts +11 -0
  106. package/dist/src/lens/kind.d.ts.map +1 -0
  107. package/dist/src/lens/kind.js +59 -0
  108. package/dist/src/lens/kind.js.map +1 -0
  109. package/dist/src/lens/knowledge.d.ts +53 -0
  110. package/dist/src/lens/knowledge.d.ts.map +1 -0
  111. package/dist/src/lens/knowledge.js +202 -0
  112. package/dist/src/lens/knowledge.js.map +1 -0
  113. package/dist/src/lens/notes.d.ts +73 -0
  114. package/dist/src/lens/notes.d.ts.map +1 -0
  115. package/dist/src/lens/notes.js +276 -0
  116. package/dist/src/lens/notes.js.map +1 -0
  117. package/dist/src/lens/panels.d.ts +10 -0
  118. package/dist/src/lens/panels.d.ts.map +1 -0
  119. package/dist/src/lens/panels.js +25 -0
  120. package/dist/src/lens/panels.js.map +1 -0
  121. package/dist/src/lens/pdf-extract-py.d.ts +15 -0
  122. package/dist/src/lens/pdf-extract-py.d.ts.map +1 -0
  123. package/dist/src/lens/pdf-extract-py.js +414 -0
  124. package/dist/src/lens/pdf-extract-py.js.map +1 -0
  125. package/dist/src/lens/podcast.d.ts +36 -0
  126. package/dist/src/lens/podcast.d.ts.map +1 -0
  127. package/dist/src/lens/podcast.js +104 -0
  128. package/dist/src/lens/podcast.js.map +1 -0
  129. package/dist/src/lens/python.d.ts +33 -0
  130. package/dist/src/lens/python.d.ts.map +1 -0
  131. package/dist/src/lens/python.js +61 -0
  132. package/dist/src/lens/python.js.map +1 -0
  133. package/dist/src/lens/web-extract-py.d.ts +9 -0
  134. package/dist/src/lens/web-extract-py.d.ts.map +1 -0
  135. package/dist/src/lens/web-extract-py.js +114 -0
  136. package/dist/src/lens/web-extract-py.js.map +1 -0
  137. package/dist/src/mcp/schemas.d.ts +3 -0
  138. package/dist/src/mcp/schemas.d.ts.map +1 -1
  139. package/dist/src/mcp/schemas.js +4 -0
  140. package/dist/src/mcp/schemas.js.map +1 -1
  141. package/dist/src/mcp/server.js +1 -1
  142. package/dist/src/mcp/server.js.map +1 -1
  143. package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -1
  144. package/dist/src/mcp/tools/digest-passthrough.js +5 -0
  145. package/dist/src/mcp/tools/digest-passthrough.js.map +1 -1
  146. package/dist/src/research/source-create.d.ts +6 -1
  147. package/dist/src/research/source-create.d.ts.map +1 -1
  148. package/dist/src/research/source-create.js +41 -10
  149. package/dist/src/research/source-create.js.map +1 -1
  150. package/package.json +1 -1
  151. package/plugin/.codex-plugin/plugin.json +1 -1
  152. package/plugin/.mcp.json +1 -1
  153. package/plugin/mcp.json +1 -1
  154. package/plugin/role-agents/lens-context/AGENT.md +94 -0
  155. package/plugin/role-agents/lens-link/AGENT.md +83 -0
  156. package/plugin/role-agents/lens-reader/AGENT.md +230 -0
  157. package/plugin/skills/_shared/proactive-routing.md +1 -0
  158. package/plugin/skills/lens/SKILL.md +375 -0
  159. package/schemas/gestalt.schema.json +13 -0
@@ -0,0 +1,59 @@
1
+ import { extname } from 'node:path';
2
+ export const AUDIO_EXTS = ['.mp3', '.m4a', '.wav', '.ogg', '.oga', '.opus', '.aac', '.flac'];
3
+ const WEB_EXTS = ['.html', '.htm'];
4
+ const YOUTUBE = /^https?:\/\/(?:www\.|m\.)?(?:youtube\.com|youtu\.be)\//i;
5
+ // 주소만으로 피드로 보는 꼴. 경로(쿼리와 # 앞)만 보고 호스트 feeds.와 쿼리 format=rss는 따로 본다.
6
+ // 경로는 끝이 /feed, /rss, 워드프레스 피드 꼴(/feed/podcast, /feed/rss2 같은), .rss, .xml일 때만 잡는다.
7
+ // 경로 중간의 /feed/는 글 주소(/feed/update/...)에도 있어서 안 잡는다. 애매하면 web으로 두고 스킬이 사용자에게 묻는다
8
+ const FEED_PATH = /(?:\/feed(?:\/(?:rss2?|atom|podcast))?|\/rss)\/?$|\.(?:rss|xml)$/i;
9
+ function parseUrl(input) {
10
+ try {
11
+ return new URL(input);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function isFeedUrl(url) {
18
+ if (FEED_PATH.test(url.pathname))
19
+ return true;
20
+ if (url.hostname.startsWith('feeds.'))
21
+ return true;
22
+ return url.searchParams.getAll('format').some((v) => v.toLowerCase() === 'rss');
23
+ }
24
+ /**
25
+ * 렌즈가 받은 주소나 파일이 무슨 자료인지 판단한다. 유튜브는 video-digest로 넘긴다.
26
+ * 순서는 유튜브, PDF, 오디오와 피드, 나머지 웹 글이다. 로컬 파일은 확장자로만 본다
27
+ */
28
+ export function detectLensKind(input) {
29
+ const trimmed = input.trim();
30
+ if (/^https?:\/\//i.test(trimmed)) {
31
+ if (YOUTUBE.test(trimmed))
32
+ return 'youtube';
33
+ const url = parseUrl(trimmed);
34
+ const path = (url?.pathname ?? '').toLowerCase();
35
+ if (path.endsWith('.pdf'))
36
+ return 'pdf';
37
+ if (/^https?:\/\/(?:www\.)?arxiv\.org\/(?:abs|pdf)\//i.test(trimmed))
38
+ return 'pdf';
39
+ if (AUDIO_EXTS.includes(extname(path)))
40
+ return 'podcast';
41
+ if (url && isFeedUrl(url))
42
+ return 'podcast';
43
+ return 'web';
44
+ }
45
+ const ext = extname(trimmed).toLowerCase();
46
+ if (ext === '.pdf')
47
+ return 'pdf';
48
+ if (AUDIO_EXTS.includes(ext))
49
+ return 'podcast';
50
+ if (WEB_EXTS.includes(ext))
51
+ return 'web';
52
+ return null;
53
+ }
54
+ /** arXiv 초록 주소는 PDF 주소로 바꾼다. 다른 주소는 그대로 둔다 */
55
+ export function pdfUrlOf(url) {
56
+ const m = /^https?:\/\/(?:www\.)?arxiv\.org\/abs\/([^?#]+)/i.exec(url);
57
+ return m ? `https://arxiv.org/pdf/${m[1]}` : url;
58
+ }
59
+ //# sourceMappingURL=kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kind.js","sourceRoot":"","sources":["../../../src/lens/kind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAC7F,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAEnC,MAAM,OAAO,GAAG,yDAAyD,CAAC;AAC1E,oEAAoE;AACpE,oFAAoF;AACpF,iFAAiF;AACjF,MAAM,SAAS,GAAG,mEAAmE,CAAC;AAEtF,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,GAAQ;IACzB,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;AAClF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,SAAS,CAAC;QAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,kDAAkD,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QACnF,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;QACzD,IAAI,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,kDAAkD,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvE,OAAO,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;AACpD,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { type KnowledgeFile } from '../digest/lint-lens-context.js';
2
+ /**
3
+ * 노션처럼 파일로 없는 노트를 스킬이 받아 두는 폴더(slug 폴더 아래). 파일 첫 줄에
4
+ * `<!-- source: <원래 주소> -->`를 적어 두면 연결 카드가 그 주소를 단다
5
+ */
6
+ export declare const KNOWLEDGE_SNAPSHOTS_DIR = "knowledge-snapshots";
7
+ /**
8
+ * 지식 소스 폴더. 사용자가 연결해 둔 폴더(~/.gestalt/lens.json)와 부를 때 넘긴 경로,
9
+ * gestalt.json의 `lens.knowledgeSources`를 합친다.
10
+ * gestalt.json은 남의 레포에도 있을 수 있어서 거기 적힌 경로는 실제 경로가 레포 안일 때만 받는다.
11
+ * 레포 밖을 가리키면(절대 경로, ../, 밖으로 나가는 심링크) 빼고 warnings에 남긴다
12
+ */
13
+ export declare function knowledgeRoots(repoRoot: string, extra?: string[], userFile?: string): {
14
+ roots: string[];
15
+ warnings: string[];
16
+ };
17
+ /**
18
+ * 원문에서 찾을 말. 두 번 넘게 나온 고유 이름(FlavorGraph, DIFUSCO)과 약어(NMI, CSP), 리포트 키워드다.
19
+ * 일상어로 찾으면 노트 줄마다 걸려서 이름 꼴만 고른다
20
+ */
21
+ export declare function pickTerms(texts: string[], keywords?: string[]): string[];
22
+ export interface NoteSearchLimits {
23
+ files: number;
24
+ totalBytes: number;
25
+ hits: number;
26
+ }
27
+ export interface NoteSearch {
28
+ hits: KnowledgeFile['hits'];
29
+ /** 자르기 전 겹친 줄 수 */
30
+ totalHits: number;
31
+ /** 파일 수나 읽기 크기, 겹친 줄 상한에 닿아 일부만 봤는지. large는 1MB 넘는 노트를 건너뛰었는지다 */
32
+ truncated: {
33
+ files: boolean;
34
+ bytes: boolean;
35
+ hits: boolean;
36
+ large: boolean;
37
+ };
38
+ }
39
+ /** limits는 테스트가 작은 상한을 넘길 때만 준다 */
40
+ export declare function searchNotes(roots: string[], terms: string[], limits?: NoteSearchLimits): NoteSearch;
41
+ export interface KnowledgeOptions {
42
+ repoRoot: string;
43
+ sources?: string[];
44
+ /** 사용자 노트 연결 파일. 테스트가 홈 폴더 대신 임시 파일을 넘긴다 */
45
+ userFile?: string;
46
+ }
47
+ /** 원문 이름과 겹치는 노트 줄을 찾아 knowledge.json에 둔다. lens-link가 여기서 연결 카드를 고른다 */
48
+ export declare function findKnowledge(slug: string, opts: KnowledgeOptions): {
49
+ path: string;
50
+ file: KnowledgeFile;
51
+ warnings: string[];
52
+ };
53
+ //# sourceMappingURL=knowledge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../../src/lens/knowledge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAapF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAQ7D;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAM,EAAO,EACpB,QAAQ,GAAE,MAA2B,GACpC;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAqCzC;AA+BD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAE5E;AAuBD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAQD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,mBAAmB;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,SAAS,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9E;AAED,mCAAmC;AACnC,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,GAAE,gBAAgC,GACvC,UAAU,CAqCZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B3D"}
@@ -0,0 +1,202 @@
1
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
3
+ import { writeJsonAtomic } from '../core/json-file.js';
4
+ import { KNOWLEDGE_FILE } from '../digest/lint-lens-context.js';
5
+ import { readUserLens, SKIP_DIRS, userLensFilePath } from './notes.js';
6
+ import { DigestStore, readRaw } from '../digest/store.js';
7
+ /** 노트로 읽는 확장자. 지식 소스 폴더에 다른 파일이 섞여 있어도 글 노트만 훑는다 */
8
+ const NOTE_EXTS = ['.md', '.markdown', '.txt'];
9
+ const MAX_FILES = 5000;
10
+ const MAX_FILE_BYTES = 1024 * 1024;
11
+ /** 한 번 찾을 때 읽는 노트 크기 합계 상한. 파일 수 상한만 두면 큰 노트가 많을 때 수 GB를 동기로 읽는다 */
12
+ const MAX_TOTAL_BYTES = 200 * 1024 * 1024;
13
+ const MAX_HITS = 40;
14
+ const MAX_TERMS = 30;
15
+ /**
16
+ * 노션처럼 파일로 없는 노트를 스킬이 받아 두는 폴더(slug 폴더 아래). 파일 첫 줄에
17
+ * `<!-- source: <원래 주소> -->`를 적어 두면 연결 카드가 그 주소를 단다
18
+ */
19
+ export const KNOWLEDGE_SNAPSHOTS_DIR = 'knowledge-snapshots';
20
+ /** 실제 경로가 parent 자신이거나 그 아래인지. 둘 다 realpath를 거친 값을 넘긴다 */
21
+ function within(parent, child) {
22
+ const rel = relative(parent, child);
23
+ return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
24
+ }
25
+ /**
26
+ * 지식 소스 폴더. 사용자가 연결해 둔 폴더(~/.gestalt/lens.json)와 부를 때 넘긴 경로,
27
+ * gestalt.json의 `lens.knowledgeSources`를 합친다.
28
+ * gestalt.json은 남의 레포에도 있을 수 있어서 거기 적힌 경로는 실제 경로가 레포 안일 때만 받는다.
29
+ * 레포 밖을 가리키면(절대 경로, ../, 밖으로 나가는 심링크) 빼고 warnings에 남긴다
30
+ */
31
+ export function knowledgeRoots(repoRoot, extra = [], userFile = userLensFilePath()) {
32
+ const roots = [];
33
+ const warnings = [];
34
+ const realDir = (p) => {
35
+ try {
36
+ const r = realpathSync(p);
37
+ return statSync(r).isDirectory() ? r : null;
38
+ }
39
+ catch {
40
+ // 없는 폴더는 건너뛴다. 결과의 roots에 안 실리니 보고에서 빠진 걸 알 수 있다
41
+ return null;
42
+ }
43
+ };
44
+ const push = (r) => {
45
+ if (r !== null && !roots.includes(r))
46
+ roots.push(r);
47
+ };
48
+ // 사용자가 연결해 둔 노트 폴더(~/.gestalt/lens.json)를 먼저 본다
49
+ for (const p of readUserLens(userFile).knowledgeSources)
50
+ push(realDir(p));
51
+ const raw = readRaw(join(repoRoot, 'gestalt.json'));
52
+ if (raw.kind === 'ok') {
53
+ const lens = raw.value.lens;
54
+ if (Array.isArray(lens?.knowledgeSources)) {
55
+ const repo = realDir(repoRoot) ?? resolve(repoRoot);
56
+ for (const p of lens.knowledgeSources) {
57
+ if (typeof p !== 'string')
58
+ continue;
59
+ const r = realDir(resolve(repoRoot, p));
60
+ if (r !== null && !within(repo, r)) {
61
+ warnings.push(`gestalt.json의 lens.knowledgeSources가 레포 밖을 가리켜 뺐어요: ${p}. 레포 밖 폴더는 gestalt lens notes add나 --source로 연결해요`);
62
+ continue;
63
+ }
64
+ push(r);
65
+ }
66
+ }
67
+ }
68
+ for (const p of extra)
69
+ push(realDir(resolve(p)));
70
+ return { roots, warnings };
71
+ }
72
+ /** 노트 파일을 훑는다. maxFiles를 넘는 파일이 더 있으면 거기서 멈추고 cut.files에 적는다 */
73
+ function* walkNotes(root, maxFiles, cut) {
74
+ let n = 0;
75
+ const stack = [root];
76
+ while (stack.length > 0) {
77
+ const dir = stack.pop();
78
+ let entries;
79
+ try {
80
+ entries = readdirSync(dir, { withFileTypes: true });
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ for (const e of entries) {
86
+ // 숨김 폴더와 의존성, 빌드 산출물 폴더는 노트가 아니다
87
+ if (e.name.startsWith('.'))
88
+ continue;
89
+ const p = join(dir, e.name);
90
+ if (e.isDirectory()) {
91
+ if (!SKIP_DIRS.has(e.name))
92
+ stack.push(p);
93
+ }
94
+ else if (e.isFile() && NOTE_EXTS.some((x) => e.name.toLowerCase().endsWith(x))) {
95
+ if (++n > maxFiles) {
96
+ cut.files = true;
97
+ return;
98
+ }
99
+ yield p;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * 원문에서 찾을 말. 두 번 넘게 나온 고유 이름(FlavorGraph, DIFUSCO)과 약어(NMI, CSP), 리포트 키워드다.
106
+ * 일상어로 찾으면 노트 줄마다 걸려서 이름 꼴만 고른다
107
+ */
108
+ export function pickTerms(texts, keywords = []) {
109
+ return rankTerms(texts, keywords).slice(0, MAX_TERMS);
110
+ }
111
+ /** 찾을 말 전부를 순서대로 낸다. findKnowledge가 상한에서 잘렸는지 알려고 자르기 전 목록을 본다 */
112
+ function rankTerms(texts, keywords) {
113
+ const counts = new Map();
114
+ for (const t of texts) {
115
+ for (const m of t.matchAll(/\b(?:[A-Z][a-z]+[A-Z][A-Za-z0-9]*|[A-Z][A-Z0-9]{2,}[a-z]?|[A-Z][a-z]+\d+[A-Za-z]*)\b/g)) {
116
+ counts.set(m[0], (counts.get(m[0]) ?? 0) + 1);
117
+ }
118
+ }
119
+ const names = [...counts]
120
+ .filter(([, n]) => n >= 2)
121
+ .sort((a, b) => b[1] - a[1])
122
+ .map(([w]) => w);
123
+ return [...new Set([...keywords, ...names])];
124
+ }
125
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
126
+ /** 영문자와 숫자에 붙은 자리는 말의 일부라 안 친다 */
127
+ const bounded = (body) => `(?<![A-Za-z0-9])${body}(?![A-Za-z0-9])`;
128
+ const SEARCH_LIMITS = {
129
+ files: MAX_FILES,
130
+ totalBytes: MAX_TOTAL_BYTES,
131
+ hits: MAX_HITS,
132
+ };
133
+ /** limits는 테스트가 작은 상한을 넘길 때만 준다 */
134
+ export function searchNotes(roots, terms, limits = SEARCH_LIMITS) {
135
+ const truncated = { files: false, bytes: false, hits: false, large: false };
136
+ if (terms.length === 0)
137
+ return { hits: [], totalHits: 0, truncated };
138
+ const patterns = terms.map((t) => ({ t, re: new RegExp(bounded(escapeRe(t)), 'i') }));
139
+ // 말을 하나로 합친 정규식으로 파일 전체를 먼저 걸러 걸린 파일만 줄마다 말별로 본다
140
+ const any = new RegExp(bounded(`(?:${terms.map(escapeRe).join('|')})`), 'i');
141
+ const hits = [];
142
+ let bytes = 0;
143
+ scan: for (const root of roots) {
144
+ for (const file of walkNotes(root, limits.files, truncated)) {
145
+ let text;
146
+ try {
147
+ const size = statSync(file).size;
148
+ if (size > MAX_FILE_BYTES) {
149
+ truncated.large = true;
150
+ continue;
151
+ }
152
+ if (bytes + size > limits.totalBytes) {
153
+ truncated.bytes = true;
154
+ break scan;
155
+ }
156
+ bytes += size;
157
+ text = readFileSync(file, 'utf-8');
158
+ }
159
+ catch {
160
+ continue;
161
+ }
162
+ if (!any.test(text))
163
+ continue;
164
+ text.split('\n').forEach((line, k) => {
165
+ const found = patterns.filter((p) => p.re.test(line)).map((p) => p.t);
166
+ if (found.length === 0)
167
+ return;
168
+ hits.push({ path: file, line: k + 1, text: line.trim().slice(0, 300), terms: found });
169
+ });
170
+ }
171
+ }
172
+ hits.sort((a, b) => b.terms.length - a.terms.length);
173
+ truncated.hits = hits.length > limits.hits;
174
+ return { hits: hits.slice(0, limits.hits), totalHits: hits.length, truncated };
175
+ }
176
+ /** 원문 이름과 겹치는 노트 줄을 찾아 knowledge.json에 둔다. lens-link가 여기서 연결 카드를 고른다 */
177
+ export function findKnowledge(slug, opts) {
178
+ const store = new DigestStore(opts.repoRoot);
179
+ const t = store.readTranscript(slug);
180
+ if (!t.ok)
181
+ throw new Error(t.message);
182
+ // 스킬이 노션 같은 클라우드 노트를 받아 둔 스냅샷 폴더도 노트 폴더로 본다
183
+ const snapshots = join(store.dir(slug), KNOWLEDGE_SNAPSHOTS_DIR);
184
+ const extra = [...(opts.sources ?? []), ...(existsSync(snapshots) ? [snapshots] : [])];
185
+ const { roots, warnings } = knowledgeRoots(resolve(opts.repoRoot), extra, opts.userFile);
186
+ const keywords = store.loadDigest(slug)?.reports[0]?.keywords.map((k) => k.term) ?? [];
187
+ const ranked = rankTerms([t.transcript.meta.title, ...t.transcript.segments.map((s) => s.text)], keywords);
188
+ const terms = ranked.slice(0, MAX_TERMS);
189
+ const found = searchNotes(roots, terms);
190
+ const file = {
191
+ roots,
192
+ terms,
193
+ hits: found.hits,
194
+ totalHits: found.totalHits,
195
+ // 하나라도 true면 lens-link와 사람이 "관련 노트가 이것뿐"으로 읽지 않게 한다
196
+ truncated: { ...found.truncated, terms: ranked.length > MAX_TERMS },
197
+ };
198
+ const path = join(store.dir(slug), KNOWLEDGE_FILE);
199
+ writeJsonAtomic(path, file);
200
+ return { path, file, warnings };
201
+ }
202
+ //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../../src/lens/knowledge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * 사용자 노트 폴더 연결. 노트 폴더는 사람마다 다른 개인 경로라 레포의 gestalt.json이 아니라
4
+ * 사용자 설정(~/.gestalt/lens.json)에 둔다. 한 번 연결하면 어느 레포에서 렌즈를 돌려도 같은 노트를 본다
5
+ */
6
+ export declare const userLensFileSchema: z.ZodObject<{
7
+ knowledgeSources: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
8
+ /** 노션 MCP로도 찾는다. 노션은 파일이 아니라 스킬이 페이지를 받아 스냅샷으로 둔다 */
9
+ notion: z.ZodDefault<z.ZodBoolean>;
10
+ /** 연결할 노트 폴더가 없다고 답했다. 다음 실행부터 다시 묻지 않는다 */
11
+ declined: z.ZodDefault<z.ZodBoolean>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ notion: boolean;
14
+ knowledgeSources: string[];
15
+ declined: boolean;
16
+ }, {
17
+ notion?: boolean | undefined;
18
+ knowledgeSources?: string[] | undefined;
19
+ declined?: boolean | undefined;
20
+ }>;
21
+ export type UserLensFile = z.infer<typeof userLensFileSchema>;
22
+ export declare function userLensFilePath(home?: string): string;
23
+ export declare function readUserLens(path?: string): UserLensFile;
24
+ /** 폴더를 연결한다. 실제 경로로 바꿔 겹치지 않게 넣고 없는 폴더는 거절한다 */
25
+ export declare function addNoteSources(dirs: string[], path?: string): UserLensFile;
26
+ export declare function removeNoteSource(dir: string, path?: string): UserLensFile;
27
+ export declare function setNotion(on: boolean, path?: string): UserLensFile;
28
+ export declare function declineNoteSources(path?: string): UserLensFile;
29
+ export interface NoteFolderCandidate {
30
+ path: string;
31
+ /** 폴더 안 노트 파일 수(3단 아래까지, 상한에서 멈춘다) */
32
+ notes: number;
33
+ /** 노트가 상한(2000개)보다 많아 세다 멈췄다. 이때 notes는 실제 수가 아니라 하한이다 */
34
+ notesCapped?: boolean;
35
+ /** 왜 후보가 됐는지. 이름(notes, kb 같은), Obsidian 보관함, Logseq 그래프, 노션 내보내기 폴더다 */
36
+ reason: 'name' | 'obsidian' | 'logseq' | 'notion-export';
37
+ /** 가장 최근에 고친 노트 이름 셋. 사용자가 어느 폴더인지 알아보게 보여 준다 */
38
+ recent: string[];
39
+ /** 가장 최근 노트를 고친 시각(ms) */
40
+ updatedAt: number;
41
+ configured: boolean;
42
+ }
43
+ /** 노트가 아닌 빌드 산출물과 의존성 폴더. knowledge.ts의 노트 찾기도 같은 목록으로 건너뛴다 */
44
+ export declare const SKIP_DIRS: Set<string>;
45
+ /**
46
+ * 탐지할 바탕 폴더. 이 레포와 그 옆 레포들(상위 폴더), 넘겨받은 폴더, 홈의 흔한 작업 폴더와 문서 폴더다.
47
+ * Orca가 없어도 이 목록만으로 돈다. Orca가 있으면 스킬이 등록된 레포를 scan으로 더 넘긴다
48
+ */
49
+ export declare function detectBases(repoRoot: string, home: string, scan?: string[]): string[];
50
+ /** 노션 앱이 깔려 있는지. 깔려 있으면 스킬이 노션 MCP로 찾을지 묻는다 */
51
+ export declare function notionAppInstalled(home?: string): boolean;
52
+ export interface DetectOptions {
53
+ repoRoot: string;
54
+ /** 더 훑을 폴더. 스킬이 Orca에 등록된 레포 경로를 넘긴다 */
55
+ scan?: string[];
56
+ home?: string;
57
+ userFile?: string;
58
+ /** 폴더 수 상한. 테스트가 작은 값을 넘긴다 */
59
+ visitCap?: number;
60
+ }
61
+ export interface DetectResult {
62
+ candidates: NoteFolderCandidate[];
63
+ /** 10개로 자르기 전 후보 수 */
64
+ totalCandidates: number;
65
+ /** 몫을 다 써서 끝까지 못 훑은 바탕 폴더. 여기 든 폴더 안의 후보는 빠졌을 수 있다 */
66
+ partialBases: string[];
67
+ }
68
+ /**
69
+ * 연결할 만한 노트 폴더를 찾는다. 노트가 3개 넘게 든 폴더만 후보로 내고 바깥 폴더에 든 안쪽 후보는 뺀다.
70
+ * 찾기만 하고 연결은 하지 않는다. 연결은 사용자가 고른 뒤 addNoteSources로 한다
71
+ */
72
+ export declare function detectNoteFolders(opts: DetectOptions): DetectResult;
73
+ //# sourceMappingURL=notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../../../src/lens/notes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AAEH,eAAO,MAAM,kBAAkB;;IAE7B,qDAAqD;;IAErD,4CAA4C;;;;;;;;;;EAE5C,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEjE;AAED,wBAAgB,YAAY,CAAC,IAAI,GAAE,MAA2B,GAAG,YAAY,CAM5E;AAOD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,MAA2B,GAAG,YAAY,CAW9F;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAA2B,GAAG,YAAY,CAU7F;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,GAAE,MAA2B,GAAG,YAAY,CAItF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAA2B,GAAG,YAAY,CAIlF;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,eAAe,CAAC;IACzD,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAMD,+DAA+D;AAC/D,eAAO,MAAM,SAAS,aAQpB,CAAC;AA8HH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA0BzF;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,OAAO,CAIpE;AAOD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,sBAAsB;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CA0BnE"}