sumulige-claude 1.1.2 → 1.2.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 (157) hide show
  1. package/.claude/.kickoff-hint.txt +3 -2
  2. package/.claude/CLAUDE.md +138 -0
  3. package/.claude/README.md +234 -43
  4. package/.claude/boris-optimizations.md +167 -0
  5. package/.claude/commands/todos.md +6 -41
  6. package/.claude/hooks/code-formatter.cjs +0 -0
  7. package/.claude/hooks/conversation-logger.cjs +222 -0
  8. package/.claude/hooks/multi-session.cjs +0 -0
  9. package/.claude/hooks/pre-commit.cjs +0 -0
  10. package/.claude/hooks/pre-push.cjs +0 -0
  11. package/.claude/hooks/project-kickoff.cjs +190 -1
  12. package/.claude/hooks/rag-skill-loader.cjs +0 -0
  13. package/.claude/hooks/session-restore.cjs +0 -0
  14. package/.claude/hooks/session-save.cjs +0 -0
  15. package/.claude/hooks/todo-manager.cjs +141 -262
  16. package/.claude/hooks/verify-work.cjs +0 -0
  17. package/.claude/quality-gate.json +9 -3
  18. package/.claude/rag/skill-index.json +128 -8
  19. package/.claude/settings.json +115 -0
  20. package/.claude/settings.local.json +16 -1
  21. package/.claude/skills/123-skill/SKILL.md +61 -0
  22. package/.claude/skills/123-skill/examples/basic.md +3 -0
  23. package/.claude/skills/123-skill/metadata.yaml +30 -0
  24. package/.claude/skills/123-skill/templates/default.md +3 -0
  25. package/.claude/skills/SKILLS.md +145 -0
  26. package/.claude/skills/code-reviewer-123/SKILL.md +61 -0
  27. package/.claude/skills/code-reviewer-123/examples/basic.md +3 -0
  28. package/.claude/skills/code-reviewer-123/metadata.yaml +30 -0
  29. package/.claude/skills/code-reviewer-123/templates/default.md +3 -0
  30. package/.claude/skills/examples/README.md +47 -0
  31. package/.claude/skills/examples/basic-task.md +67 -0
  32. package/.claude/skills/examples/bug-fix-workflow.md +92 -0
  33. package/.claude/skills/examples/feature-development.md +81 -0
  34. package/.claude/skills/manus-kickoff/SKILL.md +128 -0
  35. package/.claude/skills/manus-kickoff/examples/basic.md +84 -0
  36. package/.claude/skills/manus-kickoff/metadata.yaml +33 -0
  37. package/.claude/skills/manus-kickoff/templates/PROJECT_KICKOFF.md +89 -0
  38. package/.claude/skills/manus-kickoff/templates/PROJECT_PROPOSAL.md +227 -0
  39. package/.claude/skills/manus-kickoff/templates/TASK_PLAN.md +121 -0
  40. package/.claude/skills/my-skill/SKILL.md +61 -0
  41. package/.claude/skills/my-skill/examples/basic.md +3 -0
  42. package/.claude/skills/my-skill/metadata.yaml +30 -0
  43. package/.claude/skills/my-skill/templates/default.md +3 -0
  44. package/.claude/skills/template/metadata.yaml +30 -0
  45. package/.claude/skills/test-skill-name/SKILL.md +61 -0
  46. package/.claude/skills/test-skill-name/examples/basic.md +3 -0
  47. package/.claude/skills/test-skill-name/metadata.yaml +30 -0
  48. package/.claude/skills/test-skill-name/templates/default.md +3 -0
  49. package/.claude/templates/PROJECT_KICKOFF.md +89 -0
  50. package/.claude/templates/PROJECT_PROPOSAL.md +227 -0
  51. package/.claude/templates/TASK_PLAN.md +121 -0
  52. package/.claude/templates/hooks/README.md +302 -0
  53. package/.claude/templates/hooks/hook.sh.template +94 -0
  54. package/.claude/templates/hooks/user-prompt-submit.cjs.template +116 -0
  55. package/.claude/templates/hooks/user-response-submit.cjs.template +94 -0
  56. package/.claude/templates/hooks/validate.js +173 -0
  57. package/.claude/workflow/document-scanner.js +426 -0
  58. package/.claude/workflow/knowledge-engine.js +941 -0
  59. package/.claude/workflow/notebooklm/browser.js +1028 -0
  60. package/.claude/workflow/phases/phase1-research.js +578 -0
  61. package/.claude/workflow/phases/phase1-research.ts +465 -0
  62. package/.claude/workflow/phases/phase2-approve.js +722 -0
  63. package/.claude/workflow/phases/phase3-plan.js +1200 -0
  64. package/.claude/workflow/phases/phase4-develop.js +894 -0
  65. package/.claude/workflow/search-cache.js +230 -0
  66. package/.claude/workflow/templates/approval.md +315 -0
  67. package/.claude/workflow/templates/development.md +377 -0
  68. package/.claude/workflow/templates/planning.md +328 -0
  69. package/.claude/workflow/templates/research.md +250 -0
  70. package/.claude/workflow/types.js +37 -0
  71. package/.claude/workflow/web-search.js +278 -0
  72. package/.claude-plugin/marketplace.json +2 -2
  73. package/AGENTS.md +200 -0
  74. package/CHANGELOG.md +25 -14
  75. package/CLAUDE-template.md +114 -0
  76. package/README.md +16 -1
  77. package/cli.js +20 -0
  78. package/config/official-skills.json +2 -2
  79. package/config/quality-gate.json +9 -3
  80. package/development/cache/web-search/search_1193d605f8eb364651fc2f2041b58a31.json +36 -0
  81. package/development/cache/web-search/search_3798bf06960edc125f744a1abb5b72c5.json +36 -0
  82. package/development/cache/web-search/search_37c7d4843a53f0d83f1122a6f908a2a3.json +36 -0
  83. package/development/cache/web-search/search_44166fa0153709ee168485a22aa0ab40.json +36 -0
  84. package/development/cache/web-search/search_4deaebb1f77e86a8ca066dc5a49c59fd.json +36 -0
  85. package/development/cache/web-search/search_94da91789466070a7f545612e73c7372.json +36 -0
  86. package/development/cache/web-search/search_dd5de8491b8b803a3cb01339cd210fb0.json +36 -0
  87. package/development/knowledge-base/.index.clean.json +0 -0
  88. package/development/knowledge-base/.index.json +486 -0
  89. package/development/knowledge-base/test-best-practices.md +29 -0
  90. package/development/projects/proj_mkh1pazz_ixmt1/phase1/feasibility-report.md +160 -0
  91. package/development/projects/proj_mkh4jvnb_z7rwf/phase1/feasibility-report.md +160 -0
  92. package/development/projects/proj_mkh4jxkd_ewz5a/phase1/feasibility-report.md +160 -0
  93. package/development/projects/proj_mkh4k84n_ni73k/phase1/feasibility-report.md +160 -0
  94. package/development/projects/proj_mkh4wfyd_u9w88/phase1/feasibility-report.md +160 -0
  95. package/development/projects/proj_mkh4wsbo_iahvf/development/projects/proj_mkh4xbpg_4na5w/phase1/feasibility-report.md +160 -0
  96. package/development/projects/proj_mkh4wsbo_iahvf/phase1/feasibility-report.md +160 -0
  97. package/development/projects/proj_mkh4xulg_1ka8x/phase1/feasibility-report.md +160 -0
  98. package/development/projects/proj_mkh4xwhj_gch8j/phase1/feasibility-report.md +160 -0
  99. package/development/projects/proj_mkh4y2qk_9lm8z/phase1/feasibility-report.md +160 -0
  100. package/development/projects/proj_mkh4y2qk_9lm8z/phase2/requirements.md +226 -0
  101. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/PRD.md +345 -0
  102. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/TASK_PLAN.md +284 -0
  103. package/development/projects/proj_mkh4y2qk_9lm8z/phase3/prototype/README.md +14 -0
  104. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/DEVELOPMENT_LOG.md +35 -0
  105. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/TASKS.md +34 -0
  106. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/.env.example +5 -0
  107. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/README.md +60 -0
  108. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/package.json +25 -0
  109. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/index.js +70 -0
  110. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/src/routes/index.js +48 -0
  111. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/health.test.js +20 -0
  112. package/development/projects/proj_mkh4y2qk_9lm8z/phase4/source/tests/jest.config.js +21 -0
  113. package/development/projects/proj_mkh7veqg_3lypc/phase1/feasibility-report.md +160 -0
  114. package/development/projects/proj_mkh7veqg_3lypc/phase2/requirements.md +226 -0
  115. package/development/projects/proj_mkh7veqg_3lypc/phase3/PRD.md +345 -0
  116. package/development/projects/proj_mkh7veqg_3lypc/phase3/TASK_PLAN.md +284 -0
  117. package/development/projects/proj_mkh7veqg_3lypc/phase3/prototype/README.md +14 -0
  118. package/development/projects/proj_mkh8k8fo_rmqn5/phase1/feasibility-report.md +160 -0
  119. package/development/projects/proj_mkh8xyhy_1vshq/phase1/feasibility-report.md +178 -0
  120. package/development/projects/proj_mkh8zddd_dhamf/phase1/feasibility-report.md +377 -0
  121. package/development/projects/proj_mkh8zddd_dhamf/phase2/requirements.md +442 -0
  122. package/development/projects/proj_mkh8zddd_dhamf/phase3/api-design.md +800 -0
  123. package/development/projects/proj_mkh8zddd_dhamf/phase3/architecture.md +625 -0
  124. package/development/projects/proj_mkh8zddd_dhamf/phase3/data-model.md +830 -0
  125. package/development/projects/proj_mkh8zddd_dhamf/phase3/risks.md +957 -0
  126. package/development/projects/proj_mkh8zddd_dhamf/phase3/wbs.md +381 -0
  127. package/development/todos/.state.json +14 -1
  128. package/development/todos/INDEX.md +31 -73
  129. package/development/todos/completed/develop/local-knowledge-index.md +85 -0
  130. package/development/todos/{active → completed/develop}/todo-system.md +13 -3
  131. package/development/todos/completed/develop/web-search-integration.md +83 -0
  132. package/development/todos/completed/test/phase1-e2e-test.md +103 -0
  133. package/jest.config.js +3 -1
  134. package/lib/commands.js +1799 -992
  135. package/lib/marketplace.js +1 -0
  136. package/package.json +3 -2
  137. package/project-paradigm.md +313 -0
  138. package/prompts/how-to-find.md +163 -0
  139. package/tests/commands.test.js +940 -17
  140. package/tests/config-manager.test.js +677 -0
  141. package/tests/config-schema.test.js +425 -0
  142. package/tests/config-validator.test.js +436 -0
  143. package/tests/errors.test.js +477 -0
  144. package/tests/manual/phase1-e2e.sh +389 -0
  145. package/tests/manual/phase2-test-cases.md +311 -0
  146. package/tests/manual/phase3-test-cases.md +309 -0
  147. package/tests/manual/phase4-test-cases.md +414 -0
  148. package/tests/manual/test-cases.md +417 -0
  149. package/tests/marketplace.test.js +330 -214
  150. package/tests/quality-gate.test.js +679 -0
  151. package/tests/quality-rules.test.js +619 -0
  152. package/tests/sync-external.test.js +214 -0
  153. package/tests/update-registry.test.js +251 -0
  154. package/tests/utils.test.js +12 -8
  155. package/tests/version-check.test.js +75 -0
  156. package/tests/web-search.test.js +392 -0
  157. package/thinkinglens-silent.md +138 -0
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * [Hook Name] - UserResponseSubmit Hook Template
4
+ *
5
+ * 触发时机: AI 返回响应后
6
+ * 环境变量:
7
+ * - CLAUDE_PROJECT_DIR: 项目根目录
8
+ * - CLAUDE_RESPONSE_CONTENT: 响应内容
9
+ *
10
+ * @version 1.0.0
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // ============================================================
17
+ // 标准头部 - 所有 hooks 必须包含
18
+ // ============================================================
19
+
20
+ const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
21
+
22
+ // 如果不在 Claude Code 环境中运行,静默退出
23
+ if (!process.env.CLAUDE_PROJECT_DIR) {
24
+ process.exit(0);
25
+ }
26
+
27
+ // ============================================================
28
+ // 配置区域
29
+ // ============================================================
30
+
31
+ const CONFIG = {
32
+ enabled: true,
33
+ verbose: false,
34
+ };
35
+
36
+ // ============================================================
37
+ // 工具函数
38
+ // ============================================================
39
+
40
+ function log(...args) {
41
+ if (CONFIG.verbose) {
42
+ console.error('[Hook]', ...args);
43
+ }
44
+ }
45
+
46
+ function safeReadFile(filePath, defaultValue = '') {
47
+ try {
48
+ return fs.readFileSync(filePath, 'utf-8');
49
+ } catch (e) {
50
+ return defaultValue;
51
+ }
52
+ }
53
+
54
+ function safeWriteFile(filePath, content) {
55
+ try {
56
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
57
+ fs.writeFileSync(filePath, content, 'utf-8');
58
+ return true;
59
+ } catch (e) {
60
+ log('Write error:', e.message);
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // ============================================================
66
+ // Hook 核心逻辑
67
+ // ============================================================
68
+
69
+ function main() {
70
+ try {
71
+ const responseContent = process.env.CLAUDE_RESPONSE_CONTENT || '';
72
+
73
+ log('Response length:', responseContent.length);
74
+
75
+ // ===== 在这里实现你的 hook 逻辑 =====
76
+
77
+ // 示例: 记录响应统计
78
+ const statsFile = path.join(PROJECT_DIR, '.claude/hooks/.response-stats.json');
79
+ const stats = JSON.parse(safeReadFile(statsFile, '{}'));
80
+ stats.totalResponses = (stats.totalResponses || 0) + 1;
81
+ stats.totalChars = (stats.totalChars || 0) + responseContent.length;
82
+ stats.lastUpdate = new Date().toISOString();
83
+ safeWriteFile(statsFile, JSON.stringify(stats, null, 2));
84
+
85
+ // ===== Hook 逻辑结束 =====
86
+
87
+ } catch (error) {
88
+ log('Error:', error.message);
89
+ }
90
+
91
+ process.exit(0);
92
+ }
93
+
94
+ main();
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hook Validator - 检查 hook 是否符合最佳实践
4
+ *
5
+ * 使用方法:
6
+ * node validate.js [hook-file]
7
+ *
8
+ * 示例:
9
+ * node validate.js ../my-hook.cjs
10
+ * node validate.js *.cjs
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const CHECKS = {
17
+ ENV_FALLBACK: {
18
+ name: '环境变量 fallback',
19
+ pattern: /process\.env\.CLAUDE_PROJECT_DIR\s*\|\|/,
20
+ required: true,
21
+ error: '缺少 CLAUDE_PROJECT_DIR fallback (|| process.cwd())'
22
+ },
23
+
24
+ SILENT_EXIT: {
25
+ name: '静默退出检查',
26
+ pattern: /if\s*\(\s*!process\.env\.CLAUDE_PROJECT_DIR/,
27
+ required: true,
28
+ error: '缺少非项目环境的静默退出检查'
29
+ },
30
+
31
+ ERROR_HANDLING: {
32
+ name: '错误处理',
33
+ pattern: /try\s*\{/,
34
+ required: true,
35
+ error: '缺少 try-catch 错误处理'
36
+ },
37
+
38
+ SAFE_EXIT: {
39
+ name: '安全退出',
40
+ pattern: /process\.exit\(0\)/,
41
+ required: true,
42
+ error: '缺少明确的 process.exit(0)'
43
+ },
44
+
45
+ NO_STDOUT: {
46
+ name: '避免 stdout 输出',
47
+ pattern: /console\.log\(/,
48
+ required: false,
49
+ error: '检测到 console.log - 建议使用 console.error 用于调试'
50
+ },
51
+
52
+ SAFE_MKDIR: {
53
+ name: '安全创建目录',
54
+ pattern: /mkdirSync.*recursive/,
55
+ required: true,
56
+ error: '目录创建缺少 { recursive: true } 选项'
57
+ }
58
+ };
59
+
60
+ /**
61
+ * 验证单个 hook 文件
62
+ */
63
+ function validateHook(filePath) {
64
+ const results = {
65
+ file: filePath,
66
+ passed: 0,
67
+ failed: 0,
68
+ warnings: 0,
69
+ checks: []
70
+ };
71
+
72
+ if (!fs.existsSync(filePath)) {
73
+ results.error = '文件不存在';
74
+ return results;
75
+ }
76
+
77
+ const content = fs.readFileSync(filePath, 'utf-8');
78
+
79
+ for (const [key, check] of Object.entries(CHECKS)) {
80
+ const passed = check.pattern.test(content);
81
+
82
+ if (check.required) {
83
+ if (passed) {
84
+ results.passed++;
85
+ } else {
86
+ results.failed++;
87
+ }
88
+ } else {
89
+ // 可选检查作为警告
90
+ if (passed) {
91
+ results.warnings++;
92
+ }
93
+ }
94
+
95
+ results.checks.push({
96
+ name: check.name,
97
+ passed: check.required ? passed : !passed,
98
+ required: check.required,
99
+ error: check.error
100
+ });
101
+ }
102
+
103
+ return results;
104
+ }
105
+
106
+ /**
107
+ * 打印验证结果
108
+ */
109
+ function printResults(results) {
110
+ console.log(`\n📎 ${path.basename(results.file)}`);
111
+ console.log('─'.repeat(50));
112
+
113
+ if (results.error) {
114
+ console.log(`❌ ${results.error}`);
115
+ return;
116
+ }
117
+
118
+ for (const check of results.checks) {
119
+ if (check.passed) {
120
+ console.log(`✅ ${check.name}`);
121
+ } else if (check.required) {
122
+ console.log(`❌ ${check.name}`);
123
+ console.log(` ${check.error}`);
124
+ } else {
125
+ console.log(`⚠️ ${check.name}`);
126
+ console.log(` ${check.error}`);
127
+ }
128
+ }
129
+
130
+ const total = results.passed + results.failed;
131
+ const score = total > 0 ? Math.round((results.passed / total) * 100) : 0;
132
+
133
+ console.log('─'.repeat(50));
134
+ console.log(`得分: ${score}% | ✅ ${results.passed} | ❌ ${results.failed} | ⚠️ ${results.warnings}`);
135
+ }
136
+
137
+ /**
138
+ * 主函数
139
+ */
140
+ function main() {
141
+ const args = process.argv.slice(2);
142
+
143
+ if (args.length === 0) {
144
+ console.log(`
145
+ 🔍 Hook Validator - 检查 hook 最佳实践
146
+
147
+ 使用方法:
148
+ node validate.js <hook-file>
149
+ node validate.js *.cjs
150
+
151
+ 示例:
152
+ node validate.js my-hook.cjs
153
+ node validate.js ../*.cjs
154
+ `);
155
+ process.exit(0);
156
+ }
157
+
158
+ let allPassed = true;
159
+
160
+ for (const arg of args) {
161
+ const results = validateHook(arg);
162
+ printResults(results);
163
+
164
+ if (results.failed > 0) {
165
+ allPassed = false;
166
+ }
167
+ }
168
+
169
+ console.log('');
170
+ process.exit(allPassed ? 0 : 1);
171
+ }
172
+
173
+ main();
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Document Scanner - Extract content and metadata from local files
3
+ *
4
+ * Supports:
5
+ * - Text extraction from multiple file formats
6
+ * - Metadata extraction (word count, headings, links)
7
+ * - Content checksum for change detection
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const crypto = require('crypto');
13
+
14
+ // ============================================================================
15
+ // Configuration
16
+ // ============================================================================
17
+
18
+ const MAX_CONTENT_SIZE = 500 * 1024; // 500KB - don't store content if larger
19
+ const MAX_SNIPPET_SIZE = 2000; // Store snippet for large files
20
+
21
+ // Supported file types for content scanning
22
+ const SCANNABLE_TYPES = [
23
+ '.md', '.markdown', // Markdown
24
+ '.txt', // Plain text
25
+ '.json', '.yaml', '.yml', // Config files
26
+ '.js', '.ts', '.jsx', '.tsx', // JavaScript/TypeScript
27
+ '.py', '.rs', '.go', '.java', '.c', '.cpp', '.h', '.hpp', // Code
28
+ '.sh', '.bash', '.zsh', '.fish', // Shell scripts
29
+ '.css', '.scss', '.less', // Stylesheets
30
+ '.html', '.htm', '.xml', // Markup
31
+ '.sql', '.graphql', '.gql' // Query languages
32
+ ];
33
+
34
+ // ============================================================================
35
+ // Document Scanner Class
36
+ // ============================================================================
37
+
38
+ class DocumentScanner {
39
+ /**
40
+ * Check if a file type is scannable
41
+ */
42
+ static isScannable(filePath) {
43
+ const ext = path.extname(filePath).toLowerCase();
44
+ return SCANNABLE_TYPES.includes(ext);
45
+ }
46
+
47
+ /**
48
+ * Scan a file and extract metadata
49
+ */
50
+ static scanFile(filePath, options = {}) {
51
+ const {
52
+ includeContent = true,
53
+ maxContentSize = MAX_CONTENT_SIZE
54
+ } = options;
55
+
56
+ if (!fs.existsSync(filePath)) {
57
+ throw new Error(`File not found: ${filePath}`);
58
+ }
59
+
60
+ const stats = fs.statSync(filePath);
61
+ const ext = path.extname(filePath).toLowerCase();
62
+
63
+ // Basic metadata
64
+ const metadata = {
65
+ path: filePath,
66
+ size: stats.size,
67
+ lastModified: stats.mtimeMs,
68
+ contentType: this.getMimeType(ext),
69
+ extension: ext
70
+ };
71
+
72
+ // If file is too large or not scannable, skip content
73
+ if (!this.isScannable(filePath) || stats.size === 0) {
74
+ return {
75
+ ...metadata,
76
+ scannable: false,
77
+ wordCount: 0,
78
+ headings: [],
79
+ links: [],
80
+ };
81
+ }
82
+
83
+ // Read file content
84
+ let content;
85
+ try {
86
+ content = fs.readFileSync(filePath, 'utf-8');
87
+ } catch (error) {
88
+ return {
89
+ ...metadata,
90
+ scannable: false,
91
+ error: error.message
92
+ };
93
+ }
94
+
95
+ // Calculate checksum
96
+ const checksum = crypto
97
+ .createHash('md5')
98
+ .update(content)
99
+ .digest('hex');
100
+
101
+ // Extract metadata based on file type
102
+ const extracted = this.extractMetadata(content, ext);
103
+
104
+ // Determine whether to store full content or just snippet
105
+ const shouldStoreContent = includeContent && content.length <= maxContentSize;
106
+
107
+ return {
108
+ ...metadata,
109
+ scannable: true,
110
+ checksum,
111
+ wordCount: extracted.wordCount,
112
+ lineCount: extracted.lineCount,
113
+ headings: extracted.headings,
114
+ links: extracted.links,
115
+ codeBlocks: extracted.codeBlocks,
116
+ frontMatter: extracted.frontMatter,
117
+ // Content or snippet
118
+ content: shouldStoreContent ? content : null,
119
+ snippet: shouldStoreContent ? null : content.substring(0, MAX_SNIPPET_SIZE)
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Extract metadata from content based on file type
125
+ */
126
+ static extractMetadata(content, ext) {
127
+ const lines = content.split('\n');
128
+ const wordCount = this.countWords(content);
129
+ const lineCount = lines.length;
130
+
131
+ let headings = [];
132
+ let links = [];
133
+ let codeBlocks = [];
134
+ let frontMatter = null;
135
+
136
+ // Markdown-specific extraction
137
+ if (['.md', '.markdown'].includes(ext)) {
138
+ const mdResult = this.extractMarkdownMetadata(content);
139
+ headings = mdResult.headings;
140
+ links = mdResult.links;
141
+ codeBlocks = mdResult.codeBlocks;
142
+ frontMatter = mdResult.frontMatter;
143
+ }
144
+ // Code-specific extraction
145
+ else if (['.js', '.ts', '.jsx', '.tsx', '.py', '.rs', '.go'].includes(ext)) {
146
+ const codeResult = this.extractCodeMetadata(content, ext);
147
+ headings = codeResult.headings; // Functions/classes as headings
148
+ links = codeResult.links; // Import/require statements
149
+ }
150
+
151
+ return {
152
+ wordCount,
153
+ lineCount,
154
+ headings,
155
+ links,
156
+ codeBlocks,
157
+ frontMatter
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Extract Markdown-specific metadata
163
+ */
164
+ static extractMarkdownMetadata(content) {
165
+ const headings = [];
166
+ const links = [];
167
+ const codeBlocks = [];
168
+ let frontMatter = null;
169
+
170
+ const lines = content.split('\n');
171
+ let inCodeBlock = false;
172
+ let codeBlockLang = null;
173
+ let currentCodeBlock = [];
174
+
175
+ // Check for YAML front matter
176
+ if (lines[0] === '---') {
177
+ const endIdx = lines.slice(1).findIndex(line => line === '---');
178
+ if (endIdx > 0) {
179
+ const frontMatterContent = lines.slice(1, endIdx + 1).join('\n');
180
+ frontMatter = this.parseFrontMatter(frontMatterContent);
181
+ }
182
+ }
183
+
184
+ for (let i = 0; i < lines.length; i++) {
185
+ const line = lines[i];
186
+
187
+ // Track code blocks
188
+ if (line.startsWith('```')) {
189
+ if (!inCodeBlock) {
190
+ inCodeBlock = true;
191
+ codeBlockLang = line.substring(3).trim() || 'text';
192
+ currentCodeBlock = [];
193
+ } else {
194
+ codeBlocks.push({
195
+ language: codeBlockLang,
196
+ lineStart: i - currentCodeBlock.length,
197
+ preview: currentCodeBlock.slice(0, 3).join('\n')
198
+ });
199
+ inCodeBlock = false;
200
+ currentCodeBlock = [];
201
+ }
202
+ continue;
203
+ }
204
+
205
+ if (inCodeBlock) {
206
+ currentCodeBlock.push(line);
207
+ continue;
208
+ }
209
+
210
+ // Extract headings
211
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
212
+ if (headingMatch) {
213
+ const level = headingMatch[1].length;
214
+ const text = headingMatch[2].trim();
215
+ headings.push({ level, text, line: i + 1 });
216
+ }
217
+
218
+ // Extract links
219
+ const linkMatch = line.match(/\[([^\]]+)\]\(([^)]+)\)/g);
220
+ if (linkMatch) {
221
+ linkMatch.forEach(link => {
222
+ const match = link.match(/\[([^\]]+)\]\(([^)]+)\)/);
223
+ if (match) {
224
+ links.push({ text: match[1], url: match[2] });
225
+ }
226
+ });
227
+ }
228
+ }
229
+
230
+ return { headings, links, codeBlocks, frontMatter };
231
+ }
232
+
233
+ /**
234
+ * Extract code-specific metadata (functions, classes, imports)
235
+ */
236
+ static extractCodeMetadata(content, ext) {
237
+ const headings = [];
238
+ const links = [];
239
+
240
+ const lines = content.split('\n');
241
+
242
+ // Patterns for different languages
243
+ const patterns = {
244
+ '.js': { func: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, class: /^\s*class\s+(\w+)/, import: /^\s*import\s+.*from\s+['"]([^'"]+)['"]/ },
245
+ '.ts': { func: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, class: /^\s*class\s+(\w+)/, import: /^\s*import\s+.*from\s+['"]([^'"]+)['"]/ },
246
+ '.jsx': { func: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, class: /^\s*class\s+(\w+)/, import: /^\s*import\s+.*from\s+['"]([^'"]+)['"]/ },
247
+ '.tsx': { func: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, class: /^\s*class\s+(\w+)/, import: /^\s*import\s+.*from\s+['"]([^'"]+)['"]/ },
248
+ '.py': { func: /^\s*def\s+(\w+)\s*\(/, class: /^\s*class\s+(\w+)\s*:/, import: /^\s*(?:import|from)\s+(\w+)/ },
249
+ '.rs': { func: /^\s*(?:pub\s+)?fn\s+(\w+)\s*\(/, class: /^\s*(?:pub\s+)?(struct|enum|trait)\s+(\w+)/, import: /^\s*use\s+([^;]+);/ },
250
+ '.go': { func: /^\s*func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/, class: /^\s*type\s+(\w+)\s+struct/, import: /^\s*import\s+(?:\(|")([^")]+)/ }
251
+ };
252
+
253
+ const lang = patterns[ext] || patterns['.js'];
254
+
255
+ for (let i = 0; i < lines.length; i++) {
256
+ const line = lines[i];
257
+
258
+ // Extract functions
259
+ const funcMatch = line.match(lang.func);
260
+ if (funcMatch) {
261
+ headings.push({ type: 'function', name: funcMatch[1], line: i + 1 });
262
+ }
263
+
264
+ // Extract classes
265
+ const classMatch = line.match(lang.class);
266
+ if (classMatch) {
267
+ const name = classMatch[2] || classMatch[1];
268
+ headings.push({ type: 'class', name, line: i + 1 });
269
+ }
270
+
271
+ // Extract imports
272
+ const importMatch = line.match(lang.import);
273
+ if (importMatch) {
274
+ links.push({ type: 'import', name: importMatch[1], line: i + 1 });
275
+ }
276
+ }
277
+
278
+ return { headings, links };
279
+ }
280
+
281
+ /**
282
+ * Parse YAML front matter
283
+ */
284
+ static parseFrontMatter(content) {
285
+ const result = {};
286
+ const lines = content.split('\n');
287
+
288
+ for (const line of lines) {
289
+ const match = line.match(/^(\w+):\s*(.+)$/);
290
+ if (match) {
291
+ result[match[1]] = match[2].trim();
292
+ }
293
+ }
294
+
295
+ return result;
296
+ }
297
+
298
+ /**
299
+ * Count words in content (rough estimate for mixed content)
300
+ */
301
+ static countWords(content) {
302
+ // For code files, count tokens more accurately
303
+ // For text files, count words
304
+ const tokens = content
305
+ .replace(/\s+/g, ' ')
306
+ .replace(/[{}();,.<>[\]]/g, ' ')
307
+ .split(' ')
308
+ .filter(t => t.length > 0);
309
+
310
+ return tokens.length;
311
+ }
312
+
313
+ /**
314
+ * Get MIME type for extension
315
+ */
316
+ static getMimeType(ext) {
317
+ const mimeTypes = {
318
+ '.md': 'text/markdown',
319
+ '.markdown': 'text/markdown',
320
+ '.txt': 'text/plain',
321
+ '.json': 'application/json',
322
+ '.yaml': 'text/yaml',
323
+ '.yml': 'text/yaml',
324
+ '.js': 'text/javascript',
325
+ '.ts': 'text/typescript',
326
+ '.jsx': 'text/jsx',
327
+ '.tsx': 'text/tsx',
328
+ '.py': 'text/x-python',
329
+ '.rs': 'text/x-rust',
330
+ '.go': 'text/x-go',
331
+ '.java': 'text/x-java',
332
+ '.c': 'text/x-c',
333
+ '.cpp': 'text/x-c++',
334
+ '.h': 'text/x-c',
335
+ '.hpp': 'text/x-c++',
336
+ '.sh': 'text/x-shellscript',
337
+ '.bash': 'text/x-shellscript',
338
+ '.css': 'text/css',
339
+ '.scss': 'text/x-scss',
340
+ '.less': 'text/x-less',
341
+ '.html': 'text/html',
342
+ '.htm': 'text/html',
343
+ '.xml': 'text/xml',
344
+ '.sql': 'text/x-sql',
345
+ '.graphql': 'text/x-graphql',
346
+ '.gql': 'text/x-graphql'
347
+ };
348
+ return mimeTypes[ext.toLowerCase()] || 'text/plain';
349
+ }
350
+
351
+ /**
352
+ * Scan a directory recursively
353
+ */
354
+ static scanDirectory(dirPath, options = {}) {
355
+ const {
356
+ recursive = true,
357
+ maxDepth = 10,
358
+ includePatterns = [],
359
+ excludePatterns = ['node_modules', '.git', 'dist', 'build', 'coverage']
360
+ } = options;
361
+
362
+ if (!fs.existsSync(dirPath)) {
363
+ throw new Error(`Directory not found: ${dirPath}`);
364
+ }
365
+
366
+ const results = [];
367
+ const scanQueue = [{ dir: dirPath, depth: 0 }];
368
+
369
+ while (scanQueue.length > 0) {
370
+ const { dir, depth } = scanQueue.shift();
371
+
372
+ if (depth > maxDepth) continue;
373
+
374
+ let entries;
375
+ try {
376
+ entries = fs.readdirSync(dir, { withFileTypes: true });
377
+ } catch (error) {
378
+ continue; // Skip directories we can't read
379
+ }
380
+
381
+ for (const entry of entries) {
382
+ // Skip excluded directories
383
+ if (entry.isDirectory() && excludePatterns.includes(entry.name)) {
384
+ continue;
385
+ }
386
+
387
+ const fullPath = path.join(dir, entry.name);
388
+
389
+ if (entry.isDirectory() && recursive) {
390
+ scanQueue.push({ dir: fullPath, depth: depth + 1 });
391
+ } else if (entry.isFile()) {
392
+ // Check include patterns
393
+ if (includePatterns.length > 0) {
394
+ const matches = includePatterns.some(pattern => {
395
+ if (pattern instanceof RegExp) {
396
+ return pattern.test(fullPath);
397
+ }
398
+ return fullPath.includes(pattern);
399
+ });
400
+ if (!matches) continue;
401
+ }
402
+
403
+ try {
404
+ const scanResult = this.scanFile(fullPath, options);
405
+ results.push(scanResult);
406
+ } catch (error) {
407
+ // Skip files that can't be scanned
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ return results;
414
+ }
415
+ }
416
+
417
+ // ============================================================================
418
+ // Exports
419
+ // ============================================================================
420
+
421
+ module.exports = {
422
+ DocumentScanner,
423
+ SCANNABLE_TYPES,
424
+ MAX_CONTENT_SIZE,
425
+ MAX_SNIPPET_SIZE
426
+ };