fullstackgtm 0.14.0 → 0.15.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 +42 -0
- package/README.md +14 -0
- package/dist/cli.js +69 -6
- package/dist/connectors/hubspot.js +62 -7
- package/dist/diff.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mcp.js +20 -0
- package/dist/merge.js +1 -1
- package/dist/resolve.d.ts +37 -0
- package/dist/resolve.js +107 -0
- package/dist/rules.d.ts +12 -0
- package/dist/rules.js +25 -3
- package/dist/types.d.ts +16 -0
- package/docs/crm-health-lifecycle.md +11 -11
- package/llms.txt +4 -0
- package/package.json +1 -1
- package/src/cli.ts +70 -6
- package/src/connectors/hubspot.ts +68 -10
- package/src/diff.ts +1 -1
- package/src/index.ts +2 -0
- package/src/mcp.ts +26 -0
- package/src/merge.ts +1 -1
- package/src/resolve.ts +157 -0
- package/src/rules.ts +24 -3
- package/src/types.ts +17 -0
package/src/rules.ts
CHANGED
|
@@ -21,6 +21,27 @@ export function requiresHumanInput(value: unknown): boolean {
|
|
|
21
21
|
return typeof value === "string" && value.startsWith(REQUIRES_HUMAN_PREFIX);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Attribution for duplicate groups: when the provider exposes record-source
|
|
26
|
+
* provenance (RecordProvenance), name the writer(s) that created the group —
|
|
27
|
+
* the fix for recurring dupes is upstream in the writer, not in the records.
|
|
28
|
+
*/
|
|
29
|
+
export function provenanceSummary(records: Array<{ provenance?: { source?: string; sourceLabel?: string; sourceId?: string } }>): string {
|
|
30
|
+
const counts = new Map<string, number>();
|
|
31
|
+
for (const record of records) {
|
|
32
|
+
const p = record.provenance;
|
|
33
|
+
if (!p) continue;
|
|
34
|
+
const label = p.sourceLabel ?? p.source ?? "unknown source";
|
|
35
|
+
const key = p.sourceId ? `${label} (${p.sourceId})` : label;
|
|
36
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
37
|
+
}
|
|
38
|
+
if (counts.size === 0) return "";
|
|
39
|
+
const parts = [...counts.entries()]
|
|
40
|
+
.sort((a, b) => b[1] - a[1])
|
|
41
|
+
.map(([key, count]) => (count > 1 ? `${key} ×${count}` : key));
|
|
42
|
+
return ` Created by: ${parts.join(", ")}.`;
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
export function auditFindingId(ruleId: string, objectId: string) {
|
|
25
46
|
return `finding_${stableHash(`${ruleId}:${objectId}`)}`;
|
|
26
47
|
}
|
|
@@ -343,7 +364,7 @@ export const duplicateAccountDomainRule: GtmAuditRule = {
|
|
|
343
364
|
ruleId: "duplicate-account-domain",
|
|
344
365
|
title: "Accounts share the same domain",
|
|
345
366
|
severity: "warning",
|
|
346
|
-
summary: `${accounts.length} accounts share ${domain}: ${accounts.map((account) => account.name).join(", ")}
|
|
367
|
+
summary: `${accounts.length} accounts share ${domain}: ${accounts.map((account) => account.name).join(", ")}.${provenanceSummary(accounts)}`,
|
|
347
368
|
recommendation: "Review the group and merge duplicates so activity and deals roll up once.",
|
|
348
369
|
});
|
|
349
370
|
operations.push({
|
|
@@ -383,7 +404,7 @@ export const duplicateContactEmailRule: GtmAuditRule = {
|
|
|
383
404
|
ruleId: "duplicate-contact-email",
|
|
384
405
|
title: "Contacts share the same email",
|
|
385
406
|
severity: "warning",
|
|
386
|
-
summary: `${contacts.length} contacts share ${email}
|
|
407
|
+
summary: `${contacts.length} contacts share ${email}.${provenanceSummary(contacts)}`,
|
|
387
408
|
recommendation: "Merge the duplicates so engagement history and routing stay coherent.",
|
|
388
409
|
});
|
|
389
410
|
operations.push({
|
|
@@ -433,7 +454,7 @@ export const duplicateOpenDealRule: GtmAuditRule = {
|
|
|
433
454
|
severity: "warning",
|
|
434
455
|
summary: `${deals.length} open deals named "${anchor.name}"${
|
|
435
456
|
anchor.accountId ? " on the same account" : ""
|
|
436
|
-
}: ${deals.map((deal) => deal.id).join(", ")}
|
|
457
|
+
}: ${deals.map((deal) => deal.id).join(", ")}.${provenanceSummary(deals)}`,
|
|
437
458
|
recommendation:
|
|
438
459
|
"Keep one deal, archive the copies, and fix the integration that is re-creating them.",
|
|
439
460
|
});
|
package/src/types.ts
CHANGED
|
@@ -141,11 +141,26 @@ export type CanonicalUser = {
|
|
|
141
141
|
active?: boolean;
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Who created a record, per the provider's read-only record-source fields
|
|
146
|
+
* (HubSpot: hs_object_source / _label / _id). Populated on read; used to
|
|
147
|
+
* attribute duplicate findings to the writer that produced them.
|
|
148
|
+
*/
|
|
149
|
+
export type RecordProvenance = {
|
|
150
|
+
/** Provider source code, e.g. INTEGRATION, API, CRM_UI, IMPORT, FORM. */
|
|
151
|
+
source?: string;
|
|
152
|
+
/** Human label, e.g. an integration's name. */
|
|
153
|
+
sourceLabel?: string;
|
|
154
|
+
/** Provider-side id of the source (e.g. app id, import id). */
|
|
155
|
+
sourceId?: string;
|
|
156
|
+
};
|
|
157
|
+
|
|
144
158
|
export type CanonicalAccount = {
|
|
145
159
|
id: string;
|
|
146
160
|
provider?: CrmProvider;
|
|
147
161
|
crmId?: string;
|
|
148
162
|
identities?: ProviderIdentity[];
|
|
163
|
+
provenance?: RecordProvenance;
|
|
149
164
|
name: string;
|
|
150
165
|
domain?: string;
|
|
151
166
|
industry?: string;
|
|
@@ -163,6 +178,7 @@ export type CanonicalContact = {
|
|
|
163
178
|
provider?: CrmProvider;
|
|
164
179
|
crmId?: string;
|
|
165
180
|
identities?: ProviderIdentity[];
|
|
181
|
+
provenance?: RecordProvenance;
|
|
166
182
|
accountId?: string;
|
|
167
183
|
firstName?: string;
|
|
168
184
|
lastName?: string;
|
|
@@ -181,6 +197,7 @@ export type CanonicalDeal = {
|
|
|
181
197
|
provider?: CrmProvider;
|
|
182
198
|
crmId?: string;
|
|
183
199
|
identities?: ProviderIdentity[];
|
|
200
|
+
provenance?: RecordProvenance;
|
|
184
201
|
accountId?: string;
|
|
185
202
|
ownerId?: string;
|
|
186
203
|
name: string;
|