@agent-pattern-labs/leads-rig 0.1.4 → 0.1.6

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.
@@ -27,7 +27,9 @@ Skip this only if the task explicitly says to attach to an existing session.
27
27
  ## Do
28
28
 
29
29
  - Crawl only public, high-signal company pages.
30
- - Extract emails, role inboxes, contact forms, public names/titles, source URL, evidence, and warnings.
30
+ - Use your judgment on each email candidate: only emit an email lead when public evidence identifies one specific human owner of the address.
31
+ - Extract high-quality named human emails, contact forms, public names/titles, source URL, evidence, and warnings.
32
+ - If an address is shared, role-based, departmental, generic, blocked, or not tied to a visible named human, do not emit it as an email lead; emit a `contact_path` if useful.
31
33
  - Emit JSON/JSONL matching `templates/lead-schema.json`.
32
34
  - Run `npx public-leads validate --input <file>` before returning a result.
33
35
  - Return file paths and terminal status, not a prose-only summary.
@@ -1,4 +1,4 @@
1
- # generated by @razroo/iso-route — do not hand-edit
1
+ # generated by @agent-pattern-labs/iso-route — do not hand-edit
2
2
  model = "gpt-5.4"
3
3
  model_provider = "openai"
4
4
 
@@ -26,7 +26,9 @@ Skip this only if the task explicitly says to attach to an existing session.
26
26
  ## Do
27
27
 
28
28
  - Crawl only public, high-signal company pages.
29
- - Extract emails, role inboxes, contact forms, public names/titles, source URL, evidence, and warnings.
29
+ - Use your judgment on each email candidate: only emit an email lead when public evidence identifies one specific human owner of the address.
30
+ - Extract high-quality named human emails, contact forms, public names/titles, source URL, evidence, and warnings.
31
+ - If an address is shared, role-based, departmental, generic, blocked, or not tied to a visible named human, do not emit it as an email lead; emit a `contact_path` if useful.
30
32
  - Emit JSON/JSONL matching `templates/lead-schema.json`.
31
33
  - Run `npx public-leads validate --input <file>` before returning a result.
32
34
  - Return file paths and terminal status, not a prose-only summary.
@@ -9,7 +9,8 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
9
9
 
10
10
  ## Hard Limits
11
11
 
12
- - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt.
12
+ - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt. The primary goal is named people, not generic company inboxes.
13
+ - [H8] Apply coding-agent judgment to every email candidate. An email may pass only when public source evidence identifies one specific human owner of that email. Shared, role, departmental, generic, blocked, unknown-owner, or unnamed person-like emails must not be emitted as email leads.
13
14
  - [H2] Respect robots.txt, terms, rate limits, and site intent. If a page blocks crawling or is not public, record a warning and move on.
14
15
  - [H3] Never send outreach from this harness. The output is reviewable lead records and ingest-ready artifacts, not email campaigns.
15
16
  - [H4] Use file-backed artifacts before ingest. A subagent prose summary is not enough; write validated JSON/JSONL first, then ingest.
@@ -23,7 +24,7 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
23
24
  - [D2] Use deterministic local helpers when they can validate, normalize, manifest, preflight, postflight, or ingest artifacts.
24
25
  - [D3] Read the active mode file before executing. Mode files own output shapes and gates.
25
26
  - [D4] Prefer official company websites and high-signal pages: home, contact, about, team, leadership, people, press, blog, careers, and legal pages.
26
- - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`.
27
+ - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`. Reject generic catch-all inboxes such as `info@`, `hello@`, `contact@`, `support@`, `team@`, and similar aliases; if no named human email is available, prefer a `contact_path` record instead.
27
28
 
28
29
  ## Procedure
29
30
 
@@ -36,7 +36,9 @@ Skip this only if the task explicitly says to attach to an existing session.
36
36
  ## Do
37
37
 
38
38
  - Crawl only public, high-signal company pages.
39
- - Extract emails, role inboxes, contact forms, public names/titles, source URL, evidence, and warnings.
39
+ - Use your judgment on each email candidate: only emit an email lead when public evidence identifies one specific human owner of the address.
40
+ - Extract high-quality named human emails, contact forms, public names/titles, source URL, evidence, and warnings.
41
+ - If an address is shared, role-based, departmental, generic, blocked, or not tied to a visible named human, do not emit it as an email lead; emit a `contact_path` if useful.
40
42
  - Emit JSON/JSONL matching `templates/lead-schema.json`.
41
43
  - Run `npx public-leads validate --input <file>` before returning a result.
42
44
  - Return file paths and terminal status, not a prose-only summary.
@@ -26,7 +26,9 @@ Skip this only if the task explicitly says to attach to an existing session.
26
26
  ## Do
27
27
 
28
28
  - Crawl only public, high-signal company pages.
29
- - Extract emails, role inboxes, contact forms, public names/titles, source URL, evidence, and warnings.
29
+ - Use your judgment on each email candidate: only emit an email lead when public evidence identifies one specific human owner of the address.
30
+ - Extract high-quality named human emails, contact forms, public names/titles, source URL, evidence, and warnings.
31
+ - If an address is shared, role-based, departmental, generic, blocked, or not tied to a visible named human, do not emit it as an email lead; emit a `contact_path` if useful.
30
32
  - Emit JSON/JSONL matching `templates/lead-schema.json`.
31
33
  - Run `npx public-leads validate --input <file>` before returning a result.
32
34
  - Return file paths and terminal status, not a prose-only summary.
package/AGENTS.md CHANGED
@@ -4,7 +4,8 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
4
4
 
5
5
  ## Hard Limits
6
6
 
7
- - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt.
7
+ - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt. The primary goal is named people, not generic company inboxes.
8
+ - [H8] Apply coding-agent judgment to every email candidate. An email may pass only when public source evidence identifies one specific human owner of that email. Shared, role, departmental, generic, blocked, unknown-owner, or unnamed person-like emails must not be emitted as email leads.
8
9
  - [H2] Respect robots.txt, terms, rate limits, and site intent. If a page blocks crawling or is not public, record a warning and move on.
9
10
  - [H3] Never send outreach from this harness. The output is reviewable lead records and ingest-ready artifacts, not email campaigns.
10
11
  - [H4] Use file-backed artifacts before ingest. A subagent prose summary is not enough; write validated JSON/JSONL first, then ingest.
@@ -18,7 +19,7 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
18
19
  - [D2] Use deterministic local helpers when they can validate, normalize, manifest, preflight, postflight, or ingest artifacts.
19
20
  - [D3] Read the active mode file before executing. Mode files own output shapes and gates.
20
21
  - [D4] Prefer official company websites and high-signal pages: home, contact, about, team, leadership, people, press, blog, careers, and legal pages.
21
- - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`.
22
+ - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`. Reject generic catch-all inboxes such as `info@`, `hello@`, `contact@`, `support@`, `team@`, and similar aliases; if no named human email is available, prefer a `contact_path` record instead.
22
23
 
23
24
  ## Procedure
24
25
 
package/CLAUDE.md CHANGED
@@ -4,7 +4,8 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
4
4
 
5
5
  ## Hard Limits
6
6
 
7
- - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt.
7
+ - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt. The primary goal is named people, not generic company inboxes.
8
+ - [H8] Apply coding-agent judgment to every email candidate. An email may pass only when public source evidence identifies one specific human owner of that email. Shared, role, departmental, generic, blocked, unknown-owner, or unnamed person-like emails must not be emitted as email leads.
8
9
  - [H2] Respect robots.txt, terms, rate limits, and site intent. If a page blocks crawling or is not public, record a warning and move on.
9
10
  - [H3] Never send outreach from this harness. The output is reviewable lead records and ingest-ready artifacts, not email campaigns.
10
11
  - [H4] Use file-backed artifacts before ingest. A subagent prose summary is not enough; write validated JSON/JSONL first, then ingest.
@@ -18,7 +19,7 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
18
19
  - [D2] Use deterministic local helpers when they can validate, normalize, manifest, preflight, postflight, or ingest artifacts.
19
20
  - [D3] Read the active mode file before executing. Mode files own output shapes and gates.
20
21
  - [D4] Prefer official company websites and high-signal pages: home, contact, about, team, leadership, people, press, blog, careers, and legal pages.
21
- - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`.
22
+ - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`. Reject generic catch-all inboxes such as `info@`, `hello@`, `contact@`, `support@`, `team@`, and similar aliases; if no named human email is available, prefer a `contact_path` record instead.
22
23
 
23
24
  ## Procedure
24
25
 
package/README.md CHANGED
@@ -6,11 +6,14 @@ Agentic public-web lead discovery harness for portable, source-backed lead artif
6
6
 
7
7
  - Crawls public company websites through agent workflows.
8
8
  - Produces source-backed lead artifacts matching the local lead-ingest contract.
9
- - Validates lead JSON/JSONL before database handoff.
9
+ - Validates lead JSON/JSONL before database handoff and rejects generic catch-all inboxes such as `info@` and `hello@`.
10
10
  - Builds a local manifest for batch auditability.
11
11
  - Submits validated payloads to a configured ingest API.
12
12
  - Ships OpenCode, Claude Code, Cursor, Codex, and Pi harness config from one `iso/` source.
13
13
 
14
+ The harness is optimized to find named people who can actually be contacted. When only a general company contact path exists, it should emit a `contact_path` record rather than a generic organizational inbox.
15
+ CLI summaries distinguish total reviewable records from `goodLeads`; only non-generic `person` email records with a named human owner visible in the evidence count as good leads. Role, shared, departmental, unknown-owner, blocked, unsupported, or unnamed person-like emails are rejected by validation.
16
+
14
17
  ## Core Commands
15
18
 
16
19
  ```bash
@@ -68,7 +71,7 @@ gh run watch
68
71
  You can also publish by creating a GitHub release for the package version:
69
72
 
70
73
  ```bash
71
- gh release create v0.1.4 --title v0.1.4 --generate-notes
74
+ gh release create v0.1.6 --title v0.1.6 --generate-notes
72
75
  ```
73
76
 
74
77
  The package version must not already exist on npm.
@@ -51,7 +51,7 @@ write('package.json', JSON.stringify({
51
51
  'update-harness': 'npm install @agent-pattern-labs/leads-rig@latest && public-leads sync',
52
52
  },
53
53
  dependencies: {
54
- '@agent-pattern-labs/leads-rig': '^0.1.4',
54
+ '@agent-pattern-labs/leads-rig': '^0.1.6',
55
55
  },
56
56
  engines: {
57
57
  node: '>=20.6.0',
@@ -23,80 +23,80 @@ const commands = {
23
23
  };
24
24
 
25
25
  const helperAliases = {
26
- 'trace:list': ['@razroo/iso-trace', 'list'],
27
- 'trace:stats': ['@razroo/iso-trace', 'stats'],
28
- 'trace:show': ['@razroo/iso-trace', 'show'],
29
- 'guard:audit': ['@razroo/iso-guard', 'audit'],
30
- 'guard:explain': ['@razroo/iso-guard', 'explain'],
31
- 'ledger:status': ['@razroo/iso-ledger', 'status'],
32
- 'ledger:rebuild': ['@razroo/iso-ledger', 'rebuild'],
33
- 'ledger:verify': ['@razroo/iso-ledger', 'verify'],
34
- 'capabilities:list': ['@razroo/iso-capabilities', 'list'],
35
- 'capabilities:explain': ['@razroo/iso-capabilities', 'explain'],
36
- 'capabilities:check': ['@razroo/iso-capabilities', 'check'],
37
- 'context:list': ['@razroo/iso-context', 'list'],
38
- 'context:explain': ['@razroo/iso-context', 'explain'],
39
- 'context:plan': ['@razroo/iso-context', 'plan'],
40
- 'context:check': ['@razroo/iso-context', 'check'],
41
- 'context:render': ['@razroo/iso-context', 'render'],
42
- 'cache:key': ['@razroo/iso-cache', 'key'],
43
- 'cache:has': ['@razroo/iso-cache', 'has'],
44
- 'cache:get': ['@razroo/iso-cache', 'get'],
45
- 'cache:put': ['@razroo/iso-cache', 'put'],
46
- 'cache:status': ['@razroo/iso-cache', 'status'],
47
- 'cache:list': ['@razroo/iso-cache', 'list'],
48
- 'cache:verify': ['@razroo/iso-cache', 'verify'],
49
- 'cache:prune': ['@razroo/iso-cache', 'prune'],
50
- 'index:build': ['@razroo/iso-index', 'build'],
51
- 'index:status': ['@razroo/iso-index', 'status'],
52
- 'index:query': ['@razroo/iso-index', 'query'],
53
- 'index:has': ['@razroo/iso-index', 'has'],
54
- 'index:verify': ['@razroo/iso-index', 'verify'],
55
- 'facts:build': ['@razroo/iso-facts', 'build'],
56
- 'facts:status': ['@razroo/iso-facts', 'status'],
57
- 'facts:query': ['@razroo/iso-facts', 'query'],
58
- 'facts:has': ['@razroo/iso-facts', 'has'],
59
- 'facts:verify': ['@razroo/iso-facts', 'verify'],
60
- 'score:compute': ['@razroo/iso-score', 'compute'],
61
- 'score:verify': ['@razroo/iso-score', 'verify'],
62
- 'score:check': ['@razroo/iso-score', 'check'],
63
- 'score:gate': ['@razroo/iso-score', 'gate'],
64
- 'score:compare': ['@razroo/iso-score', 'compare'],
65
- 'score:explain': ['@razroo/iso-score', 'explain'],
66
- 'canon:normalize': ['@razroo/iso-canon', 'normalize'],
67
- 'canon:key': ['@razroo/iso-canon', 'key'],
68
- 'canon:compare': ['@razroo/iso-canon', 'compare'],
69
- 'canon:explain': ['@razroo/iso-canon', 'explain'],
70
- 'preflight:plan': ['@razroo/iso-preflight', 'plan'],
71
- 'preflight:check': ['@razroo/iso-preflight', 'check'],
72
- 'preflight:explain': ['@razroo/iso-preflight', 'explain'],
73
- 'postflight:status': ['@razroo/iso-postflight', 'status'],
74
- 'postflight:check': ['@razroo/iso-postflight', 'check'],
75
- 'postflight:explain': ['@razroo/iso-postflight', 'explain'],
76
- 'timeline:status': ['@razroo/iso-timeline', 'status'],
77
- 'timeline:build': ['@razroo/iso-timeline', 'build'],
78
- 'timeline:plan': ['@razroo/iso-timeline', 'plan'],
79
- 'timeline:due': ['@razroo/iso-timeline', 'due'],
80
- 'timeline:check': ['@razroo/iso-timeline', 'check'],
81
- 'timeline:verify': ['@razroo/iso-timeline', 'verify'],
82
- 'prioritize:status': ['@razroo/iso-prioritize', 'status'],
83
- 'prioritize:items': ['@razroo/iso-prioritize', 'items'],
84
- 'prioritize:build': ['@razroo/iso-prioritize', 'build'],
85
- 'prioritize:rank': ['@razroo/iso-prioritize', 'rank'],
86
- 'prioritize:select': ['@razroo/iso-prioritize', 'select'],
87
- 'prioritize:check': ['@razroo/iso-prioritize', 'check'],
88
- 'prioritize:verify': ['@razroo/iso-prioritize', 'verify'],
89
- 'lineage:status': ['@razroo/iso-lineage', 'status'],
90
- 'lineage:record': ['@razroo/iso-lineage', 'record'],
91
- 'lineage:check': ['@razroo/iso-lineage', 'check'],
92
- 'lineage:stale': ['@razroo/iso-lineage', 'stale'],
93
- 'lineage:verify': ['@razroo/iso-lineage', 'verify'],
94
- 'redact:scan': ['@razroo/iso-redact', 'scan'],
95
- 'redact:verify': ['@razroo/iso-redact', 'verify'],
96
- 'redact:apply': ['@razroo/iso-redact', 'apply'],
97
- 'migrate:plan': ['@razroo/iso-migrate', 'plan'],
98
- 'migrate:apply': ['@razroo/iso-migrate', 'apply'],
99
- 'migrate:check': ['@razroo/iso-migrate', 'check'],
26
+ 'trace:list': ['@agent-pattern-labs/iso-trace', 'list'],
27
+ 'trace:stats': ['@agent-pattern-labs/iso-trace', 'stats'],
28
+ 'trace:show': ['@agent-pattern-labs/iso-trace', 'show'],
29
+ 'guard:audit': ['@agent-pattern-labs/iso-guard', 'audit'],
30
+ 'guard:explain': ['@agent-pattern-labs/iso-guard', 'explain'],
31
+ 'ledger:status': ['@agent-pattern-labs/iso-ledger', 'status'],
32
+ 'ledger:rebuild': ['@agent-pattern-labs/iso-ledger', 'rebuild'],
33
+ 'ledger:verify': ['@agent-pattern-labs/iso-ledger', 'verify'],
34
+ 'capabilities:list': ['@agent-pattern-labs/iso-capabilities', 'list'],
35
+ 'capabilities:explain': ['@agent-pattern-labs/iso-capabilities', 'explain'],
36
+ 'capabilities:check': ['@agent-pattern-labs/iso-capabilities', 'check'],
37
+ 'context:list': ['@agent-pattern-labs/iso-context', 'list'],
38
+ 'context:explain': ['@agent-pattern-labs/iso-context', 'explain'],
39
+ 'context:plan': ['@agent-pattern-labs/iso-context', 'plan'],
40
+ 'context:check': ['@agent-pattern-labs/iso-context', 'check'],
41
+ 'context:render': ['@agent-pattern-labs/iso-context', 'render'],
42
+ 'cache:key': ['@agent-pattern-labs/iso-cache', 'key'],
43
+ 'cache:has': ['@agent-pattern-labs/iso-cache', 'has'],
44
+ 'cache:get': ['@agent-pattern-labs/iso-cache', 'get'],
45
+ 'cache:put': ['@agent-pattern-labs/iso-cache', 'put'],
46
+ 'cache:status': ['@agent-pattern-labs/iso-cache', 'status'],
47
+ 'cache:list': ['@agent-pattern-labs/iso-cache', 'list'],
48
+ 'cache:verify': ['@agent-pattern-labs/iso-cache', 'verify'],
49
+ 'cache:prune': ['@agent-pattern-labs/iso-cache', 'prune'],
50
+ 'index:build': ['@agent-pattern-labs/iso-index', 'build'],
51
+ 'index:status': ['@agent-pattern-labs/iso-index', 'status'],
52
+ 'index:query': ['@agent-pattern-labs/iso-index', 'query'],
53
+ 'index:has': ['@agent-pattern-labs/iso-index', 'has'],
54
+ 'index:verify': ['@agent-pattern-labs/iso-index', 'verify'],
55
+ 'facts:build': ['@agent-pattern-labs/iso-facts', 'build'],
56
+ 'facts:status': ['@agent-pattern-labs/iso-facts', 'status'],
57
+ 'facts:query': ['@agent-pattern-labs/iso-facts', 'query'],
58
+ 'facts:has': ['@agent-pattern-labs/iso-facts', 'has'],
59
+ 'facts:verify': ['@agent-pattern-labs/iso-facts', 'verify'],
60
+ 'score:compute': ['@agent-pattern-labs/iso-score', 'compute'],
61
+ 'score:verify': ['@agent-pattern-labs/iso-score', 'verify'],
62
+ 'score:check': ['@agent-pattern-labs/iso-score', 'check'],
63
+ 'score:gate': ['@agent-pattern-labs/iso-score', 'gate'],
64
+ 'score:compare': ['@agent-pattern-labs/iso-score', 'compare'],
65
+ 'score:explain': ['@agent-pattern-labs/iso-score', 'explain'],
66
+ 'canon:normalize': ['@agent-pattern-labs/iso-canon', 'normalize'],
67
+ 'canon:key': ['@agent-pattern-labs/iso-canon', 'key'],
68
+ 'canon:compare': ['@agent-pattern-labs/iso-canon', 'compare'],
69
+ 'canon:explain': ['@agent-pattern-labs/iso-canon', 'explain'],
70
+ 'preflight:plan': ['@agent-pattern-labs/iso-preflight', 'plan'],
71
+ 'preflight:check': ['@agent-pattern-labs/iso-preflight', 'check'],
72
+ 'preflight:explain': ['@agent-pattern-labs/iso-preflight', 'explain'],
73
+ 'postflight:status': ['@agent-pattern-labs/iso-postflight', 'status'],
74
+ 'postflight:check': ['@agent-pattern-labs/iso-postflight', 'check'],
75
+ 'postflight:explain': ['@agent-pattern-labs/iso-postflight', 'explain'],
76
+ 'timeline:status': ['@agent-pattern-labs/iso-timeline', 'status'],
77
+ 'timeline:build': ['@agent-pattern-labs/iso-timeline', 'build'],
78
+ 'timeline:plan': ['@agent-pattern-labs/iso-timeline', 'plan'],
79
+ 'timeline:due': ['@agent-pattern-labs/iso-timeline', 'due'],
80
+ 'timeline:check': ['@agent-pattern-labs/iso-timeline', 'check'],
81
+ 'timeline:verify': ['@agent-pattern-labs/iso-timeline', 'verify'],
82
+ 'prioritize:status': ['@agent-pattern-labs/iso-prioritize', 'status'],
83
+ 'prioritize:items': ['@agent-pattern-labs/iso-prioritize', 'items'],
84
+ 'prioritize:build': ['@agent-pattern-labs/iso-prioritize', 'build'],
85
+ 'prioritize:rank': ['@agent-pattern-labs/iso-prioritize', 'rank'],
86
+ 'prioritize:select': ['@agent-pattern-labs/iso-prioritize', 'select'],
87
+ 'prioritize:check': ['@agent-pattern-labs/iso-prioritize', 'check'],
88
+ 'prioritize:verify': ['@agent-pattern-labs/iso-prioritize', 'verify'],
89
+ 'lineage:status': ['@agent-pattern-labs/iso-lineage', 'status'],
90
+ 'lineage:record': ['@agent-pattern-labs/iso-lineage', 'record'],
91
+ 'lineage:check': ['@agent-pattern-labs/iso-lineage', 'check'],
92
+ 'lineage:stale': ['@agent-pattern-labs/iso-lineage', 'stale'],
93
+ 'lineage:verify': ['@agent-pattern-labs/iso-lineage', 'verify'],
94
+ 'redact:scan': ['@agent-pattern-labs/iso-redact', 'scan'],
95
+ 'redact:verify': ['@agent-pattern-labs/iso-redact', 'verify'],
96
+ 'redact:apply': ['@agent-pattern-labs/iso-redact', 'apply'],
97
+ 'migrate:plan': ['@agent-pattern-labs/iso-migrate', 'plan'],
98
+ 'migrate:apply': ['@agent-pattern-labs/iso-migrate', 'apply'],
99
+ 'migrate:check': ['@agent-pattern-labs/iso-migrate', 'check'],
100
100
  };
101
101
 
102
102
  const [, , cmd, ...rest] = process.argv;
@@ -161,7 +161,7 @@ function runIsoCli(pkgName, args) {
161
161
 
162
162
  function defaultIsoArgs(pkgName, subcommand, args) {
163
163
  const defaults = [];
164
- if (pkgName === '@razroo/iso-trace') {
164
+ if (pkgName === '@agent-pattern-labs/iso-trace') {
165
165
  if (!hasFlag(args, '--cwd')) defaults.push('--cwd', PROJECT_DIR);
166
166
  if ((subcommand === 'list' || subcommand === 'stats') && !hasFlag(args, '--since')) {
167
167
  defaults.push('--since', '7d');
@@ -68,10 +68,10 @@ domain input
68
68
  | `scripts/validate-leads.mjs` | Normalizes and validates JSON/JSONL lead artifacts |
69
69
  | `scripts/manifest.mjs` | Records validated batches in `data/lead-manifest.json` |
70
70
  | `scripts/ingest.mjs` | Posts to the configured ingest endpoint |
71
- | `scripts/batch-orchestrator.mjs` | Durable batch runner using `@razroo/iso-orchestrator` |
71
+ | `scripts/batch-orchestrator.mjs` | Durable batch runner using `@agent-pattern-labs/iso-orchestrator` |
72
72
  | `verify-pipeline.mjs` | Repo/consumer health gate |
73
73
 
74
- The package also carries the `@razroo/iso-*` helper ecosystem for trace, guard, ledger, cache, canon, context, preflight, postflight, prioritize, lineage, redact, migrate, score, and timeline commands.
74
+ The package also carries the `@agent-pattern-labs/iso-*` helper ecosystem for trace, guard, ledger, cache, canon, context, preflight, postflight, prioritize, lineage, redact, migrate, score, and timeline commands.
75
75
 
76
76
  ## Data Contract
77
77
 
@@ -82,4 +82,4 @@ The package also carries the `@razroo/iso-*` helper ecosystem for trace, guard,
82
82
  - page visits
83
83
  - ingest requests
84
84
 
85
- The validator intentionally accepts the same defaults the local runtime normalizes, but it fails missing source evidence, invalid URL fields, invalid `emailType`, invalid `verificationStatus`, invalid confidence, and person/role leads without email.
85
+ The validator intentionally accepts the same defaults the local runtime normalizes, but it fails missing source evidence, invalid URL fields, invalid `emailType`, invalid `verificationStatus`, invalid confidence, person leads without email, and generic catch-all inboxes such as `info@`, `hello@`, or similar organizational aliases. Any email record must be a named `person` lead with a non-generic email and a human owner visible in the evidence; role inboxes, unknown-owner emails, blocked emails, unsupported emails, and unnamed person-like emails fail validation. Summaries expose `goodLeadCount`, which only counts those high-quality named human email records.
@@ -14,7 +14,7 @@
14
14
  | Deterministic helpers | `scripts/crawl.mjs`, `scripts/pipeline.mjs`, `scripts/validate-leads.mjs`, `scripts/manifest.mjs`, `scripts/ingest.mjs`, `scripts/batch-orchestrator.mjs` |
15
15
  | Consumer scaffold | `create-public-leads-harness <dir>` writes local config, input, output, and ignore files |
16
16
  | Consumer sync | `public-leads sync` symlinks shared harness files from the installed package |
17
- | Batch orchestration | `batch/batch-runner.sh` delegates to `@razroo/iso-orchestrator` with bounded parallelism |
17
+ | Batch orchestration | `batch/batch-runner.sh` delegates to `@agent-pattern-labs/iso-orchestrator` with bounded parallelism |
18
18
  | Verification gates | `npm run verify`, `npm run smoke:iso`, sample validate/manifest/ingest, `npm pack` |
19
19
 
20
20
  ## Maintainer Rules
@@ -37,4 +37,4 @@
37
37
  6. Run `npm audit`.
38
38
  7. Run `npm pack` and confirm generated runtime surfaces are included.
39
39
  8. Scaffold a clean consumer project and test install-time sync from the packed package or source tree.
40
- 9. Publish through GitHub Actions with `gh workflow run publish.yml --ref main`, or create a published release with `gh release create v0.1.4 --title v0.1.4 --generate-notes`.
40
+ 9. Publish through GitHub Actions with `gh workflow run publish.yml --ref main`, or create a published release with `gh release create v0.1.6 --title v0.1.6 --generate-notes`.
@@ -38,7 +38,9 @@ Skip this only if the task explicitly says to attach to an existing session.
38
38
  ## Do
39
39
 
40
40
  - Crawl only public, high-signal company pages.
41
- - Extract emails, role inboxes, contact forms, public names/titles, source URL, evidence, and warnings.
41
+ - Use your judgment on each email candidate: only emit an email lead when public evidence identifies one specific human owner of the address.
42
+ - Extract high-quality named human emails, contact forms, public names/titles, source URL, evidence, and warnings.
43
+ - If an address is shared, role-based, departmental, generic, blocked, or not tied to a visible named human, do not emit it as an email lead; emit a `contact_path` if useful.
42
44
  - Emit JSON/JSONL matching `templates/lead-schema.json`.
43
45
  - Run `npx public-leads validate --input <file>` before returning a result.
44
46
  - Return file paths and terminal status, not a prose-only summary.
@@ -4,7 +4,8 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
4
4
 
5
5
  ## Hard Limits
6
6
 
7
- - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt.
7
+ - [H1] Only collect public-source business contact data. Do not infer, guess, brute-force, or enrich private email addresses. Every lead needs a source URL and evidence excerpt. The primary goal is named people, not generic company inboxes.
8
+ - [H8] Apply coding-agent judgment to every email candidate. An email may pass only when public source evidence identifies one specific human owner of that email. Shared, role, departmental, generic, blocked, unknown-owner, or unnamed person-like emails must not be emitted as email leads.
8
9
  - [H2] Respect robots.txt, terms, rate limits, and site intent. If a page blocks crawling or is not public, record a warning and move on.
9
10
  - [H3] Never send outreach from this harness. The output is reviewable lead records and ingest-ready artifacts, not email campaigns.
10
11
  - [H4] Use file-backed artifacts before ingest. A subagent prose summary is not enough; write validated JSON/JSONL first, then ingest.
@@ -18,7 +19,7 @@ Agentic public-web lead discovery harness. It crawls company domains, extracts r
18
19
  - [D2] Use deterministic local helpers when they can validate, normalize, manifest, preflight, postflight, or ingest artifacts.
19
20
  - [D3] Read the active mode file before executing. Mode files own output shapes and gates.
20
21
  - [D4] Prefer official company websites and high-signal pages: home, contact, about, team, leadership, people, press, blog, careers, and legal pages.
21
- - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`.
22
+ - [D5] Keep artifacts source-backed. Every emitted lead should include `domain`, `sourceUrl`, `evidence`, `emailType`, `extractionMethod`, `verificationStatus`, and `confidence`. Reject generic catch-all inboxes such as `info@`, `hello@`, `contact@`, `support@`, `team@`, and similar aliases; if no named human email is available, prefer a `contact_path` record instead.
22
23
 
23
24
  ## Procedure
24
25
 
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { lookup, resolveMx } from 'dns/promises';
3
3
  import { setTimeout as sleep } from 'timers/promises';
4
- import { cleanDomains, dedupeLeadRecords, mergeLeadRecords, normalizeDomain } from './leadharness-leads.mjs';
4
+ import { cleanDomains, dedupeLeadRecords, isGoodLead, isRejectedLeadEmail, mergeLeadRecords, normalizeDomain } from './leadharness-leads.mjs';
5
5
 
6
6
  const DEFAULT_MAX_PAGES = 10;
7
7
  const HARD_MAX_PAGES = 25;
@@ -366,7 +366,7 @@ function extractEmails(html) {
366
366
  for (const value of values) {
367
367
  for (const found of String(value || '').matchAll(EMAIL_RE)) {
368
368
  const email = found[0].toLowerCase().replace(/^mailto:/, '').replace(/[.,;:!?()[\]{}<>"']+$/g, '');
369
- if (email && !isLikelyAssetEmail(email)) emails.add(email);
369
+ if (email && !isLikelyAssetEmail(email) && !isRejectedLeadEmail(email)) emails.add(email);
370
370
  }
371
371
  }
372
372
  return [...emails].sort();
@@ -575,6 +575,7 @@ function classifyEmailLocal(local) {
575
575
  }
576
576
 
577
577
  function isRelevantLead(lead, options) {
578
+ if (lead.email && !isGoodLead(lead)) return false;
578
579
  if (!options.includeBlocked && lead.emailType === 'blocked') return false;
579
580
  return lead.confidence >= options.minConfidence;
580
581
  }
@@ -28,6 +28,37 @@ export const ALLOWED_VERIFICATION_STATUSES = new Set([
28
28
  'blocked',
29
29
  'unknown',
30
30
  ]);
31
+ export const REJECTED_EMAIL_LOCAL_PREFIXES = [
32
+ 'admin',
33
+ 'booking',
34
+ 'bookings',
35
+ 'business',
36
+ 'contact',
37
+ 'customerservice',
38
+ 'customer-service',
39
+ 'customersuccess',
40
+ 'customer-success',
41
+ 'events',
42
+ 'hello',
43
+ 'help',
44
+ 'hi',
45
+ 'info',
46
+ 'inquiries',
47
+ 'enquiries',
48
+ 'media',
49
+ 'office',
50
+ 'outreach',
51
+ 'partners',
52
+ 'partnerships',
53
+ 'press',
54
+ 'sales',
55
+ 'service',
56
+ 'services',
57
+ 'speaking',
58
+ 'success',
59
+ 'support',
60
+ 'team',
61
+ ];
31
62
 
32
63
  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
33
64
 
@@ -232,6 +263,9 @@ export function validatePayload(payload, { allowEmpty = false } = {}) {
232
263
 
233
264
  results.forEach((result, index) => {
234
265
  validateResult(result, `results[${index}]`, issues);
266
+ result.leads.forEach((lead, leadIndex) => {
267
+ validateLead(lead, `results[${index}].leads[${leadIndex}]`, issues);
268
+ });
235
269
  });
236
270
 
237
271
  const errors = issues.filter((item) => item.severity === 'error').length;
@@ -259,6 +293,12 @@ export function validateLead(lead, path, issues) {
259
293
  if (lead.email && !EMAIL_RE.test(lead.email)) {
260
294
  issues.push(issue('error', `${path}.email`, 'invalid_email', 'email must be a valid email address when present'));
261
295
  }
296
+ if (lead.email && isRejectedLeadEmail(lead.email)) {
297
+ issues.push(issue('error', `${path}.email`, 'generic_inbox_rejected', 'generic company inboxes such as info@, hello@, or support@ are not accepted; submit named people instead'));
298
+ }
299
+ if (lead.email && !isRejectedLeadEmail(lead.email) && ALLOWED_EMAIL_TYPES.has(lead.emailType) && !isGoodLead(lead)) {
300
+ issues.push(issue('error', `${path}.emailType`, 'non_person_email_rejected', 'email records must identify one named human visible in the evidence; role, unknown, blocked, unsupported, or unnamed person emails are not accepted'));
301
+ }
262
302
  if (lead.emailType && !ALLOWED_EMAIL_TYPES.has(lead.emailType)) {
263
303
  issues.push(issue('error', `${path}.emailType`, 'invalid_email_type', `emailType must be one of ${[...ALLOWED_EMAIL_TYPES].join(', ')}`));
264
304
  }
@@ -344,11 +384,13 @@ export function summarizePayload(payload) {
344
384
  const byType = {};
345
385
  let confidenceTotal = 0;
346
386
  let withEmail = 0;
387
+ let goodLeadCount = 0;
347
388
  const uniqueEmails = new Set();
348
389
  const mxVerifiedEmails = new Set();
349
390
  for (const lead of leads) {
350
391
  byType[lead.emailType || 'unknown'] = (byType[lead.emailType || 'unknown'] || 0) + 1;
351
392
  confidenceTotal += Number.isFinite(lead.confidence) ? lead.confidence : 0;
393
+ if (isGoodLead(lead)) goodLeadCount++;
352
394
  if (lead.email) {
353
395
  withEmail++;
354
396
  uniqueEmails.add(String(lead.email).toLowerCase());
@@ -362,6 +404,8 @@ export function summarizePayload(payload) {
362
404
  domainCount: domains.length,
363
405
  leadCount: leads.length,
364
406
  resultCount: results.length,
407
+ goodLeadCount,
408
+ reviewOnlyLeadCount: Math.max(0, leads.length - goodLeadCount),
365
409
  withEmail,
366
410
  uniqueEmailCount: uniqueEmails.size,
367
411
  mxVerifiedEmailCount: mxVerifiedEmails.size,
@@ -380,6 +424,7 @@ export function createManifestRecord({ inputPath, payload, validation, generated
380
424
  generatedAt,
381
425
  domains: payload.domains,
382
426
  leadCount: payload.leads.length,
427
+ goodLeadCount: validation.summary?.goodLeadCount || 0,
383
428
  resultCount: payload.results.length,
384
429
  errorCount: payload.errors.length,
385
430
  validation: {
@@ -611,7 +656,10 @@ function stableLeadKey(lead) {
611
656
  }
612
657
 
613
658
  function leadQualityScore(lead) {
614
- return (lead?.confidence || 0) * 1000
659
+ const goodLead = isGoodLead(lead);
660
+ return (goodLead ? 50_000 : 0)
661
+ + (lead?.email && !goodLead ? -20_000 : 0)
662
+ + (lead?.confidence || 0) * 1000
615
663
  + verificationRank(lead?.verificationStatus) * 100
616
664
  + pagePriority(lead?.sourceUrl || '') * 10
617
665
  + (Array.isArray(lead?.sources) ? lead.sources.length : 0) * 5
@@ -685,6 +733,54 @@ export function fileHash(path) {
685
733
  return createHash('sha256').update(readFileSync(path)).digest('hex');
686
734
  }
687
735
 
736
+ export function isRejectedLeadEmail(email) {
737
+ const local = String(email || '').trim().toLowerCase().split('@')[0] || '';
738
+ return isRejectedLeadLocalPart(local);
739
+ }
740
+
741
+ export function isRejectedLeadLocalPart(local) {
742
+ const value = normalizeRejectedLeadLocalPart(local);
743
+ if (!value) return false;
744
+ return REJECTED_EMAIL_LOCAL_PREFIXES.some((prefix) => {
745
+ if (value === prefix) return true;
746
+ if (!value.startsWith(prefix) || value.length === prefix.length) return false;
747
+ const next = value[prefix.length];
748
+ return next === '.' || next === '_' || next === '-' || next === '+' || /\d/.test(next);
749
+ });
750
+ }
751
+
752
+ export function isGoodLead(lead) {
753
+ if (!lead || typeof lead !== 'object') return false;
754
+ if (stringValue(lead.emailType).toLowerCase() !== 'person') return false;
755
+ if (!lead.email || isRejectedLeadEmail(lead.email)) return false;
756
+ const contactName = stringValue(lead.contactName);
757
+ if (!contactName) return false;
758
+ if (!evidenceIdentifiesContact(lead, contactName)) return false;
759
+ return true;
760
+ }
761
+
762
+ function evidenceIdentifiesContact(lead, contactName) {
763
+ const needle = compactComparable(contactName);
764
+ if (!needle) return false;
765
+ const evidence = [
766
+ lead.evidence,
767
+ ...(Array.isArray(lead.sources) ? lead.sources.map((source) => source?.evidence) : []),
768
+ ].map(compactComparable).join(' ');
769
+ return evidence.includes(needle);
770
+ }
771
+
772
+ function compactComparable(value) {
773
+ return stringValue(value).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
774
+ }
775
+
776
+ function normalizeRejectedLeadLocalPart(local) {
777
+ let value = String(local || '').trim().toLowerCase();
778
+ for (const noise of ['mailto', 'u003e', 'email']) {
779
+ if (value.startsWith(noise)) value = value.slice(noise.length);
780
+ }
781
+ return value.replace(/^[>._+-]+/, '');
782
+ }
783
+
688
784
  export function issue(severity, path, code, message) {
689
785
  return { severity, path, code, message };
690
786
  }
package/modes/_shared.md CHANGED
@@ -28,17 +28,21 @@ Required fields:
28
28
  - Prioritize home, contact, about, team, leadership, people, press, blog, careers, and legal pages.
29
29
  - Respect robots.txt, paywalls, login walls, and obvious anti-scraping notices.
30
30
  - Do not infer email formats. Do not brute-force guessed addresses.
31
+ - Use coding-agent judgment for every email candidate. Only emit an email lead when the source evidence identifies one specific human behind that address.
32
+ - Prefer named people over organizational inboxes. Do not emit generic catch-all emails such as `info@`, `hello@`, `contact@`, `support@`, `team@`, or similar aliases. If only a general contact path exists, emit `contact_path` instead.
33
+ - Reject email records that are role-based, shared, departmental, unknown ownership, blocked operational inboxes, or person-like but missing a visible human name in the source evidence.
31
34
  - Keep contact forms as `emailType: "contact_path"` with empty `email`.
32
35
  - Mark operational no-contact inboxes such as `noreply`, `abuse`, `security`, and `legal` as `blocked` with confidence `0`.
33
36
 
34
37
  ## Quality Heuristics
35
38
 
36
- - `person`: named person or person-like email with source context.
37
- - `role`: public role inbox such as `sales@`, `partnerships@`, or `press@`.
39
+ - `goodLeadCount`: counts only `person` email records with a non-generic email and named `contactName` visible in the evidence. Generic, role, unknown, blocked, unnamed, unsupported, and `contact_path` records are not good leads.
40
+ - `person`: named human with source context tying that person to the email address.
41
+ - `role`: legacy/review-only classification. Do not emit role email records in new artifacts.
38
42
  - `contact_path`: public form or page where no email is available.
39
43
  - `blocked`: public address that should not be used for outbound.
40
44
 
41
- Confidence should reflect source quality, person specificity, domain match, and verification. Low-confidence records are allowed if they preserve warnings and evidence.
45
+ Confidence should reflect source quality, person specificity, domain match, and verification. Low-confidence contact paths are allowed if they preserve warnings and evidence; email leads should be high-quality named humans only.
42
46
 
43
47
  ## Local Commands
44
48
 
package/modes/batch.md CHANGED
@@ -10,7 +10,7 @@ Prefer:
10
10
  batch/batch-runner.sh --parallel 2
11
11
  ```
12
12
 
13
- The runner delegates to `scripts/batch-orchestrator.mjs`, which uses `@razroo/iso-orchestrator` for durable workflow records in `.public-leads-runs/`, bounded fan-out, and state updates.
13
+ The runner delegates to `scripts/batch-orchestrator.mjs`, which uses `@agent-pattern-labs/iso-orchestrator` for durable workflow records in `.public-leads-runs/`, bounded fan-out, and state updates.
14
14
 
15
15
  ## Input
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-pattern-labs/leads-rig",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Agentic public-web lead discovery harness with portable ingest artifacts",
5
5
  "type": "module",
6
6
  "author": "Razroo",
@@ -155,32 +155,32 @@
155
155
  "fast-uri": "3.1.2"
156
156
  },
157
157
  "dependencies": {
158
- "@razroo/iso-cache": "^0.1.0",
159
- "@razroo/iso-canon": "^0.1.0",
160
- "@razroo/iso-capabilities": "^0.1.0",
161
- "@razroo/iso-context": "^0.1.0",
162
- "@razroo/iso-contract": "^0.1.0",
163
- "@razroo/iso-facts": "^0.1.0",
164
- "@razroo/iso-guard": "^0.1.0",
165
- "@razroo/iso-index": "^0.1.0",
166
- "@razroo/iso-ledger": "^0.1.0",
167
- "@razroo/iso-lineage": "^0.1.0",
168
- "@razroo/iso-migrate": "^0.1.0",
169
- "@razroo/iso-orchestrator": "^0.2.0",
170
- "@razroo/iso-postflight": "^0.1.0",
171
- "@razroo/iso-preflight": "^0.1.0",
172
- "@razroo/iso-prioritize": "^0.1.0",
173
- "@razroo/iso-redact": "^0.1.0",
174
- "@razroo/iso-score": "^0.1.0",
175
- "@razroo/iso-timeline": "^0.1.0",
176
- "@razroo/iso-trace": "^0.5.0",
158
+ "@agent-pattern-labs/iso-cache": "^0.1.1",
159
+ "@agent-pattern-labs/iso-canon": "^0.1.1",
160
+ "@agent-pattern-labs/iso-capabilities": "^0.1.1",
161
+ "@agent-pattern-labs/iso-context": "^0.1.1",
162
+ "@agent-pattern-labs/iso-contract": "^0.1.1",
163
+ "@agent-pattern-labs/iso-facts": "^0.1.1",
164
+ "@agent-pattern-labs/iso-guard": "^0.1.1",
165
+ "@agent-pattern-labs/iso-index": "^0.1.1",
166
+ "@agent-pattern-labs/iso-ledger": "^0.1.1",
167
+ "@agent-pattern-labs/iso-lineage": "^0.1.1",
168
+ "@agent-pattern-labs/iso-migrate": "^0.1.1",
169
+ "@agent-pattern-labs/iso-orchestrator": "^0.2.1",
170
+ "@agent-pattern-labs/iso-postflight": "^0.1.1",
171
+ "@agent-pattern-labs/iso-preflight": "^0.1.1",
172
+ "@agent-pattern-labs/iso-prioritize": "^0.1.1",
173
+ "@agent-pattern-labs/iso-redact": "^0.1.1",
174
+ "@agent-pattern-labs/iso-score": "^0.1.1",
175
+ "@agent-pattern-labs/iso-timeline": "^0.1.1",
176
+ "@agent-pattern-labs/iso-trace": "^0.5.1",
177
177
  "playwright": "^1.58.1"
178
178
  },
179
179
  "devDependencies": {
180
- "@razroo/agentmd": "^0.3.0",
181
- "@razroo/iso": "^0.3.1",
182
- "@razroo/iso-eval": "^0.4.0",
183
- "@razroo/iso-harness": "^0.8.0",
184
- "@razroo/iso-route": "^0.6.0"
180
+ "@agent-pattern-labs/agentmd": "^0.3.1",
181
+ "@agent-pattern-labs/iso": "^0.3.2",
182
+ "@agent-pattern-labs/iso-eval": "^0.4.1",
183
+ "@agent-pattern-labs/iso-harness": "^0.8.1",
184
+ "@agent-pattern-labs/iso-route": "^0.6.1"
185
185
  }
186
186
  }
@@ -6,7 +6,7 @@ import { existsSync } from 'fs';
6
6
  import { mkdir, readFile, rm, writeFile } from 'fs/promises';
7
7
  import { dirname, join, resolve } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
- import { runWorkflow } from '@razroo/iso-orchestrator';
9
+ import { runWorkflow } from '@agent-pattern-labs/iso-orchestrator';
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
  const PKG_ROOT = resolve(__dirname, '..');
package/scripts/crawl.mjs CHANGED
@@ -57,7 +57,7 @@ async function main() {
57
57
  console.log(JSON.stringify({ output: out, validation, summary: validation.summary }, null, 2));
58
58
  } else {
59
59
  console.log(`crawl: wrote ${relativeProjectPath(resolveProjectPath(out))}`);
60
- console.log(`domains=${validation.summary.domainCount} leads=${validation.summary.leadCount} results=${validation.summary.resultCount} errors=${payload.errors.length}`);
60
+ console.log(`domains=${validation.summary.domainCount} leads=${validation.summary.leadCount} goodLeads=${validation.summary.goodLeadCount} results=${validation.summary.resultCount} errors=${payload.errors.length}`);
61
61
  }
62
62
 
63
63
  if (!validation.ok) {
@@ -50,6 +50,7 @@ try {
50
50
  batchCount: manifest.batches.length,
51
51
  readyForIngest: manifest.batches.filter((item) => item.readyForIngest).length,
52
52
  leadCount: manifest.batches.reduce((sum, item) => sum + item.leadCount, 0),
53
+ goodLeadCount: manifest.batches.reduce((sum, item) => sum + (item.goodLeadCount || 0), 0),
53
54
  errorCount: manifest.batches.reduce((sum, item) => sum + item.validation.errors, 0),
54
55
  warningCount: manifest.batches.reduce((sum, item) => sum + item.validation.warnings, 0),
55
56
  };
@@ -60,8 +61,8 @@ try {
60
61
  console.log(JSON.stringify({ manifest: relativeProjectPath(manifestPath), record, summary: manifest.summary }, null, 2));
61
62
  } else {
62
63
  console.log(`manifest: ${relativeProjectPath(manifestPath)}`);
63
- console.log(`batch: ${record.id} leads=${record.leadCount} ready=${record.readyForIngest}`);
64
- console.log(`summary: batches=${manifest.summary.batchCount} ready=${manifest.summary.readyForIngest} leads=${manifest.summary.leadCount}`);
64
+ console.log(`batch: ${record.id} leads=${record.leadCount} goodLeads=${record.goodLeadCount} ready=${record.readyForIngest}`);
65
+ console.log(`summary: batches=${manifest.summary.batchCount} ready=${manifest.summary.readyForIngest} leads=${manifest.summary.leadCount} goodLeads=${manifest.summary.goodLeadCount}`);
65
66
  }
66
67
 
67
68
  process.exit(validation.ok ? 0 : 1);
@@ -66,7 +66,7 @@ try {
66
66
  const manifest = upsertManifest(manifestPath, out, payload, validation);
67
67
  console.log(`pipeline: wrote ${relativeProjectPath(resolveProjectPath(out))}`);
68
68
  console.log(`manifest: ${relativeProjectPath(resolveProjectPath(manifestPath))}`);
69
- console.log(`domains=${validation.summary.domainCount} leads=${validation.summary.leadCount} results=${validation.summary.resultCount} errors=${payload.errors.length}`);
69
+ console.log(`domains=${validation.summary.domainCount} leads=${validation.summary.leadCount} goodLeads=${validation.summary.goodLeadCount} results=${validation.summary.resultCount} errors=${payload.errors.length}`);
70
70
 
71
71
  if (opts.ingest || opts.upload) {
72
72
  const ingestResult = await ingestPayload(payload, {
@@ -110,6 +110,7 @@ function upsertManifest(manifestPath, inputPath, payload, validation) {
110
110
  batchCount: manifest.batches.length,
111
111
  readyForIngest: manifest.batches.filter((item) => item.readyForIngest).length,
112
112
  leadCount: manifest.batches.reduce((sum, item) => sum + item.leadCount, 0),
113
+ goodLeadCount: manifest.batches.reduce((sum, item) => sum + (item.goodLeadCount || 0), 0),
113
114
  errorCount: manifest.batches.reduce((sum, item) => sum + item.validation.errors, 0),
114
115
  warningCount: manifest.batches.reduce((sum, item) => sum + item.validation.warnings, 0),
115
116
  };
@@ -58,7 +58,7 @@ try {
58
58
  function printReport(report) {
59
59
  const rel = relativeProjectPath(report.input);
60
60
  console.log(`${report.ok ? 'OK' : 'ERROR'} ${rel}`);
61
- console.log(`domains=${report.summary.domainCount} leads=${report.summary.leadCount} results=${report.summary.resultCount} avgConfidence=${report.summary.averageConfidence}`);
61
+ console.log(`domains=${report.summary.domainCount} leads=${report.summary.leadCount} goodLeads=${report.summary.goodLeadCount} results=${report.summary.resultCount} avgConfidence=${report.summary.averageConfidence}`);
62
62
  if (Object.keys(report.summary.byType).length > 0) {
63
63
  console.log(`types=${Object.entries(report.summary.byType).map(([type, count]) => `${type}:${count}`).join(', ')}`);
64
64
  }