@sonnechasser/ntrp 1.4.5 → 1.4.6

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.js CHANGED
@@ -10134,7 +10134,7 @@ function buildSlideContent(explainer, opts = {}) {
10134
10134
  lines.push(...visLines);
10135
10135
  lines.push("");
10136
10136
  }
10137
- lines.push(sectionHeading("What it means"));
10137
+ lines.push(sectionHeading("How we think about it"));
10138
10138
  pushWrapped(lines, explainer.meaning, inner, " ");
10139
10139
  lines.push("");
10140
10140
  if (!opts.skipFormula) {
@@ -10146,7 +10146,7 @@ function buildSlideContent(explainer, opts = {}) {
10146
10146
  lines.push("");
10147
10147
  }
10148
10148
  if (opts.deepdive) {
10149
- lines.push(sectionHeading("Deep dive"));
10149
+ lines.push(sectionHeading("How to read it"));
10150
10150
  pushWrapped(lines, explainer.expert_read, inner, " ");
10151
10151
  lines.push("");
10152
10152
  for (const bullet of explainer.deepdive) {
@@ -11415,15 +11415,15 @@ var init_metric_definitions = __esm({
11415
11415
  kind: "vital",
11416
11416
  label: "Freshness",
11417
11417
  group: "Vital Signs",
11418
- tagline: "Does the CRM report which records are still active?",
11418
+ tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
11419
11419
  how_computed: "NTRP computes a weighted average of people, organizations, and opportunities with recent activity. Open opportunities must also not be past-due. Default windows: people and organizations 90 days, opportunities 30 days. Weights are 35 / 30 / 35.",
11420
11420
  formula_lines: [
11421
11421
  "freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
11422
11422
  "people/orgs fresh if activity within 90d",
11423
11423
  "opps fresh if activity within 30d AND not past-due"
11424
11424
  ],
11425
- meaning: "Board question: how much of this pipeline is real versus fiction? Dollar value equals the sum of amount on stale opportunities: pipeline at risk.",
11426
- expert_read: "Cut by owner and by stage first. Freshness reds concentrate on people or process. They rarely spread evenly. In a long-cycle enterprise motion, 30 quiet days can be normal cadence. In a velocity motion, 30 quiet days is a dead deal. A sudden cliff usually means a broken integration or a departed rep. It is not gradual decay. Check this false positive: bulk-imported records that nobody has touched yet.",
11425
+ meaning: 'We treat Freshness as a truth check on the pipeline number. Quiet days mean different things by motion: in enterprise, 30 quiet days can be normal cadence; in a velocity book, that same silence often means the deal is already dead. Stale open amount is why we show "pipeline at risk."',
11426
+ expert_read: `A useful first cut is by owner and by stage \u2014 reds tend to clump on people or process, not sprinkle evenly. A sudden cliff is usually a broken integration or a departed rep, not gradual decay. Watch for the false positive too: bulk-imported records nobody has touched yet can look "stale" when they're just new.`,
11427
11427
  deepdive: [
11428
11428
  "Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
11429
11429
  'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
@@ -11453,15 +11453,15 @@ var init_metric_definitions = __esm({
11453
11453
  kind: "vital",
11454
11454
  label: "Flow Rate",
11455
11455
  group: "Vital Signs",
11456
- tagline: "How fast do deals move, and where do they stop?",
11456
+ tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
11457
11457
  how_computed: "NTRP sets a base score from average open-deal age versus max_days. It then applies a penalty of up to 20 for the share of stuck deals. A deal is stuck when it has no update beyond stuck_days, or a past-due close. Status uses average open age, not the score alone.",
11458
11458
  formula_lines: [
11459
11459
  "base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
11460
11460
  "score = base \u2212 stuckSharePenalty (\u226420)",
11461
11461
  "stuck = no update > stuck_days OR past-due close"
11462
11462
  ],
11463
- meaning: "Board question: is next quarter slipping because deals are stuck? Dollar value equals the amount stuck in pipeline.",
11464
- expert_read: "Cut by stage age, not only deal age. Find the stage where deals stop. That is usually one stage. Compare stuck-deal age to this company's own median cycle. Do not use a generic norm. Stuck deals plus past-due close dates signal optimistic forecasting. That is a credibility problem before it is a revenue problem.",
11463
+ meaning: 'We think of Flow Rate as velocity risk with a dollar tag. When deals stop advancing \u2014 and especially when close dates slip past due \u2014 next quarter starts to feel optimistic on paper. The dollar line is the amount still "stuck in pipeline."',
11464
+ expert_read: "We prefer stage age over deal age alone: find the stage where advancement collapses \u2014 it's usually one stage, not everywhere. Compare stuck age to this company's own median cycle rather than a generic norm. Stuck deals plus past-due closes are often a forecast-credibility problem before they're a revenue miss.",
11465
11465
  deepdive: [
11466
11466
  "Status uses average open age. Green is 45 days or less. Yellow is 90 days or less. Else red. Defaults: max_days 120, stuck_days 60.",
11467
11467
  'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
@@ -11493,15 +11493,15 @@ var init_metric_definitions = __esm({
11493
11493
  kind: "vital",
11494
11494
  label: "Drop Rate",
11495
11495
  group: "Vital Signs",
11496
- tagline: "Where do leads drop between systems?",
11496
+ tagline: "Where does paid demand fall between systems before anyone works it?",
11497
11497
  how_computed: "NTRP blends cross-system retention and opportunity retention. Cross-system retention is marketing people also present in sales. Opportunity retention is open opportunities that are not abandoned. Default weights: cross-system 60 percent, opportunity retention 40 percent. Abandoned means open opportunities with no activity in 30 days.",
11498
11498
  formula_lines: [
11499
11499
  "score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
11500
11500
  "cross-system = marketing people also in sales CRM",
11501
11501
  "abandoned = open opps with no activity in 30d"
11502
11502
  ],
11503
- meaning: "Board question: how much pipeline did we pay for and never work? Dollar value = droppedCount \xD7 conversionRate \xD7 avgDealSize \u2014 est. lost at handoff.",
11504
- expert_read: "This is almost always a systems failure. Causes include routing rules, unassigned territories, dead rep queues, or a sync gap between marketing and CRM. It is not lazy reps. First cut by lead source. The leak usually concentrates in one or two sources. The cheapest pipeline this business can buy is the leads it already paid for.",
11503
+ meaning: 'We read Drop Rate as the price of the handoff leak \u2014 budget already spent on leads that never reach a working rep. The dollar estimate is droppedCount \xD7 conversion \xD7 avg deal size: "est. lost at handoff."',
11504
+ expert_read: 'In practice this usually looks like a systems story: routing rules, unassigned territories, dead rep queues, or a sync gap between marketing and CRM \u2014 more often than "lazy reps." A useful first cut is by lead source; the leak tends to concentrate in one or two sources. The cheapest pipeline a business can buy is often the leads it already paid for.',
11505
11505
  deepdive: [
11506
11506
  "Status bands: green 80 or more, yellow 60 or more, red below 60.",
11507
11507
  'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
@@ -11532,15 +11532,15 @@ var init_metric_definitions = __esm({
11532
11532
  kind: "vital",
11533
11533
  label: "Signal:Noise",
11534
11534
  group: "Vital Signs",
11535
- tagline: "How much activity is aimed at deals that can still close?",
11535
+ tagline: "How much of the team's effort still aims at deals that can close?",
11536
11536
  how_computed: "Over a 90-day lookback, NTRP computes (signal activities / all activities) \xD7 100. Signal is activity linked to an open opportunity, a pipeline person, or a pipeline organization.",
11537
11537
  formula_lines: [
11538
11538
  "score = (signalCount / activityCount) \xD7 100",
11539
11539
  "signal = linked to open opp / pipeline person / pipeline org",
11540
11540
  "lookback = trailing 90 days"
11541
11541
  ],
11542
- meaning: "Board question: are we burning capacity on dead accounts? Dollar value = noiseCount \xD7 hours_per_activity \xD7 rep_hourly_cost \u2014 misdirected effort.",
11543
- expert_read: "Cut by rep and by account status. Noise usually means reps work dead accounts they already know. Targeting and account lists are stale. Persistent noise is a coverage-model problem, not a coaching problem. Check whether activity is logged against closed or unlinked records. That is often a hygiene artifact.",
11542
+ meaning: `We treat Signal:Noise as activity efficiency with a capacity price. Noise hours \xD7 hours_per_activity \xD7 rep_hourly_cost becomes "misdirected effort" \u2014 energy aimed at accounts that can't still close.`,
11543
+ expert_read: "A useful cut is by rep and by account status. Persistent noise often means reps keep working dead accounts they already know \u2014 stale targeting or coverage models more than a coaching gap. Also check whether activity is logged against closed or unlinked records; that can be a hygiene artifact pretending to be effort.",
11544
11544
  deepdive: [
11545
11545
  "Status bands: green 65 or more, yellow 40 or more, red below 40.",
11546
11546
  "Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
@@ -11569,15 +11569,15 @@ var init_metric_definitions = __esm({
11569
11569
  kind: "vital",
11570
11570
  label: "Thread Depth",
11571
11571
  group: "Vital Signs",
11572
- tagline: "How fragile is the pipeline if one champion stops?",
11572
+ tagline: "How fragile is revenue if one champion changes jobs?",
11573
11573
  how_computed: "Percent of open deals with at least multi_thread_threshold distinct people active in the last 90 days. The default threshold is 2. People include opportunity-direct contacts and same-organization activity.",
11574
11574
  formula_lines: [
11575
11575
  "score = % open deals with \u22652 active people (90d)",
11576
11576
  "people counted via opp contacts + same-org activity",
11577
11577
  "threshold configurable (default 2)"
11578
11578
  ],
11579
- meaning: "Board question: how much revenue is lost if one contact changes jobs? Dollar value equals the sum of amount on single-threaded deals.",
11580
- expert_read: "Weight by deal size. One single-threaded mega-deal outweighs ten small ones. Single-threading late in the cycle is far more dangerous than early. In enterprise motions, Thread Depth is a leading indicator of slipped quarters. Champions change jobs. There is no second contact.",
11579
+ meaning: "We read Thread Depth as resilience risk. One single-threaded mega-deal can outweigh ten small ones \u2014 and late-cycle single-threading is especially scary because champions change jobs. The dollar line sums amount on single-threaded deals.",
11580
+ expert_read: "Weight by deal size when you triage. Single-threading late in the cycle tends to matter more than early. In enterprise motions, thin threads are often a leading indicator of slipped quarters: the champion leaves and there's no second contact warm enough to carry the deal.",
11581
11581
  deepdive: [
11582
11582
  "Status bands: green 65 or more, yellow 40 or more, red below 40.",
11583
11583
  'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
@@ -11609,14 +11609,14 @@ var init_metric_definitions = __esm({
11609
11609
  kind: "saas",
11610
11610
  label: "ARR",
11611
11611
  group: "Revenue",
11612
- tagline: "How big is the revenue engine, and from where?",
11612
+ tagline: "How big is the engine \u2014 and what's the mix that got it there?",
11613
11613
  how_computed: "Sum of amount on closed-won opportunities in the dataset. This is pipeline-inferred ARR when a pure subscription ledger is not available.",
11614
11614
  formula_lines: [
11615
11615
  "ARR \u2248 \u03A3 amount on closed-won opportunities",
11616
11616
  "New + Expansion = growth \xB7 Churned + Contraction = leakage"
11617
11617
  ],
11618
- meaning: "Board question: how fast are we growing, and from where? Always decompose growth into new versus expansion. The mix is the story.",
11619
- expert_read: "Always decompose growth into new versus expansion. The mix is the story. Prefer this company's own trailing history over any external prior. A number below its reliability_gate is a hypothesis, not a fact.",
11618
+ meaning: "We treat ARR as the size of the revenue engine \u2014 but the interesting story is the mix. New versus expansion growth, and how much leakage (churn + contraction) ate into it. From CRM exports we often infer this from closed-won amounts when a pure subscription ledger isn't available.",
11619
+ expert_read: "Always decompose growth into new versus expansion before you brief anyone \u2014 the mix is the story. Prefer this company's own trailing history over an external prior. When a number sits below its reliability_gate, we treat it as a hypothesis, not a fact.",
11620
11620
  deepdive: [
11621
11621
  "Companion metrics: New ARR, Expansion ARR, Churned ARR, Contraction ARR.",
11622
11622
  "Estimation method may be ledger, pipeline_inferred, or snapshot. Read confidence and reliability_gate.",
@@ -11769,15 +11769,15 @@ var init_metric_definitions = __esm({
11769
11769
  kind: "saas",
11770
11770
  label: "Net Revenue Retention",
11771
11771
  group: "Retention",
11772
- tagline: "Would this business grow if sales stopped selling?",
11772
+ tagline: "Would the base still grow if new logo sales paused?",
11773
11773
  how_computed: "startingArr = ARR + Churned + Contraction \u2212 Expansion. NRR = ((starting \u2212 Churned \u2212 Contraction + Expansion) / starting) \xD7 100.",
11774
11774
  formula_lines: [
11775
11775
  "starting = ARR + churned + contraction \u2212 expansion",
11776
11776
  "NRR = (starting \u2212 churned \u2212 contraction + expansion) / starting \xD7 100",
11777
11777
  "NRR = 100% + expansion% \u2212 contraction% \u2212 churn%"
11778
11778
  ],
11779
- meaning: "Board question: would this business grow if sales stopped selling? A value above 100% means growth from existing customers.",
11780
- expert_read: "Decompose before you judge. The same 95% can be a churn problem (product/PMF) or a no-expansion problem (packaging/motion). Those have different owners. Priors by segment: about 97% SMB, about 108% mid-market, about 118% enterprise medians. 110% or more is a strong signal at any stage.",
11779
+ meaning: "We love NRR because it answers a sharp question: would this business still grow if sales stopped selling new logos? Above 100% means the installed base compounds. From CRM we reconstruct the walk (+expansion \u2212contraction \u2212churn) and stay humble when the data is pipeline-inferred.",
11780
+ expert_read: "Decompose before you judge \u2014 the same 95% can be a churn/PMF problem or a no-expansion/packaging problem, and those have different owners. Rough priors by segment sit around 97% SMB, 108% mid-market, 118% enterprise medians; 110%+ is a strong signal at any stage.",
11781
11781
  deepdive: [
11782
11782
  "Always show the waterfall: +expansion \u2212contraction \u2212churn.",
11783
11783
  "GRR is the floor. NRR adds expansion on top.",
@@ -11805,14 +11805,14 @@ var init_metric_definitions = __esm({
11805
11805
  kind: "saas",
11806
11806
  label: "Gross Revenue Retention",
11807
11807
  group: "Retention",
11808
- tagline: "How leaky is the bucket before expansion hides it?",
11808
+ tagline: "How leaky is the bucket before expansion can hide it?",
11809
11809
  how_computed: "GRR = ((startingArr \u2212 Churned \u2212 Contraction) / startingArr) \xD7 100. Expansion is excluded on purpose.",
11810
11810
  formula_lines: [
11811
11811
  "starting = ARR + churned + contraction \u2212 expansion",
11812
11812
  "GRR = (starting \u2212 churned \u2212 contraction) / starting \xD7 100"
11813
11813
  ],
11814
- meaning: "Board question: how leaky is the bucket before expansion hides it? Prior: above 90% is healthy. Above 95% is strong for enterprise.",
11815
- expert_read: "GRR is the honesty metric. Expansion can make NRR look fine while GRR erodes. Always read both.",
11814
+ meaning: "We treat GRR as the honesty metric \u2014 churn plus contraction only, with expansion deliberately left out. That way a shiny NRR can't paper over a leaky bucket. Rough priors: above 90% feels healthy; above 95% is strong for enterprise.",
11815
+ expert_read: "Always read GRR next to NRR. Expansion can make net retention look fine while the floor erodes. Owners tend to split: product/CS for logo churn, packaging for contraction.",
11816
11816
  deepdive: [
11817
11817
  "GRR never includes Expansion. That is the point.",
11818
11818
  "Owners: product and CS for churn. Packaging for contraction."
@@ -11835,14 +11835,14 @@ var init_metric_definitions = __esm({
11835
11835
  kind: "saas",
11836
11836
  label: "Pipeline Coverage",
11837
11837
  group: "Pipeline",
11838
- tagline: "Is next quarter already at risk?",
11838
+ tagline: "Does next quarter already have enough real pipeline?",
11839
11839
  how_computed: "Open pipeline amount divided by trailing-90-day closed-won amount.",
11840
11840
  formula_lines: [
11841
11841
  "Coverage = openPipeline / trailing_90d_won",
11842
11842
  "required \u2248 1 / win_rate (discount for time left)"
11843
11843
  ],
11844
- meaning: "Board question: is next quarter already at risk? Priors scale with cycle length: about 3x for velocity/SMB, 4 to 5x for enterprise.",
11845
- expert_read: "Coverage has no meaning without win rate. Required coverage is about 1 / win rate, discounted for time left in the period. Inflated stages and zombie deals fake coverage. Cross-check with Freshness before you trust it.",
11844
+ meaning: `We read coverage as an early "is next quarter already thin?" check. Priors scale with cycle length \u2014 roughly 3x for velocity/SMB, 4\u20135x for enterprise \u2014 but coverage without win rate is half a story. We also cross-check Freshness so zombies don't fake a healthy multiple.`,
11845
+ expert_read: "Required coverage is roughly 1 / win rate, discounted for time left in the period. Inflated stages and stale deals can manufacture coverage that isn't there. Pair with Win Rate and Freshness before you trust the brief.",
11846
11846
  deepdive: [
11847
11847
  "Always pair with Win Rate and Freshness.",
11848
11848
  "Weighted Pipeline is the credibility-adjusted companion."
@@ -11927,14 +11927,14 @@ var init_metric_definitions = __esm({
11927
11927
  kind: "saas",
11928
11928
  label: "Pipeline Velocity",
11929
11929
  group: "Pipeline",
11930
- tagline: "Revenue throughput per day. Four levers, one number.",
11930
+ tagline: "Four levers, one throughput number \u2014 which lever moved?",
11931
11931
  how_computed: "(openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays. Requires 3 or more dated closed-won deals. Unit: $/day.",
11932
11932
  formula_lines: [
11933
11933
  "Velocity = (openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays",
11934
11934
  "four levers: #opps \xB7 deal size \xB7 win rate \xB7 cycle days"
11935
11935
  ],
11936
- meaning: "Board question: which lever moved when throughput changed? This is the most decision-ready pipeline metric.",
11937
- expert_read: "When velocity changes, name which lever moved. A win-rate rise on falling opportunity volume is qualification tightening, not improvement.",
11936
+ meaning: "We like velocity because it's decision-ready: revenue throughput per day with four named levers (volume, size, win rate, cycle). When throughput changes, the interesting question is which lever moved \u2014 not just whether the number went up.",
11937
+ expert_read: 'Name the lever. A win-rate rise on falling opportunity volume often means qualification tightened, not that selling got "better." Needs a handful of dated wins; otherwise we leave it unavailable rather than invent a story.',
11938
11938
  deepdive: [
11939
11939
  "Needs 3 or more dated wins. Otherwise this metric is unavailable.",
11940
11940
  "Pairs with Flow Rate (cycle) and Win Rate (conversion)."
@@ -11956,11 +11956,11 @@ var init_metric_definitions = __esm({
11956
11956
  kind: "saas",
11957
11957
  label: "Win Rate",
11958
11958
  group: "Sales Efficiency",
11959
- tagline: "Of decided deals, how often do we win?",
11959
+ tagline: "Of the deals we decide, how often do we win?",
11960
11960
  how_computed: "closed-won / (won + lost) \xD7 100.",
11961
11961
  formula_lines: ["Win Rate = won / (won + lost) \xD7 100"],
11962
- meaning: "Board question: are we converting the pipeline we create? Priors: 25\u201335% SMB, 18\u201325% mid-market, 12\u201318% enterprise on qualified opportunities.",
11963
- expert_read: "A rising win rate on falling opportunity volume is qualification tightening, not improvement. Check the denominator.",
11962
+ meaning: "We read win rate as conversion of decided deals \u2014 are we winning the pipeline we create? Rough priors: ~25\u201335% SMB, ~18\u201325% mid-market, ~12\u201318% enterprise on qualified opportunities. It also sets how much coverage you need (~1 / win rate).",
11963
+ expert_read: "Watch the denominator. A rising win rate on falling opportunity volume often means qualification tightened, not that the team suddenly sells better. Cut by segment or source before company-wide coaching.",
11964
11964
  deepdive: [
11965
11965
  "Required coverage is about 1 / win rate.",
11966
11966
  "Cut by segment or source before company-wide coaching."
@@ -12300,10 +12300,10 @@ var init_guide_slides = __esm({
12300
12300
  TALK = {
12301
12301
  id: "talk",
12302
12302
  label: "Talk to NTRP",
12303
- tagline: "Type English. Confirm the scope. Load data. Then listen.",
12303
+ tagline: "Start with the question you care about \u2014 we'll mirror it back before we compute.",
12304
12304
  visual: {
12305
12305
  kind: "funnel",
12306
- caption: "Bare Enter submits the dim \u23CE hint at these gates. If nothing is armed, Enter does nothing.",
12306
+ caption: "When a dim \u23CE hint shows, bare Enter submits it. No hint means Enter does nothing.",
12307
12307
  funnel: [
12308
12308
  { label: "Type a question", widthPct: 100 },
12309
12309
  { label: "\u23CE yes (scope)", widthPct: 78 },
@@ -12312,25 +12312,23 @@ var init_guide_slides = __esm({
12312
12312
  ]
12313
12313
  },
12314
12314
  lines: [
12315
- 'You do not need a slash. Type the question you need. Examples: "is our retention real for the board?" or "pipeline health".',
12315
+ `We designed the conversation so you don't need a slash to start. Type the question you actually need \u2014 "is our retention real for the board?" or "pipeline health" \u2014 and NTRP restates it as a scope card so you can confirm we're answering the right thing.`,
12316
12316
  "",
12317
- "NTRP restates the question as a scope card. Confirm with \u23CE yes, or type yes. NTRP listens. NTRP does not invent a different question.",
12317
+ "Empty dataset? Load a sample with \u23CE use demo data, paste a CSV path, or /ingest. When the gap card says the formulas can compute, \u23CE go ahead runs the local math.",
12318
12318
  "",
12319
- "When the dataset is empty, type \u23CE use demo data. Or paste a CSV path. Or type /ingest. When the gap card shows that the formulas can compute, type \u23CE go ahead.",
12320
- "",
12321
- "Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it."
12319
+ "Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it \u2014 that's the point of the stethoscope."
12322
12320
  ],
12323
12321
  deepdive: [
12324
12322
  "Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
12325
- 'Type "use demo data" to load the hidden_crisis scenario. Company profile is optional. In scripts, type /demo --no-profile.',
12326
- 'After compute, the prompt becomes ask \u203A. Brief is the default depth. Type "go deep" when you want the long read.',
12327
- "Type /home to show phase status. Type /help to list conversation shortcuts. /help does not list every command."
12323
+ '"use demo data" loads a fitted sample book. Company profile is optional. In scripts, /demo --no-profile.',
12324
+ 'After compute, the prompt returns to \u203A. Brief is the default depth; type "go deep" for the long read.',
12325
+ "/home shows phase status. /help lists conversation shortcuts \u2014 not every command."
12328
12326
  ]
12329
12327
  };
12330
12328
  ASK = {
12331
12329
  id: "ask",
12332
12330
  label: "After the numbers",
12333
- tagline: 'Glossary is free. "Our ARR" is compute. Narrative needs /connect.',
12331
+ tagline: 'Two kinds of questions: glossary (free) versus "our" numbers (needs data \u2014 and often a key).',
12334
12332
  visual: {
12335
12333
  kind: "split",
12336
12334
  caption: "Possessives such as our, my, and the team's skip the glossary. They go to compute or scope.",
@@ -12340,25 +12338,23 @@ var init_guide_slides = __esm({
12340
12338
  ]
12341
12339
  },
12342
12340
  lines: [
12343
- '"what is ARR?" and "how is freshness calculated?" answer from the built-in glossary. No key. No data.',
12344
- "",
12345
- '"what is our ARR?" and "why is this red?" need a loaded dataset. AI findings and /ask need a stored key. Type /connect and paste any provider key. NTRP detects it.',
12341
+ `After compute, we think in two lanes. "what is ARR?" / "how is freshness calculated?" answer from the built-in glossary \u2014 no key, no data. That's so you can learn the lens without wiring anything up.`,
12346
12342
  "",
12347
- "Type /deepdive to replay this tour. Type /deepdive freshness for one slide. You can also type nrr, arr, or another id. Type /deepdive list for the catalog. Type /deepdive guide for this how-to section.",
12343
+ '"what is our ARR?" or "why is this red?" need a loaded dataset. Narrative findings and /ask need a stored key \u2014 /connect pastes any provider key and we detect it.',
12348
12344
  "",
12349
- "When the prompt shows a dim \u23CE hint, bare Enter submits that action. Examples: \u23CE yes, \u23CE use demo data, \u23CE go ahead, \u23CE /connect. If there is no hint, Enter does nothing."
12345
+ "Replay this tour with /deepdive, jump to one slide with /deepdive freshness (or nrr, arr, \u2026), or /deepdive guide for this how-to section. When the prompt shows a dim \u23CE hint (yes, use demo data, go ahead, /connect), bare Enter submits it."
12350
12346
  ],
12351
12347
  deepdive: [
12352
- "Type /connect ollama for a keyless local model. Type /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
12353
- "Type /model refresh to re-discover models. A retired model self-heals on the first 404.",
12354
- "First-run skip of this tour does not complete it. A home \u2691 chip can bring you back after the first analysis.",
12348
+ "/connect ollama for a keyless local model. /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
12349
+ "/model refresh re-discovers models. A retired model self-heals on the first 404.",
12350
+ "Skipping this tour on first-run does not complete it \u2014 a home \u2691 chip can bring you back after the first analysis.",
12355
12351
  "Tab completes /deepdive <metric>. Finding cards and playbook triggers also link here."
12356
12352
  ]
12357
12353
  };
12358
12354
  HANDOFF = {
12359
12355
  id: "handoff",
12360
12356
  label: "Ship work to your AI",
12361
- tagline: "Teach the inbox once. Later, tell your AI to pick it up.",
12357
+ tagline: "Teach the inbox once \u2014 then your desktop AI can pick up what we write.",
12362
12358
  visual: {
12363
12359
  kind: "layer_stack",
12364
12360
  caption: "You and your AI find and open the file. NTRP only writes.",
@@ -12370,28 +12366,26 @@ var init_guide_slides = __esm({
12370
12366
  ]
12371
12367
  },
12372
12368
  lines: [
12373
- 'Type "ship a board deck" or type /handoff. Files land in ~/Documents/ntrp-inbox by default. Type /inbox set to change the folder.',
12374
- "",
12375
- "During demo or company setup, or any time, type /inbox skill. Paste a standing finder into Claude, ChatGPT, Cursor, or another desktop AI once. That skill tells the tool to follow latest-handoff.md and INDEX.md.",
12369
+ 'We think of handoff as a quiet bridge: NTRP writes a board deck or brief into a folder you choose; your desktop AI (Claude, ChatGPT, Cursor, \u2026) opens it. You teach that AI a standing finder once \u2014 then later you just say "pick up the latest NTRP handoff."',
12376
12370
  "",
12377
- 'After that, each /handoff prints "Handoff ready". You will not get a paste block every write. Tell your AI: pick up the latest NTRP handoff.',
12371
+ '"ship a board deck" or /handoff lands files in ~/Documents/ntrp-inbox by default (/inbox set to change). During demo or company setup \u2014 or anytime \u2014 /inbox skill prints the finder to paste once. After that, each /handoff prints "Handoff ready" without a paste block every write.',
12378
12372
  "",
12379
- "If you skipped this step, NTRP asks once when you load your own data. Skip then, and type /inbox set ~/Documents/ntrp-inbox. Then type /inbox skill. Type /handoff skill to reprint the same finder."
12373
+ "Skipped earlier? We ask once when you load your own data. Or /inbox set ~/Documents/ntrp-inbox, then /inbox skill. /handoff skill reprints the same finder."
12380
12374
  ],
12381
12375
  deepdive: [
12382
12376
  "Inbox copies: latest-handoff.md, latest-pickup.md, SKILL.md, INDEX.md. Dated files also live under ~/.ntrp/exports/.",
12383
- "Type /exports to list writes. Type /inbox show to print the folder and the latest pointer. Type /handoff --print to show the prompt body in the terminal.",
12384
- "Audience-framed Metric definitions append to decks and reports. Your AI then has the same glossary you walked.",
12385
- "Type /end to close a session. NTRP writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
12377
+ "/exports lists writes. /inbox show prints the folder and latest pointer. /handoff --print shows the prompt body in the terminal.",
12378
+ "Audience-framed Metric definitions append to decks and reports \u2014 your AI gets the same glossary you walked.",
12379
+ "/end closes a session and writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
12386
12380
  ]
12387
12381
  };
12388
12382
  LOOP = {
12389
12383
  id: "loop",
12390
12384
  label: "Stay in the loop",
12391
- tagline: "Diagnose \u2192 plan \u2192 review \u2192 remember. NTRP learns your business.",
12385
+ tagline: "Listen \u2192 plan \u2192 review \u2192 remember. We learn your business over time.",
12392
12386
  visual: {
12393
12387
  kind: "layer_stack",
12394
- caption: "Stethoscope, not hospital. Observe and recommend. Never prescribe surgery.",
12388
+ caption: "Stethoscope, not hospital \u2014 we observe and recommend; you decide the surgery.",
12395
12389
  layers: [
12396
12390
  { label: "Listen (/diagnose, /metrics)" },
12397
12391
  { label: 'Plan ("how should we fix this?" / /strategy)', highlight: true },
@@ -12400,18 +12394,16 @@ var init_guide_slides = __esm({
12400
12394
  ]
12401
12395
  },
12402
12396
  lines: [
12403
- 'Type "how should we fix this?" or type /strategy. NTRP builds a measurable plan with milestones and dollar-anchored ranges. Type /strategy review to check those against later vital signs.',
12397
+ 'The learning loop is how we stay useful after the first diagnose. "how should we fix this?" or /strategy builds a measurable plan with milestones and dollar-anchored ranges; /strategy review checks those against later vital signs.',
12404
12398
  "",
12405
- "When a vital sign is red, type /playbook. NTRP names the matching play. Outcomes from reviews annotate the catalog with what hit here.",
12399
+ "When a vital is red, /playbook names a matching play \u2014 and outcomes from reviews annotate the catalog with what actually hit here. /remember stores a durable fact; /rate bad <reason> writes a calibration. Optional ~/.ntrp/ANALYST.md is standing voice and priorities (it never overrides safety rules).",
12406
12400
  "",
12407
- "Type /remember to store a durable fact. Type /rate bad <reason> to write a calibration. Optional ~/.ntrp/ANALYST.md is standing voice and priorities. It never overrides safety rules.",
12408
- "",
12409
- "Commands: /sessions show, /session <id> pickup, /end (transcript plus brief), /home, /progress, /help."
12401
+ "Handy later: /sessions show, /session <id> pickup, /end (transcript + brief), /home, /progress, /help."
12410
12402
  ],
12411
12403
  deepdive: [
12412
- 'Type "build me a game plan" before analysis. NTRP queues the strategist and resumes after compute.',
12404
+ '"build me a game plan" before analysis queues the strategist and resumes after compute.',
12413
12405
  "Interactive sessions distill 5 or fewer durable facts on close when an LLM key is stored. One-shot commands do not bank hours or distill.",
12414
- "Type /scratch to factory-reset data and config. progress.json and install.json survive unless you pass --include-progress.",
12406
+ "/scratch factory-resets data and config. progress.json and install.json survive unless you pass --include-progress.",
12415
12407
  "Credits in /progress accrue in interactive ntrp only."
12416
12408
  ]
12417
12409
  };
@@ -12428,6 +12420,11 @@ var init_guide_slides = __esm({
12428
12420
  var metric_tour_exports = {};
12429
12421
  __export(metric_tour_exports, {
12430
12422
  METRICS_TOUR_MILESTONE_ID: () => METRICS_TOUR_MILESTONE_ID,
12423
+ TOUR_CLOSE_LINES: () => TOUR_CLOSE_LINES,
12424
+ TOUR_CLOSE_TITLE: () => TOUR_CLOSE_TITLE,
12425
+ TOUR_INTRO_LINES: () => TOUR_INTRO_LINES,
12426
+ TOUR_INTRO_TITLE: () => TOUR_INTRO_TITLE,
12427
+ TOUR_OFFER_BLURB: () => TOUR_OFFER_BLURB,
12431
12428
  describeTourDeck: () => describeTourDeck,
12432
12429
  firstRunTourDefaultChoice: () => firstRunTourDefaultChoice,
12433
12430
  getDeepdiveNudge: () => getDeepdiveNudge,
@@ -12481,7 +12478,7 @@ function getDeepdiveNudge(ctx) {
12481
12478
  if (hasCompletedMetricsTour() || hasSeenDeepdiveHomeNudge()) return null;
12482
12479
  if (!hasAnalysisForDeepdiveNudge(ctx)) return null;
12483
12480
  return {
12484
- text: "Tour \u2014 what the numbers mean, and how to use NTRP",
12481
+ text: "Tour \u2014 how we think about the numbers, and how to use NTRP",
12485
12482
  command: "/deepdive"
12486
12483
  };
12487
12484
  }
@@ -12567,19 +12564,13 @@ function paintIntro(opts) {
12567
12564
  index: opts.index,
12568
12565
  total: opts.total,
12569
12566
  motion: opts.motion,
12570
- titleOverride: "How NTRP reads a pipeline",
12567
+ titleOverride: TOUR_INTRO_TITLE,
12571
12568
  visualOverride: GATING_LAYER_VISUAL,
12572
12569
  skipFormula: true,
12573
12570
  extraLines: [
12574
- "Two lenses, then how to drive the tool. Same stethoscope.",
12575
- "",
12576
- "SaaS metrics (next) \u2014 ARR, NRR, coverage, win rate, velocity \u2014 the board already knows these. Quick refresher on how NTRP computes them from CRM exports.",
12571
+ ...TOUR_INTRO_LINES,
12577
12572
  "",
12578
- "Vital signs (after) \u2014 Freshness, Flow Rate, Drop Rate, Signal:Noise, Thread Depth \u2014 NTRP's own ontology. Each has a dollar translation. Scores gate in layer order: first red wins.",
12579
- "",
12580
- "How to use NTRP (after vitals) \u2014 talk in English, ask without a key, ship a handoff to your AI once, stay in the diagnose \u2192 plan loop.",
12581
- "",
12582
- chalk10.dim("No AI key needed for this tour. Press Enter to advance; type /deepdive (or d) on any slide for more.")
12573
+ chalk10.dim("No AI key needed for this tour. Enter advances; /deepdive (or d) opens more on any slide.")
12583
12574
  ],
12584
12575
  footer: `${progressDots(opts.index, opts.total)} \u23CE next \xB7 q skip`
12585
12576
  });
@@ -12590,19 +12581,19 @@ function paintClose(opts) {
12590
12581
  index: opts.index,
12591
12582
  total: opts.total,
12592
12583
  motion: opts.motion,
12593
- titleOverride: "You are set. Listen first",
12584
+ titleOverride: TOUR_CLOSE_TITLE,
12594
12585
  skipFormula: true,
12595
12586
  extraLines: [
12596
- "Ask a question \u2192 confirm scope \u2192 load data \u2192 compute. Then:",
12597
- ` ${paint("accent", "/diagnose")} \u2014 five vital signs + dollars at risk`,
12587
+ TOUR_CLOSE_LINES[0],
12588
+ ` ${paint("accent", "/diagnose")} \u2014 five vital signs with dollars attached`,
12598
12589
  ` ${paint("accent", "/metrics")} \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)`,
12599
12590
  ` ${paint("accent", "/deepdive")} \u2014 replay this tour \xB7 ${paint("accent", "/deepdive guide")} how-to only`,
12600
12591
  ` ${paint("accent", "/handoff")} \u2014 ship a file; teach the inbox once (/inbox skill)`,
12601
12592
  ` ${paint("accent", "/strategy")} \u2014 measurable plan \xB7 ${paint("accent", "/help")} shortcuts`,
12602
12593
  "",
12603
- "Fourteen more SaaS metrics live behind /deepdive list \u2014 including unit economics that unlock when spend data lands.",
12594
+ TOUR_CLOSE_LINES[7],
12604
12595
  "",
12605
- "Philosophy: stethoscope, not hospital. Observe and recommend \u2014 never prescribe surgery."
12596
+ TOUR_CLOSE_LINES[9]
12606
12597
  ],
12607
12598
  footer: `${progressDots(opts.index, opts.total)} \u23CE done \xB7 q quit`
12608
12599
  });
@@ -12628,7 +12619,7 @@ function guideExtraLines(slide, deepdive) {
12628
12619
  ];
12629
12620
  if (deepdive && slide.deepdive.length > 0) {
12630
12621
  lines.push("");
12631
- lines.push(sectionHeading("Deep dive"));
12622
+ lines.push(sectionHeading("How to read it"));
12632
12623
  for (const bullet of slide.deepdive) {
12633
12624
  lines.push(`\xB7 ${bullet}`);
12634
12625
  }
@@ -12763,10 +12754,8 @@ function firstRunTourDefaultChoice() {
12763
12754
  async function offerFirstRunTour(ctx) {
12764
12755
  if (hasCompletedMetricsTour()) return false;
12765
12756
  console.log();
12766
- console.log(" " + bold("Onboarding tour") + chalk10.dim(" \u2014 ~3 minutes, no AI key required"));
12767
- console.log(
12768
- " " + chalk10.dim("SaaS numbers, five vitals, then how to talk to NTRP and ship work to your AI.")
12769
- );
12757
+ console.log(" " + bold("A short tour") + chalk10.dim(" \u2014 ~3 minutes, no AI key required"));
12758
+ console.log(" " + chalk10.dim(TOUR_OFFER_BLURB));
12770
12759
  const tourDefault = firstRunTourDefaultChoice();
12771
12760
  if (tourDefault === "skip") {
12772
12761
  console.log(" " + chalk10.dim("You have taken this tour before."));
@@ -12805,7 +12794,7 @@ async function offerFirstRunTour(ctx) {
12805
12794
  await runMetricTour(ctx, { live: false });
12806
12795
  return true;
12807
12796
  }
12808
- var TOUR_COMPLETED_KEY, TOUR_SKIPPED_KEY, HOME_NUDGE_SEEN_KEY, METRICS_TOUR_MILESTONE_ID;
12797
+ var TOUR_COMPLETED_KEY, TOUR_SKIPPED_KEY, HOME_NUDGE_SEEN_KEY, METRICS_TOUR_MILESTONE_ID, TOUR_INTRO_TITLE, TOUR_CLOSE_TITLE, TOUR_INTRO_LINES, TOUR_CLOSE_LINES, TOUR_OFFER_BLURB;
12809
12798
  var init_metric_tour = __esm({
12810
12799
  "src/conversation/metric-tour.ts"() {
12811
12800
  "use strict";
@@ -12823,6 +12812,30 @@ var init_metric_tour = __esm({
12823
12812
  TOUR_SKIPPED_KEY = "metrics-tour-skipped";
12824
12813
  HOME_NUDGE_SEEN_KEY = "metrics-tour-home-nudge-seen";
12825
12814
  METRICS_TOUR_MILESTONE_ID = "metrics_tour";
12815
+ TOUR_INTRO_TITLE = "How we listen to a pipeline";
12816
+ TOUR_CLOSE_TITLE = "Curious? Start listening";
12817
+ TOUR_INTRO_LINES = [
12818
+ "We think about GTM health through two lenses \u2014 then we'll show how to talk to NTRP.",
12819
+ "",
12820
+ "SaaS metrics (next) \u2014 ARR, NRR, coverage, win rate, velocity. Boards already know these names; we'll walk how we derive them from CRM exports, and what we trust (or don't) when the data is messy.",
12821
+ "",
12822
+ "Vital signs (after) \u2014 Freshness, Flow Rate, Drop Rate, Signal:Noise, Thread Depth. These are our stethoscope: each score has a dollar translation, and we read them in layer order so an upstream red doesn't get papered over by a prettier downstream number.",
12823
+ "",
12824
+ "How to use NTRP (after vitals) \u2014 ask in English, peek at the glossary without a key, teach your desktop AI an inbox once, then stay in diagnose \u2192 plan \u2192 review."
12825
+ ];
12826
+ TOUR_CLOSE_LINES = [
12827
+ "A good first loop: ask something you actually care about \u2192 confirm the scope \u2192 load data (demo is fine) \u2192 let the formulas run. Then poke around:",
12828
+ "/diagnose \u2014 five vital signs with dollars attached",
12829
+ "/metrics \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)",
12830
+ "/deepdive \u2014 replay this tour \xB7 /deepdive guide how-to only",
12831
+ "/handoff \u2014 ship a file; teach the inbox once (/inbox skill)",
12832
+ "/strategy \u2014 measurable plan \xB7 /help shortcuts",
12833
+ "",
12834
+ "Fourteen more SaaS metrics sit behind /deepdive list \u2014 including unit economics that unlock when spend data lands.",
12835
+ "",
12836
+ "We're a stethoscope, not a hospital: we listen hard, translate scores into dollars, and point at plays \u2014 you decide the surgery."
12837
+ ];
12838
+ TOUR_OFFER_BLURB = "How we think about SaaS numbers and five vitals \u2014 then how to talk to NTRP and ship work to your AI.";
12826
12839
  }
12827
12840
  });
12828
12841
 
@@ -15472,7 +15485,6 @@ __export(phase_exports, {
15472
15485
  buildConversationPrompt: () => buildConversationPrompt,
15473
15486
  consumeOrientEmptyEnterCoach: () => consumeOrientEmptyEnterCoach,
15474
15487
  formatPhaseLabel: () => formatPhaseLabel,
15475
- getConversationPhaseBlock: () => getConversationPhaseBlock,
15476
15488
  resolveConversationPhase: () => resolveConversationPhase,
15477
15489
  sessionHasData: () => sessionHasData
15478
15490
  });
@@ -15512,6 +15524,8 @@ function formatPhaseLabel(phase) {
15512
15524
  switch (phase) {
15513
15525
  case "orient":
15514
15526
  return "setup";
15527
+ case "awaiting_data":
15528
+ return "data loading";
15515
15529
  case "explore":
15516
15530
  return "ready to ask";
15517
15531
  case "think":
@@ -15520,15 +15534,24 @@ function formatPhaseLabel(phase) {
15520
15534
  return phase.replace(/_/g, " ");
15521
15535
  }
15522
15536
  }
15537
+ function accentPromptHead(phase, sessionName) {
15538
+ const scope = sessionName ? ` ${sessionName}` : "";
15539
+ if (phase === "orient" || phase === "awaiting_data" || phase === "explore") {
15540
+ if (phase === "explore" && sessionName) {
15541
+ return paint("accent", `\u203A`) + chalk11.dim(`${scope} `);
15542
+ }
15543
+ return paint("accent", `\u203A `);
15544
+ }
15545
+ if (phase === "compute") {
15546
+ return paint("accent", `\u2026 `);
15547
+ }
15548
+ const word = PROMPT_LABELS[phase].replace(" \u203A", "");
15549
+ return paint("accent", `${word}${scope} \u203A `);
15550
+ }
15523
15551
  function buildConversationPrompt(ctx) {
15524
15552
  const phase = resolveConversationPhase(ctx);
15525
- const label = PROMPT_LABELS[phase];
15526
- const scope = ctx.sessionName ? ` ${ctx.sessionName}` : "";
15553
+ const sessionName = ctx.sessionName ?? "";
15527
15554
  const action = resolveRecommendedAction(ctx);
15528
- if (phase === "orient") {
15529
- const enterHint2 = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
15530
- return paint("accent", `${label} `) + enterHint2;
15531
- }
15532
15555
  if (phase === "explore") {
15533
15556
  const mode = defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2));
15534
15557
  const modeTag = mode === "brief" ? "brief" : "deep";
@@ -15536,34 +15559,15 @@ function buildConversationPrompt(ctx) {
15536
15559
  const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 strategy after /connect") : "";
15537
15560
  const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 think after /connect") : "";
15538
15561
  const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
15539
- return paint("accent", `ask${scope} \u203A `) + chalk11.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
15562
+ return accentPromptHead(phase, sessionName) + chalk11.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
15540
15563
  }
15541
15564
  if (phase === "think") {
15542
15565
  const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
15543
15566
  const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
15544
- return paint("accent", `think${scope} \u203A `) + chalk11.dim(stack) + enterHint2 + " ";
15567
+ return accentPromptHead(phase, sessionName) + chalk11.dim(stack) + enterHint2 + " ";
15545
15568
  }
15546
15569
  const enterHint = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
15547
- return paint("accent", `${label.replace(" \u203A", "")}${scope} \u203A `) + enterHint;
15548
- }
15549
- function getConversationPhaseBlock(ctx) {
15550
- const phase = resolveConversationPhase(ctx);
15551
- const lines = [`Conversation phase: ${formatPhaseLabel(phase)}`];
15552
- if (ctx.scope) {
15553
- lines.push(`Intent: ${ctx.scope.intent_summary}`);
15554
- lines.push(`Primary lens: ${ctx.scope.primary_lens}`);
15555
- if (ctx.scope.audience) lines.push(`Audience: ${ctx.scope.audience}`);
15556
- if (ctx.scope.time_horizon) lines.push(`Time horizon: ${ctx.scope.time_horizon}`);
15557
- if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
15558
- }
15559
- if (ctx.dataset?.label) lines.push(`Dataset: ${ctx.dataset.label}`);
15560
- if (ctx.gapAudit) {
15561
- lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
15562
- if (ctx.gapAudit.missing.length > 0) {
15563
- lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
15564
- }
15565
- }
15566
- return lines.join("\n");
15570
+ return accentPromptHead(phase, sessionName) + enterHint;
15567
15571
  }
15568
15572
  var PROMPT_LABELS;
15569
15573
  var init_phase = __esm({
@@ -15579,9 +15583,9 @@ var init_phase = __esm({
15579
15583
  PROMPT_LABELS = {
15580
15584
  orient: "\u203A",
15581
15585
  scope: "scope \u203A",
15582
- awaiting_data: "data \u203A",
15586
+ awaiting_data: "\u203A",
15583
15587
  compute: "\u2026",
15584
- explore: "ask \u203A",
15588
+ explore: "\u203A",
15585
15589
  think: "think \u203A",
15586
15590
  strategize: "strategy \u203A",
15587
15591
  deliver: "ship \u203A"
@@ -21107,8 +21111,11 @@ function buildInvestigationTools() {
21107
21111
  if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
21108
21112
  return tools;
21109
21113
  }
21110
- function buildFreshNlTools() {
21111
- const tools = [...AGENTIC_TOOLS, ...CONVERSATION_TOOLS];
21114
+ function buildFreshNlTools(opts = {}) {
21115
+ const conversationTools = opts.analysisReady ? CONVERSATION_TOOLS.filter(
21116
+ (t) => t.name !== "propose_scope" && t.name !== "confirm_scope" && t.name !== "run_compute"
21117
+ ) : CONVERSATION_TOOLS;
21118
+ const tools = [...AGENTIC_TOOLS, ...conversationTools];
21112
21119
  if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
21113
21120
  return tools;
21114
21121
  }
@@ -22811,12 +22818,12 @@ Cite numbers from here; call tools when you need a new cut or to pressure-test a
22811
22818
 
22812
22819
  ` : "";
22813
22820
  const conversationSection = opts.conversationBlock ? `
22814
- CONVERSATION STATE:
22815
22821
  ${opts.conversationBlock}
22816
22822
 
22817
22823
  ` : "";
22818
22824
  const scratchSection = buildScratchBlock(opts.thinkState);
22819
22825
  const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
22826
+ - Pre-explore funnel gates are handled by the CLI; you speak in the think channel after analysis.
22820
22827
  - You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
22821
22828
  - Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
22822
22829
  - Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
@@ -22850,7 +22857,7 @@ ${buildCommandCatalogBlock()}
22850
22857
  COMMAND SUGGESTION RULES:
22851
22858
  - Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
22852
22859
  - Never claim a command was run. Never invent flags.
22853
- - Type \`done\` or \`cancel\` leaves the think channel back to ask \u203A.`;
22860
+ - Type \`done\` or \`cancel\` leaves the think channel back to \u203A.`;
22854
22861
  const stable = `You are a world-class GTM operating partner in a dedicated THINK WITH ME channel. The user wants collaborative exploration \u2014 imagination, pressure-testing, and evidence \u2014 not a slide deck and not a finished strategy plan.
22855
22862
 
22856
22863
  ${companyContextSection2()}${operatorSection2()}${socraticCraft}
@@ -22900,7 +22907,7 @@ ${SAFETY_BLOCK}`;
22900
22907
  const dynamic = [
22901
22908
  "SESSION STATE (current \u2014 changes as the session progresses):",
22902
22909
  ...dynamicSections,
22903
- buildRuntimeBlock()
22910
+ buildRuntimeBlock(opts.runtimeFacts ?? {})
22904
22911
  ].join("\n\n");
22905
22912
  return { stable, dynamic };
22906
22913
  }
@@ -22912,6 +22919,170 @@ var init_think_prompt = __esm({
22912
22919
  }
22913
22920
  });
22914
22921
 
22922
+ // src/conversation/ghost-hints.ts
22923
+ function ghostExamplesForPhase(phase, limit = 2) {
22924
+ const hints = PHASE_GHOST_HINTS[phase] ?? [];
22925
+ return hints.slice(0, limit).map((h) => h.replace(/^try\s+/i, ""));
22926
+ }
22927
+ var PHASE_GHOST_HINTS;
22928
+ var init_ghost_hints = __esm({
22929
+ "src/conversation/ghost-hints.ts"() {
22930
+ "use strict";
22931
+ PHASE_GHOST_HINTS = {
22932
+ orient: [
22933
+ 'try "pipeline health"',
22934
+ "try /deepdive",
22935
+ "try /deepdive guide",
22936
+ 'try "is our retention real for the board?"',
22937
+ 'try "what is the most expensive problem to solve?"',
22938
+ 'try "board deck on Q3"'
22939
+ ],
22940
+ awaiting_data: [
22941
+ 'try "use demo data"',
22942
+ "try a CSV path",
22943
+ "try /ingest",
22944
+ 'try "go ahead"'
22945
+ ],
22946
+ explore: [
22947
+ 'try "what is the most expensive problem?"',
22948
+ 'try "how should we fix this?"',
22949
+ 'try "which segment is weakest?"',
22950
+ 'try "what is ARR?"',
22951
+ "try /deepdive freshness",
22952
+ 'try "ship a board deck"'
22953
+ ]
22954
+ };
22955
+ }
22956
+ });
22957
+
22958
+ // src/conversation/situation.ts
22959
+ function buildSituationalAwarenessBlock(ctx, opts = {}) {
22960
+ const phase = resolveConversationPhase(ctx);
22961
+ const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
22962
+ const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
22963
+ const analysisReady = isAnalysisReady(ctx);
22964
+ const action = resolveRecommendedAction(ctx);
22965
+ const examples = ghostExamplesForPhase(
22966
+ phase === "think" || phase === "strategize" || phase === "deliver" ? "explore" : phase,
22967
+ 2
22968
+ );
22969
+ const lines = [
22970
+ "WHERE YOU ARE IN NTRP:",
22971
+ FUNNEL_SPINE,
22972
+ `Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
22973
+ ];
22974
+ if (channel === "think") {
22975
+ lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
22976
+ lines.push("Response mode: deep (tools on)");
22977
+ } else if (channel === "strategist") {
22978
+ lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
22979
+ } else {
22980
+ lines.push(
22981
+ responseMode === "brief" ? "Response mode: brief (tools off; cite COMPLETED SESSION ANALYSIS; ~40\u201390 words)" : "Response mode: deep (tools on; new cuts and drill-downs)"
22982
+ );
22983
+ }
22984
+ if (ctx.scope) {
22985
+ const bits = [`Scope: ${ctx.scope.intent_summary}`, `lens=${ctx.scope.primary_lens}`];
22986
+ if (ctx.scope.audience) bits.push(`audience=${ctx.scope.audience}`);
22987
+ if (ctx.scope.time_horizon) bits.push(`horizon=${ctx.scope.time_horizon}`);
22988
+ lines.push(bits.join(" \xB7 "));
22989
+ if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
22990
+ }
22991
+ if (ctx.dataset?.label) {
22992
+ lines.push(`Dataset: ${ctx.dataset.label} \xB7 stage=${ctx.stage}`);
22993
+ } else if (sessionHasData(ctx)) {
22994
+ lines.push(`Dataset: loaded \xB7 stage=${ctx.stage}`);
22995
+ } else {
22996
+ lines.push(`Dataset: empty \xB7 stage=${ctx.stage}`);
22997
+ }
22998
+ if (ctx.gapAudit) {
22999
+ lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
23000
+ if (ctx.gapAudit.missing.length > 0) {
23001
+ lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
23002
+ }
23003
+ }
23004
+ if (analysisReady) {
23005
+ lines.push(
23006
+ "Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
23007
+ );
23008
+ lines.push(
23009
+ "Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /deepdive <metric>, /thinkwithme, /playbook"
23010
+ );
23011
+ if (channel === "explore" && responseMode === "deep") {
23012
+ lines.push(
23013
+ "Tools: diagnostics + draft_strategy + draft_handoff (+ audit_data_gaps for re-check). Scope/compute orchestration is off."
23014
+ );
23015
+ }
23016
+ } else if (phase === "awaiting_data") {
23017
+ lines.push("Already done: scope confirmed; analysis not ready");
23018
+ lines.push(
23019
+ 'Available now: tell the user to paste a CSV path, type "use demo data", or \u23CE go ahead when can_compute \u2014 you cannot open files'
23020
+ );
23021
+ } else if (phase === "scope") {
23022
+ lines.push("Already done: intent proposed; waiting on confirm");
23023
+ lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
23024
+ } else if (phase === "orient") {
23025
+ lines.push("Already done: nothing locked yet");
23026
+ lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
23027
+ } else if (phase === "think") {
23028
+ lines.push("Already done: analysis complete; think channel open");
23029
+ lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
23030
+ }
23031
+ lines.push(
23032
+ `Armed user next step: ${action ? `${action.hint} (\u23CE submits "${action.submit}")` : "none"}`
23033
+ );
23034
+ lines.push(`Engine connected: ${canUseReplAi(ctx) ? "yes" : "no"}`);
23035
+ if (examples.length > 0) {
23036
+ lines.push(`User may try: ${examples.join(" \xB7 ")}`);
23037
+ }
23038
+ lines.push(
23039
+ "Your job in this harness: fill the judgment gap after formula compute \u2014 connect dollars, name the expensive problem, do not restate scorecards the user already saw"
23040
+ );
23041
+ lines.push(
23042
+ "Pre-explore funnel gates are handled by the CLI; you speak in explore/think/strategist unless tools explicitly allow a restart"
23043
+ );
23044
+ return lines.join("\n");
23045
+ }
23046
+ function buildSituationalRuntimeFacts(ctx, opts = {}) {
23047
+ const phase = resolveConversationPhase(ctx);
23048
+ const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
23049
+ const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
23050
+ return {
23051
+ phase,
23052
+ mode: responseMode,
23053
+ lens: ctx.scope?.primary_lens,
23054
+ stage: ctx.stage,
23055
+ can_compute: ctx.gapAudit?.can_compute === void 0 ? void 0 : String(ctx.gapAudit.can_compute),
23056
+ engine: canUseReplAi(ctx) ? "connected" : "none"
23057
+ };
23058
+ }
23059
+ function getConversationPhaseBlock(ctx, opts = {}) {
23060
+ return buildSituationalAwarenessBlock(ctx, opts);
23061
+ }
23062
+ var FUNNEL_SPINE, PHASE_MEANING;
23063
+ var init_situation = __esm({
23064
+ "src/conversation/situation.ts"() {
23065
+ "use strict";
23066
+ init_context2();
23067
+ init_explore_mode();
23068
+ init_repl_api();
23069
+ init_ghost_hints();
23070
+ init_recommended_action();
23071
+ init_phase();
23072
+ FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
23073
+ PHASE_MEANING = {
23074
+ orient: "setup \u2014 open chat; user has not locked a scope yet",
23075
+ scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
23076
+ awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
23077
+ compute: "compute in progress \u2014 wait",
23078
+ explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
23079
+ think: "socratic think channel \u2014 return to \u203A via done/cancel",
23080
+ strategize: "strategist flow \u2014 objective confirm or plan drafting",
23081
+ deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
23082
+ };
23083
+ }
23084
+ });
23085
+
22915
23086
  // src/ai/agentic-loop.ts
22916
23087
  function findingsJsonSchemaNudge() {
22917
23088
  return `Respond with ONLY a JSON array of finding objects \u2014 no prose, no markdown fences. Each object needs: severity, segment, finding, vital_signs, entity_count, recommended_focus, dollar_value, recommended_plays. Shape:
@@ -23017,12 +23188,11 @@ The user already received the full report in the terminal. Answer follow-ups by
23017
23188
 
23018
23189
  ` : "";
23019
23190
  const conversationSection = conversationBlock ? `
23020
- CONVERSATION STATE:
23021
23191
  ${conversationBlock}
23022
- Do not run compute until audit_data_gaps reports can_compute. If the user needs to load data, tell them to paste a CSV path or type "use demo data". You cannot open files.
23023
23192
 
23024
23193
  ` : "";
23025
23194
  const conversationRules = `HOW A GREAT ANALYST CARRIES A CONVERSATION (read carefully \u2014 this is what separates you from a search box):
23195
+ - Pre-explore funnel gates (orient \u2192 scope \u2192 data \u2192 compute) are handled by the CLI. You speak after compute unless tools explicitly allow a restart.
23026
23196
  - You have continuity. The conversation so far is provided to you as prior messages. Read it before answering. You remember every angle you have already explored, every number you have already surfaced, and every recommendation you have already made.
23027
23197
  - Never repeat or re-recommend an analysis you have already presented in this conversation unless the user explicitly asks you to revisit, recall, or connect it. Repetition reads as forgetfulness and destroys trust.
23028
23198
  - Each answer should ADVANCE the discussion: go deeper, introduce a genuinely new angle, challenge an assumption, or synthesize across what you have already found. Move the thinking forward.
@@ -23031,6 +23201,9 @@ Do not run compute until audit_data_gaps reports can_compute. If the user needs
23031
23201
  - If you genuinely have nothing new to add on a topic, say so briefly and offer a different, higher-value direction \u2014 never pad with a recycled recommendation.`;
23032
23202
  const briefJob = `YOUR JOB (BRIEF MODE \u2014 default after analysis is complete):
23033
23203
  - Stay concise \u2014 roughly 40\u201390 words total, not a mini-report. Verdict first: your opening sentence is the headline the user should be able to repeat an hour later.
23204
+ - Lead with the single most expensive or most actionable implication from the completed session analysis \u2014 not a scorecard restatement.
23205
+ - Do not re-list all five vital signs unless the user asked for that inventory.
23206
+ - Prefer naming one playbook play or one next cut over a generic "want me to dig deeper?" close.
23034
23207
  - You are at 30,000 ft by default. If they ask for detail or a new data cut, that routes to deep mode automatically \u2014 don't cram the weeds in here.
23035
23208
  - Do NOT call tools unless the user explicitly asks for a new data cut (by rep, stage, list of deals, etc.). The completed analysis above is your source of truth.
23036
23209
  - Name a playbook play only when the user asks what to do or which action to take.
@@ -23150,7 +23323,7 @@ ${briefCommandSection}` : "";
23150
23323
  const dynamic = [
23151
23324
  "SESSION STATE (current \u2014 changes as the session progresses):",
23152
23325
  ...dynamicSections,
23153
- buildRuntimeBlock()
23326
+ buildRuntimeBlock(promptOptions.runtimeFacts ?? {})
23154
23327
  ].join("\n\n");
23155
23328
  return { stable, dynamic };
23156
23329
  }
@@ -23182,15 +23355,27 @@ async function* agenticFindings(computeResult, divergences, options) {
23182
23355
  analysisBlock: options.analysisBlock,
23183
23356
  conversationBlock: options.conversationBlock,
23184
23357
  sessionArtifact: options.sessionArtifact,
23185
- thinkState: options.ctx.thinkState
23358
+ thinkState: options.ctx.thinkState,
23359
+ runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
23360
+ channel: "think",
23361
+ responseMode: "deep"
23362
+ })
23186
23363
  }) : mode === "fresh" ? buildFreshNlSystemPrompt(
23187
23364
  options.sessionContext,
23188
23365
  options.memoryBlock,
23189
23366
  options.analysisBlock,
23190
23367
  options.conversationBlock,
23191
- { responseMode, sessionArtifact: options.sessionArtifact, experiment }
23368
+ {
23369
+ responseMode,
23370
+ sessionArtifact: options.sessionArtifact,
23371
+ experiment,
23372
+ runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
23373
+ responseMode
23374
+ })
23375
+ }
23192
23376
  ) : buildSystemPrompt();
23193
- const tools = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools() : buildInvestigationTools() : [];
23377
+ const analysisReady = isAnalysisReady(options.ctx);
23378
+ const tools = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools({ analysisReady }) : buildInvestigationTools() : [];
23194
23379
  const toolCtx = { computeResult, divergences };
23195
23380
  if (options.includeMetrics) {
23196
23381
  try {
@@ -23359,6 +23544,8 @@ var init_agentic_loop = __esm({
23359
23544
  init_prompt_parts();
23360
23545
  init_think_prompt();
23361
23546
  init_prompt();
23547
+ init_context2();
23548
+ init_situation();
23362
23549
  MAX_ITERATIONS = 10;
23363
23550
  INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
23364
23551
  BRIEF_MAX_TOKENS = 768;
@@ -32396,10 +32583,10 @@ async function runNaturalLanguage(input, ctx) {
32396
32583
  setAgentContext(ctx);
32397
32584
  try {
32398
32585
  const analysisBlock = buildAnalysisBlock(ctx);
32399
- const conversationBlock = getConversationPhaseBlock(ctx);
32586
+ const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
32587
+ const conversationBlock = getConversationPhaseBlock(ctx, { responseMode });
32400
32588
  const bundle = await loadSessionAnalysisBundle();
32401
32589
  const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
32402
- const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
32403
32590
  for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
32404
32591
  mode: "fresh",
32405
32592
  userQuestion: input,
@@ -32497,6 +32684,7 @@ var init_nl = __esm({
32497
32684
  "use strict";
32498
32685
  init_spinner();
32499
32686
  init_context2();
32687
+ init_situation();
32500
32688
  init_phase();
32501
32689
  init_agent_context();
32502
32690
  init_orchestrator();
@@ -32562,7 +32750,7 @@ async function* streamAsk(question, ctx) {
32562
32750
  sessionContext: ctx.resumedSessionSummary,
32563
32751
  includeMetrics: true,
32564
32752
  analysisBlock: buildAnalysisBlock(ctx),
32565
- conversationBlock: getConversationPhaseBlock(ctx),
32753
+ conversationBlock: getConversationPhaseBlock(ctx, { responseMode }),
32566
32754
  sessionArtifact,
32567
32755
  responseMode,
32568
32756
  priorMessages: ctx.conversation,
@@ -32607,7 +32795,7 @@ var init_ask = __esm({
32607
32795
  init_divergence();
32608
32796
  init_repl_api();
32609
32797
  init_context2();
32610
- init_phase();
32798
+ init_situation();
32611
32799
  init_session_analysis();
32612
32800
  init_smoke_protocol();
32613
32801
  }
@@ -33800,7 +33988,7 @@ async function runThinkTurn(input, ctx) {
33800
33988
  setAgentContext(ctx);
33801
33989
  try {
33802
33990
  const analysisBlock = buildAnalysisBlock(ctx);
33803
- const conversationBlock = getConversationPhaseBlock(ctx);
33991
+ const conversationBlock = getConversationPhaseBlock(ctx, { channel: "think", responseMode: "deep" });
33804
33992
  const bundle = await loadSessionAnalysisBundle();
33805
33993
  const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
33806
33994
  for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
@@ -33887,7 +34075,7 @@ var init_think = __esm({
33887
34075
  "use strict";
33888
34076
  init_spinner();
33889
34077
  init_context2();
33890
- init_phase();
34078
+ init_situation();
33891
34079
  init_agent_context();
33892
34080
  init_agentic_loop();
33893
34081
  init_thread();
@@ -33951,7 +34139,7 @@ function printChannelIntro(seed) {
33951
34139
  console.log(
33952
34140
  " " + chalk67.dim(
33953
34141
  "Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
33954
- ) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to ask \u203A.")
34142
+ ) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to \u203A.")
33955
34143
  );
33956
34144
  if (seed) {
33957
34145
  console.log(" " + chalk67.dim("Seed: ") + seed);
@@ -34037,7 +34225,7 @@ function clearThinkFlow(ctx, reason) {
34037
34225
  console.log();
34038
34226
  console.log(
34039
34227
  " " + chalk67.dim(
34040
- reason === "done" ? "Think channel closed. Back to ask \u203A." : "Think session cancelled. Continue exploration."
34228
+ reason === "done" ? "Think channel closed. Back to \u203A." : "Think session cancelled. Continue exploration."
34041
34229
  )
34042
34230
  );
34043
34231
  console.log();
@@ -36457,7 +36645,7 @@ handler: ../commands/thinkwithme.ts
36457
36645
 
36458
36646
  Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
36459
36647
  Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
36460
- Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to ask \u203A.
36648
+ Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
36461
36649
  When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
36462
36650
  },
36463
36651
  {
@@ -39468,7 +39656,7 @@ function printHelp() {
39468
39656
  console.log(" " + chalk88.dim("After analysis, type questions in English."));
39469
39657
  console.log(" " + chalk88.dim("Type ") + paint("accent", '"how should we fix this?"') + chalk88.dim(" to make a strategy."));
39470
39658
  console.log(" " + chalk88.dim("Type ") + paint("accent", '"ship a board deck"') + chalk88.dim(" to write a handoff."));
39471
- console.log(" " + chalk88.dim("The ") + paint("accent", "ask \u203A") + chalk88.dim(" prompt shows brief or deep. Brief is the default after analysis."));
39659
+ console.log(" " + chalk88.dim("The ") + paint("accent", "\u203A") + chalk88.dim(" prompt shows brief or deep after analysis. Brief is the default."));
39472
39660
  console.log();
39473
39661
  console.log(" " + sectionHeading("Shortcuts"));
39474
39662
  const shortcuts = [
@@ -39527,6 +39715,7 @@ var init_repl = __esm({
39527
39715
  init_prompt_parts();
39528
39716
  init_store();
39529
39717
  init_phase();
39718
+ init_ghost_hints();
39530
39719
  init_recommended_action();
39531
39720
  init_inline_suggestion();
39532
39721
  init_loop_guard2();
@@ -39587,23 +39776,7 @@ var init_repl = __esm({
39587
39776
  "May your pipeline stay hydrated.",
39588
39777
  "Don't let the zombie deals bite."
39589
39778
  ];
39590
- GHOST_HINTS = {
39591
- orient: [
39592
- 'try "pipeline health"',
39593
- "try /deepdive",
39594
- "try /deepdive guide",
39595
- 'try "is our retention real for the board?"',
39596
- 'try "what is the most expensive problem to solve?"',
39597
- 'try "board deck on Q3"'
39598
- ],
39599
- explore: [
39600
- 'try "what is ARR?"',
39601
- "try /deepdive freshness",
39602
- 'try "how should we fix this?"',
39603
- 'try "which segment is weakest?"',
39604
- 'try "ship a board deck"'
39605
- ]
39606
- };
39779
+ GHOST_HINTS = PHASE_GHOST_HINTS;
39607
39780
  ghostHintTurn = 0;
39608
39781
  activeGhostHint = null;
39609
39782
  suggestionPainted = false;