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/llms.txt
CHANGED
|
@@ -20,6 +20,7 @@ at/above `--fail-on`.
|
|
|
20
20
|
- [Contributing](https://github.com/fullstackgtm/core/blob/main/CONTRIBUTING.md): dev setup, the open-core mirror model, the release ritual
|
|
21
21
|
- [API reference](https://github.com/fullstackgtm/core/blob/main/docs/api.md): semver-covered surfaces — canonical model, rule interface, plan/apply contract, connector contract, config, CLI, MCP tools
|
|
22
22
|
- [CRM-health lifecycle](https://github.com/fullstackgtm/core/blob/main/docs/crm-health-lifecycle.md): the Prevent → Detect → Remediate → Verify/Attribute model; no-new-dupes design
|
|
23
|
+
- [TAM mapping](https://github.com/fullstackgtm/core/blob/main/docs/tam.md): size the reachable market from the ICP, then populate it with scheduled governed acquire and track coverage to an ETA
|
|
23
24
|
- [CHANGELOG](https://github.com/fullstackgtm/core/blob/main/CHANGELOG.md): release history
|
|
24
25
|
|
|
25
26
|
`fullstackgtm resolve <type>` is the create gate: exit 0 = safe to create,
|
|
@@ -58,6 +59,60 @@ uncertainty; report bubbles ∝ estimated share only when signals exist.
|
|
|
58
59
|
Storage is profile-scoped under `<home>/market/<category>`. MCP:
|
|
59
60
|
`fullstackgtm_market_worksheet`, `fullstackgtm_market_observe`.
|
|
60
61
|
|
|
62
|
+
## Key invariants (TAM mapping)
|
|
63
|
+
|
|
64
|
+
`fullstackgtm tam` sizes the reachable market FROM the ICP and then iteratively
|
|
65
|
+
fills it — not a one-shot headline. `tam estimate` computes a transparent model:
|
|
66
|
+
account universe × ACV = TAM (account/per-logo basis by default; `--acv-basis
|
|
67
|
+
buyer` for per-seat = contacts × ACV), where contacts = accounts ×
|
|
68
|
+
buyers/account is the population target. The account count is `--accounts <n>`
|
|
69
|
+
(assumption), OR `--source theirstack` — the TECHNOGRAPHIC universe: counts
|
|
70
|
+
companies that USE a CRM/MAP (`firmographics.technologies` → TheirStack
|
|
71
|
+
`company_technology_slug_or`) + geo + employee bounds; the real RevOps buying signal
|
|
72
|
+
AND the only source that returns a LIST (`tam accounts --source theirstack` pulls
|
|
73
|
+
real companies name/domain, `--out csv`/`--json`, `--max` caps credit spend; a
|
|
74
|
+
count is cheap but NOT free — limit:0 is rejected so a count returns 1 row ≈ 3
|
|
75
|
+
credits, a list pull ≈ 3/company; `login theirstack`). OR `--source explorium` — a firmographic
|
|
76
|
+
count via `/v1/businesses` (`total_results`), a count ONLY (Explorium 403s on
|
|
77
|
+
list-pull, can't target by CRM-usage; must OMIT `size` or it caps the total; capped
|
|
78
|
+
at 60,000 `EXPLORIUM_BUSINESS_COUNT_CAP` → `provider:explorium (≥60k cap — floor)` +
|
|
79
|
+
⚠). Verified live: people endpoints can't size a market (Explorium `/v1/prospects`
|
|
80
|
+
total == page size; pipe0/Crustdata returns only a cursor — `--source pipe0` for
|
|
81
|
+
estimate is REJECTED, it's a population source). The model always LABELS
|
|
82
|
+
`accountsSource` (provider:theirstack/explorium vs assumption). ACV must be a
|
|
83
|
+
real ANNUAL figure the operator CONFIRMS — `--acv <annual-usd>`, OR `--acv-from-crm
|
|
84
|
+
--deal-period monthly|quarterly|annual` which takes the median closed-won amount and
|
|
85
|
+
ANNUALIZES it (the period is required — a deal can be MRR/quarterly/annual and
|
|
86
|
+
guessing is a 4–12× error; $15k/mo → $180k). NO band defaults, and a bare
|
|
87
|
+
`--provider` does NOT auto-set ACV (HubSpot is the COVERAGE source, not the ACV) —
|
|
88
|
+
`estimate` REFUSES without a confirmed ACV. Buyers/account is `--buyers-per-account`
|
|
89
|
+
OR the CRM's avg-contacts-per-account OR a labeled `assumption:1-buyer`. Both
|
|
90
|
+
`acv.source` and `universe.buyersSource` are labeled (incl. deal count + the
|
|
91
|
+
period×factor, so a thin signal like "1 deal" is visible). Optional
|
|
92
|
+
citable cross-checks (`{claim,valueUsd?,sourceUrl,quote}`) sit beside the
|
|
93
|
+
bottom-up number as a top-down sanity check. `estimate`
|
|
94
|
+
records a coverage BASELINE (CRM counts now) so `status` attributes what the
|
|
95
|
+
campaign added. `tam populate --cron` schedules `enrich acquire --source <s>
|
|
96
|
+
--save` — the ONE create-side verb the schedule allowlist permits, and only in
|
|
97
|
+
its plan-producing `--save` form: each firing queues a needs_approval lead plan,
|
|
98
|
+
the acquire meter is charged only at apply, and apply stays `apply --plan-id`
|
|
99
|
+
(re-checked approved). `tam status` measures coverage on the INTERSECTION of CRM ×
|
|
100
|
+
TAM-ICP, not a raw count: `tam estimate` stores the ICP filter (`model.targeting`),
|
|
101
|
+
and status CLASSIFIES each domain-bearing account into in-TAM / out-of-TAM / unknown
|
|
102
|
+
(checked on size+industry — geo + "uses-CRM" aren't on a CanonicalAccount, so a
|
|
103
|
+
`--reverify` re-enrich is future; the classifier labels what it checked). ONLY
|
|
104
|
+
in-TAM counts toward coverage, so off-ICP accounts loaded from elsewhere can't
|
|
105
|
+
inflate it. Bottom-up vs top-down reconciliation: the in-TAM count is a FLOOR on the
|
|
106
|
+
real universe (those are verified members), so when in-TAM ≥ the estimate, status
|
|
107
|
+
stops reporting a fake 100% and flips to "estimate was a floor — real market is at
|
|
108
|
+
least N (`reconciledUniverse = max(estimate, inTam)`); re-estimate for the headroom".
|
|
109
|
+
`--save` stamps an append-only timeline. `tam report` (markdown) and `status` project
|
|
110
|
+
an ETA on the IN-TAM accounts/day burn across the timeline → days-remaining → fill
|
|
111
|
+
date — and REFUSE to project (honest "not enough history") below two readings, a flat
|
|
112
|
+
rate, or zero elapsed time.
|
|
113
|
+
Coverage + ETA are deterministic (no LLM, byte-stable). Storage profile-scoped
|
|
114
|
+
under `<home>/tam/<name>/` (`model.json` + `coverage.jsonl`, 0600).
|
|
115
|
+
|
|
61
116
|
## Key invariants (governed write verbs)
|
|
62
117
|
|
|
63
118
|
`bulk-update <object> --where … (--set|--archive|--create-task)` filters the
|
|
@@ -123,8 +178,12 @@ and no webhook ingestion (Phase 2). Never auto-writes.
|
|
|
123
178
|
The timing/outbound layer turns "who changed" into one grounded, governed
|
|
124
179
|
opener — and **never sends**. `signals fetch` captures fresh buying triggers
|
|
125
180
|
into a profile-scoped ledger (free Greenhouse/Lever/Ashby ATS scrapers in the
|
|
126
|
-
box; funding/social via
|
|
127
|
-
|
|
181
|
+
box; funding/social via `--from`; connected platforms via `--connector`
|
|
182
|
+
file/serpapi-news/hubspot-forms with secrets through the credential ladder; and
|
|
183
|
+
push platforms via the webhook spool — `--connector file` reads the conventional
|
|
184
|
+
landing zone `<home>/signals/spool/*.jsonl`, see docs/signal-spool-format.md),
|
|
185
|
+
keyed by **account domain**, ranked by learned `weights`; it writes NOTHING to
|
|
186
|
+
the CRM. `icp interview|set|show`
|
|
128
187
|
builds `icp.json`; `icp judge` ranks fresh signals into send/nurture/skip — pass
|
|
129
188
|
a snapshot (`--provider`/`--input`/`--demo`) and each decision resolves its CRM
|
|
130
189
|
target: `accountId`, the best `contact {id,email,title}`, and `contacts[]` (all
|
|
@@ -135,8 +194,11 @@ bar). `draft` emits ONE trigger-grounded opener per hot account as a governed
|
|
|
135
194
|
`create_task` targeting the resolved `contact.id` (or `accountId`) — a
|
|
136
195
|
domain-only decision (account not yet in the CRM) is REJECTED with "acquire it
|
|
137
196
|
first" (run `enrich acquire`, re-judge with the snapshot). `--channel
|
|
138
|
-
email|linkedin|task` shapes the opener
|
|
139
|
-
|
|
197
|
+
email|linkedin|task` shapes the opener. After `plans approve`, applying the draft
|
|
198
|
+
plan logs the touch as a CRM task (`apply --provider <crm>`) OR renders it to the
|
|
199
|
+
governed outbox (`apply --channel outbox` → `<home>/signals/outbox/<channel>.jsonl`
|
|
200
|
+
for a downstream sender to drain). Either way the CLI **transmits nothing** — the
|
|
201
|
+
outbox is the send-side mirror of the spool; the always-on sender is hosted/opt-in. `signals outcome --account <domain>
|
|
140
202
|
--contact <id> --result replied|...` credits the contact reached so weights
|
|
141
203
|
re-learn. The contact↔account join (`contact.accountId` + `account.domain`) is
|
|
142
204
|
surfaced at every hop, so the loop is contact-coherent end to end — see the
|
|
@@ -147,18 +209,28 @@ surfaced at every hop, so the loop is contact-coherent end to end — see the
|
|
|
147
209
|
`fullstackgtm schedule` is the horizontal scheduler — no feature namespace
|
|
148
210
|
owns cron logic. `add "<command>" --cron "<expr>"` validates the command
|
|
149
211
|
against the read/plan-side allowlist (audit, snapshot, enrich append|refresh,
|
|
150
|
-
market capture|refresh,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
212
|
+
enrich acquire --save, market capture|refresh, signals fetch, icp judge|eval,
|
|
213
|
+
draft, suggest, report, doctor) and the 5-field cron
|
|
214
|
+
expression, but touches no system timer; `install` materializes enabled
|
|
215
|
+
entries into the platform timer (`--timer` defaults launchd on macOS,
|
|
216
|
+
crontab elsewhere). crontab: a sentinel-delimited managed block
|
|
217
|
+
(`# >>> fullstackgtm <profile> >>>`) replaced wholesale on re-install, lines
|
|
218
|
+
outside it untouched. launchd: one LaunchAgent plist per entry
|
|
219
|
+
(`com.fullstackgtm.<profile>.<id>` in `~/Library/LaunchAgents`), the fleet
|
|
220
|
+
replaced wholesale, foreign plists untouched — chosen because macOS gates
|
|
221
|
+
`crontab` writes behind Full Disk Access (unpromptable TCC), while
|
|
222
|
+
LaunchAgents need no permission and coalesce firings missed during sleep
|
|
223
|
+
into one run on wake. `uninstall` removes only what install created.
|
|
224
|
+
Scheduling never auto-approves: `apply` is
|
|
155
225
|
schedulable ONLY as `apply --plan-id <id>` and every firing re-checks the
|
|
156
226
|
plan is approved — unapproved plans record a `plan_not_approved` no-op run,
|
|
157
227
|
no flag relaxes this. `schedule run <id>` is the single provider entry point
|
|
158
|
-
(in-process dispatch, never shell
|
|
228
|
+
(in-process dispatch, never shell; a second cron trigger in the same minute
|
|
229
|
+
records a `duplicate_firing` no-op — launchd double-triggers the Vixie
|
|
230
|
+
dom+dow OR corner); run records (exit code, output tail,
|
|
159
231
|
artifacts: plan ids / enrich run labels, trigger cron|manual) land under
|
|
160
232
|
`<home>/schedule/runs/<id>/`. `status` shows next firing and surfaces missed
|
|
161
|
-
firings (visibility only —
|
|
233
|
+
firings (visibility only — cron has no catch-up). Providers beyond
|
|
162
234
|
`local` are not yet implemented (future: codegen scaffolds, same contract).
|
|
163
235
|
|
|
164
236
|
## Key invariants (engagement workspace / health)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fullstackgtm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Open-source agentic GTM ops framework: canonical GTM data model, pluggable deterministic audits, reviewable dry-run patch plans, approval-gated write-back with conflict detection, and cross-system entity resolution. HubSpot, Salesforce, and Stripe connectors included.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Full Stack GTM LLC <ryan@fullstackgtm.com> (https://fullstackgtm.com)",
|
|
@@ -63,10 +63,11 @@ credentials AND stored plans per client org.
|
|
|
63
63
|
| `call parse\|score\|link\|plan` | Transcripts → evidence-quoted insights, rubric scorecards, deal linking, governed next-step writes |
|
|
64
64
|
| `enrich append\|refresh\|ingest\|status` | Governed enrichment (Apollo pull / Clay ingest), fill-blanks-only plans |
|
|
65
65
|
| `enrich acquire [--source explorium\|pipe0\|linkedin]` | Net-new ICP-targeted lead gen: resolve-first deduped `create_record` plans, capped by a per-profile windowed meter (records + spend); owner-stamped via `acquire.assign`/`--assign-owner` (never born ownerless); links each lead to a domain-stamped, signal-watchable account; `acquire.create.company` acquires accounts (ABM); `--source linkedin --list <id>` reads a HeyReach lead list (Phase 1 discovery, read-only — never sends); never auto-writes |
|
|
66
|
-
| `signals fetch\|list\|outcome\|weights` | Detect-side timing layer: capture fresh buying triggers into a profile-scoped ledger (free Greenhouse/Lever/Ashby ATS in the box;
|
|
66
|
+
| `signals fetch\|list\|outcome\|weights` | Detect-side timing layer: capture fresh buying triggers into a profile-scoped ledger (free Greenhouse/Lever/Ashby ATS in the box; `--from` staged files; `--connector file\|serpapi-news\|hubspot-forms` to pull connected platforms / read the webhook spool `<home>/signals/spool`, secrets via the credential ladder), ranked by learned weights. Writes NOTHING to the CRM; `outcome --account <domain> --contact <id> --result …` re-weights which triggers earn a touch |
|
|
67
67
|
| `icp interview\|set\|show\|judge\|eval` | Build the ICP, then `judge` ranks fresh signals into send/nurture/skip — pass a snapshot (`--provider`/`--input`) and each decision resolves its CRM target (`accountId` + the `contact`/`contacts` to reach). `eval` is the calibration gate (exit 2 below the bar) |
|
|
68
|
-
| `draft [--from-judge latest] [--channel email\|linkedin\|task]` | One trigger-grounded opener per hot account → a governed `create_task` targeting the resolved `contact.id` (or `accountId`); rejects a domain-only decision ("acquire it first"). **Never sends** —
|
|
68
|
+
| `draft [--from-judge latest] [--channel email\|linkedin\|task]` | One trigger-grounded opener per hot account → a governed `create_task` targeting the resolved `contact.id` (or `accountId`); rejects a domain-only decision ("acquire it first"). **Never sends** — after `plans approve`, `apply --provider <crm>` logs it as a CRM task, or `apply --channel outbox` renders it to `<home>/signals/outbox/<channel>.jsonl` for a downstream sender to drain; the CLI transmits nothing |
|
|
69
69
|
| `market init\|capture\|classify\|worksheet\|observe\|fronts\|axes\|overlay\|scale\|report\|refresh` | Competitive category map; evidence quotes verified verbatim against stored captures |
|
|
70
|
+
| `tam estimate\|accounts\|status\|report\|populate` | Size the reachable market FROM the ICP (real account count × real ACV; buyers/account → contact target), then iteratively fill it: `populate` schedules plan-only `enrich acquire --save` (apply stays gated), `status` classifies CRM accounts vs the TAM ICP into **in-TAM / out-of-TAM / unknown** (only in-TAM counts — junk loaded from elsewhere doesn't inflate coverage; checked on size+industry, geo/tech need re-enrich) and flips to "estimate was a FLOOR, revise up" when bottom-up in-CRM exceeds the top-down estimate; `--save` stamps a timeline, `report` projects burn-up + ETA on the in-TAM rate. **`estimate --source theirstack`** counts companies that USE a CRM/MAP (`firmographics.technologies`) — the real RevOps universe — and **`tam accounts --source theirstack`** pulls the actual company LIST (names+domains, `--out csv`) at ~3 credits/company — `--dry-run [--usd-per-credit <r>]` prices the pull + full TAM for 0 credits, `--confirm` gates pulls above `--max-credits` (default 150). `--source explorium` = firmographic count only (no list, 60k cap = floor); else `--accounts`. **ACV must be a real ANNUAL figure you confirm** — `--acv <annual-usd>`, or `--acv-from-crm --deal-period monthly\|quarterly\|annual` (median closed-won, annualized); NO band defaults, a bare `--provider` does NOT set ACV (HubSpot=coverage), refuses without one. Buyers/account = CRM avg-contacts or explicit. Every number labeled with its source |
|
|
70
71
|
| `schedule add\|list\|remove\|enable\|disable\|run\|install\|uninstall\|status` | Horizontal cron; read/plan-side allowlist only — scheduling NEVER auto-approves |
|
|
71
72
|
| `report` | Client-ready audit deliverable (markdown or self-contained HTML) |
|
|
72
73
|
| `health [--json]` | Per-profile CRM health timeline: deterministic 0–100 score, trend, per-rule deltas — accrues from `audit --save`, read-only |
|
|
@@ -78,7 +79,7 @@ Add `--json` for machine-readable output on any command.
|
|
|
78
79
|
## MCP server (alternative surface, same gates)
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
npx -y
|
|
82
|
+
npx -y fullstackgtm-mcp
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
Tools over stdio: `fullstackgtm_audit` (read-only), `fullstackgtm_rules`,
|
package/src/audit.ts
CHANGED
|
@@ -18,6 +18,19 @@ const DEFAULT_POLICY: Omit<GtmPolicy, "today"> = {
|
|
|
18
18
|
requireAccountForDeal: true,
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Wall clock for the plan's stdout-bound `createdAt`, honoring the
|
|
23
|
+
* SOURCE_DATE_EPOCH reproducible-build convention (seconds since the Unix
|
|
24
|
+
* epoch). When set, `audit --demo --json` is byte-deterministic across
|
|
25
|
+
* re-runs — golden tests and CI diffs can pin the payload. Unset (normal
|
|
26
|
+
* use) it is the real clock, exactly as before.
|
|
27
|
+
*/
|
|
28
|
+
function nowIso(): string {
|
|
29
|
+
const epoch = process.env.SOURCE_DATE_EPOCH;
|
|
30
|
+
if (epoch && /^\d+$/.test(epoch)) return new Date(Number(epoch) * 1000).toISOString();
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export function defaultPolicy(today = new Date().toISOString().slice(0, 10)): GtmPolicy {
|
|
22
35
|
return {
|
|
23
36
|
...DEFAULT_POLICY,
|
|
@@ -28,20 +41,33 @@ export function defaultPolicy(today = new Date().toISOString().slice(0, 10)): Gt
|
|
|
28
41
|
/**
|
|
29
42
|
* Run every rule over the snapshot and collect the results into a single
|
|
30
43
|
* dry-run patch plan. Pass custom rules to extend or replace the built-ins.
|
|
44
|
+
* `onRule` reports per-rule progress (presentation only — a throwing callback
|
|
45
|
+
* never fails the audit).
|
|
31
46
|
*/
|
|
32
47
|
export function auditSnapshot(
|
|
33
48
|
snapshot: CanonicalGtmSnapshot,
|
|
34
49
|
policy: GtmPolicy = defaultPolicy(),
|
|
35
50
|
rules: GtmAuditRule[] = builtinAuditRules,
|
|
51
|
+
onRule?: (ruleId: string, phase: "start" | "done", findings?: number) => void,
|
|
36
52
|
): PatchPlan {
|
|
37
53
|
const context = { snapshot, policy, index: buildSnapshotIndex(snapshot) };
|
|
38
54
|
const findings: AuditFinding[] = [];
|
|
39
55
|
const operations: PatchOperation[] = [];
|
|
40
56
|
|
|
41
57
|
for (const rule of rules) {
|
|
58
|
+
try {
|
|
59
|
+
onRule?.(rule.id, "start");
|
|
60
|
+
} catch {
|
|
61
|
+
// progress is presentation-only
|
|
62
|
+
}
|
|
42
63
|
const result = rule.evaluate(context);
|
|
43
64
|
findings.push(...result.findings);
|
|
44
65
|
operations.push(...result.operations);
|
|
66
|
+
try {
|
|
67
|
+
onRule?.(rule.id, "done", result.findings.length);
|
|
68
|
+
} catch {
|
|
69
|
+
// progress is presentation-only
|
|
70
|
+
}
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
|
|
@@ -52,7 +78,7 @@ export function auditSnapshot(
|
|
|
52
78
|
return {
|
|
53
79
|
id: `patch_plan_${stableHash(`${snapshot.provider}:${snapshot.generatedAt}:${findings.length}:${operations.length}`)}`,
|
|
54
80
|
title: "GTM hygiene audit patch plan",
|
|
55
|
-
createdAt:
|
|
81
|
+
createdAt: nowIso(),
|
|
56
82
|
status: operations.length > 0 ? "needs_approval" : "draft",
|
|
57
83
|
dryRun: true,
|
|
58
84
|
summary: `${findings.length} findings and ${operations.length} proposed dry-run operations.`,
|