autonomous-flow-daemon 1.6.0 → 1.9.0
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/CHANGELOG.md +85 -85
- package/LICENSE +21 -21
- package/README-ko.md +282 -0
- package/README.md +282 -266
- package/mcp-config.json +10 -10
- package/package.json +4 -2
- package/src/adapters/index.ts +370 -370
- package/src/cli.ts +162 -127
- package/src/commands/benchmark.ts +187 -187
- package/src/commands/correlate.ts +180 -0
- package/src/commands/dashboard.ts +404 -0
- package/src/commands/evolution.ts +84 -1
- package/src/commands/fix.ts +158 -158
- package/src/commands/lang.ts +41 -41
- package/src/commands/plugin.ts +110 -0
- package/src/commands/restart.ts +14 -14
- package/src/commands/score.ts +276 -276
- package/src/commands/start.ts +155 -155
- package/src/commands/status.ts +157 -157
- package/src/commands/stop.ts +68 -68
- package/src/commands/suggest.ts +211 -0
- package/src/commands/sync.ts +329 -16
- package/src/constants.ts +32 -32
- package/src/core/boast.ts +280 -280
- package/src/core/config.ts +49 -49
- package/src/core/correlation-engine.ts +265 -0
- package/src/core/db.ts +145 -117
- package/src/core/discovery.ts +65 -65
- package/src/core/federation.ts +129 -0
- package/src/core/hologram/engine.ts +71 -71
- package/src/core/hologram/fallback.ts +11 -11
- package/src/core/hologram/go-extractor.ts +203 -0
- package/src/core/hologram/incremental.ts +227 -227
- package/src/core/hologram/py-extractor.ts +132 -132
- package/src/core/hologram/rust-extractor.ts +244 -0
- package/src/core/hologram/ts-extractor.ts +406 -320
- package/src/core/hologram/types.ts +27 -25
- package/src/core/hologram.ts +73 -71
- package/src/core/i18n/messages.ts +309 -309
- package/src/core/locale.ts +88 -88
- package/src/core/log-rotate.ts +33 -33
- package/src/core/log-utils.ts +38 -38
- package/src/core/lru-map.ts +61 -61
- package/src/core/notify.ts +74 -74
- package/src/core/plugin-manager.ts +225 -0
- package/src/core/rule-suggestion.ts +127 -0
- package/src/core/validator-generator.ts +224 -0
- package/src/core/workspace.ts +28 -28
- package/src/daemon/client.ts +78 -65
- package/src/daemon/event-batcher.ts +108 -108
- package/src/daemon/guards.ts +13 -13
- package/src/daemon/http-routes.ts +376 -293
- package/src/daemon/mcp-handler.ts +575 -270
- package/src/daemon/mcp-subscriptions.ts +81 -0
- package/src/daemon/mesh.ts +51 -0
- package/src/daemon/server.ts +655 -590
- package/src/daemon/types.ts +121 -100
- package/src/daemon/workspace-map.ts +104 -92
- package/src/platform.ts +60 -60
- package/src/version.ts +15 -15
- package/README.ko.md +0 -266
package/src/commands/stop.ts
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import { getDaemonInfo, isDaemonAlive, daemonRequest } from "../daemon/client";
|
|
2
|
-
import { unlinkSync } from "fs";
|
|
3
|
-
import { resolveWorkspacePaths } from "../constants";
|
|
4
|
-
import { formatShiftSummary } from "../core/boast";
|
|
5
|
-
import type { ShiftSummary } from "../core/boast";
|
|
6
|
-
import { getSystemLanguage } from "../core/locale";
|
|
7
|
-
import { getMessages, t } from "../core/i18n/messages";
|
|
8
|
-
import { detectEcosystem } from "../adapters/index";
|
|
9
|
-
|
|
10
|
-
function cleanupFiles() {
|
|
11
|
-
const paths = resolveWorkspacePaths();
|
|
12
|
-
try { unlinkSync(paths.pidFile); } catch {}
|
|
13
|
-
try { unlinkSync(paths.portFile); } catch {}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function stopCommand(options?: { clean?: boolean }) {
|
|
17
|
-
const lang = getSystemLanguage();
|
|
18
|
-
const msg = getMessages(lang);
|
|
19
|
-
const info = getDaemonInfo();
|
|
20
|
-
|
|
21
|
-
if (!info) {
|
|
22
|
-
console.log(msg.DAEMON_NOT_RUNNING);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (await isDaemonAlive(info)) {
|
|
27
|
-
// Fetch shift summary before stopping
|
|
28
|
-
try {
|
|
29
|
-
const summary = await daemonRequest<ShiftSummary>("/shift-summary");
|
|
30
|
-
console.log(formatShiftSummary(summary, lang));
|
|
31
|
-
} catch {
|
|
32
|
-
// Non-fatal: summary is a nicety, not a requirement
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
await daemonRequest("/stop");
|
|
37
|
-
console.log(t(msg.DAEMON_STOPPED, { pid: info.pid }));
|
|
38
|
-
} catch {
|
|
39
|
-
try {
|
|
40
|
-
process.kill(info.pid, "SIGTERM");
|
|
41
|
-
console.log(t(msg.DAEMON_KILLED, { pid: info.pid }));
|
|
42
|
-
} catch {
|
|
43
|
-
console.log("[afd] Daemon process already gone.");
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
console.log(msg.DAEMON_NOT_RESPONDING);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
cleanupFiles();
|
|
51
|
-
|
|
52
|
-
// --clean: remove injected hooks and MCP registration
|
|
53
|
-
if (options?.clean) {
|
|
54
|
-
const cwd = process.cwd();
|
|
55
|
-
const ecosystems = detectEcosystem(cwd);
|
|
56
|
-
for (const { adapter } of ecosystems) {
|
|
57
|
-
if (adapter.removeHooks) {
|
|
58
|
-
const r = adapter.removeHooks(cwd);
|
|
59
|
-
if (r.removed) console.log(`[afd] ${r.message}`);
|
|
60
|
-
}
|
|
61
|
-
if (adapter.unregisterMcp) {
|
|
62
|
-
const r = adapter.unregisterMcp(cwd);
|
|
63
|
-
if (r.removed) console.log(`[afd] ${r.message}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
console.log("[afd] Clean stop complete. All afd integrations removed.");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
1
|
+
import { getDaemonInfo, isDaemonAlive, daemonRequest } from "../daemon/client";
|
|
2
|
+
import { unlinkSync } from "fs";
|
|
3
|
+
import { resolveWorkspacePaths } from "../constants";
|
|
4
|
+
import { formatShiftSummary } from "../core/boast";
|
|
5
|
+
import type { ShiftSummary } from "../core/boast";
|
|
6
|
+
import { getSystemLanguage } from "../core/locale";
|
|
7
|
+
import { getMessages, t } from "../core/i18n/messages";
|
|
8
|
+
import { detectEcosystem } from "../adapters/index";
|
|
9
|
+
|
|
10
|
+
function cleanupFiles() {
|
|
11
|
+
const paths = resolveWorkspacePaths();
|
|
12
|
+
try { unlinkSync(paths.pidFile); } catch {}
|
|
13
|
+
try { unlinkSync(paths.portFile); } catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function stopCommand(options?: { clean?: boolean }) {
|
|
17
|
+
const lang = getSystemLanguage();
|
|
18
|
+
const msg = getMessages(lang);
|
|
19
|
+
const info = getDaemonInfo();
|
|
20
|
+
|
|
21
|
+
if (!info) {
|
|
22
|
+
console.log(msg.DAEMON_NOT_RUNNING);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (await isDaemonAlive(info)) {
|
|
27
|
+
// Fetch shift summary before stopping
|
|
28
|
+
try {
|
|
29
|
+
const summary = await daemonRequest<ShiftSummary>("/shift-summary");
|
|
30
|
+
console.log(formatShiftSummary(summary, lang));
|
|
31
|
+
} catch {
|
|
32
|
+
// Non-fatal: summary is a nicety, not a requirement
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await daemonRequest("/stop");
|
|
37
|
+
console.log(t(msg.DAEMON_STOPPED, { pid: info.pid }));
|
|
38
|
+
} catch {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(info.pid, "SIGTERM");
|
|
41
|
+
console.log(t(msg.DAEMON_KILLED, { pid: info.pid }));
|
|
42
|
+
} catch {
|
|
43
|
+
console.log("[afd] Daemon process already gone.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
console.log(msg.DAEMON_NOT_RESPONDING);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
cleanupFiles();
|
|
51
|
+
|
|
52
|
+
// --clean: remove injected hooks and MCP registration
|
|
53
|
+
if (options?.clean) {
|
|
54
|
+
const cwd = process.cwd();
|
|
55
|
+
const ecosystems = detectEcosystem(cwd);
|
|
56
|
+
for (const { adapter } of ecosystems) {
|
|
57
|
+
if (adapter.removeHooks) {
|
|
58
|
+
const r = adapter.removeHooks(cwd);
|
|
59
|
+
if (r.removed) console.log(`[afd] ${r.message}`);
|
|
60
|
+
}
|
|
61
|
+
if (adapter.unregisterMcp) {
|
|
62
|
+
const r = adapter.unregisterMcp(cwd);
|
|
63
|
+
if (r.removed) console.log(`[afd] ${r.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.log("[afd] Clean stop complete. All afd integrations removed.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* afd suggest — Rule Suggestion Engine CLI
|
|
3
|
+
*
|
|
4
|
+
* Analyzes mistake_history to recommend auto-validator generation
|
|
5
|
+
* for frequently recurring failure patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { suggestRules } from "../core/rule-suggestion";
|
|
9
|
+
import { correlatePatterns, findMatchingHotspot } from "../core/correlation-engine";
|
|
10
|
+
import { generateValidator } from "../core/validator-generator";
|
|
11
|
+
import type { ValidatorGenInput } from "../core/validator-generator";
|
|
12
|
+
import { initDb } from "../core/db";
|
|
13
|
+
import { getSystemLanguage } from "../core/locale";
|
|
14
|
+
import { existsSync, readFileSync } from "fs";
|
|
15
|
+
|
|
16
|
+
interface SuggestOptions {
|
|
17
|
+
days?: string;
|
|
18
|
+
min?: string;
|
|
19
|
+
apply?: boolean;
|
|
20
|
+
/** Annotate suggestions matching cross-project hotspots as "Community Verified" */
|
|
21
|
+
cross?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const msgs = {
|
|
25
|
+
en: {
|
|
26
|
+
title: "afd suggest — Rule Suggestion Engine",
|
|
27
|
+
noData: "No recurring patterns found. Keep working — the engine learns from mistakes over time.",
|
|
28
|
+
header: "Recommended validators based on failure history:",
|
|
29
|
+
frequency: "occurrences",
|
|
30
|
+
lastSeen: "last seen",
|
|
31
|
+
covered: "already covered",
|
|
32
|
+
uncovered: "no validator",
|
|
33
|
+
applyTitle: "Auto-generating validators for uncovered patterns...",
|
|
34
|
+
applyDone: (n: number) => `${n} validator(s) generated. Daemon will hot-reload automatically.`,
|
|
35
|
+
applySkipped: (n: number) => `${n} pattern(s) already covered — skipped.`,
|
|
36
|
+
hint: "Run `afd suggest --apply` to auto-generate validators for uncovered patterns.",
|
|
37
|
+
daysLabel: "Analysis window",
|
|
38
|
+
minLabel: "Minimum frequency",
|
|
39
|
+
communityVerified: "Community Verified",
|
|
40
|
+
crossHint: "🌐 = Community Verified (pattern seen in multiple projects). Run `afd correlate` for details.",
|
|
41
|
+
},
|
|
42
|
+
ko: {
|
|
43
|
+
title: "afd suggest — 규칙 추천 엔진",
|
|
44
|
+
noData: "반복 패턴을 찾지 못했습니다. 작업을 계속하세요 — 엔진이 시간이 지나면 실수에서 학습합니다.",
|
|
45
|
+
header: "실패 이력 기반 추천 검증기:",
|
|
46
|
+
frequency: "회 발생",
|
|
47
|
+
lastSeen: "최근",
|
|
48
|
+
covered: "이미 보호됨",
|
|
49
|
+
uncovered: "검증기 없음",
|
|
50
|
+
applyTitle: "미보호 패턴에 대해 검증기 자동 생성 중...",
|
|
51
|
+
applyDone: (n: number) => `${n}개 검증기 생성 완료. 데몬이 자동으로 핫 리로드합니다.`,
|
|
52
|
+
applySkipped: (n: number) => `${n}개 패턴은 이미 보호됨 — 건너뜀.`,
|
|
53
|
+
hint: "`afd suggest --apply`를 실행하여 미보호 패턴의 검증기를 자동 생성하세요.",
|
|
54
|
+
daysLabel: "분석 기간",
|
|
55
|
+
minLabel: "최소 빈도",
|
|
56
|
+
communityVerified: "커뮤니티 검증됨",
|
|
57
|
+
crossHint: "🌐 = 커뮤니티 검증됨 (여러 프로젝트에서 발견된 패턴). 자세히: `afd correlate`",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
|
|
62
|
+
const W = 62;
|
|
63
|
+
|
|
64
|
+
function hline(l: string, r: string) { return `${l}${BOX.h.repeat(W)}${r}`; }
|
|
65
|
+
function row(s: string) {
|
|
66
|
+
// Simple padding — doesn't account for wide chars but good enough for ASCII
|
|
67
|
+
const pad = Math.max(0, W - 2 - s.length);
|
|
68
|
+
return `${BOX.v} ${s}${" ".repeat(pad)} ${BOX.v}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatDate(ts: number): string {
|
|
72
|
+
const d = new Date(ts);
|
|
73
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function suggestCommand(opts: SuggestOptions = {}) {
|
|
77
|
+
const lang = getSystemLanguage();
|
|
78
|
+
const m = msgs[lang];
|
|
79
|
+
|
|
80
|
+
const days = parseInt(opts.days ?? "30", 10) || 30;
|
|
81
|
+
const minFreq = parseInt(opts.min ?? "3", 10) || 3;
|
|
82
|
+
|
|
83
|
+
const db = initDb();
|
|
84
|
+
try {
|
|
85
|
+
const suggestions = suggestRules(db, { days, minFrequency: minFreq, limit: 10 });
|
|
86
|
+
|
|
87
|
+
// Cross-project correlation: load hotspots once if --cross is set
|
|
88
|
+
const hotspots = opts.cross ? correlatePatterns(db, { minScopes: 2, limit: 50 }).hotspots : [];
|
|
89
|
+
|
|
90
|
+
if (suggestions.length === 0) {
|
|
91
|
+
console.log(`[afd suggest] ${m.noData}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Apply mode: generate validators for uncovered patterns ──
|
|
96
|
+
if (opts.apply) {
|
|
97
|
+
const uncovered = suggestions.filter(s => !s.alreadyCovered);
|
|
98
|
+
const covered = suggestions.filter(s => s.alreadyCovered);
|
|
99
|
+
|
|
100
|
+
if (uncovered.length === 0) {
|
|
101
|
+
console.log(`[afd suggest] ${m.applySkipped(covered.length)}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`[afd suggest] ${m.applyTitle}`);
|
|
106
|
+
|
|
107
|
+
let generated = 0;
|
|
108
|
+
for (const s of uncovered) {
|
|
109
|
+
// Build a ValidatorGenInput from the suggestion
|
|
110
|
+
const input = buildGenInput(s);
|
|
111
|
+
if (!input) continue;
|
|
112
|
+
const result = generateValidator(input);
|
|
113
|
+
if (result.written) {
|
|
114
|
+
console.log(` ✅ ${result.filename}`);
|
|
115
|
+
generated++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`[afd suggest] ${m.applyDone(generated)}`);
|
|
120
|
+
if (covered.length > 0) {
|
|
121
|
+
console.log(`[afd suggest] ${m.applySkipped(covered.length)}`);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Display mode: show ranked suggestions ──
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log(hline(BOX.tl, BOX.tr));
|
|
129
|
+
console.log(row(`🔍 ${m.title}`));
|
|
130
|
+
console.log(hline(BOX.ml, BOX.mr));
|
|
131
|
+
console.log(row(`${m.daysLabel}: ${days}d | ${m.minLabel}: ${minFreq}`));
|
|
132
|
+
console.log(hline(BOX.ml, BOX.mr));
|
|
133
|
+
|
|
134
|
+
let hasCrossAnnotation = false;
|
|
135
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
136
|
+
const s = suggestions[i];
|
|
137
|
+
const statusIcon = s.alreadyCovered ? "🛡️" : "⚠️";
|
|
138
|
+
const statusText = s.alreadyCovered ? m.covered : m.uncovered;
|
|
139
|
+
const rank = `#${i + 1}`;
|
|
140
|
+
|
|
141
|
+
// Cross-project annotation
|
|
142
|
+
const matchedHotspot = hotspots.length > 0 ? findMatchingHotspot(s.mistakeType, hotspots) : null;
|
|
143
|
+
const crossBadge = matchedHotspot ? ` 🌐` : "";
|
|
144
|
+
if (matchedHotspot) hasCrossAnnotation = true;
|
|
145
|
+
|
|
146
|
+
console.log(row(`${rank} ${statusIcon} ${s.filePath}${crossBadge}`));
|
|
147
|
+
console.log(row(` ${s.mistakeType} — ${s.frequency} ${m.frequency}`));
|
|
148
|
+
if (matchedHotspot) {
|
|
149
|
+
console.log(row(` 🌐 ${m.communityVerified} (${matchedHotspot.scopeCount} projects)`));
|
|
150
|
+
}
|
|
151
|
+
console.log(row(` ${m.lastSeen}: ${formatDate(s.lastSeen)} | ${statusText}`));
|
|
152
|
+
|
|
153
|
+
// Truncate description
|
|
154
|
+
const maxDesc = W - 8;
|
|
155
|
+
const desc = s.description.length > maxDesc ? s.description.slice(0, maxDesc - 3) + "..." : s.description;
|
|
156
|
+
console.log(row(` ${desc}`));
|
|
157
|
+
|
|
158
|
+
if (i < suggestions.length - 1) {
|
|
159
|
+
console.log(row(""));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const uncoveredCount = suggestions.filter(s => !s.alreadyCovered).length;
|
|
164
|
+
if (uncoveredCount > 0 || hasCrossAnnotation) {
|
|
165
|
+
console.log(hline(BOX.ml, BOX.mr));
|
|
166
|
+
if (uncoveredCount > 0) console.log(row(`💡 ${m.hint}`));
|
|
167
|
+
if (hasCrossAnnotation) console.log(row(`💡 ${m.crossHint}`));
|
|
168
|
+
}
|
|
169
|
+
console.log(hline(BOX.bl, BOX.br));
|
|
170
|
+
} finally {
|
|
171
|
+
db.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build a ValidatorGenInput from a suggestion.
|
|
177
|
+
* Uses the mistake_type to infer the failure type and creates a synthetic input
|
|
178
|
+
* so the validator-generator can produce the correct template.
|
|
179
|
+
*/
|
|
180
|
+
function buildGenInput(s: { filePath: string; mistakeType: string; description: string }): ValidatorGenInput | null {
|
|
181
|
+
const filePath = s.filePath;
|
|
182
|
+
|
|
183
|
+
// Map mistake_type to failureType + synthetic content
|
|
184
|
+
if (s.mistakeType.includes("deletion") || s.mistakeType.includes("delete")) {
|
|
185
|
+
return { failureType: "deletion", originalPath: filePath, corruptedContent: "DELETED", restoredContent: readFileSafe(filePath) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (s.mistakeType.includes("empty") || s.mistakeType.includes("blank")) {
|
|
189
|
+
return { failureType: "corruption", originalPath: filePath, corruptedContent: "", restoredContent: readFileSafe(filePath) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (s.mistakeType.includes("truncat")) {
|
|
193
|
+
// Simulate severe truncation
|
|
194
|
+
const restored = readFileSafe(filePath);
|
|
195
|
+
return { failureType: "corruption", originalPath: filePath, corruptedContent: "x", restoredContent: restored };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (s.mistakeType.includes("json") || s.mistakeType.includes("syntax")) {
|
|
199
|
+
return { failureType: "corruption", originalPath: filePath, corruptedContent: "{invalid", restoredContent: readFileSafe(filePath) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Generic corruption
|
|
203
|
+
return { failureType: "corruption", originalPath: filePath, corruptedContent: "corrupted", restoredContent: readFileSafe(filePath) };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function readFileSafe(path: string): string | null {
|
|
207
|
+
try {
|
|
208
|
+
if (existsSync(path)) return readFileSync(path, "utf-8");
|
|
209
|
+
} catch { /* ignore */ }
|
|
210
|
+
return null;
|
|
211
|
+
}
|