fullstackgtm 0.45.0 → 0.47.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/INSTALL_FOR_AGENTS.md +13 -11
  3. package/README.md +27 -18
  4. package/dist/backfill.d.ts +86 -0
  5. package/dist/backfill.js +251 -0
  6. package/dist/bin.js +4 -1
  7. package/dist/cli/auth.d.ts +2 -0
  8. package/dist/cli/auth.js +119 -12
  9. package/dist/cli/backfill.d.ts +1 -0
  10. package/dist/cli/backfill.js +125 -0
  11. package/dist/cli/backfillRuns.d.ts +1 -0
  12. package/dist/cli/backfillRuns.js +187 -0
  13. package/dist/cli/call.js +23 -9
  14. package/dist/cli/enrich.js +28 -10
  15. package/dist/cli/fix.js +9 -7
  16. package/dist/cli/help.js +60 -23
  17. package/dist/cli/plans.js +13 -11
  18. package/dist/cli/shared.d.ts +4 -3
  19. package/dist/cli/shared.js +31 -20
  20. package/dist/cli/ui.d.ts +21 -0
  21. package/dist/cli/ui.js +53 -1
  22. package/dist/cli.js +37 -41
  23. package/dist/connector.d.ts +8 -0
  24. package/dist/connector.js +104 -21
  25. package/dist/connectors/hubspot.d.ts +8 -1
  26. package/dist/connectors/hubspot.js +406 -13
  27. package/dist/connectors/hubspotAuth.js +5 -1
  28. package/dist/connectors/linkedin.js +14 -14
  29. package/dist/connectors/salesforce.d.ts +8 -1
  30. package/dist/connectors/salesforce.js +163 -2
  31. package/dist/connectors/stripe.d.ts +37 -0
  32. package/dist/connectors/stripe.js +103 -31
  33. package/dist/enrich.d.ts +7 -0
  34. package/dist/enrich.js +7 -0
  35. package/dist/health.d.ts +11 -69
  36. package/dist/health.js +4 -134
  37. package/dist/healthScore.d.ts +71 -0
  38. package/dist/healthScore.js +143 -0
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +3 -1
  41. package/dist/llm.d.ts +29 -0
  42. package/dist/llm.js +206 -0
  43. package/dist/market.d.ts +39 -1
  44. package/dist/market.js +116 -44
  45. package/dist/marketClassify.d.ts +9 -1
  46. package/dist/marketClassify.js +10 -1
  47. package/dist/marketTaxonomy.d.ts +6 -1
  48. package/dist/marketTaxonomy.js +4 -2
  49. package/dist/mcp-bin.js +29 -0
  50. package/dist/mcp.js +117 -4
  51. package/dist/progress.d.ts +96 -0
  52. package/dist/progress.js +142 -0
  53. package/dist/runReport.d.ts +24 -0
  54. package/dist/runReport.js +139 -4
  55. package/dist/types.d.ts +33 -1
  56. package/docs/api.md +4 -2
  57. package/docs/architecture.md +2 -0
  58. package/docs/linkedin-connector-spec.md +1 -1
  59. package/llms.txt +3 -3
  60. package/package.json +10 -3
  61. package/skills/fullstackgtm/SKILL.md +1 -0
  62. package/src/backfill.ts +340 -0
  63. package/src/bin.ts +5 -1
  64. package/src/cli/auth.ts +135 -15
  65. package/src/cli/backfill.ts +156 -0
  66. package/src/cli/backfillRuns.ts +198 -0
  67. package/src/cli/call.ts +26 -10
  68. package/src/cli/enrich.ts +33 -10
  69. package/src/cli/fix.ts +11 -10
  70. package/src/cli/help.ts +61 -23
  71. package/src/cli/plans.ts +15 -14
  72. package/src/cli/shared.ts +44 -27
  73. package/src/cli/ui.ts +72 -1
  74. package/src/cli.ts +38 -41
  75. package/src/connector.ts +110 -16
  76. package/src/connectors/hubspot.ts +423 -14
  77. package/src/connectors/hubspotAuth.ts +5 -1
  78. package/src/connectors/linkedin.ts +14 -14
  79. package/src/connectors/salesforce.ts +168 -3
  80. package/src/connectors/stripe.ts +154 -34
  81. package/src/enrich.ts +13 -0
  82. package/src/health.ts +14 -213
  83. package/src/healthScore.ts +223 -0
  84. package/src/index.ts +28 -1
  85. package/src/llm.ts +239 -0
  86. package/src/market.ts +157 -44
  87. package/src/marketClassify.ts +18 -1
  88. package/src/marketTaxonomy.ts +10 -2
  89. package/src/mcp-bin.ts +32 -0
  90. package/src/mcp.ts +140 -6
  91. package/src/progress.ts +197 -0
  92. package/src/runReport.ts +159 -4
  93. package/src/types.ts +35 -2
  94. package/docs/dx-punch-list.md +0 -87
  95. package/docs/roadmap-to-1.0.md +0 -211
package/src/connector.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { dedupeKey } from "./dedupe.ts";
2
+ import { APPLY_STAGES, type ProgressEmitter } from "./progress.ts";
2
3
  import { requiresHumanInput } from "./rules.ts";
3
4
  import type {
4
5
  CanonicalGtmSnapshot,
@@ -112,6 +113,13 @@ export type ApplyPatchPlanOptions = {
112
113
  * full operation count.
113
114
  */
114
115
  onOperation?: (progress: ApplyProgress) => void;
116
+ /**
117
+ * Shared progress vocabulary (src/progress.ts): the run emits the
118
+ * APPLY_STAGES (preflight → operations → results), one `opResult` per
119
+ * resolved operation (id + status only — no values), and `items` over the
120
+ * plan's operations. Additive alongside `onOperation`; presentation only.
121
+ */
122
+ progress?: ProgressEmitter;
115
123
  };
116
124
 
117
125
  export type ApplyProgress = {
@@ -130,6 +138,17 @@ function normalizeForComparison(value: unknown): string | null {
130
138
  return String(value);
131
139
  }
132
140
 
141
+ function isApplyBatchCandidate(operation: PatchOperation): boolean {
142
+ return (
143
+ ((operation.operation === "set_field" || operation.operation === "clear_field") && Boolean(operation.field)) ||
144
+ operation.operation === "archive_record"
145
+ );
146
+ }
147
+
148
+ function sameApplyBatchKind(a: PatchOperation, b: PatchOperation): boolean {
149
+ return a.operation === b.operation && a.objectType === b.objectType;
150
+ }
151
+
133
152
  /**
134
153
  * Apply an approved subset of a patch plan through a connector.
135
154
  *
@@ -148,6 +167,9 @@ export async function applyPatchPlan(
148
167
  }
149
168
 
150
169
  const startedAt = new Date().toISOString();
170
+ const emitStage = (stage: (typeof APPLY_STAGES)[number]) =>
171
+ options.progress?.stage(stage, APPLY_STAGES.indexOf(stage), APPLY_STAGES.length);
172
+ emitStage("preflight");
151
173
  const approved = new Set(options.approvedOperationIds);
152
174
  const checkConflicts =
153
175
  options.checkConflicts ?? typeof connector.readField === "function";
@@ -224,6 +246,7 @@ export async function applyPatchPlan(
224
246
  : "Operation was not approved.",
225
247
  });
226
248
  }
249
+ emitStage("results");
227
250
  return {
228
251
  planId: plan.id,
229
252
  provider: connector.provider,
@@ -255,6 +278,11 @@ export async function applyPatchPlan(
255
278
  if (checkConflicts && connector.readField) {
256
279
  for (const operation of plan.operations) {
257
280
  if (!approved.has(operation.id) || conflicts.has(operation.id)) continue;
281
+ // Generic batch connectors (Salesforce) do their own batched CAS read for
282
+ // eligible field writes, so don't pre-read those one-by-one here.
283
+ if (connector.applyBatch && isApplyBatchCandidate(operation) && !operation.groupId && !operation.preconditions?.length) {
284
+ continue;
285
+ }
258
286
  let conflict: PatchOperationResult | null = null;
259
287
  if (operation.field && FIELD_WRITE_OPERATIONS.has(operation.operation)) {
260
288
  const current = await connector.readField(
@@ -336,25 +364,35 @@ export async function applyPatchPlan(
336
364
  const resultsBefore = results.length;
337
365
  let lastNotified = results.length;
338
366
  const notifyProgress = () => {
339
- if (!options.onOperation || results.length === lastNotified) return;
340
- lastNotified = results.length;
341
- const last = results[results.length - 1];
342
- if (last.status === "applied") progressCounts.applied += 1;
343
- else if (last.status === "failed") progressCounts.failed += 1;
344
- else if (last.status === "conflict") progressCounts.conflicts += 1;
345
- else progressCounts.skipped += 1;
346
- try {
347
- options.onOperation({
348
- completed: results.length - resultsBefore,
349
- total: plan.operations.length,
350
- ...progressCounts,
351
- });
352
- } catch {
353
- // progress is presentation-only
367
+ if ((!options.onOperation && !options.progress) || results.length === lastNotified) return;
368
+ while (lastNotified < results.length) {
369
+ const last = results[lastNotified];
370
+ lastNotified += 1;
371
+ if (last.status === "applied") progressCounts.applied += 1;
372
+ else if (last.status === "failed") progressCounts.failed += 1;
373
+ else if (last.status === "conflict") progressCounts.conflicts += 1;
374
+ else progressCounts.skipped += 1;
375
+ // Shared vocabulary: operation id + status only — a conflict/failure
376
+ // detail can echo field values, which never leave the machine.
377
+ options.progress?.opResult(last.operationId, last.status);
378
+ options.progress?.items(results.length - resultsBefore, plan.operations.length);
379
+ if (options.onOperation) {
380
+ try {
381
+ options.onOperation({
382
+ completed: lastNotified - resultsBefore,
383
+ total: plan.operations.length,
384
+ ...progressCounts,
385
+ });
386
+ } catch {
387
+ // progress is presentation-only
388
+ }
389
+ }
354
390
  }
355
391
  };
356
392
 
357
- for (const operation of plan.operations) {
393
+ emitStage("operations");
394
+ for (let opIndex = 0; opIndex < plan.operations.length; opIndex++) {
395
+ const operation = plan.operations[opIndex];
358
396
  // Report the previous iteration's result (guarded: no-op if it pushed
359
397
  // nothing). One-iteration lag keeps this a two-line hook instead of a
360
398
  // notify after all ten push sites; the loop-exit call below flushes the last.
@@ -422,6 +460,61 @@ export async function applyPatchPlan(
422
460
  continue;
423
461
  }
424
462
 
463
+ if (connector.applyBatch && isApplyBatchCandidate(operation) && (checkConflicts || operation.operation === "archive_record") && !operation.groupId && !operation.preconditions?.length) {
464
+ const chunk: PatchOperation[] = [];
465
+ const applyBatchLimit = Math.max(1, connector.applyBatchLimit ?? 200);
466
+ for (let scan = opIndex; scan < plan.operations.length && chunk.length < applyBatchLimit; scan++) {
467
+ const candidate = plan.operations[scan];
468
+ if (!sameApplyBatchKind(operation, candidate) || !isApplyBatchCandidate(candidate)) break;
469
+ const candidateOverride = options.valueOverrides?.[candidate.id];
470
+ if (
471
+ !approved.has(candidate.id) ||
472
+ (requiresHumanInput(candidate.afterValue) && candidateOverride === undefined) ||
473
+ conflicts.has(candidate.id) ||
474
+ Boolean(candidate.groupId) ||
475
+ Boolean(candidate.preconditions?.length) ||
476
+ (staleByFilter && staleByFilter.has(candidate.objectId)) ||
477
+ irreversibleStale.has(candidate.id)
478
+ ) {
479
+ break;
480
+ }
481
+ chunk.push(candidateOverride === undefined ? candidate : { ...candidate, afterValue: candidateOverride });
482
+ }
483
+ if (chunk.length > 1) {
484
+ try {
485
+ const batchResults = await connector.applyBatch(chunk);
486
+ const byOperationId = new Map(batchResults.map((result) => [result.operationId, result]));
487
+ for (const batchOperation of chunk) {
488
+ const result = byOperationId.get(batchOperation.id) ?? {
489
+ operationId: batchOperation.id,
490
+ status: "failed" as const,
491
+ detail: "Batch connector did not return a result for this operation.",
492
+ };
493
+ results.push(result);
494
+ if (result.status === "applied" || result.status === "failed") attempted += 1;
495
+ if (result.status === "applied") applied += 1;
496
+ }
497
+ const appliedInBatch = batchResults.filter((result) => result.status === "applied").length;
498
+ appliedSinceRecheck += appliedInBatch;
499
+ if (appliedInBatch > 0 && (applied === appliedInBatch || appliedSinceRecheck >= recheckEvery)) {
500
+ appliedSinceRecheck = 0;
501
+ await refreshSnapshotChecks();
502
+ }
503
+ } catch (error) {
504
+ for (const batchOperation of chunk) {
505
+ results.push({
506
+ operationId: batchOperation.id,
507
+ status: "failed",
508
+ detail: error instanceof Error ? error.message : String(error),
509
+ });
510
+ attempted += 1;
511
+ }
512
+ }
513
+ opIndex += chunk.length - 1;
514
+ continue;
515
+ }
516
+ }
517
+
425
518
  const resolved: PatchOperation =
426
519
  override === undefined ? operation : { ...operation, afterValue: override };
427
520
 
@@ -449,6 +542,7 @@ export async function applyPatchPlan(
449
542
  }
450
543
  }
451
544
  notifyProgress();
545
+ emitStage("results");
452
546
 
453
547
  return {
454
548
  planId: plan.id,