fullstackgtm 0.43.0 → 0.45.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 +378 -1
- package/README.md +37 -14
- package/dist/audit.d.ts +3 -1
- package/dist/audit.js +29 -2
- package/dist/cli/audit.d.ts +15 -0
- package/dist/cli/audit.js +392 -0
- package/dist/cli/auth.d.ts +80 -0
- package/dist/cli/auth.js +500 -0
- package/dist/cli/call.d.ts +1 -0
- package/dist/cli/call.js +373 -0
- package/dist/cli/capabilities.d.ts +30 -0
- package/dist/cli/capabilities.js +197 -0
- package/dist/cli/draft.d.ts +7 -0
- package/dist/cli/draft.js +87 -0
- package/dist/cli/enrich.d.ts +8 -0
- package/dist/cli/enrich.js +788 -0
- package/dist/cli/fix.d.ts +33 -0
- package/dist/cli/fix.js +344 -0
- package/dist/cli/help.d.ts +25 -0
- package/dist/cli/help.js +609 -0
- package/dist/cli/icp.d.ts +7 -0
- package/dist/cli/icp.js +229 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +58 -0
- package/dist/cli/market.d.ts +1 -0
- package/dist/cli/market.js +391 -0
- package/dist/cli/plans.d.ts +5 -0
- package/dist/cli/plans.js +454 -0
- package/dist/cli/schedule.d.ts +8 -0
- package/dist/cli/schedule.js +479 -0
- package/dist/cli/shared.d.ts +70 -0
- package/dist/cli/shared.js +331 -0
- package/dist/cli/signals.d.ts +7 -0
- package/dist/cli/signals.js +412 -0
- package/dist/cli/suggest.d.ts +49 -0
- package/dist/cli/suggest.js +135 -0
- package/dist/cli/tam.d.ts +9 -0
- package/dist/cli/tam.js +387 -0
- package/dist/cli/ui.d.ts +121 -0
- package/dist/cli/ui.js +375 -0
- package/dist/cli.d.ts +1 -57
- package/dist/cli.js +104 -4556
- package/dist/connector.d.ts +15 -0
- package/dist/connector.js +35 -0
- package/dist/connectors/hubspot.d.ts +3 -1
- package/dist/connectors/hubspot.js +10 -3
- package/dist/connectors/outboxChannel.d.ts +64 -0
- package/dist/connectors/outboxChannel.js +170 -0
- package/dist/connectors/prospectSources.d.ts +22 -0
- package/dist/connectors/prospectSources.js +43 -0
- package/dist/connectors/salesforce.d.ts +3 -1
- package/dist/connectors/salesforce.js +12 -5
- package/dist/connectors/signalSources.d.ts +105 -0
- package/dist/connectors/signalSources.js +264 -0
- package/dist/connectors/theirstack.d.ts +84 -0
- package/dist/connectors/theirstack.js +125 -0
- package/dist/icp.d.ts +52 -4
- package/dist/icp.js +112 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +3 -0
- package/dist/judge.d.ts +2 -0
- package/dist/judge.js +6 -0
- package/dist/judgeEval.d.ts +7 -0
- package/dist/judgeEval.js +8 -1
- package/dist/marketClassify.d.ts +2 -0
- package/dist/marketClassify.js +7 -1
- package/dist/mcp-bin.js +2 -2
- package/dist/mcp.js +259 -166
- package/dist/runReport.d.ts +26 -0
- package/dist/runReport.js +15 -0
- package/dist/schedule.d.ts +80 -2
- package/dist/schedule.js +272 -5
- package/dist/signals.d.ts +54 -0
- package/dist/signals.js +64 -0
- package/dist/spoolFiles.d.ts +44 -0
- package/dist/spoolFiles.js +114 -0
- package/dist/tam.d.ts +225 -0
- package/dist/tam.js +470 -0
- package/dist/types.d.ts +11 -0
- package/docs/api.md +91 -11
- package/docs/outbox-format.md +92 -0
- package/docs/recipes.md +37 -0
- package/docs/roadmap-to-1.0.md +31 -1
- package/docs/signal-spool-format.md +175 -0
- package/docs/tam.md +195 -0
- package/llms.txt +83 -11
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +4 -3
- package/src/audit.ts +27 -1
- package/src/cli/audit.ts +447 -0
- package/src/cli/auth.ts +549 -0
- package/src/cli/call.ts +398 -0
- package/src/cli/capabilities.ts +215 -0
- package/src/cli/draft.ts +101 -0
- package/src/cli/enrich.ts +885 -0
- package/src/cli/fix.ts +372 -0
- package/src/cli/help.ts +664 -0
- package/src/cli/icp.ts +265 -0
- package/src/cli/init.ts +65 -0
- package/src/cli/market.ts +423 -0
- package/src/cli/plans.ts +523 -0
- package/src/cli/schedule.ts +526 -0
- package/src/cli/shared.ts +375 -0
- package/src/cli/signals.ts +434 -0
- package/src/cli/suggest.ts +151 -0
- package/src/cli/tam.ts +435 -0
- package/src/cli/ui.ts +426 -0
- package/src/cli.ts +103 -5170
- package/src/connector.ts +46 -0
- package/src/connectors/hubspot.ts +14 -2
- package/src/connectors/outboxChannel.ts +202 -0
- package/src/connectors/prospectSources.ts +57 -0
- package/src/connectors/salesforce.ts +18 -2
- package/src/connectors/signalSources.ts +314 -0
- package/src/connectors/theirstack.ts +170 -0
- package/src/icp.ts +120 -34
- package/src/index.ts +32 -0
- package/src/init.ts +3 -0
- package/src/judge.ts +7 -0
- package/src/judgeEval.ts +8 -1
- package/src/marketClassify.ts +8 -1
- package/src/mcp-bin.ts +2 -2
- package/src/mcp.ts +130 -57
- package/src/runReport.ts +39 -0
- package/src/schedule.ts +330 -7
- package/src/signals.ts +90 -0
- package/src/spoolFiles.ts +116 -0
- package/src/tam.ts +654 -0
- package/src/types.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,384 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
5
5
|
and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
The path to 1.0 is planned in [docs/roadmap-to-1.0.md](./docs/roadmap-to-1.0.md).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.45.0] — 2026-07-02
|
|
9
|
+
|
|
10
|
+
### Added (TUI glamour — interactive terminals only)
|
|
11
|
+
|
|
12
|
+
- **A terminal presentation layer (`src/cli/ui.ts`, zero new dependencies) —
|
|
13
|
+
styling and animation exist ONLY on an interactive TTY.** Piped, redirected,
|
|
14
|
+
`--json`, CI, and `NO_COLOR` output is byte-identical to the pre-TUI CLI
|
|
15
|
+
(verified against the previous build for 14 verbs: stdout, stderr, exit
|
|
16
|
+
codes); a new `agent-cli.yml` step and `tests/fullstackgtmAnsiSafety.test.ts`
|
|
17
|
+
enforce zero ANSI bytes in piped output permanently. `FORCE_COLOR=1` opts
|
|
18
|
+
piped output back into color per spec. On a TTY:
|
|
19
|
+
- `doctor` colors each check (green ok / red MISSING), dims what's not
|
|
20
|
+
connected, bands the health score, appends a score-history sparkline, and
|
|
21
|
+
frames "Next step" in a box. `--help` gets a dimmed brand header
|
|
22
|
+
(version · active profile), bold command names, cyan lifecycle groups.
|
|
23
|
+
- `health` renders a styled rollup: banded score, ▲/▼ delta, score
|
|
24
|
+
sparkline, aligned object-type and per-rule tables (positive finding
|
|
25
|
+
deltas red — more findings is worse — negative green).
|
|
26
|
+
- Live provider pulls show a stderr spinner with a running tally ("Pulling
|
|
27
|
+
contacts from hubspot… 2,400 fetched · 12s"): the HubSpot/Salesforce
|
|
28
|
+
connectors accept an optional per-page `onProgress` (presentation-only,
|
|
29
|
+
errors swallowed). `audit` renders its rule registry as a live checklist
|
|
30
|
+
(○ → spinner → ✓ with finding counts) via a new optional `onRule` on
|
|
31
|
+
`auditSnapshot`; `signals fetch` shows a per-domain ATS tally.
|
|
32
|
+
- `apply` and `fix --yes` tick per-operation progress ("Applying 41/120 ·
|
|
33
|
+
✓ 40 applied · 0 failed · 1 conflict") via a new optional `onOperation` on
|
|
34
|
+
`applyPatchPlan`, closing with a colored outcome badge. `enrich
|
|
35
|
+
append/refresh` gets a progress bar with rate + ETA over the Apollo pull;
|
|
36
|
+
`enrich acquire` adds a budget fuel gauge (records + spend vs day/month
|
|
37
|
+
caps, color-banded by burn).
|
|
38
|
+
- LLM waits show an elapsed status line (`call parse` / `call score`);
|
|
39
|
+
`icp judge` and `market classify` report per-account / per-vendor progress
|
|
40
|
+
via new optional `onAccount` / `onVendor` callbacks.
|
|
41
|
+
- Patch plans render with severity banding and value diffs — current value
|
|
42
|
+
red, proposed value green, `requires_human_*` placeholders yellow — in
|
|
43
|
+
`audit` and `plans show`; `plans list` becomes an aligned status-banded
|
|
44
|
+
table (summaries truncated with `…` to the terminal width so rows never
|
|
45
|
+
wrap); `fix` frames its stop-before-apply command in a box.
|
|
46
|
+
|
|
47
|
+
### Added (agent ergonomics pass 1)
|
|
48
|
+
|
|
49
|
+
- **Flag typos are caught instead of silently ignored.** Any `--flag` that is
|
|
50
|
+
documented nowhere in the help reference but sits within one edit of a
|
|
51
|
+
documented flag (after lowercasing and `_`→`-` normalization) now stops with
|
|
52
|
+
exit 1, a `Did you mean: --json` hint, and the exact corrected command;
|
|
53
|
+
`--json` callers get a structured `UNKNOWN_FLAG` envelope. Previously
|
|
54
|
+
`audit --demo --jsn` exited 0 and printed markdown where the caller asked
|
|
55
|
+
for JSON, and `reassign … --sav` exited 0 without saving. Suggest-only —
|
|
56
|
+
the correction is never auto-executed, so a typo can never change what a
|
|
57
|
+
write-shaped invocation stages. The known-flag registry is derived at
|
|
58
|
+
runtime from the help table (no parallel list); unknown flags with no
|
|
59
|
+
near-miss behave exactly as before.
|
|
60
|
+
- **Unknown commands get a did-you-mean on the plain-text path too.**
|
|
61
|
+
`fullstackgtm plan` now answers `Did you mean: fullstackgtm plans` plus one
|
|
62
|
+
pointer line each to `--help` and `capabilities --json`, instead of the
|
|
63
|
+
full usage-reference dump. (The `--json` envelope already had the hint.)
|
|
64
|
+
- **`doctor` is now the one-call triage surface.** `doctor --json` adds a
|
|
65
|
+
`workspace` slice — `healthScore`, `scoreDelta`, `lastAuditAt`,
|
|
66
|
+
`auditCount`, and `pendingPlans` (id, summary, operation/approved counts) —
|
|
67
|
+
and when a plan is awaiting approval `nextSteps` becomes the copy-pasteable
|
|
68
|
+
`plans show` → `plans approve` → `apply` chain with the connected provider
|
|
69
|
+
filled in. Text `doctor` gains the matching `Workspace:` section.
|
|
70
|
+
Previously orienting an agent took three calls (doctor + health +
|
|
71
|
+
plans list).
|
|
72
|
+
- **`audit` output is byte-deterministic under `SOURCE_DATE_EPOCH`.** The
|
|
73
|
+
plan's `createdAt` honors the reproducible-build convention (seconds since
|
|
74
|
+
the epoch) so golden tests and CI diffs can pin `audit --demo --json`
|
|
75
|
+
byte-for-byte. Unset, it remains the real clock.
|
|
76
|
+
- **GNU-style `--flag=value` is corrected too.** `audit --rules=x` was
|
|
77
|
+
silently dropped (the audit ran with all rules); an `=`-form whose base
|
|
78
|
+
resolves to a documented flag now stops with the space-separated form this
|
|
79
|
+
CLI parses (`Try: … --rules x`).
|
|
80
|
+
- **A flag-shaped first token is diagnosed as flag-before-command.**
|
|
81
|
+
`fullstackgtm --jsn` was "Unknown command: --jsn"; now it explains that a
|
|
82
|
+
command must come first and resolves the flag when it near-misses a
|
|
83
|
+
documented one.
|
|
84
|
+
- **Subverb typos get a nearest-match did-you-mean on all 7 multi-verb
|
|
85
|
+
commands** (`enrich`, `market`, `icp`, `signals`, `plans`, `schedule`,
|
|
86
|
+
`tam`): `enrich apend` → `Did you mean: fullstackgtm enrich append?` plus
|
|
87
|
+
the full list. `market <typo>` no longer dies with a `market.config.json`
|
|
88
|
+
ENOENT before the subcommand is validated.
|
|
89
|
+
|
|
90
|
+
### Changed (agent ergonomics pass 1)
|
|
91
|
+
|
|
92
|
+
- **`apply <planId>` / `suggest <planId>` (positional) now name the exact
|
|
93
|
+
corrected command** (`apply --plan-id <id> --provider <…>`) instead of
|
|
94
|
+
misdiagnosing the mistake as a missing `--provider`. Both verbs stay
|
|
95
|
+
flag-explicit; nothing is inferred into execution.
|
|
96
|
+
- **`Unknown provider: hubpsot` now adds `Did you mean hubspot?`** (CLI and
|
|
97
|
+
MCP paths); the supported-provider list is unchanged.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- **MCP zero-install is now one short command: `npx -y fullstackgtm-mcp`.** A
|
|
102
|
+
thin wrapper package (`fullstackgtm-mcp@0.1.0`, monorepo
|
|
103
|
+
`packages/fullstackgtm-mcp`, published manually — the OIDC release flow
|
|
104
|
+
covers only this package) pins the optional MCP peers as hard dependencies
|
|
105
|
+
and delegates to this package's `dist/mcp-bin.js`. README, SKILL.md, and the
|
|
106
|
+
missing-peer help text now lead with the short command; the explicit
|
|
107
|
+
`-p fullstackgtm -p @modelcontextprotocol/sdk -p zod` form still works.
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- **`schedule install` no longer needs Full Disk Access on macOS: launchd
|
|
112
|
+
LaunchAgents are the default timer there.** macOS gates `crontab` writes
|
|
113
|
+
behind FDA — an unpromptable TCC permission granted to the terminal app,
|
|
114
|
+
not the CLI — so on darwin `install` now writes one LaunchAgent plist per
|
|
115
|
+
enabled entry (`com.fullstackgtm.<profile>.<id>` in
|
|
116
|
+
`~/Library/LaunchAgents`, loaded via `launchctl bootstrap gui/<uid>`),
|
|
117
|
+
which needs no permission at all. The cron expression compiles to
|
|
118
|
+
`StartCalendarInterval` dicts (full-range fields become launchd wildcards;
|
|
119
|
+
dense expressions are capped at 512 dicts with a clear error; Vixie
|
|
120
|
+
dom+dow OR becomes a Day set plus a Weekday set). Every plist's
|
|
121
|
+
`ProgramArguments` is `… schedule run <id> --profile <p> --trigger cron`
|
|
122
|
+
and nothing else — the same single-entry-point audit story as the crontab
|
|
123
|
+
block — and re-install replaces the profile's plist fleet wholesale,
|
|
124
|
+
never touching foreign plists. launchd also coalesces firings missed
|
|
125
|
+
during sleep into one run on wake, where cron silently skips. `--timer
|
|
126
|
+
crontab|launchd` overrides the platform default (launchd on macOS, crontab
|
|
127
|
+
elsewhere); stdout/stderr of each firing lands in
|
|
128
|
+
`<home>/schedule/logs/<label>.log`. New run no-op reason
|
|
129
|
+
`duplicate_firing`: `schedule run` drops a second cron trigger in the same
|
|
130
|
+
minute, so a date matching both the Day and Weekday interval sets cannot
|
|
131
|
+
double-run a command.
|
|
132
|
+
|
|
133
|
+
- **Standardized flag aliases (additive — every legacy flag keeps working).**
|
|
134
|
+
`--dry-run` is now accepted on the plan-spine verbs (`audit`, `suggest`,
|
|
135
|
+
`bulk-update`, `dedupe`, `reassign`, `fix`, `call plan`,
|
|
136
|
+
`enrich append`/`refresh`/`acquire`, `signals fetch`, `icp judge`, `draft`,
|
|
137
|
+
`tam status`, `market overlay`): these verbs were already preview-by-default,
|
|
138
|
+
so the flag asserts that default — it suppresses `--save` (with a stderr note
|
|
139
|
+
when both are passed) and locks `fix` to its stop-before-apply path even with
|
|
140
|
+
`--yes`. Omitting it changes nothing. `--confirm` is now an accepted alias
|
|
141
|
+
for `fix --yes` (matching `tam accounts --confirm`); `--dry-run` beats both.
|
|
142
|
+
- **Staged-file ingestion converged on the spool container convention
|
|
143
|
+
(additive).** `signals fetch --from` and `enrich ingest` now also accept a
|
|
144
|
+
`*.jsonl` spool file (one JSON row per line) or a directory of `*.jsonl` /
|
|
145
|
+
`*.json` spool files — the Phase-2 webhook landing-zone format
|
|
146
|
+
(docs/signal-spool-format.md) — and `market observe --from` accepts a
|
|
147
|
+
`*.jsonl` file / directory of observation-set envelopes, each validated and
|
|
148
|
+
appended through the same gates as the single-file path. Existing `.json` /
|
|
149
|
+
`.csv` inputs parse exactly as before. Shared reader: `src/spoolFiles.ts`
|
|
150
|
+
(the `file` signal-source connector now delegates to it, error text
|
|
151
|
+
unchanged).
|
|
152
|
+
- **Machine-readable agent contract: `capabilities`, `robot-docs`, and
|
|
153
|
+
`--help --json`.** `capabilities` prints the CLI contract as JSON: the
|
|
154
|
+
command inventory with read-only vs write-shaped access derived from the
|
|
155
|
+
help table's lifecycle phases (never a hand-maintained parallel list), the
|
|
156
|
+
documented exit-code contract, safety defaults, and the MCP entrypoint.
|
|
157
|
+
`<command> --help --json` (and `help <command> --json`) prints the same
|
|
158
|
+
per-command help as JSON — plain `--help` output is unchanged except that
|
|
159
|
+
`audit --help` now documents the pre-existing `--input <path>` flag.
|
|
160
|
+
Unknown commands invoked with `--json` return a structured
|
|
161
|
+
`{ ok: false, error: { code: "UNKNOWN_COMMAND", hints: [did-you-mean] } }`
|
|
162
|
+
envelope on stdout (exit 1) instead of the full-usage text wall.
|
|
163
|
+
`robot-docs` prints the packaged agent guide (skills/fullstackgtm/SKILL.md,
|
|
164
|
+
already shipped in the npm tarball) — one maintained guide, not a third
|
|
165
|
+
parallel one.
|
|
166
|
+
- **MCP `fullstackgtm_capabilities` tool.** The MCP server's tools are now
|
|
167
|
+
declared in a single registration table and registered in a loop; the new
|
|
168
|
+
tool reports the inventory (with per-tool `writesCrm` flags — only
|
|
169
|
+
`fullstackgtm_apply` can reach a CRM) from that same table, so the
|
|
170
|
+
advertised list cannot drift from what is actually registered. The existing
|
|
171
|
+
tools' behavior is unchanged.
|
|
172
|
+
- **`--input` snapshot files are validated.** `--input` now checks the file
|
|
173
|
+
parses to the canonical snapshot shape (the JSON `snapshot --out` writes)
|
|
174
|
+
and fails with a field-level error naming what's missing, instead of a
|
|
175
|
+
blind cast that surfaced as a crash deep in a rule — or an empty "clean"
|
|
176
|
+
audit.
|
|
177
|
+
|
|
178
|
+
### Fixed
|
|
179
|
+
|
|
180
|
+
- **`bulk-update` restored to the help front door.** The grouped short-usage
|
|
181
|
+
map listed the verb in its Remediate group but silently skipped rendering
|
|
182
|
+
it (no help-table entry), and `help bulk-update` dead-ended on the short
|
|
183
|
+
map. It now has a proper entry and appears in the derived `capabilities`
|
|
184
|
+
inventory.
|
|
185
|
+
|
|
186
|
+
### Changed
|
|
187
|
+
|
|
188
|
+
- **Internal: `src/cli.ts` (6,003 lines) split into per-verb modules under
|
|
189
|
+
`src/cli/`** (help, audit, fix, call, market, enrich, signals, draft, tam,
|
|
190
|
+
icp, init, schedule, plans, auth, shared). No behavior change: `dist/bin.js`
|
|
191
|
+
stays the entry, arg parsing / help text / exit codes are unchanged, and
|
|
192
|
+
`cli.ts` still exports `runCli`, `doctorReport`, `assertSecureBrokerUrl`.
|
|
193
|
+
|
|
194
|
+
## [0.44.0] — 2026-07-01
|
|
195
|
+
|
|
196
|
+
### Added
|
|
197
|
+
|
|
198
|
+
- **Paired CLI auto-links HubSpot to the hosted deployment.** When a broker
|
|
199
|
+
credential exists (a paired CLI), `audit --provider hubspot` hands the local
|
|
200
|
+
HubSpot private-app token to the deployment over the broker channel
|
|
201
|
+
(best-effort, with a printed notice), so the hosted Integrations page shows
|
|
202
|
+
HubSpot connected and syncing without a separate OAuth setup. Unpaired CLIs
|
|
203
|
+
are unaffected — pairing remains opt-in via `login --via`.
|
|
204
|
+
|
|
205
|
+
### Added
|
|
206
|
+
|
|
207
|
+
- **`tam accounts` credit-cost preview + spend guard.** Pulling the list costs ~3
|
|
208
|
+
TheirStack credits/company, so `tam accounts` now prints the cost up front and
|
|
209
|
+
never spends by surprise: `--dry-run [--usd-per-credit <rate>]` prices the pull
|
|
210
|
+
AND the full TAM (e.g. "up to 100 ≈ 300 credits; full TAM ~43,517 ≈ 130,551
|
|
211
|
+
credits (~$5,483)") for **0 credits**, and a pull above `--max-credits` (default
|
|
212
|
+
150 ≈ 50 companies) requires `--confirm`. The dollar figure appears only with an
|
|
213
|
+
explicit rate (no fabricated default; TheirStack is ~$0.04–$0.11/credit by tier).
|
|
214
|
+
`tam populate` also notes where spend lives — the acquire meter governs
|
|
215
|
+
population, separate from the TheirStack list pull. Library:
|
|
216
|
+
`theirStackPullCost` + `THEIRSTACK_CREDITS_PER_COMPANY`.
|
|
217
|
+
|
|
218
|
+
### Changed
|
|
219
|
+
|
|
220
|
+
- **`tam status` coverage is now classified against the TAM ICP — not a raw
|
|
221
|
+
account count.** Previously it counted *every* domain-bearing CRM account over
|
|
222
|
+
the universe, so accounts loaded from any source (off-ICP or not) inflated
|
|
223
|
+
coverage and could read >100%. Now `tam estimate` stores the ICP filter
|
|
224
|
+
(`model.targeting`), and `status` classifies each account into **in-TAM /
|
|
225
|
+
out-of-TAM / unknown** (checked on size + industry; geo and "uses-CRM" aren't on
|
|
226
|
+
a `CanonicalAccount`, so they need re-enrichment — a `--reverify` pass is
|
|
227
|
+
planned, and the classifier labels what it checked). Only in-TAM counts toward
|
|
228
|
+
coverage; off-ICP junk is bucketed out. **Bottom-up vs top-down reconciliation:**
|
|
229
|
+
the in-TAM count is a floor on the real universe, so when it meets/exceeds the
|
|
230
|
+
estimate, `status` stops reporting a fake 100% and flips to "the estimate was a
|
|
231
|
+
floor — your real market is at least N (`reconciledUniverse`); re-estimate for
|
|
232
|
+
the headroom." ETA now burns on in-TAM accounts/day. New library:
|
|
233
|
+
`classifyAccount`, `classifyCoverage`, `crmCheckableCriteria`, `coveredAccounts`,
|
|
234
|
+
`TamTargeting`/`TamClassified` (and `TamModel.targeting`). Legacy models without
|
|
235
|
+
targeting fall back to counting all accounts, with a "re-estimate to classify"
|
|
236
|
+
note.
|
|
237
|
+
|
|
238
|
+
### Added
|
|
239
|
+
|
|
240
|
+
- **`tam` technographic sourcing via TheirStack — target by CRM-usage, get a real
|
|
241
|
+
list.** The Explorium firmographic count (NAICS/size/geo) is a weak proxy for a
|
|
242
|
+
RevOps/CRM-hygiene tool and can't return the list (it 403s on pull). New
|
|
243
|
+
`--source theirstack` counts companies that actually **use** a CRM/MAP — set
|
|
244
|
+
`icp.firmographics.technologies` (e.g. `["salesforce","hubspot","pipedrive"]`),
|
|
245
|
+
mapped to TheirStack `company_technology_slug_or` + employee bounds + country —
|
|
246
|
+
which is the real buying signal, labeled `provider:theirstack (uses-CRM)`. And a
|
|
247
|
+
new **`tam accounts --source theirstack`** pulls the actual company list (real
|
|
248
|
+
names + domains, `--out <csv>` or `--json`, `--max` caps the credit spend), so the
|
|
249
|
+
TAM is a list you can review and acquire, not just a number. Counting is cheap
|
|
250
|
+
but not free (TheirStack rejects `limit:0`, so a count returns 1 row ≈ 3 credits;
|
|
251
|
+
a list pull is ~3 credits/company). New connector
|
|
252
|
+
`connectors/theirstack.ts` (`theirStackCountCompanies` / `theirStackSearchCompanies`),
|
|
253
|
+
`icpToTheirStackFilters` + `employeeBandsToRange`, and `login theirstack`.
|
|
254
|
+
(Verified live: filters + `metadata.total_results`; `limit` must be ≥ 1.)
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- **`tam estimate` requires a confirmed ANNUAL ACV — no fabricated defaults, and
|
|
259
|
+
the CRM is not silently the ACV source.** A TAM dollar figure built on a guessed
|
|
260
|
+
ACV is a false signal, so the `--acv-band smb|mid|enterprise` presets (hardcoded
|
|
261
|
+
$6k/$24k/$90k) are removed and `estimate` refuses to run without a real ACV. Two
|
|
262
|
+
confirmed paths, always labeled on the model (`acv.source`): `--acv <annual-usd>`
|
|
263
|
+
(`"explicit (annual)"`), or `--acv-from-crm --deal-period monthly|quarterly|annual`
|
|
264
|
+
— the median closed-won deal amount **annualized** by the stated period
|
|
265
|
+
(`"crm:closed-won (N deals, median $X/monthly ×12 = annual)"`). The period is
|
|
266
|
+
**required**: a deal amount can be MRR/quarterly/annual and guessing it is a
|
|
267
|
+
4–12× error (a $15k/mo deal is a $180k ACV). A bare `--provider` is the COVERAGE
|
|
268
|
+
source and **no longer auto-sets ACV**. **Buyers/account** likewise:
|
|
269
|
+
`--buyers-per-account <n>`, else the CRM's **average contacts per account**
|
|
270
|
+
(`"crm:avg-contacts/account (N)"`), else a labeled `"assumption:1-buyer"` — never
|
|
271
|
+
a silent default of 3. New library helpers `deriveAcvFromClosedWon` /
|
|
272
|
+
`deriveBuyersPerAccount`; `TamModel.acv.source` and `universe.buyersSource` are
|
|
273
|
+
new fields (and `acv.band` is gone).
|
|
274
|
+
|
|
275
|
+
### Fixed
|
|
276
|
+
|
|
277
|
+
- **`icp eval --golden default` no longer rots with the calendar.** The default
|
|
278
|
+
golden set's `firstSeen` stamps are relative to a pinned instant
|
|
279
|
+
(`DEFAULT_GOLDEN_NOW_ISO`, exported), but grading used wall-clock `new Date()`
|
|
280
|
+
— so freshness decay silently degraded the "fresh → send" rows until the gate
|
|
281
|
+
started failing (~8 days after the set was authored) on every fresh install
|
|
282
|
+
and in CI, with no code change. The default set is now graded on its own
|
|
283
|
+
pinned clock; file-based golden sets still grade against wall time (their
|
|
284
|
+
timestamps are the caller's).
|
|
285
|
+
|
|
286
|
+
- **`enrich acquire` (pipe0/Crustdata) over-narrow ICP filter → zero discovery.**
|
|
287
|
+
Live testing surfaced that a RevOps ICP returned 0 prospects from pipe0/Crustdata
|
|
288
|
+
even though a title-only search returned plenty — the prime suspect is the
|
|
289
|
+
industry vocabulary: LinkedIn renamed its industry taxonomy (v1→v2) and the map
|
|
290
|
+
sent only one generation, so a vendor on the other generation matches nothing.
|
|
291
|
+
`CRUSTDATA_INDUSTRY` now sends BOTH generations per cluster (e.g. "Software
|
|
292
|
+
Development" *and* "Computer Software"), OR-matched within the field. (pipe0
|
|
293
|
+
credits were exhausted mid-investigation, so this is a reasoned fix not yet
|
|
294
|
+
re-confirmed end-to-end — re-run `enrich acquire --source pipe0` once credits
|
|
295
|
+
refill; if still zero, the next suspects are the `current_seniority_levels`
|
|
296
|
+
shape and `locations`. Note `scoreProspectAgainstIcp` backstops persona but NOT
|
|
297
|
+
industry, so the industry filter is load-bearing.)
|
|
298
|
+
- **`enrich acquire` now surfaces a zero-discovery result** instead of emitting a
|
|
299
|
+
silent empty plan: when a provider returns 0 prospects it warns that this is
|
|
300
|
+
usually an over-narrow filter (not an empty market) and points at `icp show`;
|
|
301
|
+
and when all discovered prospects score below the fit threshold it says so.
|
|
302
|
+
|
|
303
|
+
### Added
|
|
304
|
+
|
|
305
|
+
- **`tam` — Total Addressable Market mapping.** Size the reachable market FROM
|
|
306
|
+
the ICP, then iteratively fill it and track coverage to an ETA. `tam estimate`
|
|
307
|
+
computes a transparent model (account universe × ACV = TAM, account/per-logo
|
|
308
|
+
basis by default or `--acv-basis buyer`; contacts = accounts × buyers/account
|
|
309
|
+
is the population target). The account count is `--accounts <n>` (assumption)
|
|
310
|
+
or `--source explorium` — a live count of matching **companies** via Explorium
|
|
311
|
+
`/v1/businesses`, which is the account universe directly. (Verified against the
|
|
312
|
+
live APIs: people/lead endpoints can't size a market — Explorium `/v1/prospects`
|
|
313
|
+
`total_results` equals the page size and pipe0/Crustdata returns only a cursor —
|
|
314
|
+
so `--source pipe0` for estimate is rejected; pipe0 remains a discovery/
|
|
315
|
+
population source. The businesses count omits `size`, which otherwise caps the
|
|
316
|
+
total.) Explorium caps the count at 60,000; a saturated reading is surfaced as a
|
|
317
|
+
floor (`provider:explorium (≥60k cap — floor)` + a warning to narrow the ICP).
|
|
318
|
+
The model always labels `accountsSource` provider-vs-assumption. Optional citable
|
|
319
|
+
cross-checks sit beside the bottom-up number. `tam populate --cron` schedules
|
|
320
|
+
governed `enrich acquire --save` (plan-only; see below). `tam status --save`
|
|
321
|
+
stamps a coverage timeline (CRM accounts-with-domain + contacts vs. the
|
|
322
|
+
universe, `$ covered` proportional, what the campaign added since baseline);
|
|
323
|
+
`tam report` renders a markdown burn-up. `status`/`report` project a linear
|
|
324
|
+
accounts/day ETA and refuse to project (honest "not enough history") below two
|
|
325
|
+
readings or a flat rate. Coverage + ETA are deterministic (no LLM). Library:
|
|
326
|
+
`estimateTam`, `computeCoverage`, `projectEta`, `tamReportToMarkdown`, the
|
|
327
|
+
store, and the count probe (`probeExploriumBusinessCount` +
|
|
328
|
+
`EXPLORIUM_BUSINESS_COUNT_CAP`). See [docs/tam.md](./docs/tam.md).
|
|
329
|
+
- **Source connectors for `signals` (Phase 1).** `signals fetch` gains an
|
|
330
|
+
additive, opt-in `--connector <id>[,<id>] [--connector-opt k=v …]` that pulls
|
|
331
|
+
signals from connected platforms instead of only ATS boards and hand-staged
|
|
332
|
+
`--from` files. Three connectors ship: `file` (local JSON/JSONL — also the
|
|
333
|
+
webhook landing-zone spool, no auth), `serpapi-news` (funding/company news via
|
|
334
|
+
a news REST API, key through the credential ladder), and `hubspot-forms`
|
|
335
|
+
(first-party `demand` from recent form submissions, reusing the existing
|
|
336
|
+
HubSpot login). Secrets resolve through the env → login → broker ladder, never
|
|
337
|
+
argv; `--connector-opt` carries non-secret knobs only. **Default behavior is
|
|
338
|
+
unchanged** when no `--connector` is passed. Connectors are read-only re: the
|
|
339
|
+
CRM and run through the same evidence-gate/dedup/weight pipeline as `--from`
|
|
340
|
+
(the `readStagedSignals` row validator is now the shared `stagedRowToSignal`).
|
|
341
|
+
Library: `connectors/signalSources.ts` (`listSignalSources`, `getSignalSource`,
|
|
342
|
+
`fileSource`, `serpapiNewsSource`, `hubspotFormsSource`), `stagedRowToSignal` +
|
|
343
|
+
`StagedSignalRow` from `signals.ts`. Design:
|
|
344
|
+
[docs/spec-connectors-signals-outbound.md](../../docs/spec-connectors-signals-outbound.md)
|
|
345
|
+
(the connector taxonomy + the Phase 2/3 webhook-spool and governed-channel plan).
|
|
346
|
+
- **Webhook spool format + conventional landing zone (signals Phase 2).**
|
|
347
|
+
`--connector file` with no path now reads the conventional spool directory
|
|
348
|
+
(`<profile home>/signals/spool`, via `signalsSpoolDir`) — every `*.jsonl` in
|
|
349
|
+
it, name-sorted, so a webhook receiver can append one row per event (per
|
|
350
|
+
source: `rb2b.jsonl`, `hubspot.jsonl`, …) and they all land in one fetch. The
|
|
351
|
+
`file` connector reads a directory as well as a single file. New
|
|
352
|
+
[docs/signal-spool-format.md](./docs/signal-spool-format.md) documents the row
|
|
353
|
+
format, the landing zone, re-read/retention semantics, and per-platform
|
|
354
|
+
payload mappings (RB2B, Trigify, HubSpot form webhook). Per the open-core
|
|
355
|
+
boundary the format + reader are open; the always-on receiver is a hosted
|
|
356
|
+
concern — the package ships no receiver. Also fixes a latent filter bug:
|
|
357
|
+
explicitly-provided rows (`--from`, connectors) whose bucket has no configured
|
|
358
|
+
`sources` (notably `demand`) were silently dropped unless `--bucket` was given;
|
|
359
|
+
now only an explicit `--bucket` narrows them, so spool/`hubspot-forms` `demand`
|
|
360
|
+
signals flow by default.
|
|
361
|
+
- **Outbox channel — governed send terminus (signals Phase 3).** `apply
|
|
362
|
+
--channel outbox` renders each APPROVED drafted opener to a local outbox
|
|
363
|
+
(`<profile home>/signals/outbox/<channel>.jsonl`, via `signalsOutboxDir`) for a
|
|
364
|
+
downstream sender to drain — and **transmits nothing**, preserving the "drafts
|
|
365
|
+
everything, transmits nothing" invariant (the send-side mirror of the spool).
|
|
366
|
+
The outbox channel is a `GtmConnector` whose `applyOperation` renders instead
|
|
367
|
+
of writing a CRM, so it reuses the entire governed apply path (approval set,
|
|
368
|
+
integrity verification, idempotency, run recording) with no change to the apply
|
|
369
|
+
engine; `apply` now takes `--provider <crm>` OR `--channel <id>`. It only
|
|
370
|
+
renders `draft:`-policy `create_task` ops (any other op is skipped) and is
|
|
371
|
+
idempotent on the operation id. New
|
|
372
|
+
[docs/outbox-format.md](./docs/outbox-format.md). Per the open-core boundary the
|
|
373
|
+
governed artifact + format are open; the always-on sender that actually
|
|
374
|
+
transmits (ESP/LinkedIn) is a hosted concern — no sender ships in the package.
|
|
375
|
+
Library: `connectors/outboxChannel.ts` (`createOutboxChannelConnector`,
|
|
376
|
+
`createChannelConnector`, `listOutbox`, `OutboxEntry`), `signalsOutboxDir`.
|
|
377
|
+
|
|
378
|
+
### Changed
|
|
379
|
+
|
|
380
|
+
- **`enrich acquire --save` is now schedulable** (for `tam populate`). It joins
|
|
381
|
+
the read/plan-side schedule allowlist in its plan-producing form ONLY — a
|
|
382
|
+
scheduled `enrich acquire` must include `--save` (rejected otherwise), so each
|
|
383
|
+
firing queues a `needs_approval` lead plan and writes nothing. The acquire
|
|
384
|
+
meter is still charged only at apply, and apply stays a separate human gate
|
|
385
|
+
(`apply --plan-id`, re-checked approved). Scheduling never auto-approves.
|
|
9
386
|
|
|
10
387
|
## [0.43.0] — 2026-06-25
|
|
11
388
|
|
package/README.md
CHANGED
|
@@ -38,13 +38,17 @@ It installs a compact operating guide ([skills/fullstackgtm/SKILL.md](./skills/f
|
|
|
38
38
|
## Five-minute loop
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
# 0.
|
|
41
|
+
# 0. Scaffold a workspace: a starter icp.json, an enrich.config.json acquire
|
|
42
|
+
# preset, and a PLAYBOOK.md wired for your provider + discovery source
|
|
43
|
+
npx fullstackgtm init --provider hubspot --source pipe0
|
|
44
|
+
|
|
45
|
+
# 1. No credentials? Try it on a realistic, deliberately messy demo CRM
|
|
42
46
|
npx fullstackgtm audit --demo
|
|
43
47
|
|
|
44
|
-
#
|
|
48
|
+
# 2. Audit your real HubSpot portal (private app token or OAuth access token)
|
|
45
49
|
HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm audit --provider hubspot --out plan.json
|
|
46
50
|
|
|
47
|
-
#
|
|
51
|
+
# 3. Review plan.json, then apply ONLY the operations you approve
|
|
48
52
|
HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm apply \
|
|
49
53
|
--plan plan.json --provider hubspot \
|
|
50
54
|
--approve op_abc123,op_def456 \
|
|
@@ -53,6 +57,8 @@ HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm apply \
|
|
|
53
57
|
|
|
54
58
|
Nothing is ever written without an explicit `--approve`. Operations whose value is a human decision (`requires_human_*` placeholders, e.g. which owner to assign) are refused unless you supply a concrete `--value` override.
|
|
55
59
|
|
|
60
|
+
`init` keeps any files you already have (`--force` overwrites) and works with `--source pipe0|explorium|linkedin` and `--provider hubspot|salesforce`. The PLAYBOOK.md it writes wires the cold-start and outbound-loop recipes to your workspace; the full play set — cold start, trigger-based outbound, scheduling, ABM, hygiene-gating, TAM — is [docs/recipes.md](./docs/recipes.md).
|
|
61
|
+
|
|
56
62
|
## Call workflows: calls become governed evidence
|
|
57
63
|
|
|
58
64
|
Calls are where pipeline truth lives. `call parse` normalizes any transcript dialect — `Speaker: text` lines (Fathom, Gong exports), `[Speaker]:` labels, or raw Granola utterance JSON — into canonical segments, insights, and `GtmEvidence` records.
|
|
@@ -211,8 +217,11 @@ LinkedIn is just another discovery source on the same scored → deduped → met
|
|
|
211
217
|
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
218
|
|
|
213
219
|
```bash
|
|
214
|
-
# 1. Watch for movement. Free, no-auth public job boards in the box;
|
|
220
|
+
# 1. Watch for movement. Free, no-auth public job boards in the box; pull from
|
|
221
|
+
# connected platforms via source connectors; webhook platforms via the spool.
|
|
215
222
|
fullstackgtm signals fetch --bucket job --source greenhouse,lever,ashby --keywords "revops,growth" --save
|
|
223
|
+
fullstackgtm signals fetch --connector serpapi-news,hubspot-forms --save # news + first-party form demand
|
|
224
|
+
fullstackgtm signals fetch --connector file --save # webhook landing zone (see docs/signal-spool-format.md)
|
|
216
225
|
fullstackgtm signals list --since 7d # ranked triggers, each with a verbatim source quote
|
|
217
226
|
|
|
218
227
|
# 2. Decide who's worth a touch — and who isn't. Scores timing × fit × memory into send/nurture/skip.
|
|
@@ -220,8 +229,10 @@ fullstackgtm icp judge --signals-from latest --with-history --save
|
|
|
220
229
|
fullstackgtm icp eval --golden default # gate: prove the judge is calibrated before any send (exits 2 if not)
|
|
221
230
|
|
|
222
231
|
# 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
|
|
232
|
+
fullstackgtm draft --from-judge latest --min-score 80 --channel email --save
|
|
233
|
+
fullstackgtm plans approve <id> --operations all
|
|
234
|
+
fullstackgtm apply --plan-id <id> --provider hubspot # log the touch as a CRM task
|
|
235
|
+
fullstackgtm apply --plan-id <id> --channel outbox # OR render to the outbox for a sender — transmits nothing (docs/outbox-format.md)
|
|
225
236
|
|
|
226
237
|
# 4. Close the loop. Outcomes re-weight which signals earn a touch.
|
|
227
238
|
fullstackgtm signals outcome --account acme.com --result replied
|
|
@@ -243,15 +254,15 @@ Everything the CLI produces is accurate the moment it runs and silently stale af
|
|
|
243
254
|
fullstackgtm schedule add "enrich refresh --source apollo --save" --cron "0 6 * * 1" --label weekly-apollo
|
|
244
255
|
fullstackgtm schedule add "audit --provider hubspot --save" --cron "0 2 * * *" # nightly drift baseline
|
|
245
256
|
fullstackgtm schedule list # declarative entries; nothing runs yet
|
|
246
|
-
fullstackgtm schedule install # materialize enabled entries into
|
|
257
|
+
fullstackgtm schedule install # materialize enabled entries into the system timer (launchd on macOS, crontab elsewhere)
|
|
247
258
|
fullstackgtm schedule run <id> # execute now; same run record a cron firing produces
|
|
248
259
|
fullstackgtm schedule status --runs 5 # last runs, exit codes, artifacts, next + missed firings
|
|
249
260
|
fullstackgtm schedule uninstall # remove the managed block, touch nothing else
|
|
250
261
|
```
|
|
251
262
|
|
|
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
|
|
263
|
+
**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 timer line you audit — crontab entry or LaunchAgent `ProgramArguments` — is always `fullstackgtm schedule run <id>` and nothing else.
|
|
253
264
|
|
|
254
|
-
`install`
|
|
265
|
+
`install` materializes enabled entries into the system timer; `--timer` defaults by platform. On macOS it writes one LaunchAgent plist per entry (`com.fullstackgtm.<profile>.<id>` in `~/Library/LaunchAgents`, loaded via `launchctl bootstrap`) — macOS gates `crontab` writes behind Full Disk Access, a permission no program can request, while LaunchAgents need none; launchd also coalesces firings missed during sleep into one run on wake. Re-install replaces the profile's plist fleet wholesale and never touches foreign plists. Elsewhere (or with `--timer crontab`) it renders 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: 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.
|
|
255
266
|
|
|
256
267
|
### Working across organizations
|
|
257
268
|
|
|
@@ -270,6 +281,17 @@ Set `FULLSTACKGTM_PROFILE=acme` to pin a shell (or agent sandbox) to one client.
|
|
|
270
281
|
Every command is designed to compose in an agent loop — deterministic output, machine-readable everywhere, meaningful exit codes:
|
|
271
282
|
|
|
272
283
|
```bash
|
|
284
|
+
# Discover the machine-readable contract: every command, read-only vs
|
|
285
|
+
# write-shaped access, exit codes, safety defaults (derived from the same
|
|
286
|
+
# help table humans read, so it can't drift)
|
|
287
|
+
fullstackgtm capabilities --json
|
|
288
|
+
|
|
289
|
+
# Per-command help as JSON; plain --help stays human-shaped
|
|
290
|
+
fullstackgtm audit --help --json
|
|
291
|
+
|
|
292
|
+
# Print the shipped agent operating guide (the same SKILL.md `npx skills add` installs)
|
|
293
|
+
fullstackgtm robot-docs
|
|
294
|
+
|
|
273
295
|
# Discover what the auditor checks
|
|
274
296
|
fullstackgtm rules --json
|
|
275
297
|
|
|
@@ -285,11 +307,12 @@ fullstackgtm audit --provider hubspot --fail-on warning
|
|
|
285
307
|
fullstackgtm diff --before old.json --after new.json --fail-on-new-findings
|
|
286
308
|
```
|
|
287
309
|
|
|
310
|
+
- Terminal polish never leaks into machine output: color, spinners, progress bars, and sparklines render **only on an interactive TTY** (stderr for anything animated). Piped, redirected, `--json`, CI, and `NO_COLOR` output carries **zero ANSI bytes** — enforced by tests and a CI contract check — so transcripts and pipes see the same plain text they always did (`FORCE_COLOR=1` opts back in).
|
|
288
311
|
- Finding and operation ids are **stable hashes** of rule + record, so two runs over the same data produce identical ids — agents can diff plans, track findings across runs, and approve operations by id without re-parsing.
|
|
289
312
|
- `--demo` (with `--seed`) generates a realistic mid-market CRM with injected real-world failure modes — departed owners, unlinked deals, orphan accounts, stale pipeline — so agents and CI can exercise the full snapshot → audit → apply pipeline with zero credentials.
|
|
290
313
|
- Exit codes: `0` success, `1` error, `2` findings at/above `--fail-on`.
|
|
291
314
|
|
|
292
|
-
"Built for agents" is measured, not asserted: a 1,
|
|
315
|
+
"Built for agents" is measured, not asserted: a 1,892-run benchmark (20 scenarios = 17 synthetic + 3 seeded from an anonymized real portal, × 3 tool-surface arms × up to 4 trials, across nine models from six vendors, deterministic graders over final CRM state, τ-bench-style pass^k) shows the gated CLI surface beating raw CRM-API access on completion-under-policy for every model tested — and the tool-surface effect is monotonic and vendor-independent. Full matrix and methodology: [the leaderboard](./evals/crm/leaderboard/RESULTS.md).
|
|
293
316
|
|
|
294
317
|
The design is **deterministic apply, governed suggest**: the parts that touch your CRM — the audit rules, the plan/apply contract, compare-and-set, the survivor/merge logic — are deterministic and replayable; the parts that read free text (`call parse`/`score`, `market classify`) are LLM-powered but bounded, with every quoted span mechanically verified against the source before it can drive a writeback. Nondeterministic suggestion, deterministic governance.
|
|
295
318
|
|
|
@@ -477,14 +500,14 @@ The MCP entrypoint needs the optional peer dependencies `@modelcontextprotocol/s
|
|
|
477
500
|
npm install fullstackgtm @modelcontextprotocol/sdk zod
|
|
478
501
|
HUBSPOT_ACCESS_TOKEN=pat-... npx fullstackgtm-mcp
|
|
479
502
|
|
|
480
|
-
# Zero-install
|
|
481
|
-
npx -
|
|
503
|
+
# Zero-install (the fullstackgtm-mcp wrapper package bundles the MCP peers)
|
|
504
|
+
npx -y fullstackgtm-mcp
|
|
482
505
|
```
|
|
483
506
|
|
|
484
507
|
Add it to Claude Code in one command:
|
|
485
508
|
|
|
486
509
|
```bash
|
|
487
|
-
claude mcp add fullstackgtm -e HUBSPOT_ACCESS_TOKEN=pat-... -- npx -y
|
|
510
|
+
claude mcp add fullstackgtm -e HUBSPOT_ACCESS_TOKEN=pat-... -- npx -y fullstackgtm-mcp
|
|
488
511
|
```
|
|
489
512
|
|
|
490
513
|
Or configure any MCP client (Cursor, Claude Desktop, …) with:
|
|
@@ -494,7 +517,7 @@ Or configure any MCP client (Cursor, Claude Desktop, …) with:
|
|
|
494
517
|
"mcpServers": {
|
|
495
518
|
"fullstackgtm": {
|
|
496
519
|
"command": "npx",
|
|
497
|
-
"args": ["-y", "
|
|
520
|
+
"args": ["-y", "fullstackgtm-mcp"],
|
|
498
521
|
"env": { "HUBSPOT_ACCESS_TOKEN": "pat-..." }
|
|
499
522
|
}
|
|
500
523
|
}
|
package/dist/audit.d.ts
CHANGED
|
@@ -3,5 +3,7 @@ export declare function defaultPolicy(today?: string): GtmPolicy;
|
|
|
3
3
|
/**
|
|
4
4
|
* Run every rule over the snapshot and collect the results into a single
|
|
5
5
|
* dry-run patch plan. Pass custom rules to extend or replace the built-ins.
|
|
6
|
+
* `onRule` reports per-rule progress (presentation only — a throwing callback
|
|
7
|
+
* never fails the audit).
|
|
6
8
|
*/
|
|
7
|
-
export declare function auditSnapshot(snapshot: CanonicalGtmSnapshot, policy?: GtmPolicy, rules?: GtmAuditRule[]): PatchPlan;
|
|
9
|
+
export declare function auditSnapshot(snapshot: CanonicalGtmSnapshot, policy?: GtmPolicy, rules?: GtmAuditRule[], onRule?: (ruleId: string, phase: "start" | "done", findings?: number) => void): PatchPlan;
|
package/dist/audit.js
CHANGED
|
@@ -4,6 +4,19 @@ const DEFAULT_POLICY = {
|
|
|
4
4
|
requireDealOwner: true,
|
|
5
5
|
requireAccountForDeal: true,
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Wall clock for the plan's stdout-bound `createdAt`, honoring the
|
|
9
|
+
* SOURCE_DATE_EPOCH reproducible-build convention (seconds since the Unix
|
|
10
|
+
* epoch). When set, `audit --demo --json` is byte-deterministic across
|
|
11
|
+
* re-runs — golden tests and CI diffs can pin the payload. Unset (normal
|
|
12
|
+
* use) it is the real clock, exactly as before.
|
|
13
|
+
*/
|
|
14
|
+
function nowIso() {
|
|
15
|
+
const epoch = process.env.SOURCE_DATE_EPOCH;
|
|
16
|
+
if (epoch && /^\d+$/.test(epoch))
|
|
17
|
+
return new Date(Number(epoch) * 1000).toISOString();
|
|
18
|
+
return new Date().toISOString();
|
|
19
|
+
}
|
|
7
20
|
export function defaultPolicy(today = new Date().toISOString().slice(0, 10)) {
|
|
8
21
|
return {
|
|
9
22
|
...DEFAULT_POLICY,
|
|
@@ -13,15 +26,29 @@ export function defaultPolicy(today = new Date().toISOString().slice(0, 10)) {
|
|
|
13
26
|
/**
|
|
14
27
|
* Run every rule over the snapshot and collect the results into a single
|
|
15
28
|
* dry-run patch plan. Pass custom rules to extend or replace the built-ins.
|
|
29
|
+
* `onRule` reports per-rule progress (presentation only — a throwing callback
|
|
30
|
+
* never fails the audit).
|
|
16
31
|
*/
|
|
17
|
-
export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builtinAuditRules) {
|
|
32
|
+
export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builtinAuditRules, onRule) {
|
|
18
33
|
const context = { snapshot, policy, index: buildSnapshotIndex(snapshot) };
|
|
19
34
|
const findings = [];
|
|
20
35
|
const operations = [];
|
|
21
36
|
for (const rule of rules) {
|
|
37
|
+
try {
|
|
38
|
+
onRule?.(rule.id, "start");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// progress is presentation-only
|
|
42
|
+
}
|
|
22
43
|
const result = rule.evaluate(context);
|
|
23
44
|
findings.push(...result.findings);
|
|
24
45
|
operations.push(...result.operations);
|
|
46
|
+
try {
|
|
47
|
+
onRule?.(rule.id, "done", result.findings.length);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// progress is presentation-only
|
|
51
|
+
}
|
|
25
52
|
}
|
|
26
53
|
const evidence = buildEvidence(snapshot, findings, policy.today);
|
|
27
54
|
const pipelineFindings = buildPipelineFindings(findings, operations, evidence, policy.today);
|
|
@@ -29,7 +56,7 @@ export function auditSnapshot(snapshot, policy = defaultPolicy(), rules = builti
|
|
|
29
56
|
return {
|
|
30
57
|
id: `patch_plan_${stableHash(`${snapshot.provider}:${snapshot.generatedAt}:${findings.length}:${operations.length}`)}`,
|
|
31
58
|
title: "GTM hygiene audit patch plan",
|
|
32
|
-
createdAt:
|
|
59
|
+
createdAt: nowIso(),
|
|
33
60
|
status: operations.length > 0 ? "needs_approval" : "draft",
|
|
34
61
|
dryRun: true,
|
|
35
62
|
summary: `${findings.length} findings and ${operations.length} proposed dry-run operations.`,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function snapshotCommand(args: string[]): Promise<void>;
|
|
2
|
+
export declare function audit(args: string[]): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Roll up the active profile's health timeline (accrued by `audit --save`):
|
|
5
|
+
* current deterministic score, change since the last audit, and per-rule
|
|
6
|
+
* deltas. Read-only — it only reads `health.jsonl`, never re-audits.
|
|
7
|
+
*/
|
|
8
|
+
export declare function healthCommand(args: string[]): void;
|
|
9
|
+
/**
|
|
10
|
+
* Render an audit as a client-facing deliverable. Same sources and audit
|
|
11
|
+
* options as `audit`; `--plan` instead renders an existing plan JSON without
|
|
12
|
+
* re-fetching (useful for a plan produced earlier or by another machine).
|
|
13
|
+
*/
|
|
14
|
+
export declare function reportCommand(args: string[]): Promise<void>;
|
|
15
|
+
export declare function rulesCommand(args: string[]): Promise<void>;
|