autosnippet 3.3.5 → 3.3.7
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/dashboard/dist/assets/icons-FHns2ypa.js +1 -0
- package/dashboard/dist/assets/index-BRJv5Y3r.js +135 -0
- package/dashboard/dist/assets/index-DzoB7kxK.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/api-server.js +1 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +137 -9
- package/dist/lib/agent/AgentFactory.d.ts +0 -17
- package/dist/lib/agent/AgentFactory.js +1 -25
- package/dist/lib/agent/AgentRuntime.d.ts +2 -2
- package/dist/lib/agent/AgentRuntime.js +26 -18
- package/dist/lib/agent/capabilities.d.ts +11 -0
- package/dist/lib/agent/capabilities.js +29 -5
- package/dist/lib/agent/context/ExplorationTracker.js +10 -1
- package/dist/lib/agent/context/exploration/ExplorationStrategies.d.ts +2 -0
- package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -2
- package/dist/lib/agent/domain/ChatAgentTasks.js +4 -0
- package/dist/lib/agent/domain/insight-analyst.d.ts +47 -3
- package/dist/lib/agent/domain/insight-analyst.js +111 -11
- package/dist/lib/agent/domain/insight-evolver.d.ts +69 -0
- package/dist/lib/agent/domain/insight-evolver.js +230 -0
- package/dist/lib/agent/domain/insight-gate.d.ts +42 -0
- package/dist/lib/agent/domain/insight-gate.js +41 -0
- package/dist/lib/agent/domain/insight-producer.d.ts +27 -2
- package/dist/lib/agent/domain/insight-producer.js +60 -5
- package/dist/lib/agent/domain/scan-prompts.js +10 -7
- package/dist/lib/agent/forced-summary.js +7 -2
- package/dist/lib/agent/memory/ActiveContext.d.ts +2 -28
- package/dist/lib/agent/memory/MemoryCoordinator.d.ts +2 -2
- package/dist/lib/agent/memory/SessionStore.d.ts +6 -12
- package/dist/lib/agent/memory/SessionStore.js +9 -15
- package/dist/lib/agent/memory/memory-flush-contract.d.ts +49 -0
- package/dist/lib/agent/memory/memory-flush-contract.js +16 -0
- package/dist/lib/agent/memory/session-store-schema.d.ts +20 -0
- package/dist/lib/agent/memory/session-store-schema.js +41 -0
- package/dist/lib/agent/presets.d.ts +89 -1
- package/dist/lib/agent/presets.js +53 -5
- package/dist/lib/agent/tools/_shared.d.ts +7 -15
- package/dist/lib/agent/tools/_shared.js +20 -21
- package/dist/lib/agent/tools/composite.d.ts +25 -22
- package/dist/lib/agent/tools/composite.js +108 -109
- package/dist/lib/agent/tools/evolution-tools.d.ts +145 -0
- package/dist/lib/agent/tools/evolution-tools.js +161 -0
- package/dist/lib/agent/tools/index.d.ts +163 -92
- package/dist/lib/agent/tools/index.js +9 -1
- package/dist/lib/agent/tools/lifecycle.d.ts +7 -1
- package/dist/lib/agent/tools/lifecycle.js +59 -75
- package/dist/lib/cli/AiScanService.js +5 -5
- package/dist/lib/cli/KnowledgeSyncService.js +1 -1
- package/dist/lib/core/AstAnalyzer.d.ts +1 -0
- package/dist/lib/core/discovery/ConfigWatcher.d.ts +64 -0
- package/dist/lib/core/discovery/ConfigWatcher.js +336 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +30 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.js +1305 -0
- package/dist/lib/core/discovery/DiscovererPreference.d.ts +44 -0
- package/dist/lib/core/discovery/DiscovererPreference.js +141 -0
- package/dist/lib/core/discovery/DiscovererRegistry.d.ts +10 -1
- package/dist/lib/core/discovery/DiscovererRegistry.js +42 -2
- package/dist/lib/core/discovery/ProjectDiscoverer.d.ts +19 -0
- package/dist/lib/core/discovery/index.d.ts +2 -0
- package/dist/lib/core/discovery/index.js +4 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.d.ts +32 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.js +148 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.d.ts +43 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.js +171 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.d.ts +45 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.js +122 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.d.ts +49 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.js +282 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.d.ts +33 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.js +229 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.d.ts +37 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.js +212 -0
- package/dist/lib/{service/bootstrap/DimensionCopyRegistry.d.ts → domain/dimension/DimensionCopy.d.ts} +2 -2
- package/dist/lib/{service/bootstrap/DimensionCopyRegistry.js → domain/dimension/DimensionCopy.js} +22 -72
- package/dist/lib/domain/dimension/DimensionRegistry.d.ts +54 -0
- package/dist/lib/domain/dimension/DimensionRegistry.js +620 -0
- package/dist/lib/domain/dimension/DimensionSop.d.ts +55 -0
- package/dist/lib/domain/dimension/DimensionSop.js +1604 -0
- package/dist/lib/domain/dimension/UnifiedDimension.d.ts +61 -0
- package/dist/lib/domain/dimension/UnifiedDimension.js +53 -0
- package/dist/lib/domain/dimension/index.d.ts +10 -0
- package/dist/lib/domain/dimension/index.js +9 -0
- package/dist/lib/domain/knowledge/FieldSpec.d.ts +1 -1
- package/dist/lib/domain/knowledge/FieldSpec.js +29 -16
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +40 -112
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +44 -9
- package/dist/lib/domain/knowledge/KnowledgeRepository.d.ts +1 -0
- package/dist/lib/domain/knowledge/KnowledgeRepository.js +3 -0
- package/dist/lib/domain/knowledge/Lifecycle.js +1 -1
- package/dist/lib/domain/knowledge/StyleGuide.d.ts +1 -1
- package/dist/lib/domain/knowledge/StyleGuide.js +1 -1
- package/dist/lib/domain/knowledge/UnifiedValidator.js +15 -0
- package/dist/lib/external/ai/AiProvider.d.ts +12 -0
- package/dist/lib/external/ai/AiProvider.js +24 -0
- package/dist/lib/external/ai/AiProviderManager.d.ts +101 -0
- package/dist/lib/external/ai/AiProviderManager.js +193 -0
- package/dist/lib/external/ai/providers/ClaudeProvider.js +11 -0
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +18 -0
- package/dist/lib/external/ai/providers/MockProvider.d.ts +21 -3
- package/dist/lib/external/ai/providers/MockProvider.js +290 -14
- package/dist/lib/external/ai/providers/OpenAiProvider.js +16 -0
- package/dist/lib/external/lark/LarkTransport.d.ts +5 -1
- package/dist/lib/external/lark/LarkTransport.js +10 -2
- package/dist/lib/external/mcp/McpServer.js +4 -0
- package/dist/lib/external/mcp/handlers/TargetClassifier.d.ts +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.d.ts +8 -16
- package/dist/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +10 -10
- package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.d.ts +7 -0
- package/dist/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +20 -0
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +52 -132
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +204 -17
- package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.d.ts +11 -75
- package/dist/lib/external/mcp/handlers/bootstrap/base-dimensions.js +40 -191
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.d.ts +13 -78
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +30 -52
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.d.ts +0 -1
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.d.ts +20 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +432 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +99 -12
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +188 -169
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +7 -17
- package/dist/lib/external/mcp/handlers/bootstrap/refine.js +8 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.d.ts +46 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/async-fill-helpers.js +58 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.d.ts +25 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/audit-helpers.js +47 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +50 -12
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +30 -10
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-text.js +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.d.ts +24 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/handler-types.js +14 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.d.ts +14 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/panorama-utils.js +48 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.d.ts +21 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/session-helpers.js +45 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/skill-generator.d.ts +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.d.ts +27 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/target-file-map.js +44 -0
- package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +23 -10
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +41 -51
- package/dist/lib/external/mcp/handlers/bootstrap-internal.d.ts +2 -0
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +117 -82
- package/dist/lib/external/mcp/handlers/consolidated.d.ts +4 -4
- package/dist/lib/external/mcp/handlers/consolidated.js +108 -332
- package/dist/lib/external/mcp/handlers/dimension-complete-external.js +71 -2
- package/dist/lib/external/mcp/handlers/evolve-external.d.ts +54 -0
- package/dist/lib/external/mcp/handlers/evolve-external.js +229 -0
- package/dist/lib/external/mcp/handlers/knowledge.js +30 -5
- package/dist/lib/external/mcp/handlers/rescan-external.d.ts +76 -0
- package/dist/lib/external/mcp/handlers/rescan-external.js +335 -0
- package/dist/lib/external/mcp/handlers/rescan-internal.d.ts +120 -0
- package/dist/lib/external/mcp/handlers/rescan-internal.js +359 -0
- package/dist/lib/external/mcp/handlers/search.d.ts +6 -5
- package/dist/lib/external/mcp/handlers/search.js +6 -5
- package/dist/lib/external/mcp/handlers/types.d.ts +2 -1
- package/dist/lib/external/mcp/handlers/wiki-external.js +2 -2
- package/dist/lib/external/mcp/tools.d.ts +8 -18
- package/dist/lib/external/mcp/tools.js +58 -2
- package/dist/lib/http/routes/ai.js +111 -30
- package/dist/lib/http/routes/candidates.js +11 -4
- package/dist/lib/http/routes/commands.js +10 -1
- package/dist/lib/http/routes/health.js +11 -0
- package/dist/lib/http/routes/knowledge.js +122 -1
- package/dist/lib/http/routes/modules.js +52 -3
- package/dist/lib/http/routes/panorama.js +16 -4
- package/dist/lib/http/routes/recipes.js +7 -0
- package/dist/lib/http/utils/routeHelpers.js +2 -1
- package/dist/lib/infrastructure/cache/CacheCoordinator.d.ts +41 -0
- package/dist/lib/infrastructure/cache/CacheCoordinator.js +105 -0
- package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.d.ts +7 -0
- package/dist/lib/infrastructure/database/migrations/006_lifecycle_transition_events.js +28 -0
- package/dist/lib/infrastructure/vector/HnswVectorAdapter.js +1 -1
- package/dist/lib/injection/ServiceContainer.d.ts +6 -5
- package/dist/lib/injection/ServiceContainer.js +64 -25
- package/dist/lib/injection/ServiceMap.d.ts +10 -1
- package/dist/lib/injection/modules/AiModule.d.ts +6 -9
- package/dist/lib/injection/modules/AiModule.js +82 -39
- package/dist/lib/injection/modules/KnowledgeModule.js +15 -1
- package/dist/lib/injection/modules/PanoramaModule.js +1 -1
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +4 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +16 -1
- package/dist/lib/service/bootstrap/BootstrapEventEmitter.d.ts +3 -2
- package/dist/lib/service/bootstrap/BootstrapEventEmitter.js +1 -1
- package/dist/lib/service/bootstrap/DeliveryVerifier.d.ts +51 -0
- package/dist/lib/service/bootstrap/DeliveryVerifier.js +163 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.d.ts +5 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.js +20 -0
- package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +54 -0
- package/dist/lib/service/bootstrap/bootstrap-event-types.js +10 -0
- package/dist/lib/service/cleanup/CleanupService.d.ts +132 -0
- package/dist/lib/service/cleanup/CleanupService.js +571 -0
- package/dist/lib/service/delivery/AgentInstructionsGenerator.js +39 -43
- package/dist/lib/service/delivery/FileProtection.d.ts +20 -0
- package/dist/lib/service/delivery/FileProtection.js +54 -0
- package/dist/lib/service/delivery/SkillsSyncer.js +16 -21
- package/dist/lib/service/evolution/ContentPatcher.d.ts +44 -0
- package/dist/lib/service/evolution/ContentPatcher.js +310 -0
- package/dist/lib/service/evolution/ProposalExecutor.d.ts +4 -0
- package/dist/lib/service/evolution/ProposalExecutor.js +77 -13
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +64 -0
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +458 -0
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +89 -0
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +492 -0
- package/dist/lib/service/evolution/createSupersedeProposal.d.ts +44 -0
- package/dist/lib/service/evolution/createSupersedeProposal.js +81 -0
- package/dist/lib/service/guard/ComplianceReporter.d.ts +4 -0
- package/dist/lib/service/guard/ComplianceReporter.js +51 -0
- package/dist/lib/service/guard/GuardCheckEngine.js +5 -4
- package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +6 -0
- package/dist/lib/service/knowledge/CodeEntityGraph.js +16 -0
- package/dist/lib/service/knowledge/ConfidenceRouter.js +1 -1
- package/dist/lib/service/knowledge/KnowledgeService.d.ts +11 -1
- package/dist/lib/service/knowledge/KnowledgeService.js +67 -14
- package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +225 -0
- package/dist/lib/service/knowledge/RecipeProductionGateway.js +384 -0
- package/dist/lib/service/module/ModuleService.js +10 -19
- package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +10 -1
- package/dist/lib/service/panorama/CouplingAnalyzer.js +44 -2
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +4 -3
- package/dist/lib/service/panorama/DimensionAnalyzer.js +40 -151
- package/dist/lib/service/panorama/LayerInferrer.d.ts +16 -1
- package/dist/lib/service/panorama/LayerInferrer.js +118 -1
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +9 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.js +58 -2
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +6 -2
- package/dist/lib/service/panorama/PanoramaAggregator.js +84 -6
- package/dist/lib/service/panorama/PanoramaScanner.js +28 -0
- package/dist/lib/service/panorama/PanoramaService.js +10 -5
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +38 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +2 -0
- package/dist/lib/service/panorama/RoleRefiner.js +41 -0
- package/dist/lib/service/panorama/TechStackProfiler.d.ts +13 -0
- package/dist/lib/service/panorama/TechStackProfiler.js +191 -0
- package/dist/lib/service/search/BM25Scorer.d.ts +2 -2
- package/dist/lib/service/search/SearchEngine.d.ts +11 -10
- package/dist/lib/service/search/SearchEngine.js +38 -36
- package/dist/lib/service/search/SearchTypes.d.ts +14 -8
- package/dist/lib/service/search/SearchTypes.js +1 -1
- package/dist/lib/service/search/tokenizer.d.ts +1 -1
- package/dist/lib/service/search/tokenizer.js +2 -2
- package/dist/lib/service/skills/SignalCollector.d.ts +1 -0
- package/dist/lib/service/skills/SignalCollector.js +6 -5
- package/dist/lib/service/vector/ContextualEnricher.d.ts +1 -0
- package/dist/lib/service/vector/ContextualEnricher.js +4 -0
- package/dist/lib/shared/LanguageService.js +3 -0
- package/dist/lib/shared/developer-identity.d.ts +18 -0
- package/dist/lib/shared/developer-identity.js +62 -0
- package/dist/lib/shared/schemas/common.d.ts +4 -4
- package/dist/lib/shared/schemas/http-requests.d.ts +20 -18
- package/dist/lib/shared/schemas/http-requests.js +17 -6
- package/dist/lib/shared/schemas/mcp-tools.d.ts +32 -2
- package/dist/lib/shared/schemas/mcp-tools.js +38 -0
- package/dist/lib/types/evolution.d.ts +135 -0
- package/dist/lib/types/evolution.js +6 -0
- package/dist/lib/types/graph-shared.d.ts +25 -0
- package/dist/lib/types/graph-shared.js +7 -0
- package/dist/lib/types/knowledge-wire.d.ts +132 -0
- package/dist/lib/types/knowledge-wire.js +7 -0
- package/dist/lib/types/project-snapshot-builder.d.ts +19 -0
- package/dist/lib/types/project-snapshot-builder.js +189 -0
- package/dist/lib/types/project-snapshot.d.ts +399 -0
- package/dist/lib/types/project-snapshot.js +17 -0
- package/dist/lib/types/search-wire.d.ts +46 -0
- package/dist/lib/types/search-wire.js +7 -0
- package/dist/lib/types/snapshot-views.d.ts +58 -0
- package/dist/lib/types/snapshot-views.js +103 -0
- package/package.json +1 -1
- package/skills/autosnippet-recipes/SKILL.md +1 -1
- package/templates/instructions/agent-static.md +2 -0
- package/templates/instructions/conventions.md +3 -1
- package/templates/recipes-setup/README.md +2 -2
- package/dashboard/dist/assets/icons-BJ2mUBi8.js +0 -1
- package/dashboard/dist/assets/index-B659K9t5.js +0 -128
- package/dashboard/dist/assets/index-NCm40PMD.css +0 -1
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.d.ts +0 -169
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +0 -727
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.d.ts +0 -370
- package/dist/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +0 -821
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { TEMPLATES_DIR } from '../../shared/package-root.js';
|
|
18
|
-
import {
|
|
18
|
+
import { mergeSection } from './FileProtection.js';
|
|
19
19
|
import { estimateTokens } from './TokenBudget.js';
|
|
20
20
|
/**
|
|
21
21
|
* Agent 指令文件 token 预算
|
|
@@ -49,6 +49,14 @@ const MCP_TOOLS_SUMMARY = [
|
|
|
49
49
|
{ name: 'autosnippet_graph', desc: 'Knowledge graph query (query/impact/path/stats)' },
|
|
50
50
|
{ name: 'autosnippet_skill', desc: 'Skill management (list/load/create/update/delete)' },
|
|
51
51
|
{ name: 'autosnippet_bootstrap', desc: 'Project cold-start & scan' },
|
|
52
|
+
{
|
|
53
|
+
name: 'autosnippet_rescan',
|
|
54
|
+
desc: 'Incremental rescan: preserves Recipes, cleans caches, re-analyzes project, runs relevance audit',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'autosnippet_evolve',
|
|
58
|
+
desc: 'Batch Recipe evolution decisions (propose_evolution/confirm_deprecation/skip), used per-dimension during rescan or standalone',
|
|
59
|
+
},
|
|
52
60
|
{
|
|
53
61
|
name: 'autosnippet_panorama',
|
|
54
62
|
desc: 'Project panorama (operation: overview/module/gaps/health)',
|
|
@@ -95,8 +103,8 @@ export class AgentInstructionsGenerator {
|
|
|
95
103
|
const filesWritten = allResults.filter((r) => !r.skipped).length;
|
|
96
104
|
const skippedFiles = allResults.filter((r) => r.skipped);
|
|
97
105
|
if (skippedFiles.length > 0) {
|
|
98
|
-
this.logger.info?.(`[AgentInstructions] Skipped ${skippedFiles.length} file(s)
|
|
99
|
-
|
|
106
|
+
this.logger.info?.(`[AgentInstructions] Skipped ${skippedFiles.length} file(s): ` +
|
|
107
|
+
skippedFiles.map((f) => f.filePath).join(', '));
|
|
100
108
|
}
|
|
101
109
|
this.logger.info?.(`[AgentInstructions] Generated ${filesWritten} files in ${duration}ms — ` +
|
|
102
110
|
`AGENTS.md: ${agents.tokensUsed}t, CLAUDE.md: ${claude.tokensUsed}t, ` +
|
|
@@ -159,7 +167,8 @@ export class AgentInstructionsGenerator {
|
|
|
159
167
|
}
|
|
160
168
|
// ─── AGENTS.md ─────────────────────────────────────
|
|
161
169
|
_writeAgentsMd(sections) {
|
|
162
|
-
|
|
170
|
+
// 文件头部(仅用于新建/旧版重写场景)
|
|
171
|
+
const header = [
|
|
163
172
|
`# ${this.projectName} — Agent Instructions`,
|
|
164
173
|
'',
|
|
165
174
|
'> Auto-generated by [AutoSnippet](https://github.com/GxFn/AutoSnippet). Do not edit manually.',
|
|
@@ -167,31 +176,28 @@ export class AgentInstructionsGenerator {
|
|
|
167
176
|
'This project uses **AutoSnippet** for knowledge management.',
|
|
168
177
|
'Access the knowledge base through MCP tools.',
|
|
169
178
|
'',
|
|
170
|
-
];
|
|
171
|
-
//
|
|
179
|
+
].join('\n');
|
|
180
|
+
// 动态区段内容(始终在 markers 内管理)
|
|
181
|
+
const sectionLines = [];
|
|
172
182
|
if (sections.ruleLines.length > 0) {
|
|
173
|
-
|
|
183
|
+
sectionLines.push('## Coding Standards', '', ...sections.ruleLines, '');
|
|
174
184
|
}
|
|
175
|
-
// Architecture Patterns
|
|
176
185
|
if (sections.patternRows.length > 0) {
|
|
177
|
-
|
|
186
|
+
sectionLines.push('## Architecture Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
|
|
178
187
|
}
|
|
179
|
-
|
|
180
|
-
lines.push('## MCP Tools', '', ...sections.toolLines, '');
|
|
181
|
-
// Skills
|
|
188
|
+
sectionLines.push('## MCP Tools', '', ...sections.toolLines, '');
|
|
182
189
|
if (sections.skillLines.length > 0) {
|
|
183
|
-
|
|
190
|
+
sectionLines.push('## Skills', '', 'Load with `autosnippet_skill({ operation: "load", name: "<skill>" })`:', '', ...sections.skillLines, '');
|
|
184
191
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const content = `${lines.join('\n')}\n`;
|
|
192
|
+
sectionLines.push('## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
|
|
193
|
+
const sectionContent = sectionLines.join('\n');
|
|
188
194
|
const filePath = path.join(this.projectRoot, 'AGENTS.md');
|
|
189
|
-
const result =
|
|
190
|
-
return { filePath, tokensUsed: estimateTokens(
|
|
195
|
+
const result = mergeSection(filePath, sectionContent, { header, logger: this.logger });
|
|
196
|
+
return { filePath, tokensUsed: estimateTokens(sectionContent), skipped: !result.written };
|
|
191
197
|
}
|
|
192
198
|
// ─── CLAUDE.md ─────────────────────────────────────
|
|
193
199
|
_writeClaudeMd(sections) {
|
|
194
|
-
const
|
|
200
|
+
const header = [
|
|
195
201
|
`# ${this.projectName} — Claude Code Instructions`,
|
|
196
202
|
'',
|
|
197
203
|
'> Auto-generated by AutoSnippet. Regenerated when knowledge base changes.',
|
|
@@ -199,27 +205,23 @@ export class AgentInstructionsGenerator {
|
|
|
199
205
|
'This project uses **AutoSnippet** for knowledge management.',
|
|
200
206
|
'Access the knowledge base through MCP tools.',
|
|
201
207
|
'',
|
|
202
|
-
];
|
|
203
|
-
|
|
208
|
+
].join('\n');
|
|
209
|
+
const sectionLines = [];
|
|
204
210
|
if (sections.ruleLines.length > 0) {
|
|
205
|
-
|
|
211
|
+
sectionLines.push('## Coding Standards', '', ...sections.ruleLines, '');
|
|
206
212
|
}
|
|
207
|
-
// Patterns
|
|
208
213
|
if (sections.patternRows.length > 0) {
|
|
209
|
-
|
|
214
|
+
sectionLines.push('## Key Patterns', '', '| Trigger | When | Do |', '|---------|------|----|', ...sections.patternRows, '');
|
|
210
215
|
}
|
|
211
|
-
|
|
212
|
-
lines.push('## MCP Tools', '', ...sections.toolLines, '');
|
|
213
|
-
// Skills
|
|
216
|
+
sectionLines.push('## MCP Tools', '', ...sections.toolLines, '');
|
|
214
217
|
if (sections.skillLines.length > 0) {
|
|
215
|
-
|
|
218
|
+
sectionLines.push('## Skills', '', ...sections.skillLines, '');
|
|
216
219
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const content = `${lines.join('\n')}\n`;
|
|
220
|
+
sectionLines.push('', '## Constraints', '', '1. Do NOT modify knowledge base files directly (`AutoSnippet/recipes/`, `.autosnippet/`).', '2. Prefer Recipes as project standards; source code is supplementary.', '3. Create or update knowledge only through MCP tools.', '');
|
|
221
|
+
const sectionContent = sectionLines.join('\n');
|
|
220
222
|
const filePath = path.join(this.projectRoot, 'CLAUDE.md');
|
|
221
|
-
const result =
|
|
222
|
-
return { filePath, tokensUsed: estimateTokens(
|
|
223
|
+
const result = mergeSection(filePath, sectionContent, { header, logger: this.logger });
|
|
224
|
+
return { filePath, tokensUsed: estimateTokens(sectionContent), skipped: !result.written };
|
|
223
225
|
}
|
|
224
226
|
// ─── copilot-instructions.md ───────────────────────
|
|
225
227
|
/**
|
|
@@ -228,7 +230,7 @@ export class AgentInstructionsGenerator {
|
|
|
228
230
|
*/
|
|
229
231
|
_writeCopilotInstructions(_sections) {
|
|
230
232
|
const body = this._loadConventionsTemplate();
|
|
231
|
-
const
|
|
233
|
+
const section = [
|
|
232
234
|
'<!-- autosnippet:begin -->',
|
|
233
235
|
'',
|
|
234
236
|
'# AutoSnippet Conventions',
|
|
@@ -236,16 +238,10 @@ export class AgentInstructionsGenerator {
|
|
|
236
238
|
body,
|
|
237
239
|
'',
|
|
238
240
|
'<!-- autosnippet:end -->',
|
|
239
|
-
'',
|
|
240
241
|
].join('\n');
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
if (canWrite) {
|
|
245
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
246
|
-
}
|
|
247
|
-
const result = safeWriteFile(filePath, content, { logger: this.logger });
|
|
248
|
-
return { filePath, tokensUsed: estimateTokens(content), skipped: !result.written };
|
|
242
|
+
const filePath = path.join(this.projectRoot, '.github', 'copilot-instructions.md');
|
|
243
|
+
const result = mergeSection(filePath, section, { logger: this.logger });
|
|
244
|
+
return { filePath, tokensUsed: estimateTokens(section), skipped: !result.written };
|
|
249
245
|
}
|
|
250
246
|
// ─── 模板读取 ──────────────────────────────────────
|
|
251
247
|
/**
|
|
@@ -61,3 +61,23 @@ export declare function safeCopyFile(srcPath: string, destPath: string, options?
|
|
|
61
61
|
reason: string;
|
|
62
62
|
filePath: string;
|
|
63
63
|
};
|
|
64
|
+
export type MergeStrategy = 'create' | 'replace-section' | 'rewrite-legacy' | 'append-section';
|
|
65
|
+
/**
|
|
66
|
+
* 智能合并 AutoSnippet 管理区段到目标文件
|
|
67
|
+
*
|
|
68
|
+
* 四种场景:
|
|
69
|
+
* 1. 文件不存在 → 创建完整文件(header + markers)
|
|
70
|
+
* 2. 文件有 begin/end 标记 → 仅替换标记区段(增量更新)
|
|
71
|
+
* 3. 文件有旧版 AutoSnippet 签名但无标记 → 全量重写并加标记(旧版迁移)
|
|
72
|
+
* 4. 文件无签名无标记(用户文件)→ 追加标记区段到末尾(共存)
|
|
73
|
+
*/
|
|
74
|
+
export declare function mergeSection(filePath: string, section: string, options?: {
|
|
75
|
+
header?: string;
|
|
76
|
+
logger?: {
|
|
77
|
+
info?: (...args: unknown[]) => void;
|
|
78
|
+
};
|
|
79
|
+
}): {
|
|
80
|
+
written: boolean;
|
|
81
|
+
strategy: MergeStrategy;
|
|
82
|
+
filePath: string;
|
|
83
|
+
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - "auto-generated by autosnippet" (case-insensitive)
|
|
13
13
|
*/
|
|
14
14
|
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
15
16
|
/**
|
|
16
17
|
* AutoSnippet 文件签名模式(case-insensitive)
|
|
17
18
|
* 检查文件前 1024 字节即可——签名总在文件头部
|
|
@@ -96,3 +97,56 @@ export function safeCopyFile(srcPath, destPath, options = {}) {
|
|
|
96
97
|
logger?.info?.(`[FileProtection] Skipped "${destPath}" — ${reason} (file exists and is not AutoSnippet-generated)`);
|
|
97
98
|
return { written: false, reason, filePath: destPath };
|
|
98
99
|
}
|
|
100
|
+
// ─── Section Merge ──────────────────────────────────
|
|
101
|
+
const SECTION_BEGIN = '<!-- autosnippet:begin -->';
|
|
102
|
+
const SECTION_END = '<!-- autosnippet:end -->';
|
|
103
|
+
const SECTION_PATTERN = /<!-- autosnippet:begin -->[\s\S]*?<!-- autosnippet:end -->/;
|
|
104
|
+
/**
|
|
105
|
+
* 智能合并 AutoSnippet 管理区段到目标文件
|
|
106
|
+
*
|
|
107
|
+
* 四种场景:
|
|
108
|
+
* 1. 文件不存在 → 创建完整文件(header + markers)
|
|
109
|
+
* 2. 文件有 begin/end 标记 → 仅替换标记区段(增量更新)
|
|
110
|
+
* 3. 文件有旧版 AutoSnippet 签名但无标记 → 全量重写并加标记(旧版迁移)
|
|
111
|
+
* 4. 文件无签名无标记(用户文件)→ 追加标记区段到末尾(共存)
|
|
112
|
+
*/
|
|
113
|
+
export function mergeSection(filePath, section, options = {}) {
|
|
114
|
+
const { header = '', logger } = options;
|
|
115
|
+
const fileName = path.basename(filePath);
|
|
116
|
+
// 确保 section 包含 begin/end 标记
|
|
117
|
+
const wrappedSection = section.includes(SECTION_BEGIN)
|
|
118
|
+
? section
|
|
119
|
+
: `${SECTION_BEGIN}\n\n${section}\n${SECTION_END}`;
|
|
120
|
+
// Case 1: 文件不存在 → 创建完整文件
|
|
121
|
+
if (!fs.existsSync(filePath)) {
|
|
122
|
+
const content = header ? `${header}\n${wrappedSection}\n` : `${wrappedSection}\n`;
|
|
123
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
124
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
125
|
+
logger?.info?.(`[FileProtection] Created "${fileName}" (new file)`);
|
|
126
|
+
return { written: true, strategy: 'create', filePath };
|
|
127
|
+
}
|
|
128
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
129
|
+
// Case 2: 文件已有 begin/end 标记 → 仅替换标记区段
|
|
130
|
+
if (SECTION_PATTERN.test(existing)) {
|
|
131
|
+
const updated = existing.replace(SECTION_PATTERN, wrappedSection);
|
|
132
|
+
if (updated === existing) {
|
|
133
|
+
logger?.info?.(`[FileProtection] "${fileName}" — section unchanged, skipped write`);
|
|
134
|
+
return { written: false, strategy: 'replace-section', filePath };
|
|
135
|
+
}
|
|
136
|
+
fs.writeFileSync(filePath, updated, 'utf8');
|
|
137
|
+
logger?.info?.(`[FileProtection] Updated "${fileName}" (replaced marker section)`);
|
|
138
|
+
return { written: true, strategy: 'replace-section', filePath };
|
|
139
|
+
}
|
|
140
|
+
// Case 3: 文件有 AutoSnippet 签名但无标记 → 旧版格式,重写并加标记
|
|
141
|
+
if (SIGNATURE_PATTERN.test(existing.slice(0, 1024))) {
|
|
142
|
+
const content = header ? `${header}\n${wrappedSection}\n` : `${wrappedSection}\n`;
|
|
143
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
144
|
+
logger?.info?.(`[FileProtection] Rewrote "${fileName}" (legacy → marker format)`);
|
|
145
|
+
return { written: true, strategy: 'rewrite-legacy', filePath };
|
|
146
|
+
}
|
|
147
|
+
// Case 4: 用户自有文件 → 追加标记区段到末尾
|
|
148
|
+
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
149
|
+
fs.writeFileSync(filePath, `${existing}${separator}${wrappedSection}\n`, 'utf8');
|
|
150
|
+
logger?.info?.(`[FileProtection] Appended AutoSnippet section to "${fileName}" (user-owned file preserved)`);
|
|
151
|
+
return { written: true, strategy: 'append-section', filePath };
|
|
152
|
+
}
|
|
@@ -19,36 +19,31 @@ import { SKILLS_DIR as BUILTIN_SKILLS_DIR } from '../../shared/package-root.js';
|
|
|
19
19
|
*/
|
|
20
20
|
const SKILL_NAME_MAP = {
|
|
21
21
|
'project-architecture': 'autosnippet-architecture',
|
|
22
|
-
'project-
|
|
23
|
-
'project-profile': 'autosnippet-profile',
|
|
22
|
+
'project-coding-standards': 'autosnippet-coding-standards',
|
|
24
23
|
'project-agent-guidelines': 'autosnippet-guidelines',
|
|
25
|
-
'project-event-
|
|
26
|
-
'project-
|
|
27
|
-
'project-
|
|
28
|
-
'project-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'project-
|
|
32
|
-
'project-framework-conventions': 'autosnippet-framework-conventions',
|
|
24
|
+
'project-data-event-flow': 'autosnippet-data-flow',
|
|
25
|
+
'project-design-patterns': 'autosnippet-design-patterns',
|
|
26
|
+
'project-error-resilience': 'autosnippet-error-resilience',
|
|
27
|
+
'project-swift-objc-idiom': 'autosnippet-swift-objc-idiom',
|
|
28
|
+
// 语言维度
|
|
29
|
+
'project-ts-js-module': 'autosnippet-ts-js-module',
|
|
30
|
+
'project-react-patterns': 'autosnippet-react-patterns',
|
|
33
31
|
'project-python-structure': 'autosnippet-python-structure',
|
|
34
|
-
'project-jvm-
|
|
32
|
+
'project-jvm-annotation': 'autosnippet-jvm-annotation',
|
|
35
33
|
};
|
|
36
34
|
/** 用途描述模板(英文,Cursor 优先) */
|
|
37
35
|
const SKILL_DESC_MAP = {
|
|
38
36
|
'autosnippet-architecture': 'Architecture patterns, module boundaries, and dependency rules for {project}. Use when creating new modules, reviewing architecture, or understanding dependencies.',
|
|
39
|
-
'autosnippet-
|
|
40
|
-
'autosnippet-profile': 'Project overview and profile for {project}. Use when needing background on the project, its tech stack, or structure.',
|
|
37
|
+
'autosnippet-coding-standards': 'Coding standards and style conventions for {project}. Use when writing new code, reviewing formatting, or enforcing naming conventions.',
|
|
41
38
|
'autosnippet-guidelines': 'Agent interaction guidelines for {project}. Use when understanding how to work with this specific project.',
|
|
42
39
|
'autosnippet-data-flow': 'Event and data flow patterns for {project}. Use when working with events, state management, or data pipelines.',
|
|
43
|
-
'autosnippet-
|
|
44
|
-
'autosnippet-
|
|
45
|
-
'autosnippet-
|
|
46
|
-
'autosnippet-
|
|
47
|
-
|
|
48
|
-
'autosnippet-module-exports': 'Module export structure, barrel exports, and public API surface for {project}. Use when working with imports/exports or module boundaries.',
|
|
49
|
-
'autosnippet-framework-conventions': 'Framework-specific conventions (component structure, routing, state management) for {project}. Use when following framework patterns.',
|
|
40
|
+
'autosnippet-design-patterns': 'Common code patterns and idioms for {project}. Use when implementing features following project conventions.',
|
|
41
|
+
'autosnippet-error-resilience': 'Error handling, resilience patterns and defensive coding for {project}. Use when making design decisions or code review.',
|
|
42
|
+
'autosnippet-swift-objc-idiom': 'Swift/ObjC idioms, categories, method swizzling and interop for {project}. Use when working with Swift or Objective-C code.',
|
|
43
|
+
'autosnippet-ts-js-module': 'Module export structure, barrel exports, and public API surface for {project}. Use when working with imports/exports or module boundaries.',
|
|
44
|
+
'autosnippet-react-patterns': 'React component patterns, state management conventions and routing for {project}. Use when following framework patterns.',
|
|
50
45
|
'autosnippet-python-structure': 'Python package structure, __init__.py exports, import patterns and type hint coverage for {project}. Use when working with Python modules.',
|
|
51
|
-
'autosnippet-jvm-
|
|
46
|
+
'autosnippet-jvm-annotation': 'Annotation patterns (DI, ORM, API, custom) and meta-programming for {project}. Use when working with Spring, Jakarta, or framework annotations.',
|
|
52
47
|
};
|
|
53
48
|
export class SkillsSyncer {
|
|
54
49
|
knowledgeService;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentPatcher — Proposal suggestedChanges 消费引擎
|
|
3
|
+
*
|
|
4
|
+
* 核心职责:
|
|
5
|
+
* 1. 从 Proposal.evidence 提取 suggestedChanges
|
|
6
|
+
* 2. 解析为结构化 Patch(JSON 或降级为纯文本)
|
|
7
|
+
* 3. 创建 Recipe 内容快照(before)
|
|
8
|
+
* 4. 应用 patch 到 Recipe 字段
|
|
9
|
+
* 5. 创建快照(after)
|
|
10
|
+
* 6. 持久化更新
|
|
11
|
+
*
|
|
12
|
+
* 安全边界:
|
|
13
|
+
* - 只修改 Patch 指定的字段,不擅自变更其他内容
|
|
14
|
+
* - suggestedChanges 缺失或格式不合规时降级跳过(不阻塞状态转移)
|
|
15
|
+
* - 所有变更在 before/after 快照中可追溯
|
|
16
|
+
*
|
|
17
|
+
* @module service/evolution/ContentPatcher
|
|
18
|
+
*/
|
|
19
|
+
import type { ContentPatchResult } from '../../types/evolution.js';
|
|
20
|
+
interface DatabaseLike {
|
|
21
|
+
prepare(sql: string): {
|
|
22
|
+
all(...params: unknown[]): Record<string, unknown>[];
|
|
23
|
+
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
24
|
+
run(...params: unknown[]): {
|
|
25
|
+
changes: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export declare class ContentPatcher {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(db: DatabaseLike);
|
|
32
|
+
/**
|
|
33
|
+
* 从 Proposal evidence 提取 suggestedChanges 并应用到 Recipe
|
|
34
|
+
*
|
|
35
|
+
* @returns ContentPatchResult — success: 是否成功应用了至少一个 patch
|
|
36
|
+
*/
|
|
37
|
+
applyProposal(proposal: {
|
|
38
|
+
id: string;
|
|
39
|
+
type: string;
|
|
40
|
+
targetRecipeId: string;
|
|
41
|
+
evidence: Record<string, unknown>[];
|
|
42
|
+
}, patchSource?: 'agent-suggestion' | 'correction' | 'merge'): ContentPatchResult;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentPatcher — Proposal suggestedChanges 消费引擎
|
|
3
|
+
*
|
|
4
|
+
* 核心职责:
|
|
5
|
+
* 1. 从 Proposal.evidence 提取 suggestedChanges
|
|
6
|
+
* 2. 解析为结构化 Patch(JSON 或降级为纯文本)
|
|
7
|
+
* 3. 创建 Recipe 内容快照(before)
|
|
8
|
+
* 4. 应用 patch 到 Recipe 字段
|
|
9
|
+
* 5. 创建快照(after)
|
|
10
|
+
* 6. 持久化更新
|
|
11
|
+
*
|
|
12
|
+
* 安全边界:
|
|
13
|
+
* - 只修改 Patch 指定的字段,不擅自变更其他内容
|
|
14
|
+
* - suggestedChanges 缺失或格式不合规时降级跳过(不阻塞状态转移)
|
|
15
|
+
* - 所有变更在 before/after 快照中可追溯
|
|
16
|
+
*
|
|
17
|
+
* @module service/evolution/ContentPatcher
|
|
18
|
+
*/
|
|
19
|
+
import Logger from '../../infrastructure/logging/Logger.js';
|
|
20
|
+
/* ────────────────────── Patchable Fields ────────────────────── */
|
|
21
|
+
/** 允许 patch 的顶层字段白名单 */
|
|
22
|
+
const PATCHABLE_FIELDS = new Set([
|
|
23
|
+
'coreCode',
|
|
24
|
+
'doClause',
|
|
25
|
+
'dontClause',
|
|
26
|
+
'whenClause',
|
|
27
|
+
'content.markdown',
|
|
28
|
+
'content.rationale',
|
|
29
|
+
'sourceRefs',
|
|
30
|
+
'headers',
|
|
31
|
+
]);
|
|
32
|
+
/* ────────────────────── Class ────────────────────── */
|
|
33
|
+
export class ContentPatcher {
|
|
34
|
+
#db;
|
|
35
|
+
#logger = Logger.getInstance();
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.#db = db;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 从 Proposal evidence 提取 suggestedChanges 并应用到 Recipe
|
|
41
|
+
*
|
|
42
|
+
* @returns ContentPatchResult — success: 是否成功应用了至少一个 patch
|
|
43
|
+
*/
|
|
44
|
+
applyProposal(proposal, patchSource = 'agent-suggestion') {
|
|
45
|
+
const recipeId = proposal.targetRecipeId;
|
|
46
|
+
// 1. 获取 Recipe 当前内容
|
|
47
|
+
const recipe = this.#getRecipe(recipeId);
|
|
48
|
+
if (!recipe) {
|
|
49
|
+
return this.#skipResult(recipeId, patchSource, 'Recipe not found');
|
|
50
|
+
}
|
|
51
|
+
// 2. 提取 suggestedChanges
|
|
52
|
+
const rawChanges = this.#extractSuggestedChanges(proposal.evidence);
|
|
53
|
+
if (!rawChanges) {
|
|
54
|
+
return this.#skipResult(recipeId, patchSource, 'No suggestedChanges in proposal evidence');
|
|
55
|
+
}
|
|
56
|
+
// 3. 解析为结构化 Patch
|
|
57
|
+
const patch = this.#parsePatch(rawChanges);
|
|
58
|
+
if (!patch || patch.changes.length === 0) {
|
|
59
|
+
return this.#skipResult(recipeId, patchSource, 'suggestedChanges could not be parsed or empty');
|
|
60
|
+
}
|
|
61
|
+
// 4. 创建 before 快照
|
|
62
|
+
const beforeSnapshot = this.#createSnapshot(recipe);
|
|
63
|
+
// 5. 应用 patch
|
|
64
|
+
const fieldsPatched = this.#applyPatch(recipe, patch.changes);
|
|
65
|
+
if (fieldsPatched.length === 0) {
|
|
66
|
+
return this.#skipResult(recipeId, patchSource, 'No valid fields to patch');
|
|
67
|
+
}
|
|
68
|
+
// 6. 持久化
|
|
69
|
+
this.#persistRecipe(recipe);
|
|
70
|
+
// 7. 创建 after 快照
|
|
71
|
+
const afterSnapshot = this.#createSnapshot(recipe);
|
|
72
|
+
this.#logger.info(`[ContentPatcher] Applied ${fieldsPatched.length} patches to recipe ${recipeId}: ${fieldsPatched.join(', ')}`);
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
recipeId,
|
|
76
|
+
fieldsPatched,
|
|
77
|
+
beforeSnapshot,
|
|
78
|
+
afterSnapshot,
|
|
79
|
+
patchSource,
|
|
80
|
+
skipped: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/* ═══════════════════ Extract ═══════════════════ */
|
|
84
|
+
#extractSuggestedChanges(evidence) {
|
|
85
|
+
for (const ev of evidence) {
|
|
86
|
+
const cast = ev;
|
|
87
|
+
if (cast.suggestedChanges &&
|
|
88
|
+
typeof cast.suggestedChanges === 'string' &&
|
|
89
|
+
cast.suggestedChanges.trim().length > 0) {
|
|
90
|
+
return cast.suggestedChanges;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/* ═══════════════════ Parse ═══════════════════ */
|
|
96
|
+
/**
|
|
97
|
+
* 解析 suggestedChanges — 优先 JSON,降级为纯文本全量替换
|
|
98
|
+
*/
|
|
99
|
+
#parsePatch(raw) {
|
|
100
|
+
// 尝试 JSON 解析
|
|
101
|
+
const trimmed = raw.trim();
|
|
102
|
+
if (trimmed.startsWith('{')) {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(trimmed);
|
|
105
|
+
if (parsed.changes && Array.isArray(parsed.changes)) {
|
|
106
|
+
// JSON 有效但 changes 为空 → 视为无 patch
|
|
107
|
+
if (parsed.changes.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
patchVersion: parsed.patchVersion ?? 1,
|
|
112
|
+
changes: parsed.changes,
|
|
113
|
+
reasoning: parsed.reasoning ?? '',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// JSON 解析失败,降级到纯文本
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 降级:纯文本视为 content.markdown 全量替换(适用于旧格式 Agent 输出)
|
|
122
|
+
if (trimmed.length >= 20) {
|
|
123
|
+
return {
|
|
124
|
+
patchVersion: 1,
|
|
125
|
+
changes: [
|
|
126
|
+
{
|
|
127
|
+
field: 'content.markdown',
|
|
128
|
+
action: 'replace',
|
|
129
|
+
newValue: trimmed,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
reasoning: 'Fallback: raw text treated as content.markdown replacement',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/* ═══════════════════ Apply ═══════════════════ */
|
|
138
|
+
#applyPatch(recipe, changes) {
|
|
139
|
+
const patched = [];
|
|
140
|
+
for (const change of changes) {
|
|
141
|
+
if (!PATCHABLE_FIELDS.has(change.field)) {
|
|
142
|
+
this.#logger.warn(`[ContentPatcher] Skipping non-patchable field: ${change.field}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const success = this.#applyOneChange(recipe, change);
|
|
146
|
+
if (success) {
|
|
147
|
+
patched.push(change.field);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return patched;
|
|
151
|
+
}
|
|
152
|
+
#applyOneChange(recipe, change) {
|
|
153
|
+
const { field, action } = change;
|
|
154
|
+
// content.* 嵌套字段
|
|
155
|
+
if (field.startsWith('content.')) {
|
|
156
|
+
return this.#applyContentChange(recipe, change);
|
|
157
|
+
}
|
|
158
|
+
// 顶层字段
|
|
159
|
+
if (field === 'sourceRefs' || field === 'headers') {
|
|
160
|
+
return this.#applyArrayChange(recipe, field, change);
|
|
161
|
+
}
|
|
162
|
+
// 简单字符串字段
|
|
163
|
+
const key = field;
|
|
164
|
+
if (action === 'replace' && change.newValue !== undefined) {
|
|
165
|
+
recipe[key] = change.newValue;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (action === 'append' && change.newValue !== undefined) {
|
|
169
|
+
recipe[key] = `${recipe[key]}\n${change.newValue}`;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
#applyContentChange(recipe, change) {
|
|
175
|
+
const contentObj = safeJsonParse(recipe.content, {});
|
|
176
|
+
const subField = change.field.split('.')[1]; // 'markdown' | 'rationale'
|
|
177
|
+
if (!subField) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (change.action === 'replace' && change.newValue !== undefined) {
|
|
181
|
+
contentObj[subField] = change.newValue;
|
|
182
|
+
recipe.content = JSON.stringify(contentObj);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (change.action === 'replace-section' && change.section && change.newContent) {
|
|
186
|
+
const current = contentObj[subField] ?? '';
|
|
187
|
+
const updated = this.#replaceSection(current, change.section, change.newContent);
|
|
188
|
+
if (updated !== current) {
|
|
189
|
+
contentObj[subField] = updated;
|
|
190
|
+
recipe.content = JSON.stringify(contentObj);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (change.action === 'append' && change.newValue !== undefined) {
|
|
196
|
+
const current = contentObj[subField] ?? '';
|
|
197
|
+
contentObj[subField] = `${current}\n${change.newValue}`;
|
|
198
|
+
recipe.content = JSON.stringify(contentObj);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
#applyArrayChange(recipe, field, change) {
|
|
204
|
+
if (change.action === 'replace' && change.newValue !== undefined) {
|
|
205
|
+
try {
|
|
206
|
+
const newArr = JSON.parse(change.newValue);
|
|
207
|
+
if (Array.isArray(newArr)) {
|
|
208
|
+
recipe[field] = JSON.stringify(newArr);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// invalid JSON array
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 替换 Markdown 中指定 section(基于标题行匹配)
|
|
221
|
+
*/
|
|
222
|
+
#replaceSection(markdown, sectionTitle, newContent) {
|
|
223
|
+
const lines = markdown.split('\n');
|
|
224
|
+
const titleLine = lines.findIndex((line) => line.trim() === sectionTitle.trim());
|
|
225
|
+
if (titleLine === -1) {
|
|
226
|
+
// Section 不存在 → 追加
|
|
227
|
+
return `${markdown}\n\n${newContent}`;
|
|
228
|
+
}
|
|
229
|
+
// 找到 section 结尾(下一个同级或更高级标题)
|
|
230
|
+
const headingLevel = (sectionTitle.match(/^#+/) ?? [''])[0].length;
|
|
231
|
+
let endLine = lines.length;
|
|
232
|
+
for (let i = titleLine + 1; i < lines.length; i++) {
|
|
233
|
+
const match = lines[i].match(/^(#+)\s/);
|
|
234
|
+
if (match && match[1].length <= headingLevel) {
|
|
235
|
+
endLine = i;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const before = lines.slice(0, titleLine);
|
|
240
|
+
const after = lines.slice(endLine);
|
|
241
|
+
return [...before, newContent, ...after].join('\n');
|
|
242
|
+
}
|
|
243
|
+
/* ═══════════════════ Snapshot ═══════════════════ */
|
|
244
|
+
#createSnapshot(recipe) {
|
|
245
|
+
const contentObj = safeJsonParse(recipe.content, {});
|
|
246
|
+
return {
|
|
247
|
+
coreCode: recipe.coreCode,
|
|
248
|
+
doClause: recipe.doClause,
|
|
249
|
+
dontClause: recipe.dontClause,
|
|
250
|
+
whenClause: recipe.whenClause,
|
|
251
|
+
content: {
|
|
252
|
+
markdown: contentObj.markdown ?? undefined,
|
|
253
|
+
rationale: contentObj.rationale ?? undefined,
|
|
254
|
+
},
|
|
255
|
+
sourceRefs: safeJsonParse(recipe.sourceRefs, []),
|
|
256
|
+
headers: safeJsonParse(recipe.headers, []),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/* ═══════════════════ DB ═══════════════════ */
|
|
260
|
+
#getRecipe(recipeId) {
|
|
261
|
+
const row = this.#db
|
|
262
|
+
.prepare(`SELECT id, title, coreCode, doClause, dontClause, whenClause, content, sourceRefs, headers
|
|
263
|
+
FROM knowledge_entries WHERE id = ?`)
|
|
264
|
+
.get(recipeId);
|
|
265
|
+
return row ?? null;
|
|
266
|
+
}
|
|
267
|
+
#persistRecipe(recipe) {
|
|
268
|
+
this.#db
|
|
269
|
+
.prepare(`UPDATE knowledge_entries
|
|
270
|
+
SET coreCode = ?, doClause = ?, dontClause = ?, whenClause = ?,
|
|
271
|
+
content = ?, sourceRefs = ?, headers = ?, updatedAt = ?
|
|
272
|
+
WHERE id = ?`)
|
|
273
|
+
.run(recipe.coreCode, recipe.doClause, recipe.dontClause, recipe.whenClause, recipe.content, recipe.sourceRefs, recipe.headers, Date.now(), recipe.id);
|
|
274
|
+
}
|
|
275
|
+
/* ═══════════════════ Helpers ═══════════════════ */
|
|
276
|
+
#skipResult(recipeId, patchSource, reason) {
|
|
277
|
+
this.#logger.info(`[ContentPatcher] Skipped for ${recipeId}: ${reason}`);
|
|
278
|
+
const emptySnapshot = {
|
|
279
|
+
coreCode: '',
|
|
280
|
+
doClause: '',
|
|
281
|
+
dontClause: '',
|
|
282
|
+
whenClause: '',
|
|
283
|
+
content: {},
|
|
284
|
+
sourceRefs: [],
|
|
285
|
+
headers: [],
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
recipeId,
|
|
290
|
+
fieldsPatched: [],
|
|
291
|
+
beforeSnapshot: emptySnapshot,
|
|
292
|
+
afterSnapshot: emptySnapshot,
|
|
293
|
+
patchSource,
|
|
294
|
+
skipped: true,
|
|
295
|
+
skipReason: reason,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/* ────────────────────── Util ────────────────────── */
|
|
300
|
+
function safeJsonParse(json, fallback) {
|
|
301
|
+
if (!json) {
|
|
302
|
+
return fallback;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
return JSON.parse(json);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return fallback;
|
|
309
|
+
}
|
|
310
|
+
}
|