relayax-cli 0.4.35 → 0.4.36

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.
@@ -34,7 +34,13 @@ export declare function computeContentsDiff(contents: ContentEntry[], relayDir:
34
34
  /**
35
35
  * contents 항목 단위로 from → .relay/ 동기화한다.
36
36
  */
37
- export declare function syncContentsToRelay(contents: ContentEntry[], contentsDiff: ContentDiffEntry[], relayDir: string, projectPath: string): void;
37
+ export declare function syncContentsToRelay(contents: ContentEntry[], contentsDiff: ContentDiffEntry[], relayDir: string, projectPath: string): {
38
+ removed: string[];
39
+ };
40
+ /**
41
+ * .relay/ 내에 있지만 contents 매니페스트에 없는 orphan 항목을 찾는다.
42
+ */
43
+ export declare function findOrphanItems(contents: ContentEntry[], relayDir: string): string[];
38
44
  /**
39
45
  * 패키지 홈 디렉토리를 결정한다.
40
46
  * 1. 프로젝트에 .relay/가 있으면 → projectPath/.relay/
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.computeContentsDiff = computeContentsDiff;
7
7
  exports.syncContentsToRelay = syncContentsToRelay;
8
+ exports.findOrphanItems = findOrphanItems;
8
9
  exports.resolveRelayDir = resolveRelayDir;
9
10
  exports.initGlobalAgentHome = initGlobalAgentHome;
10
11
  exports.registerPackage = registerPackage;
@@ -214,27 +215,60 @@ function discoverNewItems(contents, projectPath) {
214
215
  * contents 항목 단위로 from → .relay/ 동기화한다.
215
216
  */
216
217
  function syncContentsToRelay(contents, contentsDiff, relayDir, projectPath) {
218
+ const removed = [];
217
219
  for (const diffEntry of contentsDiff) {
220
+ const content = contents.find((c) => c.name === diffEntry.name && c.type === diffEntry.type);
221
+ // source_missing: 소스에서 삭제됨 → .relay/에서도 제거
222
+ if (diffEntry.status === 'source_missing') {
223
+ const relaySubPath = content ? deriveRelaySubPath(content) : `${diffEntry.type}s/${diffEntry.name}`;
224
+ const relayTarget = path_1.default.join(relayDir, relaySubPath);
225
+ if (fs_1.default.existsSync(relayTarget)) {
226
+ fs_1.default.rmSync(relayTarget, { recursive: true, force: true });
227
+ removed.push(relaySubPath);
228
+ }
229
+ continue;
230
+ }
218
231
  if (diffEntry.status !== 'modified')
219
232
  continue;
220
- const content = contents.find((c) => c.name === diffEntry.name && c.type === diffEntry.type);
221
233
  if (!content)
222
234
  continue;
223
235
  const absFrom = resolveFromPath(getFromPath(content), projectPath);
224
236
  const relaySubPath = deriveRelaySubPath(content);
225
237
  const relayTarget = path_1.default.join(relayDir, relaySubPath);
226
- // 단일 파일인 경우 직접 복사 (디렉토리 기반 diff/sync 불필요)
238
+ // 단일 파일인 경우 직접 복사
227
239
  if (fs_1.default.existsSync(absFrom) && fs_1.default.statSync(absFrom).isFile()) {
228
240
  fs_1.default.mkdirSync(path_1.default.dirname(relayTarget), { recursive: true });
229
241
  fs_1.default.copyFileSync(absFrom, relayTarget);
230
242
  continue;
231
243
  }
232
- // 디렉토리인 경우 diff 기반 동기화
244
+ // 디렉토리인 경우 diff 기반 동기화 (deleted 포함)
233
245
  const sourceFiles = scanPath(absFrom);
234
246
  const relayFiles = scanPath(relayTarget);
235
247
  const fileDiff = computeDiff(sourceFiles, relayFiles);
236
248
  syncToRelay(absFrom, relayTarget, fileDiff);
237
249
  }
250
+ return { removed };
251
+ }
252
+ /**
253
+ * .relay/ 내에 있지만 contents 매니페스트에 없는 orphan 항목을 찾는다.
254
+ */
255
+ function findOrphanItems(contents, relayDir) {
256
+ const contentPaths = new Set(contents.map((c) => deriveRelaySubPath(c)));
257
+ const orphans = [];
258
+ for (const dir of SYNC_DIRS) {
259
+ const fullDir = path_1.default.join(relayDir, dir);
260
+ if (!fs_1.default.existsSync(fullDir))
261
+ continue;
262
+ for (const entry of fs_1.default.readdirSync(fullDir, { withFileTypes: true })) {
263
+ if (entry.name.startsWith('.'))
264
+ continue;
265
+ const relPath = `${dir}/${entry.name}`;
266
+ if (!contentPaths.has(relPath)) {
267
+ orphans.push(relPath);
268
+ }
269
+ }
270
+ }
271
+ return orphans;
238
272
  }
239
273
  // ─── Global Agent Home ───
240
274
  /**
@@ -462,20 +496,31 @@ function registerPackage(program) {
462
496
  }
463
497
  // contents 기반 diff 계산
464
498
  const { diff: contentsDiff, newItems } = computeContentsDiff(contents, relayDir, projectPath);
499
+ const orphans = findOrphanItems(contents, relayDir);
465
500
  const summary = {
466
501
  modified: contentsDiff.filter((d) => d.status === 'modified').length,
467
502
  unchanged: contentsDiff.filter((d) => d.status === 'unchanged').length,
468
503
  source_missing: contentsDiff.filter((d) => d.status === 'source_missing').length,
469
504
  new_available: newItems.length,
505
+ orphaned: orphans.length,
470
506
  };
471
- const hasChanges = summary.modified > 0;
472
- // --sync: contents 단위 동기화
507
+ const hasChanges = summary.modified > 0 || summary.source_missing > 0 || summary.orphaned > 0;
508
+ // --sync: contents 단위 동기화 + orphan 정리
473
509
  if (opts.sync && hasChanges) {
474
- syncContentsToRelay(contents, contentsDiff, relayDir, projectPath);
510
+ const { removed } = syncContentsToRelay(contents, contentsDiff, relayDir, projectPath);
511
+ // orphan 항목 삭제
512
+ for (const orphan of orphans) {
513
+ const orphanPath = path_1.default.join(relayDir, orphan);
514
+ if (fs_1.default.existsSync(orphanPath)) {
515
+ fs_1.default.rmSync(orphanPath, { recursive: true, force: true });
516
+ removed.push(orphan);
517
+ }
518
+ }
475
519
  }
476
520
  const result = {
477
521
  diff: contentsDiff.filter((d) => d.status !== 'unchanged'),
478
522
  new_items: newItems,
523
+ orphans,
479
524
  synced: opts.sync === true && hasChanges,
480
525
  summary,
481
526
  };
@@ -499,6 +544,12 @@ function registerPackage(program) {
499
544
  }
500
545
  }
501
546
  }
547
+ if (orphans.length > 0) {
548
+ console.error('\n \x1b[33m.relay/에만 존재 (소스에서 삭제됨):\x1b[0m');
549
+ for (const orphan of orphans) {
550
+ console.error(` \x1b[31m✗ ${orphan}\x1b[0m`);
551
+ }
552
+ }
502
553
  if (newItems.length > 0) {
503
554
  console.error('\n 새로 발견된 콘텐츠:');
504
555
  for (const item of newItems) {
@@ -506,7 +557,7 @@ function registerPackage(program) {
506
557
  }
507
558
  }
508
559
  console.error('');
509
- console.error(` 합계: 변경 ${summary.modified}, 유지 ${summary.unchanged}, 원본 없음 ${summary.source_missing}, 신규 ${summary.new_available}`);
560
+ console.error(` 합계: 변경 ${summary.modified}, 유지 ${summary.unchanged}, 원본 없음 ${summary.source_missing}, 신규 ${summary.new_available}, 고아 ${summary.orphaned}`);
510
561
  if (opts.sync) {
511
562
  console.error('\n✓ .relay/에 반영 완료');
512
563
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayax-cli",
3
- "version": "0.4.35",
3
+ "version": "0.4.36",
4
4
  "description": "RelayAX Agent Team Marketplace CLI - Install and manage agent teams",
5
5
  "main": "dist/index.js",
6
6
  "bin": {