@claudetree/core 0.1.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/application/SessionManager.d.ts +18 -0
  3. package/dist/application/SessionManager.d.ts.map +1 -0
  4. package/dist/application/SessionManager.js +86 -0
  5. package/dist/application/SessionManager.js.map +1 -0
  6. package/dist/application/WorktreeSyncService.d.ts +17 -0
  7. package/dist/application/WorktreeSyncService.d.ts.map +1 -0
  8. package/dist/application/WorktreeSyncService.js +63 -0
  9. package/dist/application/WorktreeSyncService.js.map +1 -0
  10. package/dist/application/index.d.ts +3 -0
  11. package/dist/application/index.d.ts.map +1 -0
  12. package/dist/application/index.js +3 -0
  13. package/dist/application/index.js.map +1 -0
  14. package/dist/domain/index.d.ts +2 -0
  15. package/dist/domain/index.d.ts.map +1 -0
  16. package/dist/domain/index.js +2 -0
  17. package/dist/domain/index.js.map +1 -0
  18. package/dist/domain/repositories/ICodeReviewRepository.d.ts +7 -0
  19. package/dist/domain/repositories/ICodeReviewRepository.d.ts.map +1 -0
  20. package/dist/domain/repositories/ICodeReviewRepository.js +2 -0
  21. package/dist/domain/repositories/ICodeReviewRepository.js.map +1 -0
  22. package/dist/domain/repositories/IEventRepository.d.ts +8 -0
  23. package/dist/domain/repositories/IEventRepository.d.ts.map +1 -0
  24. package/dist/domain/repositories/IEventRepository.js +2 -0
  25. package/dist/domain/repositories/IEventRepository.js.map +1 -0
  26. package/dist/domain/repositories/ISessionRepository.d.ts +31 -0
  27. package/dist/domain/repositories/ISessionRepository.d.ts.map +1 -0
  28. package/dist/domain/repositories/ISessionRepository.js +2 -0
  29. package/dist/domain/repositories/ISessionRepository.js.map +1 -0
  30. package/dist/domain/repositories/IToolApprovalRepository.d.ts +8 -0
  31. package/dist/domain/repositories/IToolApprovalRepository.d.ts.map +1 -0
  32. package/dist/domain/repositories/IToolApprovalRepository.js +2 -0
  33. package/dist/domain/repositories/IToolApprovalRepository.js.map +1 -0
  34. package/dist/domain/repositories/IWorktreeRepository.d.ts +8 -0
  35. package/dist/domain/repositories/IWorktreeRepository.d.ts.map +1 -0
  36. package/dist/domain/repositories/IWorktreeRepository.js +2 -0
  37. package/dist/domain/repositories/IWorktreeRepository.js.map +1 -0
  38. package/dist/domain/repositories/index.d.ts +6 -0
  39. package/dist/domain/repositories/index.d.ts.map +1 -0
  40. package/dist/domain/repositories/index.js +2 -0
  41. package/dist/domain/repositories/index.js.map +1 -0
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/infra/claude/ClaudeSessionAdapter.d.ts +17 -0
  47. package/dist/infra/claude/ClaudeSessionAdapter.d.ts.map +1 -0
  48. package/dist/infra/claude/ClaudeSessionAdapter.js +162 -0
  49. package/dist/infra/claude/ClaudeSessionAdapter.js.map +1 -0
  50. package/dist/infra/claude/ClaudeSessionAdapter.test.d.ts +2 -0
  51. package/dist/infra/claude/ClaudeSessionAdapter.test.d.ts.map +1 -0
  52. package/dist/infra/claude/ClaudeSessionAdapter.test.js +122 -0
  53. package/dist/infra/claude/ClaudeSessionAdapter.test.js.map +1 -0
  54. package/dist/infra/claude/index.d.ts +2 -0
  55. package/dist/infra/claude/index.d.ts.map +1 -0
  56. package/dist/infra/claude/index.js +2 -0
  57. package/dist/infra/claude/index.js.map +1 -0
  58. package/dist/infra/git/GitWorktreeAdapter.d.ts +14 -0
  59. package/dist/infra/git/GitWorktreeAdapter.d.ts.map +1 -0
  60. package/dist/infra/git/GitWorktreeAdapter.js +74 -0
  61. package/dist/infra/git/GitWorktreeAdapter.js.map +1 -0
  62. package/dist/infra/git/GitWorktreeAdapter.test.d.ts +2 -0
  63. package/dist/infra/git/GitWorktreeAdapter.test.d.ts.map +1 -0
  64. package/dist/infra/git/GitWorktreeAdapter.test.js +104 -0
  65. package/dist/infra/git/GitWorktreeAdapter.test.js.map +1 -0
  66. package/dist/infra/git/index.d.ts +2 -0
  67. package/dist/infra/git/index.d.ts.map +1 -0
  68. package/dist/infra/git/index.js +2 -0
  69. package/dist/infra/git/index.js.map +1 -0
  70. package/dist/infra/github/GitHubAdapter.d.ts +20 -0
  71. package/dist/infra/github/GitHubAdapter.d.ts.map +1 -0
  72. package/dist/infra/github/GitHubAdapter.js +61 -0
  73. package/dist/infra/github/GitHubAdapter.js.map +1 -0
  74. package/dist/infra/github/GitHubAdapter.test.d.ts +2 -0
  75. package/dist/infra/github/GitHubAdapter.test.d.ts.map +1 -0
  76. package/dist/infra/github/GitHubAdapter.test.js +116 -0
  77. package/dist/infra/github/GitHubAdapter.test.js.map +1 -0
  78. package/dist/infra/github/index.d.ts +3 -0
  79. package/dist/infra/github/index.d.ts.map +1 -0
  80. package/dist/infra/github/index.js +2 -0
  81. package/dist/infra/github/index.js.map +1 -0
  82. package/dist/infra/index.d.ts +6 -0
  83. package/dist/infra/index.d.ts.map +1 -0
  84. package/dist/infra/index.js +6 -0
  85. package/dist/infra/index.js.map +1 -0
  86. package/dist/infra/storage/FileCodeReviewRepository.d.ts +14 -0
  87. package/dist/infra/storage/FileCodeReviewRepository.d.ts.map +1 -0
  88. package/dist/infra/storage/FileCodeReviewRepository.js +67 -0
  89. package/dist/infra/storage/FileCodeReviewRepository.js.map +1 -0
  90. package/dist/infra/storage/FileEventRepository.d.ts +16 -0
  91. package/dist/infra/storage/FileEventRepository.d.ts.map +1 -0
  92. package/dist/infra/storage/FileEventRepository.js +58 -0
  93. package/dist/infra/storage/FileEventRepository.js.map +1 -0
  94. package/dist/infra/storage/FileSessionRepository.d.ts +17 -0
  95. package/dist/infra/storage/FileSessionRepository.d.ts.map +1 -0
  96. package/dist/infra/storage/FileSessionRepository.js +68 -0
  97. package/dist/infra/storage/FileSessionRepository.js.map +1 -0
  98. package/dist/infra/storage/FileSessionRepository.test.d.ts +2 -0
  99. package/dist/infra/storage/FileSessionRepository.test.d.ts.map +1 -0
  100. package/dist/infra/storage/FileSessionRepository.test.js +84 -0
  101. package/dist/infra/storage/FileSessionRepository.test.js.map +1 -0
  102. package/dist/infra/storage/FileToolApprovalRepository.d.ts +17 -0
  103. package/dist/infra/storage/FileToolApprovalRepository.d.ts.map +1 -0
  104. package/dist/infra/storage/FileToolApprovalRepository.js +90 -0
  105. package/dist/infra/storage/FileToolApprovalRepository.js.map +1 -0
  106. package/dist/infra/storage/index.d.ts +5 -0
  107. package/dist/infra/storage/index.d.ts.map +1 -0
  108. package/dist/infra/storage/index.js +5 -0
  109. package/dist/infra/storage/index.js.map +1 -0
  110. package/dist/infra/websocket/WebSocketServer.d.ts +15 -0
  111. package/dist/infra/websocket/WebSocketServer.d.ts.map +1 -0
  112. package/dist/infra/websocket/WebSocketServer.js +45 -0
  113. package/dist/infra/websocket/WebSocketServer.js.map +1 -0
  114. package/dist/infra/websocket/index.d.ts +3 -0
  115. package/dist/infra/websocket/index.d.ts.map +1 -0
  116. package/dist/infra/websocket/index.js +2 -0
  117. package/dist/infra/websocket/index.js.map +1 -0
  118. package/package.json +40 -0
@@ -0,0 +1,3 @@
1
+ export { GitHubAdapter } from './GitHubAdapter.js';
2
+ export type { PRResult, ParsedIssueUrl } from './GitHubAdapter.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/github/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { GitHubAdapter } from './GitHubAdapter.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/github/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from './git/index.js';
2
+ export * from './claude/index.js';
3
+ export * from './storage/index.js';
4
+ export * from './websocket/index.js';
5
+ export * from './github/index.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/infra/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from './git/index.js';
2
+ export * from './claude/index.js';
3
+ export * from './storage/index.js';
4
+ export * from './websocket/index.js';
5
+ export * from './github/index.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/infra/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { CodeReview, ReviewStatus } from '@claudetree/shared';
2
+ import type { ICodeReviewRepository } from '../../domain/repositories/ICodeReviewRepository.js';
3
+ export declare class FileCodeReviewRepository implements ICodeReviewRepository {
4
+ private readonly reviewsDir;
5
+ constructor(configDir: string);
6
+ findBySessionId(sessionId: string): Promise<CodeReview | null>;
7
+ save(review: CodeReview): Promise<void>;
8
+ updateStatus(id: string, status: ReviewStatus, comment?: string): Promise<void>;
9
+ private getFilePath;
10
+ private getAllSessionIds;
11
+ private serialize;
12
+ private deserialize;
13
+ }
14
+ //# sourceMappingURL=FileCodeReviewRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileCodeReviewRepository.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/FileCodeReviewRepository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EAEb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oDAAoD,CAAC;AAIhG,qBAAa,wBAAyB,YAAW,qBAAqB;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,SAAS,EAAE,MAAM;IAIvB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAU9D,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IASvC,YAAY,CAChB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgBhB,OAAO,CAAC,WAAW;YAIL,gBAAgB;IAY9B,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,WAAW;CAOpB"}
@@ -0,0 +1,67 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const REVIEWS_DIR = 'reviews';
4
+ export class FileCodeReviewRepository {
5
+ reviewsDir;
6
+ constructor(configDir) {
7
+ this.reviewsDir = join(configDir, REVIEWS_DIR);
8
+ }
9
+ async findBySessionId(sessionId) {
10
+ try {
11
+ const content = await readFile(this.getFilePath(sessionId), 'utf-8');
12
+ const data = JSON.parse(content);
13
+ return this.deserialize(data);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ async save(review) {
20
+ await mkdir(this.reviewsDir, { recursive: true });
21
+ const data = this.serialize(review);
22
+ await writeFile(this.getFilePath(review.sessionId), JSON.stringify(data, null, 2));
23
+ }
24
+ async updateStatus(id, status, comment) {
25
+ const allFiles = await this.getAllSessionIds();
26
+ for (const sessionId of allFiles) {
27
+ const review = await this.findBySessionId(sessionId);
28
+ if (review && review.id === id) {
29
+ review.status = status;
30
+ review.comment = comment ?? review.comment;
31
+ review.resolvedAt = new Date();
32
+ await this.save(review);
33
+ return;
34
+ }
35
+ }
36
+ }
37
+ getFilePath(sessionId) {
38
+ return join(this.reviewsDir, `${sessionId}.json`);
39
+ }
40
+ async getAllSessionIds() {
41
+ try {
42
+ const { readdir } = await import('node:fs/promises');
43
+ const files = await readdir(this.reviewsDir);
44
+ return files
45
+ .filter((f) => f.endsWith('.json'))
46
+ .map((f) => f.replace('.json', ''));
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ }
52
+ serialize(review) {
53
+ return {
54
+ ...review,
55
+ requestedAt: review.requestedAt.toISOString(),
56
+ resolvedAt: review.resolvedAt?.toISOString() ?? null,
57
+ };
58
+ }
59
+ deserialize(data) {
60
+ return {
61
+ ...data,
62
+ requestedAt: new Date(data.requestedAt),
63
+ resolvedAt: data.resolvedAt ? new Date(data.resolvedAt) : null,
64
+ };
65
+ }
66
+ }
67
+ //# sourceMappingURL=FileCodeReviewRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileCodeReviewRepository.js","sourceRoot":"","sources":["../../../src/infra/storage/FileCodeReviewRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { SessionEvent } from '@claudetree/shared';
2
+ import type { IEventRepository } from '../../domain/repositories/IEventRepository.js';
3
+ export declare class FileEventRepository implements IEventRepository {
4
+ private readonly eventsDir;
5
+ constructor(configDir: string);
6
+ findBySessionId(sessionId: string): Promise<SessionEvent[]>;
7
+ append(event: SessionEvent): Promise<void>;
8
+ getLatest(sessionId: string, limit: number): Promise<SessionEvent[]>;
9
+ clear(sessionId: string): Promise<void>;
10
+ private getFilePath;
11
+ private loadEvents;
12
+ private saveEvents;
13
+ private serialize;
14
+ private deserialize;
15
+ }
16
+ //# sourceMappingURL=FileEventRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileEventRepository.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/FileEventRepository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAA0B,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAKtF,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,SAAS,EAAE,MAAM;IAIvB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAI3D,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAKpE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,OAAO,CAAC,WAAW;YAIL,UAAU;YAUV,UAAU;IASxB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,WAAW;CAMpB"}
@@ -0,0 +1,58 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const EVENTS_DIR = 'events';
4
+ const MAX_EVENTS = 1000;
5
+ export class FileEventRepository {
6
+ eventsDir;
7
+ constructor(configDir) {
8
+ this.eventsDir = join(configDir, EVENTS_DIR);
9
+ }
10
+ async findBySessionId(sessionId) {
11
+ return this.loadEvents(sessionId);
12
+ }
13
+ async append(event) {
14
+ const events = await this.loadEvents(event.sessionId);
15
+ events.push(event);
16
+ // Rotate if over max
17
+ const trimmed = events.slice(-MAX_EVENTS);
18
+ await this.saveEvents(event.sessionId, trimmed);
19
+ }
20
+ async getLatest(sessionId, limit) {
21
+ const events = await this.loadEvents(sessionId);
22
+ return events.slice(-limit);
23
+ }
24
+ async clear(sessionId) {
25
+ await this.saveEvents(sessionId, []);
26
+ }
27
+ getFilePath(sessionId) {
28
+ return join(this.eventsDir, `${sessionId}.json`);
29
+ }
30
+ async loadEvents(sessionId) {
31
+ try {
32
+ const content = await readFile(this.getFilePath(sessionId), 'utf-8');
33
+ const data = JSON.parse(content);
34
+ return data.map(this.deserialize);
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ async saveEvents(sessionId, events) {
41
+ await mkdir(this.eventsDir, { recursive: true });
42
+ const data = events.map(this.serialize);
43
+ await writeFile(this.getFilePath(sessionId), JSON.stringify(data, null, 2));
44
+ }
45
+ serialize(event) {
46
+ return {
47
+ ...event,
48
+ timestamp: event.timestamp.toISOString(),
49
+ };
50
+ }
51
+ deserialize(data) {
52
+ return {
53
+ ...data,
54
+ timestamp: new Date(data.timestamp),
55
+ };
56
+ }
57
+ }
58
+ //# sourceMappingURL=FileEventRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileEventRepository.js","sourceRoot":"","sources":["../../../src/infra/storage/FileEventRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,MAAM,UAAU,GAAG,QAAQ,CAAC;AAC5B,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,MAAM,OAAO,mBAAmB;IACb,SAAS,CAAS;IAEnC,YAAY,SAAiB;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAiB;QACrC,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAmB;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEnB,qBAAqB;QACrB,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAAiB,EAAE,KAAa;QAC9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAEO,WAAW,CAAC,SAAiB;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;IACnD,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,SAAiB;QACxC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;YACrE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA6B,CAAC;YAC7D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CACtB,SAAiB,EACjB,MAAsB;QAEtB,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;IAEO,SAAS,CAAC,KAAmB;QACnC,OAAO;YACL,GAAG,KAAK;YACR,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE;SACzC,CAAC;IACJ,CAAC;IAEO,WAAW,CAAC,IAA4B;QAC9C,OAAO;YACL,GAAG,IAAI;YACP,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;SACpC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ import type { Session } from '@claudetree/shared';
2
+ import type { ISessionRepository } from '../../domain/repositories/ISessionRepository.js';
3
+ export declare class FileSessionRepository implements ISessionRepository {
4
+ private readonly configDir;
5
+ private readonly filePath;
6
+ constructor(configDir: string);
7
+ findById(id: string): Promise<Session | null>;
8
+ findByWorktreeId(worktreeId: string): Promise<Session | null>;
9
+ findAll(): Promise<Session[]>;
10
+ save(session: Session): Promise<void>;
11
+ delete(id: string): Promise<void>;
12
+ private loadSessions;
13
+ private saveSessions;
14
+ private serializeSession;
15
+ private deserializeSession;
16
+ }
17
+ //# sourceMappingURL=FileSessionRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSessionRepository.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/FileSessionRepository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAC;AAI1F,qBAAa,qBAAsB,YAAW,kBAAkB;IAGlD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAFtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEL,SAAS,EAAE,MAAM;IAIxC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAK7C,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAK7D,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI7B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAMzB,YAAY;YAUZ,YAAY;IAM1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,kBAAkB;CAO3B"}
@@ -0,0 +1,68 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const SESSIONS_FILE = 'sessions.json';
4
+ export class FileSessionRepository {
5
+ configDir;
6
+ filePath;
7
+ constructor(configDir) {
8
+ this.configDir = configDir;
9
+ this.filePath = join(configDir, SESSIONS_FILE);
10
+ }
11
+ async findById(id) {
12
+ const sessions = await this.loadSessions();
13
+ return sessions.find((s) => s.id === id) ?? null;
14
+ }
15
+ async findByWorktreeId(worktreeId) {
16
+ const sessions = await this.loadSessions();
17
+ return sessions.find((s) => s.worktreeId === worktreeId) ?? null;
18
+ }
19
+ async findAll() {
20
+ return this.loadSessions();
21
+ }
22
+ async save(session) {
23
+ const sessions = await this.loadSessions();
24
+ const index = sessions.findIndex((s) => s.id === session.id);
25
+ if (index >= 0) {
26
+ sessions[index] = session;
27
+ }
28
+ else {
29
+ sessions.push(session);
30
+ }
31
+ await this.saveSessions(sessions);
32
+ }
33
+ async delete(id) {
34
+ const sessions = await this.loadSessions();
35
+ const filtered = sessions.filter((s) => s.id !== id);
36
+ await this.saveSessions(filtered);
37
+ }
38
+ async loadSessions() {
39
+ try {
40
+ const content = await readFile(this.filePath, 'utf-8');
41
+ const data = JSON.parse(content);
42
+ return data.map(this.deserializeSession);
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ }
48
+ async saveSessions(sessions) {
49
+ await mkdir(this.configDir, { recursive: true });
50
+ const data = sessions.map(this.serializeSession);
51
+ await writeFile(this.filePath, JSON.stringify(data, null, 2));
52
+ }
53
+ serializeSession(session) {
54
+ return {
55
+ ...session,
56
+ createdAt: session.createdAt.toISOString(),
57
+ updatedAt: session.updatedAt.toISOString(),
58
+ };
59
+ }
60
+ deserializeSession(data) {
61
+ return {
62
+ ...data,
63
+ createdAt: new Date(data.createdAt),
64
+ updatedAt: new Date(data.updatedAt),
65
+ };
66
+ }
67
+ }
68
+ //# sourceMappingURL=FileSessionRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSessionRepository.js","sourceRoot":"","sources":["../../../src/infra/storage/FileSessionRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=FileSessionRepository.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSessionRepository.test.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/FileSessionRepository.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { FileSessionRepository } from './FileSessionRepository.js';
6
+ describe('FileSessionRepository', () => {
7
+ let testDir;
8
+ let repo;
9
+ beforeEach(async () => {
10
+ testDir = await mkdtemp(join(tmpdir(), 'claudetree-session-test-'));
11
+ repo = new FileSessionRepository(testDir);
12
+ });
13
+ afterEach(async () => {
14
+ await rm(testDir, { recursive: true, force: true });
15
+ });
16
+ const createTestSession = (overrides = {}) => ({
17
+ id: 'test-session-1',
18
+ worktreeId: 'worktree-1',
19
+ claudeSessionId: null,
20
+ status: 'pending',
21
+ issueNumber: null,
22
+ prompt: 'Test prompt',
23
+ createdAt: new Date('2024-01-01'),
24
+ updatedAt: new Date('2024-01-01'),
25
+ ...overrides,
26
+ });
27
+ describe('save and findById', () => {
28
+ it('should save and retrieve a session', async () => {
29
+ const session = createTestSession();
30
+ await repo.save(session);
31
+ const found = await repo.findById(session.id);
32
+ expect(found).not.toBeNull();
33
+ expect(found?.id).toBe(session.id);
34
+ expect(found?.worktreeId).toBe(session.worktreeId);
35
+ expect(found?.status).toBe(session.status);
36
+ });
37
+ it('should return null for non-existent session', async () => {
38
+ const found = await repo.findById('non-existent');
39
+ expect(found).toBeNull();
40
+ });
41
+ });
42
+ describe('findByWorktreeId', () => {
43
+ it('should find session by worktree id', async () => {
44
+ const session = createTestSession({ worktreeId: 'wt-123' });
45
+ await repo.save(session);
46
+ const found = await repo.findByWorktreeId('wt-123');
47
+ expect(found).not.toBeNull();
48
+ expect(found?.worktreeId).toBe('wt-123');
49
+ });
50
+ });
51
+ describe('findAll', () => {
52
+ it('should return all sessions', async () => {
53
+ await repo.save(createTestSession({ id: 's1' }));
54
+ await repo.save(createTestSession({ id: 's2' }));
55
+ await repo.save(createTestSession({ id: 's3' }));
56
+ const all = await repo.findAll();
57
+ expect(all).toHaveLength(3);
58
+ });
59
+ it('should return empty array when no sessions', async () => {
60
+ const all = await repo.findAll();
61
+ expect(all).toEqual([]);
62
+ });
63
+ });
64
+ describe('delete', () => {
65
+ it('should delete a session', async () => {
66
+ const session = createTestSession();
67
+ await repo.save(session);
68
+ await repo.delete(session.id);
69
+ const found = await repo.findById(session.id);
70
+ expect(found).toBeNull();
71
+ });
72
+ });
73
+ describe('update', () => {
74
+ it('should update existing session', async () => {
75
+ const session = createTestSession();
76
+ await repo.save(session);
77
+ const updated = { ...session, status: 'running' };
78
+ await repo.save(updated);
79
+ const found = await repo.findById(session.id);
80
+ expect(found?.status).toBe('running');
81
+ });
82
+ });
83
+ });
84
+ //# sourceMappingURL=FileSessionRepository.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSessionRepository.test.js","sourceRoot":"","sources":["../../../src/infra/storage/FileSessionRepository.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ApprovalStatus, ToolApproval } from '@claudetree/shared';
2
+ import type { IToolApprovalRepository } from '../../domain/repositories/IToolApprovalRepository.js';
3
+ export declare class FileToolApprovalRepository implements IToolApprovalRepository {
4
+ private readonly approvalsDir;
5
+ constructor(configDir: string);
6
+ findBySessionId(sessionId: string): Promise<ToolApproval[]>;
7
+ findPending(sessionId: string): Promise<ToolApproval[]>;
8
+ save(approval: ToolApproval): Promise<void>;
9
+ updateStatus(id: string, status: ApprovalStatus, approvedBy?: string): Promise<void>;
10
+ private getFilePath;
11
+ private getAllSessionIds;
12
+ private loadApprovals;
13
+ private saveApprovals;
14
+ private serialize;
15
+ private deserialize;
16
+ }
17
+ //# sourceMappingURL=FileToolApprovalRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileToolApprovalRepository.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/FileToolApprovalRepository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAEd,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sDAAsD,CAAC;AAIpG,qBAAa,0BAA2B,YAAW,uBAAuB;IACxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,SAAS,EAAE,MAAM;IAIvB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAI3D,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAKvD,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3C,YAAY,CAChB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,cAAc,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAqBhB,OAAO,CAAC,WAAW;YAIL,gBAAgB;YAYhB,aAAa;YAUb,aAAa;IAS3B,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,WAAW;CAOpB"}
@@ -0,0 +1,90 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const APPROVALS_DIR = 'approvals';
4
+ export class FileToolApprovalRepository {
5
+ approvalsDir;
6
+ constructor(configDir) {
7
+ this.approvalsDir = join(configDir, APPROVALS_DIR);
8
+ }
9
+ async findBySessionId(sessionId) {
10
+ return this.loadApprovals(sessionId);
11
+ }
12
+ async findPending(sessionId) {
13
+ const approvals = await this.loadApprovals(sessionId);
14
+ return approvals.filter((a) => a.status === 'pending');
15
+ }
16
+ async save(approval) {
17
+ const approvals = await this.loadApprovals(approval.sessionId);
18
+ const index = approvals.findIndex((a) => a.id === approval.id);
19
+ if (index >= 0) {
20
+ approvals[index] = approval;
21
+ }
22
+ else {
23
+ approvals.push(approval);
24
+ }
25
+ await this.saveApprovals(approval.sessionId, approvals);
26
+ }
27
+ async updateStatus(id, status, approvedBy) {
28
+ // Find which session has this approval
29
+ const allFiles = await this.getAllSessionIds();
30
+ for (const sessionId of allFiles) {
31
+ const approvals = await this.loadApprovals(sessionId);
32
+ const index = approvals.findIndex((a) => a.id === id);
33
+ if (index >= 0) {
34
+ const approval = approvals[index];
35
+ if (approval) {
36
+ approval.status = status;
37
+ approval.approvedBy = approvedBy ?? null;
38
+ approval.resolvedAt = new Date();
39
+ await this.saveApprovals(sessionId, approvals);
40
+ }
41
+ return;
42
+ }
43
+ }
44
+ }
45
+ getFilePath(sessionId) {
46
+ return join(this.approvalsDir, `${sessionId}.json`);
47
+ }
48
+ async getAllSessionIds() {
49
+ try {
50
+ const { readdir } = await import('node:fs/promises');
51
+ const files = await readdir(this.approvalsDir);
52
+ return files
53
+ .filter((f) => f.endsWith('.json'))
54
+ .map((f) => f.replace('.json', ''));
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ }
60
+ async loadApprovals(sessionId) {
61
+ try {
62
+ const content = await readFile(this.getFilePath(sessionId), 'utf-8');
63
+ const data = JSON.parse(content);
64
+ return data.map(this.deserialize);
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ async saveApprovals(sessionId, approvals) {
71
+ await mkdir(this.approvalsDir, { recursive: true });
72
+ const data = approvals.map(this.serialize);
73
+ await writeFile(this.getFilePath(sessionId), JSON.stringify(data, null, 2));
74
+ }
75
+ serialize(approval) {
76
+ return {
77
+ ...approval,
78
+ requestedAt: approval.requestedAt.toISOString(),
79
+ resolvedAt: approval.resolvedAt?.toISOString() ?? null,
80
+ };
81
+ }
82
+ deserialize(data) {
83
+ return {
84
+ ...data,
85
+ requestedAt: new Date(data.requestedAt),
86
+ resolvedAt: data.resolvedAt ? new Date(data.resolvedAt) : null,
87
+ };
88
+ }
89
+ }
90
+ //# sourceMappingURL=FileToolApprovalRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileToolApprovalRepository.js","sourceRoot":"","sources":["../../../src/infra/storage/FileToolApprovalRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { FileSessionRepository } from './FileSessionRepository.js';
2
+ export { FileEventRepository } from './FileEventRepository.js';
3
+ export { FileToolApprovalRepository } from './FileToolApprovalRepository.js';
4
+ export { FileCodeReviewRepository } from './FileCodeReviewRepository.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { FileSessionRepository } from './FileSessionRepository.js';
2
+ export { FileEventRepository } from './FileEventRepository.js';
3
+ export { FileToolApprovalRepository } from './FileToolApprovalRepository.js';
4
+ export { FileCodeReviewRepository } from './FileCodeReviewRepository.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,15 @@
1
+ export type EventType = 'session:started' | 'session:updated' | 'session:stopped' | 'session:output' | 'worktree:created' | 'worktree:deleted' | 'event:created' | 'approval:requested' | 'approval:resolved' | 'review:requested' | 'review:resolved';
2
+ export interface WSMessage {
3
+ type: EventType;
4
+ payload: unknown;
5
+ timestamp: Date;
6
+ }
7
+ export declare class WebSocketBroadcaster {
8
+ private wss;
9
+ private clients;
10
+ constructor(port: number);
11
+ broadcast(message: WSMessage): void;
12
+ close(): void;
13
+ get clientCount(): number;
14
+ }
15
+ //# sourceMappingURL=WebSocketServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketServer.d.ts","sourceRoot":"","sources":["../../../src/infra/websocket/WebSocketServer.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,eAAe,GACf,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,GAClB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,OAAO,CAAwB;gBAE3B,IAAI,EAAE,MAAM;IAyBxB,SAAS,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAanC,KAAK,IAAI,IAAI;IAOb,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}
@@ -0,0 +1,45 @@
1
+ import { WebSocketServer as WSServer, WebSocket } from 'ws';
2
+ export class WebSocketBroadcaster {
3
+ wss;
4
+ clients = new Set();
5
+ constructor(port) {
6
+ this.wss = new WSServer({ port });
7
+ this.wss.on('connection', (ws) => {
8
+ this.clients.add(ws);
9
+ console.log(`WebSocket client connected (${this.clients.size} total)`);
10
+ ws.on('close', () => {
11
+ this.clients.delete(ws);
12
+ console.log(`WebSocket client disconnected (${this.clients.size} total)`);
13
+ });
14
+ ws.on('error', (err) => {
15
+ console.error('WebSocket error:', err.message);
16
+ this.clients.delete(ws);
17
+ });
18
+ });
19
+ this.wss.on('error', (err) => {
20
+ console.error('WebSocket server error:', err.message);
21
+ });
22
+ console.log(`WebSocket server listening on port ${port}`);
23
+ }
24
+ broadcast(message) {
25
+ const data = JSON.stringify({
26
+ ...message,
27
+ timestamp: message.timestamp.toISOString(),
28
+ });
29
+ for (const client of this.clients) {
30
+ if (client.readyState === WebSocket.OPEN) {
31
+ client.send(data);
32
+ }
33
+ }
34
+ }
35
+ close() {
36
+ for (const client of this.clients) {
37
+ client.close();
38
+ }
39
+ this.wss.close();
40
+ }
41
+ get clientCount() {
42
+ return this.clients.size;
43
+ }
44
+ }
45
+ //# sourceMappingURL=WebSocketServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketServer.js","sourceRoot":"","sources":["../../../src/infra/websocket/WebSocketServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,QAAQ,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAqB5D,MAAM,OAAO,oBAAoB;IACvB,GAAG,CAAW;IACd,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IAEvC,YAAY,IAAY;QACtB,IAAI,CAAC,GAAG,GAAG,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAElC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;YAC/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,+BAA+B,IAAI,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,CAAC;YAEvE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBAClB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,CAAC;YAC5E,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACrB,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC3B,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,sCAAsC,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,SAAS,CAAC,OAAkB;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,GAAG,OAAO;YACV,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;SAC3C,CAAC,CAAC;QAEH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK;QACH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;CACF"}