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/sync.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
2
|
import { resolve, join } from "path";
|
|
3
|
-
import { daemonRequest } from "../daemon/client";
|
|
3
|
+
import { daemonRequest, getMeshPeers } from "../daemon/client";
|
|
4
4
|
import { AFD_DIR } from "../constants";
|
|
5
5
|
import { getSystemLanguage } from "../core/locale";
|
|
6
|
+
import { resolveScope } from "../core/federation";
|
|
7
|
+
import type { FederatedAntibody, FederatedPayload } from "../core/federation";
|
|
6
8
|
|
|
7
9
|
interface SyncResponse {
|
|
8
10
|
status: string;
|
|
@@ -10,26 +12,15 @@ interface SyncResponse {
|
|
|
10
12
|
count: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ecosystem: string;
|
|
17
|
-
antibodyCount: number;
|
|
18
|
-
antibodies: VaccineAntibody[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface VaccineAntibody {
|
|
22
|
-
id: string;
|
|
23
|
-
patternType: string;
|
|
24
|
-
fileTarget: string;
|
|
25
|
-
patches: { op: string; path: string; value?: string }[];
|
|
26
|
-
learnedAt: string;
|
|
27
|
-
}
|
|
15
|
+
// Re-export federated types under legacy names for local push/pull compatibility
|
|
16
|
+
type VaccinePayload = FederatedPayload;
|
|
17
|
+
type VaccineAntibody = FederatedAntibody;
|
|
28
18
|
|
|
29
19
|
interface SyncOptions {
|
|
30
20
|
push?: boolean;
|
|
31
21
|
pull?: boolean;
|
|
32
22
|
remote?: string;
|
|
23
|
+
localMesh?: boolean;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
const msgs = {
|
|
@@ -53,6 +44,17 @@ const msgs = {
|
|
|
53
44
|
pullHint: "Run `afd sync --push` first to create the shared store.",
|
|
54
45
|
learnedVia: "Learned via pull",
|
|
55
46
|
ready: "antibody(ies) ready for global federation.",
|
|
47
|
+
// Remote
|
|
48
|
+
remotePushTitle: "afd sync --push --remote",
|
|
49
|
+
remotePushSuccess: "Pushed to remote vaccine store",
|
|
50
|
+
remotePullTitle: "afd sync --pull --remote",
|
|
51
|
+
remotePullSuccess: "Pulled from remote vaccine store",
|
|
52
|
+
remoteSyncTitle: "afd sync --remote (bidirectional)",
|
|
53
|
+
remoteInvalidUrl: "Invalid URL. Must start with http:// or https://",
|
|
54
|
+
remoteTimeout: "Request timed out (10s)",
|
|
55
|
+
remoteNetworkError: "Network error",
|
|
56
|
+
remoteInvalidResponse: "Invalid response payload from remote",
|
|
57
|
+
remoteStatusError: "Remote returned error status",
|
|
56
58
|
},
|
|
57
59
|
ko: {
|
|
58
60
|
title: "afd sync — 백신 네트워크",
|
|
@@ -74,6 +76,17 @@ const msgs = {
|
|
|
74
76
|
pullHint: "`afd sync --push`를 먼저 실행하여 공유 저장소를 생성하세요.",
|
|
75
77
|
learnedVia: "풀로 학습됨",
|
|
76
78
|
ready: "개 항체가 글로벌 페더레이션 준비 완료.",
|
|
79
|
+
// 원격
|
|
80
|
+
remotePushTitle: "afd sync --push --remote",
|
|
81
|
+
remotePushSuccess: "원격 백신 저장소에 푸시 완료",
|
|
82
|
+
remotePullTitle: "afd sync --pull --remote",
|
|
83
|
+
remotePullSuccess: "원격 백신 저장소에서 풀 완료",
|
|
84
|
+
remoteSyncTitle: "afd sync --remote (양방향 동기화)",
|
|
85
|
+
remoteInvalidUrl: "올바르지 않은 URL입니다. http:// 또는 https://로 시작해야 합니다.",
|
|
86
|
+
remoteTimeout: "요청 시간 초과 (10초)",
|
|
87
|
+
remoteNetworkError: "네트워크 오류",
|
|
88
|
+
remoteInvalidResponse: "원격 서버의 응답 페이로드가 올바르지 않습니다.",
|
|
89
|
+
remoteStatusError: "원격 서버가 오류 상태를 반환했습니다.",
|
|
77
90
|
},
|
|
78
91
|
};
|
|
79
92
|
|
|
@@ -84,6 +97,27 @@ export async function syncCommand(opts: SyncOptions = {}) {
|
|
|
84
97
|
const lang = getSystemLanguage();
|
|
85
98
|
const m = msgs[lang];
|
|
86
99
|
|
|
100
|
+
if (opts.localMesh) {
|
|
101
|
+
await syncLocalMesh(m);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (opts.remote) {
|
|
106
|
+
const url = validateRemoteUrl(opts.remote, m);
|
|
107
|
+
if (!url) return;
|
|
108
|
+
|
|
109
|
+
if (opts.push && !opts.pull) {
|
|
110
|
+
await syncRemotePush(url, m);
|
|
111
|
+
} else if (opts.pull && !opts.push) {
|
|
112
|
+
await syncRemotePull(url, m);
|
|
113
|
+
} else {
|
|
114
|
+
// --remote alone (or both flags): bidirectional — pull first, then push
|
|
115
|
+
await syncRemotePull(url, m);
|
|
116
|
+
await syncRemotePush(url, m);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
if (opts.push) {
|
|
88
122
|
await syncPush(m);
|
|
89
123
|
return;
|
|
@@ -98,6 +132,285 @@ export async function syncCommand(opts: SyncOptions = {}) {
|
|
|
98
132
|
await syncExport(m);
|
|
99
133
|
}
|
|
100
134
|
|
|
135
|
+
// ─── Local mesh sync ─────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Bidirectional antibody sync across all live mesh peers (monorepo daemons).
|
|
139
|
+
* For each peer:
|
|
140
|
+
* 1. Pull their antibodies → POST to our /antibodies/learn
|
|
141
|
+
* 2. Push our antibodies → POST to their /antibodies/learn
|
|
142
|
+
* Conflict arbitration is handled by shouldAcceptRemote() on each daemon's side.
|
|
143
|
+
*/
|
|
144
|
+
async function syncLocalMesh(m: typeof msgs.en) {
|
|
145
|
+
const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
|
|
146
|
+
const W = 60;
|
|
147
|
+
const hline = (l: string, r: string) => `${l}${BOX.h.repeat(W)}${r}`;
|
|
148
|
+
const row = (s: string) => `${BOX.v} ${s}${" ".repeat(Math.max(0, W - 2 - s.length))} ${BOX.v}`;
|
|
149
|
+
|
|
150
|
+
let peers: Awaited<ReturnType<typeof getMeshPeers>>;
|
|
151
|
+
try {
|
|
152
|
+
peers = await getMeshPeers();
|
|
153
|
+
} catch {
|
|
154
|
+
console.error("[afd sync] Daemon not running. Start with `afd start`.");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(hline(BOX.tl, BOX.tr));
|
|
159
|
+
console.log(row(`🔗 afd sync --local-mesh`));
|
|
160
|
+
console.log(hline(BOX.ml, BOX.mr));
|
|
161
|
+
|
|
162
|
+
if (peers.length === 0) {
|
|
163
|
+
console.log(row("No live mesh peers found. Start daemons in other workspaces."));
|
|
164
|
+
console.log(hline(BOX.bl, BOX.br));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fetch our own antibodies once
|
|
169
|
+
const ours = (await daemonRequest<{ antibodies: AntibodyRow[] }>("/antibodies")).antibodies;
|
|
170
|
+
|
|
171
|
+
let totalPulled = 0;
|
|
172
|
+
let totalPushed = 0;
|
|
173
|
+
|
|
174
|
+
for (const peer of peers) {
|
|
175
|
+
const baseUrl = `http://127.0.0.1:${peer.port}`;
|
|
176
|
+
console.log(row(`Peer: ${peer.workspace} (port ${peer.port})`));
|
|
177
|
+
|
|
178
|
+
// 1. Pull from peer
|
|
179
|
+
try {
|
|
180
|
+
const theirData = await fetchJson<{ antibodies: AntibodyRow[] }>(`${baseUrl}/antibodies`);
|
|
181
|
+
for (const ab of theirData.antibodies) {
|
|
182
|
+
await daemonRequest<unknown>("/antibodies/learn", "POST", {
|
|
183
|
+
id: ab.id,
|
|
184
|
+
patternType: ab.pattern_type,
|
|
185
|
+
fileTarget: ab.file_target,
|
|
186
|
+
patches: JSON.parse(ab.patch_op),
|
|
187
|
+
scope: ab.scope ?? "local",
|
|
188
|
+
version: ab.ab_version ?? 1,
|
|
189
|
+
updatedAt: ab.updated_at,
|
|
190
|
+
});
|
|
191
|
+
totalPulled++;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
console.log(row(` ⚠ Pull failed (peer may be busy)`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 2. Push to peer
|
|
198
|
+
try {
|
|
199
|
+
for (const ab of ours) {
|
|
200
|
+
await fetchJson<unknown>(`${baseUrl}/antibodies/learn`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
id: ab.id,
|
|
204
|
+
patternType: ab.pattern_type,
|
|
205
|
+
fileTarget: ab.file_target,
|
|
206
|
+
patches: JSON.parse(ab.patch_op),
|
|
207
|
+
scope: ab.scope ?? "local",
|
|
208
|
+
version: ab.ab_version ?? 1,
|
|
209
|
+
updatedAt: ab.updated_at,
|
|
210
|
+
}),
|
|
211
|
+
headers: { "Content-Type": "application/json" },
|
|
212
|
+
});
|
|
213
|
+
totalPushed++;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
console.log(row(` ⚠ Push failed (peer may be busy)`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(hline(BOX.ml, BOX.mr));
|
|
221
|
+
console.log(row(`✅ Synced ${peers.length} peer(s) — pulled ${totalPulled}, pushed ${totalPushed}`));
|
|
222
|
+
console.log(hline(BOX.bl, BOX.br));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface AntibodyRow {
|
|
226
|
+
id: string;
|
|
227
|
+
pattern_type: string;
|
|
228
|
+
file_target: string;
|
|
229
|
+
patch_op: string;
|
|
230
|
+
scope: string;
|
|
231
|
+
ab_version: number;
|
|
232
|
+
updated_at: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|
236
|
+
const res = await fetch(url, init);
|
|
237
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
238
|
+
return res.json() as T;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Remote helpers ───────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
const REMOTE_TIMEOUT_MS = 10_000;
|
|
244
|
+
|
|
245
|
+
function validateRemoteUrl(raw: string, m: typeof msgs.en): string | null {
|
|
246
|
+
try {
|
|
247
|
+
const u = new URL(raw);
|
|
248
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error();
|
|
249
|
+
return u.toString();
|
|
250
|
+
} catch {
|
|
251
|
+
console.error(`[afd sync] ${m.remoteInvalidUrl}: ${raw}`);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function syncRemotePush(url: string, m: typeof msgs.en) {
|
|
257
|
+
// 1. Export latest local payload via daemon
|
|
258
|
+
let result: SyncResponse;
|
|
259
|
+
try {
|
|
260
|
+
result = await daemonRequest<SyncResponse>("/sync");
|
|
261
|
+
} catch (err: unknown) {
|
|
262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
263
|
+
console.error(`[afd sync] ${msg}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (result.count === 0) {
|
|
268
|
+
console.log(`[afd sync] ${m.noAntibodies}`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const localPayloadPath = resolve(AFD_DIR, "global-vaccine-payload.json");
|
|
273
|
+
const rawPayload: VaccinePayload = JSON.parse(readFileSync(localPayloadPath, "utf-8"));
|
|
274
|
+
|
|
275
|
+
// Stamp publisher scope on all antibodies before sending
|
|
276
|
+
const publisherScope = resolveScope();
|
|
277
|
+
const now = new Date().toISOString();
|
|
278
|
+
const payload: VaccinePayload = {
|
|
279
|
+
...rawPayload,
|
|
280
|
+
version: "1.7",
|
|
281
|
+
scope: publisherScope,
|
|
282
|
+
antibodies: rawPayload.antibodies.map(ab => ({
|
|
283
|
+
...ab,
|
|
284
|
+
scope: publisherScope,
|
|
285
|
+
fqid: `${publisherScope}/${ab.id}`,
|
|
286
|
+
version: ab.version ?? 1,
|
|
287
|
+
updatedAt: ab.updatedAt ?? ab.learnedAt ?? now,
|
|
288
|
+
})),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// 2. POST to remote
|
|
292
|
+
let res: Response;
|
|
293
|
+
try {
|
|
294
|
+
res = await fetch(url, {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: { "Content-Type": "application/json", "User-Agent": "afd-sync/1.7" },
|
|
297
|
+
body: JSON.stringify(payload),
|
|
298
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),
|
|
299
|
+
});
|
|
300
|
+
} catch (err: unknown) {
|
|
301
|
+
const isTimeout = err instanceof Error && err.name === "TimeoutError";
|
|
302
|
+
console.error(`[afd sync] ${isTimeout ? m.remoteTimeout : m.remoteNetworkError}: ${url}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!res.ok) {
|
|
307
|
+
console.error(`[afd sync] ${m.remoteStatusError} ${res.status} ${res.statusText}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
|
|
312
|
+
const W = 50;
|
|
313
|
+
const line = (l: string, r: string) => `${l}${BOX.h.repeat(W)}${r}`;
|
|
314
|
+
const row = (s: string) => `${BOX.v} ${s}${" ".repeat(Math.max(0, W - 2 - s.length))}${BOX.v}`;
|
|
315
|
+
|
|
316
|
+
console.log(line(BOX.tl, BOX.tr));
|
|
317
|
+
console.log(row(`📤 ${m.remotePushTitle}`));
|
|
318
|
+
console.log(line(BOX.ml, BOX.mr));
|
|
319
|
+
console.log(row(`${m.antibodies}: ${payload.antibodyCount}`));
|
|
320
|
+
console.log(row(`URL: ${url}`));
|
|
321
|
+
console.log(line(BOX.ml, BOX.mr));
|
|
322
|
+
console.log(row(`✅ ${m.remotePushSuccess}`));
|
|
323
|
+
console.log(line(BOX.bl, BOX.br));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function syncRemotePull(url: string, m: typeof msgs.en) {
|
|
327
|
+
// 1. GET payload from remote
|
|
328
|
+
let res: Response;
|
|
329
|
+
try {
|
|
330
|
+
res = await fetch(url, {
|
|
331
|
+
method: "GET",
|
|
332
|
+
headers: { "Accept": "application/json", "User-Agent": "afd-sync/1.7" },
|
|
333
|
+
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),
|
|
334
|
+
});
|
|
335
|
+
} catch (err: unknown) {
|
|
336
|
+
const isTimeout = err instanceof Error && err.name === "TimeoutError";
|
|
337
|
+
console.error(`[afd sync] ${isTimeout ? m.remoteTimeout : m.remoteNetworkError}: ${url}`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!res.ok) {
|
|
342
|
+
console.error(`[afd sync] ${m.remoteStatusError} ${res.status} ${res.statusText}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let remotePayload: VaccinePayload;
|
|
347
|
+
try {
|
|
348
|
+
const json = await res.json();
|
|
349
|
+
if (!json || !Array.isArray(json.antibodies)) throw new Error("missing antibodies array");
|
|
350
|
+
remotePayload = json as VaccinePayload;
|
|
351
|
+
} catch {
|
|
352
|
+
console.error(`[afd sync] ${m.remoteInvalidResponse}`);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 2. Learn each antibody into the running daemon (same as local pull)
|
|
357
|
+
let newCount = 0;
|
|
358
|
+
let skippedCount = 0;
|
|
359
|
+
const results: { id: string; status: string }[] = [];
|
|
360
|
+
|
|
361
|
+
for (const ab of remotePayload.antibodies) {
|
|
362
|
+
try {
|
|
363
|
+
const learnRes = await fetch(
|
|
364
|
+
`http://127.0.0.1:${getDaemonPort()}/antibodies/learn`,
|
|
365
|
+
{
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: { "Content-Type": "application/json" },
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
id: ab.id,
|
|
370
|
+
scope: ab.scope ?? remotePayload.scope ?? "remote",
|
|
371
|
+
version: ab.version ?? 1,
|
|
372
|
+
updatedAt: ab.updatedAt ?? ab.learnedAt,
|
|
373
|
+
patternType: ab.patternType,
|
|
374
|
+
fileTarget: ab.fileTarget,
|
|
375
|
+
patches: ab.patches,
|
|
376
|
+
}),
|
|
377
|
+
signal: AbortSignal.timeout(2000),
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
if (learnRes.ok) {
|
|
381
|
+
results.push({ id: ab.id, status: m.pullNew });
|
|
382
|
+
newCount++;
|
|
383
|
+
} else {
|
|
384
|
+
results.push({ id: ab.id, status: m.pullSkipped });
|
|
385
|
+
skippedCount++;
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
results.push({ id: ab.id, status: m.pullSkipped });
|
|
389
|
+
skippedCount++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
|
|
394
|
+
const W = 50;
|
|
395
|
+
const line = (l: string, r: string) => `${l}${BOX.h.repeat(W)}${r}`;
|
|
396
|
+
const row = (s: string) => `${BOX.v} ${s}${" ".repeat(Math.max(0, W - 2 - s.length))}${BOX.v}`;
|
|
397
|
+
|
|
398
|
+
console.log(line(BOX.tl, BOX.tr));
|
|
399
|
+
console.log(row(`📥 ${m.remotePullTitle}`));
|
|
400
|
+
console.log(line(BOX.ml, BOX.mr));
|
|
401
|
+
console.log(row(`URL: ${url}`));
|
|
402
|
+
console.log(line(BOX.ml, BOX.mr));
|
|
403
|
+
|
|
404
|
+
for (const r of results) {
|
|
405
|
+
const icon = r.status === m.pullNew ? "✅" : "⏭️";
|
|
406
|
+
console.log(row(`${icon} ${r.id}: ${r.status}`));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(line(BOX.ml, BOX.mr));
|
|
410
|
+
console.log(row(`✅ ${m.remotePullSuccess}: ${newCount} ${m.pullMerged}, ${skippedCount} ${m.pullSkipped}`));
|
|
411
|
+
console.log(line(BOX.bl, BOX.br));
|
|
412
|
+
}
|
|
413
|
+
|
|
101
414
|
async function syncExport(m: typeof msgs.en) {
|
|
102
415
|
let result: SyncResponse;
|
|
103
416
|
try {
|
package/src/constants.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { findWorkspaceRoot } from "./core/workspace";
|
|
3
|
-
|
|
4
|
-
// Relative paths (used when cwd is already the workspace root)
|
|
5
|
-
export const AFD_DIR = ".afd";
|
|
6
|
-
export const PID_FILE = join(AFD_DIR, "daemon.pid");
|
|
7
|
-
export const PORT_FILE = join(AFD_DIR, "daemon.port");
|
|
8
|
-
export const DB_FILE = join(AFD_DIR, "antibodies.sqlite");
|
|
9
|
-
export const LOG_FILE = join(AFD_DIR, "daemon.log");
|
|
10
|
-
export const QUARANTINE_DIR = join(AFD_DIR, "quarantine");
|
|
11
|
-
export const WATCH_TARGETS = [
|
|
12
|
-
".claude/", "CLAUDE.md", ".cursorrules", ".claudeignore", ".gitignore",
|
|
13
|
-
".windsurfrules", ".windsurf/", "codex.md", ".codex/",
|
|
14
|
-
".cursorignore", ".windsurfignore", ".codexignore",
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resolve all `.afd/` paths against the workspace root.
|
|
19
|
-
* Works correctly even when CLI is invoked from a subdirectory.
|
|
20
|
-
*/
|
|
21
|
-
export function resolveWorkspacePaths(from?: string) {
|
|
22
|
-
const root = findWorkspaceRoot(from);
|
|
23
|
-
return {
|
|
24
|
-
root,
|
|
25
|
-
afdDir: join(root, AFD_DIR),
|
|
26
|
-
pidFile: join(root, PID_FILE),
|
|
27
|
-
portFile: join(root, PORT_FILE),
|
|
28
|
-
dbFile: join(root, DB_FILE),
|
|
29
|
-
logFile: join(root, LOG_FILE),
|
|
30
|
-
quarantineDir: join(root, QUARANTINE_DIR),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { findWorkspaceRoot } from "./core/workspace";
|
|
3
|
+
|
|
4
|
+
// Relative paths (used when cwd is already the workspace root)
|
|
5
|
+
export const AFD_DIR = ".afd";
|
|
6
|
+
export const PID_FILE = join(AFD_DIR, "daemon.pid");
|
|
7
|
+
export const PORT_FILE = join(AFD_DIR, "daemon.port");
|
|
8
|
+
export const DB_FILE = join(AFD_DIR, "antibodies.sqlite");
|
|
9
|
+
export const LOG_FILE = join(AFD_DIR, "daemon.log");
|
|
10
|
+
export const QUARANTINE_DIR = join(AFD_DIR, "quarantine");
|
|
11
|
+
export const WATCH_TARGETS = [
|
|
12
|
+
".claude/", "CLAUDE.md", ".cursorrules", ".claudeignore", ".gitignore",
|
|
13
|
+
".windsurfrules", ".windsurf/", "codex.md", ".codex/",
|
|
14
|
+
".cursorignore", ".windsurfignore", ".codexignore",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve all `.afd/` paths against the workspace root.
|
|
19
|
+
* Works correctly even when CLI is invoked from a subdirectory.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveWorkspacePaths(from?: string) {
|
|
22
|
+
const root = findWorkspaceRoot(from);
|
|
23
|
+
return {
|
|
24
|
+
root,
|
|
25
|
+
afdDir: join(root, AFD_DIR),
|
|
26
|
+
pidFile: join(root, PID_FILE),
|
|
27
|
+
portFile: join(root, PORT_FILE),
|
|
28
|
+
dbFile: join(root, DB_FILE),
|
|
29
|
+
logFile: join(root, LOG_FILE),
|
|
30
|
+
quarantineDir: join(root, QUARANTINE_DIR),
|
|
31
|
+
};
|
|
32
|
+
}
|