autosnippet 3.0.11 → 3.1.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 (120) hide show
  1. package/bin/api-server.js +2 -0
  2. package/bin/cli.js +84 -16
  3. package/config/default.json +10 -1
  4. package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
  5. package/dashboard/dist/index.html +1 -1
  6. package/lib/bootstrap.js +4 -4
  7. package/lib/cli/SetupService.js +116 -29
  8. package/lib/cli/UpgradeService.js +16 -6
  9. package/lib/core/AstAnalyzer.js +1 -1
  10. package/lib/core/ast/ensure-grammars.js +1 -1
  11. package/lib/core/ast/index.js +62 -11
  12. package/lib/core/ast/lang-dart.js +27 -21
  13. package/lib/core/ast/lang-go.js +6 -20
  14. package/lib/core/ast/lang-rust.js +53 -28
  15. package/lib/core/ast/parser-init.js +9 -5
  16. package/lib/core/discovery/DartDiscoverer.js +4 -10
  17. package/lib/core/discovery/GenericDiscoverer.js +4 -28
  18. package/lib/core/discovery/GoDiscoverer.js +45 -25
  19. package/lib/core/discovery/NodeDiscoverer.js +1 -3
  20. package/lib/core/discovery/PythonDiscoverer.js +7 -1
  21. package/lib/core/discovery/RustDiscoverer.js +111 -38
  22. package/lib/core/discovery/index.js +2 -2
  23. package/lib/core/enhancement/django-enhancement.js +10 -4
  24. package/lib/core/enhancement/fastapi-enhancement.js +16 -9
  25. package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
  26. package/lib/core/enhancement/go-web-enhancement.js +3 -6
  27. package/lib/core/enhancement/ml-enhancement.js +6 -3
  28. package/lib/core/enhancement/nextjs-enhancement.js +17 -7
  29. package/lib/core/enhancement/node-server-enhancement.js +4 -2
  30. package/lib/core/enhancement/react-enhancement.js +6 -3
  31. package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
  32. package/lib/core/enhancement/rust-web-enhancement.js +13 -7
  33. package/lib/core/enhancement/vue-enhancement.js +10 -5
  34. package/lib/external/ai/AiFactory.js +3 -1
  35. package/lib/external/ai/AiProvider.js +3 -1
  36. package/lib/external/mcp/McpServer.js +2 -0
  37. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +245 -0
  38. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +86 -0
  39. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
  40. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +629 -0
  41. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +131 -348
  42. package/lib/external/mcp/handlers/bootstrap/refine.js +364 -0
  43. package/lib/external/mcp/handlers/bootstrap.js +7 -597
  44. package/lib/external/mcp/handlers/browse.js +123 -9
  45. package/lib/external/mcp/handlers/guard.js +29 -6
  46. package/lib/external/mcp/handlers/search.js +56 -24
  47. package/lib/external/mcp/handlers/skill.js +6 -2
  48. package/lib/http/HttpServer.js +1 -1
  49. package/lib/http/routes/candidates.js +3 -1
  50. package/lib/http/routes/extract.js +4 -5
  51. package/lib/http/routes/guardRules.js +9 -17
  52. package/lib/http/routes/modules.js +9 -3
  53. package/lib/http/routes/skills.js +54 -6
  54. package/lib/http/routes/violations.js +4 -3
  55. package/lib/infrastructure/external/ClipboardManager.js +24 -7
  56. package/lib/infrastructure/external/NativeUi.js +3 -1
  57. package/lib/infrastructure/external/OpenBrowser.js +1 -0
  58. package/lib/infrastructure/external/XcodeAutomation.js +5 -5
  59. package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
  60. package/lib/injection/ServiceContainer.js +45 -13
  61. package/lib/platform/ios/index.js +20 -25
  62. package/lib/platform/ios/routes/spm.js +6 -3
  63. package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
  64. package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
  65. package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
  66. package/lib/platform/ios/spm/SpmService.js +3 -1
  67. package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
  68. package/lib/platform/ios/xcode/XcodeIntegration.js +43 -664
  69. package/lib/platform/ios/xcode/XcodeWriteUtils.js +225 -0
  70. package/lib/service/automation/FileWatcher.js +1 -3
  71. package/lib/service/automation/handlers/CreateHandler.js +3 -5
  72. package/lib/service/automation/handlers/GuardHandler.js +11 -32
  73. package/lib/service/automation/handlers/SearchHandler.js +9 -9
  74. package/lib/service/chat/CandidateGuardrail.js +11 -6
  75. package/lib/service/chat/ChatAgent.js +51 -421
  76. package/lib/service/chat/ChatAgentPrompts.js +149 -0
  77. package/lib/service/chat/ChatAgentTasks.js +297 -0
  78. package/lib/service/chat/HandoffProtocol.js +5 -2
  79. package/lib/service/chat/tools/_shared.js +61 -0
  80. package/lib/service/chat/tools/ai-analysis.js +284 -0
  81. package/lib/service/chat/tools/ast-graph.js +681 -0
  82. package/lib/service/chat/tools/composite.js +497 -0
  83. package/lib/service/chat/tools/guard.js +265 -0
  84. package/lib/service/chat/tools/index.js +239 -0
  85. package/lib/service/chat/tools/infrastructure.js +227 -0
  86. package/lib/service/chat/tools/knowledge-graph.js +234 -0
  87. package/lib/service/chat/tools/lifecycle.js +486 -0
  88. package/lib/service/chat/tools/project-access.js +919 -0
  89. package/lib/service/chat/tools/query.js +264 -0
  90. package/lib/service/chat/tools.js +13 -3994
  91. package/lib/service/cursor/AgentInstructionsGenerator.js +413 -0
  92. package/lib/service/cursor/CursorDeliveryPipeline.js +71 -11
  93. package/lib/service/cursor/FileProtection.js +116 -0
  94. package/lib/service/cursor/KnowledgeCompressor.js +70 -11
  95. package/lib/service/cursor/SkillsSyncer.js +5 -3
  96. package/lib/service/cursor/TopicClassifier.js +19 -3
  97. package/lib/service/guard/ComplianceReporter.js +5 -2
  98. package/lib/service/guard/ExclusionManager.js +26 -2
  99. package/lib/service/guard/GuardCheckEngine.js +83 -388
  100. package/lib/service/guard/GuardCodeChecks.js +391 -0
  101. package/lib/service/guard/GuardCrossFileChecks.js +326 -0
  102. package/lib/service/guard/GuardPatternUtils.js +187 -0
  103. package/lib/service/guard/GuardService.js +80 -38
  104. package/lib/service/module/ModuleService.js +181 -56
  105. package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
  106. package/lib/service/search/SearchEngine.js +10 -2
  107. package/lib/service/snippet/SnippetFactory.js +3 -3
  108. package/lib/service/snippet/SnippetInstaller.js +35 -11
  109. package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
  110. package/lib/service/wiki/WikiGenerator.js +247 -1535
  111. package/lib/service/wiki/WikiRenderers.js +1903 -0
  112. package/lib/service/wiki/WikiUtils.js +1044 -0
  113. package/lib/shared/LanguageService.js +359 -2
  114. package/lib/shared/PathGuard.js +0 -8
  115. package/package.json +3 -9
  116. package/scripts/bench-real-projects.mjs +29 -29
  117. package/scripts/generate-recipe-drafts.js +17 -27
  118. package/scripts/init-snippets.js +43 -24
  119. package/scripts/install-vscode-copilot.js +3 -19
  120. package/scripts/setup-mcp-config.js +0 -4
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>AutoSnippet Dashboard</title>
8
- <script type="module" crossorigin src="/assets/index-I2ySoCmF.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-Bnm26ulL.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-CEnWn7aV.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
package/lib/bootstrap.js CHANGED
@@ -57,7 +57,7 @@ export class Bootstrap {
57
57
  // 2. 初始化日志系统
58
58
  await this.initializeLogger();
59
59
 
60
- this.components.logger.info('AutoSnippet 2.0 - Starting initialization...');
60
+ this.components.logger.info('AutoSnippet - Starting initialization...');
61
61
 
62
62
  // 3. 连接数据库
63
63
  await this.initializeDatabase();
@@ -75,7 +75,7 @@ export class Bootstrap {
75
75
  // await this.registerRoutes();
76
76
 
77
77
  const duration = Date.now() - startTime;
78
- this.components.logger.info(`AutoSnippet 2.0 initialized successfully (${duration}ms)`);
78
+ this.components.logger.info(`AutoSnippet initialized successfully (${duration}ms)`);
79
79
 
80
80
  return this.components;
81
81
  } catch (error) {
@@ -200,14 +200,14 @@ export class Bootstrap {
200
200
  * 关闭应用程序
201
201
  */
202
202
  async shutdown() {
203
- this.components.logger?.info('AutoSnippet 2.0 - Shutting down...');
203
+ this.components.logger?.info('AutoSnippet - Shutting down...');
204
204
 
205
205
  // 关闭数据库连接
206
206
  if (this.components.db) {
207
207
  await this.components.db.close();
208
208
  }
209
209
 
210
- this.components.logger?.info('AutoSnippet 2.0 - Shutdown complete');
210
+ this.components.logger?.info('AutoSnippet - Shutdown complete');
211
211
  }
212
212
 
213
213
  /**
@@ -53,6 +53,7 @@ import {
53
53
  } from 'node:fs';
54
54
  import { dirname, join, resolve } from 'node:path';
55
55
  import { fileURLToPath } from 'node:url';
56
+ import { checkWriteSafety } from '../service/cursor/FileProtection.js';
56
57
 
57
58
  const __filename = fileURLToPath(import.meta.url);
58
59
  const __dirname = dirname(__filename);
@@ -102,21 +103,15 @@ export class SetupService {
102
103
  const results = [];
103
104
  const total = steps.length;
104
105
 
105
- console.log('');
106
- console.log(` ⚙ AutoSnippet Setup — ${this.projectName}`);
107
- console.log(` ${'─'.repeat(44)}`);
108
-
109
106
  for (let i = 0; i < total; i++) {
110
107
  const { label, fn } = steps[i];
111
108
  const tag = `[${i + 1}/${total}]`;
112
109
  process.stdout.write(` ${tag} ${label}...`);
113
110
  try {
114
111
  const r = await fn();
115
- const detail = this._formatStepDetail(r);
116
- console.log(` ✅${detail}`);
112
+ const _detail = this._formatStepDetail(r);
117
113
  results.push({ step: i + 1, label, ok: true, ...(r || {}) });
118
114
  } catch (err) {
119
- console.log(` ❌`);
120
115
  console.error(` ${err.message}`);
121
116
  results.push({ step: i + 1, label, ok: false, error: err.message });
122
117
  }
@@ -128,7 +123,9 @@ export class SetupService {
128
123
 
129
124
  /** @private 格式化步骤结果的简要信息 */
130
125
  _formatStepDetail(r) {
131
- if (!r) return '';
126
+ if (!r) {
127
+ return '';
128
+ }
132
129
  const parts = [];
133
130
  if (r.configured) {
134
131
  parts.push(r.configured.join(', '));
@@ -144,17 +141,8 @@ export class SetupService {
144
141
 
145
142
  printSummary() {
146
143
  const results = this._results || [];
147
- const ok = results.filter((r) => r.ok).length;
148
- const fail = results.filter((r) => !r.ok).length;
149
-
150
- console.log(` ${'─'.repeat(44)}`);
151
- console.log(` ✨ Setup 完成: ${ok} 成功${fail > 0 ? `, ${fail} 失败` : ''}`);
152
- console.log('');
153
- console.log(' 后续操作:');
154
- console.log(' asd coldstart 扫描项目、AI 生成知识库');
155
- console.log(' asd ui 启动 Dashboard + API Server');
156
- console.log(' asd watch 启动 Xcode 文件监听');
157
- console.log('');
144
+ const _ok = results.filter((r) => r.ok).length;
145
+ const _fail = results.filter((r) => !r.ok).length;
158
146
  }
159
147
 
160
148
  /* ═══ Step 1: 运行时目录与配置 ═══════════════════════ */
@@ -451,10 +439,10 @@ export class SetupService {
451
439
  this._configureVSCodeMCP(mcpServerPath);
452
440
  this._configureCursorMCP(mcpServerPath);
453
441
  this._copyCopilotInstructions();
442
+ this._generateAgentInstructionFiles();
454
443
  this._copyCursorRules();
455
444
  this._copySkillsTemplate();
456
- this._mirrorCursorToIDE('.qoder');
457
- this._mirrorCursorToIDE('.trae');
445
+ // NOTE: .qoder/ .trae/ 不再自动创建,用户可通过 `asd mirror` 按需生成
458
446
 
459
447
  const extResult = this._installVSCodeExtension();
460
448
 
@@ -462,10 +450,9 @@ export class SetupService {
462
450
  'vscode-mcp',
463
451
  'cursor-mcp',
464
452
  'copilot-instructions',
453
+ 'agent-instructions',
465
454
  'cursor-rules',
466
455
  'skills-template',
467
- 'qoder-rules',
468
- 'trae-rules',
469
456
  ];
470
457
  if (extResult) {
471
458
  configured.push(...extResult);
@@ -577,7 +564,12 @@ export class SetupService {
577
564
  const whichCmd = process.platform === 'win32' ? 'where' : 'which';
578
565
  for (const cmd of ['code', 'cursor', 'codex', 'code-insiders']) {
579
566
  try {
580
- const p = execSync(`${whichCmd} ${cmd}`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim().split(/\r?\n/)[0];
567
+ const p = execSync(`${whichCmd} ${cmd}`, {
568
+ encoding: 'utf8',
569
+ stdio: ['pipe', 'pipe', 'ignore'],
570
+ })
571
+ .trim()
572
+ .split(/\r?\n/)[0];
581
573
  if (p) {
582
574
  candidates.push({ name: cmd, cli: p });
583
575
  }
@@ -591,7 +583,11 @@ export class SetupService {
591
583
  // macOS: /Applications/xxx.app/Contents/Resources/app/bin/
592
584
  const appPaths = [
593
585
  { name: 'vscode', app: '/Applications/Visual Studio Code.app', bin: 'code' },
594
- { name: 'vscode-insiders', app: '/Applications/Visual Studio Code - Insiders.app', bin: 'code-insiders' },
586
+ {
587
+ name: 'vscode-insiders',
588
+ app: '/Applications/Visual Studio Code - Insiders.app',
589
+ bin: 'code-insiders',
590
+ },
595
591
  { name: 'cursor', app: '/Applications/Cursor.app', bin: 'cursor' },
596
592
  { name: 'codex', app: '/Applications/Codex.app', bin: 'codex' },
597
593
  ];
@@ -603,10 +599,15 @@ export class SetupService {
603
599
  }
604
600
  } else if (process.platform === 'win32') {
605
601
  // Windows: %LOCALAPPDATA%\Programs\xxx
606
- const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || '', 'AppData', 'Local');
602
+ const localAppData =
603
+ process.env.LOCALAPPDATA || join(process.env.USERPROFILE || '', 'AppData', 'Local');
607
604
  const winPaths = [
608
605
  { name: 'vscode', dir: 'Microsoft VS Code', bin: 'bin/code.cmd' },
609
- { name: 'vscode-insiders', dir: 'Microsoft VS Code Insiders', bin: 'bin/code-insiders.cmd' },
606
+ {
607
+ name: 'vscode-insiders',
608
+ dir: 'Microsoft VS Code Insiders',
609
+ bin: 'bin/code-insiders.cmd',
610
+ },
610
611
  { name: 'cursor', dir: 'cursor', bin: 'cursor.exe' },
611
612
  ];
612
613
  for (const { name, dir, bin } of winPaths) {
@@ -639,7 +640,9 @@ export class SetupService {
639
640
  } catch {
640
641
  /* use as-is */
641
642
  }
642
- if (seen.has(realPath)) return false;
643
+ if (seen.has(realPath)) {
644
+ return false;
645
+ }
643
646
  seen.add(realPath);
644
647
  return true;
645
648
  });
@@ -706,7 +709,7 @@ export class SetupService {
706
709
  writeFileSync(configPath, JSON.stringify(existing, null, 2));
707
710
  }
708
711
 
709
- /** @private .github/copilot-instructions.md */
712
+ /** @private .github/copilot-instructions.md (static template fallback) */
710
713
  _copyCopilotInstructions() {
711
714
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
712
715
  if (!existsSync(src)) {
@@ -719,10 +722,94 @@ export class SetupService {
719
722
  return;
720
723
  }
721
724
 
725
+ // 即使 --force,也不覆盖用户原有的非 AutoSnippet 文件
726
+ const { canWrite } = checkWriteSafety(dest);
727
+ if (!canWrite) {
728
+ return;
729
+ }
730
+
722
731
  mkdirSync(destDir, { recursive: true });
723
732
  copyFileSync(src, dest);
724
733
  }
725
734
 
735
+ /**
736
+ * @private 生成 AGENTS.md + CLAUDE.md 静态骨架
737
+ * setup 阶段没有知识库数据,所以只生成指向 MCP 的基础版本。
738
+ * bootstrap / delivery pipeline 完成后会用动态版本覆盖。
739
+ */
740
+ _generateAgentInstructionFiles() {
741
+ const projectName = this.projectRoot.split('/').pop();
742
+
743
+ // AGENTS.md
744
+ const agentsPath = join(this.projectRoot, 'AGENTS.md');
745
+ if (!existsSync(agentsPath) || this.force) {
746
+ const { canWrite } = checkWriteSafety(agentsPath);
747
+ if (canWrite) {
748
+ const agentsContent = [
749
+ `# ${projectName} — Agent Instructions`,
750
+ '',
751
+ '> Auto-generated by AutoSnippet. Will be enriched after `asd bootstrap`.',
752
+ '',
753
+ '## Project Knowledge Base',
754
+ '',
755
+ 'This project uses **AutoSnippet** for knowledge management.',
756
+ 'Run `asd bootstrap` to populate the knowledge base, then this file will be',
757
+ 'regenerated with coding standards, patterns, and tool references.',
758
+ '',
759
+ '## MCP Tools',
760
+ '',
761
+ 'Use these MCP tools to access the full knowledge base:',
762
+ '- `autosnippet_search` — Search knowledge (mode: auto/context/keyword/semantic)',
763
+ '- `autosnippet_knowledge` — Browse/get recipes (operation: list/get/insights)',
764
+ '- `autosnippet_submit_knowledge` — Submit candidate (strict validation)',
765
+ '- `autosnippet_guard` — Code compliance check',
766
+ '- `autosnippet_health` — Service health & KB stats',
767
+ '',
768
+ '## Mandatory Constraints',
769
+ '',
770
+ '1. **Do NOT modify** knowledge base files directly (`AutoSnippet/`, `.autosnippet/`).',
771
+ '2. Create or update knowledge **only** through MCP tools.',
772
+ '3. **Prefer Recipes** as project standards; source code is supplementary.',
773
+ '',
774
+ ].join('\n');
775
+ writeFileSync(agentsPath, agentsContent);
776
+ } else {
777
+ }
778
+ }
779
+
780
+ // CLAUDE.md
781
+ const claudePath = join(this.projectRoot, 'CLAUDE.md');
782
+ if (!existsSync(claudePath) || this.force) {
783
+ const { canWrite } = checkWriteSafety(claudePath);
784
+ if (canWrite) {
785
+ const claudeContent = [
786
+ `# ${projectName} — Claude Code Instructions`,
787
+ '',
788
+ '> Auto-generated by AutoSnippet. Will be enriched after `asd bootstrap`.',
789
+ '',
790
+ '## MCP Integration',
791
+ '',
792
+ 'This project has an AutoSnippet MCP server configured.',
793
+ 'Use the following tools to access project knowledge:',
794
+ '- `autosnippet_search` — Search knowledge (mode: auto/context/keyword/semantic)',
795
+ '- `autosnippet_knowledge` — Browse/get recipes (operation: list/get/insights)',
796
+ '- `autosnippet_submit_knowledge` — Submit candidate (strict validation)',
797
+ '- `autosnippet_guard` — Code compliance check',
798
+ '- `autosnippet_health` — Service health & KB stats',
799
+ '',
800
+ '## Mandatory Constraints',
801
+ '',
802
+ '1. **Do NOT modify** knowledge base files directly (`AutoSnippet/`, `.autosnippet/`).',
803
+ '2. Create or update knowledge **only** through MCP tools.',
804
+ '3. **Prefer Recipes** as project standards; source code is supplementary.',
805
+ '',
806
+ ].join('\n');
807
+ writeFileSync(claudePath, claudeContent);
808
+ } else {
809
+ }
810
+ }
811
+ }
812
+
726
813
  /** @private .cursor/rules/autosnippet-conventions.mdc */
727
814
  _copyCursorRules() {
728
815
  const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
@@ -5,7 +5,7 @@
5
5
  * ① MCP 配置(.cursor/mcp.json + .vscode/mcp.json)
6
6
  * ② Cursor Skills(.cursor/skills/)
7
7
  * ③ Cursor Rules(.cursor/rules/autosnippet-conventions.mdc + autosnippet-skills.mdc)
8
- * ④ Copilot Instructions(.github/copilot-instructions.md
8
+ * ④ Agent Instructions(AGENTS.md + CLAUDE.md + .github/copilot-instructions.md — 通过 Channel F 动态生成)
9
9
  * ⑤ Constitution(AutoSnippet/constitution.yaml)
10
10
  * ⑥ .gitignore(升级规则 + 清理旧版本残留)
11
11
  * ⑦ Skills 路径迁移(.autosnippet/skills/ → AutoSnippet/skills/)
@@ -24,6 +24,7 @@ import {
24
24
  } from 'node:fs';
25
25
  import { dirname, join, resolve } from 'node:path';
26
26
  import { fileURLToPath } from 'node:url';
27
+ import { safeCopyFile } from '../service/cursor/FileProtection.js';
27
28
 
28
29
  const __filename = fileURLToPath(import.meta.url);
29
30
  const __dirname = dirname(__filename);
@@ -47,8 +48,7 @@ export class UpgradeService {
47
48
  }
48
49
  if (!skillsOnly && !mcpOnly) {
49
50
  results.push(this._upgradeCursorRules());
50
- results.push(this._upgradeMirrorIDE('.qoder'));
51
- results.push(this._upgradeMirrorIDE('.trae'));
51
+ // NOTE: .qoder/ .trae/ 不再自动镜像,用户可通过 `asd mirror` 按需同步
52
52
  results.push(this._upgradeSkillsTemplate());
53
53
  results.push(this._upgradeCopilotInstructions());
54
54
  results.push(this._upgradeConstitution());
@@ -169,6 +169,7 @@ export class UpgradeService {
169
169
 
170
170
  /**
171
171
  * 触发 Cursor Delivery Pipeline 动态生成
172
+ * 包含 Channel A-D + Channel F (AGENTS.md / CLAUDE.md / copilot-instructions)
172
173
  * 非阻塞 — 失败不影响 upgrade 流程
173
174
  */
174
175
  _triggerCursorDelivery() {
@@ -180,7 +181,9 @@ export class UpgradeService {
180
181
  pipeline
181
182
  .deliver()
182
183
  .then((_result) => {})
183
- .catch((_err) => {});
184
+ .catch((_err) => {
185
+ /* fire-and-forget: delivery failure is non-critical during upgrade */
186
+ });
184
187
  }
185
188
  })
186
189
  .catch(() => {
@@ -247,8 +250,13 @@ export class UpgradeService {
247
250
  }
248
251
  }
249
252
 
250
- /* ═══ Copilot Instructions ══════════════════════════ */
253
+ /* ═══ Copilot Instructions (fallback static copy) ══════════════════ */
251
254
 
255
+ /**
256
+ * 静态模板回退 — 当 _triggerCursorDelivery() 无法运行时
257
+ * (无 DB 环境),至少保证有一份基础模板。
258
+ * Channel F 动态生成会覆盖此文件。
259
+ */
252
260
  _upgradeCopilotInstructions() {
253
261
  const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
254
262
  if (!existsSync(src)) {
@@ -258,7 +266,9 @@ export class UpgradeService {
258
266
  const destDir = join(this.projectRoot, '.github');
259
267
  const dest = join(destDir, 'copilot-instructions.md');
260
268
  mkdirSync(destDir, { recursive: true });
261
- copyFileSync(src, dest);
269
+ const { written } = safeCopyFile(src, dest);
270
+ if (!written) {
271
+ }
262
272
  }
263
273
 
264
274
  /* ═══ Constitution ══════════════════════════════════ */
@@ -128,7 +128,7 @@ function analyzeProject(files, lang, options) {
128
128
 
129
129
  // SFC 预处理: .vue / .svelte 等文件 → 提取 <script> 块再交给 AST
130
130
  if (preprocessFile) {
131
- const ext = file.name ? ('.' + file.name.split('.').pop()) : '';
131
+ const ext = file.name ? `.${file.name.split('.').pop()}` : '';
132
132
  const result = preprocessFile(content, ext);
133
133
  if (result) {
134
134
  content = result.content;
@@ -88,7 +88,7 @@ export async function ensureGrammars(detectedLanguages, options = {}) {
88
88
  if (result.failed.length > 0) {
89
89
  logger?.warn?.(
90
90
  `[ensure-grammars] ${result.failed.length} grammar(s) missing. ` +
91
- `Expected in: ${GRAMMARS_DIR}`
91
+ `Expected in: ${GRAMMARS_DIR}`
92
92
  );
93
93
  } else {
94
94
  logger?.info?.('[ensure-grammars] All required grammar .wasm files available');
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { registerLanguage } from '../AstAnalyzer.js';
23
- import { initParser, loadLanguageWasm, isParserReady } from './parser-init.js';
23
+ import { initParser, isParserReady, loadLanguageWasm } from './parser-init.js';
24
24
 
25
25
  let _loaded = false;
26
26
 
@@ -36,17 +36,68 @@ export function _resetForReload() {
36
36
  * 语言注册表 — langId → { wasmFile, module, setGrammarFn, langId, tsxWasmFile?, setTsxGrammarFn? }
37
37
  */
38
38
  const LANG_REGISTRY = [
39
- { langId: 'objectivec', wasmFile: 'tree-sitter-objc.wasm', module: './lang-objc.js', setFn: 'setGrammar' },
40
- { langId: 'swift', wasmFile: 'tree-sitter-swift.wasm', module: './lang-swift.js', setFn: 'setGrammar' },
41
- { langId: 'typescript', wasmFile: 'tree-sitter-typescript.wasm', module: './lang-typescript.js', setFn: 'setGrammar' },
42
- { langId: 'tsx', wasmFile: 'tree-sitter-tsx.wasm', module: './lang-typescript.js', setFn: 'setTsxGrammar', pluginKey: 'tsxPlugin' },
43
- { langId: 'javascript', wasmFile: 'tree-sitter-javascript.wasm', module: './lang-javascript.js', setFn: 'setGrammar' },
44
- { langId: 'python', wasmFile: 'tree-sitter-python.wasm', module: './lang-python.js', setFn: 'setGrammar' },
45
- { langId: 'java', wasmFile: 'tree-sitter-java.wasm', module: './lang-java.js', setFn: 'setGrammar' },
46
- { langId: 'kotlin', wasmFile: 'tree-sitter-kotlin.wasm', module: './lang-kotlin.js', setFn: 'setGrammar' },
39
+ {
40
+ langId: 'objectivec',
41
+ wasmFile: 'tree-sitter-objc.wasm',
42
+ module: './lang-objc.js',
43
+ setFn: 'setGrammar',
44
+ },
45
+ {
46
+ langId: 'swift',
47
+ wasmFile: 'tree-sitter-swift.wasm',
48
+ module: './lang-swift.js',
49
+ setFn: 'setGrammar',
50
+ },
51
+ {
52
+ langId: 'typescript',
53
+ wasmFile: 'tree-sitter-typescript.wasm',
54
+ module: './lang-typescript.js',
55
+ setFn: 'setGrammar',
56
+ },
57
+ {
58
+ langId: 'tsx',
59
+ wasmFile: 'tree-sitter-tsx.wasm',
60
+ module: './lang-typescript.js',
61
+ setFn: 'setTsxGrammar',
62
+ pluginKey: 'tsxPlugin',
63
+ },
64
+ {
65
+ langId: 'javascript',
66
+ wasmFile: 'tree-sitter-javascript.wasm',
67
+ module: './lang-javascript.js',
68
+ setFn: 'setGrammar',
69
+ },
70
+ {
71
+ langId: 'python',
72
+ wasmFile: 'tree-sitter-python.wasm',
73
+ module: './lang-python.js',
74
+ setFn: 'setGrammar',
75
+ },
76
+ {
77
+ langId: 'java',
78
+ wasmFile: 'tree-sitter-java.wasm',
79
+ module: './lang-java.js',
80
+ setFn: 'setGrammar',
81
+ },
82
+ {
83
+ langId: 'kotlin',
84
+ wasmFile: 'tree-sitter-kotlin.wasm',
85
+ module: './lang-kotlin.js',
86
+ setFn: 'setGrammar',
87
+ },
47
88
  { langId: 'go', wasmFile: 'tree-sitter-go.wasm', module: './lang-go.js', setFn: 'setGrammar' },
48
- { langId: 'dart', wasmFile: 'tree-sitter-dart.wasm', module: './lang-dart.js', setFn: 'setGrammar' },
49
- { langId: 'rust', wasmFile: 'tree-sitter-rust.wasm', module: './lang-rust.js', setFn: 'setGrammar' },
89
+ {
90
+ langId: 'dart',
91
+ wasmFile: 'tree-sitter-dart.wasm',
92
+ module: './lang-dart.js',
93
+ setFn: 'setGrammar',
94
+ },
95
+ {
96
+ langId: 'rust',
97
+ wasmFile: 'tree-sitter-rust.wasm',
98
+ module: './lang-rust.js',
99
+ setFn: 'setGrammar',
100
+ },
50
101
  ];
51
102
 
52
103
  /**
@@ -146,8 +146,12 @@ function _parseClassDef(node, ctx) {
146
146
  }
147
147
 
148
148
  let kind = 'class';
149
- if (isAbstract) kind = 'abstract-class';
150
- if (isSealed) kind = 'sealed-class';
149
+ if (isAbstract) {
150
+ kind = 'abstract-class';
151
+ }
152
+ if (isSealed) {
153
+ kind = 'sealed-class';
154
+ }
151
155
 
152
156
  ctx.classes.push({
153
157
  name,
@@ -227,7 +231,9 @@ function _parseMixinDecl(node, ctx) {
227
231
  );
228
232
  const name = nameNode?.text || 'Unknown';
229
233
 
230
- const onClause = node.namedChildren.find((c) => c.type === 'on_clause' || c.type === 'superclass');
234
+ const onClause = node.namedChildren.find(
235
+ (c) => c.type === 'on_clause' || c.type === 'superclass'
236
+ );
231
237
  const constraints = [];
232
238
  if (onClause) {
233
239
  for (let i = 0; i < onClause.namedChildCount; i++) {
@@ -262,9 +268,7 @@ function _parseExtensionDecl(node, ctx) {
262
268
  const name = nameNode?.text || 'anonymous_extension';
263
269
 
264
270
  // on Type
265
- const onType = node.namedChildren.find(
266
- (c) => c.type === 'type_identifier' && c !== nameNode
267
- );
271
+ const onType = node.namedChildren.find((c) => c.type === 'type_identifier' && c !== nameNode);
268
272
 
269
273
  ctx.categories.push({
270
274
  name,
@@ -273,7 +277,9 @@ function _parseExtensionDecl(node, ctx) {
273
277
  endLine: node.endPosition.row + 1,
274
278
  });
275
279
 
276
- const body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body');
280
+ const body = node.namedChildren.find(
281
+ (c) => c.type === 'class_body' || c.type === 'extension_body'
282
+ );
277
283
  if (body) {
278
284
  _walkClassBody(body, ctx, name);
279
285
  }
@@ -310,9 +316,7 @@ function _parseFunctionDef(node, className) {
310
316
  const isAsync = node.text.includes('async') || node.text.includes('async*');
311
317
  const isOverride = node.text.includes('@override');
312
318
 
313
- const body = node.namedChildren.find(
314
- (c) => c.type === 'function_body' || c.type === 'block'
315
- );
319
+ const body = node.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
316
320
  const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
317
321
  const complexity = body ? _estimateComplexity(body) : 1;
318
322
  const nestingDepth = body ? _maxNesting(body, 0) : 0;
@@ -372,13 +376,17 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
372
376
  const classPropMap = {};
373
377
  for (const m of methods) {
374
378
  if (m.className) {
375
- if (!classMethodMap[m.className]) classMethodMap[m.className] = [];
379
+ if (!classMethodMap[m.className]) {
380
+ classMethodMap[m.className] = [];
381
+ }
376
382
  classMethodMap[m.className].push(m);
377
383
  }
378
384
  }
379
385
  for (const p of properties) {
380
386
  if (p.className) {
381
- if (!classPropMap[p.className]) classPropMap[p.className] = [];
387
+ if (!classPropMap[p.className]) {
388
+ classPropMap[p.className] = [];
389
+ }
382
390
  classPropMap[p.className].push(p);
383
391
  }
384
392
  }
@@ -430,10 +438,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
430
438
  }
431
439
 
432
440
  // ChangeNotifier (Provider pattern)
433
- if (
434
- cls.superclass === 'ChangeNotifier' ||
435
- (cls.mixins && cls.mixins.includes('ChangeNotifier'))
436
- ) {
441
+ if (cls.superclass === 'ChangeNotifier' || cls.mixins?.includes('ChangeNotifier')) {
437
442
  patterns.push({
438
443
  type: 'change-notifier',
439
444
  className: cls.name,
@@ -448,9 +453,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
448
453
  const hasPrivateConstructor = classMethods.some(
449
454
  (m) => m.kind === 'constructor' && m.name.startsWith('_')
450
455
  );
451
- const hasStaticInstance = classProps.some(
452
- (p) => p.isStatic && (p.isFinal || p.isConst)
453
- );
456
+ const hasStaticInstance = classProps.some((p) => p.isStatic && (p.isFinal || p.isConst));
454
457
  const hasFactoryConstructor = classMethods.some((m) => m.kind === 'factory');
455
458
 
456
459
  if (hasPrivateConstructor && (hasStaticInstance || hasFactoryConstructor)) {
@@ -493,7 +496,7 @@ function detectDartPatterns(root, lang, methods, properties, classes) {
493
496
  }
494
497
 
495
498
  // Freezed pattern — @freezed/@Freezed annotation + with _$ClassName mixin
496
- if (cls.mixins && cls.mixins.some((m) => m.startsWith('_$'))) {
499
+ if (cls.mixins?.some((m) => m.startsWith('_$'))) {
497
500
  patterns.push({
498
501
  type: 'freezed',
499
502
  className: cls.name,
@@ -548,7 +551,10 @@ function _detectStreamUsage(root, patterns) {
548
551
  if (node.type === 'type_identifier' && node.text === 'Stream') {
549
552
  streamCount++;
550
553
  }
551
- if (node.type === 'identifier' && (node.text === 'StreamController' || node.text === 'StreamSubscription')) {
554
+ if (
555
+ node.type === 'identifier' &&
556
+ (node.text === 'StreamController' || node.text === 'StreamSubscription')
557
+ ) {
552
558
  streamCount++;
553
559
  }
554
560
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -39,9 +39,7 @@ function walkGo(root, ctx) {
39
39
  // 单行 import
40
40
  const spec = child.namedChildren.find((c) => c.type === 'import_spec');
41
41
  if (spec) {
42
- const strLit = spec.namedChildren.find(
43
- (c) => c.type === 'interpreted_string_literal'
44
- );
42
+ const strLit = spec.namedChildren.find((c) => c.type === 'interpreted_string_literal');
45
43
  if (strLit) {
46
44
  ctx.imports.push(strLit.text.replace(/"/g, ''));
47
45
  }
@@ -117,7 +115,7 @@ function _walkTypeDeclaration(node, ctx) {
117
115
 
118
116
  function _parseStruct(name, structNode, specNode, ctx) {
119
117
  const fields = [];
120
- let embeddedTypes = [];
118
+ const embeddedTypes = [];
121
119
 
122
120
  const fieldList = structNode.namedChildren.find((c) => c.type === 'field_declaration_list');
123
121
  if (fieldList) {
@@ -326,14 +324,9 @@ function detectGoPatterns(root, lang, methods, properties, classes) {
326
324
  continue;
327
325
  }
328
326
  // 检查是否有 package-level var 指向此 struct
329
- const hasPackageVar = properties.some(
330
- (p) => !p.className && !p.isConst && !p.isExported
331
- );
327
+ const hasPackageVar = properties.some((p) => !p.className && !p.isConst && !p.isExported);
332
328
  const hasNewFunc = methods.some(
333
- (m) =>
334
- !m.className &&
335
- /^(?:New|Get|Default)/.test(m.name) &&
336
- m.isExported
329
+ (m) => !m.className && /^(?:New|Get|Default)/.test(m.name) && m.isExported
337
330
  );
338
331
  if (hasPackageVar && hasNewFunc) {
339
332
  patterns.push({
@@ -346,11 +339,7 @@ function detectGoPatterns(root, lang, methods, properties, classes) {
346
339
 
347
340
  // Factory: New* / Create* package-level functions
348
341
  for (const m of methods) {
349
- if (
350
- !m.className &&
351
- m.isExported &&
352
- /^(?:New|Create|Make|Build|Open|Connect)/.test(m.name)
353
- ) {
342
+ if (!m.className && m.isExported && /^(?:New|Create|Make|Build|Open|Connect)/.test(m.name)) {
354
343
  patterns.push({
355
344
  type: 'factory',
356
345
  methodName: m.name,
@@ -446,10 +435,7 @@ function _countParams(paramList) {
446
435
  let count = 0;
447
436
  for (let i = 0; i < paramList.namedChildCount; i++) {
448
437
  const child = paramList.namedChild(i);
449
- if (
450
- child.type === 'parameter_declaration' ||
451
- child.type === 'variadic_parameter_declaration'
452
- ) {
438
+ if (child.type === 'parameter_declaration' || child.type === 'variadic_parameter_declaration') {
453
439
  // Each identifier in the same declaration is a parameter
454
440
  const ids = child.namedChildren.filter((c) => c.type === 'identifier');
455
441
  count += Math.max(ids.length, 1);