autosnippet 3.2.4 → 3.2.6

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 (64) hide show
  1. package/README.md +2 -4
  2. package/bin/cli.js +164 -145
  3. package/config/constitution.yaml +2 -0
  4. package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
  5. package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
  6. package/dashboard/dist/index.html +2 -2
  7. package/lib/cli/CliLogger.js +78 -0
  8. package/lib/cli/SetupService.js +9 -718
  9. package/lib/cli/UpgradeService.js +23 -398
  10. package/lib/cli/deploy/FileDeployer.js +562 -0
  11. package/lib/cli/deploy/FileManifest.js +272 -0
  12. package/lib/external/mcp/McpServer.js +22 -26
  13. package/lib/external/mcp/autoApproveInjector.js +1 -0
  14. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
  19. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
  20. package/lib/external/mcp/handlers/consolidated.js +8 -9
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
  22. package/lib/external/mcp/handlers/guard.js +283 -5
  23. package/lib/external/mcp/handlers/task.js +183 -9
  24. package/lib/external/mcp/tools.js +32 -81
  25. package/lib/http/routes/task.js +55 -0
  26. package/lib/service/chat/AnalystAgent.js +12 -12
  27. package/lib/service/chat/ChatAgent.js +227 -545
  28. package/lib/service/chat/ChatAgentPrompts.js +9 -11
  29. package/lib/service/chat/ContextWindow.js +2 -296
  30. package/lib/service/chat/EpisodicConsolidator.js +15 -15
  31. package/lib/service/chat/ExplorationTracker.js +1262 -0
  32. package/lib/service/chat/HandoffProtocol.js +8 -9
  33. package/lib/service/chat/Memory.js +4 -0
  34. package/lib/service/chat/ProducerAgent.js +9 -6
  35. package/lib/service/chat/ProjectSemanticMemory.js +4 -0
  36. package/lib/service/chat/ReasoningTrace.js +182 -0
  37. package/lib/service/chat/WorkingMemory.js +4 -0
  38. package/lib/service/chat/memory/ActiveContext.js +910 -0
  39. package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
  40. package/lib/service/chat/memory/PersistentMemory.js +450 -0
  41. package/lib/service/chat/memory/SessionStore.js +896 -0
  42. package/lib/service/chat/memory/index.js +13 -0
  43. package/lib/service/chat/tools/ast-graph.js +17 -16
  44. package/lib/service/cursor/AgentInstructionsGenerator.js +76 -47
  45. package/lib/service/cursor/FileProtection.js +4 -1
  46. package/lib/service/guard/GuardCheckEngine.js +10 -3
  47. package/lib/service/task/TaskGraphService.js +3 -3
  48. package/lib/shared/LanguageService.js +2 -1
  49. package/package.json +1 -1
  50. package/skills/autosnippet-intent/SKILL.md +1 -3
  51. package/skills/autosnippet-recipes/SKILL.md +1 -3
  52. package/templates/claude-code/commands/prime.md +19 -0
  53. package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
  54. package/templates/claude-code/settings.json +21 -0
  55. package/templates/copilot-instructions.md +64 -177
  56. package/templates/cursor-hooks/commands/prime.md +12 -0
  57. package/templates/cursor-hooks/hooks/session-start.sh +10 -0
  58. package/templates/cursor-hooks/hooks.json +11 -0
  59. package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
  60. package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
  61. package/lib/external/mcp/handlers/decide.js +0 -109
  62. package/lib/external/mcp/handlers/ready.js +0 -42
  63. package/lib/service/chat/ReasoningLayer.js +0 -888
  64. package/templates/claude-hooks.yaml +0 -19
package/bin/cli.js CHANGED
@@ -9,18 +9,20 @@
9
9
  * asd ais [Target] - AI 扫描 Target → 直接发布 Recipes
10
10
  * asd search <query> - 搜索知识库
11
11
  * asd guard <file> - Guard 检查
12
+ * asd guard:ci [path] - CI/CD Guard 合规检查
12
13
  * asd watch - 文件监控
13
- * asd compliance - 合规评估
14
14
  * asd server - 启动 API 服务
15
- * asd status - 环境状态
16
15
  * asd ui - 启动 Dashboard UI
16
+ * asd upgrade - 升级 IDE 集成
17
17
  * asd mirror - 镜像 .cursor/ → .qoder/ .trae/
18
+ * asd status - 环境状态
18
19
  */
19
20
 
20
21
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
21
22
  import { dirname, join, resolve } from 'node:path';
22
23
  import { fileURLToPath } from 'node:url';
23
24
  import { Command } from 'commander';
25
+ import { cli } from '../lib/cli/CliLogger.js';
24
26
 
25
27
  const __filename = fileURLToPath(import.meta.url);
26
28
  const __dirname = dirname(__filename);
@@ -89,7 +91,7 @@ program
89
91
  .action(async (opts) => {
90
92
  const projectRoot = resolve(opts.dir);
91
93
  if (opts.skipGuard) {
92
- console.log('ℹ️ Guard 审计已跳过');
94
+ cli.log('ℹ️ Guard 审计已跳过');
93
95
  }
94
96
 
95
97
  try {
@@ -111,7 +113,7 @@ program
111
113
  spinner.stop();
112
114
 
113
115
  if (opts.json) {
114
- console.log(JSON.stringify(result, null, 2));
116
+ cli.json(result);
115
117
  } else {
116
118
  // 输出骨架报告
117
119
  const report = result.report || {};
@@ -121,11 +123,11 @@ program
121
123
  const astSummary = result.astSummary;
122
124
  const framework = result.analysisFramework || {};
123
125
 
124
- console.log('\n📊 Coldstart Report');
125
- console.log(`${'─'.repeat(50)}`);
126
+ cli.log('\n📊 Coldstart Report');
127
+ cli.log(`${'─'.repeat(50)}`);
126
128
 
127
129
  if (targets.length > 0) {
128
- console.log(`\n Targets: ${targets.map(t => t.name || t).join(', ')}`);
130
+ cli.log(`\n Targets: ${targets.map(t => t.name || t).join(', ')}`);
129
131
  }
130
132
 
131
133
  if (Object.keys(langStats).length > 0) {
@@ -133,40 +135,40 @@ program
133
135
  .sort((a, b) => b[1] - a[1])
134
136
  .slice(0, 5)
135
137
  .map(([ext, count]) => `${ext}(${count})`);
136
- console.log(` Languages: ${langParts.join(', ')}`);
138
+ cli.log(` Languages: ${langParts.join(', ')}`);
137
139
  }
138
140
 
139
141
  // AST 分析
140
142
  if (astSummary) {
141
143
  if (astSummary.metrics) {
142
- console.log(` AST Metrics: ${JSON.stringify(astSummary.metrics)}`);
144
+ cli.log(` AST Metrics: ${JSON.stringify(astSummary.metrics)}`);
143
145
  }
144
146
  }
145
147
 
146
148
  // SPM 依赖
147
149
  if (report.phases?.spmDependencyGraph) {
148
150
  const spm = report.phases.spmDependencyGraph;
149
- console.log(` SPM Dependencies: ${spm.packageCount ?? '?'} packages`);
151
+ cli.log(` SPM Dependencies: ${spm.packageCount ?? '?'} packages`);
150
152
  }
151
153
 
152
154
  // Guard 审计
153
155
  if (guardSummary) {
154
- console.log(` Guard: ${guardSummary.totalViolations ?? guardSummary.total ?? '?'} violations (${guardSummary.errors ?? '?'} errors, ${guardSummary.warnings ?? '?'} warnings)`);
156
+ cli.log(` Guard: ${guardSummary.totalViolations ?? guardSummary.total ?? '?'} violations (${guardSummary.errors ?? '?'} errors, ${guardSummary.warnings ?? '?'} warnings)`);
155
157
  }
156
158
 
157
159
  // 维度分析框架
158
160
  if (framework.dimensions) {
159
- console.log('\n Analysis Dimensions:');
161
+ cli.log('\n Analysis Dimensions:');
160
162
  for (const dim of framework.dimensions) {
161
163
  const type = dim.skillWorthy ? (dim.dualOutput ? 'Dual' : 'Skill') : 'Candidate';
162
- console.log(` ${type.padEnd(10)} ${dim.id || dim.name || '?'}`);
164
+ cli.log(` ${type.padEnd(10)} ${dim.id || dim.name || '?'}`);
163
165
  }
164
166
  }
165
167
  if (result.bootstrapSession) {
166
168
  const session = result.bootstrapSession;
167
- console.log(`\n Session: ${session.id || 'N/A'} (${session.status || 'unknown'})`);
169
+ cli.log(`\n Session: ${session.id || 'N/A'} (${session.status || 'unknown'})`);
168
170
  }
169
- console.log('');
171
+ cli.blank();
170
172
  }
171
173
 
172
174
  // 等待模式: 轮询 BootstrapTaskManager 直到所有维度完成
@@ -210,12 +212,12 @@ program
210
212
  if (!opts.json) {
211
213
  const succeeded = sessionStatus.tasks.filter((t) => t.status === 'done').length;
212
214
  const failed = sessionStatus.tasks.filter((t) => t.status === 'error').length;
213
- console.log(`\n Results: ${succeeded} succeeded, ${failed} failed`);
215
+ cli.log(`\n Results: ${succeeded} succeeded, ${failed} failed`);
214
216
  for (const t of sessionStatus.tasks) {
215
217
  const icon = t.status === 'done' ? '✅' : '❌';
216
- console.log(` ${icon} ${t.meta?.label || t.id}`);
218
+ cli.log(` ${icon} ${t.meta?.label || t.id}`);
217
219
  }
218
- console.log('');
220
+ cli.blank();
219
221
  }
220
222
  break;
221
223
  }
@@ -228,15 +230,13 @@ program
228
230
  waitSpinner.warn('AI 填充超时(10 分钟),可通过 asd ui 查看进度');
229
231
  }
230
232
  } else if (!opts.json) {
231
- console.log(' 💡 AI 填充已在后台运行。用 --wait 等待完成,或用 asd ui 查看进度。');
233
+ cli.log(' 💡 AI 填充已在后台运行。用 --wait 等待完成,或用 asd ui 查看进度。');
232
234
  }
233
235
 
234
236
  await bootstrap.shutdown();
235
237
  } catch (err) {
236
- console.error(`\n❌ ${err.message}`);
237
- if (process.env.ASD_DEBUG === '1') {
238
- console.error(err.stack);
239
- }
238
+ cli.error(`\n❌ ${err.message}`);
239
+ cli.debug(err.stack);
240
240
  process.exit(1);
241
241
  }
242
242
  });
@@ -254,10 +254,10 @@ program
254
254
  .action(async (target, opts) => {
255
255
  const projectRoot = resolve(opts.dir);
256
256
  if (target) {
257
- console.log(`Target: ${target}`);
257
+ cli.log(`Target: ${target}`);
258
258
  }
259
259
  if (opts.dryRun) {
260
- console.log('ℹ️ Dry-run mode: no Recipes will be published');
260
+ cli.log('ℹ️ Dry-run mode: no Recipes will be published');
261
261
  }
262
262
 
263
263
  try {
@@ -277,33 +277,31 @@ program
277
277
  spinner.stop();
278
278
 
279
279
  if (opts.json) {
280
- console.log(JSON.stringify(report, null, 2));
280
+ cli.json(report);
281
281
  } else {
282
- console.log(`\n📝 AI Scan Report`);
283
- console.log(` Files scanned: ${report.files}`);
284
- console.log(` Published: ${report.published}`);
285
- console.log(` Skipped: ${report.skipped || 0}`);
282
+ cli.log(`\n📝 AI Scan Report`);
283
+ cli.log(` Files scanned: ${report.files}`);
284
+ cli.log(` Published: ${report.published}`);
285
+ cli.log(` Skipped: ${report.skipped || 0}`);
286
286
  if (report.errors.length > 0) {
287
- console.log(` Errors: ${report.errors.length}`);
287
+ cli.log(` Errors: ${report.errors.length}`);
288
288
  for (const err of report.errors.slice(0, 10)) {
289
- console.log(` ❌ ${err}`);
289
+ cli.log(` ❌ ${err}`);
290
290
  }
291
291
  if (report.errors.length > 10) {
292
- console.log(` ... and ${report.errors.length - 10} more`);
292
+ cli.log(` ... and ${report.errors.length - 10} more`);
293
293
  }
294
294
  }
295
295
  if (!opts.dryRun && report.published > 0) {
296
- console.log(`\n ✅ ${report.published} Recipes published successfully.`);
296
+ cli.log(`\n ✅ ${report.published} Recipes published successfully.`);
297
297
  }
298
- console.log('');
298
+ cli.blank();
299
299
  }
300
300
 
301
301
  await bootstrap.shutdown();
302
302
  } catch (err) {
303
- console.error(`\n❌ ${err.message}`);
304
- if (process.env.ASD_DEBUG === '1') {
305
- console.error(err.stack);
306
- }
303
+ cli.error(`\n❌ ${err.message}`);
304
+ cli.debug(err.stack);
307
305
  process.exit(1);
308
306
  }
309
307
  });
@@ -328,23 +326,23 @@ program
328
326
  });
329
327
 
330
328
  if (results.items.length === 0) {
331
- console.log('No results found.');
329
+ cli.log('No results found.');
332
330
  } else {
333
- console.log(`\n🔍 ${results.items.length} result(s) for "${query}"\n`);
331
+ cli.log(`\n🔍 ${results.items.length} result(s) for "${query}"\n`);
334
332
  for (const item of results.items) {
335
333
  const badge = item.type === 'recipe' ? '📘' : item.type === 'solution' ? '💡' : '🛡️';
336
334
  const score = item.score ? ` [${(item.score * 100).toFixed(0)}%]` : '';
337
- console.log(` ${badge} ${item.title || item.trigger || item.id}${score}`);
335
+ cli.log(` ${badge} ${item.title || item.trigger || item.id}${score}`);
338
336
  if (item.description) {
339
- console.log(` ${item.description.slice(0, 100)}`);
337
+ cli.log(` ${item.description.slice(0, 100)}`);
340
338
  }
341
339
  }
342
- console.log('');
340
+ cli.blank();
343
341
  }
344
342
 
345
343
  await bootstrap.shutdown();
346
344
  } catch (err) {
347
- console.error('Error:', err.message);
345
+ cli.error('Error:', err.message);
348
346
  process.exit(1);
349
347
  }
350
348
  });
@@ -361,7 +359,7 @@ program
361
359
  try {
362
360
  const filePath = resolve(file);
363
361
  if (!existsSync(filePath)) {
364
- console.error(`File not found: ${filePath}`);
362
+ cli.error(`File not found: ${filePath}`);
365
363
  process.exit(1);
366
364
  }
367
365
 
@@ -374,26 +372,26 @@ program
374
372
  const violations = engine.checkCode(code, language, { scope: opts.scope });
375
373
 
376
374
  if (opts.json) {
377
- console.log(JSON.stringify({ violations, summary: { total: violations.length, errors: violations.filter(v => v.severity === 'error').length, warnings: violations.filter(v => v.severity === 'warning').length } }, null, 2));
375
+ cli.json({ violations, summary: { total: violations.length, errors: violations.filter(v => v.severity === 'error').length, warnings: violations.filter(v => v.severity === 'warning').length } });
378
376
  } else if (violations.length === 0) {
379
- console.log('✅ No violations found.');
377
+ cli.log('✅ No violations found.');
380
378
  } else {
381
379
  const errors = violations.filter((v) => v.severity === 'error');
382
380
  const warnings = violations.filter((v) => v.severity === 'warning');
383
- console.log(`\n🔍 Guard: ${violations.length} violation(s) — ${errors.length} error(s), ${warnings.length} warning(s)\n`);
381
+ cli.log(`\n🔍 Guard: ${violations.length} violation(s) — ${errors.length} error(s), ${warnings.length} warning(s)\n`);
384
382
  for (const v of violations) {
385
383
  const icon = v.severity === 'error' ? '❌' : v.severity === 'warning' ? '⚠️' : 'ℹ️';
386
- console.log(` ${icon} [${v.ruleId}] ${v.message}`);
387
- if (v.line) console.log(` Line ${v.line}: ${v.snippet || ''}`);
388
- if (v.fixSuggestion) console.log(` 💡 Fix: ${v.fixSuggestion}`);
384
+ cli.log(` ${icon} [${v.ruleId}] ${v.message}`);
385
+ if (v.line) cli.log(` Line ${v.line}: ${v.snippet || ''}`);
386
+ if (v.fixSuggestion) cli.log(` 💡 Fix: ${v.fixSuggestion}`);
389
387
  }
390
- console.log('');
388
+ cli.blank();
391
389
  }
392
390
 
393
391
  await bootstrap.shutdown();
394
392
  process.exit(violations.some((v) => v.severity === 'error') ? 1 : 0);
395
393
  } catch (err) {
396
- console.error('Error:', err.message);
394
+ cli.error('Error:', err.message);
397
395
  process.exit(1);
398
396
  }
399
397
  });
@@ -432,9 +430,9 @@ program
432
430
  if (opts.output) {
433
431
  const { writeFileSync } = await import('node:fs');
434
432
  writeFileSync(opts.output, output, 'utf8');
435
- console.log(`Report written to ${opts.output}`);
433
+ cli.log(`Report written to ${opts.output}`);
436
434
  } else {
437
- console.log(output);
435
+ cli.log(output);
438
436
  }
439
437
  } else {
440
438
  reporter.printReport(report, { format: opts.report });
@@ -454,10 +452,8 @@ program
454
452
  }
455
453
  process.exit(0);
456
454
  } catch (err) {
457
- console.error('Error:', err.message);
458
- if (process.env.ASD_DEBUG === '1') {
459
- console.error(err.stack);
460
- }
455
+ cli.error(`Error: ${err.message}`);
456
+ cli.debug(err.stack);
461
457
  process.exit(1);
462
458
  }
463
459
  });
@@ -484,7 +480,7 @@ program
484
480
  .split('\n')
485
481
  .filter(Boolean);
486
482
  } catch (_err) {
487
- console.error('❌ 无法获取 git staged 文件(是否在 git 仓库中?)');
483
+ cli.error('❌ 无法获取 git staged 文件(是否在 git 仓库中?)');
488
484
  process.exit(1);
489
485
  }
490
486
 
@@ -520,32 +516,30 @@ program
520
516
  const { summary } = result;
521
517
 
522
518
  if (opts.json) {
523
- console.log(JSON.stringify({ files: result.files, summary }, null, 2));
519
+ cli.json({ files: result.files, summary });
524
520
  } else if (summary.totalViolations === 0) {
525
- console.log(`✅ ${sourceFiles.length} staged file(s) checked — no violations.`);
521
+ cli.log(`✅ ${sourceFiles.length} staged file(s) checked — no violations.`);
526
522
  } else {
527
- console.log(`\n🔍 Guard (staged): ${summary.totalViolations} violation(s) in ${sourceFiles.length} file(s)\n`);
523
+ cli.log(`\n🔍 Guard (staged): ${summary.totalViolations} violation(s) in ${sourceFiles.length} file(s)\n`);
528
524
  const filesWithIssues = result.files.filter((f) => f.summary.total > 0);
529
525
  for (const file of filesWithIssues.slice(0, 10)) {
530
- console.log(` 📄 ${file.filePath || file.path}`);
526
+ cli.log(` 📄 ${file.filePath || file.path}`);
531
527
  for (const v of file.violations.slice(0, 5)) {
532
528
  const icon = v.severity === 'error' ? '❌' : '⚠️';
533
- console.log(` ${icon} [${v.ruleId}] ${v.message}`);
529
+ cli.log(` ${icon} [${v.ruleId}] ${v.message}`);
534
530
  }
535
531
  if (file.violations.length > 5) {
536
- console.log(` ... and ${file.violations.length - 5} more`);
532
+ cli.log(` ... and ${file.violations.length - 5} more`);
537
533
  }
538
534
  }
539
- console.log('');
535
+ cli.blank();
540
536
  }
541
537
 
542
538
  await bootstrap.shutdown();
543
539
  process.exit(summary.totalErrors > 0 ? 1 : 0);
544
540
  } catch (err) {
545
- console.error('Error:', err.message);
546
- if (process.env.ASD_DEBUG === '1') {
547
- console.error(err.stack);
548
- }
541
+ cli.error(`Error: ${err.message}`);
542
+ cli.debug(err.stack);
549
543
  process.exit(1);
550
544
  }
551
545
  });
@@ -596,10 +590,8 @@ program
596
590
  process.exit(0);
597
591
  });
598
592
  } catch (err) {
599
- console.error('Error:', err.message);
600
- if (process.env.ASD_DEBUG === '1') {
601
- console.error(err.stack);
602
- }
593
+ cli.error(`Error: ${err.message}`);
594
+ cli.debug(err.stack);
603
595
  process.exit(1);
604
596
  }
605
597
  });
@@ -686,10 +678,8 @@ program
686
678
  signalCollector.start();
687
679
  global._signalCollector = signalCollector;
688
680
  } catch (scErr) {
689
- console.warn(`⚠️ SignalCollector failed to start: ${scErr.message}`);
690
- if (process.env.ASD_DEBUG === '1') {
691
- console.error(scErr.stack);
692
- }
681
+ cli.warn(`⚠️ SignalCollector failed to start: ${scErr.message}`);
682
+ cli.debug(scErr.stack);
693
683
  }
694
684
 
695
685
  // 3. 启动文件监听器(仅 iOS/macOS 项目 — Xcode 工作流)
@@ -779,17 +769,15 @@ program
779
769
  if (isDebugMode) {
780
770
  }
781
771
  } catch (watchErr) {
782
- console.warn(`⚠️ File watcher failed to start: ${watchErr.message}`);
783
- if (process.env.ASD_DEBUG === '1') {
784
- console.error(watchErr.stack);
785
- }
772
+ cli.warn(`⚠️ File watcher failed to start: ${watchErr.message}`);
773
+ cli.debug(watchErr.stack);
786
774
  }
787
775
  } else if (process.env.ASD_DEBUG === '1') {
788
776
  }
789
777
  } catch (err) {
790
- console.error(`❌ API server failed to start: ${err.message}`);
778
+ cli.error(`❌ API server failed to start: ${err.message}`);
791
779
  if (err.code === 'EADDRINUSE') {
792
- console.error(
780
+ cli.error(
793
781
  ` Port ${port} is already in use. Kill it with: lsof -ti:${port} | xargs kill -9`
794
782
  );
795
783
  }
@@ -836,7 +824,7 @@ program
836
824
  });
837
825
 
838
826
  vite.on('error', (err) => {
839
- console.error(`❌ Vite failed to start: ${err.message}`);
827
+ cli.error(`❌ Vite failed to start: ${err.message}`);
840
828
  });
841
829
 
842
830
  process.on('SIGINT', () => {
@@ -853,35 +841,35 @@ program
853
841
  .command('status')
854
842
  .description('检查环境状态')
855
843
  .action(async () => {
856
- console.log('\n AutoSnippet Environment Status');
857
- console.log(` ${'─'.repeat(40)}`);
844
+ cli.log('\n AutoSnippet Environment Status');
845
+ cli.log(` ${'─'.repeat(40)}`);
858
846
 
859
847
  // AI 配置
860
848
  const { getAiConfigInfo } = await import('../lib/external/ai/AiFactory.js');
861
849
  const aiInfo = getAiConfigInfo();
862
- console.log(` AI Provider: ${aiInfo.provider || 'not configured'}`);
863
- if (aiInfo.model) console.log(` AI Model: ${aiInfo.model}`);
850
+ cli.log(` AI Provider: ${aiInfo.provider || 'not configured'}`);
851
+ if (aiInfo.model) cli.log(` AI Model: ${aiInfo.model}`);
864
852
 
865
853
  // 检查数据库
866
854
  const dbPath = join(process.cwd(), '.autosnippet', 'autosnippet.db');
867
855
  const dbExists = existsSync(dbPath);
868
- console.log(` Database: ${dbExists ? '✅ ' + dbPath : '❌ not found'}`);
856
+ cli.log(` Database: ${dbExists ? '✅ ' + dbPath : '❌ not found'}`);
869
857
 
870
858
  // 检查 .autosnippet 目录
871
859
  const asdDir = join(process.cwd(), '.autosnippet');
872
- console.log(` Workspace: ${existsSync(asdDir) ? '✅ .autosnippet/' : '❌ not initialized (run asd setup)'}`);
860
+ cli.log(` Workspace: ${existsSync(asdDir) ? '✅ .autosnippet/' : '❌ not initialized (run asd setup)'}`);
873
861
 
874
862
  // 检查依赖
875
- console.log(' Dependencies:');
863
+ cli.log(' Dependencies:');
876
864
  for (const dep of ['better-sqlite3', 'commander', 'express']) {
877
865
  try {
878
866
  await import(dep);
879
- console.log(` ✅ ${dep}`);
867
+ cli.log(` ✅ ${dep}`);
880
868
  } catch {
881
- console.log(` ❌ ${dep} (missing)`);
869
+ cli.log(` ❌ ${dep} (missing)`);
882
870
  }
883
871
  }
884
- console.log('');
872
+ cli.blank();
885
873
  });
886
874
 
887
875
  // ─────────────────────────────────────────────────────
@@ -889,7 +877,7 @@ program
889
877
  // ─────────────────────────────────────────────────────
890
878
  program
891
879
  .command('upgrade')
892
- .description('升级 IDE 集成:MCP 配置、Skills、Cursor Rules、Copilot Instructions')
880
+ .description('升级 IDE 集成(全量:MCP + Rules + Hooks + Instructions + Skills + Constitution + .gitignore)')
893
881
  .option('-d, --dir <path>', '项目目录', '.')
894
882
  .option('--skills-only', '仅更新 Skills')
895
883
  .option('--mcp-only', '仅更新 MCP 配置')
@@ -918,22 +906,22 @@ program
918
906
  try {
919
907
  const pipeline = container.get('cursorDeliveryPipeline');
920
908
  const result = await pipeline.deliver();
921
- console.log('\n Cursor Rules Delivery');
922
- console.log(` ${'─'.repeat(40)}`);
923
- console.log(` Channel A: ${result.channelA?.count ?? '?'} always-on rules`);
924
- console.log(` Channel B: ${result.channelB?.count ?? Object.keys(result.channelB?.topics || {}).length} topic rules`);
925
- console.log(` Channel C: ${result.channelC?.count ?? '?'} skills (${result.channelC?.errors ?? 0} errors)`);
909
+ cli.log('\n Cursor Rules Delivery');
910
+ cli.log(` ${'─'.repeat(40)}`);
911
+ cli.log(` Channel A: ${result.channelA?.count ?? '?'} always-on rules`);
912
+ cli.log(` Channel B: ${result.channelB?.count ?? Object.keys(result.channelB?.topics || {}).length} topic rules`);
913
+ cli.log(` Channel C: ${result.channelC?.count ?? '?'} skills (${result.channelC?.errors ?? 0} errors)`);
926
914
  if (result.channelC.errors > 0) {
927
- console.log(` ⚠️ ${result.channelC.errors} skill(s) failed to deliver`);
915
+ cli.log(` ⚠️ ${result.channelC.errors} skill(s) failed to deliver`);
928
916
  }
929
917
 
930
918
  if (opts.verbose && result.channelB.topics) {
931
- console.log('\n Channel B Topics:');
919
+ cli.log('\n Channel B Topics:');
932
920
  for (const [topic, info] of Object.entries(result.channelB.topics)) {
933
- console.log(` ${topic}: ${info.count ?? info.rules?.length ?? '?'} rules`);
921
+ cli.log(` ${topic}: ${info.count ?? info.rules?.length ?? '?'} rules`);
934
922
  }
935
923
  }
936
- console.log('');
924
+ cli.blank();
937
925
  } finally {
938
926
  await bootstrap.shutdown?.();
939
927
  }
@@ -961,18 +949,18 @@ taskCmd
961
949
  if (opts.status) filters.status = opts.status;
962
950
  const tasks = await svc.list(filters, { limit: parseInt(opts.limit, 10) });
963
951
  if (tasks.length === 0) {
964
- console.log('No tasks found.');
952
+ cli.log('No tasks found.');
965
953
  } else {
966
- console.log(`\n ID Status Priority Title`);
967
- console.log(` ${'─'.repeat(70)}`);
954
+ cli.log(`\n ID Status Priority Title`);
955
+ cli.log(` ${'─'.repeat(70)}`);
968
956
  for (const t of tasks) {
969
957
  const j = t.toJSON ? t.toJSON() : t;
970
958
  const id = (j.id || '').padEnd(16);
971
959
  const status = (j.status || '').padEnd(13);
972
960
  const pri = String(j.priority ?? '-').padEnd(9);
973
- console.log(` ${id} ${status} ${pri} ${j.title}`);
961
+ cli.log(` ${id} ${status} ${pri} ${j.title}`);
974
962
  }
975
- console.log(`\n Total: ${tasks.length}\n`);
963
+ cli.log(`\n Total: ${tasks.length}\n`);
976
964
  }
977
965
  } finally {
978
966
  await bootstrap.shutdown?.();
@@ -994,19 +982,19 @@ taskCmd
994
982
  withKnowledge: true,
995
983
  });
996
984
  if (tasks.length === 0) {
997
- console.log('No ready tasks.');
985
+ cli.log('No ready tasks.');
998
986
  } else {
999
987
  for (const t of tasks) {
1000
988
  const j = t.toJSON ? t.toJSON() : t;
1001
- console.log(`\n ▸ ${j.id} — ${j.title} (P${j.priority ?? '?'})`);
989
+ cli.log(`\n ▸ ${j.id} — ${j.title} (P${j.priority ?? '?'})`);
1002
990
  if (t.knowledgeContext?.relatedKnowledge?.length) {
1003
- console.log(` Knowledge: ${t.knowledgeContext.relatedKnowledge.map(k => k.title).join(', ')}`);
991
+ cli.log(` Knowledge: ${t.knowledgeContext.relatedKnowledge.map(k => k.title).join(', ')}`);
1004
992
  }
1005
993
  if (t.knowledgeContext?.guardRules?.length) {
1006
- console.log(` Guard: ${t.knowledgeContext.guardRules.map(r => r.title).join(', ')}`);
994
+ cli.log(` Guard: ${t.knowledgeContext.guardRules.map(r => r.title).join(', ')}`);
1007
995
  }
1008
996
  }
1009
- console.log('');
997
+ cli.blank();
1010
998
  }
1011
999
  } finally {
1012
1000
  await bootstrap.shutdown?.();
@@ -1023,24 +1011,24 @@ taskCmd
1023
1011
  try {
1024
1012
  const svc = container.get('taskGraphService');
1025
1013
  const result = await svc.prime({ withKnowledge: true });
1026
- console.log(`\n TaskGraph Prime`);
1027
- console.log(` ${'─'.repeat(40)}`);
1028
- console.log(` In Progress: ${result.inProgress.length}`);
1029
- console.log(` Ready: ${result.ready.length}`);
1030
- console.log(` Stats: ${JSON.stringify(result.stats)}`);
1014
+ cli.log(`\n TaskGraph Prime`);
1015
+ cli.log(` ${'─'.repeat(40)}`);
1016
+ cli.log(` In Progress: ${result.inProgress.length}`);
1017
+ cli.log(` Ready: ${result.ready.length}`);
1018
+ cli.log(` Stats: ${JSON.stringify(result.stats)}`);
1031
1019
  if (result.inProgress.length > 0) {
1032
- console.log(`\n ▸ In Progress:`);
1020
+ cli.log(`\n ▸ In Progress:`);
1033
1021
  for (const t of result.inProgress) {
1034
- console.log(` ${t.id} — ${t.title}`);
1022
+ cli.log(` ${t.id} — ${t.title}`);
1035
1023
  }
1036
1024
  }
1037
1025
  if (result.ready.length > 0) {
1038
- console.log(`\n ▸ Ready:`);
1026
+ cli.log(`\n ▸ Ready:`);
1039
1027
  for (const t of result.ready) {
1040
- console.log(` ${t.id} — ${t.title}`);
1028
+ cli.log(` ${t.id} — ${t.title}`);
1041
1029
  }
1042
1030
  }
1043
- console.log('');
1031
+ cli.blank();
1044
1032
  } finally {
1045
1033
  await bootstrap.shutdown?.();
1046
1034
  }
@@ -1056,12 +1044,12 @@ taskCmd
1056
1044
  try {
1057
1045
  const svc = container.get('taskGraphService');
1058
1046
  const stats = await svc.stats();
1059
- console.log(`\n TaskGraph Statistics`);
1060
- console.log(` ${'─'.repeat(30)}`);
1047
+ cli.log(`\n TaskGraph Statistics`);
1048
+ cli.log(` ${'─'.repeat(30)}`);
1061
1049
  for (const [key, val] of Object.entries(stats)) {
1062
- console.log(` ${key.padEnd(15)} ${val}`);
1050
+ cli.log(` ${key.padEnd(15)} ${val}`);
1063
1051
  }
1064
- console.log('');
1052
+ cli.blank();
1065
1053
  } finally {
1066
1054
  await bootstrap.shutdown?.();
1067
1055
  }
@@ -1081,11 +1069,14 @@ program
1081
1069
 
1082
1070
  const cursorDir = join(projectRoot, '.cursor');
1083
1071
  if (!existsSync(cursorDir)) {
1084
- console.error('❌ 未找到 .cursor/ 目录,请先运行 asd setup 或 asd cursor-rules');
1072
+ cli.error('❌ 未找到 .cursor/ 目录,请先运行 asd setup 或 asd cursor-rules');
1085
1073
  process.exit(1);
1086
1074
  }
1087
1075
 
1088
1076
  for (const target of targets) {
1077
+ let count = 0;
1078
+
1079
+ // 1. 镜像 rules/ — autosnippet- 前缀文件(.mdc → .md 改名)
1089
1080
  const cursorRulesDir = join(cursorDir, 'rules');
1090
1081
  if (existsSync(cursorRulesDir)) {
1091
1082
  const targetRulesDir = join(projectRoot, target, 'rules');
@@ -1096,9 +1087,11 @@ program
1096
1087
  for (const file of files) {
1097
1088
  const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
1098
1089
  copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
1090
+ count++;
1099
1091
  }
1100
1092
  }
1101
1093
 
1094
+ // 2. 镜像 skills/ — autosnippet- 前缀目录
1102
1095
  const cursorSkillsDir = join(cursorDir, 'skills');
1103
1096
  if (existsSync(cursorSkillsDir)) {
1104
1097
  const targetSkillsDir = join(projectRoot, target, 'skills');
@@ -1107,8 +1100,34 @@ program
1107
1100
  );
1108
1101
  for (const dir of skillDirs) {
1109
1102
  _copyDirRecursive(join(cursorSkillsDir, dir.name), join(targetSkillsDir, dir.name));
1103
+ count++;
1110
1104
  }
1111
1105
  }
1106
+
1107
+ // 3. 镜像 hooks/ — hook 脚本(全覆盖)
1108
+ const cursorHooksDir = join(cursorDir, 'hooks');
1109
+ if (existsSync(cursorHooksDir)) {
1110
+ _copyDirRecursive(cursorHooksDir, join(projectRoot, target, 'hooks'));
1111
+ count++;
1112
+ }
1113
+
1114
+ // 4. 镜像 commands/ — 斜杠命令(全覆盖)
1115
+ const cursorCommandsDir = join(cursorDir, 'commands');
1116
+ if (existsSync(cursorCommandsDir)) {
1117
+ _copyDirRecursive(cursorCommandsDir, join(projectRoot, target, 'commands'));
1118
+ count++;
1119
+ }
1120
+
1121
+ // 5. 镜像 hooks.json
1122
+ const hooksJson = join(cursorDir, 'hooks.json');
1123
+ if (existsSync(hooksJson)) {
1124
+ mkdirSync(join(projectRoot, target), { recursive: true });
1125
+ copyFileSync(hooksJson, join(projectRoot, target, 'hooks.json'));
1126
+ count++;
1127
+ }
1128
+
1129
+ const label = target.replace('.', '').charAt(0).toUpperCase() + target.slice(2);
1130
+ cli.log(` ✅ ${label}: ${count} item(s) mirrored`);
1112
1131
  }
1113
1132
  });
1114
1133
 
@@ -1140,7 +1159,7 @@ program
1140
1159
  const { KnowledgeSyncService } = await import('../lib/cli/KnowledgeSyncService.js');
1141
1160
  const syncService = new KnowledgeSyncService(projectRoot);
1142
1161
  if (opts.dryRun) {
1143
- console.log('ℹ️ Dry-run mode: no changes will be written');
1162
+ cli.log('ℹ️ Dry-run mode: no changes will be written');
1144
1163
  }
1145
1164
 
1146
1165
  // 通过 Bootstrap 打开目标项目的 DB
@@ -1153,7 +1172,7 @@ program
1153
1172
  const { bootstrap, container } = await initContainer({ projectRoot });
1154
1173
  const db = container.get('database')?.getDb?.();
1155
1174
  if (!db) {
1156
- console.error('❌ 无法打开数据库,请先运行 asd setup');
1175
+ cli.error('❌ 无法打开数据库,请先运行 asd setup');
1157
1176
  process.exit(1);
1158
1177
  }
1159
1178
 
@@ -1163,27 +1182,27 @@ program
1163
1182
  force: opts.force,
1164
1183
  });
1165
1184
 
1166
- console.log('\n Knowledge Sync Report');
1167
- console.log(` ${'─'.repeat(40)}`);
1168
- console.log(` Created: ${report.created ?? 0}`);
1169
- console.log(` Updated: ${report.updated ?? 0}`);
1170
- console.log(` Unchanged: ${report.unchanged ?? 0}`);
1171
- console.log(` Deleted: ${report.deleted ?? 0}`);
1185
+ cli.log('\n Knowledge Sync Report');
1186
+ cli.log(` ${'─'.repeat(40)}`);
1187
+ cli.log(` Created: ${report.created ?? 0}`);
1188
+ cli.log(` Updated: ${report.updated ?? 0}`);
1189
+ cli.log(` Unchanged: ${report.unchanged ?? 0}`);
1190
+ cli.log(` Deleted: ${report.deleted ?? 0}`);
1172
1191
 
1173
1192
  if (report.violations.length > 0) {
1174
- console.log(`\n ⚠️ Violations (${report.violations.length}):`);
1193
+ cli.log(`\n ⚠️ Violations (${report.violations.length}):`);
1175
1194
  for (const v of report.violations) {
1176
- console.log(` ❌ ${v.file || v.id}: ${v.message || v}`);
1195
+ cli.log(` ❌ ${v.file || v.id}: ${v.message || v}`);
1177
1196
  }
1178
1197
  }
1179
1198
 
1180
1199
  if (report.orphaned.length > 0) {
1181
- console.log(`\n 👻 Orphaned entries (${report.orphaned.length}):`);
1200
+ cli.log(`\n 👻 Orphaned entries (${report.orphaned.length}):`);
1182
1201
  for (const id of report.orphaned) {
1183
- console.log(` ${id}`);
1202
+ cli.log(` ${id}`);
1184
1203
  }
1185
1204
  }
1186
- console.log('');
1205
+ cli.blank();
1187
1206
  } finally {
1188
1207
  await bootstrap.shutdown?.();
1189
1208
  }