@gscdump/analysis 0.30.0 → 0.31.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/dist/index.d.mts CHANGED
@@ -440,6 +440,39 @@ declare function formatAnalysisError(error: AnalysisError): string;
440
440
  * continue to match.
441
441
  */
442
442
  declare function analysisErrorToException(error: AnalysisError): Error;
443
+ declare const INTENT_CLASSIFIER_VERSION = 1;
444
+ type SearchIntent = 'transactional' | 'commercial' | 'informational' | 'unknown';
445
+ interface IntentClassification {
446
+ /** Primary intent, by priority transactional > commercial > informational. */
447
+ intent: SearchIntent;
448
+ /** Procedural sub-signal of informational ("how to …"). */
449
+ howTo: boolean;
450
+ /** Matched cue tokens/phrases, for explainability + debugging. */
451
+ signals: readonly string[];
452
+ }
453
+ declare const SEARCH_INTENT_CODE: Record<SearchIntent, number>;
454
+ /** Pack a classification into one small int for cheap dimension storage. */
455
+ declare function encodeIntent(c: Pick<IntentClassification, 'intent' | 'howTo'>): number;
456
+ /** Inverse of {@link encodeIntent}. */
457
+ declare function decodeIntent(code: number): {
458
+ intent: SearchIntent;
459
+ howTo: boolean;
460
+ };
461
+ /**
462
+ * Classify the search intent of a raw query from lexical cues. Deterministic;
463
+ * version it (`INTENT_CLASSIFIER_VERSION`) and bump on any rule change so a
464
+ * materialized `intent` can detect staleness, like the canonical normalizer.
465
+ */
466
+ declare function classifyQueryIntent(query: string): IntentClassification;
467
+ /**
468
+ * Algorithm version. Bump on ANY behaviour change (synonyms, depluralize,
469
+ * folding) so downstream stores can record which version produced a
470
+ * `query_canonical` and detect/repair staleness on a rule change instead of
471
+ * silently mixing old and new keys. v1 = the original ASCII heuristic;
472
+ * v2 adds Unicode folding, the empty-canonical guard, and `pluralize`-based
473
+ * singularization.
474
+ */
475
+ declare const NORMALIZER_VERSION = 2;
443
476
  /**
444
477
  * Produce a canonical form of a search query for grouping near-duplicates.
445
478
  * Idempotent: `normalizeQuery(normalizeQuery(q)) === normalizeQuery(q)`.
@@ -567,4 +600,4 @@ interface InMemoryQuerySourceOptions {
567
600
  }
568
601
  declare function createInMemoryQuerySource(options: InMemoryQuerySourceOptions): AnalysisQuerySource$1;
569
602
  declare const SQL_ANALYZERS: readonly Analyzer$1[];
570
- export { type ActionPriorityResult, type ActionPrioritySourceState, type ActionPrioritySourceStatus, type ActionSource, type AnalysisError, type AnalysisErrorKind, type AnalysisParams, type AnalysisPeriod, type AnalysisQuerySource, type AnalysisResult, type AnalysisSourceKind, type AnalysisTool, type Analyzer, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerRunner, type AnalyzerVariants, type BaseMetrics, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSummary, type BrowserAnalyzeOptions, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type ClusterType, type ClusteringOptions, type ClusteringResult, type ComparisonMode, type ComparisonPeriod, type CompositeSourceOptions, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, DEFAULT_PRIORITY_SOURCES, type DateRow, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type DefineAnalyzerOptions, type DefineReportOptions, type DefinedAnalyzer, type DefinedReport, type DryRunReportResult, ENGINE_QUERY_CAPABILITIES, type Effort, type EngineQuerySourceOptions, type ExecuteSqlOptions, type FileSet, type FormatReportOptions, IN_MEMORY_DEFAULT_CAPABILITIES, type InMemoryQuerySourceOptions, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, type OpportunityResult, type PadTimeseriesOptions, type PageRow, type Plan, type PriorityAction, type QueriesRow, type QueryPageRow, type QueryRow, REPORTS, ROW_ANALYZERS, type ReduceContext, type ReduceCtx, type Reducer, type ReportAction, type ReportContext, type ReportFinding, type ReportPlanStep, type ReportResult, type ReportSection, type RequiredCapability, type ResolveWindowOptions, type ResolvedWindow, type RowQueriesPlan, type RunReportOptions, SQL_ANALYZERS, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceResult, type TypedQuery, type TypedRowQuery, type WindowPreset, type ZeroClickResult, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
603
+ export { type ActionPriorityResult, type ActionPrioritySourceState, type ActionPrioritySourceStatus, type ActionSource, type AnalysisError, type AnalysisErrorKind, type AnalysisParams, type AnalysisPeriod, type AnalysisQuerySource, type AnalysisResult, type AnalysisSourceKind, type AnalysisTool, type Analyzer, AnalyzerCapabilityError, type AnalyzerRegistry, type AnalyzerRegistryInit, type AnalyzerRunner, type AnalyzerVariants, type BaseMetrics, type BrandSegmentationOptions, type BrandSegmentationResult, type BrandSummary, type BrowserAnalyzeOptions, type CannibalizationCompetitor, type CannibalizationEvent, type CannibalizationOptions, type CannibalizationPage, type CannibalizationResult, type CannibalizationSortMetric, type ClusterType, type ClusteringOptions, type ClusteringResult, type ComparisonMode, type ComparisonPeriod, type CompositeSourceOptions, type ConcentrationInput, type ConcentrationItem, type ConcentrationOptions, type ConcentrationResult, type ConcentrationRiskLevel, DEFAULT_PRIORITY_SOURCES, type DateRow, type DecayInput, type DecayOptions, type DecayResult, type DecaySeriesPoint, type DecaySortMetric, type DefineAnalyzerOptions, type DefineReportOptions, type DefinedAnalyzer, type DefinedReport, type DryRunReportResult, ENGINE_QUERY_CAPABILITIES, type Effort, type EngineQuerySourceOptions, type ExecuteSqlOptions, type FileSet, type FormatReportOptions, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, type InMemoryQuerySourceOptions, type IntentClassification, type KeywordCluster, type MonthlyData, type MoverData, type MoversInput, type MoversOptions, type MoversResult, type MoversSortMetric, NORMALIZER_VERSION, type OpportunityResult, type PadTimeseriesOptions, type PageRow, type Plan, type PriorityAction, type QueriesRow, type QueryPageRow, type QueryRow, REPORTS, ROW_ANALYZERS, type ReduceContext, type ReduceCtx, type Reducer, type ReportAction, type ReportContext, type ReportFinding, type ReportPlanStep, type ReportResult, type ReportSection, type RequiredCapability, type ResolveWindowOptions, type ResolvedWindow, type RowQueriesPlan, type RunReportOptions, SEARCH_INTENT_CODE, SQL_ANALYZERS, type SearchIntent, type SeasonalityMetric, type SeasonalityOptions, type SeasonalityResult, type SitemapDelta, type SitemapHealthDiff, type SitemapHealthInput, type SitemapHealthRow, type SitemapHealthTotals, type SortOrder, type SourceCapabilities, type SqlExtraQuery, type SqlPlan, type SqlPlanSpec, type StrikingDistanceInputRow, type StrikingDistanceResult, type TypedQuery, type TypedRowQuery, type WindowPreset, type ZeroClickResult, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, classifyQueryIntent, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, decodeIntent, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import { err, ok, unwrapResult } from "gscdump/result";
7
7
  import { between, date, extractDateRange, gsc, page, query } from "gscdump/query";
8
8
  import { MS_PER_DAY, daysAgo, toIsoDate } from "gscdump";
9
9
  import { buildExtrasQueries, buildTotalsSql, mergeExtras, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized } from "@gscdump/engine/resolver";
10
+ import pluralize from "pluralize";
10
11
  import { ENGINE_QUERY_CAPABILITIES, createAttachedTableSource, createEngineQuerySource, queryComparisonRows, queryRows, rewriteForTableSource, runAnalyzerWithEngine, typedQuery } from "@gscdump/engine/source";
11
12
  import { computeInputHash, createReportRegistry, defineReport } from "@gscdump/engine/report";
12
13
  import { canProxyToGsc } from "@gscdump/engine-gsc-api";
@@ -1370,7 +1371,7 @@ const INTENT_PREFIXES = [
1370
1371
  "free",
1371
1372
  "near me"
1372
1373
  ];
1373
- const WHITESPACE_RE$1 = /\s+/;
1374
+ const WHITESPACE_RE$2 = /\s+/;
1374
1375
  function str$18(v) {
1375
1376
  return v == null ? "" : String(v);
1376
1377
  }
@@ -1388,7 +1389,7 @@ function extractIntentPrefix(keyword) {
1388
1389
  return null;
1389
1390
  }
1390
1391
  function extractWordPrefix(keyword, wordCount = 2) {
1391
- const words = keyword.toLowerCase().split(WHITESPACE_RE$1).filter(Boolean);
1392
+ const words = keyword.toLowerCase().split(WHITESPACE_RE$2).filter(Boolean);
1392
1393
  if (words.length < wordCount + 1) return null;
1393
1394
  return words.slice(0, wordCount).join(" ");
1394
1395
  }
@@ -2600,6 +2601,154 @@ function shapeDataDetailRows(rows, params, extras) {
2600
2601
  meta
2601
2602
  };
2602
2603
  }
2604
+ const INTENT_CLASSIFIER_VERSION = 1;
2605
+ const SEARCH_INTENT_CODE = {
2606
+ unknown: 0,
2607
+ informational: 1,
2608
+ commercial: 2,
2609
+ transactional: 3
2610
+ };
2611
+ const INTENT_BY_CODE = [
2612
+ "unknown",
2613
+ "informational",
2614
+ "commercial",
2615
+ "transactional"
2616
+ ];
2617
+ const HOWTO_BIT = 4;
2618
+ function encodeIntent(c) {
2619
+ return SEARCH_INTENT_CODE[c.intent] | (c.howTo ? HOWTO_BIT : 0);
2620
+ }
2621
+ function decodeIntent(code) {
2622
+ return {
2623
+ intent: INTENT_BY_CODE[code & 3] ?? "unknown",
2624
+ howTo: (code & HOWTO_BIT) !== 0
2625
+ };
2626
+ }
2627
+ const INFORMATIONAL = /* @__PURE__ */ new Set([
2628
+ "how",
2629
+ "what",
2630
+ "why",
2631
+ "when",
2632
+ "where",
2633
+ "who",
2634
+ "which",
2635
+ "whose",
2636
+ "whom",
2637
+ "guide",
2638
+ "tutorial",
2639
+ "example",
2640
+ "examples",
2641
+ "meaning",
2642
+ "definition",
2643
+ "define",
2644
+ "explained",
2645
+ "explain",
2646
+ "tip",
2647
+ "idea",
2648
+ "learn",
2649
+ "basics",
2650
+ "intro",
2651
+ "introduction",
2652
+ "overview",
2653
+ "cheatsheet",
2654
+ "documentation",
2655
+ "docs",
2656
+ "faq"
2657
+ ]);
2658
+ const COMMERCIAL = /* @__PURE__ */ new Set([
2659
+ "best",
2660
+ "top",
2661
+ "vs",
2662
+ "versus",
2663
+ "review",
2664
+ "comparison",
2665
+ "compare",
2666
+ "alternative",
2667
+ "alternatives",
2668
+ "cheapest",
2669
+ "recommended",
2670
+ "popular"
2671
+ ]);
2672
+ const TRANSACTIONAL = /* @__PURE__ */ new Set([
2673
+ "buy",
2674
+ "price",
2675
+ "pricing",
2676
+ "cost",
2677
+ "cheap",
2678
+ "coupon",
2679
+ "deal",
2680
+ "discount",
2681
+ "order",
2682
+ "purchase",
2683
+ "download",
2684
+ "subscribe",
2685
+ "subscription",
2686
+ "signup",
2687
+ "trial",
2688
+ "demo",
2689
+ "install",
2690
+ "free",
2691
+ "sale",
2692
+ "quote",
2693
+ "checkout",
2694
+ "cart"
2695
+ ]);
2696
+ const PHRASE_CUES = [
2697
+ {
2698
+ re: /\bfor sale\b/,
2699
+ intent: "transactional"
2700
+ },
2701
+ {
2702
+ re: /\bsign up\b/,
2703
+ intent: "transactional"
2704
+ },
2705
+ {
2706
+ re: /\bnear me\b/,
2707
+ intent: "transactional"
2708
+ },
2709
+ {
2710
+ re: /\bhow much\b/,
2711
+ intent: "transactional"
2712
+ }
2713
+ ];
2714
+ const SEPARATOR_RE$1 = /[-_/.@#:+]+/g;
2715
+ const WHITESPACE_RE$1 = /\s+/g;
2716
+ const DIACRITICS_RE$1 = /\p{Diacritic}/gu;
2717
+ function tokenize(query) {
2718
+ const text = query.normalize("NFKD").replace(DIACRITICS_RE$1, "").toLowerCase().replace(SEPARATOR_RE$1, " ").replace(WHITESPACE_RE$1, " ").trim();
2719
+ return {
2720
+ text,
2721
+ tokens: text.split(" ").filter(Boolean)
2722
+ };
2723
+ }
2724
+ function classifyQueryIntent(query) {
2725
+ const { text, tokens } = tokenize(query);
2726
+ const signals = [];
2727
+ let transactional = false;
2728
+ let commercial = false;
2729
+ let informational = false;
2730
+ for (const t of tokens) if (TRANSACTIONAL.has(t)) {
2731
+ transactional = true;
2732
+ signals.push(t);
2733
+ } else if (COMMERCIAL.has(t)) {
2734
+ commercial = true;
2735
+ signals.push(t);
2736
+ } else if (INFORMATIONAL.has(t)) {
2737
+ informational = true;
2738
+ signals.push(t);
2739
+ }
2740
+ for (const { re, intent } of PHRASE_CUES) if (re.test(text)) {
2741
+ signals.push(re.source);
2742
+ if (intent === "transactional") transactional = true;
2743
+ }
2744
+ const howTo = /\bhow to\b/.test(text);
2745
+ if (howTo) informational = true;
2746
+ return {
2747
+ intent: transactional ? "transactional" : commercial ? "commercial" : informational ? "informational" : "unknown",
2748
+ howTo,
2749
+ signals
2750
+ };
2751
+ }
2603
2752
  const SYNONYMS = {
2604
2753
  checker: "validator",
2605
2754
  tester: "validator",
@@ -2736,18 +2885,53 @@ const NO_STRIP_S = /* @__PURE__ */ new Set([
2736
2885
  "conscious"
2737
2886
  ]);
2738
2887
  function depluralize(token) {
2739
- if (token.length <= 3) return token;
2740
- if (NO_STRIP_S.has(token)) return token;
2741
- if (token.endsWith("ies") && token.length > 4) return `${token.slice(0, -3)}y`;
2742
- if (token.endsWith("ses") && token.length > 4) return token.slice(0, -1);
2743
- if (token.endsWith("shes") || token.endsWith("ches") || token.endsWith("xes") || token.endsWith("zes")) return token.slice(0, -2);
2744
- if (token.endsWith("s") && !token.endsWith("ss")) return token.slice(0, -1);
2745
- return token;
2888
+ if (token.length <= 3 || NO_STRIP_S.has(token) || token.endsWith("sis")) return token;
2889
+ return pluralize.singular(token);
2746
2890
  }
2747
2891
  const SEPARATOR_RE = /[-_/.@#:+]+/g;
2748
2892
  const WHITESPACE_RE = /\s+/g;
2893
+ const DIACRITICS_RE = /\p{Diacritic}/gu;
2894
+ const NORMALIZER_VERSION = 2;
2895
+ function foldUnicode(s) {
2896
+ return s.normalize("NFKD").replace(DIACRITICS_RE, "");
2897
+ }
2898
+ const DIRECTIONAL_CONNECTORS = /* @__PURE__ */ new Set([
2899
+ "to",
2900
+ "into",
2901
+ ">",
2902
+ "→"
2903
+ ]);
2904
+ const NON_DIRECTIONAL_BEFORE = /* @__PURE__ */ new Set([
2905
+ "how",
2906
+ "what",
2907
+ "why",
2908
+ "when",
2909
+ "where",
2910
+ "who",
2911
+ "which",
2912
+ "guide",
2913
+ "way",
2914
+ "tip",
2915
+ "intro",
2916
+ "introduction",
2917
+ "learn",
2918
+ "tutorial",
2919
+ "step",
2920
+ "reason",
2921
+ "idea",
2922
+ "example",
2923
+ "benefit",
2924
+ "need"
2925
+ ]);
2926
+ function isOrderSensitive(tokens) {
2927
+ for (let i = 1; i < tokens.length - 1; i++) if (DIRECTIONAL_CONNECTORS.has(tokens[i]) && !NON_DIRECTIONAL_BEFORE.has(tokens[i - 1])) return true;
2928
+ return false;
2929
+ }
2749
2930
  function normalizeQuery(query) {
2750
- return query.toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim().split(" ").filter(Boolean).map((token) => SYNONYMS[token] ?? token).filter(Boolean).map(depluralize).sort().join(" ");
2931
+ const cleaned = foldUnicode(query).toLowerCase().replace(SEPARATOR_RE, " ").replace(WHITESPACE_RE, " ").trim().split(" ").filter(Boolean);
2932
+ const mapped = cleaned.map((token) => SYNONYMS[token] ?? token).filter(Boolean);
2933
+ const tokens = (mapped.length > 0 ? mapped : cleaned).map(depluralize);
2934
+ return (isOrderSensitive(tokens) ? tokens : tokens.sort()).join(" ");
2751
2935
  }
2752
2936
  const dataDetailAnalyzer = defineAnalyzer$1({
2753
2937
  id: "data-detail",
@@ -7108,4 +7292,4 @@ function createInMemoryQuerySource(options) {
7108
7292
  };
7109
7293
  }
7110
7294
  const SQL_ANALYZERS = ALL_ANALYZERS.flatMap((d) => d.sql ? [d.sql] : []);
7111
- export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, IN_MEMORY_DEFAULT_CAPABILITIES, REPORTS, ROW_ANALYZERS, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
7295
+ export { AnalyzerCapabilityError, DEFAULT_PRIORITY_SOURCES, ENGINE_QUERY_CAPABILITIES, INTENT_CLASSIFIER_VERSION, IN_MEMORY_DEFAULT_CAPABILITIES, NORMALIZER_VERSION, REPORTS, ROW_ANALYZERS, SEARCH_INTENT_CODE, SQL_ANALYZERS, analysisErrorToException, analysisErrors, analyzeBrandSegmentation, analyzeCannibalization, analyzeClustering, analyzeConcentration, analyzeDecay, analyzeInBrowser, analyzeKeywordConcentration, analyzeMovers, analyzePageConcentration, analyzeSeasonality, classifyQueryIntent, comparisonOf, createAnalyzerRegistry, createCompositeSource, createEngineQuerySource, createInMemoryQuerySource, createSorter, decodeIntent, defaultAnalyzerRegistry, defaultReportRegistry, defineAnalyzer, diffSitemapHealth, dryRunReport, encodeIntent, formatAnalysisError, formatReport, isAnalysisError, mergePriorityActions, normalizePriorityActions, normalizeQuery, num, padTimeseries, periodOf, queryComparisonRows, queryRows, resolveWindow, rewriteForTableSource, runAnalyzerFromSource, runAnalyzerWithEngine, runReport, runReportResult, scorePriorityActions, typedQuery, windowToComparisonPeriod, windowToPeriod };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/analysis",
3
3
  "type": "module",
4
- "version": "0.30.0",
4
+ "version": "0.31.0",
5
5
  "description": "GSC analyzers — striking-distance, opportunity, movers, decay, brand, clustering, concentration, seasonality. Pure row-based + DuckDB-native.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -75,9 +75,10 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "drizzle-orm": "1.0.0-rc.3",
78
- "@gscdump/engine": "0.30.0",
79
- "@gscdump/engine-gsc-api": "0.30.0",
80
- "gscdump": "0.30.0"
78
+ "pluralize": "^8.0.0",
79
+ "@gscdump/engine-gsc-api": "0.31.0",
80
+ "@gscdump/engine": "0.31.0",
81
+ "gscdump": "0.31.0"
81
82
  },
82
83
  "devDependencies": {
83
84
  "vitest": "^4.1.9"