@vima_tech/telos 1.4.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.
package/bin/telos.js ADDED
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+ const { spawn, execSync } = require('child_process');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+
8
+ const NPM_PKG = '@vima_tech/telos';
9
+
10
+ function resolveScript(name) {
11
+ return path.join(__dirname, '../scripts/' + name + '.sh');
12
+ }
13
+
14
+ function resolveKernelFile(name) {
15
+ return path.join(__dirname, '../' + name);
16
+ }
17
+
18
+ function runScript(scriptPath, args, extraEnv = {}) {
19
+ const env = { ...process.env, TELOS_DIR: process.cwd(), ...extraEnv };
20
+ if (proxy) env.TELOS_PROXY = proxy;
21
+
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn('bash', [scriptPath, ...args], {
24
+ cwd: process.cwd(),
25
+ stdio: 'inherit',
26
+ env,
27
+ });
28
+ child.on('exit', (code) => {
29
+ if (code === 0) resolve(0);
30
+ else reject(new Error('exit code ' + code));
31
+ });
32
+ });
33
+ }
34
+
35
+ function downloadFile(url, destPath) {
36
+ return new Promise((resolve, reject) => {
37
+ const args = ['-fsSL', url, '-o', destPath];
38
+ if (proxy) args.splice(1, 0, '--proxy', proxy);
39
+ spawn('curl', args, { stdio: 'ignore' })
40
+ .on('exit', (code) => {
41
+ if (code === 0) resolve(0);
42
+ else reject(new Error('curl failed'));
43
+ });
44
+ });
45
+ }
46
+
47
+ async function initKernel(dir, skill) {
48
+ const baseUrl = 'https://raw.githubusercontent.com/renmengkai/telos/main';
49
+
50
+ const kernelFiles = [
51
+ 'CLAUDE.md', '.gitignore',
52
+ '.claude/commands/telos.md', '.claude/commands/skill.md', '.claude/commands/add-skill.md',
53
+ 'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
54
+ 'scripts/bridge-to-coder.sh', 'scripts/telos-install.sh',
55
+ 'templates/state.json',
56
+ 'skills/_kernel/distillation.md', 'skills/_kernel/skill-extraction.md',
57
+ 'skills/_template/skill.yaml', 'skills/_template/domain.md',
58
+ 'skills/_template/feedback-questions.sh',
59
+ ];
60
+
61
+ for (const f of kernelFiles) {
62
+ const dest = path.join(dir, f);
63
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
64
+ const srcPath = resolveKernelFile(f);
65
+ if (fs.existsSync(srcPath)) {
66
+ fs.copyFileSync(srcPath, dest);
67
+ } else {
68
+ await downloadFile(baseUrl + '/' + f, dest);
69
+ }
70
+ }
71
+
72
+ if (skill) {
73
+ const skillRequired = [
74
+ 'skills/' + skill + '/skill.yaml',
75
+ 'skills/' + skill + '/domain.md',
76
+ 'skills/' + skill + '/feedback-questions.sh',
77
+ ];
78
+ const skillOptional = [
79
+ 'skills/' + skill + '/artifacts.md',
80
+ 'skills/' + skill + '/memory/failure-patterns.md',
81
+ ];
82
+
83
+ for (const f of skillRequired) {
84
+ const dest = path.join(dir, f);
85
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
86
+ const srcPath = resolveKernelFile(f);
87
+ if (fs.existsSync(srcPath)) {
88
+ fs.copyFileSync(srcPath, dest);
89
+ } else {
90
+ await downloadFile(baseUrl + '/' + f, dest);
91
+ }
92
+ }
93
+
94
+ for (const f of skillOptional) {
95
+ const dest = path.join(dir, f);
96
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
97
+ const srcPath = resolveKernelFile(f);
98
+ if (fs.existsSync(srcPath)) {
99
+ fs.copyFileSync(srcPath, dest);
100
+ } else {
101
+ try { await downloadFile(baseUrl + '/' + f, dest); } catch (e) {}
102
+ }
103
+ }
104
+
105
+ // 解析 skill.yaml 中声明的行业包,逐一下载(找不到则跳过)
106
+ const skillYamlPath = path.join(dir, 'skills/' + skill + '/skill.yaml');
107
+ if (fs.existsSync(skillYamlPath)) {
108
+ const yamlContent = fs.readFileSync(skillYamlPath, 'utf8');
109
+ for (const match of yamlContent.matchAll(/^\s+file:\s+(.+)$/gm)) {
110
+ const f = match[1].trim();
111
+ const dest = path.join(dir, f);
112
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
113
+ const srcPath = resolveKernelFile(f);
114
+ if (fs.existsSync(srcPath)) {
115
+ fs.copyFileSync(srcPath, dest);
116
+ } else {
117
+ try { await downloadFile(baseUrl + '/' + f, dest); } catch (e) {}
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ fs.mkdirSync(path.join(dir, '.distill-needed'), { recursive: true });
124
+ fs.mkdirSync(path.join(dir, 'episodic-logs'), { recursive: true });
125
+ fs.mkdirSync(path.join(dir, 'projects'), { recursive: true });
126
+ fs.writeFileSync(path.join(dir, '.distill-needed/.gitkeep'), '');
127
+ fs.writeFileSync(path.join(dir, 'episodic-logs/.gitkeep'), '');
128
+ fs.writeFileSync(path.join(dir, 'projects/.gitkeep'), '');
129
+
130
+ if (!fs.existsSync(path.join(dir, '.gitignore'))) {
131
+ fs.writeFileSync(path.join(dir, '.gitignore'), `# telos-managed\nprojects/*/\nepisodic-logs/\n.distill-needed/*\n!.distill-needed/.gitkeep\n*.log\n`);
132
+ }
133
+
134
+ fs.writeFileSync(path.join(dir, '.telos'), JSON.stringify({
135
+ version: '1.4.0',
136
+ created: new Date().toISOString(),
137
+ skill: skill || 'none'
138
+ }));
139
+
140
+ try {
141
+ execSync('chmod +x ' + path.join(dir, 'scripts/*.sh'), { stdio: 'ignore', shell: true });
142
+ } catch (e) {}
143
+ }
144
+
145
+ async function cmdNew(args) {
146
+ if (args.length < 1) {
147
+ console.error('用法: telos new <project-name> [--skill req-mining]');
148
+ process.exit(1);
149
+ }
150
+
151
+ const projectName = args[0];
152
+ const targetDir = path.join(process.cwd(), projectName);
153
+
154
+ if (fs.existsSync(targetDir)) {
155
+ console.error('❌ 目录已存在: ' + targetDir);
156
+ process.exit(1);
157
+ }
158
+
159
+ let skill = '';
160
+
161
+ for (let i = 1; i < args.length; i++) {
162
+ if (args[i] === '--skill' && args[i + 1]) {
163
+ skill = args[i + 1];
164
+ i++;
165
+ }
166
+ }
167
+
168
+ console.log('');
169
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
170
+ console.log(' Telos 初始化');
171
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
172
+ console.log(' 项目: ' + projectName);
173
+ console.log(' 目录: ' + targetDir);
174
+ console.log(' Skill: ' + (skill || '无(后续手动添加)'));
175
+ console.log('');
176
+
177
+ console.log('📦 初始化框架文件...');
178
+ fs.mkdirSync(targetDir, { recursive: true });
179
+ await initKernel(targetDir, skill);
180
+ console.log(' ✓ 安装完成');
181
+
182
+ if (!skill) {
183
+ console.log('');
184
+ console.log(' ℹ 未指定 Skill,可后续安装:');
185
+ console.log(' telos install req-mining');
186
+ console.log(' telos available # 查看可用 Skill');
187
+ }
188
+
189
+ console.log('');
190
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
191
+ console.log(' ✅ 项目创建完成!');
192
+ console.log('');
193
+ console.log(' cd ' + projectName + ' && claude');
194
+ console.log(' 然后输入 tos 启动 Telos');
195
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
196
+ }
197
+
198
+ function cmdStart() {
199
+ const detected = detectCodingAgent();
200
+ if (!detected) {
201
+ console.error('❌ 未检测到 Claude Code 或 OpenCode');
202
+ console.error('请先安装 Claude Code 或 OpenCode');
203
+ process.exit(1);
204
+ }
205
+
206
+ console.log('🚀 启动 ' + detected + '...');
207
+ spawn(detected, [], {
208
+ cwd: process.cwd(),
209
+ stdio: 'inherit',
210
+ shell: true,
211
+ env: { ...process.env, TELOS_DIR: process.cwd() },
212
+ });
213
+ }
214
+
215
+ function detectCodingAgent() {
216
+ const agents = ['claude', 'claude-code', 'opencode', 'codex'];
217
+ for (const a of agents) {
218
+ try {
219
+ execSync('which ' + a, { stdio: 'ignore' });
220
+ return a;
221
+ } catch (e) {}
222
+ }
223
+ return null;
224
+ }
225
+
226
+ function findTelosProjects(startPath, maxDepth = 3) {
227
+ const projects = [];
228
+ const homeDir = os.homedir();
229
+
230
+ function scan(dir, depth) {
231
+ if (depth > maxDepth) return;
232
+ try {
233
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
234
+ for (const entry of entries) {
235
+ if (!entry.isDirectory()) continue;
236
+ if (entry.name === '.' || entry.name === '..') continue;
237
+ if (entry.name === 'node_modules') continue;
238
+
239
+ const fullPath = path.join(dir, entry.name);
240
+ if (fs.existsSync(path.join(fullPath, '.telos'))) {
241
+ projects.push(fullPath);
242
+ } else if (fullPath.startsWith(homeDir)) {
243
+ scan(fullPath, depth + 1);
244
+ }
245
+ }
246
+ } catch (e) {}
247
+ }
248
+
249
+ scan(startPath, 0);
250
+ return projects;
251
+ }
252
+
253
+ async function updateProjectFramework(projectPath) {
254
+ const baseUrl = 'https://raw.githubusercontent.com/renmengkai/telos/main';
255
+
256
+ const kernelFiles = [
257
+ 'CLAUDE.md',
258
+ '.claude/commands/telos.md', '.claude/commands/skill.md', '.claude/commands/add-skill.md',
259
+ 'scripts/auto-distill.sh', 'scripts/feedback-hook.sh',
260
+ 'scripts/bridge-to-coder.sh', 'scripts/telos-install.sh',
261
+ 'skills/_kernel/distillation.md', 'skills/_kernel/skill-extraction.md',
262
+ 'skills/_template/skill.yaml', 'skills/_template/domain.md',
263
+ 'skills/_template/feedback-questions.sh',
264
+ ];
265
+
266
+ console.log(' 更新框架文件...');
267
+ for (const f of kernelFiles) {
268
+ const dest = path.join(projectPath, f);
269
+ try {
270
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
271
+ await downloadFile(baseUrl + '/' + f, dest);
272
+ console.log(' ✓ ' + f);
273
+ } catch (e) {
274
+ console.log(' ✗ ' + f + ' (失败)');
275
+ }
276
+ }
277
+
278
+ // 更新已安装的 Skill 文件
279
+ const telosMarker = path.join(projectPath, '.telos');
280
+ if (fs.existsSync(telosMarker)) {
281
+ let skill = '';
282
+ try { skill = JSON.parse(fs.readFileSync(telosMarker, 'utf8')).skill || ''; } catch (e) {}
283
+ if (skill && skill !== 'none') {
284
+ console.log(' 更新 Skill: ' + skill);
285
+ const skillFiles = [
286
+ 'skills/' + skill + '/skill.yaml',
287
+ 'skills/' + skill + '/domain.md',
288
+ 'skills/' + skill + '/feedback-questions.sh',
289
+ 'skills/' + skill + '/artifacts.md',
290
+ 'skills/' + skill + '/memory/failure-patterns.md',
291
+ ];
292
+ for (const f of skillFiles) {
293
+ const dest = path.join(projectPath, f);
294
+ try {
295
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
296
+ await downloadFile(baseUrl + '/' + f, dest);
297
+ console.log(' ✓ ' + f);
298
+ } catch (e) {}
299
+ }
300
+ // 更新行业包(从已下载的 skill.yaml 中读取)
301
+ const skillYamlPath = path.join(projectPath, 'skills/' + skill + '/skill.yaml');
302
+ if (fs.existsSync(skillYamlPath)) {
303
+ const yamlContent = fs.readFileSync(skillYamlPath, 'utf8');
304
+ for (const match of yamlContent.matchAll(/^\s+file:\s+(.+)$/gm)) {
305
+ const f = match[1].trim();
306
+ const dest = path.join(projectPath, f);
307
+ try {
308
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
309
+ await downloadFile(baseUrl + '/' + f, dest);
310
+ console.log(' ✓ ' + f);
311
+ } catch (e) {}
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ async function cmdUpgrade(args) {
319
+ console.log('');
320
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
321
+ console.log(' Telos 升级');
322
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
323
+
324
+ console.log('');
325
+ console.log('📦 升级 telos CLI...');
326
+ const npmArgs = ['install', '-g', NPM_PKG];
327
+ if (proxy) npmArgs.push('--proxy', proxy);
328
+ const npmSpawn = spawn('npm', npmArgs, { stdio: 'inherit' });
329
+ await new Promise((resolve) => npmSpawn.on('exit', resolve));
330
+
331
+ if (args.length > 0) {
332
+ const targetPath = path.resolve(args[0]);
333
+ if (fs.existsSync(path.join(targetPath, '.telos'))) {
334
+ console.log('');
335
+ console.log('📦 更新项目框架: ' + targetPath);
336
+ await updateProjectFramework(targetPath);
337
+ } else {
338
+ console.error('❌ 不是有效的 telos 项目: ' + targetPath);
339
+ }
340
+ } else {
341
+ console.log('');
342
+ console.log('🔍 扫描 telos 项目...');
343
+ const projects = findTelosProjects(os.homedir());
344
+
345
+ if (projects.length === 0) {
346
+ console.log(' 未找到 telos 项目');
347
+ } else {
348
+ console.log(' 找到 ' + projects.length + ' 个项目:');
349
+ projects.forEach((p, i) => console.log(' ' + (i + 1) + '. ' + p));
350
+
351
+ console.log('');
352
+ console.log(' 是否更新所有项目框架?(y/N)');
353
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
354
+ const answer = await new Promise((resolve) => rl.question('', resolve));
355
+ rl.close();
356
+
357
+ if (answer.toLowerCase() === 'y') {
358
+ for (const proj of projects) {
359
+ console.log('');
360
+ console.log('📦 更新: ' + proj);
361
+ await updateProjectFramework(proj);
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ console.log('');
368
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
369
+ console.log(' ✅ 升级完成');
370
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
371
+ }
372
+
373
+ let proxy = '';
374
+ const rawArgs = process.argv.slice(2);
375
+ const filteredArgs = [];
376
+ for (let i = 0; i < rawArgs.length; i++) {
377
+ if (rawArgs[i] === '-x' || rawArgs[i] === '--proxy') {
378
+ proxy = rawArgs[i + 1];
379
+ i++;
380
+ } else {
381
+ filteredArgs.push(rawArgs[i]);
382
+ }
383
+ }
384
+
385
+ const [command, ...restArgs] = filteredArgs;
386
+
387
+ const HELP = `
388
+ Telos CLI — 对话驱动的个人能力操作系统
389
+
390
+ 用法: telos <command> [args]
391
+
392
+ 可用命令:
393
+ new|create|init 创建新项目
394
+ start|run|launch 启动 Claude Code 或 OpenCode
395
+ install|add|i 安装 Skill 包
396
+ list|ls 列出已安装的 Skill
397
+ update [skill] 更新 Skill 包
398
+ publish <skill> 发布自建 Skill 到注册表
399
+ available 查看可安装的 Skill
400
+ distill|dist 手动触发蒸馏
401
+ upgrade [path] 升级 telos 工具,并更新项目框架
402
+ -v|--version 显示版本号
403
+ -h|--help 显示帮助
404
+
405
+ 选项:
406
+ -x, --proxy <url> 使用代理
407
+
408
+ 示例:
409
+ telos new my-project
410
+ telos new my-project --skill req-mining
411
+ telos install req-mining
412
+ telos update
413
+ telos upgrade
414
+ telos upgrade ./my-project
415
+ `;
416
+
417
+ if (!command) {
418
+ console.log(HELP);
419
+ process.exit(0);
420
+ }
421
+
422
+ switch (command) {
423
+ case '-v':
424
+ case '--version':
425
+ case '-V':
426
+ case 'version':
427
+ console.log('@vima_tech/telos v1.4.0');
428
+ break;
429
+ case 'new':
430
+ case 'create':
431
+ case 'init':
432
+ cmdNew(restArgs).catch((e) => { console.error(e); process.exit(1); });
433
+ break;
434
+ case 'start':
435
+ case 'run':
436
+ case 'launch':
437
+ cmdStart();
438
+ break;
439
+ case '-h':
440
+ case '--help':
441
+ case '-?':
442
+ case 'help':
443
+ case 'h':
444
+ console.log(HELP);
445
+ break;
446
+ case 'install':
447
+ case 'add':
448
+ case 'i':
449
+ runScript(resolveScript('telos-install'), ['add', ...restArgs]).catch((e) => process.exit(1));
450
+ break;
451
+ case 'available':
452
+ case 'avail':
453
+ case 'search':
454
+ runScript(resolveScript('telos-install'), ['available']).catch((e) => process.exit(1));
455
+ break;
456
+ case 'list':
457
+ case 'ls':
458
+ runScript(resolveScript('telos-install'), ['list']).catch((e) => process.exit(1));
459
+ break;
460
+ case 'update':
461
+ runScript(resolveScript('telos-install'), ['update', ...restArgs]).catch((e) => process.exit(1));
462
+ break;
463
+ case 'publish':
464
+ runScript(resolveScript('telos-install'), ['publish', ...restArgs]).catch((e) => process.exit(1));
465
+ break;
466
+ case 'distill':
467
+ case 'dist':
468
+ runScript(resolveScript('auto-distill'), restArgs).catch((e) => process.exit(1));
469
+ break;
470
+ case 'upgrade':
471
+ cmdUpgrade(restArgs).catch((e) => { console.error(e); process.exit(1); });
472
+ break;
473
+ default:
474
+ console.error('未知命令: ' + command);
475
+ console.error('运行 telos help 查看用法');
476
+ process.exit(1);
477
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@vima_tech/telos",
3
+ "version": "1.4.0",
4
+ "description": "对话驱动的个人能力操作系统 — 每次对话都让你变得更懂行,技能跨会话永久积累",
5
+ "main": "bin/telos.js",
6
+ "bin": {
7
+ "telos": "./bin/telos.js",
8
+ "tos": "./bin/telos.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "scripts/",
13
+ "templates/",
14
+ "skills/_kernel/",
15
+ "skills/_template/",
16
+ "skills/req-mining/"
17
+ ],
18
+ "scripts": {
19
+ "postinstall": "chmod +x bin/telos.js"
20
+ },
21
+ "keywords": [
22
+ "ai",
23
+ "skill",
24
+ "claude",
25
+ "telos",
26
+ "procedural-memory",
27
+ "self-evolution"
28
+ ],
29
+ "author": "renmengkai",
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": ">=16.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@playwright/test": "^1.60.0"
36
+ }
37
+ }
@@ -0,0 +1,154 @@
1
+ #!/bin/bash
2
+ # scripts/auto-distill.sh
3
+ # 自动检查 episodic-logs 和 .distill-needed/ 标记,触发蒸馏
4
+ #
5
+ # 用法:
6
+ # ./scripts/auto-distill.sh # 检查所有项目
7
+ # ./scripts/auto-distill.sh <project_id> # 检查指定项目
8
+ # ./scripts/auto-distill.sh --cron # Cron 模式(静默,仅在需要时输出)
9
+ #
10
+ # 设置为 Cron 自动运行(每天早上 9 点检查):
11
+ # 0 9 * * * /path/to/telos/scripts/auto-distill.sh --cron
12
+
13
+ set -e
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
17
+ LOGS_DIR="$REPO_DIR/episodic-logs"
18
+ DISTILL_NEEDED_DIR="$REPO_DIR/.distill-needed"
19
+ THRESHOLD=${DISTILL_THRESHOLD:-3}
20
+ CRON_MODE=false
21
+ TARGET_PROJECT=""
22
+
23
+ for arg in "$@"; do
24
+ case "$arg" in
25
+ --cron) CRON_MODE=true ;;
26
+ *) TARGET_PROJECT="$arg" ;;
27
+ esac
28
+ done
29
+
30
+ check_project() {
31
+ local project_id="$1"
32
+
33
+ # 优先检查标记文件(由 feedback-hook.sh --auto 写入)
34
+ if [ -f "$DISTILL_NEEDED_DIR/$project_id" ]; then
35
+ return 0
36
+ fi
37
+
38
+ # 回退:扫描日志文件
39
+ local log_file="$LOGS_DIR/${project_id}.jsonl"
40
+ [ -f "$log_file" ] || return 1
41
+
42
+ local pending
43
+ pending=$(python3 - << PYEOF
44
+ import json
45
+ try:
46
+ with open("$log_file") as f:
47
+ events = [json.loads(l) for l in f if l.strip()]
48
+ pending = sum(1 for e in events
49
+ if e.get("distillation_candidate") and not e.get("distilled")
50
+ and e.get("quality_score", 0) >= 0.6)
51
+ print(pending)
52
+ except:
53
+ print(0)
54
+ PYEOF
55
+ )
56
+ [ "$pending" -ge "$THRESHOLD" ]
57
+ }
58
+
59
+ report_status() {
60
+ local project_id="$1"
61
+ local log_file="$LOGS_DIR/${project_id}.jsonl"
62
+ local has_marker=""
63
+ [ -f "$DISTILL_NEEDED_DIR/$project_id" ] && has_marker=" [标记文件]"
64
+
65
+ python3 - << PYEOF
66
+ import json
67
+ try:
68
+ with open("$log_file") as f:
69
+ events = [json.loads(l) for l in f if l.strip()]
70
+ total = len(events)
71
+ pending = sum(1 for e in events
72
+ if e.get("distillation_candidate") and not e.get("distilled")
73
+ and e.get("quality_score", 0) >= 0.6)
74
+ distilled = sum(1 for e in events if e.get("distilled"))
75
+ print(f" 项目 $project_id$has_marker: 总事件={total}, 待蒸馏={pending}, 已蒸馏={distilled}")
76
+ except:
77
+ print(" 项目 $project_id$has_marker: 无日志文件(由标记文件触发)")
78
+ PYEOF
79
+ }
80
+
81
+ # ── 收集待检查项目 ─────────────────────────────────────────────
82
+ declare -A seen_projects
83
+
84
+ if [ -n "$TARGET_PROJECT" ]; then
85
+ seen_projects["$TARGET_PROJECT"]=1
86
+ else
87
+ # 从标记文件收集
88
+ if [ -d "$DISTILL_NEEDED_DIR" ]; then
89
+ for marker in "$DISTILL_NEEDED_DIR"/*; do
90
+ [ -f "$marker" ] || continue
91
+ project_id=$(basename "$marker")
92
+ [ "$project_id" = ".gitkeep" ] && continue
93
+ seen_projects["$project_id"]=1
94
+ done
95
+ fi
96
+ # 从日志文件收集
97
+ for log_file in "$LOGS_DIR"/*.jsonl; do
98
+ [ -f "$log_file" ] || continue
99
+ project_id=$(basename "$log_file" .jsonl)
100
+ [ "$project_id" = ".gitkeep" ] && continue
101
+ seen_projects["$project_id"]=1
102
+ done
103
+ fi
104
+
105
+ projects=("${!seen_projects[@]}")
106
+
107
+ if [ ${#projects[@]} -eq 0 ]; then
108
+ $CRON_MODE || echo "📭 没有找到任何项目。"
109
+ exit 0
110
+ fi
111
+
112
+ $CRON_MODE || echo "━━━ 蒸馏检查器 ━━━"
113
+ $CRON_MODE || echo "检查阈值:$THRESHOLD 条待蒸馏事件"
114
+ $CRON_MODE || echo ""
115
+
116
+ needs_distill=()
117
+ for project_id in "${projects[@]}"; do
118
+ if check_project "$project_id"; then
119
+ needs_distill+=("$project_id")
120
+ $CRON_MODE || report_status "$project_id"
121
+ $CRON_MODE || echo " ⚡ 需要蒸馏"
122
+ else
123
+ $CRON_MODE || report_status "$project_id"
124
+ fi
125
+ done
126
+
127
+ if [ ${#needs_distill[@]} -eq 0 ]; then
128
+ $CRON_MODE || echo "✅ 所有项目暂无需要蒸馏(待蒸馏事件均 < $THRESHOLD 条)"
129
+ exit 0
130
+ fi
131
+
132
+ # ── 有需要蒸馏的项目 ──────────────────────────────────────────
133
+ echo ""
134
+ echo "━━━ 蒸馏建议 ━━━"
135
+ echo "以下项目已积累足够反馈,建议进行蒸馏:"
136
+ for p in "${needs_distill[@]}"; do
137
+ echo " • $p"
138
+ done
139
+ echo ""
140
+ echo "操作方式:"
141
+ echo " 1. 打开 Telos 会话(cd $REPO_DIR && claude)"
142
+ echo " 2. 系统将自动检测待蒸馏标记并启动蒸馏"
143
+ echo ""
144
+
145
+ # Cron 模式下发送桌面通知
146
+ if $CRON_MODE; then
147
+ if command -v notify-send &>/dev/null; then
148
+ notify-send "Telos 蒸馏提醒" "项目 ${needs_distill[*]} 已积累足够反馈,建议蒸馏"
149
+ elif command -v osascript &>/dev/null; then
150
+ osascript -e "display notification \"项目 ${needs_distill[*]} 已积累足够反馈\" with title \"req-miner 蒸馏提醒\""
151
+ fi
152
+ fi
153
+
154
+ exit 0
@@ -0,0 +1,11 @@
1
+ {
2
+ "agents": [
3
+ {
4
+ "name": "req-mining",
5
+ "version": "1.0.0",
6
+ "tier": "bundled",
7
+ "description": "需求挖掘与落地分析"
8
+ }
9
+ ],
10
+ "updated": "2026-05-17"
11
+ }