fullstackgtm 0.47.0 → 0.48.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 +38 -6
- package/INSTALL_FOR_AGENTS.md +3 -3
- package/README.md +16 -8
- package/dist/audit.js +7 -0
- package/dist/backfill.js +2 -16
- package/dist/cli/fix.d.ts +3 -0
- package/dist/cli/fix.js +70 -1
- package/dist/cli/help.d.ts +6 -0
- package/dist/cli/help.js +76 -2
- package/dist/cli/suggest.d.ts +2 -1
- package/dist/cli/suggest.js +49 -3
- package/dist/cli.js +40 -15
- package/dist/connectors/hubspot.d.ts +2 -1
- package/dist/connectors/salesforce.d.ts +2 -1
- package/dist/connectors/signalSources.js +2 -11
- package/dist/format.js +31 -5
- package/dist/freeEmailDomains.d.ts +1 -0
- package/dist/freeEmailDomains.js +14 -0
- package/dist/hierarchy.d.ts +29 -0
- package/dist/hierarchy.js +164 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/mcp.js +19 -15
- package/dist/relationships.d.ts +39 -0
- package/dist/relationships.js +101 -0
- package/dist/route.d.ts +46 -0
- package/dist/route.js +228 -0
- package/dist/rules.d.ts +1 -0
- package/dist/rules.js +172 -12
- package/dist/types.d.ts +15 -0
- package/docs/api.md +20 -3
- package/docs/architecture.md +5 -2
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +2 -2
- package/src/audit.ts +7 -0
- package/src/backfill.ts +2 -17
- package/src/cli/fix.ts +68 -1
- package/src/cli/help.ts +83 -2
- package/src/cli/suggest.ts +58 -4
- package/src/cli.ts +38 -13
- package/src/connectors/hubspot.ts +3 -1
- package/src/connectors/salesforce.ts +3 -1
- package/src/connectors/signalSources.ts +2 -12
- package/src/format.ts +33 -5
- package/src/freeEmailDomains.ts +14 -0
- package/src/hierarchy.ts +185 -0
- package/src/index.ts +25 -0
- package/src/mcp.ts +22 -16
- package/src/relationships.ts +115 -0
- package/src/route.ts +278 -0
- package/src/rules.ts +192 -12
- package/src/types.ts +17 -0
package/src/rules.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
CanonicalGtmSnapshot,
|
|
8
8
|
GtmAuditRule,
|
|
9
9
|
GtmSnapshotIndex,
|
|
10
|
+
PatchPlanAssumption,
|
|
10
11
|
RiskLevel,
|
|
11
12
|
} from "./types.ts";
|
|
12
13
|
|
|
@@ -50,6 +51,109 @@ export function patchOperationId(ruleId: string, objectId: string) {
|
|
|
50
51
|
return `op_${stableHash(`${ruleId}:${objectId}`)}`;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
|
|
55
|
+
const OPEN_DEAL_FLAGS_ASSUMPTION: PatchPlanAssumption = {
|
|
56
|
+
id: "open-deal-derived-from-closed-won-flags",
|
|
57
|
+
text: "Derived open-deal status from canonical isClosed/isWon flags; a deal is open when neither flag is true.",
|
|
58
|
+
source: "CanonicalDeal.isClosed/isWon",
|
|
59
|
+
confidence: "derived",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const MISSING_CLOSED_WON_FLAGS_ASSUMPTION: PatchPlanAssumption = {
|
|
63
|
+
id: "missing-closed-won-flags-treated-open",
|
|
64
|
+
text: "Treated deals with missing isClosed/isWon flags as open so hygiene rules do not silently ignore active pipeline.",
|
|
65
|
+
source: "CanonicalDeal.isClosed/isWon",
|
|
66
|
+
confidence: "heuristic",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const ORPHAN_ACCOUNT_RELATIONSHIP_ASSUMPTION: PatchPlanAssumption = {
|
|
70
|
+
id: "account-without-contacts-or-deals-treated-orphan",
|
|
71
|
+
text: "Treated an account as orphaned when the snapshot has no contacts and no deals linked to that account.",
|
|
72
|
+
source: "snapshot account/contact/deal associations",
|
|
73
|
+
confidence: "derived",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const DEAL_OWNER_POLICY_ASSUMPTION: PatchPlanAssumption = {
|
|
77
|
+
id: "deal-owner-required-by-policy",
|
|
78
|
+
text: "Treated a missing or unknown deal owner as invalid when requireDealOwner policy is enabled.",
|
|
79
|
+
source: "GtmPolicy.requireDealOwner",
|
|
80
|
+
confidence: "heuristic",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const DEAL_ACCOUNT_POLICY_ASSUMPTION: PatchPlanAssumption = {
|
|
84
|
+
id: "deal-account-required-by-policy",
|
|
85
|
+
text: "Treated a missing or unknown deal account link as invalid when requireAccountForDeal policy is enabled.",
|
|
86
|
+
source: "GtmPolicy.requireAccountForDeal",
|
|
87
|
+
confidence: "heuristic",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const DEAL_AMOUNT_POLICY_ASSUMPTION: PatchPlanAssumption = {
|
|
91
|
+
id: "open-deal-amount-required-by-policy",
|
|
92
|
+
text: "Treated open deals with missing or zero amount as forecast-risk findings unless requireDealAmount is disabled.",
|
|
93
|
+
source: "GtmPolicy.requireDealAmount",
|
|
94
|
+
confidence: "heuristic",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const DUPLICATE_ACCOUNT_DOMAIN_ASSUMPTION: PatchPlanAssumption = {
|
|
98
|
+
id: "same-normalized-domain-duplicate-account",
|
|
99
|
+
text: "Treated accounts with the same normalized domain as duplicate-account candidates.",
|
|
100
|
+
source: "CanonicalAccount.domain",
|
|
101
|
+
confidence: "heuristic",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const DUPLICATE_CONTACT_EMAIL_ASSUMPTION: PatchPlanAssumption = {
|
|
105
|
+
id: "same-email-duplicate-contact",
|
|
106
|
+
text: "Treated contacts with the same lowercased email address as duplicate-contact candidates.",
|
|
107
|
+
source: "CanonicalContact.email",
|
|
108
|
+
confidence: "heuristic",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const DUPLICATE_OPEN_DEAL_ASSUMPTION: PatchPlanAssumption = {
|
|
112
|
+
id: "same-account-and-name-duplicate-open-deal",
|
|
113
|
+
text: "Treated open deals with the same normalized name on the same account scope as duplicate-opportunity candidates.",
|
|
114
|
+
source: "CanonicalDeal.accountId/name",
|
|
115
|
+
confidence: "heuristic",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const ACCOUNT_WITH_OPEN_DEAL_NEEDS_CONTACT_ASSUMPTION: PatchPlanAssumption = {
|
|
119
|
+
id: "open-pipeline-account-needs-contact",
|
|
120
|
+
text: "Treated accounts with open pipeline but no linked contacts as coverage gaps requiring a buying-committee contact.",
|
|
121
|
+
source: "snapshot account/contact/deal associations",
|
|
122
|
+
confidence: "heuristic",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const SINGLE_SOURCE_ACCOUNT_ASSUMPTION: PatchPlanAssumption = {
|
|
126
|
+
id: "single-source-account-is-cross-system-gap",
|
|
127
|
+
text: "Treated an account present in only one connected system as a cross-system reconciliation gap.",
|
|
128
|
+
source: "ProviderIdentity.provider",
|
|
129
|
+
confidence: "heuristic",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function staleDealPolicyAssumption(days: number): PatchPlanAssumption {
|
|
133
|
+
return {
|
|
134
|
+
id: "stale-deal-days-policy",
|
|
135
|
+
text: `Treated open deals with no recorded activity for more than ${days} days as stale.`,
|
|
136
|
+
source: "GtmPolicy.staleDealDays",
|
|
137
|
+
confidence: "heuristic",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function closingSoonPolicyAssumption(windowDays: number, idleDays: number): PatchPlanAssumption {
|
|
142
|
+
return {
|
|
143
|
+
id: "closing-soon-idle-policy",
|
|
144
|
+
text: `Treated open deals closing within ${windowDays} days and idle for more than ${idleDays} days as silent slip risk.`,
|
|
145
|
+
source: "GtmPolicy.closingSoonDays/closingSoonIdleDays",
|
|
146
|
+
confidence: "heuristic",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function assumptionsIfFindings(
|
|
151
|
+
findings: AuditFinding[],
|
|
152
|
+
assumptions: PatchPlanAssumption[],
|
|
153
|
+
): PatchPlanAssumption[] | undefined {
|
|
154
|
+
return findings.length > 0 ? assumptions : undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
53
157
|
export function stableHash(value: string) {
|
|
54
158
|
let hash = 0;
|
|
55
159
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -168,7 +272,11 @@ export const orphanAccountRule: GtmAuditRule = {
|
|
|
168
272
|
approvalRequired: true,
|
|
169
273
|
});
|
|
170
274
|
}
|
|
171
|
-
return {
|
|
275
|
+
return {
|
|
276
|
+
findings,
|
|
277
|
+
operations,
|
|
278
|
+
assumptions: assumptionsIfFindings(findings, [ORPHAN_ACCOUNT_RELATIONSHIP_ASSUMPTION]),
|
|
279
|
+
};
|
|
172
280
|
},
|
|
173
281
|
};
|
|
174
282
|
|
|
@@ -198,7 +306,11 @@ export const missingDealOwnerRule: GtmAuditRule = {
|
|
|
198
306
|
approvalRequired: true,
|
|
199
307
|
});
|
|
200
308
|
}
|
|
201
|
-
return {
|
|
309
|
+
return {
|
|
310
|
+
findings,
|
|
311
|
+
operations,
|
|
312
|
+
assumptions: assumptionsIfFindings(findings, [DEAL_OWNER_POLICY_ASSUMPTION]),
|
|
313
|
+
};
|
|
202
314
|
},
|
|
203
315
|
};
|
|
204
316
|
|
|
@@ -227,7 +339,11 @@ export const missingDealAccountRule: GtmAuditRule = {
|
|
|
227
339
|
approvalRequired: true,
|
|
228
340
|
});
|
|
229
341
|
}
|
|
230
|
-
return {
|
|
342
|
+
return {
|
|
343
|
+
findings,
|
|
344
|
+
operations,
|
|
345
|
+
assumptions: assumptionsIfFindings(findings, [DEAL_ACCOUNT_POLICY_ASSUMPTION]),
|
|
346
|
+
};
|
|
231
347
|
},
|
|
232
348
|
};
|
|
233
349
|
|
|
@@ -256,7 +372,14 @@ export const pastCloseDateRule: GtmAuditRule = {
|
|
|
256
372
|
approvalRequired: true,
|
|
257
373
|
});
|
|
258
374
|
}
|
|
259
|
-
return {
|
|
375
|
+
return {
|
|
376
|
+
findings,
|
|
377
|
+
operations,
|
|
378
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
379
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
380
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
381
|
+
]),
|
|
382
|
+
};
|
|
260
383
|
},
|
|
261
384
|
};
|
|
262
385
|
|
|
@@ -297,7 +420,15 @@ export const staleDealRule: GtmAuditRule = {
|
|
|
297
420
|
approvalRequired: true,
|
|
298
421
|
});
|
|
299
422
|
}
|
|
300
|
-
return {
|
|
423
|
+
return {
|
|
424
|
+
findings,
|
|
425
|
+
operations,
|
|
426
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
427
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
428
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
429
|
+
staleDealPolicyAssumption(policy.staleDealDays),
|
|
430
|
+
]),
|
|
431
|
+
};
|
|
301
432
|
},
|
|
302
433
|
};
|
|
303
434
|
|
|
@@ -327,7 +458,15 @@ export const missingDealAmountRule: GtmAuditRule = {
|
|
|
327
458
|
approvalRequired: true,
|
|
328
459
|
});
|
|
329
460
|
}
|
|
330
|
-
return {
|
|
461
|
+
return {
|
|
462
|
+
findings,
|
|
463
|
+
operations,
|
|
464
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
465
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
466
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
467
|
+
DEAL_AMOUNT_POLICY_ASSUMPTION,
|
|
468
|
+
]),
|
|
469
|
+
};
|
|
331
470
|
},
|
|
332
471
|
};
|
|
333
472
|
|
|
@@ -382,7 +521,11 @@ export const duplicateAccountDomainRule: GtmAuditRule = {
|
|
|
382
521
|
"IRREVERSIBLE: provider merges cannot be unmerged. The pre-apply snapshot retains every record's field values; recreate a record manually from it if a merge was wrong.",
|
|
383
522
|
});
|
|
384
523
|
}
|
|
385
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
findings,
|
|
526
|
+
operations,
|
|
527
|
+
assumptions: assumptionsIfFindings(findings, [DUPLICATE_ACCOUNT_DOMAIN_ASSUMPTION]),
|
|
528
|
+
};
|
|
386
529
|
},
|
|
387
530
|
};
|
|
388
531
|
|
|
@@ -422,7 +565,11 @@ export const duplicateContactEmailRule: GtmAuditRule = {
|
|
|
422
565
|
"IRREVERSIBLE: provider merges cannot be unmerged. The pre-apply snapshot retains every record's field values; recreate a record manually from it if a merge was wrong.",
|
|
423
566
|
});
|
|
424
567
|
}
|
|
425
|
-
return {
|
|
568
|
+
return {
|
|
569
|
+
findings,
|
|
570
|
+
operations,
|
|
571
|
+
assumptions: assumptionsIfFindings(findings, [DUPLICATE_CONTACT_EMAIL_ASSUMPTION]),
|
|
572
|
+
};
|
|
426
573
|
},
|
|
427
574
|
};
|
|
428
575
|
|
|
@@ -473,7 +620,15 @@ export const duplicateOpenDealRule: GtmAuditRule = {
|
|
|
473
620
|
"IRREVERSIBLE: provider merges cannot be unmerged. The pre-apply snapshot retains every record's field values; recreate a record manually from it if a merge was wrong.",
|
|
474
621
|
});
|
|
475
622
|
}
|
|
476
|
-
return {
|
|
623
|
+
return {
|
|
624
|
+
findings,
|
|
625
|
+
operations,
|
|
626
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
627
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
628
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
629
|
+
DUPLICATE_OPEN_DEAL_ASSUMPTION,
|
|
630
|
+
]),
|
|
631
|
+
};
|
|
477
632
|
},
|
|
478
633
|
};
|
|
479
634
|
|
|
@@ -514,7 +669,15 @@ export const activeDealAccountWithoutContactsRule: GtmAuditRule = {
|
|
|
514
669
|
approvalRequired: true,
|
|
515
670
|
});
|
|
516
671
|
}
|
|
517
|
-
return {
|
|
672
|
+
return {
|
|
673
|
+
findings,
|
|
674
|
+
operations,
|
|
675
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
676
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
677
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
678
|
+
ACCOUNT_WITH_OPEN_DEAL_NEEDS_CONTACT_ASSUMPTION,
|
|
679
|
+
]),
|
|
680
|
+
};
|
|
518
681
|
},
|
|
519
682
|
};
|
|
520
683
|
|
|
@@ -559,13 +722,22 @@ export const closingSoonInactiveRule: GtmAuditRule = {
|
|
|
559
722
|
approvalRequired: true,
|
|
560
723
|
});
|
|
561
724
|
}
|
|
562
|
-
return {
|
|
725
|
+
return {
|
|
726
|
+
findings,
|
|
727
|
+
operations,
|
|
728
|
+
assumptions: assumptionsIfFindings(findings, [
|
|
729
|
+
OPEN_DEAL_FLAGS_ASSUMPTION,
|
|
730
|
+
MISSING_CLOSED_WON_FLAGS_ASSUMPTION,
|
|
731
|
+
closingSoonPolicyAssumption(windowDays, idleDays),
|
|
732
|
+
]),
|
|
733
|
+
};
|
|
563
734
|
},
|
|
564
735
|
};
|
|
565
736
|
|
|
566
737
|
export const accountSingleSourceRule: GtmAuditRule = {
|
|
567
738
|
id: "account-single-source",
|
|
568
739
|
title: "Account exists in only one connected system",
|
|
740
|
+
emitsOperations: false,
|
|
569
741
|
description:
|
|
570
742
|
"On merged multi-system snapshots, flags accounts known to just one source — the seams where GTM systems disagree.",
|
|
571
743
|
category: "cross-system",
|
|
@@ -597,10 +769,18 @@ export const accountSingleSourceRule: GtmAuditRule = {
|
|
|
597
769
|
"Check whether the account is missing from the other systems or matched under a different domain/name.",
|
|
598
770
|
});
|
|
599
771
|
}
|
|
600
|
-
return {
|
|
772
|
+
return {
|
|
773
|
+
findings,
|
|
774
|
+
operations: [],
|
|
775
|
+
assumptions: assumptionsIfFindings(findings, [SINGLE_SOURCE_ACCOUNT_ASSUMPTION]),
|
|
776
|
+
};
|
|
601
777
|
},
|
|
602
778
|
};
|
|
603
779
|
|
|
780
|
+
export function isFindingsOnlyRule(rule: GtmAuditRule): boolean {
|
|
781
|
+
return rule.emitsOperations === false;
|
|
782
|
+
}
|
|
783
|
+
|
|
604
784
|
export const builtinAuditRules: GtmAuditRule[] = [
|
|
605
785
|
orphanAccountRule,
|
|
606
786
|
missingDealOwnerRule,
|
package/src/types.ts
CHANGED
|
@@ -105,6 +105,15 @@ export type CreateRecordPayload = {
|
|
|
105
105
|
|
|
106
106
|
export type AuditFindingSeverity = "info" | "warning" | "critical";
|
|
107
107
|
|
|
108
|
+
export type PatchPlanAssumptionConfidence = "derived" | "heuristic" | "guess";
|
|
109
|
+
|
|
110
|
+
export type PatchPlanAssumption = {
|
|
111
|
+
id: string;
|
|
112
|
+
text: string;
|
|
113
|
+
source?: string;
|
|
114
|
+
confidence?: PatchPlanAssumptionConfidence;
|
|
115
|
+
};
|
|
116
|
+
|
|
108
117
|
/**
|
|
109
118
|
* One claim that a canonical record exists in an external system. A record
|
|
110
119
|
* merged from several providers carries one identity per provider.
|
|
@@ -397,6 +406,10 @@ export type PatchPlan = {
|
|
|
397
406
|
* Account/contact hygiene findings are NOT here; read `findings` for those. */
|
|
398
407
|
pipelineFindings?: PipelineFinding[];
|
|
399
408
|
evidence?: GtmEvidence[];
|
|
409
|
+
/** Assumptions used to turn audit observations into findings and operations. */
|
|
410
|
+
assumptions?: PatchPlanAssumption[];
|
|
411
|
+
/** Human decisions or unresolved questions that affect how the plan should be applied. */
|
|
412
|
+
openQuestions?: string[];
|
|
400
413
|
operations: PatchOperation[];
|
|
401
414
|
/**
|
|
402
415
|
* The filter this plan's operations were selected by. Re-evaluated per
|
|
@@ -454,6 +467,8 @@ export type GtmRuleContext = {
|
|
|
454
467
|
export type GtmRuleResult = {
|
|
455
468
|
findings: AuditFinding[];
|
|
456
469
|
operations: PatchOperation[];
|
|
470
|
+
/** Rule-level assumptions that explain the policy or heuristic behind returned findings. */
|
|
471
|
+
assumptions?: PatchPlanAssumption[];
|
|
457
472
|
};
|
|
458
473
|
|
|
459
474
|
/**
|
|
@@ -466,6 +481,8 @@ export type GtmAuditRule = {
|
|
|
466
481
|
description: string;
|
|
467
482
|
/** Grouping for docs and discovery, e.g. "hygiene", "forecast", "coverage". */
|
|
468
483
|
category?: string;
|
|
484
|
+
/** False for findings-only rules whose evaluator intentionally emits no patch operations. */
|
|
485
|
+
emitsOperations?: boolean;
|
|
469
486
|
evaluate: (context: GtmRuleContext) => GtmRuleResult;
|
|
470
487
|
};
|
|
471
488
|
|