autosnippet 3.2.3 → 3.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/bin/cli.js +164 -145
- package/config/constitution.yaml +3 -0
- package/dashboard/dist/assets/{index-DNOHYBhy.css → index-BaGY7kJI.css} +1 -1
- package/dashboard/dist/assets/{index-6itPuGFl.js → index-DfHY_3ln.js} +25 -25
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/CliLogger.js +78 -0
- package/lib/cli/SetupService.js +9 -719
- package/lib/cli/UpgradeService.js +23 -398
- package/lib/cli/deploy/FileDeployer.js +562 -0
- package/lib/cli/deploy/FileManifest.js +272 -0
- package/lib/external/mcp/McpServer.js +22 -26
- package/lib/external/mcp/autoApproveInjector.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +25 -3
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +6 -6
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +4 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +5 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +89 -44
- package/lib/external/mcp/handlers/consolidated.js +8 -9
- package/lib/external/mcp/handlers/dimension-complete-external.js +4 -4
- package/lib/external/mcp/handlers/guard.js +283 -5
- package/lib/external/mcp/handlers/task.js +183 -9
- package/lib/external/mcp/tools.js +32 -81
- package/lib/http/routes/task.js +55 -0
- package/lib/service/chat/AnalystAgent.js +12 -12
- package/lib/service/chat/ChatAgent.js +227 -545
- package/lib/service/chat/ChatAgentPrompts.js +9 -11
- package/lib/service/chat/ContextWindow.js +2 -296
- package/lib/service/chat/EpisodicConsolidator.js +15 -15
- package/lib/service/chat/ExplorationTracker.js +1262 -0
- package/lib/service/chat/HandoffProtocol.js +8 -9
- package/lib/service/chat/Memory.js +4 -0
- package/lib/service/chat/ProducerAgent.js +9 -6
- package/lib/service/chat/ProjectSemanticMemory.js +4 -0
- package/lib/service/chat/ReasoningTrace.js +182 -0
- package/lib/service/chat/WorkingMemory.js +4 -0
- package/lib/service/chat/memory/ActiveContext.js +910 -0
- package/lib/service/chat/memory/MemoryCoordinator.js +662 -0
- package/lib/service/chat/memory/PersistentMemory.js +450 -0
- package/lib/service/chat/memory/SessionStore.js +896 -0
- package/lib/service/chat/memory/index.js +13 -0
- package/lib/service/chat/tools/ast-graph.js +17 -16
- package/lib/service/cursor/AgentInstructionsGenerator.js +75 -40
- package/lib/service/cursor/FileProtection.js +4 -1
- package/lib/service/guard/GuardCheckEngine.js +10 -3
- package/lib/service/task/TaskGraphService.js +3 -3
- package/lib/shared/LanguageService.js +2 -1
- package/package.json +1 -1
- package/skills/autosnippet-intent/SKILL.md +1 -3
- package/skills/autosnippet-recipes/SKILL.md +1 -3
- package/templates/claude-code/commands/prime.md +19 -0
- package/templates/claude-code/hooks/autosnippet-session.sh +63 -0
- package/templates/claude-code/settings.json +21 -0
- package/templates/copilot-instructions.md +66 -177
- package/templates/cursor-hooks/commands/prime.md +12 -0
- package/templates/cursor-hooks/hooks/session-start.sh +10 -0
- package/templates/cursor-hooks/hooks.json +11 -0
- package/templates/cursor-rules/autosnippet-conventions.mdc +52 -3
- package/templates/cursor-rules/autosnippet-workflow.mdc +51 -27
- package/lib/external/mcp/handlers/decide.js +0 -109
- package/lib/external/mcp/handlers/ready.js +0 -42
- package/lib/service/chat/ReasoningLayer.js +0 -888
- package/templates/claude-hooks.yaml +0 -19
|
@@ -21,16 +21,15 @@ import { AnalystAgent } from '../../../../../service/chat/AnalystAgent.js';
|
|
|
21
21
|
import { EpisodicConsolidator } from '../../../../../service/chat/EpisodicConsolidator.js';
|
|
22
22
|
import { ProducerAgent } from '../../../../../service/chat/ProducerAgent.js';
|
|
23
23
|
import { ProjectSemanticMemory } from '../../../../../service/chat/ProjectSemanticMemory.js';
|
|
24
|
-
import {
|
|
24
|
+
import { MemoryCoordinator } from '../../../../../service/chat/memory/MemoryCoordinator.js';
|
|
25
|
+
import { SessionStore } from '../../../../../service/chat/memory/SessionStore.js';
|
|
25
26
|
import { clearCheckpoints, loadCheckpoints, saveDimensionCheckpoint } from './checkpoint.js';
|
|
26
27
|
import { buildTierReflection, DIMENSION_CONFIGS_V3, getFullDimensionConfig } from './dimension-configs.js';
|
|
27
28
|
import { getDimensionFocusKeywords } from '../shared/dimension-sop.js';
|
|
28
29
|
import { DimensionContext, parseDimensionDigest } from './dimension-context.js';
|
|
29
|
-
import { EpisodicMemory } from './EpisodicMemory.js';
|
|
30
30
|
import { IncrementalBootstrap } from './IncrementalBootstrap.js';
|
|
31
|
-
import { runNoAiFallback } from './noAiFallback.js';
|
|
32
|
-
import { ToolResultCache } from './ToolResultCache.js';
|
|
33
31
|
import { TierScheduler } from './tier-scheduler.js';
|
|
32
|
+
import { runNoAiFallback } from './noAiFallback.js';
|
|
34
33
|
import { generateSkill } from '../shared/skill-generator.js';
|
|
35
34
|
import { BootstrapEventEmitter } from '../../../../../shared/BootstrapEventEmitter.js';
|
|
36
35
|
|
|
@@ -283,25 +282,25 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
283
282
|
guardSummary: guardAudit?.summary || null,
|
|
284
283
|
});
|
|
285
284
|
|
|
286
|
-
// v4.0:
|
|
285
|
+
// v4.0: SessionStore — 替代 EpisodicMemory + ToolResultCache
|
|
287
286
|
// v5.0: 增量模式下从快照恢复已完成维度的记忆
|
|
288
|
-
let
|
|
287
|
+
let sessionStore;
|
|
289
288
|
if (isIncremental && incrementalPlan.restoredEpisodic) {
|
|
290
|
-
|
|
291
|
-
const restoredDims =
|
|
289
|
+
sessionStore = incrementalPlan.restoredEpisodic;
|
|
290
|
+
const restoredDims = sessionStore.getCompletedDimensions();
|
|
292
291
|
logger.info(
|
|
293
|
-
`[Bootstrap-v3] Restored
|
|
292
|
+
`[Bootstrap-v3] Restored SessionStore: ${restoredDims.length} dims [${restoredDims.join(', ')}]`
|
|
294
293
|
);
|
|
295
294
|
|
|
296
295
|
// 同步恢复 DimensionContext 的 digests (兼容)
|
|
297
296
|
for (const dimId of restoredDims) {
|
|
298
|
-
const report =
|
|
297
|
+
const report = sessionStore.getDimensionReport(dimId);
|
|
299
298
|
if (report?.digest) {
|
|
300
299
|
dimContext.addDimensionDigest(dimId, report.digest);
|
|
301
300
|
}
|
|
302
301
|
}
|
|
303
302
|
} else {
|
|
304
|
-
|
|
303
|
+
sessionStore = new SessionStore({
|
|
305
304
|
projectName: projectInfo.name,
|
|
306
305
|
primaryLang: projectInfo.lang,
|
|
307
306
|
fileCount: projectInfo.fileCount,
|
|
@@ -309,9 +308,6 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
309
308
|
});
|
|
310
309
|
}
|
|
311
310
|
|
|
312
|
-
// v4.0: ToolResultCache — 跨维度工具结果缓存 (search/read 去重)
|
|
313
|
-
const toolResultCache = new ToolResultCache();
|
|
314
|
-
|
|
315
311
|
// v4.1: ProjectSemanticMemory — 项目级永久语义记忆 (Tier 3)
|
|
316
312
|
// 加载历史 bootstrap 记忆 → 注入 AnalystAgent prompt
|
|
317
313
|
let semanticMemory = null;
|
|
@@ -349,6 +345,13 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
349
345
|
logger.warn(`[Bootstrap-v3] CodeEntityGraph init failed (non-blocking): ${cegErr.message}`);
|
|
350
346
|
}
|
|
351
347
|
|
|
348
|
+
// v5.0: MemoryCoordinator — 统一记忆协调器 (会话级)
|
|
349
|
+
const memoryCoordinator = new MemoryCoordinator({
|
|
350
|
+
persistentMemory: semanticMemory,
|
|
351
|
+
sessionStore,
|
|
352
|
+
mode: 'bootstrap',
|
|
353
|
+
});
|
|
354
|
+
|
|
352
355
|
// ═══════════════════════════════════════════════════════════
|
|
353
356
|
// Step 2: 按维度分层执行 (Analyst → Gate → Producer)
|
|
354
357
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -393,8 +396,8 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
393
396
|
// 恢复 DimensionContext 中的 digest
|
|
394
397
|
if (checkpoint.digest) {
|
|
395
398
|
dimContext.addDimensionDigest(dimId, checkpoint.digest);
|
|
396
|
-
// v4.0: 同步恢复到
|
|
397
|
-
|
|
399
|
+
// v4.0: 同步恢复到 SessionStore
|
|
400
|
+
sessionStore.addDimensionDigest(dimId, checkpoint.digest);
|
|
398
401
|
}
|
|
399
402
|
emitter.emitDimensionComplete(dimId, {
|
|
400
403
|
type: 'checkpoint-restored',
|
|
@@ -415,7 +418,7 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
415
418
|
async function executeDimension(dimId) {
|
|
416
419
|
// v5.0: 增量模式 — 跳过未受影响的维度 (使用历史 EpisodicMemory)
|
|
417
420
|
if (incrementalSkippedDims.includes(dimId)) {
|
|
418
|
-
const report =
|
|
421
|
+
const report = sessionStore.getDimensionReport(dimId);
|
|
419
422
|
const dimResult = {
|
|
420
423
|
candidateCount: report?.candidatesSummary?.length || 0,
|
|
421
424
|
rejectedCount: 0,
|
|
@@ -462,7 +465,7 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
462
465
|
},
|
|
463
466
|
producerResult: { candidateCount: cp.candidateCount || 0, toolCalls: [] },
|
|
464
467
|
};
|
|
465
|
-
|
|
468
|
+
sessionStore.storeDimensionReport(dimId, {
|
|
466
469
|
analysisText: cp.analysisText,
|
|
467
470
|
findings: [],
|
|
468
471
|
referencedFiles: restoredFiles,
|
|
@@ -528,18 +531,17 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
528
531
|
const dimStartTime = Date.now();
|
|
529
532
|
|
|
530
533
|
try {
|
|
531
|
-
//
|
|
532
|
-
const
|
|
534
|
+
// v5.0: 为每个维度创建独立的 ActiveContext 作用域
|
|
535
|
+
const analystScopeId = `${dimId}:analyst`;
|
|
536
|
+
memoryCoordinator.createDimensionScope(analystScopeId);
|
|
533
537
|
|
|
534
538
|
// ── Phase 1: Analyst ──
|
|
535
539
|
const analysisReport = await Promise.race([
|
|
536
540
|
analystAgent.analyze(dimConfig, projectInfo, {
|
|
537
541
|
sessionId,
|
|
538
542
|
dimensionContext: dimContext,
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
workingMemory: dimWorkingMemory,
|
|
542
|
-
toolResultCache,
|
|
543
|
+
// v5.0: 统一 MemoryCoordinator
|
|
544
|
+
memoryCoordinator,
|
|
543
545
|
// v4.1: Semantic Memory (历史 bootstrap 记忆)
|
|
544
546
|
semanticMemory,
|
|
545
547
|
// Phase E: Code Entity Graph 代码实体图谱
|
|
@@ -550,9 +552,10 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
550
552
|
),
|
|
551
553
|
]);
|
|
552
554
|
|
|
553
|
-
//
|
|
554
|
-
const
|
|
555
|
-
|
|
555
|
+
// v5.0: 通过 coordinator 蒸馏 ActiveContext → SessionStore
|
|
556
|
+
const ac = memoryCoordinator.getActiveContext(analystScopeId);
|
|
557
|
+
const distilled = ac ? ac.distill() : { keyFindings: [], totalObservations: 0, toolCallSummary: [] };
|
|
558
|
+
sessionStore.storeDimensionReport(dimId, {
|
|
556
559
|
analysisText: analysisReport.analysisText,
|
|
557
560
|
findings: analysisReport.findings || distilled.keyFindings,
|
|
558
561
|
referencedFiles: analysisReport.referencedFiles || [],
|
|
@@ -593,8 +596,15 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
593
596
|
|
|
594
597
|
if (needsCandidates && analysisReport.analysisText.length >= 100) {
|
|
595
598
|
try {
|
|
599
|
+
// v5.0: 为 Producer 创建独立作用域
|
|
600
|
+
const producerScopeId = `${dimId}:producer`;
|
|
601
|
+
memoryCoordinator.createDimensionScope(producerScopeId);
|
|
602
|
+
|
|
596
603
|
producerResult = await Promise.race([
|
|
597
|
-
producerAgent.produce(analysisReport, dimConfig, projectInfo, {
|
|
604
|
+
producerAgent.produce(analysisReport, dimConfig, projectInfo, {
|
|
605
|
+
sessionId,
|
|
606
|
+
memoryCoordinator,
|
|
607
|
+
}),
|
|
598
608
|
new Promise((_, reject) =>
|
|
599
609
|
setTimeout(() => reject(new Error(`Producer timeout for "${dimId}"`)), 180_000)
|
|
600
610
|
),
|
|
@@ -624,8 +634,8 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
624
634
|
};
|
|
625
635
|
dimContext.addDimensionDigest(dimId, digest);
|
|
626
636
|
|
|
627
|
-
// v4.0: 同步 digest 到
|
|
628
|
-
|
|
637
|
+
// v4.0: 同步 digest 到 SessionStore
|
|
638
|
+
sessionStore.addDimensionDigest(dimId, digest);
|
|
629
639
|
|
|
630
640
|
// 记录到 DimensionContext + EpisodicMemory
|
|
631
641
|
for (const tc of producerResult.toolCalls || []) {
|
|
@@ -637,8 +647,8 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
637
647
|
summary: tc.params?.summary || '',
|
|
638
648
|
};
|
|
639
649
|
dimContext.addSubmittedCandidate(dimId, candidateSummary);
|
|
640
|
-
// v4.0: 同步到
|
|
641
|
-
|
|
650
|
+
// v4.0: 同步到 SessionStore
|
|
651
|
+
sessionStore.addSubmittedCandidate(dimId, candidateSummary);
|
|
642
652
|
}
|
|
643
653
|
}
|
|
644
654
|
|
|
@@ -718,8 +728,8 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
718
728
|
|
|
719
729
|
// v4.0: Tier 级 Reflection — 综合本 Tier 所有维度的发现
|
|
720
730
|
try {
|
|
721
|
-
const reflection = buildTierReflection(tierIndex, tierResults,
|
|
722
|
-
|
|
731
|
+
const reflection = buildTierReflection(tierIndex, tierResults, sessionStore);
|
|
732
|
+
sessionStore.addTierReflection(tierIndex, reflection);
|
|
723
733
|
logger.info(
|
|
724
734
|
`[Bootstrap-v3] Tier ${tierIndex + 1} reflection: ` +
|
|
725
735
|
`${reflection.topFindings.length} top findings, ` +
|
|
@@ -734,18 +744,19 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
734
744
|
logger.info(
|
|
735
745
|
`[Bootstrap-v3] All tiers complete: ${results.size} dimensions in ${Date.now() - t0}ms`
|
|
736
746
|
);
|
|
737
|
-
// v4.0: 记录
|
|
738
|
-
const emStats =
|
|
739
|
-
const cacheStats = toolResultCache.getStats();
|
|
747
|
+
// v4.0: 记录 SessionStore 统计
|
|
748
|
+
const emStats = sessionStore.getStats();
|
|
740
749
|
logger.info(
|
|
741
750
|
`[Bootstrap-v3] Memory stats: ${emStats.completedDimensions} dims, ` +
|
|
742
751
|
`${emStats.totalFindings} findings, ${emStats.referencedFiles} files, ` +
|
|
743
752
|
`${emStats.crossReferences} cross-refs, ${emStats.tierReflections} reflections`
|
|
744
753
|
);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
754
|
+
if (emStats.cacheStats) {
|
|
755
|
+
logger.info(
|
|
756
|
+
`[Bootstrap-v3] Cache stats: ${emStats.cacheStats.hitRate} hit rate, ` +
|
|
757
|
+
`${emStats.cacheStats.searchCacheSize} searches, ${emStats.cacheStats.fileCacheSize} files`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
749
760
|
} else {
|
|
750
761
|
// 串行: 按 TierScheduler 内部顺序逐个执行
|
|
751
762
|
for (const tier of scheduler.getTiers()) {
|
|
@@ -787,8 +798,42 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
787
798
|
const analysisText = dimData.analysisReport.analysisText;
|
|
788
799
|
const referencedFiles = dimData.analysisReport.referencedFiles || [];
|
|
789
800
|
|
|
801
|
+
// 从 SessionStore 获取结构化发现,供 Skill 生成使用
|
|
802
|
+
const dimReport = sessionStore.getDimensionReport(dim.id);
|
|
803
|
+
const keyFindings = (dimReport?.findings || [])
|
|
804
|
+
.sort((a, b) => (b.importance || 5) - (a.importance || 5))
|
|
805
|
+
.slice(0, 10)
|
|
806
|
+
.map((f) => f.finding);
|
|
807
|
+
|
|
808
|
+
// 当 analysisText 过短(如 force-exit 时 AI 仅输出 JSON digest 被清洗后)
|
|
809
|
+
// 从 distilled findings 合成补充文本,避免 Skill 质量门控拦截
|
|
810
|
+
let effectiveText = analysisText;
|
|
811
|
+
if (analysisText.trim().length < 100 && keyFindings.length > 0) {
|
|
812
|
+
const distilled = dimReport?.workingMemoryDistilled;
|
|
813
|
+
const synthesized = [
|
|
814
|
+
`## ${dim.label || dim.id}`,
|
|
815
|
+
'',
|
|
816
|
+
analysisText.trim(),
|
|
817
|
+
'',
|
|
818
|
+
'## 关键发现',
|
|
819
|
+
'',
|
|
820
|
+
...keyFindings.map((f, i) => `${i + 1}. ${f}`),
|
|
821
|
+
];
|
|
822
|
+
if (distilled?.toolCallSummary?.length > 0) {
|
|
823
|
+
synthesized.push('', '## 探索记录', '');
|
|
824
|
+
for (const s of distilled.toolCallSummary.slice(0, 10)) {
|
|
825
|
+
synthesized.push(`- ${s}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
effectiveText = synthesized.join('\n');
|
|
829
|
+
logger.info(
|
|
830
|
+
`[Bootstrap-v3] Skill "${dim.id}": analysisText too short (${analysisText.trim().length} chars), ` +
|
|
831
|
+
`synthesized from ${keyFindings.length} findings → ${effectiveText.length} chars`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
|
|
790
835
|
const result = await generateSkill(
|
|
791
|
-
ctx, dim,
|
|
836
|
+
ctx, dim, effectiveText, referencedFiles, keyFindings, 'bootstrap-v3'
|
|
792
837
|
);
|
|
793
838
|
|
|
794
839
|
if (result.success) {
|
|
@@ -870,7 +915,7 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
870
915
|
const semanticMemory = new ProjectSemanticMemory(db, { logger });
|
|
871
916
|
const consolidator = new EpisodicConsolidator(semanticMemory, { logger });
|
|
872
917
|
|
|
873
|
-
consolidationResult = consolidator.consolidate(
|
|
918
|
+
consolidationResult = consolidator.consolidate(sessionStore, {
|
|
874
919
|
bootstrapSession: sessionId,
|
|
875
920
|
clearPrevious: true, // 全量冷启动: 先清除旧的 bootstrap 记忆
|
|
876
921
|
});
|
|
@@ -1042,7 +1087,7 @@ export async function fillDimensionsV3(fillContext) {
|
|
|
1042
1087
|
sessionId,
|
|
1043
1088
|
allFiles,
|
|
1044
1089
|
dimensionStats,
|
|
1045
|
-
episodicMemory,
|
|
1090
|
+
episodicMemory: sessionStore,
|
|
1046
1091
|
meta: {
|
|
1047
1092
|
durationMs: totalTimeMs,
|
|
1048
1093
|
candidateCount: candidateResults.created,
|
|
@@ -127,23 +127,22 @@ export async function consolidatedGraph(ctx, args) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
// ─── autosnippet_guard (整合
|
|
130
|
+
// ─── autosnippet_guard (整合 3 → 1) ─────────────────────────
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Guard 检查:按参数自动路由
|
|
134
|
-
*
|
|
135
|
-
* 有 files
|
|
134
|
+
* 无参数 → guardReview() (自动 git diff 检测 + inline recipe)
|
|
135
|
+
* 有 files → guardReview() (指定文件 + inline recipe) — files 为 string[] 或 {path}[]
|
|
136
|
+
* 有 code → guardCheck() (单文件内联检查)
|
|
136
137
|
*/
|
|
137
138
|
export async function consolidatedGuard(ctx, args) {
|
|
138
|
-
|
|
139
|
-
return guardHandlers.guardAuditFiles(ctx, args);
|
|
140
|
-
}
|
|
139
|
+
// 有 code → 单文件检查(旧模式)
|
|
141
140
|
if (args.code) {
|
|
142
141
|
return guardHandlers.guardCheck(ctx, args);
|
|
143
142
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
143
|
+
// 有 files(string[] 或 {path}[])或无参数 → review 模式
|
|
144
|
+
// review 模式内部处理 files 参数和自动检测
|
|
145
|
+
return guardHandlers.guardReview(ctx, args);
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
// ─── autosnippet_skill (整合 6 → 1) ─────────────────────────
|
|
@@ -336,11 +336,11 @@ export async function dimensionComplete(ctx, args) {
|
|
|
336
336
|
try {
|
|
337
337
|
const { EpisodicConsolidator } = await import('../../../service/chat/EpisodicConsolidator.js');
|
|
338
338
|
const db = ctx.container.get?.('database') ?? ctx.container.get?.('db');
|
|
339
|
-
if (db && session.
|
|
339
|
+
if (db && session.sessionStore) {
|
|
340
340
|
const { ProjectSemanticMemory } = await import('../../../service/chat/ProjectSemanticMemory.js');
|
|
341
341
|
const semanticMemory = new ProjectSemanticMemory(db, { logger });
|
|
342
342
|
const consolidator = new EpisodicConsolidator(semanticMemory, { logger });
|
|
343
|
-
const result = await consolidator.consolidate(session.
|
|
343
|
+
const result = await consolidator.consolidate(session.sessionStore, {
|
|
344
344
|
bootstrapSession: session.id,
|
|
345
345
|
clearPrevious: true,
|
|
346
346
|
});
|
|
@@ -409,12 +409,12 @@ export async function dimensionComplete(ctx, args) {
|
|
|
409
409
|
titles: s.titles,
|
|
410
410
|
referencedFiles: s.referencedFiles,
|
|
411
411
|
})),
|
|
412
|
-
// v3: 从
|
|
412
|
+
// v3: 从 SessionStore 提取前序维度分析摘要 + 关键发现(对标内部 Agent 的 buildContextForDimension)
|
|
413
413
|
previousDimensionAnalysis: (() => {
|
|
414
414
|
try {
|
|
415
415
|
const summaries = [];
|
|
416
416
|
for (const dimSummary of accumulatedEvidence.completedDimSummaries) {
|
|
417
|
-
const report = session.
|
|
417
|
+
const report = session.sessionStore.getDimensionReport(dimSummary.dimId);
|
|
418
418
|
if (report) {
|
|
419
419
|
summaries.push({
|
|
420
420
|
dimId: dimSummary.dimId,
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Handlers — Guard 审计 & 项目扫描
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* 统一入口:autosnippet_guard
|
|
5
|
+
* 无参数 → review 模式(自动 git diff 增量文件 + inline recipe)
|
|
6
|
+
* files: string[] → 指定文件检查(+ inline recipe)
|
|
7
|
+
* code: string → 单文件内联检查
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import fs from 'node:fs';
|
|
7
11
|
import path from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
8
13
|
import { envelope } from '../envelope.js';
|
|
9
14
|
|
|
15
|
+
// ═══ Review 轮次追踪(模块私有) ═══════════════════
|
|
16
|
+
|
|
17
|
+
const _reviewRounds = new Map(); // projectRoot → round count
|
|
18
|
+
const _lastReviewPassed = new Map(); // projectRoot → boolean
|
|
19
|
+
const MAX_REVIEW_ROUNDS = 5;
|
|
20
|
+
|
|
10
21
|
export async function guardCheck(ctx, args) {
|
|
11
22
|
const { GuardCheckEngine, detectLanguage } = await import(
|
|
12
23
|
'../../../service/guard/GuardCheckEngine.js'
|
|
@@ -81,11 +92,21 @@ export async function guardAuditFiles(ctx, args) {
|
|
|
81
92
|
// 注入 Enhancement Pack Guard 规则
|
|
82
93
|
await _injectEnhancementGuardRules(engine, ctx);
|
|
83
94
|
|
|
95
|
+
// 解析项目根路径(用于相对路径转绝对路径)
|
|
96
|
+
const projectRoot =
|
|
97
|
+
ctx.container?.singletons?._projectRoot ||
|
|
98
|
+
process.env.ASD_PROJECT_DIR ||
|
|
99
|
+
process.cwd();
|
|
100
|
+
|
|
84
101
|
// 补充缺失的 content(从磁盘读取)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
// 相对路径自动转绝对路径,避免 MCP 进程 cwd 不在项目目录时读不到文件
|
|
103
|
+
const filesToAudit = args.files.map((f) => {
|
|
104
|
+
const absPath = path.isAbsolute(f.path) ? f.path : path.resolve(projectRoot, f.path);
|
|
105
|
+
return {
|
|
106
|
+
path: absPath,
|
|
107
|
+
content: f.content || (fs.existsSync(absPath) ? fs.readFileSync(absPath, 'utf8') : ''),
|
|
108
|
+
};
|
|
109
|
+
});
|
|
89
110
|
|
|
90
111
|
const result = engine.auditFiles(filesToAudit, { scope });
|
|
91
112
|
|
|
@@ -131,6 +152,263 @@ export async function guardAuditFiles(ctx, args) {
|
|
|
131
152
|
});
|
|
132
153
|
}
|
|
133
154
|
|
|
155
|
+
// ═══ Review 模式 — 编码后质量门禁(无参数 = 自动检测) ═══
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Guard Review — 编码后的代码质量检查
|
|
159
|
+
*
|
|
160
|
+
* 设计要点:
|
|
161
|
+
* 1. 无参数 → 自动从 git diff 检测增量文件(staged + unstaged + untracked)
|
|
162
|
+
* 2. files: string[] → 指定文件路径(简化,不再要求对象数组)
|
|
163
|
+
* 3. violations 内联 recipe 修复指南(doClause + coreCode)
|
|
164
|
+
* 4. 防无限循环:reviewRound 计数 + MAX_REVIEW_ROUNDS 限制
|
|
165
|
+
* 5. 不绑定 task ID — 代码检查独立于任务系统
|
|
166
|
+
*
|
|
167
|
+
* @param {object} ctx — MCP context with container
|
|
168
|
+
* @param {object} args — { files?: string[] }
|
|
169
|
+
*/
|
|
170
|
+
export async function guardReview(ctx, args) {
|
|
171
|
+
const { GuardCheckEngine, detectLanguage } = await import(
|
|
172
|
+
'../../../service/guard/GuardCheckEngine.js'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const projectRoot = _getProjectRoot(ctx);
|
|
176
|
+
|
|
177
|
+
// 轮次追踪(基于 projectRoot,不绑定 task)
|
|
178
|
+
const round = (_reviewRounds.get(projectRoot) || 0) + 1;
|
|
179
|
+
_reviewRounds.set(projectRoot, round);
|
|
180
|
+
|
|
181
|
+
if (round > MAX_REVIEW_ROUNDS) {
|
|
182
|
+
_reviewRounds.delete(projectRoot);
|
|
183
|
+
_lastReviewPassed.set(projectRoot, true); // 强制通过
|
|
184
|
+
return envelope({
|
|
185
|
+
success: true,
|
|
186
|
+
data: { passed: true, files: [], totalViolations: 0, reviewRound: round, maxRoundsReached: true },
|
|
187
|
+
message: `⚠️ Guard review round ${round} exceeds max ${MAX_REVIEW_ROUNDS}. Force-passing. Remaining issues should be tracked as follow-up.`,
|
|
188
|
+
meta: { tool: 'autosnippet_guard', mode: 'review' },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 1. 确定待检查文件
|
|
193
|
+
let filePaths = [];
|
|
194
|
+
let fileSource = 'git-diff';
|
|
195
|
+
|
|
196
|
+
if (args.files && Array.isArray(args.files) && args.files.length > 0) {
|
|
197
|
+
// files 参数: string[] — 简化版,自动读取文件内容
|
|
198
|
+
filePaths = args.files
|
|
199
|
+
.map(f => typeof f === 'string' ? f : (f.path || f))
|
|
200
|
+
.map(f => path.isAbsolute(f) ? f : path.resolve(projectRoot, f))
|
|
201
|
+
.filter(f => fs.existsSync(f));
|
|
202
|
+
fileSource = 'explicit';
|
|
203
|
+
} else {
|
|
204
|
+
// 无参数 → 自动检测 git 变更文件
|
|
205
|
+
filePaths = _detectChangedFiles(projectRoot);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!filePaths.length) {
|
|
209
|
+
_reviewRounds.delete(projectRoot);
|
|
210
|
+
_lastReviewPassed.set(projectRoot, true);
|
|
211
|
+
return envelope({
|
|
212
|
+
success: true,
|
|
213
|
+
data: { passed: true, files: [], totalViolations: 0, reviewRound: round, fileSource },
|
|
214
|
+
message: '✅ No changed source files detected. Guard review passed.',
|
|
215
|
+
meta: { tool: 'autosnippet_guard', mode: 'review' },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 2. 预加载 rule recipe 缓存
|
|
220
|
+
const recipeMap = _loadRuleRecipes(ctx);
|
|
221
|
+
|
|
222
|
+
// 3. 创建引擎,注入 Enhancement Pack
|
|
223
|
+
const engine = _getOrCreateEngine(ctx, GuardCheckEngine);
|
|
224
|
+
await _injectEnhancementGuardRules(engine, ctx);
|
|
225
|
+
|
|
226
|
+
// 4. 逐文件检查
|
|
227
|
+
const results = [];
|
|
228
|
+
let totalViolations = 0;
|
|
229
|
+
let totalErrors = 0;
|
|
230
|
+
let totalWarnings = 0;
|
|
231
|
+
|
|
232
|
+
for (const fp of filePaths) {
|
|
233
|
+
try {
|
|
234
|
+
const code = fs.readFileSync(fp, 'utf8');
|
|
235
|
+
const lang = detectLanguage(fp);
|
|
236
|
+
const violations = engine.checkCode(code, lang, { filePath: fp });
|
|
237
|
+
|
|
238
|
+
const fileSummary = {
|
|
239
|
+
total: violations.length,
|
|
240
|
+
errors: violations.filter(v => v.severity === 'error').length,
|
|
241
|
+
warnings: violations.filter(v => v.severity === 'warning').length,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
totalViolations += violations.length;
|
|
245
|
+
totalErrors += fileSummary.errors;
|
|
246
|
+
totalWarnings += fileSummary.warnings;
|
|
247
|
+
|
|
248
|
+
// 内联 recipe 修复指南
|
|
249
|
+
const enriched = violations.map(v => {
|
|
250
|
+
const base = {
|
|
251
|
+
ruleId: v.ruleId,
|
|
252
|
+
message: v.message,
|
|
253
|
+
severity: v.severity,
|
|
254
|
+
line: v.line,
|
|
255
|
+
snippet: v.snippet,
|
|
256
|
+
fixSuggestion: v.fixSuggestion || null,
|
|
257
|
+
};
|
|
258
|
+
const recipe = recipeMap.get(v.ruleId);
|
|
259
|
+
if (recipe) {
|
|
260
|
+
base.recipe = {
|
|
261
|
+
title: recipe.title,
|
|
262
|
+
doClause: recipe.doClause || null,
|
|
263
|
+
dontClause: recipe.dontClause || null,
|
|
264
|
+
coreCode: recipe.coreCode || null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return base;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
results.push({ filePath: fp, language: lang, violations: enriched, summary: fileSummary });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
results.push({
|
|
273
|
+
filePath: fp, error: `Cannot read: ${err.message}`,
|
|
274
|
+
violations: [], summary: { total: 0, errors: 0, warnings: 0 },
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const passed = totalViolations === 0;
|
|
280
|
+
|
|
281
|
+
// 5. 更新共享状态
|
|
282
|
+
if (passed) {
|
|
283
|
+
_reviewRounds.delete(projectRoot);
|
|
284
|
+
_lastReviewPassed.set(projectRoot, true);
|
|
285
|
+
} else {
|
|
286
|
+
_lastReviewPassed.set(projectRoot, false);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 6. 写入 ViolationsStore
|
|
290
|
+
try {
|
|
291
|
+
const violationsStore = ctx.container.get('violationsStore');
|
|
292
|
+
for (const r of results) {
|
|
293
|
+
if (r.violations.length > 0) {
|
|
294
|
+
violationsStore.appendRun({
|
|
295
|
+
filePath: r.filePath, violations: r.violations,
|
|
296
|
+
summary: `guard review round ${round}: ${r.summary.errors}E ${r.summary.warnings}W`,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} catch { /* optional */ }
|
|
301
|
+
|
|
302
|
+
// 7. 构造消息
|
|
303
|
+
let message;
|
|
304
|
+
if (passed) {
|
|
305
|
+
message = `✅ Guard review passed (round ${round}). ${filePaths.length} file(s) checked, 0 violations.`;
|
|
306
|
+
} else {
|
|
307
|
+
const violatingFiles = results.filter(r => r.violations.length > 0);
|
|
308
|
+
const details = violatingFiles.map(f =>
|
|
309
|
+
` ${path.basename(f.filePath)}: ${f.violations.map(v => `L${v.line} ${v.ruleId}`).join(', ')}`
|
|
310
|
+
).join('\n');
|
|
311
|
+
|
|
312
|
+
message = [
|
|
313
|
+
`⚠️ Guard review round ${round}: ${totalViolations} violation(s) in ${violatingFiles.length} file(s).`,
|
|
314
|
+
details,
|
|
315
|
+
'',
|
|
316
|
+
'Each violation includes inline `recipe` with doClause + coreCode — apply fixes directly.',
|
|
317
|
+
round >= MAX_REVIEW_ROUNDS - 1
|
|
318
|
+
? `⚠️ Next round is the last (max ${MAX_REVIEW_ROUNDS}). Unresolved issues will be force-passed.`
|
|
319
|
+
: `Fix and call autosnippet_guard again (round ${round + 1}).`,
|
|
320
|
+
].join('\n');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return envelope({
|
|
324
|
+
success: true,
|
|
325
|
+
data: {
|
|
326
|
+
passed,
|
|
327
|
+
reviewRound: round,
|
|
328
|
+
fileSource,
|
|
329
|
+
files: results,
|
|
330
|
+
totalViolations,
|
|
331
|
+
summary: { total: totalViolations, errors: totalErrors, warnings: totalWarnings, filesChecked: filePaths.length },
|
|
332
|
+
},
|
|
333
|
+
message,
|
|
334
|
+
meta: { tool: 'autosnippet_guard', mode: 'review' },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ═══ Recipe 缓存 ═════════════════════════════════════════
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 预加载所有 rule 类型 recipe 的修复字段
|
|
342
|
+
* 构建 guardId → recipe 映射
|
|
343
|
+
*/
|
|
344
|
+
function _loadRuleRecipes(ctx) {
|
|
345
|
+
const map = new Map();
|
|
346
|
+
try {
|
|
347
|
+
const db = typeof ctx.container.get('database')?.getDb === 'function'
|
|
348
|
+
? ctx.container.get('database').getDb()
|
|
349
|
+
: ctx.container.get('database');
|
|
350
|
+
|
|
351
|
+
const rows = db.prepare(`
|
|
352
|
+
SELECT id, title, doClause, dontClause, coreCode, constraints
|
|
353
|
+
FROM knowledge_entries
|
|
354
|
+
WHERE (kind = 'rule' OR knowledgeType = 'boundary-constraint')
|
|
355
|
+
AND lifecycle = 'active'
|
|
356
|
+
`).all();
|
|
357
|
+
|
|
358
|
+
for (const row of rows) {
|
|
359
|
+
try {
|
|
360
|
+
const constraints = JSON.parse(row.constraints || '{}');
|
|
361
|
+
const guards = constraints.guards || [];
|
|
362
|
+
for (const g of guards) {
|
|
363
|
+
if (g.id) {
|
|
364
|
+
map.set(g.id, { title: row.title, doClause: row.doClause, dontClause: row.dontClause, coreCode: row.coreCode });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} catch { /* skip */ }
|
|
368
|
+
map.set(row.id, { title: row.title, doClause: row.doClause, dontClause: row.dontClause, coreCode: row.coreCode });
|
|
369
|
+
}
|
|
370
|
+
} catch { /* DB not available */ }
|
|
371
|
+
return map;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ═══ Git Diff 检测 ═══════════════════════════════════════
|
|
375
|
+
|
|
376
|
+
function _getProjectRoot(ctx) {
|
|
377
|
+
const root = ctx.container?.singletons?._projectRoot;
|
|
378
|
+
if (root) return root;
|
|
379
|
+
return process.env.ASD_PROJECT_DIR || process.cwd();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const SOURCE_EXTS = new Set([
|
|
383
|
+
'.m', '.mm', '.h', '.swift',
|
|
384
|
+
'.js', '.ts', '.jsx', '.tsx',
|
|
385
|
+
'.py', '.rb', '.java', '.kt', '.go', '.rs',
|
|
386
|
+
'.c', '.cpp', '.cc', '.cs',
|
|
387
|
+
'.vue', '.svelte',
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
function _detectChangedFiles(projectRoot) {
|
|
391
|
+
const root = projectRoot || process.env.ASD_PROJECT_DIR || process.cwd();
|
|
392
|
+
try {
|
|
393
|
+
const diffOutput = execSync(
|
|
394
|
+
'git diff --name-only HEAD 2>/dev/null; git diff --staged --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null',
|
|
395
|
+
{ cwd: root, encoding: 'utf8', timeout: 5000 }
|
|
396
|
+
);
|
|
397
|
+
const files = [...new Set(
|
|
398
|
+
diffOutput.split('\n')
|
|
399
|
+
.map(f => f.trim())
|
|
400
|
+
.filter(f => f && SOURCE_EXTS.has(path.extname(f).toLowerCase()))
|
|
401
|
+
)];
|
|
402
|
+
return files
|
|
403
|
+
.map(f => path.isAbsolute(f) ? f : path.resolve(root, f))
|
|
404
|
+
.filter(f => fs.existsSync(f));
|
|
405
|
+
} catch {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ═══ 项目扫描 ════════════════════════════════════════════
|
|
411
|
+
|
|
134
412
|
export async function scanProject(ctx, args) {
|
|
135
413
|
const maxFiles = args.maxFiles || 200;
|
|
136
414
|
const includeContent = args.includeContent || false;
|