autosnippet 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +655 -260
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
package/lib/cli/SetupService.js
CHANGED
|
@@ -41,21 +41,25 @@
|
|
|
41
41
|
* 子仓库 git 权限只是"一种能力(capability)",最终裁决权在 Constitution YAML。
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
+
import { execSync } from 'node:child_process';
|
|
44
45
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
copyFileSync,
|
|
47
|
+
existsSync,
|
|
48
|
+
mkdirSync,
|
|
49
|
+
readdirSync,
|
|
50
|
+
readFileSync,
|
|
51
|
+
writeFileSync,
|
|
52
|
+
} from 'node:fs';
|
|
53
|
+
import { dirname, join, resolve } from 'node:path';
|
|
54
|
+
import { fileURLToPath } from 'node:url';
|
|
51
55
|
|
|
52
56
|
const __filename = fileURLToPath(import.meta.url);
|
|
53
|
-
const __dirname
|
|
57
|
+
const __dirname = dirname(__filename);
|
|
54
58
|
|
|
55
59
|
/** AutoSnippet 源码仓库根目录(定位 templates/ 等资源) */
|
|
56
60
|
const REPO_ROOT = resolve(__dirname, '..', '..');
|
|
57
61
|
/** V2 子项目根目录(定位 bin/mcp-server.js 等) */
|
|
58
|
-
const V2_ROOT
|
|
62
|
+
const V2_ROOT = resolve(__dirname, '..', '..');
|
|
59
63
|
|
|
60
64
|
// ─────────────────────────────────────────────────────
|
|
61
65
|
|
|
@@ -66,92 +70,89 @@ export class SetupService {
|
|
|
66
70
|
constructor(options) {
|
|
67
71
|
this.projectRoot = resolve(options.projectRoot);
|
|
68
72
|
this.projectName = this.projectRoot.split('/').pop();
|
|
69
|
-
this.force
|
|
70
|
-
this.seed
|
|
73
|
+
this.force = options.force || false;
|
|
74
|
+
this.seed = options.seed || false;
|
|
71
75
|
|
|
72
76
|
// 运行时目录(gitignored)
|
|
73
|
-
this.runtimeDir
|
|
74
|
-
this.dbPath
|
|
77
|
+
this.runtimeDir = join(this.projectRoot, '.autosnippet');
|
|
78
|
+
this.dbPath = join(this.runtimeDir, 'autosnippet.db');
|
|
75
79
|
|
|
76
80
|
// 核心数据目录(子仓库)
|
|
77
|
-
this.coreDir
|
|
78
|
-
this.recipesDir
|
|
79
|
-
this.candidatesDir
|
|
80
|
-
this.skillsDir
|
|
81
|
+
this.coreDir = join(this.projectRoot, 'AutoSnippet');
|
|
82
|
+
this.recipesDir = join(this.coreDir, 'recipes');
|
|
83
|
+
this.candidatesDir = join(this.coreDir, 'candidates');
|
|
84
|
+
this.skillsDir = join(this.coreDir, 'skills');
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/* ═══ 公共入口 ═══════════════════════════════════════ */
|
|
84
88
|
|
|
85
89
|
getSteps() {
|
|
86
90
|
return [
|
|
87
|
-
{ label: '创建运行时目录与配置',
|
|
88
|
-
{ label: '初始化核心数据子仓库',
|
|
89
|
-
{ label: '配置 IDE 集成',
|
|
90
|
-
{ label: '初始化数据库',
|
|
91
|
-
{ label: '平台相关初始化',
|
|
91
|
+
{ label: '创建运行时目录与配置', fn: () => this.stepRuntime() },
|
|
92
|
+
{ label: '初始化核心数据子仓库', fn: () => this.stepCoreRepo() },
|
|
93
|
+
{ label: '配置 IDE 集成', fn: () => this.stepIDE() },
|
|
94
|
+
{ label: '初始化数据库', fn: () => this.stepDatabase() },
|
|
95
|
+
{ label: '平台相关初始化', fn: () => this.stepPlatform() },
|
|
92
96
|
];
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
async run() {
|
|
96
100
|
const steps = this.getSteps();
|
|
97
101
|
const results = [];
|
|
102
|
+
const total = steps.length;
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(` ⚙ AutoSnippet Setup — ${this.projectName}`);
|
|
106
|
+
console.log(` ${'─'.repeat(44)}`);
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < total; i++) {
|
|
100
109
|
const { label, fn } = steps[i];
|
|
101
|
-
|
|
110
|
+
const tag = `[${i + 1}/${total}]`;
|
|
111
|
+
process.stdout.write(` ${tag} ${label}...`);
|
|
102
112
|
try {
|
|
103
113
|
const r = await fn();
|
|
114
|
+
const detail = this._formatStepDetail(r);
|
|
115
|
+
console.log(` ✅${detail}`);
|
|
104
116
|
results.push({ step: i + 1, label, ok: true, ...(r || {}) });
|
|
105
117
|
} catch (err) {
|
|
106
|
-
console.
|
|
118
|
+
console.log(` ❌`);
|
|
119
|
+
console.error(` ${err.message}`);
|
|
107
120
|
results.push({ step: i + 1, label, ok: false, error: err.message });
|
|
108
121
|
}
|
|
109
122
|
}
|
|
110
123
|
|
|
111
|
-
|
|
124
|
+
this._results = results;
|
|
112
125
|
return results;
|
|
113
126
|
}
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
console.log(' ├─ recipes/*.md 统一知识实体 ← 受 git push 保护');
|
|
125
|
-
console.log(' ├─ candidates/*.md 候选代码片段 ← 受 git push 保护');
|
|
126
|
-
console.log(' ├─ skills/ Project Skills(冷启动自动生成 + 手动创建)');
|
|
127
|
-
console.log(' └─ *.json 运行数据(统计/反馈/规则学习/排除策略)');
|
|
128
|
-
console.log(' .autosnippet/ 运行时缓存(gitignored)');
|
|
129
|
-
console.log(' ├─ config.json 项目配置');
|
|
130
|
-
console.log(' └─ autosnippet.db SQLite 索引缓存\n');
|
|
131
|
-
|
|
132
|
-
console.log('🔐 权限模型(三层架构)');
|
|
133
|
-
console.log(' ① 能力层 git push --dry-run → 子仓库物理写权限');
|
|
134
|
-
console.log(' ② 角色层 constitution.yaml → 角色权限矩阵');
|
|
135
|
-
console.log(' ③ 治理层 constitution.yaml → 优先级规则引擎');
|
|
136
|
-
console.log(' 核心数据(统一 Recipe 实体)必须通过 git 修改,DB 只是缓存\n');
|
|
137
|
-
|
|
138
|
-
console.log('📊 数据流');
|
|
139
|
-
console.log(' 写入: 编辑 AutoSnippet/ 文件 → git push → asd sync → DB 缓存更新');
|
|
140
|
-
console.log(' 读取: 查询 .autosnippet/autosnippet.db(快速索引)\n');
|
|
141
|
-
|
|
142
|
-
console.log('🎯 后续步骤');
|
|
143
|
-
console.log(' 1. 团队协作 — 为子仓库添加远程仓库:');
|
|
144
|
-
console.log(' cd AutoSnippet && git remote add origin <url>');
|
|
145
|
-
console.log(' 2. 或使用 git submodule(推荐):');
|
|
146
|
-
console.log(' rm -rf AutoSnippet');
|
|
147
|
-
console.log(' git submodule add <url> AutoSnippet');
|
|
148
|
-
console.log(' 3. 同步子仓库数据到 DB 缓存: asd sync');
|
|
149
|
-
console.log(' 4. 重启编辑器(VSCode / Cursor / Xcode)');
|
|
150
|
-
console.log(' 5. 测试 MCP: @autosnippet search <关键词>');
|
|
151
|
-
console.log(' 6. 启动面板: asd ui -d .');
|
|
152
|
-
if (process.platform === 'darwin') {
|
|
153
|
-
console.log(' 6. 在 Xcode 中输入 "ass" 尝试 Snippet');
|
|
128
|
+
/** @private 格式化步骤结果的简要信息 */
|
|
129
|
+
_formatStepDetail(r) {
|
|
130
|
+
if (!r) return '';
|
|
131
|
+
const parts = [];
|
|
132
|
+
if (r.configured) {
|
|
133
|
+
parts.push(r.configured.join(', '));
|
|
134
|
+
}
|
|
135
|
+
if (r.entries !== undefined) {
|
|
136
|
+
parts.push(`${r.entries} entries`);
|
|
154
137
|
}
|
|
138
|
+
if (r.migrated !== undefined) {
|
|
139
|
+
parts.push(`migrated ${r.migrated}`);
|
|
140
|
+
}
|
|
141
|
+
return parts.length > 0 ? ` (${parts.join('; ')})` : '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
printSummary() {
|
|
145
|
+
const results = this._results || [];
|
|
146
|
+
const ok = results.filter((r) => r.ok).length;
|
|
147
|
+
const fail = results.filter((r) => !r.ok).length;
|
|
148
|
+
|
|
149
|
+
console.log(` ${'─'.repeat(44)}`);
|
|
150
|
+
console.log(` ✨ Setup 完成: ${ok} 成功${fail > 0 ? `, ${fail} 失败` : ''}`);
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(' 后续操作:');
|
|
153
|
+
console.log(' asd coldstart 扫描项目、AI 生成知识库');
|
|
154
|
+
console.log(' asd ui 启动 Dashboard + API Server');
|
|
155
|
+
console.log(' asd watch 启动 Xcode 文件监听');
|
|
155
156
|
console.log('');
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -163,7 +164,6 @@ export class SetupService {
|
|
|
163
164
|
// config.json
|
|
164
165
|
const configPath = join(this.runtimeDir, 'config.json');
|
|
165
166
|
if (existsSync(configPath) && !this.force) {
|
|
166
|
-
console.log(' ℹ️ config.json 已存在,跳过');
|
|
167
167
|
} else {
|
|
168
168
|
const config = {
|
|
169
169
|
version: 2,
|
|
@@ -182,7 +182,6 @@ export class SetupService {
|
|
|
182
182
|
},
|
|
183
183
|
};
|
|
184
184
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
185
|
-
console.log(' ✅ .autosnippet/config.json');
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
// 确保 .autosnippet/ 在主仓库 .gitignore 中
|
|
@@ -208,9 +207,7 @@ export class SetupService {
|
|
|
208
207
|
// 初始化 git(如果还不是 git 仓库)
|
|
209
208
|
if (!alreadyRepo) {
|
|
210
209
|
this._git(['init'], this.coreDir);
|
|
211
|
-
console.log(' ✅ git init AutoSnippet/');
|
|
212
210
|
} else {
|
|
213
|
-
console.log(' ℹ️ AutoSnippet/ 已是 git 仓库');
|
|
214
211
|
}
|
|
215
212
|
|
|
216
213
|
// constitution.yaml — 权限宪法
|
|
@@ -240,7 +237,6 @@ export class SetupService {
|
|
|
240
237
|
if (!alreadyRepo) {
|
|
241
238
|
this._git(['add', '.'], this.coreDir);
|
|
242
239
|
this._git(['commit', '-m', 'Init AutoSnippet knowledge base'], this.coreDir);
|
|
243
|
-
console.log(' ✅ 初始提交完成(本地模式,无 remote → 允许写入)');
|
|
244
240
|
}
|
|
245
241
|
|
|
246
242
|
return { coreInit: true, alreadyRepo };
|
|
@@ -250,7 +246,6 @@ export class SetupService {
|
|
|
250
246
|
_writeConstitution() {
|
|
251
247
|
const dest = join(this.coreDir, 'constitution.yaml');
|
|
252
248
|
if (existsSync(dest) && !this.force) {
|
|
253
|
-
console.log(' ℹ️ constitution.yaml 已存在');
|
|
254
249
|
return;
|
|
255
250
|
}
|
|
256
251
|
|
|
@@ -259,173 +254,192 @@ export class SetupService {
|
|
|
259
254
|
copyFileSync(tmpl, dest);
|
|
260
255
|
} else {
|
|
261
256
|
// 内联生成最小宪法(模板文件不可用时的 fallback)
|
|
262
|
-
writeFileSync(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
257
|
+
writeFileSync(
|
|
258
|
+
dest,
|
|
259
|
+
[
|
|
260
|
+
'# AutoSnippet Constitution',
|
|
261
|
+
'version: "2.0"',
|
|
262
|
+
'',
|
|
263
|
+
'capabilities:',
|
|
264
|
+
' git_write:',
|
|
265
|
+
' description: "子仓库 git push 权限"',
|
|
266
|
+
' probe: "git push --dry-run"',
|
|
267
|
+
' no_subrepo: "allow"',
|
|
268
|
+
' no_remote: "allow"',
|
|
269
|
+
' cache_ttl: 86400',
|
|
270
|
+
'',
|
|
271
|
+
'rules:',
|
|
272
|
+
' - id: destructive_confirm',
|
|
273
|
+
' check: "删除操作必须有 confirmed: true"',
|
|
274
|
+
' - id: content_required',
|
|
275
|
+
' check: "创建 candidate/recipe 必须提供 code 或 content"',
|
|
276
|
+
' - id: ai_no_direct_recipe',
|
|
277
|
+
' check: "AI actor 不能直接创建或批准 Recipe"',
|
|
278
|
+
' - id: batch_authorized',
|
|
279
|
+
' check: "批量操作必须有 authorized: true"',
|
|
280
|
+
'',
|
|
281
|
+
'roles:',
|
|
282
|
+
' - id: "developer"',
|
|
283
|
+
' name: "Developer"',
|
|
284
|
+
' permissions: ["*"]',
|
|
285
|
+
' requires_capability: ["git_write"]',
|
|
286
|
+
' - id: "external_agent"',
|
|
287
|
+
' name: "External Agent"',
|
|
288
|
+
' permissions: ["read:recipes", "read:guard_rules", "create:candidates", "submit:knowledge"]',
|
|
289
|
+
' - id: "chat_agent"',
|
|
290
|
+
' name: "ChatAgent"',
|
|
291
|
+
' permissions: ["read:recipes", "read:candidates", "create:candidates", "read:guard_rules"]',
|
|
292
|
+
'',
|
|
293
|
+
].join('\n')
|
|
294
|
+
);
|
|
297
295
|
}
|
|
298
|
-
console.log(' ✅ AutoSnippet/constitution.yaml');
|
|
299
296
|
}
|
|
300
297
|
|
|
301
298
|
/** @private 写入 boxspec.json */
|
|
302
299
|
_writeBoxspec() {
|
|
303
300
|
const dest = join(this.coreDir, 'boxspec.json');
|
|
304
301
|
if (existsSync(dest) && !this.force) {
|
|
305
|
-
console.log(' ℹ️ boxspec.json 已存在');
|
|
306
302
|
return;
|
|
307
303
|
}
|
|
308
304
|
|
|
309
|
-
writeFileSync(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
305
|
+
writeFileSync(
|
|
306
|
+
dest,
|
|
307
|
+
JSON.stringify(
|
|
308
|
+
{
|
|
309
|
+
name: this.projectName,
|
|
310
|
+
schemaVersion: 2,
|
|
311
|
+
kind: 'root',
|
|
312
|
+
root: true,
|
|
313
|
+
knowledgeBase: { dir: 'AutoSnippet' },
|
|
314
|
+
module: { rootDir: 'AutoSnippet' },
|
|
315
|
+
},
|
|
316
|
+
null,
|
|
317
|
+
2
|
|
318
|
+
)
|
|
319
|
+
);
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
/** @private 复制 _template.md 到 recipes/ */
|
|
321
323
|
_copyRecipeTemplate() {
|
|
322
324
|
const src = join(REPO_ROOT, 'templates', 'recipes-setup', '_template.md');
|
|
323
|
-
if (!existsSync(src))
|
|
325
|
+
if (!existsSync(src)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
324
328
|
|
|
325
329
|
const dest = join(this.recipesDir, '_template.md');
|
|
326
|
-
if (existsSync(dest) && !this.force)
|
|
330
|
+
if (existsSync(dest) && !this.force) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
327
333
|
copyFileSync(src, dest);
|
|
328
|
-
console.log(' ✅ AutoSnippet/recipes/_template.md(格式参考)');
|
|
329
334
|
}
|
|
330
335
|
|
|
331
336
|
/** @private 复制示例 Recipe(冷启动推荐) */
|
|
332
337
|
_copySeedRecipes() {
|
|
333
338
|
const seedDir = join(REPO_ROOT, 'templates', 'recipes-setup');
|
|
334
|
-
if (!existsSync(seedDir))
|
|
339
|
+
if (!existsSync(seedDir)) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
335
342
|
|
|
336
343
|
// 匹配 seed-*.md 文件
|
|
337
344
|
let files;
|
|
338
345
|
try {
|
|
339
|
-
files = readdirSync(seedDir).filter(f => f.startsWith('seed-') && f.endsWith('.md'));
|
|
340
|
-
} catch {
|
|
346
|
+
files = readdirSync(seedDir).filter((f) => f.startsWith('seed-') && f.endsWith('.md'));
|
|
347
|
+
} catch {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
341
350
|
|
|
342
351
|
let count = 0;
|
|
343
352
|
for (const file of files) {
|
|
344
353
|
const dest = join(this.recipesDir, file.replace('seed-', ''));
|
|
345
|
-
if (existsSync(dest) && !this.force)
|
|
354
|
+
if (existsSync(dest) && !this.force) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
346
357
|
copyFileSync(join(seedDir, file), dest);
|
|
347
358
|
count++;
|
|
348
359
|
}
|
|
349
360
|
if (count > 0) {
|
|
350
|
-
console.log(` ✅ 预置 ${count} 个示例 Recipe(冷启动数据)`);
|
|
351
361
|
}
|
|
352
362
|
}
|
|
353
363
|
|
|
354
364
|
/** @private 写入核心目录 README */
|
|
355
365
|
_writeCoreReadme() {
|
|
356
366
|
const dest = join(this.coreDir, 'README.md');
|
|
357
|
-
if (existsSync(dest) && !this.force)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
367
|
+
if (existsSync(dest) && !this.force) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
writeFileSync(
|
|
372
|
+
dest,
|
|
373
|
+
[
|
|
374
|
+
`# ${this.projectName} — AutoSnippet Knowledge Base`,
|
|
375
|
+
'',
|
|
376
|
+
'此目录是项目的 **核心知识库**,通过 Git 子仓库管理,同时承载数据存储与权限控制。',
|
|
377
|
+
'',
|
|
378
|
+
'## 目录结构',
|
|
379
|
+
'',
|
|
380
|
+
'```',
|
|
381
|
+
'AutoSnippet/',
|
|
382
|
+
'├── constitution.yaml 权限宪法(角色 + 权限 + 治理规则 + 能力探测)',
|
|
383
|
+
'├── boxspec.json 项目规格',
|
|
384
|
+
'├── recipes/ 统一知识实体(Markdown + YAML front-matter)',
|
|
385
|
+
'│ ├── _template.md 格式参考',
|
|
386
|
+
'│ ├── naming-rules.md 代码规范示例',
|
|
387
|
+
'│ ├── mvvm-arch.md 架构模式示例',
|
|
388
|
+
'│ └── ... 代码模式/调用链/数据流/约束/风格/...',
|
|
389
|
+
'├── skills/ Project Skills(冷启动自动生成 + 手动创建)',
|
|
390
|
+
'│ └── <name>/SKILL.md AI Agent 知识增强文档',
|
|
391
|
+
'└── README.md',
|
|
392
|
+
'```',
|
|
393
|
+
'',
|
|
394
|
+
'## 统一知识模型',
|
|
395
|
+
'',
|
|
396
|
+
'所有知识统一为 **Recipe** 实体,由 `knowledgeType` 区分维度:',
|
|
397
|
+
'',
|
|
398
|
+
'| knowledgeType | 说明 |',
|
|
399
|
+
'|---------------|------|',
|
|
400
|
+
'| code-standard | 代码规范 |',
|
|
401
|
+
'| code-pattern | 代码模式 |',
|
|
402
|
+
'| code-relation | 代码关联 |',
|
|
403
|
+
'| inheritance | 继承与接口 |',
|
|
404
|
+
'| call-chain | 调用链路 |',
|
|
405
|
+
'| data-flow | 数据流向 |',
|
|
406
|
+
'| module-dependency | 模块与依赖 |',
|
|
407
|
+
'| architecture | 模式与架构 |',
|
|
408
|
+
'| best-practice | 最佳实践 |',
|
|
409
|
+
'| boundary-constraint | 边界约束(含 Guard 规则) |',
|
|
410
|
+
'| code-style | 代码风格 |',
|
|
411
|
+
'| solution | 问题解决方案 |',
|
|
412
|
+
'',
|
|
413
|
+
'## 权限模型',
|
|
414
|
+
'',
|
|
415
|
+
'AutoSnippet 使用 **三层权限架构**:',
|
|
416
|
+
'',
|
|
417
|
+
'| 层级 | 机制 | 职责 |',
|
|
418
|
+
'|------|------|------|',
|
|
419
|
+
'| ① 能力层 | `git push --dry-run` | 探测子仓库物理写权限 |',
|
|
420
|
+
'| ② 角色层 | `constitution.yaml` roles | 角色权限矩阵 (action:resource) |',
|
|
421
|
+
'| ③ 治理层 | `constitution.yaml` priorities | 业务规则引擎 |',
|
|
422
|
+
'',
|
|
423
|
+
'git 权限只是"能力信号",**最终裁决权在 Constitution YAML**。',
|
|
424
|
+
'',
|
|
425
|
+
'## 团队使用',
|
|
426
|
+
'',
|
|
427
|
+
'```bash',
|
|
428
|
+
'# 方式 1: 添加远程仓库',
|
|
429
|
+
'cd AutoSnippet',
|
|
430
|
+
'git remote add origin <your-repo-url>',
|
|
431
|
+
'',
|
|
432
|
+
'# 方式 2: 使用 git submodule(推荐)',
|
|
433
|
+
'cd ..',
|
|
434
|
+
'rm -rf AutoSnippet',
|
|
435
|
+
'git submodule add <your-repo-url> AutoSnippet',
|
|
436
|
+
'```',
|
|
437
|
+
'',
|
|
438
|
+
'> 运行时缓存(DB 索引、Candidates、Snippets、审计日志)在 `.autosnippet/autosnippet.db`。',
|
|
439
|
+
'> **核心数据的唯一真实来源是此目录中的文件**,DB 仅做缓存。修改 Recipe/Guard 规则必须通过 git。',
|
|
440
|
+
'',
|
|
441
|
+
].join('\n')
|
|
442
|
+
);
|
|
429
443
|
}
|
|
430
444
|
|
|
431
445
|
/* ═══ Step 3: IDE 集成 ═══════════════════════════════ */
|
|
@@ -441,23 +455,190 @@ export class SetupService {
|
|
|
441
455
|
this._mirrorCursorToIDE('.qoder');
|
|
442
456
|
this._mirrorCursorToIDE('.trae');
|
|
443
457
|
|
|
444
|
-
|
|
458
|
+
const extResult = this._installVSCodeExtension();
|
|
459
|
+
|
|
460
|
+
const configured = [
|
|
461
|
+
'vscode-mcp',
|
|
462
|
+
'cursor-mcp',
|
|
463
|
+
'copilot-instructions',
|
|
464
|
+
'cursor-rules',
|
|
465
|
+
'skills-template',
|
|
466
|
+
'qoder-rules',
|
|
467
|
+
'trae-rules',
|
|
468
|
+
];
|
|
469
|
+
if (extResult) {
|
|
470
|
+
configured.push(...extResult);
|
|
471
|
+
}
|
|
472
|
+
return { configured };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @private 构建 + 安装 VSCode Extension (.vsix)
|
|
477
|
+
*
|
|
478
|
+
* 流程:
|
|
479
|
+
* 1. 编译 TypeScript(tsc)
|
|
480
|
+
* 2. 打包 .vsix(vsce package)
|
|
481
|
+
* 3. 探测所有可用的 VS Code 兼容 IDE CLI
|
|
482
|
+
* 4. 对每个 IDE 执行 --install-extension
|
|
483
|
+
*
|
|
484
|
+
* 支持:VS Code / Cursor / Codex 等基于 VS Code 的 IDE。
|
|
485
|
+
* 找不到任何 IDE CLI 时静默跳过,不阻断 setup 流程。
|
|
486
|
+
*
|
|
487
|
+
* @returns {string[]|null} 安装成功的 IDE 列表, 或 null
|
|
488
|
+
*/
|
|
489
|
+
_installVSCodeExtension() {
|
|
490
|
+
const extDir = join(REPO_ROOT, 'resources', 'vscode-ext');
|
|
491
|
+
const pkgJson = join(extDir, 'package.json');
|
|
492
|
+
|
|
493
|
+
if (!existsSync(pkgJson)) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ── 1. 编译 TypeScript ──
|
|
498
|
+
try {
|
|
499
|
+
execSync('npx tsc -p ./tsconfig.json', { cwd: extDir, stdio: 'pipe' });
|
|
500
|
+
} catch (e) {
|
|
501
|
+
console.error(` ⚠ VSCode Extension 编译失败: ${e.stderr?.toString().trim() || e.message}`);
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ── 2. 打包 .vsix ──
|
|
506
|
+
let vsixPath;
|
|
507
|
+
try {
|
|
508
|
+
const out = execSync('npx @vscode/vsce package --no-dependencies 2>&1', {
|
|
509
|
+
cwd: extDir,
|
|
510
|
+
stdio: 'pipe',
|
|
511
|
+
encoding: 'utf8',
|
|
512
|
+
});
|
|
513
|
+
// 从输出中提取 vsix 文件路径: "DONE Packaged: /path/to/autosnippet-0.1.0.vsix ..."
|
|
514
|
+
const m = out.match(/Packaged:\s*(.+\.vsix)/);
|
|
515
|
+
if (m) {
|
|
516
|
+
vsixPath = m[1].trim();
|
|
517
|
+
}
|
|
518
|
+
} catch (e) {
|
|
519
|
+
console.error(` ⚠ VSCode Extension 打包失败: ${e.message}`);
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// fallback: 扫描目录找 .vsix 文件
|
|
524
|
+
if (!vsixPath || !existsSync(vsixPath)) {
|
|
525
|
+
try {
|
|
526
|
+
const files = readdirSync(extDir).filter((f) => f.endsWith('.vsix'));
|
|
527
|
+
if (files.length > 0) {
|
|
528
|
+
// 取最新的
|
|
529
|
+
files.sort().reverse();
|
|
530
|
+
vsixPath = join(extDir, files[0]);
|
|
531
|
+
}
|
|
532
|
+
} catch {
|
|
533
|
+
/* ignore */
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!vsixPath || !existsSync(vsixPath)) {
|
|
538
|
+
console.error(' ⚠ 找不到 .vsix 文件,跳过 Extension 安装');
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ── 3. 探测可用的 IDE CLI ──
|
|
543
|
+
const cliCandidates = this._discoverIDEClis();
|
|
544
|
+
if (cliCandidates.length === 0) {
|
|
545
|
+
console.error(' ⚠ 未找到 VS Code / Cursor 等 IDE CLI,跳过 Extension 安装');
|
|
546
|
+
console.error(' 提示: 手动安装 → 在 IDE 中 Cmd+Shift+P → "Install from VSIX"');
|
|
547
|
+
console.error(` 文件: ${vsixPath}`);
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ── 4. 逐个安装 ──
|
|
552
|
+
const installed = [];
|
|
553
|
+
for (const { name, cli } of cliCandidates) {
|
|
554
|
+
try {
|
|
555
|
+
execSync(`"${cli}" --install-extension "${vsixPath}" --force 2>&1`, {
|
|
556
|
+
stdio: 'pipe',
|
|
557
|
+
encoding: 'utf8',
|
|
558
|
+
timeout: 30_000,
|
|
559
|
+
});
|
|
560
|
+
installed.push(`vscode-ext:${name}`);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
console.error(` ⚠ ${name} Extension 安装失败: ${e.message}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return installed.length > 0 ? installed : null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* @private 探测系统中所有 VS Code 兼容 IDE 的 CLI 路径
|
|
570
|
+
* @returns {{ name: string, cli: string }[]}
|
|
571
|
+
*/
|
|
572
|
+
_discoverIDEClis() {
|
|
573
|
+
const candidates = [];
|
|
574
|
+
|
|
575
|
+
// 1. PATH 中的命令
|
|
576
|
+
for (const cmd of ['code', 'cursor', 'codex', 'code-insiders']) {
|
|
577
|
+
try {
|
|
578
|
+
const p = execSync(`which ${cmd} 2>/dev/null`, { encoding: 'utf8' }).trim();
|
|
579
|
+
if (p) {
|
|
580
|
+
candidates.push({ name: cmd, cli: p });
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
/* not in PATH */
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// 2. macOS 应用内置 CLI(/Applications/xxx.app/Contents/Resources/app/bin/)
|
|
588
|
+
if (process.platform === 'darwin') {
|
|
589
|
+
const appPaths = [
|
|
590
|
+
{ name: 'vscode', app: '/Applications/Visual Studio Code.app', bin: 'code' },
|
|
591
|
+
{ name: 'vscode-insiders', app: '/Applications/Visual Studio Code - Insiders.app', bin: 'code-insiders' },
|
|
592
|
+
{ name: 'cursor', app: '/Applications/Cursor.app', bin: 'cursor' },
|
|
593
|
+
{ name: 'codex', app: '/Applications/Codex.app', bin: 'codex' },
|
|
594
|
+
];
|
|
595
|
+
for (const { name, app, bin } of appPaths) {
|
|
596
|
+
const cli = join(app, 'Contents', 'Resources', 'app', 'bin', bin);
|
|
597
|
+
if (existsSync(cli) && !candidates.some((c) => c.name === name)) {
|
|
598
|
+
candidates.push({ name, cli });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 3. 去重(同一个二进制不重复安装)
|
|
604
|
+
const seen = new Set();
|
|
605
|
+
return candidates.filter((c) => {
|
|
606
|
+
let realPath = c.cli;
|
|
607
|
+
try {
|
|
608
|
+
realPath = execSync(`readlink -f "${c.cli}" 2>/dev/null || echo "${c.cli}"`, {
|
|
609
|
+
encoding: 'utf8',
|
|
610
|
+
}).trim();
|
|
611
|
+
} catch {
|
|
612
|
+
/* use as-is */
|
|
613
|
+
}
|
|
614
|
+
if (seen.has(realPath)) return false;
|
|
615
|
+
seen.add(realPath);
|
|
616
|
+
return true;
|
|
617
|
+
});
|
|
445
618
|
}
|
|
446
619
|
|
|
447
620
|
/** @private VSCode settings.json → Copilot MCP */
|
|
448
621
|
_configureVSCodeMCP(mcpServerPath) {
|
|
449
|
-
const vscodeDir
|
|
622
|
+
const vscodeDir = join(this.projectRoot, '.vscode');
|
|
450
623
|
const settingsPath = join(vscodeDir, 'settings.json');
|
|
451
624
|
mkdirSync(vscodeDir, { recursive: true });
|
|
452
625
|
|
|
453
626
|
let settings = {};
|
|
454
627
|
if (existsSync(settingsPath)) {
|
|
455
|
-
try {
|
|
628
|
+
try {
|
|
629
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
630
|
+
} catch {
|
|
631
|
+
/* ignore */
|
|
632
|
+
}
|
|
456
633
|
}
|
|
457
634
|
|
|
458
|
-
if (!settings['github.copilot.mcp'])
|
|
459
|
-
|
|
460
|
-
|
|
635
|
+
if (!settings['github.copilot.mcp']) {
|
|
636
|
+
settings['github.copilot.mcp'] = {};
|
|
637
|
+
}
|
|
638
|
+
if (!settings['github.copilot.mcp'].servers) {
|
|
639
|
+
settings['github.copilot.mcp'].servers = {};
|
|
640
|
+
}
|
|
641
|
+
settings['github.copilot.mcp'].servers.autosnippet = {
|
|
461
642
|
type: 'stdio',
|
|
462
643
|
command: 'node',
|
|
463
644
|
args: [mcpServerPath],
|
|
@@ -468,22 +649,27 @@ export class SetupService {
|
|
|
468
649
|
};
|
|
469
650
|
|
|
470
651
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
471
|
-
console.log(' ✅ .vscode/settings.json (Copilot MCP)');
|
|
472
652
|
}
|
|
473
653
|
|
|
474
654
|
/** @private .cursor/mcp.json */
|
|
475
655
|
_configureCursorMCP(mcpServerPath) {
|
|
476
|
-
const cursorDir
|
|
656
|
+
const cursorDir = join(this.projectRoot, '.cursor');
|
|
477
657
|
const configPath = join(cursorDir, 'mcp.json');
|
|
478
658
|
mkdirSync(cursorDir, { recursive: true });
|
|
479
659
|
|
|
480
660
|
let existing = {};
|
|
481
661
|
if (existsSync(configPath)) {
|
|
482
|
-
try {
|
|
662
|
+
try {
|
|
663
|
+
existing = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
664
|
+
} catch {
|
|
665
|
+
/* ignore */
|
|
666
|
+
}
|
|
483
667
|
}
|
|
484
668
|
|
|
485
|
-
if (!existing.mcpServers)
|
|
486
|
-
|
|
669
|
+
if (!existing.mcpServers) {
|
|
670
|
+
existing.mcpServers = {};
|
|
671
|
+
}
|
|
672
|
+
existing.mcpServers.autosnippet = {
|
|
487
673
|
command: 'node',
|
|
488
674
|
args: [mcpServerPath],
|
|
489
675
|
env: {
|
|
@@ -493,83 +679,83 @@ export class SetupService {
|
|
|
493
679
|
};
|
|
494
680
|
|
|
495
681
|
writeFileSync(configPath, JSON.stringify(existing, null, 2));
|
|
496
|
-
console.log(' ✅ .cursor/mcp.json');
|
|
497
682
|
}
|
|
498
683
|
|
|
499
684
|
/** @private .github/copilot-instructions.md */
|
|
500
685
|
_copyCopilotInstructions() {
|
|
501
686
|
const src = join(REPO_ROOT, 'templates', 'copilot-instructions.md');
|
|
502
|
-
if (!existsSync(src))
|
|
687
|
+
if (!existsSync(src)) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
503
690
|
|
|
504
691
|
const destDir = join(this.projectRoot, '.github');
|
|
505
|
-
const dest
|
|
692
|
+
const dest = join(destDir, 'copilot-instructions.md');
|
|
506
693
|
if (existsSync(dest) && !this.force) {
|
|
507
|
-
console.log(' ℹ️ copilot-instructions.md 已存在');
|
|
508
694
|
return;
|
|
509
695
|
}
|
|
510
696
|
|
|
511
697
|
mkdirSync(destDir, { recursive: true });
|
|
512
698
|
copyFileSync(src, dest);
|
|
513
|
-
console.log(' ✅ .github/copilot-instructions.md');
|
|
514
699
|
}
|
|
515
700
|
|
|
516
701
|
/** @private .cursor/rules/autosnippet-conventions.mdc */
|
|
517
702
|
_copyCursorRules() {
|
|
518
703
|
const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-conventions.mdc');
|
|
519
|
-
if (!existsSync(src))
|
|
704
|
+
if (!existsSync(src)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
520
707
|
|
|
521
708
|
const destDir = join(this.projectRoot, '.cursor', 'rules');
|
|
522
|
-
const dest
|
|
709
|
+
const dest = join(destDir, 'autosnippet-conventions.mdc');
|
|
523
710
|
if (existsSync(dest) && !this.force) {
|
|
524
|
-
console.log(' ℹ️ cursor rules 已存在');
|
|
525
711
|
return;
|
|
526
712
|
}
|
|
527
713
|
|
|
528
714
|
mkdirSync(destDir, { recursive: true });
|
|
529
715
|
copyFileSync(src, dest);
|
|
530
|
-
console.log(' ✅ .cursor/rules/autosnippet-conventions.mdc');
|
|
531
716
|
}
|
|
532
717
|
|
|
533
718
|
/** @private .cursor/rules/autosnippet-skills.mdc — Project Skills 索引模板 */
|
|
534
719
|
_copySkillsTemplate() {
|
|
535
720
|
const src = join(REPO_ROOT, 'templates', 'cursor-rules', 'autosnippet-skills.mdc');
|
|
536
|
-
if (!existsSync(src))
|
|
721
|
+
if (!existsSync(src)) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
537
724
|
|
|
538
725
|
const destDir = join(this.projectRoot, '.cursor', 'rules');
|
|
539
|
-
const dest
|
|
726
|
+
const dest = join(destDir, 'autosnippet-skills.mdc');
|
|
540
727
|
if (existsSync(dest) && !this.force) {
|
|
541
|
-
console.log(' ℹ️ skills template 已存在');
|
|
542
728
|
return;
|
|
543
729
|
}
|
|
544
730
|
|
|
545
731
|
mkdirSync(destDir, { recursive: true });
|
|
546
732
|
copyFileSync(src, dest);
|
|
547
|
-
console.log(' ✅ .cursor/rules/autosnippet-skills.mdc');
|
|
548
733
|
}
|
|
549
734
|
/** @private 镜像 .cursor/rules/ 中的 autosnippet-* 文件到目标 IDE 目录(Qoder / Trae 兼容)
|
|
550
735
|
* 只复制 autosnippet- 前缀的文件,不触碰用户自己创建的规则 */
|
|
551
736
|
_mirrorCursorToIDE(targetDirName) {
|
|
552
737
|
const cursorRulesDir = join(this.projectRoot, '.cursor', 'rules');
|
|
553
|
-
if (!existsSync(cursorRulesDir))
|
|
738
|
+
if (!existsSync(cursorRulesDir)) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
554
741
|
|
|
555
742
|
const targetRulesDir = join(this.projectRoot, targetDirName, 'rules');
|
|
556
743
|
mkdirSync(targetRulesDir, { recursive: true });
|
|
557
744
|
|
|
558
745
|
// 只镜像 autosnippet- 前缀的文件,保留目标目录中用户已有的其他文件
|
|
559
|
-
const files = readdirSync(cursorRulesDir).filter(
|
|
560
|
-
(f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
|
|
746
|
+
const files = readdirSync(cursorRulesDir).filter(
|
|
747
|
+
(f) => (f.endsWith('.mdc') || f.endsWith('.md')) && f.startsWith('autosnippet-')
|
|
561
748
|
);
|
|
562
749
|
for (const file of files) {
|
|
563
750
|
const destName = file.endsWith('.mdc') ? file.replace(/\.mdc$/, '.md') : file;
|
|
564
751
|
copyFileSync(join(cursorRulesDir, file), join(targetRulesDir, destName));
|
|
565
752
|
}
|
|
566
|
-
console.log(` ✅ ${targetDirName}/rules/ (镜像 ${files.length} 个 autosnippet 规则文件)`);
|
|
567
753
|
}
|
|
568
754
|
/* ═══ Step 4: 数据库初始化 ═══════════════════════════ */
|
|
569
755
|
|
|
570
756
|
async stepDatabase() {
|
|
571
757
|
const ConfigLoader = (await import('../infrastructure/config/ConfigLoader.js')).default;
|
|
572
|
-
const Bootstrap
|
|
758
|
+
const Bootstrap = (await import('../bootstrap.js')).default;
|
|
573
759
|
|
|
574
760
|
const env = process.env.NODE_ENV || 'development';
|
|
575
761
|
ConfigLoader.load(env);
|
|
@@ -577,7 +763,6 @@ export class SetupService {
|
|
|
577
763
|
|
|
578
764
|
const bootstrap = new Bootstrap({ env });
|
|
579
765
|
await bootstrap.initialize();
|
|
580
|
-
console.log(' ✅ 数据库已初始化(migrations applied)');
|
|
581
766
|
|
|
582
767
|
const db = bootstrap.components?.db?.getDb?.();
|
|
583
768
|
if (db) {
|
|
@@ -586,7 +771,7 @@ export class SetupService {
|
|
|
586
771
|
}
|
|
587
772
|
|
|
588
773
|
await bootstrap.shutdown();
|
|
589
|
-
ConfigLoader.config = null;
|
|
774
|
+
ConfigLoader.config = null; // 重置静态状态
|
|
590
775
|
return { dbPath: this.dbPath };
|
|
591
776
|
}
|
|
592
777
|
|
|
@@ -600,45 +785,32 @@ export class SetupService {
|
|
|
600
785
|
const report = syncService.sync(db, { skipViolations: true });
|
|
601
786
|
|
|
602
787
|
if (report.synced > 0) {
|
|
603
|
-
console.log(` ✅ 已同步 ${report.synced} 个知识文件到 DB 缓存(新增 ${report.created},更新 ${report.updated})`);
|
|
604
788
|
} else {
|
|
605
|
-
console.log(' ℹ️ 暂无 .md 文件,跳过同步');
|
|
606
789
|
}
|
|
607
790
|
|
|
608
791
|
if (report.orphaned.length > 0) {
|
|
609
|
-
console.log(` ℹ️ ${report.orphaned.length} 个孤儿条目已标记 deprecated`);
|
|
610
792
|
}
|
|
611
793
|
}
|
|
612
794
|
|
|
613
|
-
/* ═══ Step 5:
|
|
795
|
+
/* ═══ Step 5: Snippet 初始化 (Xcode + VSCode) ═══════ */
|
|
614
796
|
|
|
615
797
|
async stepPlatform() {
|
|
616
|
-
|
|
617
|
-
console.log(' ℹ️ 非 macOS,跳过 Xcode 初始化');
|
|
618
|
-
return { skipped: true };
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const initScript = join(REPO_ROOT, 'scripts', 'init-xcode-snippets.js');
|
|
798
|
+
const initScript = join(REPO_ROOT, 'scripts', 'init-snippets.js');
|
|
622
799
|
if (!existsSync(initScript)) {
|
|
623
|
-
console.log(' ℹ️ init-xcode-snippets 脚本不存在,跳过');
|
|
624
800
|
return { skipped: true };
|
|
625
801
|
}
|
|
626
802
|
|
|
627
803
|
try {
|
|
628
|
-
const mod
|
|
804
|
+
const mod = await import(initScript);
|
|
629
805
|
const initFn = mod.initialize || mod.default?.initialize || mod.default;
|
|
630
806
|
if (typeof initFn !== 'function') {
|
|
631
|
-
console.log(' ℹ️ init-xcode-snippets 格式不兼容,跳过');
|
|
632
807
|
return { skipped: true };
|
|
633
808
|
}
|
|
634
809
|
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
? ' ✅ Xcode Snippets 已添加'
|
|
638
|
-
: ' ℹ️ Xcode Snippets 未更新(可能已存在)');
|
|
639
|
-
return { xcode: ok };
|
|
810
|
+
const result = await initFn(this.projectRoot, 'all');
|
|
811
|
+
return result;
|
|
640
812
|
} catch (e) {
|
|
641
|
-
console.warn(` ⚠️
|
|
813
|
+
console.warn(` ⚠️ Snippet 初始化失败:${e.message}`);
|
|
642
814
|
return { error: e.message };
|
|
643
815
|
}
|
|
644
816
|
}
|
|
@@ -652,7 +824,6 @@ export class SetupService {
|
|
|
652
824
|
_ensureEnvFile() {
|
|
653
825
|
const envPath = join(this.projectRoot, '.env');
|
|
654
826
|
if (existsSync(envPath)) {
|
|
655
|
-
console.log(' ℹ️ .env 已存在,跳过写入。如需配置 AI,请手动编辑或通过 Dashboard 设置');
|
|
656
827
|
return;
|
|
657
828
|
}
|
|
658
829
|
|
|
@@ -661,17 +832,19 @@ export class SetupService {
|
|
|
661
832
|
copyFileSync(examplePath, envPath);
|
|
662
833
|
} else {
|
|
663
834
|
// fallback: .env.example 缺失时写入最小模板
|
|
664
|
-
writeFileSync(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
835
|
+
writeFileSync(
|
|
836
|
+
envPath,
|
|
837
|
+
[
|
|
838
|
+
'# AutoSnippet AI 配置(由 asd setup 自动生成)',
|
|
839
|
+
'# 完整配置说明见 .env.example',
|
|
840
|
+
'',
|
|
841
|
+
'ASD_AI_PROVIDER=google',
|
|
842
|
+
'ASD_AI_MODEL=gemini-3-flash-preview',
|
|
843
|
+
'# ASD_GOOGLE_API_KEY=',
|
|
844
|
+
'',
|
|
845
|
+
].join('\n')
|
|
846
|
+
);
|
|
673
847
|
}
|
|
674
|
-
console.log(' ✅ .env(已从 .env.example 复制,请填写 API Key 后使用)');
|
|
675
848
|
}
|
|
676
849
|
|
|
677
850
|
/** @private 确保项目 .gitignore 正确配置 AutoSnippet 相关规则 */
|
|
@@ -684,71 +857,58 @@ export class SetupService {
|
|
|
684
857
|
// 旧格式会忽略整个目录(git 不遍历内部),导致 skills/ 和 config.json 无法被 negation 恢复
|
|
685
858
|
// 新格式忽略目录内所有文件,允许 negation 模式取消特定子路径
|
|
686
859
|
if (content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
|
|
687
|
-
content = content.replace(
|
|
688
|
-
/^\.autosnippet\/$/m,
|
|
689
|
-
'.autosnippet/*',
|
|
690
|
-
);
|
|
860
|
+
content = content.replace(/^\.autosnippet\/$/m, '.autosnippet/*');
|
|
691
861
|
changed = true;
|
|
692
|
-
console.log(' ✅ .gitignore: .autosnippet/ → .autosnippet/*(升级为精细忽略)');
|
|
693
862
|
}
|
|
694
863
|
|
|
695
864
|
// ── 必须忽略:.autosnippet/*(运行时缓存、DB、向量索引、memory) ──
|
|
696
865
|
if (!content.includes('.autosnippet/') && !content.includes('.autosnippet/*')) {
|
|
697
866
|
content += `\n# AutoSnippet 运行时缓存(不入库)\n.autosnippet/*\n`;
|
|
698
867
|
changed = true;
|
|
699
|
-
console.log(' ✅ .gitignore += .autosnippet/*');
|
|
700
868
|
}
|
|
701
869
|
|
|
702
870
|
// ── 必须跟踪:.autosnippet/config.json(项目配置) ──
|
|
703
871
|
if (!content.includes('!.autosnippet/config.json')) {
|
|
704
872
|
content += `!.autosnippet/config.json\n`;
|
|
705
873
|
changed = true;
|
|
706
|
-
console.log(' ✅ .gitignore += !.autosnippet/config.json');
|
|
707
874
|
}
|
|
708
875
|
|
|
709
876
|
// ── 必须忽略:.env(包含 API Key 等敏感信息) ──
|
|
710
877
|
if (!content.includes('.env') || (!content.match(/^\.env$/m) && !content.match(/^\.env\s/m))) {
|
|
711
878
|
content += `\n# AutoSnippet 环境变量(含 API Key,不入库)\n.env\n`;
|
|
712
879
|
changed = true;
|
|
713
|
-
console.log(' ✅ .gitignore += .env');
|
|
714
880
|
}
|
|
715
881
|
|
|
716
882
|
// ── 必须忽略:logs/(winston 运行日志,可达数十 MB) ──
|
|
717
883
|
if (!content.match(/^logs\/?$/m)) {
|
|
718
884
|
content += `\n# AutoSnippet 运行日志\nlogs/\n`;
|
|
719
885
|
changed = true;
|
|
720
|
-
console.log(' ✅ .gitignore += logs/');
|
|
721
886
|
}
|
|
722
887
|
|
|
723
888
|
// ── 必须忽略:.autosnippet-drafts/(AI 草稿临时目录) ──
|
|
724
889
|
if (!content.includes('.autosnippet-drafts')) {
|
|
725
890
|
content += `\n# AutoSnippet AI 草稿(临时)\n.autosnippet-drafts/\n`;
|
|
726
891
|
changed = true;
|
|
727
|
-
console.log(' ✅ .gitignore += .autosnippet-drafts/');
|
|
728
892
|
}
|
|
729
893
|
|
|
730
894
|
// ── 必须忽略:_draft_*.md(AI Agent 在项目根目录创建的草稿文件) ──
|
|
731
895
|
if (!content.includes('_draft_*.md')) {
|
|
732
896
|
content += `\n# AutoSnippet AI 草稿文件(项目根目录临时文件)\n_draft_*.md\n`;
|
|
733
897
|
changed = true;
|
|
734
|
-
console.log(' ✅ .gitignore += _draft_*.md');
|
|
735
898
|
}
|
|
736
899
|
|
|
737
900
|
// ── 必须忽略:常见系统 / 编辑器临时文件 ──
|
|
738
901
|
if (!content.includes('.DS_Store')) {
|
|
739
902
|
content += `\n# macOS 元数据\n.DS_Store\n`;
|
|
740
903
|
changed = true;
|
|
741
|
-
console.log(' ✅ .gitignore += .DS_Store');
|
|
742
904
|
}
|
|
743
905
|
if (!content.includes('nohup.out')) {
|
|
744
906
|
content += `nohup.out\n`;
|
|
745
907
|
changed = true;
|
|
746
|
-
console.log(' ✅ .gitignore += nohup.out');
|
|
747
908
|
}
|
|
748
909
|
if (!content.match(/\*\.sw[a-p]/)) {
|
|
749
910
|
content += `*.sw[a-p]\n`;
|
|
750
911
|
changed = true;
|
|
751
|
-
console.log(' ✅ .gitignore += *.sw[a-p]');
|
|
752
912
|
}
|
|
753
913
|
|
|
754
914
|
// Skills 已迁移到 AutoSnippet/skills/(知识库目录内),自动跟随 Git
|
|
@@ -757,22 +917,22 @@ export class SetupService {
|
|
|
757
917
|
if (content.includes('!.autosnippet/skills/')) {
|
|
758
918
|
content = content.replace(/^!?\.autosnippet\/skills\/.*\n?/gm, '');
|
|
759
919
|
changed = true;
|
|
760
|
-
console.log(' ✅ .gitignore: 移除旧版 .autosnippet/skills/ 规则(已迁移到 AutoSnippet/skills/)');
|
|
761
920
|
}
|
|
762
921
|
|
|
763
922
|
// ── 必须跟踪:AutoSnippet/(知识库子仓库)──
|
|
764
923
|
// 如果用户误将 AutoSnippet/ 加入忽略,追加 !AutoSnippet/ 取消忽略
|
|
765
924
|
const lines = content.split('\n');
|
|
766
|
-
const hasIgnoreAS = lines.some(l => {
|
|
925
|
+
const hasIgnoreAS = lines.some((l) => {
|
|
767
926
|
const t = l.trim();
|
|
768
|
-
return (
|
|
927
|
+
return (
|
|
928
|
+
(t === 'AutoSnippet/' || t === 'AutoSnippet') && !t.startsWith('#') && !t.startsWith('!')
|
|
929
|
+
);
|
|
769
930
|
});
|
|
770
|
-
const hasNegation = lines.some(l => l.trim() === '!AutoSnippet/');
|
|
931
|
+
const hasNegation = lines.some((l) => l.trim() === '!AutoSnippet/');
|
|
771
932
|
|
|
772
933
|
if (hasIgnoreAS && !hasNegation) {
|
|
773
934
|
content += `\n# AutoSnippet 知识库必须入库(取消上方忽略)\n!AutoSnippet/\n`;
|
|
774
935
|
changed = true;
|
|
775
|
-
console.log(' ✅ .gitignore += !AutoSnippet/ (取消忽略)');
|
|
776
936
|
}
|
|
777
937
|
|
|
778
938
|
if (changed) {
|
|
@@ -789,7 +949,9 @@ export class SetupService {
|
|
|
789
949
|
encoding: 'utf8',
|
|
790
950
|
}).trim();
|
|
791
951
|
} catch (e) {
|
|
792
|
-
if (args[0] === 'commit' && e.status === 1)
|
|
952
|
+
if (args[0] === 'commit' && e.status === 1) {
|
|
953
|
+
return '';
|
|
954
|
+
}
|
|
793
955
|
throw e;
|
|
794
956
|
}
|
|
795
957
|
}
|