terminalhire 0.2.5 → 0.3.1

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.
@@ -16,221 +16,347 @@ var init_types = __esm({
16
16
  }
17
17
  });
18
18
 
19
- // ../../packages/core/src/vocabulary.ts
19
+ // ../../packages/core/src/vocab/graph.data.ts
20
+ var VOCAB_NODES;
21
+ var init_graph_data = __esm({
22
+ "../../packages/core/src/vocab/graph.data.ts"() {
23
+ "use strict";
24
+ VOCAB_NODES = [
25
+ // ── Languages ─────────────────────────────────────────────────────────────
26
+ { id: "javascript", synonyms: ["js"], related: [{ to: "typescript", w: 0.6 }] },
27
+ { id: "typescript", parents: ["javascript"], synonyms: ["ts"] },
28
+ { id: "python", synonyms: ["py"] },
29
+ { id: "go", synonyms: ["golang"] },
30
+ { id: "rust" },
31
+ { id: "java", related: [{ to: "kotlin", w: 0.45 }, { to: "scala", w: 0.4 }] },
32
+ { id: "ruby" },
33
+ { id: "elixir" },
34
+ { id: "scala", related: [{ to: "java", w: 0.4 }] },
35
+ { id: "kotlin", related: [{ to: "java", w: 0.45 }] },
36
+ { id: "swift" },
37
+ { id: "cpp", synonyms: ["c++"] },
38
+ { id: "csharp", synonyms: ["c#"] },
39
+ { id: "php" },
40
+ { id: "haskell" },
41
+ { id: "clojure" },
42
+ { id: "r" },
43
+ { id: "dart" },
44
+ // ── Frontend ──────────────────────────────────────────────────────────────
45
+ {
46
+ id: "react",
47
+ parents: ["javascript"],
48
+ synonyms: ["reactjs"],
49
+ related: [{ to: "nextjs", w: 0.55 }, { to: "vue", w: 0.4 }, { to: "svelte", w: 0.4 }, { to: "solidjs", w: 0.5 }, { to: "angular", w: 0.35 }]
50
+ },
51
+ { id: "nextjs", parents: ["react"], synonyms: ["next", "next.js"], related: [{ to: "remix", w: 0.5 }] },
52
+ { id: "vue", parents: ["javascript"], synonyms: ["vue.js"], related: [{ to: "nuxt", w: 0.6 }] },
53
+ { id: "nuxt", parents: ["vue"], synonyms: ["nuxt.js"] },
54
+ { id: "svelte", parents: ["javascript"], related: [{ to: "sveltekit", w: 0.65 }] },
55
+ { id: "sveltekit", parents: ["svelte"] },
56
+ { id: "angular", parents: ["typescript"], synonyms: ["angular.js", "angularjs"] },
57
+ { id: "solidjs", parents: ["javascript"] },
58
+ { id: "remix", parents: ["react"], synonyms: ["remix.run"] },
59
+ { id: "astro", parents: ["javascript"], related: [{ to: "nextjs", w: 0.4 }] },
60
+ { id: "qwik", parents: ["javascript"] },
61
+ { id: "tailwind", parents: ["css"], synonyms: ["tailwindcss", "tw"] },
62
+ { id: "css" },
63
+ { id: "html" },
64
+ { id: "redux", parents: ["react"] },
65
+ { id: "vite", parents: ["frontend"] },
66
+ { id: "webpack", parents: ["frontend"] },
67
+ { id: "storybook", parents: ["frontend"] },
68
+ // ── Backend frameworks / runtimes ───────────────────────────────────────────
69
+ {
70
+ id: "nodejs",
71
+ parents: ["javascript"],
72
+ synonyms: ["node", "node.js"],
73
+ related: [{ to: "express", w: 0.5 }, { to: "fastify", w: 0.45 }, { to: "nestjs", w: 0.45 }]
74
+ },
75
+ { id: "express", parents: ["nodejs"], synonyms: ["express.js", "expressjs"], related: [{ to: "fastify", w: 0.5 }] },
76
+ { id: "fastify", parents: ["nodejs"] },
77
+ { id: "nestjs", parents: ["nodejs"], synonyms: ["nest", "nest.js"] },
78
+ { id: "hono", parents: ["nodejs"] },
79
+ { id: "deno", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }, { to: "bun", w: 0.5 }] },
80
+ { id: "bun", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }] },
81
+ { id: "django", parents: ["python"], related: [{ to: "flask", w: 0.5 }, { to: "fastapi", w: 0.45 }] },
82
+ { id: "fastapi", parents: ["python"], related: [{ to: "flask", w: 0.55 }, { to: "django", w: 0.45 }] },
83
+ { id: "flask", parents: ["python"] },
84
+ { id: "rails", parents: ["ruby"], synonyms: ["ruby-on-rails", "ror"] },
85
+ { id: "spring", parents: ["java"], synonyms: ["spring-boot", "springboot"] },
86
+ { id: "actix", parents: ["rust"] },
87
+ { id: "gin", parents: ["go"] },
88
+ { id: "phoenix", parents: ["elixir"] },
89
+ { id: "laravel", parents: ["php"] },
90
+ { id: "dotnet", parents: ["csharp"], synonyms: [".net", "asp.net", "dotnet-core"] },
91
+ // ── Infrastructure & DevOps ─────────────────────────────────────────────────
92
+ { id: "kubernetes", synonyms: ["k8s", "kube"], related: [{ to: "docker", w: 0.5 }, { to: "helm", w: 0.55 }, { to: "terraform", w: 0.4 }, { to: "argocd", w: 0.45 }] },
93
+ { id: "docker", parents: ["devops"], related: [{ to: "kubernetes", w: 0.5 }] },
94
+ { id: "terraform", synonyms: ["tf"], related: [{ to: "pulumi", w: 0.55 }, { to: "ansible", w: 0.4 }, { to: "aws", w: 0.4 }] },
95
+ { id: "pulumi", related: [{ to: "terraform", w: 0.55 }] },
96
+ { id: "ansible" },
97
+ { id: "aws", synonyms: ["amazon-web-services"], related: [{ to: "gcp", w: 0.4 }, { to: "azure", w: 0.4 }] },
98
+ { id: "gcp", synonyms: ["google-cloud", "google-cloud-platform"], related: [{ to: "aws", w: 0.4 }, { to: "azure", w: 0.4 }] },
99
+ { id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] },
100
+ { id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] },
101
+ { id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] },
102
+ { id: "gitlab-ci", parents: ["ci-cd"], synonyms: ["gitlab"] },
103
+ { id: "linux" },
104
+ { id: "nginx" },
105
+ { id: "prometheus", parents: ["observability"], related: [{ to: "grafana", w: 0.6 }] },
106
+ { id: "grafana", parents: ["observability"] },
107
+ { id: "datadog", parents: ["observability"] },
108
+ { id: "opentelemetry", parents: ["observability"], synonyms: ["otel"] },
109
+ { id: "vercel", related: [{ to: "netlify", w: 0.5 }, { to: "nextjs", w: 0.4 }] },
110
+ { id: "netlify" },
111
+ { id: "fly", synonyms: ["fly.io"], related: [{ to: "railway", w: 0.5 }, { to: "render", w: 0.5 }] },
112
+ { id: "railway", related: [{ to: "render", w: 0.5 }] },
113
+ { id: "render" },
114
+ { id: "cloudflare", synonyms: ["cloudflare-workers"] },
115
+ { id: "helm", parents: ["kubernetes"] },
116
+ { id: "argocd", parents: ["kubernetes"] },
117
+ { id: "serverless", parents: ["devops"] },
118
+ // ── Databases & storage ─────────────────────────────────────────────────────
119
+ { id: "postgresql", synonyms: ["postgres", "pg"], related: [{ to: "mysql", w: 0.45 }, { to: "sqlite", w: 0.4 }] },
120
+ { id: "mysql", related: [{ to: "postgresql", w: 0.45 }] },
121
+ { id: "sqlite" },
122
+ { id: "mongodb", synonyms: ["mongo"] },
123
+ { id: "redis", related: [{ to: "caching", w: 0.5 }] },
124
+ { id: "elasticsearch", synonyms: ["elastic"], related: [{ to: "search", w: 0.55 }] },
125
+ { id: "kafka", synonyms: ["apache-kafka"], related: [{ to: "rabbitmq", w: 0.5 }, { to: "message-queue", w: 0.55 }] },
126
+ { id: "rabbitmq", related: [{ to: "message-queue", w: 0.55 }] },
127
+ { id: "cassandra" },
128
+ { id: "dynamodb", parents: ["aws"] },
129
+ { id: "snowflake", parents: ["data-engineering"], related: [{ to: "clickhouse", w: 0.4 }] },
130
+ { id: "clickhouse", parents: ["data-engineering"], related: [{ to: "duckdb", w: 0.35 }] },
131
+ { id: "duckdb", parents: ["data-engineering"] },
132
+ { id: "supabase", related: [{ to: "postgresql", w: 0.5 }, { to: "neon", w: 0.4 }] },
133
+ { id: "planetscale", related: [{ to: "mysql", w: 0.5 }] },
134
+ { id: "neon", related: [{ to: "postgresql", w: 0.5 }] },
135
+ { id: "turso", related: [{ to: "sqlite", w: 0.5 }] },
136
+ { id: "cockroachdb", related: [{ to: "postgresql", w: 0.45 }] },
137
+ { id: "prisma", parents: ["backend"], synonyms: ["@prisma/client"], related: [{ to: "drizzle", w: 0.5 }, { to: "typeorm", w: 0.45 }, { to: "sequelize", w: 0.4 }] },
138
+ { id: "drizzle", synonyms: ["drizzle-orm"], related: [{ to: "prisma", w: 0.5 }] },
139
+ { id: "sequelize", related: [{ to: "typeorm", w: 0.4 }] },
140
+ { id: "typeorm", related: [{ to: "prisma", w: 0.45 }] },
141
+ { id: "sqlalchemy", parents: ["python"] },
142
+ // ── Data engineering & ML ───────────────────────────────────────────────────
143
+ { id: "data-engineering", synonyms: ["data-eng"], related: [{ to: "spark", w: 0.5 }, { to: "airflow", w: 0.5 }, { to: "dbt", w: 0.45 }] },
144
+ { id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
145
+ { id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
146
+ { id: "dbt", parents: ["data-engineering"] },
147
+ { id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }] },
148
+ { id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
149
+ { id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
150
+ { id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
151
+ { id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }] },
152
+ { id: "numpy", parents: ["python"] },
153
+ { id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
154
+ { id: "jupyter", parents: ["python"] },
155
+ { id: "langchain", parents: ["llm"], synonyms: ["llamaindex"] },
156
+ { id: "huggingface", parents: ["ml"], synonyms: ["hugging-face"] },
157
+ { id: "openai", parents: ["llm"] },
158
+ { id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
159
+ { id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
160
+ { id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
161
+ // ── Mobile ──────────────────────────────────────────────────────────────────
162
+ { id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
163
+ { id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
164
+ { id: "android", parents: ["mobile"], related: [{ to: "kotlin", w: 0.4 }] },
165
+ { id: "swiftui", parents: ["ios", "swift"] },
166
+ { id: "react-native", parents: ["mobile", "react"], synonyms: ["reactnative"], related: [{ to: "flutter", w: 0.4 }, { to: "expo", w: 0.6 }] },
167
+ { id: "flutter", parents: ["mobile", "dart"] },
168
+ { id: "expo", parents: ["react-native"] },
169
+ { id: "kotlin-multiplatform", parents: ["mobile", "kotlin"], synonyms: ["kmp"] },
170
+ // ── Domains / capabilities ──────────────────────────────────────────────────
171
+ { id: "frontend", related: [{ to: "react", w: 0.4 }, { to: "css", w: 0.3 }] },
172
+ { id: "backend", related: [{ to: "api-design", w: 0.4 }, { to: "microservices", w: 0.4 }] },
173
+ { id: "devops", related: [{ to: "kubernetes", w: 0.4 }, { to: "ci-cd", w: 0.4 }, { to: "docker", w: 0.4 }] },
174
+ { id: "authentication", synonyms: ["auth", "jwt", "saml", "passport", "auth0", "clerk", "nextauth"], related: [{ to: "oauth", w: 0.6 }, { to: "security", w: 0.5 }] },
175
+ { id: "oauth", parents: ["authentication"], synonyms: ["oauth2", "oidc"], related: [{ to: "security", w: 0.4 }] },
176
+ { id: "security", related: [{ to: "authentication", w: 0.5 }] },
177
+ { id: "payments", synonyms: ["stripe", "braintree", "paddle", "lemonsqueezy", "@stripe/stripe-js"], related: [{ to: "billing", w: 0.6 }] },
178
+ { id: "billing", synonyms: ["recurly", "chargebee"] },
179
+ { id: "api-design", synonyms: ["rest", "restful", "rest-api"], related: [{ to: "graphql", w: 0.4 }, { to: "grpc", w: 0.4 }, { to: "backend", w: 0.4 }] },
180
+ { id: "graphql", synonyms: ["gql"], related: [{ to: "trpc", w: 0.4 }] },
181
+ { id: "trpc", related: [{ to: "graphql", w: 0.4 }] },
182
+ { id: "grpc", synonyms: ["grpc-web"], related: [{ to: "microservices", w: 0.3 }] },
183
+ { id: "microservices" },
184
+ { id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] },
185
+ { id: "realtime", synonyms: ["real-time"] },
186
+ { id: "message-queue", synonyms: ["mq"] },
187
+ { id: "caching", synonyms: ["cache"] },
188
+ { id: "search", synonyms: ["full-text-search"] },
189
+ { id: "observability", synonyms: ["o11y"], related: [{ to: "monitoring", w: 0.6 }] },
190
+ { id: "monitoring", related: [{ to: "prometheus", w: 0.4 }] },
191
+ { id: "testing", related: [{ to: "unit-testing", w: 0.5 }, { to: "e2e-testing", w: 0.5 }] },
192
+ { id: "unit-testing", parents: ["testing"] },
193
+ { id: "e2e-testing", parents: ["testing"], synonyms: ["e2e", "end-to-end-testing"] },
194
+ { id: "jest", parents: ["testing"], related: [{ to: "vitest", w: 0.6 }, { to: "mocha", w: 0.5 }] },
195
+ { id: "vitest", parents: ["testing"], related: [{ to: "jest", w: 0.6 }] },
196
+ { id: "playwright", parents: ["e2e-testing"], related: [{ to: "cypress", w: 0.6 }] },
197
+ { id: "cypress", parents: ["e2e-testing"] },
198
+ { id: "mocha", parents: ["testing"] },
199
+ { id: "pytest", parents: ["testing", "python"] },
200
+ { id: "accessibility", synonyms: ["a11y"] },
201
+ { id: "seo" },
202
+ { id: "performance", synonyms: ["perf", "web-performance"] }
203
+ ];
204
+ }
205
+ });
206
+
207
+ // ../../packages/core/src/vocab/closure.ts
208
+ function round3(n) {
209
+ return Math.round(n * 1e3) / 1e3;
210
+ }
211
+ function validateGraph(nodes) {
212
+ const ids = /* @__PURE__ */ new Set();
213
+ for (const n of nodes) {
214
+ if (ids.has(n.id)) throw new Error(`vocab: duplicate id "${n.id}"`);
215
+ ids.add(n.id);
216
+ }
217
+ const seenAlias = /* @__PURE__ */ new Map();
218
+ for (const n of nodes) {
219
+ for (const p of n.parents ?? []) {
220
+ if (p === n.id) throw new Error(`vocab: "${n.id}" lists itself as a parent`);
221
+ if (!ids.has(p)) throw new Error(`vocab: "${n.id}" parent "${p}" is not a defined id`);
222
+ }
223
+ for (const e of n.related ?? []) {
224
+ if (e.to === n.id) throw new Error(`vocab: "${n.id}" relates to itself`);
225
+ if (!ids.has(e.to)) throw new Error(`vocab: "${n.id}" related "${e.to}" is not a defined id`);
226
+ if (!(e.w > 0 && e.w <= 1)) throw new Error(`vocab: "${n.id}"\u2192"${e.to}" weight ${e.w} out of (0,1]`);
227
+ }
228
+ for (const s of n.synonyms ?? []) {
229
+ const alias = s.toLowerCase();
230
+ if (ids.has(alias)) throw new Error(`vocab: synonym "${alias}" collides with a canonical id`);
231
+ const prev = seenAlias.get(alias);
232
+ if (prev && prev !== n.id) throw new Error(`vocab: synonym "${alias}" maps to both "${prev}" and "${n.id}"`);
233
+ seenAlias.set(alias, n.id);
234
+ }
235
+ }
236
+ const visiting = /* @__PURE__ */ new Set();
237
+ const done = /* @__PURE__ */ new Set();
238
+ const parentMap = new Map(nodes.map((n) => [n.id, n.parents ?? []]));
239
+ const walk = (id, path) => {
240
+ if (done.has(id)) return;
241
+ if (visiting.has(id)) throw new Error(`vocab: parent cycle ${[...path, id].join(" \u2192 ")}`);
242
+ visiting.add(id);
243
+ for (const p of parentMap.get(id) ?? []) walk(p, [...path, id]);
244
+ visiting.delete(id);
245
+ done.add(id);
246
+ };
247
+ for (const n of nodes) walk(n.id, []);
248
+ }
249
+ function buildAdjacency(nodes) {
250
+ const adj = /* @__PURE__ */ new Map();
251
+ const add = (from, to, w) => {
252
+ let m = adj.get(from);
253
+ if (!m) adj.set(from, m = /* @__PURE__ */ new Map());
254
+ if (w > (m.get(to) ?? 0)) m.set(to, w);
255
+ };
256
+ for (const n of nodes) {
257
+ for (const p of n.parents ?? []) {
258
+ add(n.id, p, PARENT_UP);
259
+ add(p, n.id, PARENT_DOWN);
260
+ }
261
+ for (const e of n.related ?? []) {
262
+ add(n.id, e.to, e.w);
263
+ add(e.to, n.id, e.w);
264
+ }
265
+ }
266
+ return adj;
267
+ }
268
+ function closureFrom(source, adj) {
269
+ const best = /* @__PURE__ */ new Map();
270
+ for (const [t, w] of adj.get(source) ?? []) {
271
+ if (w >= DECAY_FLOOR) best.set(t, { w: round3(w), via: t });
272
+ }
273
+ const settled = /* @__PURE__ */ new Set([source]);
274
+ while (true) {
275
+ let u;
276
+ let uw = 0;
277
+ for (const [t, e] of best) {
278
+ if (!settled.has(t) && e.w > uw) {
279
+ u = t;
280
+ uw = e.w;
281
+ }
282
+ }
283
+ if (!u) break;
284
+ settled.add(u);
285
+ const via = best.get(u).via;
286
+ for (const [t, we] of adj.get(u) ?? []) {
287
+ if (settled.has(t)) continue;
288
+ const cand = round3(uw * we);
289
+ if (cand >= DECAY_FLOOR && cand > (best.get(t)?.w ?? 0)) {
290
+ best.set(t, { w: cand, via });
291
+ }
292
+ }
293
+ }
294
+ best.delete(source);
295
+ return best;
296
+ }
297
+ function buildGraph(nodes) {
298
+ validateGraph(nodes);
299
+ const ids = new Set(nodes.map((n) => n.id));
300
+ const synonyms = /* @__PURE__ */ new Map();
301
+ for (const n of nodes) {
302
+ for (const s of n.synonyms ?? []) synonyms.set(s.toLowerCase(), n.id);
303
+ }
304
+ const adj = buildAdjacency(nodes);
305
+ const closure = /* @__PURE__ */ new Map();
306
+ for (const n of nodes) closure.set(n.id, closureFrom(n.id, adj));
307
+ return { ids, synonyms, closure };
308
+ }
309
+ var PARENT_UP, PARENT_DOWN, DECAY_FLOOR;
310
+ var init_closure = __esm({
311
+ "../../packages/core/src/vocab/closure.ts"() {
312
+ "use strict";
313
+ PARENT_UP = 0.6;
314
+ PARENT_DOWN = 0.35;
315
+ DECAY_FLOOR = 0.25;
316
+ }
317
+ });
318
+
319
+ // ../../packages/core/src/vocab/types.ts
320
+ var init_types2 = __esm({
321
+ "../../packages/core/src/vocab/types.ts"() {
322
+ "use strict";
323
+ }
324
+ });
325
+
326
+ // ../../packages/core/src/vocab/index.ts
20
327
  function normalize(tokens) {
21
328
  const result = /* @__PURE__ */ new Set();
22
329
  for (const raw of tokens) {
23
330
  const lower = raw.toLowerCase().trim();
24
- if (VOCAB_SET.has(lower)) {
331
+ if (GRAPH.ids.has(lower)) {
25
332
  result.add(lower);
26
333
  continue;
27
334
  }
28
- const mapped = SYNONYMS[lower];
29
- if (mapped && VOCAB_SET.has(mapped)) {
30
- result.add(mapped);
31
- }
335
+ const mapped = GRAPH.synonyms.get(lower);
336
+ if (mapped) result.add(mapped);
32
337
  }
33
338
  return Array.from(result);
34
339
  }
35
- var VOCABULARY, SYNONYMS, VOCAB_SET;
340
+ var GRAPH, VOCABULARY, SYNONYMS;
341
+ var init_vocab = __esm({
342
+ "../../packages/core/src/vocab/index.ts"() {
343
+ "use strict";
344
+ init_graph_data();
345
+ init_closure();
346
+ init_types2();
347
+ init_closure();
348
+ init_graph_data();
349
+ GRAPH = buildGraph(VOCAB_NODES);
350
+ VOCABULARY = [...GRAPH.ids];
351
+ SYNONYMS = Object.fromEntries(GRAPH.synonyms);
352
+ }
353
+ });
354
+
355
+ // ../../packages/core/src/vocabulary.ts
36
356
  var init_vocabulary = __esm({
37
357
  "../../packages/core/src/vocabulary.ts"() {
38
358
  "use strict";
39
- VOCABULARY = [
40
- // Languages
41
- "typescript",
42
- "javascript",
43
- "python",
44
- "go",
45
- "rust",
46
- "java",
47
- "ruby",
48
- "elixir",
49
- "scala",
50
- "kotlin",
51
- "swift",
52
- "cpp",
53
- "csharp",
54
- "php",
55
- "haskell",
56
- "clojure",
57
- "r",
58
- // Frontend frameworks / libs
59
- "react",
60
- "nextjs",
61
- "vue",
62
- "nuxt",
63
- "svelte",
64
- "angular",
65
- "solidjs",
66
- "tailwind",
67
- "css",
68
- "html",
69
- "graphql",
70
- "trpc",
71
- // Backend frameworks
72
- "nodejs",
73
- "express",
74
- "fastify",
75
- "nestjs",
76
- "django",
77
- "fastapi",
78
- "flask",
79
- "rails",
80
- "spring",
81
- "actix",
82
- "gin",
83
- "phoenix",
84
- "laravel",
85
- "dotnet",
86
- // Infrastructure & DevOps
87
- "kubernetes",
88
- "docker",
89
- "terraform",
90
- "aws",
91
- "gcp",
92
- "azure",
93
- "ci-cd",
94
- "github-actions",
95
- "linux",
96
- "nginx",
97
- "pulumi",
98
- "ansible",
99
- "prometheus",
100
- "grafana",
101
- "datadog",
102
- "opentelemetry",
103
- // Data & ML
104
- "postgresql",
105
- "mysql",
106
- "sqlite",
107
- "mongodb",
108
- "redis",
109
- "elasticsearch",
110
- "kafka",
111
- "rabbitmq",
112
- "data-engineering",
113
- "spark",
114
- "airflow",
115
- "dbt",
116
- "ml",
117
- "llm",
118
- "pytorch",
119
- "tensorflow",
120
- "pandas",
121
- "numpy",
122
- // Domains / capabilities
123
- "oauth",
124
- "authentication",
125
- "security",
126
- "payments",
127
- "billing",
128
- "frontend",
129
- "backend",
130
- "devops",
131
- "mobile",
132
- "ios",
133
- "android",
134
- "api-design",
135
- "microservices",
136
- "websockets",
137
- "testing",
138
- "accessibility",
139
- "seo",
140
- "performance",
141
- "observability",
142
- "search",
143
- "realtime"
144
- ];
145
- SYNONYMS = {
146
- // Kubernetes aliases
147
- "k8s": "kubernetes",
148
- "kube": "kubernetes",
149
- // Auth / identity
150
- "passport": "authentication",
151
- "oauth2": "oauth",
152
- "oidc": "oauth",
153
- "jwt": "authentication",
154
- "saml": "authentication",
155
- "auth0": "authentication",
156
- "clerk": "authentication",
157
- "nextauth": "authentication",
158
- // Payments
159
- "@stripe/stripe-js": "payments",
160
- "stripe": "payments",
161
- "braintree": "payments",
162
- "paddle": "payments",
163
- "lemonsqueezy": "payments",
164
- "recurly": "billing",
165
- "chargebee": "billing",
166
- // Framework / lib aliases
167
- "next": "nextjs",
168
- "next.js": "nextjs",
169
- "nuxt.js": "nuxt",
170
- "vue.js": "vue",
171
- "angular.js": "angular",
172
- "angularjs": "angular",
173
- "express.js": "express",
174
- "expressjs": "express",
175
- "fastapi": "fastapi",
176
- "nest": "nestjs",
177
- "nest.js": "nestjs",
178
- "sveltekit": "svelte",
179
- // Language aliases
180
- "ts": "typescript",
181
- "js": "javascript",
182
- "py": "python",
183
- "golang": "go",
184
- "c++": "cpp",
185
- "c#": "csharp",
186
- ".net": "dotnet",
187
- "asp.net": "dotnet",
188
- // DB aliases
189
- "postgres": "postgresql",
190
- "pg": "postgresql",
191
- "mongo": "mongodb",
192
- "elastic": "elasticsearch",
193
- // Cloud aliases
194
- "amazon web services": "aws",
195
- "google cloud": "gcp",
196
- "google cloud platform": "gcp",
197
- "microsoft azure": "azure",
198
- // CI/CD aliases
199
- "github actions": "github-actions",
200
- "circle ci": "ci-cd",
201
- "circleci": "ci-cd",
202
- "jenkins": "ci-cd",
203
- "gitlab ci": "ci-cd",
204
- "travis": "ci-cd",
205
- // Mobile
206
- "react native": "mobile",
207
- "flutter": "mobile",
208
- "expo": "mobile",
209
- // AI / ML
210
- "openai": "llm",
211
- "anthropic": "llm",
212
- "langchain": "llm",
213
- "llamaindex": "llm",
214
- "hugging face": "ml",
215
- "huggingface": "ml",
216
- "scikit-learn": "ml",
217
- "sklearn": "ml",
218
- // Data pipeline
219
- "apache kafka": "kafka",
220
- "apache spark": "spark",
221
- "apache airflow": "airflow",
222
- // Misc
223
- "tailwindcss": "tailwind",
224
- "tw": "tailwind",
225
- "gql": "graphql",
226
- "ws": "websockets",
227
- "socket.io": "websockets",
228
- "jest": "testing",
229
- "vitest": "testing",
230
- "playwright": "testing",
231
- "cypress": "testing"
232
- };
233
- VOCAB_SET = new Set(VOCABULARY);
359
+ init_vocab();
234
360
  }
235
361
  });
236
362
 
@@ -238,6 +364,7 @@ var init_vocabulary = __esm({
238
364
  var init_matcher = __esm({
239
365
  "../../packages/core/src/matcher.ts"() {
240
366
  "use strict";
367
+ init_vocabulary();
241
368
  }
242
369
  });
243
370
 
@@ -273,11 +400,19 @@ var init_himalayas = __esm({
273
400
  }
274
401
  });
275
402
 
403
+ // ../../packages/core/src/feeds/entities.ts
404
+ var init_entities = __esm({
405
+ "../../packages/core/src/feeds/entities.ts"() {
406
+ "use strict";
407
+ }
408
+ });
409
+
276
410
  // ../../packages/core/src/feeds/wwr.ts
277
411
  var init_wwr = __esm({
278
412
  "../../packages/core/src/feeds/wwr.ts"() {
279
413
  "use strict";
280
414
  init_vocabulary();
415
+ init_entities();
281
416
  }
282
417
  });
283
418
 
@@ -286,6 +421,24 @@ var init_hn = __esm({
286
421
  "../../packages/core/src/feeds/hn.ts"() {
287
422
  "use strict";
288
423
  init_vocabulary();
424
+ init_entities();
425
+ }
426
+ });
427
+
428
+ // ../../packages/core/src/feeds/bounty-gate.ts
429
+ var init_bounty_gate = __esm({
430
+ "../../packages/core/src/feeds/bounty-gate.ts"() {
431
+ "use strict";
432
+ }
433
+ });
434
+
435
+ // ../../packages/core/src/feeds/github-bounties.ts
436
+ var init_github_bounties = __esm({
437
+ "../../packages/core/src/feeds/github-bounties.ts"() {
438
+ "use strict";
439
+ init_vocabulary();
440
+ init_entities();
441
+ init_bounty_gate();
289
442
  }
290
443
  });
291
444
 
@@ -303,6 +456,8 @@ var init_feeds = __esm({
303
456
  init_himalayas();
304
457
  init_wwr();
305
458
  init_hn();
459
+ init_github_bounties();
460
+ init_bounty_gate();
306
461
  GREENHOUSE_SLUGS_BY_TIER = {
307
462
  bigco: [
308
463
  "stripe",
@@ -411,13 +566,22 @@ var init_feeds = __esm({
411
566
  }
412
567
  });
413
568
 
414
- // ../../packages/core/src/coastal.ts
569
+ // ../../packages/core/src/partners.ts
415
570
  import { readFileSync } from "fs";
416
571
  import { join } from "path";
417
572
  import { fileURLToPath } from "url";
418
- var init_coastal = __esm({
419
- "../../packages/core/src/coastal.ts"() {
573
+ var EXAMPLE_BUYER, BUYER_REGISTRY;
574
+ var init_partners = __esm({
575
+ "../../packages/core/src/partners.ts"() {
420
576
  "use strict";
577
+ EXAMPLE_BUYER = {
578
+ id: "northstar",
579
+ legalName: "Northstar Talent Partners",
580
+ matchCriteria: { roleTypes: ["full_time"] }
581
+ };
582
+ BUYER_REGISTRY = {
583
+ [EXAMPLE_BUYER.id]: EXAMPLE_BUYER
584
+ };
421
585
  }
422
586
  });
423
587
 
@@ -426,7 +590,7 @@ var init_indexer = __esm({
426
590
  "../../packages/core/src/indexer.ts"() {
427
591
  "use strict";
428
592
  init_feeds();
429
- init_coastal();
593
+ init_partners();
430
594
  }
431
595
  });
432
596
 
@@ -447,7 +611,7 @@ var init_src = __esm({
447
611
  init_matcher();
448
612
  init_feeds();
449
613
  init_indexer();
450
- init_coastal();
614
+ init_partners();
451
615
  init_github();
452
616
  }
453
617
  });
@@ -458,35 +622,31 @@ __export(signal_exports, {
458
622
  extractFingerprint: () => extractFingerprint
459
623
  });
460
624
  import { readFileSync as readFileSync2, readdirSync } from "fs";
461
- import { execSync } from "child_process";
625
+ import { execFileSync } from "child_process";
462
626
  import { join as join2 } from "path";
463
- function safeExec(cmd) {
627
+ function safeGit(args, cwd) {
464
628
  try {
465
- return execSync(cmd, { timeout: 2e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
629
+ return execFileSync("git", ["-C", cwd, ...args], {
630
+ timeout: 2e3,
631
+ stdio: ["ignore", "pipe", "ignore"]
632
+ }).toString().trim();
466
633
  } catch {
467
634
  return "";
468
635
  }
469
636
  }
470
637
  function isEmployerContext(cwd) {
471
- const remote = safeExec('git -C "' + cwd + '" remote get-url origin 2>/dev/null');
638
+ const inRepo = safeGit(["rev-parse", "--is-inside-work-tree"], cwd);
639
+ if (inRepo !== "true") return false;
640
+ const remote = safeGit(["remote", "get-url", "origin"], cwd);
472
641
  if (remote) {
473
- try {
474
- const sshMatch = remote.match(/^git@([^:]+):/);
475
- const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
476
- const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
477
- if (host && !PERSONAL_GIT_HOSTS.has(host)) {
478
- return true;
479
- }
480
- } catch {
481
- }
482
- }
483
- const email = safeExec('git -C "' + cwd + '" config user.email 2>/dev/null');
484
- if (email) {
485
- const domain = email.split("@")[1]?.toLowerCase() ?? "";
486
- if (domain && !PERSONAL_EMAIL_DOMAINS.has(domain)) {
487
- return true;
488
- }
642
+ const sshMatch = remote.match(/^git@([^:]+):/);
643
+ const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
644
+ const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
645
+ if (host) return !PERSONAL_GIT_HOSTS.has(host);
489
646
  }
647
+ const email = safeGit(["config", "user.email"], cwd);
648
+ const domain = email.split("@")[1]?.toLowerCase() ?? "";
649
+ if (domain) return !PERSONAL_EMAIL_DOMAINS.has(domain);
490
650
  return false;
491
651
  }
492
652
  function readJsonSafe(path) {
@@ -509,10 +669,24 @@ function tokensFromPackageJson(cwd) {
509
669
  const p = pkg;
510
670
  const deps = {
511
671
  ...typeof p["dependencies"] === "object" ? p["dependencies"] : {},
512
- ...typeof p["devDependencies"] === "object" ? p["devDependencies"] : {}
672
+ ...typeof p["devDependencies"] === "object" ? p["devDependencies"] : {},
673
+ ...typeof p["peerDependencies"] === "object" ? p["peerDependencies"] : {}
513
674
  };
514
675
  return Object.keys(deps);
515
676
  }
677
+ function workspaceMemberDirs(cwd) {
678
+ const dirs = [cwd];
679
+ for (const group of ["apps", "packages"]) {
680
+ try {
681
+ const groupDir = join2(cwd, group);
682
+ for (const e of readdirSync(groupDir, { withFileTypes: true })) {
683
+ if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join2(groupDir, e.name));
684
+ }
685
+ } catch {
686
+ }
687
+ }
688
+ return dirs;
689
+ }
516
690
  function tokensFromRequirementsTxt(cwd) {
517
691
  const content = readFileSafe(join2(cwd, "requirements.txt"));
518
692
  if (!content) return [];
@@ -520,14 +694,26 @@ function tokensFromRequirementsTxt(cwd) {
520
694
  }
521
695
  function tokensFromGoMod(cwd) {
522
696
  const content = readFileSafe(join2(cwd, "go.mod"));
523
- if (!content) return ["go"];
697
+ if (!content) return [];
524
698
  const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
525
699
  return ["go", ...requires];
526
700
  }
527
701
  function tokensFromCargoToml(cwd) {
528
702
  const content = readFileSafe(join2(cwd, "Cargo.toml"));
529
703
  if (!content) return [];
530
- const deps = Array.from(content.matchAll(/^([a-zA-Z0-9_-]+)\s*=/gm)).map((m) => m[1].toLowerCase());
704
+ const deps = [];
705
+ let inDeps = false;
706
+ for (const line of content.split("\n")) {
707
+ const trimmed = line.trim();
708
+ const section = trimmed.match(/^\[([^\]]+)\]/);
709
+ if (section) {
710
+ inDeps = /(^|\.)(dependencies|dev-dependencies|build-dependencies)$/.test(section[1].trim());
711
+ continue;
712
+ }
713
+ if (!inDeps) continue;
714
+ const key = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=/);
715
+ if (key) deps.push(key[1].toLowerCase());
716
+ }
531
717
  return ["rust", ...deps];
532
718
  }
533
719
  function tokensFromFileExtensions(cwd) {
@@ -567,11 +753,7 @@ function inferSeniority(rawTokens) {
567
753
  "opentelemetry",
568
754
  "prometheus",
569
755
  "grafana",
570
- "microservices",
571
- "api-design",
572
- "security",
573
- "oauth",
574
- "payments"
756
+ "microservices"
575
757
  ]);
576
758
  const midSignals = /* @__PURE__ */ new Set([
577
759
  "docker",
@@ -581,7 +763,11 @@ function inferSeniority(rawTokens) {
581
763
  "postgresql",
582
764
  "redis",
583
765
  "graphql",
584
- "trpc"
766
+ "trpc",
767
+ "api-design",
768
+ "security",
769
+ "oauth",
770
+ "payments"
585
771
  ]);
586
772
  const normalized = new Set(normalize(rawTokens));
587
773
  const seniorHits = [...normalized].filter((t) => seniorSignals.has(t)).length;
@@ -592,13 +778,16 @@ function inferSeniority(rawTokens) {
592
778
  }
593
779
  function extractFingerprint(cwd) {
594
780
  const employer = isEmployerContext(cwd);
595
- const rawTokens = [
596
- ...tokensFromPackageJson(cwd),
597
- ...tokensFromRequirementsTxt(cwd),
598
- ...tokensFromGoMod(cwd),
599
- ...tokensFromCargoToml(cwd),
600
- ...tokensFromFileExtensions(cwd)
601
- ];
781
+ const rawTokens = [];
782
+ for (const dir of workspaceMemberDirs(cwd)) {
783
+ rawTokens.push(
784
+ ...tokensFromPackageJson(dir),
785
+ ...tokensFromRequirementsTxt(dir),
786
+ ...tokensFromGoMod(dir),
787
+ ...tokensFromCargoToml(dir),
788
+ ...tokensFromFileExtensions(dir)
789
+ );
790
+ }
602
791
  let skillTags = normalize(rawTokens);
603
792
  if (employer) {
604
793
  skillTags = skillTags.filter((t) => LANGUAGE_TAGS.has(t));
@@ -783,10 +972,10 @@ function migrateTagWeights(profile) {
783
972
  if (!profile.tagWeights) {
784
973
  profile.tagWeights = {};
785
974
  }
786
- const now = (/* @__PURE__ */ new Date()).toISOString();
975
+ const seed = profile.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
787
976
  for (const tag of profile.skillTags) {
788
977
  if (!profile.tagWeights[tag]) {
789
- profile.tagWeights[tag] = { count: 1, firstSeen: now, lastSeen: now, sessions: 1 };
978
+ profile.tagWeights[tag] = { count: 1, firstSeen: seed, lastSeen: seed, sessions: 1 };
790
979
  }
791
980
  }
792
981
  }
@@ -812,7 +1001,7 @@ async function writeProfile(profile) {
812
1001
  const blob = encrypt(JSON.stringify(profile), key);
813
1002
  writeFileSync(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
814
1003
  }
815
- function accumulateSession(profile, tags, isEmployerContext2, inferredSeniority) {
1004
+ function accumulateSession(profile, tags, isEmployerContext2, inferredSeniority, seniorityIsAuthoritative = false) {
816
1005
  const now = (/* @__PURE__ */ new Date()).toISOString();
817
1006
  let filtered = normalize(tags);
818
1007
  if (isEmployerContext2) {
@@ -830,7 +1019,9 @@ function accumulateSession(profile, tags, isEmployerContext2, inferredSeniority)
830
1019
  }
831
1020
  }
832
1021
  if (inferredSeniority && !isEmployerContext2) {
833
- profile.seniority = inferredSeniority;
1022
+ if (seniorityIsAuthoritative || !profile.github) {
1023
+ profile.seniority = inferredSeniority;
1024
+ }
834
1025
  }
835
1026
  }
836
1027
  async function accumulateTags(rawTokens, isEmployerContext2, inferredSeniority) {
@@ -838,12 +1029,14 @@ async function accumulateTags(rawTokens, isEmployerContext2, inferredSeniority)
838
1029
  accumulateSession(profile, rawTokens, isEmployerContext2, inferredSeniority);
839
1030
  await writeProfile(profile);
840
1031
  }
841
- function accumulateGitHubTags(profile, tags) {
1032
+ function accumulateGitHubTags(profile, tags, inferredSeniority) {
842
1033
  accumulateSession(
843
1034
  profile,
844
1035
  tags,
845
1036
  /* isEmployerContext */
846
- false
1037
+ false,
1038
+ inferredSeniority,
1039
+ true
847
1040
  );
848
1041
  }
849
1042
  async function listSavedJobs() {