projex-cli 0.2.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 (85) hide show
  1. package/dist/cli.d.ts +6 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +321 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/event-bus.d.ts +35 -0
  6. package/dist/event-bus.d.ts.map +1 -0
  7. package/dist/event-bus.js +71 -0
  8. package/dist/event-bus.js.map +1 -0
  9. package/dist/events/event-bus.d.ts +35 -0
  10. package/dist/events/event-bus.d.ts.map +1 -0
  11. package/dist/events/event-bus.js +71 -0
  12. package/dist/events/event-bus.js.map +1 -0
  13. package/dist/events/index.d.ts +3 -0
  14. package/dist/events/index.d.ts.map +1 -0
  15. package/dist/events/index.js +3 -0
  16. package/dist/events/index.js.map +1 -0
  17. package/dist/events/types.d.ts +143 -0
  18. package/dist/events/types.d.ts.map +1 -0
  19. package/dist/events/types.js +6 -0
  20. package/dist/events/types.js.map +1 -0
  21. package/dist/index.d.ts +11 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/inference/enricher.d.ts +18 -0
  26. package/dist/inference/enricher.d.ts.map +1 -0
  27. package/dist/inference/enricher.js +290 -0
  28. package/dist/inference/enricher.js.map +1 -0
  29. package/dist/inference/index.d.ts +5 -0
  30. package/dist/inference/index.d.ts.map +1 -0
  31. package/dist/inference/index.js +5 -0
  32. package/dist/inference/index.js.map +1 -0
  33. package/dist/inference/lifecycle-tracker.d.ts +40 -0
  34. package/dist/inference/lifecycle-tracker.d.ts.map +1 -0
  35. package/dist/inference/lifecycle-tracker.js +132 -0
  36. package/dist/inference/lifecycle-tracker.js.map +1 -0
  37. package/dist/inference/portfolio-generator.d.ts +44 -0
  38. package/dist/inference/portfolio-generator.d.ts.map +1 -0
  39. package/dist/inference/portfolio-generator.js +239 -0
  40. package/dist/inference/portfolio-generator.js.map +1 -0
  41. package/dist/inference/project-detector.d.ts +17 -0
  42. package/dist/inference/project-detector.d.ts.map +1 -0
  43. package/dist/inference/project-detector.js +127 -0
  44. package/dist/inference/project-detector.js.map +1 -0
  45. package/dist/ingestion/github-client.d.ts +56 -0
  46. package/dist/ingestion/github-client.d.ts.map +1 -0
  47. package/dist/ingestion/github-client.js +213 -0
  48. package/dist/ingestion/github-client.js.map +1 -0
  49. package/dist/ingestion/github-poller.d.ts +41 -0
  50. package/dist/ingestion/github-poller.d.ts.map +1 -0
  51. package/dist/ingestion/github-poller.js +121 -0
  52. package/dist/ingestion/github-poller.js.map +1 -0
  53. package/dist/ingestion/normalizer.d.ts +41 -0
  54. package/dist/ingestion/normalizer.d.ts.map +1 -0
  55. package/dist/ingestion/normalizer.js +173 -0
  56. package/dist/ingestion/normalizer.js.map +1 -0
  57. package/dist/integrations/analyzer.d.ts +74 -0
  58. package/dist/integrations/analyzer.d.ts.map +1 -0
  59. package/dist/integrations/analyzer.js +287 -0
  60. package/dist/integrations/analyzer.js.map +1 -0
  61. package/dist/integrations/index.d.ts +4 -0
  62. package/dist/integrations/index.d.ts.map +1 -0
  63. package/dist/integrations/index.js +5 -0
  64. package/dist/integrations/index.js.map +1 -0
  65. package/dist/integrations/injectors.d.ts +60 -0
  66. package/dist/integrations/injectors.d.ts.map +1 -0
  67. package/dist/integrations/injectors.js +361 -0
  68. package/dist/integrations/injectors.js.map +1 -0
  69. package/dist/integrations/template-generator.d.ts +45 -0
  70. package/dist/integrations/template-generator.d.ts.map +1 -0
  71. package/dist/integrations/template-generator.js +187 -0
  72. package/dist/integrations/template-generator.js.map +1 -0
  73. package/dist/storage/index.d.ts +2 -0
  74. package/dist/storage/index.d.ts.map +1 -0
  75. package/dist/storage/index.js +2 -0
  76. package/dist/storage/index.js.map +1 -0
  77. package/dist/storage/project-store.d.ts +23 -0
  78. package/dist/storage/project-store.d.ts.map +1 -0
  79. package/dist/storage/project-store.js +107 -0
  80. package/dist/storage/project-store.js.map +1 -0
  81. package/dist/types.d.ts +143 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +6 -0
  84. package/dist/types.js.map +1 -0
  85. package/package.json +55 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * GitHub API client wrapper using Octokit.
3
+ * Provides typed access to repository data and events.
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ export class GitHubClient {
7
+ octokit;
8
+ username;
9
+ constructor(token, username) {
10
+ this.octokit = new Octokit({ auth: token });
11
+ this.username = username;
12
+ }
13
+ /**
14
+ * Fetch all repositories for the configured user
15
+ */
16
+ async listUserRepos() {
17
+ const repos = [];
18
+ // Paginate through all repos
19
+ for await (const response of this.octokit.paginate.iterator(this.octokit.repos.listForAuthenticatedUser, { per_page: 100, sort: 'updated' })) {
20
+ for (const repo of response.data) {
21
+ repos.push({
22
+ id: repo.id,
23
+ name: repo.name,
24
+ fullName: repo.full_name,
25
+ owner: repo.owner?.login ?? this.username,
26
+ url: repo.html_url,
27
+ description: repo.description,
28
+ topics: repo.topics ?? [],
29
+ isPrivate: repo.private,
30
+ isArchived: repo.archived ?? false,
31
+ defaultBranch: repo.default_branch,
32
+ createdAt: new Date(repo.created_at),
33
+ updatedAt: new Date(repo.updated_at),
34
+ pushedAt: repo.pushed_at ? new Date(repo.pushed_at) : null,
35
+ starCount: repo.stargazers_count ?? 0,
36
+ forkCount: repo.forks_count ?? 0,
37
+ });
38
+ }
39
+ }
40
+ return repos;
41
+ }
42
+ /**
43
+ * Get detailed repo info including topics
44
+ */
45
+ async getRepo(owner, repo) {
46
+ const { data } = await this.octokit.repos.get({ owner, repo });
47
+ return {
48
+ id: data.id,
49
+ name: data.name,
50
+ fullName: data.full_name,
51
+ owner: data.owner.login,
52
+ url: data.html_url,
53
+ description: data.description,
54
+ topics: data.topics ?? [],
55
+ isPrivate: data.private,
56
+ isArchived: data.archived,
57
+ defaultBranch: data.default_branch,
58
+ createdAt: new Date(data.created_at),
59
+ updatedAt: new Date(data.updated_at),
60
+ pushedAt: data.pushed_at ? new Date(data.pushed_at) : null,
61
+ starCount: data.stargazers_count ?? 0,
62
+ forkCount: data.forks_count ?? 0,
63
+ };
64
+ }
65
+ /**
66
+ * Fetch enrichment data: languages, readme, package files
67
+ */
68
+ async getEnrichmentData(owner, repo) {
69
+ const [languages, readme, packageFiles] = await Promise.all([
70
+ this.getLanguages(owner, repo),
71
+ this.getReadme(owner, repo),
72
+ this.getPackageFiles(owner, repo),
73
+ ]);
74
+ return { languages, readme, packageFiles };
75
+ }
76
+ async getLanguages(owner, repo) {
77
+ try {
78
+ const { data } = await this.octokit.repos.listLanguages({ owner, repo });
79
+ return data;
80
+ }
81
+ catch {
82
+ return {};
83
+ }
84
+ }
85
+ async getReadme(owner, repo) {
86
+ try {
87
+ const { data } = await this.octokit.repos.getReadme({ owner, repo });
88
+ // Decode base64 content
89
+ const content = Buffer.from(data.content, 'base64').toString('utf-8');
90
+ return content;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ async getPackageFiles(owner, repo) {
97
+ const packageFiles = [];
98
+ // Check for common package files
99
+ const filesToCheck = [
100
+ { path: 'package.json', type: 'npm' },
101
+ { path: 'Cargo.toml', type: 'cargo' },
102
+ { path: 'requirements.txt', type: 'pip' },
103
+ { path: 'pyproject.toml', type: 'pip' },
104
+ { path: 'go.mod', type: 'go' },
105
+ { path: 'Gemfile', type: 'gem' },
106
+ { path: 'composer.json', type: 'composer' },
107
+ ];
108
+ for (const file of filesToCheck) {
109
+ try {
110
+ const { data } = await this.octokit.repos.getContent({
111
+ owner,
112
+ repo,
113
+ path: file.path,
114
+ });
115
+ if ('content' in data) {
116
+ const content = Buffer.from(data.content, 'base64').toString('utf-8');
117
+ const parsed = this.parsePackageFile(file.type, content);
118
+ if (parsed) {
119
+ packageFiles.push({
120
+ type: file.type,
121
+ filename: file.path,
122
+ dependencies: parsed.dependencies,
123
+ devDependencies: parsed.devDependencies,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ catch {
129
+ // File doesn't exist, skip
130
+ }
131
+ }
132
+ return packageFiles;
133
+ }
134
+ parsePackageFile(type, content) {
135
+ try {
136
+ switch (type) {
137
+ case 'npm': {
138
+ const pkg = JSON.parse(content);
139
+ return {
140
+ dependencies: Object.keys(pkg.dependencies ?? {}),
141
+ devDependencies: Object.keys(pkg.devDependencies ?? {}),
142
+ };
143
+ }
144
+ case 'pip': {
145
+ // Simple requirements.txt parsing
146
+ const deps = content
147
+ .split('\n')
148
+ .map(line => line.trim())
149
+ .filter(line => line && !line.startsWith('#'))
150
+ .map(line => line.split(/[=<>!]/)[0].trim());
151
+ return { dependencies: deps, devDependencies: [] };
152
+ }
153
+ case 'cargo': {
154
+ // Basic Cargo.toml parsing (extract dependency names)
155
+ const depMatches = content.match(/^\[dependencies\][\s\S]*?(?=\n\[|$)/m);
156
+ const deps = [];
157
+ if (depMatches) {
158
+ const lines = depMatches[0].split('\n');
159
+ for (const line of lines) {
160
+ const match = line.match(/^(\w[\w-]*)\s*=/);
161
+ if (match)
162
+ deps.push(match[1]);
163
+ }
164
+ }
165
+ return { dependencies: deps, devDependencies: [] };
166
+ }
167
+ case 'go': {
168
+ // Extract require statements from go.mod
169
+ const deps = [];
170
+ const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
171
+ if (requireBlock) {
172
+ const lines = requireBlock[1].split('\n');
173
+ for (const line of lines) {
174
+ const match = line.trim().match(/^([^\s]+)\s+/);
175
+ if (match)
176
+ deps.push(match[1]);
177
+ }
178
+ }
179
+ return { dependencies: deps, devDependencies: [] };
180
+ }
181
+ default:
182
+ return { dependencies: [], devDependencies: [] };
183
+ }
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ /**
190
+ * Get recent commits for activity tracking
191
+ */
192
+ async getRecentCommits(owner, repo, since) {
193
+ try {
194
+ const params = {
195
+ owner,
196
+ repo,
197
+ per_page: 30,
198
+ };
199
+ if (since) {
200
+ params.since = since.toISOString();
201
+ }
202
+ const { data } = await this.octokit.repos.listCommits(params);
203
+ return data.map(commit => ({
204
+ sha: commit.sha,
205
+ date: new Date(commit.commit.committer?.date ?? commit.commit.author?.date ?? new Date()),
206
+ }));
207
+ }
208
+ catch {
209
+ return [];
210
+ }
211
+ }
212
+ }
213
+ //# sourceMappingURL=github-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-client.js","sourceRoot":"","sources":["../../src/ingestion/github-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * GitHub Poller - periodically fetches repos and emits change events.
3
+ * Uses Normalizer to detect changes and EventBus to publish events.
4
+ */
5
+ import { Normalizer } from './normalizer.js';
6
+ import { EventBus } from '../events/event-bus.js';
7
+ import type { Config, SignalEvent } from '../events/types.js';
8
+ export declare class GitHubPoller {
9
+ private client;
10
+ private normalizer;
11
+ private eventBus;
12
+ private config;
13
+ private pollInterval;
14
+ private isPolling;
15
+ constructor(config: Config, eventBus: EventBus);
16
+ /**
17
+ * Start periodic polling
18
+ */
19
+ start(): void;
20
+ /**
21
+ * Stop polling
22
+ */
23
+ stop(): void;
24
+ /**
25
+ * Manual poll trigger (useful for testing)
26
+ */
27
+ poll(): Promise<SignalEvent[]>;
28
+ /**
29
+ * Enrich a specific repo with detailed data
30
+ */
31
+ private enrichRepo;
32
+ /**
33
+ * Force enrichment for a specific repo by ID
34
+ */
35
+ enrichRepoById(repoId: string): Promise<void>;
36
+ /**
37
+ * Get normalizer for snapshot persistence
38
+ */
39
+ getNormalizer(): Normalizer;
40
+ }
41
+ //# sourceMappingURL=github-poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-poller.d.ts","sourceRoot":"","sources":["../../src/ingestion/github-poller.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE9D,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAO9C;;OAEG;IACH,KAAK,IAAI,IAAI;IAgBb;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IA0CpC;;OAEG;YACW,UAAU;IAexB;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD;;OAEG;IACH,aAAa,IAAI,UAAU;CAG9B"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * GitHub Poller - periodically fetches repos and emits change events.
3
+ * Uses Normalizer to detect changes and EventBus to publish events.
4
+ */
5
+ import { GitHubClient } from './github-client.js';
6
+ import { Normalizer } from './normalizer.js';
7
+ export class GitHubPoller {
8
+ client;
9
+ normalizer;
10
+ eventBus;
11
+ config;
12
+ pollInterval = null;
13
+ isPolling = false;
14
+ constructor(config, eventBus) {
15
+ this.config = config;
16
+ this.client = new GitHubClient(config.github.token, config.github.username);
17
+ this.normalizer = new Normalizer();
18
+ this.eventBus = eventBus;
19
+ }
20
+ /**
21
+ * Start periodic polling
22
+ */
23
+ start() {
24
+ if (this.pollInterval) {
25
+ console.log('[GitHubPoller] Already running');
26
+ return;
27
+ }
28
+ const intervalMs = this.config.github.pollingIntervalMinutes * 60 * 1000;
29
+ console.log(`[GitHubPoller] Starting with ${this.config.github.pollingIntervalMinutes}min interval`);
30
+ // Run immediately on start
31
+ this.poll();
32
+ // Then set up interval
33
+ this.pollInterval = setInterval(() => this.poll(), intervalMs);
34
+ }
35
+ /**
36
+ * Stop polling
37
+ */
38
+ stop() {
39
+ if (this.pollInterval) {
40
+ clearInterval(this.pollInterval);
41
+ this.pollInterval = null;
42
+ console.log('[GitHubPoller] Stopped');
43
+ }
44
+ }
45
+ /**
46
+ * Manual poll trigger (useful for testing)
47
+ */
48
+ async poll() {
49
+ if (this.isPolling) {
50
+ console.log('[GitHubPoller] Poll already in progress, skipping');
51
+ return [];
52
+ }
53
+ this.isPolling = true;
54
+ const allEvents = [];
55
+ try {
56
+ console.log('[GitHubPoller] Fetching repositories...');
57
+ const repos = await this.client.listUserRepos();
58
+ console.log(`[GitHubPoller] Found ${repos.length} repositories`);
59
+ // Detect changes
60
+ const changeEvents = this.normalizer.detectChanges(repos);
61
+ console.log(`[GitHubPoller] Detected ${changeEvents.length} change events`);
62
+ // Publish events
63
+ for (const event of changeEvents) {
64
+ await this.eventBus.publish(event);
65
+ allEvents.push(event);
66
+ }
67
+ // Enrichment for new repos (repo.created events)
68
+ const newRepoEvents = changeEvents.filter(e => e.eventType === 'repo.created');
69
+ for (const event of newRepoEvents) {
70
+ const repo = repos.find(r => `github:${r.fullName}` === event.repo.id);
71
+ if (repo) {
72
+ await this.enrichRepo(repo, allEvents);
73
+ }
74
+ }
75
+ }
76
+ catch (error) {
77
+ console.error('[GitHubPoller] Error during poll:', error);
78
+ }
79
+ finally {
80
+ this.isPolling = false;
81
+ }
82
+ return allEvents;
83
+ }
84
+ /**
85
+ * Enrich a specific repo with detailed data
86
+ */
87
+ async enrichRepo(repo, allEvents) {
88
+ try {
89
+ console.log(`[GitHubPoller] Enriching ${repo.fullName}...`);
90
+ const enrichmentData = await this.client.getEnrichmentData(repo.owner, repo.name);
91
+ const enrichmentEvent = this.normalizer.createEnrichmentEvent(repo, enrichmentData);
92
+ await this.eventBus.publish(enrichmentEvent);
93
+ allEvents.push(enrichmentEvent);
94
+ }
95
+ catch (error) {
96
+ console.error(`[GitHubPoller] Error enriching ${repo.fullName}:`, error);
97
+ }
98
+ }
99
+ /**
100
+ * Force enrichment for a specific repo by ID
101
+ */
102
+ async enrichRepoById(repoId) {
103
+ // Parse "github:owner/repo" format
104
+ const match = repoId.match(/^github:(.+)\/(.+)$/);
105
+ if (!match) {
106
+ throw new Error(`Invalid repo ID format: ${repoId}`);
107
+ }
108
+ const [, owner, name] = match;
109
+ const repo = await this.client.getRepo(owner, name);
110
+ const enrichmentData = await this.client.getEnrichmentData(owner, name);
111
+ const enrichmentEvent = this.normalizer.createEnrichmentEvent(repo, enrichmentData);
112
+ await this.eventBus.publish(enrichmentEvent);
113
+ }
114
+ /**
115
+ * Get normalizer for snapshot persistence
116
+ */
117
+ getNormalizer() {
118
+ return this.normalizer;
119
+ }
120
+ }
121
+ //# sourceMappingURL=github-poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-poller.js","sourceRoot":"","sources":["../../src/ingestion/github-poller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Normalizer transforms raw GitHub data into normalized SignalEvents.
3
+ * Compares current state with previous state to detect changes.
4
+ */
5
+ import type { SignalEvent, RepoEnrichedEvent } from '../events/types.js';
6
+ import type { GitHubRepoData, GitHubEnrichmentData } from './github-client.js';
7
+ export interface RepoSnapshot {
8
+ id: string;
9
+ description: string | null;
10
+ topics: string[];
11
+ isArchived: boolean;
12
+ pushedAt: Date | null;
13
+ updatedAt: Date;
14
+ }
15
+ export declare class Normalizer {
16
+ private previousSnapshots;
17
+ /**
18
+ * Compare current repo data with previous snapshot and emit change events
19
+ */
20
+ detectChanges(repos: GitHubRepoData[]): SignalEvent[];
21
+ /**
22
+ * Create enrichment event from enrichment data
23
+ */
24
+ createEnrichmentEvent(repo: GitHubRepoData, enrichment: GitHubEnrichmentData): RepoEnrichedEvent;
25
+ /**
26
+ * Load previous snapshots (for persistence across restarts)
27
+ */
28
+ loadSnapshots(snapshots: RepoSnapshot[]): void;
29
+ /**
30
+ * Export current snapshots for persistence
31
+ */
32
+ exportSnapshots(): RepoSnapshot[];
33
+ private hasPushActivity;
34
+ private topicsChanged;
35
+ private createRepoCreatedEvent;
36
+ private createRepoPushedEvent;
37
+ private createTopicsChangedEvent;
38
+ private createDescriptionChangedEvent;
39
+ private createArchivedEvent;
40
+ }
41
+ //# sourceMappingURL=normalizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../src/ingestion/normalizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACR,WAAW,EAMX,iBAAiB,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;CACnB;AAED,qBAAa,UAAU;IACnB,OAAO,CAAC,iBAAiB,CAAwC;IAEjE;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,WAAW,EAAE;IAqDrD;;OAEG;IACH,qBAAqB,CACjB,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,oBAAoB,GACjC,iBAAiB;IAuBpB;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI;IAO9C;;OAEG;IACH,eAAe,IAAI,YAAY,EAAE;IAIjC,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,sBAAsB;IAkB9B,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,wBAAwB;IAkBhC,OAAO,CAAC,6BAA6B;IAkBrC,OAAO,CAAC,mBAAmB;CAe9B"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Normalizer transforms raw GitHub data into normalized SignalEvents.
3
+ * Compares current state with previous state to detect changes.
4
+ */
5
+ import { randomUUID } from 'crypto';
6
+ export class Normalizer {
7
+ previousSnapshots = new Map();
8
+ /**
9
+ * Compare current repo data with previous snapshot and emit change events
10
+ */
11
+ detectChanges(repos) {
12
+ const events = [];
13
+ const currentRepoIds = new Set();
14
+ for (const repo of repos) {
15
+ const repoId = `github:${repo.fullName}`;
16
+ currentRepoIds.add(repoId);
17
+ const repoIdentifier = {
18
+ id: repoId,
19
+ name: repo.name,
20
+ url: repo.url,
21
+ owner: repo.owner,
22
+ };
23
+ const previous = this.previousSnapshots.get(repoId);
24
+ if (!previous) {
25
+ // New repo detected
26
+ events.push(this.createRepoCreatedEvent(repo, repoIdentifier));
27
+ }
28
+ else {
29
+ // Check for changes
30
+ if (this.hasPushActivity(repo, previous)) {
31
+ events.push(this.createRepoPushedEvent(repo, repoIdentifier));
32
+ }
33
+ if (this.topicsChanged(repo, previous)) {
34
+ events.push(this.createTopicsChangedEvent(repo, previous, repoIdentifier));
35
+ }
36
+ if (repo.description !== previous.description) {
37
+ events.push(this.createDescriptionChangedEvent(repo, previous, repoIdentifier));
38
+ }
39
+ if (repo.isArchived && !previous.isArchived) {
40
+ events.push(this.createArchivedEvent(repo, repoIdentifier));
41
+ }
42
+ }
43
+ // Update snapshot
44
+ this.previousSnapshots.set(repoId, {
45
+ id: repoId,
46
+ description: repo.description,
47
+ topics: [...repo.topics],
48
+ isArchived: repo.isArchived,
49
+ pushedAt: repo.pushedAt,
50
+ updatedAt: repo.updatedAt,
51
+ });
52
+ }
53
+ return events;
54
+ }
55
+ /**
56
+ * Create enrichment event from enrichment data
57
+ */
58
+ createEnrichmentEvent(repo, enrichment) {
59
+ return {
60
+ eventId: randomUUID(),
61
+ eventType: 'repo.enriched',
62
+ timestamp: new Date(),
63
+ source: 'github',
64
+ repo: {
65
+ id: `github:${repo.fullName}`,
66
+ name: repo.name,
67
+ url: repo.url,
68
+ owner: repo.owner,
69
+ },
70
+ payload: {
71
+ languages: enrichment.languages,
72
+ readme: enrichment.readme,
73
+ packageFiles: enrichment.packageFiles,
74
+ defaultBranch: repo.defaultBranch,
75
+ starCount: repo.starCount,
76
+ forkCount: repo.forkCount,
77
+ },
78
+ };
79
+ }
80
+ /**
81
+ * Load previous snapshots (for persistence across restarts)
82
+ */
83
+ loadSnapshots(snapshots) {
84
+ this.previousSnapshots.clear();
85
+ for (const snapshot of snapshots) {
86
+ this.previousSnapshots.set(snapshot.id, snapshot);
87
+ }
88
+ }
89
+ /**
90
+ * Export current snapshots for persistence
91
+ */
92
+ exportSnapshots() {
93
+ return Array.from(this.previousSnapshots.values());
94
+ }
95
+ hasPushActivity(current, previous) {
96
+ if (!current.pushedAt || !previous.pushedAt)
97
+ return false;
98
+ return current.pushedAt.getTime() > previous.pushedAt.getTime();
99
+ }
100
+ topicsChanged(current, previous) {
101
+ if (current.topics.length !== previous.topics.length)
102
+ return true;
103
+ const sortedCurrent = [...current.topics].sort();
104
+ const sortedPrevious = [...previous.topics].sort();
105
+ return sortedCurrent.some((topic, i) => topic !== sortedPrevious[i]);
106
+ }
107
+ createRepoCreatedEvent(repo, repoIdentifier) {
108
+ return {
109
+ eventId: randomUUID(),
110
+ eventType: 'repo.created',
111
+ timestamp: repo.createdAt,
112
+ source: 'github',
113
+ repo: repoIdentifier,
114
+ payload: {
115
+ description: repo.description,
116
+ topics: repo.topics,
117
+ isPrivate: repo.isPrivate,
118
+ },
119
+ };
120
+ }
121
+ createRepoPushedEvent(repo, repoIdentifier) {
122
+ return {
123
+ eventId: randomUUID(),
124
+ eventType: 'repo.pushed',
125
+ timestamp: repo.pushedAt ?? new Date(),
126
+ source: 'github',
127
+ repo: repoIdentifier,
128
+ payload: {
129
+ commitCount: 1, // Approximate, would need commit API for exact count
130
+ branch: repo.defaultBranch,
131
+ },
132
+ };
133
+ }
134
+ createTopicsChangedEvent(repo, previous, repoIdentifier) {
135
+ return {
136
+ eventId: randomUUID(),
137
+ eventType: 'repo.topics_changed',
138
+ timestamp: new Date(),
139
+ source: 'github',
140
+ repo: repoIdentifier,
141
+ payload: {
142
+ topics: repo.topics,
143
+ previousTopics: previous.topics,
144
+ },
145
+ };
146
+ }
147
+ createDescriptionChangedEvent(repo, previous, repoIdentifier) {
148
+ return {
149
+ eventId: randomUUID(),
150
+ eventType: 'repo.description_changed',
151
+ timestamp: new Date(),
152
+ source: 'github',
153
+ repo: repoIdentifier,
154
+ payload: {
155
+ description: repo.description,
156
+ previousDescription: previous.description,
157
+ },
158
+ };
159
+ }
160
+ createArchivedEvent(repo, repoIdentifier) {
161
+ return {
162
+ eventId: randomUUID(),
163
+ eventType: 'repo.archived',
164
+ timestamp: new Date(),
165
+ source: 'github',
166
+ repo: repoIdentifier,
167
+ payload: {
168
+ archivedAt: new Date(),
169
+ },
170
+ };
171
+ }
172
+ }
173
+ //# sourceMappingURL=normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.js","sourceRoot":"","sources":["../../src/ingestion/normalizer.ts"],"names":[],"mappings":"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"}