autosnippet 3.0.10 → 3.0.13

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 (56) hide show
  1. package/bin/cli.js +64 -1
  2. package/config/default.json +9 -0
  3. package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
  4. package/dashboard/dist/index.html +1 -1
  5. package/lib/cli/SetupService.js +92 -5
  6. package/lib/cli/UpgradeService.js +14 -5
  7. package/lib/core/discovery/GenericDiscoverer.js +4 -28
  8. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +246 -0
  9. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +80 -0
  10. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
  11. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +600 -0
  12. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +125 -342
  13. package/lib/external/mcp/handlers/bootstrap/refine.js +362 -0
  14. package/lib/external/mcp/handlers/bootstrap.js +6 -590
  15. package/lib/external/mcp/handlers/browse.js +119 -9
  16. package/lib/external/mcp/handlers/guard.js +25 -6
  17. package/lib/external/mcp/handlers/search.js +56 -24
  18. package/lib/http/routes/guardRules.js +9 -17
  19. package/lib/injection/ServiceContainer.js +12 -3
  20. package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
  21. package/lib/platform/ios/xcode/XcodeIntegration.js +40 -659
  22. package/lib/platform/ios/xcode/XcodeWriteUtils.js +220 -0
  23. package/lib/service/chat/ChatAgent.js +39 -418
  24. package/lib/service/chat/ChatAgentPrompts.js +149 -0
  25. package/lib/service/chat/ChatAgentTasks.js +297 -0
  26. package/lib/service/chat/tools/_shared.js +61 -0
  27. package/lib/service/chat/tools/ai-analysis.js +284 -0
  28. package/lib/service/chat/tools/ast-graph.js +681 -0
  29. package/lib/service/chat/tools/composite.js +496 -0
  30. package/lib/service/chat/tools/guard.js +265 -0
  31. package/lib/service/chat/tools/index.js +250 -0
  32. package/lib/service/chat/tools/infrastructure.js +222 -0
  33. package/lib/service/chat/tools/knowledge-graph.js +234 -0
  34. package/lib/service/chat/tools/lifecycle.js +469 -0
  35. package/lib/service/chat/tools/project-access.js +923 -0
  36. package/lib/service/chat/tools/query.js +264 -0
  37. package/lib/service/chat/tools.js +14 -3994
  38. package/lib/service/cursor/AgentInstructionsGenerator.js +395 -0
  39. package/lib/service/cursor/CursorDeliveryPipeline.js +70 -11
  40. package/lib/service/cursor/FileProtection.js +116 -0
  41. package/lib/service/cursor/KnowledgeCompressor.js +61 -11
  42. package/lib/service/cursor/SkillsSyncer.js +5 -3
  43. package/lib/service/cursor/TopicClassifier.js +19 -3
  44. package/lib/service/guard/ExclusionManager.js +26 -2
  45. package/lib/service/guard/GuardCheckEngine.js +38 -370
  46. package/lib/service/guard/GuardCodeChecks.js +362 -0
  47. package/lib/service/guard/GuardCrossFileChecks.js +307 -0
  48. package/lib/service/guard/GuardPatternUtils.js +180 -0
  49. package/lib/service/guard/GuardService.js +80 -38
  50. package/lib/service/module/ModuleService.js +1 -0
  51. package/lib/service/search/SearchEngine.js +10 -2
  52. package/lib/service/wiki/WikiGenerator.js +226 -1532
  53. package/lib/service/wiki/WikiRenderers.js +1878 -0
  54. package/lib/service/wiki/WikiUtils.js +907 -0
  55. package/lib/shared/LanguageService.js +299 -0
  56. package/package.json +1 -1
@@ -0,0 +1,395 @@
1
+ /**
2
+ * AgentInstructionsGenerator — 通用 AI Agent 指令文件生成器
3
+ *
4
+ * Channel F: 为多种 AI 编码工具生成项目指令文件
5
+ * - AGENTS.md → OpenAI Codex / 通用 Agent
6
+ * - CLAUDE.md → Claude Code
7
+ * - .github/copilot-instructions.md → GitHub Copilot(动态版,替代静态模板)
8
+ *
9
+ * 设计原则:
10
+ * 1. 内容来源统一 — 从 _loadEntries() 已加载的知识条目中提取
11
+ * 2. 格式差异化 — 同一数据按目标工具特性适配输出
12
+ * 3. 轻量索引 — 只输出摘要和规则,详细内容引导至 MCP 工具
13
+ * 4. 幂等生成 — 每次 deliver 重写全部文件,不做增量 diff
14
+ */
15
+
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { checkWriteSafety, safeWriteFile } from './FileProtection.js';
19
+ import { estimateTokens } from './TokenBudget.js';
20
+
21
+ /**
22
+ * Agent 指令文件 token 预算
23
+ * AGENTS.md / CLAUDE.md 不受 Cursor 200K 限制,但需控制体积以便 Agent 快速消化
24
+ */
25
+ const AGENT_BUDGET = Object.freeze({
26
+ MAX_RULES: 15,
27
+ MAX_PATTERNS: 10,
28
+ MAX_SKILLS: 10,
29
+ MAX_TOTAL_TOKENS: 3000,
30
+ });
31
+
32
+ /**
33
+ * MCP 工具清单 — 精简版(跟随实际 MCP handler 注册名称)
34
+ */
35
+ const MCP_TOOLS_SUMMARY = [
36
+ { name: 'autosnippet_search', desc: 'Search knowledge base (mode: auto/context/keyword/semantic)' },
37
+ { name: 'autosnippet_knowledge', desc: 'Knowledge CRUD (operation: list/get/insights/confirm_usage)' },
38
+ { name: 'autosnippet_submit_knowledge', desc: 'Submit a knowledge candidate (strict validation)' },
39
+ { name: 'autosnippet_submit_knowledge_batch', desc: 'Batch submit candidates (with dedup + throttle)' },
40
+ { name: 'autosnippet_guard', desc: 'Code compliance check (single file or batch audit)' },
41
+ { name: 'autosnippet_structure', desc: 'Project structure discovery (targets/files/metadata)' },
42
+ { name: 'autosnippet_graph', desc: 'Knowledge graph query (query/impact/path/stats)' },
43
+ { name: 'autosnippet_skill', desc: 'Skill management (list/load/create/update/delete/suggest)' },
44
+ { name: 'autosnippet_save_document', desc: 'Save development document (auto-publish)' },
45
+ { name: 'autosnippet_bootstrap', desc: 'Project cold-start & scan (knowledge/refine/scan)' },
46
+ { name: 'autosnippet_health', desc: 'Service health & KB statistics' },
47
+ { name: 'autosnippet_capabilities', desc: 'List all available MCP tools (self-discovery)' },
48
+ ];
49
+
50
+ export class AgentInstructionsGenerator {
51
+ /**
52
+ * @param {string} projectRoot
53
+ * @param {string} projectName
54
+ * @param {Object} [logger]
55
+ */
56
+ constructor(projectRoot, projectName = 'Project', logger = console) {
57
+ this.projectRoot = projectRoot;
58
+ this.projectName = projectName;
59
+ this.logger = logger;
60
+ }
61
+
62
+ /**
63
+ * 生成所有 Agent 指令文件
64
+ *
65
+ * @param {Object} params
66
+ * @param {Array<Object>} params.rules - kind='rule' 的条目(已排序)
67
+ * @param {Array<Object>} params.patterns - kind='pattern' 的条目(已排序)
68
+ * @param {string[]} params.skills - 可用 Skill 名称列表
69
+ * @returns {{ agents: Object, claude: Object, copilot: Object }}
70
+ */
71
+ generate({ rules = [], patterns = [], skills = [] }) {
72
+ const startTime = Date.now();
73
+
74
+ // 构建共享内容块
75
+ const sections = this._buildSections({ rules, patterns, skills });
76
+
77
+ // 生成 3 个目标文件
78
+ const agents = this._writeAgentsMd(sections);
79
+ const claude = this._writeClaudeMd(sections);
80
+ const copilot = this._writeCopilotInstructions(sections);
81
+
82
+ const duration = Date.now() - startTime;
83
+ const filesWritten = [agents, claude, copilot].filter((r) => !r.skipped).length;
84
+ const skippedFiles = [agents, claude, copilot].filter((r) => r.skipped);
85
+ if (skippedFiles.length > 0) {
86
+ this.logger.info?.(
87
+ `[AgentInstructions] Skipped ${skippedFiles.length} file(s) — ` +
88
+ `user-owned files will not be overwritten: ${skippedFiles.map((f) => f.filePath).join(', ')}`
89
+ );
90
+ }
91
+ this.logger.info?.(
92
+ `[AgentInstructions] Generated ${filesWritten} files in ${duration}ms — ` +
93
+ `AGENTS.md: ${agents.tokensUsed}t, CLAUDE.md: ${claude.tokensUsed}t, ` +
94
+ `copilot-instructions: ${copilot.tokensUsed}t`
95
+ );
96
+
97
+ return {
98
+ agents,
99
+ claude,
100
+ copilot,
101
+ stats: {
102
+ filesWritten,
103
+ filesSkipped: skippedFiles.length,
104
+ totalTokens: agents.tokensUsed + claude.tokensUsed + copilot.tokensUsed,
105
+ duration,
106
+ },
107
+ };
108
+ }
109
+
110
+ // ─── 内容构建 ──────────────────────────────────────
111
+
112
+ /**
113
+ * 从知识条目构建共享内容段
114
+ * @private
115
+ */
116
+ _buildSections({ rules, patterns, skills }) {
117
+ // 编码规则(Channel A 格式,一行一条)
118
+ const ruleLines = rules
119
+ .slice(0, AGENT_BUDGET.MAX_RULES)
120
+ .filter((e) => e.doClause)
121
+ .map((e) => {
122
+ const langPrefix = e.language && e.scope !== 'universal' ? `[${e.language}] ` : '';
123
+ const doText = e.doClause.replace(/\.+$/, '');
124
+ let line = `${langPrefix}${doText}`;
125
+ if (e.dontClause) {
126
+ // 有明确否定词的统一为 "Do NOT",否则保留原文(如 "Avoid ...")
127
+ const hasNegPrefix = /^(Don't|Do not|Never)\s+/i.test(e.dontClause);
128
+ if (hasNegPrefix) {
129
+ const stripped = e.dontClause.replace(/^(Don't|Do not|Never)\s+/i, '').replace(/\.+$/, '');
130
+ line += `. Do NOT ${stripped}`;
131
+ } else {
132
+ line += `. ${e.dontClause.replace(/\.+$/, '')}`;
133
+ }
134
+ }
135
+ return `- ${line}.`;
136
+ });
137
+
138
+ // 架构模式(摘要表格行)
139
+ const patternRows = patterns
140
+ .slice(0, AGENT_BUDGET.MAX_PATTERNS)
141
+ .filter((e) => e.trigger && e.doClause)
142
+ .map((e) => {
143
+ const trigger = e.trigger.startsWith('@') ? e.trigger : `@${e.trigger}`;
144
+ const when = (e.whenClause || '').substring(0, 60);
145
+ const doText = (e.doClause || '').substring(0, 80);
146
+ return `| ${trigger} | ${when} | ${doText} |`;
147
+ });
148
+
149
+ // Skills 列表
150
+ const skillLines = skills
151
+ .slice(0, AGENT_BUDGET.MAX_SKILLS)
152
+ .map((s) => `- \`${s}\``);
153
+
154
+ // MCP 工具列表
155
+ const toolLines = MCP_TOOLS_SUMMARY.map(
156
+ (t) => `- \`${t.name}\` — ${t.desc}`
157
+ );
158
+
159
+ return { ruleLines, patternRows, skillLines, toolLines };
160
+ }
161
+
162
+ // ─── AGENTS.md ─────────────────────────────────────
163
+
164
+ /**
165
+ * @private
166
+ */
167
+ _writeAgentsMd(sections) {
168
+ const lines = [
169
+ `# ${this.projectName} — Agent Instructions`,
170
+ '',
171
+ '> Auto-generated by [AutoSnippet](https://github.com/anthropic/autosnippet). Do not edit manually.',
172
+ '> This file is regenerated when the knowledge base changes.',
173
+ '',
174
+ '## Project Knowledge Base',
175
+ '',
176
+ `This project uses **AutoSnippet** as its knowledge management system.`,
177
+ `The knowledge base contains coding standards, architecture patterns, and best practices`,
178
+ `accessible through MCP tools.`,
179
+ '',
180
+ ...this._renderConstraints(),
181
+ '',
182
+ ];
183
+
184
+ // Coding Standards
185
+ if (sections.ruleLines.length > 0) {
186
+ lines.push('## Coding Standards', '');
187
+ lines.push(...sections.ruleLines);
188
+ lines.push('');
189
+ }
190
+
191
+ // Architecture Patterns
192
+ if (sections.patternRows.length > 0) {
193
+ lines.push('## Architecture Patterns', '');
194
+ lines.push('| Trigger | When | Do |');
195
+ lines.push('|---------|------|----|');
196
+ lines.push(...sections.patternRows);
197
+ lines.push('');
198
+ }
199
+
200
+ // MCP Tools
201
+ lines.push('## MCP Tools (AutoSnippet)', '');
202
+ lines.push('Use these MCP tools to access the full knowledge base:', '');
203
+ lines.push(...sections.toolLines);
204
+ lines.push('');
205
+
206
+ // Skills
207
+ if (sections.skillLines.length > 0) {
208
+ lines.push('## Available Skills', '');
209
+ lines.push('Load with `autosnippet_skill({ operation: "load", name: "<skill>" })`:', '');
210
+ lines.push(...sections.skillLines);
211
+ lines.push('');
212
+ }
213
+
214
+ // Workflow
215
+ lines.push(...this._renderWorkflow());
216
+
217
+ const content = `${lines.join('\n')}\n`;
218
+ const filePath = path.join(this.projectRoot, 'AGENTS.md');
219
+ const result = safeWriteFile(filePath, content, { logger: this.logger });
220
+
221
+ return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
222
+ }
223
+
224
+ // ─── CLAUDE.md ─────────────────────────────────────
225
+
226
+ /**
227
+ * @private
228
+ */
229
+ _writeClaudeMd(sections) {
230
+ const lines = [
231
+ `# ${this.projectName} — Claude Code Instructions`,
232
+ '',
233
+ '> Auto-generated by AutoSnippet. Regenerated when knowledge base changes.',
234
+ '',
235
+ ];
236
+
237
+ // Constraints (Claude prefers clear bullet points)
238
+ lines.push(...this._renderConstraints());
239
+ lines.push('');
240
+
241
+ // Coding Standards
242
+ if (sections.ruleLines.length > 0) {
243
+ lines.push('## Coding Standards', '');
244
+ lines.push('These are mandatory project rules extracted from the knowledge base:', '');
245
+ lines.push(...sections.ruleLines);
246
+ lines.push('');
247
+ }
248
+
249
+ // Patterns (Claude benefits from When/Do format)
250
+ if (sections.patternRows.length > 0) {
251
+ lines.push('## Key Patterns', '');
252
+ lines.push('| Trigger | When | Do |');
253
+ lines.push('|---------|------|----|');
254
+ lines.push(...sections.patternRows);
255
+ lines.push('');
256
+ }
257
+
258
+ // MCP — Claude Code natively supports MCP
259
+ lines.push('## MCP Integration', '');
260
+ lines.push(
261
+ 'This project has an AutoSnippet MCP server configured. ',
262
+ 'Use the following tools to access project knowledge:',
263
+ ''
264
+ );
265
+ lines.push(...sections.toolLines);
266
+ lines.push('');
267
+
268
+ // Key tools highlight for Claude
269
+ lines.push('### Recommended Workflow', '');
270
+ lines.push('1. **Before writing code**: `autosnippet_search({ query: "<topic>" })` to find relevant patterns');
271
+ lines.push('2. **Check compliance**: `autosnippet_guard({ code: "<your code>" })` before committing');
272
+ lines.push('3. **Record knowledge**: `autosnippet_submit_knowledge({ ... })` when discovering reusable patterns');
273
+ lines.push('4. **Confirm adoption**: `autosnippet_knowledge({ operation: "confirm_usage", id: "<id>" })`');
274
+ lines.push('');
275
+
276
+ // Skills
277
+ if (sections.skillLines.length > 0) {
278
+ lines.push('## Skills', '');
279
+ lines.push(...sections.skillLines);
280
+ lines.push('');
281
+ }
282
+
283
+ const content = `${lines.join('\n')}\n`;
284
+ const filePath = path.join(this.projectRoot, 'CLAUDE.md');
285
+ const result = safeWriteFile(filePath, content, { logger: this.logger });
286
+
287
+ return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
288
+ }
289
+
290
+ // ─── copilot-instructions.md ───────────────────────
291
+
292
+ /**
293
+ * 动态生成 copilot-instructions.md
294
+ * 替代原有的静态模板复制
295
+ * @private
296
+ */
297
+ _writeCopilotInstructions(sections) {
298
+ const lines = [
299
+ '# AutoSnippet Copilot Instructions',
300
+ '',
301
+ '## Project Overview',
302
+ `- Project: **${this.projectName}**`,
303
+ '- Knowledge System: AutoSnippet V3 (ESM, SQLite, MCP)',
304
+ '- Knowledge Base: `AutoSnippet/` directory (recipes, skills, constitution)',
305
+ '',
306
+ ...this._renderConstraints(),
307
+ '',
308
+ ];
309
+
310
+ // Coding Standards
311
+ if (sections.ruleLines.length > 0) {
312
+ lines.push('## Coding Standards', '');
313
+ lines.push(...sections.ruleLines);
314
+ lines.push('');
315
+ }
316
+
317
+ // MCP Tools (compact for Copilot)
318
+ lines.push('## MCP Tools', '');
319
+ lines.push('Access the knowledge base through MCP:', '');
320
+ // Copilot: show fewer, most essential tools
321
+ const essentialTools = [
322
+ '- `autosnippet_search` — Search knowledge (mode: auto/context/keyword/semantic)',
323
+ '- `autosnippet_knowledge` — Browse/get recipes (operation: list/get/insights)',
324
+ '- `autosnippet_submit_knowledge` — Submit candidate (strict validation, all fields required)',
325
+ '- `autosnippet_guard` — Code compliance check',
326
+ '- `autosnippet_skill` — Load project skills (list/load)',
327
+ '- `autosnippet_health` — Service health & KB stats',
328
+ ];
329
+ lines.push(...essentialTools);
330
+ lines.push('');
331
+
332
+ // Knowledge Types
333
+ lines.push('## Knowledge Types', '');
334
+ lines.push('- **rule** — Coding standards, enforced by Guard');
335
+ lines.push('- **pattern** — Reusable code patterns and architecture');
336
+ lines.push('- **fact** — Structural knowledge (relations, data flow)');
337
+ lines.push('');
338
+
339
+ // Workflow
340
+ lines.push('## Workflow', '');
341
+ lines.push('1. Search before coding: `autosnippet_search({ query: "..." })`');
342
+ lines.push('2. Prefer Recipe over raw source code');
343
+ lines.push('3. Submit discoveries: `autosnippet_submit_knowledge({ ... })`');
344
+ lines.push('4. Do NOT directly modify `AutoSnippet/` or `.autosnippet/` files');
345
+ lines.push('');
346
+
347
+ const content = `${lines.join('\n')}\n`;
348
+ const destDir = path.join(this.projectRoot, '.github');
349
+ const filePath = path.join(destDir, 'copilot-instructions.md');
350
+ const { canWrite } = checkWriteSafety(filePath);
351
+ if (canWrite) {
352
+ fs.mkdirSync(destDir, { recursive: true });
353
+ }
354
+ const result = safeWriteFile(filePath, content, { logger: this.logger });
355
+
356
+ return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
357
+ }
358
+
359
+ // ─── 共享模板片段 ──────────────────────────────────
360
+
361
+ /**
362
+ * 核心约束(所有 Agent 共享)
363
+ * @private
364
+ */
365
+ _renderConstraints() {
366
+ return [
367
+ '## Mandatory Constraints',
368
+ '',
369
+ '1. **Do NOT modify** knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).',
370
+ '2. Create or update knowledge **only** through MCP tools (`autosnippet_submit_knowledge`).',
371
+ '3. **Prefer Recipes** as project standards; source code is supplementary.',
372
+ '4. Use `autosnippet_search` for knowledge retrieval; do not retry on failure in the same turn.',
373
+ '5. Skills handle semantics and workflow; MCP handles capabilities — do not hardcode URLs in Skills.',
374
+ ];
375
+ }
376
+
377
+ /**
378
+ * 推荐工作流(AGENTS.md 用)
379
+ * @private
380
+ */
381
+ _renderWorkflow() {
382
+ return [
383
+ '## Recommended Workflow',
384
+ '',
385
+ '1. **Search first**: `autosnippet_search({ query: "<topic>" })` before writing code',
386
+ '2. **Check patterns**: Look for existing `@trigger` patterns before implementing',
387
+ '3. **Guard check**: `autosnippet_guard({ code: "<code>" })` to validate compliance',
388
+ '4. **Submit discoveries**: `autosnippet_submit_knowledge({ ... })` for reusable patterns',
389
+ '5. **Confirm adoption**: `autosnippet_knowledge({ operation: "confirm_usage" })`',
390
+ '',
391
+ ];
392
+ }
393
+ }
394
+
395
+ export default AgentInstructionsGenerator;
@@ -1,7 +1,14 @@
1
1
  /**
2
- * CursorDeliveryPipeline — 4 通道交付主入口
2
+ * CursorDeliveryPipeline — 6 通道交付主入口
3
3
  *
4
- * 读取知识库 → 筛选 + 分类 + 排序 + 压缩 → 写入 4 个 Cursor 通道
4
+ * 读取知识库 → 筛选 + 分类 + 排序 + 压缩 → 写入 6 个通道
5
+ *
6
+ * Channel A: .cursor/rules/autosnippet-project-rules.mdc (alwaysApply rules)
7
+ * Channel B: .cursor/rules/autosnippet-patterns-{topic}.mdc (smart rules)
8
+ * Channel C: .cursor/skills/ (project skills sync)
9
+ * Channel D: .cursor/skills/autosnippet-devdocs/ (dev documents)
10
+ * Channel F: AGENTS.md + CLAUDE.md + .github/copilot-instructions.md (agent instructions)
11
+ * + Mirror: .qoder/ .trae/ (IDE mirror)
5
12
  *
6
13
  * 触发时机:
7
14
  * 1. bootstrap 完成后自动触发
@@ -13,6 +20,7 @@
13
20
  import fs from 'node:fs';
14
21
  import path from 'node:path';
15
22
  import { DELIVERY_RANK, KNOWLEDGE_CONFIDENCE } from '../../shared/constants.js';
23
+ import { AgentInstructionsGenerator } from './AgentInstructionsGenerator.js';
16
24
  import { KnowledgeCompressor } from './KnowledgeCompressor.js';
17
25
  import { RulesGenerator } from './RulesGenerator.js';
18
26
  import { SkillsSyncer } from './SkillsSyncer.js';
@@ -38,11 +46,12 @@ export class CursorDeliveryPipeline {
38
46
  this.topicClassifier = new TopicClassifier(this.projectName);
39
47
  this.rulesGenerator = new RulesGenerator(projectRoot, this.projectName);
40
48
  this.skillsSyncer = new SkillsSyncer(projectRoot, this.projectName, knowledgeService);
49
+ this.agentInstructions = new AgentInstructionsGenerator(projectRoot, this.projectName, logger);
41
50
  }
42
51
 
43
52
  /**
44
- * 完整交付流程 — 生成 4 通道 Cursor 消费物料
45
- * @returns {Promise<{ channelA: Object, channelB: Object, channelC: Object, stats: Object }>}
53
+ * 完整交付流程 — 生成 6 通道消费物料
54
+ * @returns {Promise<{ channelA: Object, channelB: Object, channelC: Object, channelD: Object, channelF: Object, stats: Object }>}
46
55
  */
47
56
  async deliver() {
48
57
  const startTime = Date.now();
@@ -51,6 +60,7 @@ export class CursorDeliveryPipeline {
51
60
  channelB: { topicCount: 0, patternsCount: 0, totalTokens: 0 },
52
61
  channelC: { synced: 0, skipped: 0, errors: 0 },
53
62
  channelD: { documentsCount: 0, filesWritten: 0 },
63
+ channelF: { filesWritten: 0, totalTokens: 0 },
54
64
  totalTokensUsed: 0,
55
65
  duration: 0,
56
66
  };
@@ -85,12 +95,13 @@ export class CursorDeliveryPipeline {
85
95
  const channelD = this._generateChannelD(documents);
86
96
  stats.channelD = channelD;
87
97
 
88
- // 统计
89
- // ── Mirror to .qoder/ & .trae/ ──
90
- this._mirrorToIDE('.qoder');
91
- this._mirrorToIDE('.trae');
98
+ // ── Channel F: Agent Instructions (AGENTS.md / CLAUDE.md / copilot-instructions) ──
99
+ const channelF = this._generateChannelF(rules, patterns);
100
+ stats.channelF = channelF;
101
+
102
+ // NOTE: .qoder/ .trae/ 镜像不再自动执行,由 `asd mirror` 按需触发
92
103
 
93
- stats.totalTokensUsed = channelA.tokensUsed + channelB.totalTokens;
104
+ stats.totalTokensUsed = channelA.tokensUsed + channelB.totalTokens + (channelF.totalTokens || 0);
94
105
  stats.duration = Date.now() - startTime;
95
106
 
96
107
  this.logger.info?.(
@@ -98,10 +109,11 @@ export class CursorDeliveryPipeline {
98
109
  `A: ${channelA.rulesCount} rules (${channelA.tokensUsed} tokens), ` +
99
110
  `B: ${channelB.topicCount} topics (${channelB.totalTokens} tokens), ` +
100
111
  `C: ${channelC.synced} skills synced, ` +
101
- `D: ${channelD.documentsCount} documents`
112
+ `D: ${channelD.documentsCount} documents, ` +
113
+ `F: ${channelF.filesWritten} agent files`
102
114
  );
103
115
 
104
- return { channelA, channelB, channelC, channelD, stats };
116
+ return { channelA, channelB, channelC, channelD, channelF, stats };
105
117
  } catch (error) {
106
118
  this.logger.error?.(`[CursorDelivery] Error: ${error.message}`);
107
119
  throw error;
@@ -403,6 +415,53 @@ export class CursorDeliveryPipeline {
403
415
  return result;
404
416
  }
405
417
 
418
+ /**
419
+ * Channel F — Agent Instructions 生成
420
+ * 生成 AGENTS.md / CLAUDE.md / .github/copilot-instructions.md
421
+ * @private
422
+ */
423
+ _generateChannelF(rules, patterns) {
424
+ try {
425
+ // 收集可用 Skills 名称
426
+ const skillsDir = path.join(this.projectRoot, 'AutoSnippet', 'skills');
427
+ let skills = [];
428
+ if (fs.existsSync(skillsDir)) {
429
+ skills = fs
430
+ .readdirSync(skillsDir, { withFileTypes: true })
431
+ .filter((d) => d.isDirectory())
432
+ .map((d) => d.name);
433
+ }
434
+
435
+ // 排序后传入
436
+ const rankedRules = this._rank(rules);
437
+ const rankedPatterns = this._rank(patterns);
438
+
439
+ const result = this.agentInstructions.generate({
440
+ rules: rankedRules,
441
+ patterns: rankedPatterns,
442
+ skills,
443
+ });
444
+
445
+ this.logger.info?.(
446
+ `[CursorDelivery] Channel F: ${result.stats.filesWritten} agent instruction files ` +
447
+ `(${result.stats.totalTokens} tokens)`
448
+ );
449
+
450
+ return {
451
+ filesWritten: result.stats.filesWritten,
452
+ totalTokens: result.stats.totalTokens,
453
+ files: {
454
+ agents: result.agents.filePath,
455
+ claude: result.claude.filePath,
456
+ copilot: result.copilot.filePath,
457
+ },
458
+ };
459
+ } catch (err) {
460
+ this.logger.warn?.(`[CursorDelivery] Channel F error (non-blocking): ${err.message}`);
461
+ return { filesWritten: 0, totalTokens: 0, files: {} };
462
+ }
463
+ }
464
+
406
465
  /**
407
466
  * 文件名安全 slug 化
408
467
  * @private
@@ -0,0 +1,116 @@
1
+ /**
2
+ * FileProtection — 非 AutoSnippet 独有文件的写入保护
3
+ *
4
+ * 保护逻辑:
5
+ * - 如果目标文件不存在 → 直接写入(首次生成)
6
+ * - 如果目标文件存在且包含 AutoSnippet 签名 → 允许覆盖(我们生成的)
7
+ * - 如果目标文件存在但不包含 AutoSnippet 签名 → 拒绝覆盖(用户原有文件)
8
+ *
9
+ * 签名标记(任意一个匹配即视为 AutoSnippet 所有):
10
+ * - "Auto-generated by AutoSnippet"
11
+ * - "Auto-generated by [AutoSnippet]"
12
+ * - "auto-generated by autosnippet" (case-insensitive)
13
+ */
14
+
15
+ import fs from 'node:fs';
16
+
17
+ /**
18
+ * AutoSnippet 文件签名模式(case-insensitive)
19
+ * 检查文件前 1024 字节即可——签名总在文件头部
20
+ */
21
+ const SIGNATURE_PATTERN = /auto-generated by (?:\[)?autosnippet(?:\])?/i;
22
+
23
+ /**
24
+ * 检查文件是否可以被 AutoSnippet 安全写入
25
+ *
26
+ * @param {string} filePath - 目标文件绝对路径
27
+ * @returns {{ canWrite: boolean, reason: string }}
28
+ * - canWrite: true → 可以安全写入
29
+ * - canWrite: false → 文件存在且非 AutoSnippet 生成,不应覆盖
30
+ */
31
+ export function checkWriteSafety(filePath) {
32
+ if (!fs.existsSync(filePath)) {
33
+ return { canWrite: true, reason: 'file-not-exist' };
34
+ }
35
+
36
+ try {
37
+ // 只读前 1024 字节检测签名,避免大文件全量读取
38
+ const fd = fs.openSync(filePath, 'r');
39
+ const buf = Buffer.alloc(1024);
40
+ const bytesRead = fs.readSync(fd, buf, 0, 1024, 0);
41
+ fs.closeSync(fd);
42
+
43
+ const header = buf.toString('utf8', 0, bytesRead);
44
+ if (SIGNATURE_PATTERN.test(header)) {
45
+ return { canWrite: true, reason: 'autosnippet-owned' };
46
+ }
47
+
48
+ return { canWrite: false, reason: 'user-owned' };
49
+ } catch {
50
+ // 读取失败(权限等问题),保守处理:不覆盖
51
+ return { canWrite: false, reason: 'read-error' };
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 安全写入文件 — 带保护机制
57
+ *
58
+ * @param {string} filePath - 目标文件绝对路径
59
+ * @param {string} content - 文件内容
60
+ * @param {Object} [options]
61
+ * @param {boolean} [options.force=false] - 强制覆盖(忽略保护)
62
+ * @param {Object} [options.logger] - 日志器
63
+ * @returns {{ written: boolean, reason: string, filePath: string }}
64
+ */
65
+ export function safeWriteFile(filePath, content, options = {}) {
66
+ const { force = false, logger } = options;
67
+
68
+ if (force) {
69
+ fs.writeFileSync(filePath, content, 'utf8');
70
+ return { written: true, reason: 'force', filePath };
71
+ }
72
+
73
+ const { canWrite, reason } = checkWriteSafety(filePath);
74
+
75
+ if (canWrite) {
76
+ fs.writeFileSync(filePath, content, 'utf8');
77
+ return { written: true, reason, filePath };
78
+ }
79
+
80
+ // 用户原有文件 → 跳过
81
+ logger?.info?.(
82
+ `[FileProtection] Skipped "${filePath}" — ${reason} (file exists and is not AutoSnippet-generated)`
83
+ );
84
+ return { written: false, reason, filePath };
85
+ }
86
+
87
+ /**
88
+ * 安全复制文件 — 带保护机制
89
+ *
90
+ * @param {string} srcPath - 源文件路径
91
+ * @param {string} destPath - 目标文件路径
92
+ * @param {Object} [options]
93
+ * @param {boolean} [options.force=false] - 强制覆盖
94
+ * @param {Object} [options.logger] - 日志器
95
+ * @returns {{ written: boolean, reason: string, filePath: string }}
96
+ */
97
+ export function safeCopyFile(srcPath, destPath, options = {}) {
98
+ const { force = false, logger } = options;
99
+
100
+ if (force) {
101
+ fs.copyFileSync(srcPath, destPath);
102
+ return { written: true, reason: 'force', filePath: destPath };
103
+ }
104
+
105
+ const { canWrite, reason } = checkWriteSafety(destPath);
106
+
107
+ if (canWrite) {
108
+ fs.copyFileSync(srcPath, destPath);
109
+ return { written: true, reason, filePath: destPath };
110
+ }
111
+
112
+ logger?.info?.(
113
+ `[FileProtection] Skipped "${destPath}" — ${reason} (file exists and is not AutoSnippet-generated)`
114
+ );
115
+ return { written: false, reason, filePath: destPath };
116
+ }