Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit 52e7c08

Browse files
AsiaOstrichclaude
andcommitted
feat(evolution): integrate drift-scan into analyze + on-report trigger mode (XSPEC-004 Phase 4.5b).
Add DriftDetectionConfig to EvolutionConfig; extend devap evolution analyze to run DriftDetector as [4/4] step; export loadEvolutionConfig/executeEvolutionAnalyze from CLI; add on-report trigger in devap run that auto-invokes evolution analyze. 新增 DriftDetectionConfig 至 EvolutionConfig.analyzers; devap evolution analyze 整合 drift-scan 為第 [4/4] 步驟; 匯出 loadEvolutionConfig / executeEvolutionAnalyze 供 devap run 呼叫; devap run 完成後若設定 on-report 模式,自動觸發演進分析。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 81d58fd commit 52e7c08

4 files changed

Lines changed: 67 additions & 7 deletions

File tree

‎packages/cli/src/commands/evolution.ts‎

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
QualityStrategyProposalGenerator,
2525
ApprovalManager,
2626
} from "@devap/core";
27-
import type { EvolutionConfig, AnalyzerConfig, QualityStrategyConfig, ProposalStatus } from "@devap/core";
27+
import type { EvolutionConfig, AnalyzerConfig, QualityStrategyConfig, DriftDetectionConfig, ProposalStatus } from "@devap/core";
2828

2929
// ─── 預設配置 ───────────────────────────────────────────────
3030

@@ -48,20 +48,25 @@ const DEFAULT_QUALITY_STRATEGY_CONFIG: QualityStrategyConfig = {
4848
token_overhead_ratio: 1.5,
4949
};
5050

51+
const DEFAULT_DRIFT_DETECTION_CONFIG: DriftDetectionConfig = {
52+
enabled: true,
53+
};
54+
5155
const DEFAULT_EVOLUTION_CONFIG: EvolutionConfig = {
5256
enabled: true,
5357
analyzers: {
5458
"token-cost": DEFAULT_TOKEN_COST_CONFIG,
5559
"hook-efficiency": DEFAULT_HOOK_EFFICIENCY_CONFIG,
5660
"quality-strategy": DEFAULT_QUALITY_STRATEGY_CONFIG,
61+
"drift-detection": DEFAULT_DRIFT_DETECTION_CONFIG,
5762
},
5863
trigger: { mode: "manual" },
5964
approval: { required: true },
6065
};
6166

6267
// ─── Config 載入 ────────────────────────────────────────────
6368

64-
async function loadEvolutionConfig(cwd: string): Promise<EvolutionConfig> {
69+
export async function loadEvolutionConfig(cwd: string): Promise<EvolutionConfig> {
6570
const configPath = join(cwd, ".evolution", "config.yaml");
6671
try {
6772
const raw = await readFile(configPath, "utf-8");
@@ -82,6 +87,14 @@ async function loadEvolutionConfig(cwd: string): Promise<EvolutionConfig> {
8287
...DEFAULT_QUALITY_STRATEGY_CONFIG,
8388
...parsed.analyzers?.["quality-strategy"],
8489
},
90+
"drift-detection": {
91+
...DEFAULT_DRIFT_DETECTION_CONFIG,
92+
...parsed.analyzers?.["drift-detection"],
93+
},
94+
},
95+
trigger: {
96+
...DEFAULT_EVOLUTION_CONFIG.trigger,
97+
...parsed.trigger,
8598
},
8699
};
87100
} catch {
@@ -132,7 +145,7 @@ export async function executeEvolutionAnalyze(opts: {
132145
// ── Token Cost Analysis ──────────────────────────────────
133146
const tokenConfig = config.analyzers["token-cost"];
134147
if (tokenConfig.enabled) {
135-
console.log("📊 [1/3] Token 成本分析器...");
148+
console.log("📊 [1/4] Token 成本分析器...");
136149
const tokenAnalyzer = new TokenCostAnalyzer(executionHistoryBackend, tokenConfig);
137150
const tokenResult = await tokenAnalyzer.analyze();
138151

@@ -158,7 +171,7 @@ export async function executeEvolutionAnalyze(opts: {
158171
// ── Hook Efficiency Analysis ─────────────────────────────
159172
const hookConfig = config.analyzers["hook-efficiency"];
160173
if (hookConfig?.enabled) {
161-
console.log("\n🪝 [2/3] Hook 效率分析器...");
174+
console.log("\n🪝 [2/4] Hook 效率分析器...");
162175
const hookAnalyzer = new HookEfficiencyAnalyzer(cwd, hookConfig);
163176
const hookResult = await hookAnalyzer.analyze();
164177

@@ -189,7 +202,7 @@ export async function executeEvolutionAnalyze(opts: {
189202
// ── Quality Strategy Analysis ────────────────────────────
190203
const qualityConfig = config.analyzers["quality-strategy"];
191204
if (qualityConfig?.enabled) {
192-
console.log("\n🎯 [3/3] 品質策略分析器...");
205+
console.log("\n🎯 [3/4] 品質策略分析器...");
193206
const qualityAnalyzer = new QualityStrategyAnalyzer(executionHistoryBackend, qualityConfig);
194207
const qualityResult = await qualityAnalyzer.analyze();
195208

@@ -218,6 +231,33 @@ export async function executeEvolutionAnalyze(opts: {
218231
}
219232
}
220233

234+
// ── Drift Detection ──────────────────────────────────────
235+
const driftConfig = config.analyzers["drift-detection"];
236+
if (driftConfig?.enabled) {
237+
console.log("\n🔍 [4/4] 文件飄移偵測...");
238+
const driftDetector = new DriftDetector(cwd);
239+
const driftResult = await driftDetector.analyze();
240+
241+
if (driftResult.skipped) {
242+
console.log(` ⏭ 跳過:${driftResult.skip_reason}(找不到 .standards/ 目錄)`);
243+
} else {
244+
console.log(` ✓ 掃描 ${driftResult.files_scanned} 個檔案`);
245+
console.log(` 📌 發現 ${driftResult.items.length} 個飄移引用`);
246+
247+
if (driftResult.items.length > 0) {
248+
const broken = driftResult.items.filter((i) => i.drift_type === "broken_reference");
249+
const stale = driftResult.items.filter((i) => i.drift_type === "stale_standard");
250+
if (broken.length > 0) console.log(` - 失效路徑引用:${broken.length} 個`);
251+
if (stale.length > 0) console.log(` - 失效標準引用:${stale.length} 個`);
252+
253+
const reportPath = await driftDetector.writeReport(driftResult, join(cwd, ".evolution"));
254+
if (reportPath) {
255+
console.log(` 📄 drift-report.md 已寫入 .evolution/proposals/`);
256+
}
257+
}
258+
}
259+
}
260+
221261
console.log(`\n─────────────────────────────────────────────`);
222262
if (totalProposals > 0) {
223263
console.log(`✅ 分析完成,共產生 ${totalProposals} 個提案`);

‎packages/cli/src/index.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { registerInitCommand } from "./commands/init.js";
1313
import { registerSyncStandardsCommand } from "./commands/sync-standards.js";
1414
import { registerPackageCommand } from "./commands/package.js";
1515
import { registerReportCommand } from "./commands/report.js";
16-
import { registerEvolutionCommand } from "./commands/evolution.js";
16+
import { registerEvolutionCommand, loadEvolutionConfig, executeEvolutionAnalyze } from "./commands/evolution.js";
1717
import {
1818
orchestrate,
1919
validatePlan,
@@ -177,6 +177,20 @@ program
177177
// 靜默失敗,不影響主流程
178178
}
179179

180+
// XSPEC-004: on-report 觸發 — 若 evolution config 設為 on-report,自動執行演進分析
181+
if (!opts.dryRun) {
182+
try {
183+
const evoConfig = await loadEvolutionConfig(cwd);
184+
if (evoConfig.enabled && evoConfig.trigger.mode === "on-report") {
185+
console.log("\n─────────────────────────────────────────────");
186+
console.log("🔄 [on-report] 自動觸發演進分析...");
187+
await executeEvolutionAnalyze({ cwd, project: plan.project });
188+
}
189+
} catch {
190+
// 靜默失敗,不影響主流程的 exit code
191+
}
192+
}
193+
180194
// 有失敗時回傳非零 exit code
181195
if (report.summary.failed > 0) {
182196
process.exit(1);

‎packages/core/src/evolution/types.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ export interface QualityStrategyConfig extends AnalyzerConfig {
2929
token_overhead_ratio?: number;
3030
}
3131

32+
/** 飄移偵測配置(比其他 analyzer 更輕量,不需 min_samples) */
33+
export interface DriftDetectionConfig {
34+
enabled: boolean;
35+
}
36+
3237
/** Evolution 模組配置(對應 .evolution/config.yaml) */
3338
export interface EvolutionConfig {
3439
enabled: boolean;
3540
analyzers: {
3641
"token-cost": AnalyzerConfig;
3742
"hook-efficiency"?: AnalyzerConfig;
3843
"quality-strategy"?: QualityStrategyConfig;
44+
"drift-detection"?: DriftDetectionConfig;
3945
};
4046
trigger: {
4147
mode: "manual" | "on-report" | "scheduled";

‎packages/core/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export { resolveQualityProfile, checkQualityWarnings } from "./quality-profile.j
1212
export { runFixLoop, buildStructuredFeedback, type ExecuteResult, type FixLoopCallbacks } from "./fix-loop.js";
1313
export { parseTelemetryJsonl } from "./telemetry-parser.js";
1414
export { TokenCostAnalyzer, ProposalGenerator, HookEfficiencyAnalyzer, HookEfficiencyProposalGenerator, QualityStrategyAnalyzer, QualityStrategyProposalGenerator, DriftDetector, ApprovalManager, serializeProposal, parseProposal } from "./evolution/index.js";
15-
export type { EvolutionConfig, AnalyzerConfig, QualityStrategyConfig, AnalysisResult, HookEfficiencyAnalysisResult, HookEfficiencyIssue, QualityStrategyAnalysisResult, QualityStrategyIssue, QualityStrategySignal, DriftAnalysisResult, DriftItem, DriftType, GroupKey, GroupStats, Outlier, Proposal, ProposalMeta, ProposalStatus, ProposalImpact, ProposalTarget, AnalysisLogEntry, ConfirmApplyFn, ApprovalResult } from "./evolution/index.js";
15+
export type { EvolutionConfig, AnalyzerConfig, QualityStrategyConfig, DriftDetectionConfig, AnalysisResult, HookEfficiencyAnalysisResult, HookEfficiencyIssue, QualityStrategyAnalysisResult, QualityStrategyIssue, QualityStrategySignal, DriftAnalysisResult, DriftItem, DriftType, GroupKey, GroupStats, Outlier, Proposal, ProposalMeta, ProposalStatus, ProposalImpact, ProposalTarget, AnalysisLogEntry, ConfirmApplyFn, ApprovalResult } from "./evolution/index.js";
1616
export { LocalStorageBackend } from "./execution-history/index.js";
1717
// SPEC-015: 打包框架
1818
export { loadRecipe, resolveConfig, executeTarget, interpolateCommand, orchestratePackaging } from "./packaging/index.js";

0 commit comments

Comments
 (0)