openings 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 (52) hide show
  1. package/.codex-plugin/plugin.json +23 -0
  2. package/.mcp.json +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +110 -0
  5. package/data/companies.json +2550 -0
  6. package/docs/job-seeker-quickstart.md +109 -0
  7. package/package.json +42 -0
  8. package/skills/openings/SKILL.md +28 -0
  9. package/src/artifact-path.ts +48 -0
  10. package/src/atomic-file.ts +13 -0
  11. package/src/candidate-profile.ts +273 -0
  12. package/src/career-tracing.ts +181 -0
  13. package/src/catalog.ts +382 -0
  14. package/src/cli.ts +601 -0
  15. package/src/common-crawl-discovery.ts +137 -0
  16. package/src/company-seeds.ts +43 -0
  17. package/src/country-coverage.ts +203 -0
  18. package/src/crawl-reporting.ts +56 -0
  19. package/src/crawler.ts +146 -0
  20. package/src/enrichment-registry.ts +137 -0
  21. package/src/file-lock.ts +85 -0
  22. package/src/index.ts +37 -0
  23. package/src/intent-validation.ts +28 -0
  24. package/src/job-coverage.ts +73 -0
  25. package/src/job-fit-analysis.ts +247 -0
  26. package/src/job-matching.ts +445 -0
  27. package/src/job-recommendations.ts +193 -0
  28. package/src/job-search-preparation.ts +116 -0
  29. package/src/jobposting-probe.ts +167 -0
  30. package/src/local-jobs.ts +113 -0
  31. package/src/locations.ts +180 -0
  32. package/src/mcp.ts +98 -0
  33. package/src/package-mcp.ts +8 -0
  34. package/src/recruitee-round.ts +114 -0
  35. package/src/report-meta.ts +17 -0
  36. package/src/requirement-vocabulary.ts +111 -0
  37. package/src/resume-optimization.ts +118 -0
  38. package/src/runtime.ts +51 -0
  39. package/src/safe-get.ts +88 -0
  40. package/src/safe-head.ts +79 -0
  41. package/src/screening-requirements.ts +99 -0
  42. package/src/selected-job-lookup.ts +14 -0
  43. package/src/snapshot-catalog.ts +21 -0
  44. package/src/snapshot-export.ts +66 -0
  45. package/src/snapshot-store.ts +31 -0
  46. package/src/source-discovery-pipeline.ts +26 -0
  47. package/src/source-discovery.ts +231 -0
  48. package/src/source-enrichment.ts +92 -0
  49. package/src/source-pipeline.ts +245 -0
  50. package/src/source-verification.ts +297 -0
  51. package/src/tools.ts +194 -0
  52. package/src/types.ts +136 -0
@@ -0,0 +1,79 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { request } from "node:https";
3
+ import { isIP } from "node:net";
4
+ import { connect as connectTls } from "node:tls";
5
+
6
+ export type ResolveHost = (hostname: string) => Promise<string[]>;
7
+ export type HeadTransport = (url: URL, address: string, signal: AbortSignal) => Promise<Response>;
8
+ export interface SafeHeadResult { response: Response; finalUrl: string }
9
+
10
+ export async function fetchSafeHead(url: string, options: { resolveHost?: ResolveHost; transport?: HeadTransport; timeoutMs?: number } = {}): Promise<SafeHeadResult> {
11
+ const controller = new AbortController();
12
+ const timer = setTimeout(() => controller.abort(new Error(`Timed out after ${options.timeoutMs ?? 15_000}ms`)), options.timeoutMs ?? 15_000);
13
+ try {
14
+ let current = new URL(url);
15
+ for (let redirects = 0; redirects <= 5; redirects += 1) {
16
+ const address = await withAbort(publicAddress(current, options.resolveHost ?? resolveAddresses), controller.signal);
17
+ const response = await withAbort((options.transport ?? pinnedHead)(current, address, controller.signal), controller.signal);
18
+ if (![301, 302, 303, 307, 308].includes(response.status)) return { response, finalUrl: current.href };
19
+ const location = response.headers.get("location");
20
+ if (!location) return { response, finalUrl: current.href };
21
+ current = new URL(location, current);
22
+ if (current.protocol !== "https:") throw new Error("Career redirect must use HTTPS");
23
+ }
24
+ throw new Error("Career redirect limit exceeded");
25
+ } finally { clearTimeout(timer); }
26
+ }
27
+
28
+ function withAbort<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {
29
+ if (signal.aborted) return Promise.reject(signal.reason);
30
+ return new Promise((resolve, reject) => {
31
+ const abort = () => reject(signal.reason);
32
+ signal.addEventListener("abort", abort, { once: true });
33
+ operation.then(
34
+ (value) => { signal.removeEventListener("abort", abort); resolve(value); },
35
+ (error) => { signal.removeEventListener("abort", abort); reject(error); },
36
+ );
37
+ });
38
+ }
39
+
40
+ async function resolveAddresses(hostname: string): Promise<string[]> {
41
+ return (await lookup(hostname, { all: true, verbatim: true })).map(({ address }) => address);
42
+ }
43
+
44
+ async function publicAddress(url: URL, resolveHost: ResolveHost): Promise<string> {
45
+ if (url.protocol !== "https:") throw new Error("Career URL must use HTTPS");
46
+ const addresses = isIP(url.hostname) ? [url.hostname] : await resolveHost(url.hostname);
47
+ const publicAddresses = addresses.filter(isPublicAddress);
48
+ if (!addresses.length || publicAddresses.length !== addresses.length) throw new Error(`Career URL resolves to a non-public address: ${url.hostname}`);
49
+ return publicAddresses[0]!;
50
+ }
51
+
52
+ function pinnedHead(url: URL, address: string, signal: AbortSignal): Promise<Response> {
53
+ return new Promise((resolve, reject) => {
54
+ const req = request(url, {
55
+ method: "HEAD", signal, servername: url.hostname, agent: false,
56
+ createConnection: () => {
57
+ const socket = connectTls({ host: address, port: Number(url.port || 443), servername: url.hostname });
58
+ const destroy = () => socket.destroy(signal.reason instanceof Error ? signal.reason : undefined);
59
+ if (signal.aborted) destroy();
60
+ else signal.addEventListener("abort", destroy, { once: true });
61
+ return socket;
62
+ },
63
+ }, (response) => resolve(new Response(null, { status: response.statusCode ?? 500, headers: response.headers as HeadersInit })));
64
+ req.once("error", reject);
65
+ req.end();
66
+ });
67
+ }
68
+
69
+ function isPublicAddress(address: string): boolean {
70
+ if (isIP(address) === 4) {
71
+ const [a, b] = address.split(".").map(Number);
72
+ return !(a === 0 || a === 10 || a === 127 || (a === 100 && b! >= 64 && b! <= 127) || (a === 169 && b === 254) || (a === 172 && b! >= 16 && b! <= 31) || (a === 192 && (b === 0 || b === 168)) || (a === 198 && (b === 18 || b === 19 || b === 51)) || (a === 203 && b === 0) || a! >= 224);
73
+ }
74
+ if (isIP(address) === 6) {
75
+ const value = address.toLowerCase();
76
+ return !(value === "::" || value === "::1" || value.startsWith("::ffff:") || value.startsWith("fc") || value.startsWith("fd") || /^fe[89ab]/.test(value) || value.startsWith("ff") || value.startsWith("2001:db8"));
77
+ }
78
+ return false;
79
+ }
@@ -0,0 +1,99 @@
1
+ import type { CandidateProfile } from "./candidate-profile.ts";
2
+ import type { Job } from "./types.ts";
3
+
4
+ export interface ScreeningRequirement {
5
+ kind: "experience" | "education";
6
+ requirement: string;
7
+ status: "supported" | "partial" | "unsupported";
8
+ factIds: string[];
9
+ }
10
+
11
+ export function evaluateScreeningRequirements(profile: CandidateProfile, job: Job): ScreeningRequirement[] {
12
+ const description = normalizeStructuredText(job.description);
13
+ const requirements = [
14
+ ...experienceRequirements(profile, description),
15
+ ...educationRequirements(profile, description),
16
+ ];
17
+ return [...new Map(requirements.map((item) => [`${item.kind}:${item.requirement.toLocaleLowerCase()}`, item])).values()];
18
+ }
19
+
20
+ function normalizeStructuredText(value: string): string {
21
+ return value
22
+ .replace(/<br\s*\/?>/gi, "\n")
23
+ .replace(/<\/h[1-6]>/gi, "\n")
24
+ .replace(/<[^>]+>/g, " ")
25
+ .replace(/&nbsp;|&#160;|\u00a0/gi, " ")
26
+ .replace(/&amp;/gi, "&")
27
+ .replace(/[ \t]+/g, " ");
28
+ }
29
+
30
+ function experienceRequirements(profile: CandidateProfile, description: string): ScreeningRequirement[] {
31
+ const pattern = /\b(\d+)\s*\+?\s*years?\s+(?:of\s+)?(?:[A-Za-z][A-Za-z/-]*\s+){0,6}?experience\b/gi;
32
+ return [...description.matchAll(pattern)].flatMap<ScreeningRequirement>((match) => {
33
+ if (!isMandatory(description, match.index!, match[0])) return [];
34
+ const requirement = clean(match[0]);
35
+ const inference = profile.inferences.find((value) => value.kind === "approximate_experience_years");
36
+ if (!inference) return [{ kind: "experience", requirement, status: "unsupported", factIds: [] }];
37
+ return [{
38
+ kind: "experience",
39
+ requirement,
40
+ status: inference.value >= Number(match[1]) ? "supported" : "partial",
41
+ factIds: inference.derivedFromFactIds,
42
+ }];
43
+ });
44
+ }
45
+
46
+ function educationRequirements(profile: CandidateProfile, description: string): ScreeningRequirement[] {
47
+ const fieldPattern = /\b((?:bachelor|master)(?:['’]s)?(?:\s+or\s+(?:bachelor|master)(?:['’]s)?)*)\s+degree\s+in\s+([^.\n]+)/gi;
48
+ const fieldSpecific = [...description.matchAll(fieldPattern)].flatMap((match) => {
49
+ if (!isMandatory(description, match.index!, match[0])) return [];
50
+ const levels = [...match[1]!.matchAll(/bachelor|master/gi)].map((value) => value[0]!.toLocaleLowerCase());
51
+ const fact = profile.facts.find((value) => value.kind === "education" && levels.some((level) => educationLevelMatches(level, value.value)) && educationFieldMatches(match[2]!, value.value));
52
+ return [{ kind: "education", requirement: clean(match[0]), status: fact ? "supported" : "unsupported", factIds: fact ? [fact.id] : [] } satisfies ScreeningRequirement];
53
+ });
54
+ const genericPattern = /\b(bachelor(?:['’]s)?|master(?:['’]s)?|ph\.?d\.?|doctorate)\s+degree\b[^.\n]{0,30}\b(?:required|minimum|must)\b/gi;
55
+ const generic = [...description.matchAll(genericPattern)].flatMap<ScreeningRequirement>((match) => {
56
+ if (!isMandatory(description, match.index!, match[0])) return [];
57
+ const level = match[1]!.toLocaleLowerCase();
58
+ const fact = profile.facts.find((value) => value.kind === "education" && educationLevelMatches(level, value.value));
59
+ const requirement = level.startsWith("bachelor") ? "Bachelor's degree" : level.startsWith("master") ? "Master's degree" : "Doctoral degree";
60
+ return [{ kind: "education", requirement, status: fact ? "supported" : "unsupported", factIds: fact ? [fact.id] : [] }];
61
+ });
62
+ return [...fieldSpecific, ...generic];
63
+ }
64
+
65
+ function isMandatory(description: string, start: number, value: string): boolean {
66
+ const clauseEnd = description.slice(start).search(/[.\n]/);
67
+ const clause = description.slice(start, clauseEnd < 0 ? undefined : start + clauseEnd);
68
+ const rawPrefix = description.slice(Math.max(0, start - 40), start);
69
+ const prefix = rawPrefix.slice(Math.max(rawPrefix.lastIndexOf("."), rawPrefix.lastIndexOf("\n")) + 1);
70
+ const context = `${prefix} ${value} ${clause}`;
71
+ if (/\b(?:preferred|optional|desired|nice to have|not required|up to)\b/i.test(context)) return false;
72
+ if (/\b(?:required|must|minimum|at least)\b/i.test(context)) return true;
73
+ return lastSectionKind(description.slice(0, start)) === "required";
74
+ }
75
+
76
+ function lastSectionKind(value: string): "required" | "optional" | undefined {
77
+ let kind: "required" | "optional" | undefined;
78
+ for (const rawLine of value.split("\n")) {
79
+ const line = rawLine.trim().replace(/:$/, "");
80
+ if (/^(?:preferred qualifications?|optional requirements?|nice to have|extra awesome|optional)$/i.test(line)) kind = "optional";
81
+ else if (/^(?:what(?:'|’)s required|requirements?|qualifications?|required qualifications|minimum qualifications?|essentials?|essential qualifications?|must haves?|your experience includes)$/i.test(line)) kind = "required";
82
+ }
83
+ return kind;
84
+ }
85
+
86
+ function clean(value: string): string { return value.trim().replace(/\s+/g, " "); }
87
+
88
+ function educationLevelMatches(level: string, value: string): boolean {
89
+ if (level.startsWith("bachelor")) return /\b(?:bachelor|b\.?\s*(?:tech|e|sc|a|com|ba|eng))\b/i.test(value);
90
+ if (level.startsWith("master")) return /\b(?:master|m\.?\s*(?:tech|e|sc|a|com|ba|eng))\b/i.test(value);
91
+ return /\b(?:ph\.?d|doctorate)\b/i.test(value);
92
+ }
93
+
94
+ function educationFieldMatches(requiredField: string, value: string): boolean {
95
+ const candidate = value.toLocaleLowerCase();
96
+ if (/\b(?:computer science|computing|software|information technology|engineering)\b/i.test(requiredField) && /\b(?:computer science|computing|software|information technology|engineering|b\.?tech|b\.?e\.?|m\.?tech|m\.?e\.?)\b/i.test(candidate)) return true;
97
+ if (/\b(?:technical|business)\b/i.test(requiredField) && /\b(?:technology|technical|engineering|computer|software|information systems|business|commerce|bba|mba|b\.?tech|m\.?tech)\b/i.test(candidate)) return true;
98
+ return false;
99
+ }
@@ -0,0 +1,14 @@
1
+ import type { Job } from "./types.ts";
2
+
3
+ export interface SelectedJobLookupOptions {
4
+ getSnapshotJob(id: string): Promise<Job | null>;
5
+ getDetailedJob(id: string): Promise<Job | null>;
6
+ }
7
+
8
+ export function createSelectedJobLookup(options: SelectedJobLookupOptions) {
9
+ return async (id: string): Promise<Job | null> => {
10
+ const snapshot = await options.getSnapshotJob(id);
11
+ if (snapshot?.description.trim()) return snapshot;
12
+ return options.getDetailedJob(id);
13
+ };
14
+ }
@@ -0,0 +1,21 @@
1
+ import { searchJobs, type Catalog } from "./catalog.ts";
2
+ import type { SnapshotStore } from "./crawler.ts";
3
+
4
+ export function createSnapshotCatalog(store: SnapshotStore): Catalog {
5
+ return {
6
+ async search(query) {
7
+ const snapshot = await store.read();
8
+ if (!snapshot) return [];
9
+ return searchJobs(Object.values(snapshot.partitions).flatMap((partition) => partition.jobs), query);
10
+ },
11
+ async get(id) {
12
+ const snapshot = await store.read();
13
+ if (!snapshot) return null;
14
+ for (const partition of Object.values(snapshot.partitions)) {
15
+ const job = partition.jobs.find((candidate) => candidate.id === id);
16
+ if (job) return job;
17
+ }
18
+ return null;
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,66 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, readFile, unlink } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { atomicWrite } from "./atomic-file.ts";
5
+ import type { JobSnapshot } from "./types.ts";
6
+
7
+ export interface SnapshotExportReport {
8
+ outputDir: string;
9
+ sources: number;
10
+ jobs: number;
11
+ countries: Record<string, number>;
12
+ manifestPath: string;
13
+ }
14
+
15
+ export async function exportSnapshot(inputPath: string, outputDir: string): Promise<SnapshotExportReport> {
16
+ const snapshot = JSON.parse(await readFile(inputPath, "utf8")) as JobSnapshot;
17
+ if (snapshot.version !== 1 || !snapshot.partitions || typeof snapshot.partitions !== "object") throw new Error(`Unsupported snapshot format: ${inputPath}`);
18
+ const slugs = Object.keys(snapshot.partitions);
19
+ if (slugs.some((slug) => !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug))) throw new Error("Snapshot contains an unsafe source slug");
20
+ const sourceDir = join(outputDir, "sources");
21
+ await mkdir(sourceDir, { recursive: true });
22
+ const countries: Record<string, number> = {};
23
+ const expectedFiles = new Set(slugs.map((slug) => `${slug}.json`));
24
+ for (const file of await previousPartitionFiles(outputDir)) if (!expectedFiles.has(file)) await unlink(join(sourceDir, file)).catch(() => undefined);
25
+ const partitions = [];
26
+ let jobCount = 0;
27
+
28
+ for (const slug of Object.keys(snapshot.partitions).sort()) {
29
+ const partition = snapshot.partitions[slug]!;
30
+ const value = { source: slug, fetchedAt: partition.fetchedAt, jobs: [...partition.jobs].sort((left, right) => left.id.localeCompare(right.id)) };
31
+ const content = stableJson(value);
32
+ const path = `sources/${slug}.json`;
33
+ await atomicWrite(join(outputDir, path), content);
34
+ jobCount += value.jobs.length;
35
+ for (const job of value.jobs) for (const country of job.eligibleCountries) countries[country] = (countries[country] ?? 0) + 1;
36
+ partitions.push({ source: slug, path, jobs: value.jobs.length, sha256: sha256(content) });
37
+ }
38
+
39
+ const sortedCountries = Object.fromEntries(Object.entries(countries).sort(([left], [right]) => left.localeCompare(right)));
40
+ const manifest = {
41
+ version: 1,
42
+ updatedAt: snapshot.updatedAt,
43
+ sources: partitions.length,
44
+ jobs: jobCount,
45
+ countries: sortedCountries,
46
+ partitions,
47
+ };
48
+ const manifestPath = join(outputDir, "manifest.json");
49
+ await atomicWrite(manifestPath, stableJson(manifest));
50
+ return { outputDir, sources: partitions.length, jobs: jobCount, countries: sortedCountries, manifestPath };
51
+ }
52
+
53
+ async function previousPartitionFiles(outputDir: string): Promise<string[]> {
54
+ try {
55
+ const value: unknown = JSON.parse(await readFile(join(outputDir, "manifest.json"), "utf8"));
56
+ if (!value || typeof value !== "object" || !("partitions" in value) || !Array.isArray(value.partitions)) return [];
57
+ return value.partitions.flatMap((partition) => {
58
+ if (!partition || typeof partition !== "object" || !("path" in partition) || typeof partition.path !== "string") return [];
59
+ const match = /^sources\/([a-z0-9]+(?:-[a-z0-9]+)*\.json)$/.exec(partition.path);
60
+ return match ? [match[1]!] : [];
61
+ });
62
+ } catch { return []; }
63
+ }
64
+
65
+ function stableJson(value: unknown): string { return `${JSON.stringify(value, null, 2)}\n`; }
66
+ function sha256(value: string): string { return createHash("sha256").update(value).digest("hex"); }
@@ -0,0 +1,31 @@
1
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import type { SnapshotStore } from "./crawler.ts";
4
+ import type { JobSnapshot } from "./types.ts";
5
+
6
+ export function createFileSnapshotStore(path: string): SnapshotStore {
7
+ return {
8
+ async read() {
9
+ try {
10
+ const parsed = JSON.parse(await readFile(path, "utf8")) as JobSnapshot;
11
+ if (parsed.version !== 1 || typeof parsed.partitions !== "object") {
12
+ throw new Error(`Unsupported snapshot format: ${path}`);
13
+ }
14
+ return parsed;
15
+ } catch (error) {
16
+ if (isMissing(error)) return null;
17
+ throw error;
18
+ }
19
+ },
20
+ async write(snapshot) {
21
+ await mkdir(dirname(path), { recursive: true });
22
+ const temporary = `${path}.${process.pid}.tmp`;
23
+ await writeFile(temporary, `${JSON.stringify(snapshot)}\n`, "utf8");
24
+ await rename(temporary, path);
25
+ },
26
+ };
27
+ }
28
+
29
+ function isMissing(error: unknown): boolean {
30
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
31
+ }
@@ -0,0 +1,26 @@
1
+ import type { SourceDiscoveryReport } from "./source-discovery.ts";
2
+ import { runSourceVerification, type SourcePipelineReport } from "./source-pipeline.ts";
3
+
4
+ interface PromotionOptions {
5
+ fetch?: (input: string | URL, init?: RequestInit) => Promise<Response>;
6
+ concurrency?: number;
7
+ timeoutMs?: number;
8
+ now?: () => Date;
9
+ registryPath?: string;
10
+ }
11
+
12
+ export interface DiscoveryPromotionResult {
13
+ discovery: SourceDiscoveryReport;
14
+ promotion: SourcePipelineReport;
15
+ }
16
+
17
+ export async function discoverAndPromote(
18
+ discover: () => Promise<SourceDiscoveryReport>,
19
+ candidatesPath: string,
20
+ catalogPath: string,
21
+ options: PromotionOptions = {},
22
+ ): Promise<DiscoveryPromotionResult> {
23
+ const discovery = await discover();
24
+ const promotion = await runSourceVerification(candidatesPath, catalogPath, options);
25
+ return { discovery, promotion };
26
+ }
@@ -0,0 +1,231 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { atomicJson } from "./atomic-file.ts";
4
+ import { stampReport, type ReportMeta } from "./report-meta.ts";
5
+ import { withFileLock } from "./file-lock.ts";
6
+ import { resolveSource } from "./source-verification.ts";
7
+ import type { DiscoveryChannel, SourceCandidate } from "./types.ts";
8
+ import { mergeEnrichmentLeads, type EnrichmentLead, type IdentityEvidence } from "./enrichment-registry.ts";
9
+
10
+ type Fetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
11
+
12
+ interface DiscoveryOptions {
13
+ country?: string;
14
+ fetch?: Fetch;
15
+ concurrency?: number;
16
+ timeoutMs?: number;
17
+ registryPath?: string;
18
+ }
19
+
20
+ interface FeedEntry { sourceUrl: string; companyName?: string; companyDomain?: string; reference?: string; channel?: DiscoveryChannel; domainEvidence?: "authoritative_dataset" | "company_registry" | "company_redirect" }
21
+ interface DiscoveryIssue { sourceUrl: string; reason: string; detail: string; companyName?: string; reference?: string }
22
+
23
+ export interface SourceDiscoveryReport extends ReportMeta {
24
+ discovered: number;
25
+ ready: number;
26
+ alreadyKnown: number;
27
+ needsDomain: number;
28
+ rejected: number;
29
+ candidatesPath: string;
30
+ reportPath: string;
31
+ unresolved: DiscoveryIssue[];
32
+ rejections: DiscoveryIssue[];
33
+ registryPath?: string;
34
+ registryAdded: number;
35
+ }
36
+
37
+ export async function runSourceDiscovery(feedPath: string, candidatesPath: string, reportPath: string, options: DiscoveryOptions = {}): Promise<SourceDiscoveryReport> {
38
+ const feed = await readFeed(feedPath);
39
+ return discoverEntries(feed, candidatesPath, reportPath, options, feedPath, false);
40
+ }
41
+
42
+ export async function runYcSourceDiscovery(candidatesPath: string, reportPath: string, options: DiscoveryOptions & { country: string }): Promise<SourceDiscoveryReport> {
43
+ const fetcher = options.fetch ?? globalThis.fetch;
44
+ const response = await fetcher("https://yc-oss.github.io/api/companies/all.json");
45
+ if (!response.ok) throw new Error(`YC company API returned HTTP ${response.status}`);
46
+ const value: unknown = await response.json();
47
+ if (!Array.isArray(value) || !value.every(isRecord)) throw new Error("YC company API returned an invalid payload");
48
+ const country = options.country.toUpperCase();
49
+ const countryName = new Intl.DisplayNames(["en"], { type: "region" }).of(country);
50
+ if (!countryName || countryName === country) throw new Error("country must be a valid two-letter code");
51
+ const countryPattern = new RegExp(`\\b${escapeRegExp(countryName)}\\b`, "i");
52
+ const feed: FeedEntry[] = value.flatMap((company) => {
53
+ if (typeof company.slug !== "string" || typeof company.website !== "string" || typeof company.all_locations !== "string" || !countryPattern.test(company.all_locations)) return [];
54
+ let companyDomain: string;
55
+ try { companyDomain = new URL(company.website).hostname.replace(/^www\./, ""); } catch { return []; }
56
+ return [{
57
+ sourceUrl: `https://job-boards.greenhouse.io/${company.slug}`,
58
+ companyName: typeof company.name === "string" ? company.name : company.slug,
59
+ companyDomain,
60
+ reference: `https://www.ycombinator.com/companies/${company.slug}`,
61
+ domainEvidence: "authoritative_dataset",
62
+ }];
63
+ });
64
+ return discoverEntries(feed, candidatesPath, reportPath, { ...options, fetch: fetcher, country }, "YC public company API", true);
65
+ }
66
+
67
+ async function discoverEntries(feed: FeedEntry[], candidatesPath: string, reportPath: string, options: DiscoveryOptions, feedReference: string, trustDomainEvidence: boolean): Promise<SourceDiscoveryReport> {
68
+ const existing = await readCandidates(candidatesPath);
69
+ const concurrency = Math.max(1, Math.trunc(options.concurrency ?? 10));
70
+ const country = options.country?.toUpperCase();
71
+ if (country && !/^[A-Z]{2}$/.test(country)) throw new Error("country must be a two-letter code");
72
+ const ready: Array<{ index: number; candidate: SourceCandidate }> = [];
73
+ const unresolved: Array<{ index: number; issue: DiscoveryIssue }> = [];
74
+ const rejected: Array<{ index: number; issue: DiscoveryIssue }> = [];
75
+ const registryRows: EnrichmentLead[] = [];
76
+ const existingSources = new Set(existing.map((candidate) => sourceKey(candidate.sourceUrl)).filter(Boolean));
77
+ let alreadyKnown = 0;
78
+ let cursor = 0;
79
+
80
+ async function worker() {
81
+ while (cursor < feed.length) {
82
+ const index = cursor++;
83
+ const entry = feed[index];
84
+ if (!entry || typeof entry.sourceUrl !== "string") {
85
+ rejected.push({ index, issue: { sourceUrl: "", reason: "invalid_entry", detail: "sourceUrl must be a string" } });
86
+ continue;
87
+ }
88
+ if (entry.companyDomain !== undefined && (typeof entry.companyDomain !== "string" || !/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(entry.companyDomain))) {
89
+ rejected.push({ index, issue: issue({ sourceUrl: entry.sourceUrl }, "invalid_domain", "companyDomain must be a hostname") });
90
+ continue;
91
+ }
92
+ if (entry.companyName !== undefined && (typeof entry.companyName !== "string" || !entry.companyName.trim())) {
93
+ rejected.push({ index, issue: issue({ sourceUrl: entry.sourceUrl }, "invalid_name", "companyName must be a non-empty string") });
94
+ continue;
95
+ }
96
+ if (entry.reference !== undefined && typeof entry.reference !== "string") {
97
+ rejected.push({ index, issue: issue({ sourceUrl: entry.sourceUrl }, "invalid_reference", "reference must be a string") });
98
+ continue;
99
+ }
100
+ const source = resolveSource(entry.sourceUrl);
101
+ if (!source) {
102
+ rejected.push({ index, issue: issue(entry, "unsupported_source", "Discovery accepts Greenhouse, Lever, Ashby, Workday, and Recruitee source URLs") });
103
+ continue;
104
+ }
105
+ if (entry.channel !== undefined && !["search", "career_page", "provider_directory", "community", "dataset"].includes(entry.channel)) {
106
+ rejected.push({ index, issue: issue(entry, "invalid_channel", `Unsupported discovery channel: ${entry.channel}`) });
107
+ continue;
108
+ }
109
+ if (entry.domainEvidence !== undefined && !["authoritative_dataset", "company_registry", "company_redirect"].includes(entry.domainEvidence)) {
110
+ rejected.push({ index, issue: issue(entry, "invalid_domain_evidence", `Unsupported domain evidence: ${entry.domainEvidence}`) });
111
+ continue;
112
+ }
113
+ const key = `${source.ats}:${source.token.toLocaleLowerCase()}`;
114
+ const companyName = typeof entry.companyName === "string" ? entry.companyName.trim() : "";
115
+ const reference = entry.reference?.trim() || feedReference;
116
+ const match = entry.companyDomain && companyName ? [{ companyName, companyDomain: entry.companyDomain.toLowerCase(), method: "normalized_token" as const, reference }] : [];
117
+ const redirectTrusted = entry.domainEvidence === "company_redirect" && entry.companyDomain && referenceBelongsToDomain(reference, entry.companyDomain);
118
+ const datasetTrusted = trustDomainEvidence && entry.domainEvidence && entry.domainEvidence !== "company_redirect";
119
+ const evidence: IdentityEvidence[] = (redirectTrusted || datasetTrusted) && entry.companyDomain && companyName ? [{ companyName, companyDomain: entry.companyDomain.toLowerCase(), kind: entry.domainEvidence!, reference, observedAt: new Date().toISOString() }] : [];
120
+ registryRows.push({ sourceKey: key, sourceUrl: source.canonicalSourceUrl, ats: source.ats, token: source.token,
121
+ discoveredFrom: [{ channel: entry.channel ?? "dataset", reference }], companyMatches: match, identityEvidence: evidence, attempts: [] });
122
+ if (existingSources.has(key)) {
123
+ alreadyKnown++;
124
+ continue;
125
+ }
126
+ if (!entry.companyDomain || !companyName) {
127
+ unresolved.push({ index, issue: { ...issue(entry, "needs_identity", "companyName and companyDomain are required before verification"), companyName: companyName || undefined } });
128
+ continue;
129
+ }
130
+ const providerCanAcquireIdentity = source.ats === "recruitee";
131
+ if (!redirectTrusted && !datasetTrusted && !providerCanAcquireIdentity) {
132
+ unresolved.push({ index, issue: { ...issue(entry, "needs_domain_evidence", "The entry needs trusted dataset evidence or a company-owned redirect"), companyName } });
133
+ continue;
134
+ }
135
+ ready.push({ index, candidate: {
136
+ companyName, companyDomain: entry.companyDomain.toLocaleLowerCase(), sourceUrl: source.canonicalSourceUrl,
137
+ cohorts: country ? [country] : undefined,
138
+ discoveredFrom: { channel: entry.channel ?? "dataset", reference },
139
+ ...((redirectTrusted || datasetTrusted) ? { domainEvidence: { kind: entry.domainEvidence!, reference } } : {}),
140
+ } });
141
+ }
142
+ }
143
+
144
+ await Promise.all(Array.from({ length: Math.min(concurrency, feed.length) }, worker));
145
+ const registry = options.registryPath ? await mergeEnrichmentLeads(options.registryPath, registryRows) : { added: 0 };
146
+ const additions: SourceCandidate[] = [];
147
+ const acceptedSources = new Set<string>();
148
+ for (const row of ready.sort((a, b) => a.index - b.index)) {
149
+ const key = sourceKey(row.candidate.sourceUrl)!;
150
+ if (acceptedSources.has(key)) rejected.push({ index: row.index, issue: issue({ sourceUrl: row.candidate.sourceUrl, reference: row.candidate.discoveredFrom.reference }, "duplicate_source", `Duplicate of ${key}`) });
151
+ else { acceptedSources.add(key); additions.push(row.candidate); }
152
+ }
153
+ const filteredUnresolved = unresolved.filter((row) => {
154
+ const key = sourceKey(row.issue.sourceUrl);
155
+ if (!key || !acceptedSources.has(key)) return true;
156
+ rejected.push({ index: row.index, issue: { ...row.issue, reason: "duplicate_source", detail: `Duplicate of ${key}` } });
157
+ return false;
158
+ });
159
+ const unresolvedSources = new Set<string>();
160
+ const uniqueUnresolved = filteredUnresolved.filter((row) => {
161
+ const key = sourceKey(row.issue.sourceUrl);
162
+ if (!key || !unresolvedSources.has(key)) { if (key) unresolvedSources.add(key); return true; }
163
+ rejected.push({ index: row.index, issue: { ...row.issue, reason: "duplicate_source", detail: `Duplicate of ${key}` } });
164
+ return false;
165
+ });
166
+ const appended = await mergeSourceCandidates(candidatesPath, additions);
167
+ const report: SourceDiscoveryReport = stampReport("source-discovery:1", 1, {
168
+ discovered: feed.length, ready: appended, alreadyKnown, needsDomain: uniqueUnresolved.length, rejected: rejected.length,
169
+ candidatesPath, reportPath, registryPath: options.registryPath, registryAdded: registry.added,
170
+ unresolved: uniqueUnresolved.sort((a, b) => a.index - b.index).map((row) => row.issue),
171
+ rejections: rejected.sort((a, b) => a.index - b.index).map((row) => row.issue),
172
+ });
173
+ await atomicJson(reportPath, report);
174
+ return report;
175
+ }
176
+
177
+ async function readFeed(path: string): Promise<FeedEntry[]> {
178
+ const value: unknown = JSON.parse(await readFile(path, "utf8"));
179
+ if (!Array.isArray(value) || !value.every(isRecord)) throw new Error("Discovery feed must be a JSON array of objects");
180
+ return value as unknown as FeedEntry[];
181
+ }
182
+
183
+ async function readCandidates(path: string): Promise<SourceCandidate[]> {
184
+ try {
185
+ const value: unknown = JSON.parse(await readFile(path, "utf8"));
186
+ if (!Array.isArray(value)) throw new Error("Candidate file must contain an array");
187
+ return value as SourceCandidate[];
188
+ } catch (error) {
189
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
190
+ throw error;
191
+ }
192
+ }
193
+
194
+ export async function mergeSourceCandidates(path: string, additions: SourceCandidate[]): Promise<number> {
195
+ await mkdir(dirname(path), { recursive: true });
196
+ return withFileLock(path, async () => {
197
+ const current = await readCandidates(path);
198
+ const positions = new Map(current.flatMap((candidate, index) => {
199
+ const key = sourceKey(candidate.sourceUrl);
200
+ return key ? [[key, index] as const] : [];
201
+ }));
202
+ const seen = new Set(positions.keys());
203
+ const unique = additions.filter((candidate) => {
204
+ const key = sourceKey(candidate.sourceUrl);
205
+ if (!key) return false;
206
+ const existingIndex = positions.get(key);
207
+ if (existingIndex !== undefined) {
208
+ const existing = current[existingIndex]!;
209
+ const cohorts = [...new Set([...(existing.cohorts ?? []), ...(candidate.cohorts ?? [])])].sort();
210
+ current[existingIndex] = {
211
+ ...existing,
212
+ ...(cohorts.length ? { cohorts } : {}),
213
+ ...(candidate.domainEvidence && !existing.domainEvidence ? { domainEvidence: candidate.domainEvidence, discoveredFrom: candidate.discoveredFrom } : {}),
214
+ };
215
+ return false;
216
+ }
217
+ if (seen.has(key)) return false;
218
+ seen.add(key);
219
+ return true;
220
+ });
221
+ await atomicJson(path, [...current, ...unique]);
222
+ return unique.length;
223
+ }, { operation: "merge source candidates" });
224
+ }
225
+
226
+
227
+ function sourceKey(value: string): string | undefined { const source = resolveSource(value); return source ? `${source.ats}:${source.token.toLocaleLowerCase()}` : undefined; }
228
+ function issue(entry: FeedEntry, reason: string, detail: string): DiscoveryIssue { return { sourceUrl: entry.sourceUrl, reference: entry.reference, reason, detail }; }
229
+ function referenceBelongsToDomain(reference: string, domain: string): boolean { try { const host = new URL(reference).hostname.toLowerCase().replace(/^www\./, ""); const expected = domain.toLowerCase().replace(/^www\./, ""); return host === expected || host.endsWith(`.${expected}`); } catch { return false; } }
230
+ function isRecord(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null && !Array.isArray(value); }
231
+ function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }