fullstackgtm 0.40.0 → 0.42.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,63 @@ The path to 1.0 is planned in [docs/roadmap-to-1.0.md](./docs/roadmap-to-1.0.md)
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.42.0] — 2026-06-25
11
+
12
+ ### Added
13
+
14
+ - **Bulk apply for lead creation (HubSpot + Salesforce).** `applyPatchPlan` now
15
+ routes independent, approved `create_record` **contact** ops through a new
16
+ optional connector capability, `applyCreateContactsBatch`, instead of two API
17
+ round-trips per lead (a resolve-first search + a create). HubSpot batches the
18
+ resolve-first via a `search` `IN` and creates via `batch/create` (100/call);
19
+ Salesforce batches via a SOQL `IN` and the Composite sObject Collections API
20
+ (`allOrNone: false`, 200/call). A batch rejection (e.g. a duplicate that the
21
+ provider 4xxs the whole batch over) falls back to per-record `createRecord`, so
22
+ one bad row never sinks the rest and resolve-first is preserved. Turns ~2N API
23
+ calls into ~N/100; ops that aren't safe to batch (grouped, value-overridden,
24
+ company-associated, conflicted, or non-create) stay on the serial path
25
+ unchanged. Connectors without the capability are unaffected.
26
+
27
+ ## [0.41.0] — 2026-06-23
28
+
29
+ ### Added
30
+
31
+ - **Signal-based outbound: `signals`, `icp judge`/`icp eval`, `draft`.** A timing
32
+ layer on top of CRM hygiene — reach an account the week something changes, not
33
+ from a rented list. `signals fetch/list/outcome/weights` captures buying
34
+ triggers into a local, profile-scoped ledger (Detect-side — it writes nothing
35
+ to the CRM), sorted into five weighted buckets
36
+ (`demand`/`funding`/`job`/`company`/`social`); public ATS boards (Greenhouse,
37
+ Lever, Ashby) are the free, no-auth source in the box, while
38
+ funding/company/social arrive via staged ingest and `demand` is reserved for a
39
+ privileged source. A reposted role outweighs a first-time post, and recorded
40
+ outcomes (`signals outcome`) re-weight which buckets earn a touch. `icp judge`
41
+ scores each account on timing × fit × memory into `send`/`nurture`/`skip` —
42
+ every *why-now* must quote a real trigger verbatim (the same evidence gate as
43
+ `call`/`market`), with a deterministic baseline when no LLM key is set.
44
+ `icp eval` grades the judge against a golden set (and hot-vs-cold outcomes),
45
+ exiting `2` below the bar — the calibration gate that blocks a miscalibrated
46
+ judge from a live send. `draft` writes one trigger-grounded opener per hot
47
+ account as a governed `create_task` plan through the existing approve → apply
48
+ gate; it has no send capability and adds none. All four are read/plan-side and
49
+ schedulable; none can auto-apply or send.
50
+ - **LLM base-URL override.** `ANTHROPIC_API_BASE_URL` / `OPENAI_API_BASE_URL` let
51
+ every LLM feature run against an Anthropic/OpenAI-compatible endpoint (e.g. a
52
+ GLM/z.ai endpoint or a local Ollama) with no code change; unset preserves the
53
+ default endpoints.
54
+
55
+ ### Changed
56
+
57
+ - **pipe0 resolution runs chunks in parallel (bounded) with exponential backoff.**
58
+ `pipe0ResolveWorkEmails` and `pipe0ResolveCompanyDomains` previously issued one
59
+ serial HTTP call per chunk — a few hundred leads took tens of minutes. They now
60
+ run up to `concurrency` chunks at once (default 3) with exponential backoff on
61
+ transient failures (throttle/5xx/network: 500ms → 4s, up to 5 attempts). Bounded
62
+ concurrency + real backoff cuts wall-clock several-fold while keeping coverage:
63
+ an early concurrency-6 + single-retry build throttled pipe0 at scale and dropped
64
+ the work-email hit-rate from ~79% to ~17%, so the pairing matters. Chunk size
65
+ stays small (the waterfall's batch failure is all-or-nothing).
66
+
10
67
  ## [0.40.0] — 2026-06-23
11
68
 
12
69
  ### Added
package/README.md CHANGED
@@ -206,6 +206,35 @@ LinkedIn is just another discovery source on the same scored → deduped → met
206
206
 
207
207
  **Leads are never born ownerless.** An `acquire.assign` policy stamps an owner onto every created lead at create time (mapped to HubSpot `hubspot_owner_id`), routed by one of four strategies — `fixed`, `round-robin` (distributed deterministically, no rotation state to drift), `territory` (by geo / industry / size / department / title), or `account-owner` (inherit the matched company's owner). With no policy and a single-owner portal, acquire defaults every lead to that owner (or pass `--assign-owner <id>`); with multiple owners and no policy it warns and leaves them unassigned rather than guess. To clear *existing* ownerless records, `reassign --assign-unowned --to <ownerId>` applies the same intent as a backfill.
208
208
 
209
+ ## Signal-based outbound: reach accounts the week something changes
210
+
211
+ Cleaning and filling the CRM tells you *who* to reach; it never tells you *when*. The **signal → judge → draft** loop adds timing — and, like everything else, it stays on the dry-run → approve → apply spine and sends nothing.
212
+
213
+ ```bash
214
+ # 1. Watch for movement. Free, no-auth public job boards in the box; funding/company/social via staged ingest.
215
+ fullstackgtm signals fetch --bucket job --source greenhouse,lever,ashby --keywords "revops,growth" --save
216
+ fullstackgtm signals list --since 7d # ranked triggers, each with a verbatim source quote
217
+
218
+ # 2. Decide who's worth a touch — and who isn't. Scores timing × fit × memory into send/nurture/skip.
219
+ fullstackgtm icp judge --signals-from latest --with-history --save
220
+ fullstackgtm icp eval --golden default # gate: prove the judge is calibrated before any send (exits 2 if not)
221
+
222
+ # 3. Draft the opener from the trigger. A create_task plan — proposed, never transmitted.
223
+ fullstackgtm draft --from-judge latest --min-score 80 --save
224
+ fullstackgtm plans approve <id> --operations all && fullstackgtm apply --plan-id <id> --provider hubspot
225
+
226
+ # 4. Close the loop. Outcomes re-weight which signals earn a touch.
227
+ fullstackgtm signals outcome --account acme.com --result replied
228
+ ```
229
+
230
+ **`signals`** is Detect-side — it captures triggers into a local, profile-scoped ledger and writes *nothing* to your CRM. The five buckets (`demand`, `funding`, `job`, `company`, `social`) are not equal: a fresh round outweighs a lone social like, and a *reposted* role — the first hire fell through — outweighs a first-time post. Public ATS boards (Greenhouse, Lever, Ashby) are the free, no-auth source shipped in the box; funding/company/social arrive through `--from` staged ingest (an agent or a feed supplies them — the CLI scrapes no one), and `demand` (first-party intent) is reserved for a privileged source.
231
+
232
+ **`icp judge`** turns raw signals into a short ranked list: it scores each account on **timing × fit × memory** — the signal's weight and recency, your ICP fit, and whether you've touched the account in the last 7 days — and returns `send` / `nurture` / `skip`. Every *why-now* it produces must quote a real trigger **verbatim** (the same evidence gate as `call` and `market`); an ungrounded why-now is never stored. With no LLM key it runs a deterministic baseline. **`icp eval`** is the gate the demos skip: it grades the judge against a labeled golden set (and, once outcomes exist, checks that accounts scored hot actually book more than cold), exiting `2` below the bar so a miscalibrated judge can't reach a live send.
233
+
234
+ **`draft`** writes one opener per hot account whose first line quotes the trigger in the buyer's own words — no "Hi {{firstName}}", one ask, no manufactured urgency. It emits a `create_task` plan through the normal approval gate; it has **no send capability** and adds none — execution stays in your sender of choice. Without an LLM key it emits a clearly-labeled stub, never wooden copy passed off as a draft.
235
+
236
+ **Runs on any model.** Every LLM step honors `ANTHROPIC_API_BASE_URL` / `OPENAI_API_BASE_URL`, so the same loop runs on Claude, a GLM/z.ai endpoint, or a local Ollama by pointing one env var — `--model` picks the model id.
237
+
209
238
  ## Schedules: declare a cadence once, keep the governance contract under automation
210
239
 
211
240
  Everything the CLI produces is accurate the moment it runs and silently stale afterward. The **schedule layer** is the horizontal fix — any read/plan-side command on a cron cadence, one component, every verb (no feature owns its own cron logic):
@@ -220,7 +249,7 @@ fullstackgtm schedule status --runs 5 # last runs, exit codes, artifa
220
249
  fullstackgtm schedule uninstall # remove the managed block, touch nothing else
221
250
  ```
222
251
 
223
- **Scheduling never auto-approves.** Schedulable commands are read/plan-side only — `audit`, `snapshot`, `enrich append|refresh`, `market capture|refresh`, `suggest`, `report`, `doctor` — so unattended runs accumulate *proposals* (plans in the queue, run records, reports), never CRM writes. `apply` is schedulable only as `apply --plan-id <id>`, and every firing re-checks the plan's status is approved: an unapproved plan records a `plan_not_approved` no-op run instead of executing, and no flag relaxes this. Arbitrary shell is not schedulable — an entry's argv must resolve to a known fullstackgtm command (validated at `add` time and re-checked at run time), and the crontab line you audit is always `fullstackgtm schedule run <id>` and nothing else.
252
+ **Scheduling never auto-approves.** Schedulable commands are read/plan-side only — `audit`, `snapshot`, `enrich append|refresh`, `market capture|refresh`, `signals fetch`, `icp judge`, `icp eval`, `draft`, `suggest`, `report`, `doctor` — so unattended runs accumulate *proposals* (plans in the queue, run records, reports), never CRM writes. `apply` is schedulable only as `apply --plan-id <id>`, and every firing re-checks the plan's status is approved: an unapproved plan records a `plan_not_approved` no-op run instead of executing, and no flag relaxes this. Arbitrary shell is not schedulable — an entry's argv must resolve to a known fullstackgtm command (validated at `add` time and re-checked at run time), and the crontab line you audit is always `fullstackgtm schedule run <id>` and nothing else.
224
253
 
225
254
  `install` renders enabled entries into a sentinel-delimited block (`# >>> fullstackgtm <profile> >>>` … `# <<< fullstackgtm <profile> <<<`) in your user crontab; re-install replaces the block wholesale and never touches lines outside it. Honest limitation: local cron has no catch-up — a laptop asleep at firing time means a missed run. `schedule status` surfaces missed firings by comparing expected-vs-actual run history, so the gap is at least visible. Entries are provider-agnostic; cloud providers (Modal, AWS) arrive as scaffold generators that call the same `schedule run <id>` contract, and are refused as "not yet implemented" until then.
226
255