@sonnechasser/ntrp 1.4.5 → 1.4.7
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 +413 -177
- package/dist/mcp/server.js +299 -98
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -232,6 +232,30 @@ function severityLabel(severity) {
|
|
|
232
232
|
return severity.toUpperCase();
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
+
function pickGoverningFinding(findings) {
|
|
236
|
+
if (findings.length === 0) return null;
|
|
237
|
+
return findings.find((f) => f.severity === "critical") ?? findings.find((f) => f.severity === "warning") ?? findings[0] ?? null;
|
|
238
|
+
}
|
|
239
|
+
function findingHeadline(findingText) {
|
|
240
|
+
const trimmed = findingText.trim();
|
|
241
|
+
if (!trimmed) return "";
|
|
242
|
+
const match = trimmed.match(/^(.+?[.!?])(?:\s|$)/);
|
|
243
|
+
return (match?.[1] ?? trimmed.split(/\n/)[0] ?? trimmed).trim();
|
|
244
|
+
}
|
|
245
|
+
function formatTheCallSection(findings) {
|
|
246
|
+
const pick3 = pickGoverningFinding(findings);
|
|
247
|
+
if (!pick3) return "";
|
|
248
|
+
const headline = findingHeadline(pick3.finding);
|
|
249
|
+
if (!headline) return "";
|
|
250
|
+
const alreadyHasDollar = /\$[\d.,]+/.test(headline);
|
|
251
|
+
const dollar = !alreadyHasDollar && pick3.dollar_value != null && pick3.dollar_value > 0 ? `**${formatDollarValue(pick3.dollar_value)}** \u2014 ` : "";
|
|
252
|
+
return [
|
|
253
|
+
"## The Call",
|
|
254
|
+
"",
|
|
255
|
+
`${dollar}${headline}`,
|
|
256
|
+
""
|
|
257
|
+
].join("\n");
|
|
258
|
+
}
|
|
235
259
|
var VITAL_SIGN_LABELS, DOLLAR_LABELS;
|
|
236
260
|
var init_formatters = __esm({
|
|
237
261
|
"src/output/formatters.ts"() {
|
|
@@ -10134,7 +10158,7 @@ function buildSlideContent(explainer, opts = {}) {
|
|
|
10134
10158
|
lines.push(...visLines);
|
|
10135
10159
|
lines.push("");
|
|
10136
10160
|
}
|
|
10137
|
-
lines.push(sectionHeading("
|
|
10161
|
+
lines.push(sectionHeading("How we think about it"));
|
|
10138
10162
|
pushWrapped(lines, explainer.meaning, inner, " ");
|
|
10139
10163
|
lines.push("");
|
|
10140
10164
|
if (!opts.skipFormula) {
|
|
@@ -10146,7 +10170,7 @@ function buildSlideContent(explainer, opts = {}) {
|
|
|
10146
10170
|
lines.push("");
|
|
10147
10171
|
}
|
|
10148
10172
|
if (opts.deepdive) {
|
|
10149
|
-
lines.push(sectionHeading("
|
|
10173
|
+
lines.push(sectionHeading("How to read it"));
|
|
10150
10174
|
pushWrapped(lines, explainer.expert_read, inner, " ");
|
|
10151
10175
|
lines.push("");
|
|
10152
10176
|
for (const bullet of explainer.deepdive) {
|
|
@@ -11415,15 +11439,15 @@ var init_metric_definitions = __esm({
|
|
|
11415
11439
|
kind: "vital",
|
|
11416
11440
|
label: "Freshness",
|
|
11417
11441
|
group: "Vital Signs",
|
|
11418
|
-
tagline: "
|
|
11442
|
+
tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
|
|
11419
11443
|
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
11444
|
formula_lines: [
|
|
11421
11445
|
"freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
|
|
11422
11446
|
"people/orgs fresh if activity within 90d",
|
|
11423
11447
|
"opps fresh if activity within 30d AND not past-due"
|
|
11424
11448
|
],
|
|
11425
|
-
meaning:
|
|
11426
|
-
expert_read:
|
|
11449
|
+
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."',
|
|
11450
|
+
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
11451
|
deepdive: [
|
|
11428
11452
|
"Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
|
|
11429
11453
|
'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
|
|
@@ -11453,15 +11477,15 @@ var init_metric_definitions = __esm({
|
|
|
11453
11477
|
kind: "vital",
|
|
11454
11478
|
label: "Flow Rate",
|
|
11455
11479
|
group: "Vital Signs",
|
|
11456
|
-
tagline: "
|
|
11480
|
+
tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
|
|
11457
11481
|
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
11482
|
formula_lines: [
|
|
11459
11483
|
"base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
|
|
11460
11484
|
"score = base \u2212 stuckSharePenalty (\u226420)",
|
|
11461
11485
|
"stuck = no update > stuck_days OR past-due close"
|
|
11462
11486
|
],
|
|
11463
|
-
meaning:
|
|
11464
|
-
expert_read: "
|
|
11487
|
+
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."',
|
|
11488
|
+
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
11489
|
deepdive: [
|
|
11466
11490
|
"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
11491
|
'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
|
|
@@ -11493,15 +11517,15 @@ var init_metric_definitions = __esm({
|
|
|
11493
11517
|
kind: "vital",
|
|
11494
11518
|
label: "Drop Rate",
|
|
11495
11519
|
group: "Vital Signs",
|
|
11496
|
-
tagline: "Where
|
|
11520
|
+
tagline: "Where does paid demand fall between systems before anyone works it?",
|
|
11497
11521
|
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
11522
|
formula_lines: [
|
|
11499
11523
|
"score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
|
|
11500
11524
|
"cross-system = marketing people also in sales CRM",
|
|
11501
11525
|
"abandoned = open opps with no activity in 30d"
|
|
11502
11526
|
],
|
|
11503
|
-
meaning:
|
|
11504
|
-
expert_read:
|
|
11527
|
+
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."',
|
|
11528
|
+
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
11529
|
deepdive: [
|
|
11506
11530
|
"Status bands: green 80 or more, yellow 60 or more, red below 60.",
|
|
11507
11531
|
'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
|
|
@@ -11532,15 +11556,15 @@ var init_metric_definitions = __esm({
|
|
|
11532
11556
|
kind: "vital",
|
|
11533
11557
|
label: "Signal:Noise",
|
|
11534
11558
|
group: "Vital Signs",
|
|
11535
|
-
tagline: "How much
|
|
11559
|
+
tagline: "How much of the team's effort still aims at deals that can close?",
|
|
11536
11560
|
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
11561
|
formula_lines: [
|
|
11538
11562
|
"score = (signalCount / activityCount) \xD7 100",
|
|
11539
11563
|
"signal = linked to open opp / pipeline person / pipeline org",
|
|
11540
11564
|
"lookback = trailing 90 days"
|
|
11541
11565
|
],
|
|
11542
|
-
meaning:
|
|
11543
|
-
expert_read: "
|
|
11566
|
+
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.`,
|
|
11567
|
+
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
11568
|
deepdive: [
|
|
11545
11569
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
11546
11570
|
"Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
|
|
@@ -11569,15 +11593,15 @@ var init_metric_definitions = __esm({
|
|
|
11569
11593
|
kind: "vital",
|
|
11570
11594
|
label: "Thread Depth",
|
|
11571
11595
|
group: "Vital Signs",
|
|
11572
|
-
tagline: "How fragile is
|
|
11596
|
+
tagline: "How fragile is revenue if one champion changes jobs?",
|
|
11573
11597
|
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
11598
|
formula_lines: [
|
|
11575
11599
|
"score = % open deals with \u22652 active people (90d)",
|
|
11576
11600
|
"people counted via opp contacts + same-org activity",
|
|
11577
11601
|
"threshold configurable (default 2)"
|
|
11578
11602
|
],
|
|
11579
|
-
meaning: "
|
|
11580
|
-
expert_read: "Weight by deal size
|
|
11603
|
+
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.",
|
|
11604
|
+
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
11605
|
deepdive: [
|
|
11582
11606
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
11583
11607
|
'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
|
|
@@ -11609,14 +11633,14 @@ var init_metric_definitions = __esm({
|
|
|
11609
11633
|
kind: "saas",
|
|
11610
11634
|
label: "ARR",
|
|
11611
11635
|
group: "Revenue",
|
|
11612
|
-
tagline: "How big is the
|
|
11636
|
+
tagline: "How big is the engine \u2014 and what's the mix that got it there?",
|
|
11613
11637
|
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
11638
|
formula_lines: [
|
|
11615
11639
|
"ARR \u2248 \u03A3 amount on closed-won opportunities",
|
|
11616
11640
|
"New + Expansion = growth \xB7 Churned + Contraction = leakage"
|
|
11617
11641
|
],
|
|
11618
|
-
meaning: "
|
|
11619
|
-
expert_read: "Always decompose growth into new versus expansion
|
|
11642
|
+
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.",
|
|
11643
|
+
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
11644
|
deepdive: [
|
|
11621
11645
|
"Companion metrics: New ARR, Expansion ARR, Churned ARR, Contraction ARR.",
|
|
11622
11646
|
"Estimation method may be ledger, pipeline_inferred, or snapshot. Read confidence and reliability_gate.",
|
|
@@ -11769,15 +11793,15 @@ var init_metric_definitions = __esm({
|
|
|
11769
11793
|
kind: "saas",
|
|
11770
11794
|
label: "Net Revenue Retention",
|
|
11771
11795
|
group: "Retention",
|
|
11772
|
-
tagline: "Would
|
|
11796
|
+
tagline: "Would the base still grow if new logo sales paused?",
|
|
11773
11797
|
how_computed: "startingArr = ARR + Churned + Contraction \u2212 Expansion. NRR = ((starting \u2212 Churned \u2212 Contraction + Expansion) / starting) \xD7 100.",
|
|
11774
11798
|
formula_lines: [
|
|
11775
11799
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
11776
11800
|
"NRR = (starting \u2212 churned \u2212 contraction + expansion) / starting \xD7 100",
|
|
11777
11801
|
"NRR = 100% + expansion% \u2212 contraction% \u2212 churn%"
|
|
11778
11802
|
],
|
|
11779
|
-
meaning: "
|
|
11780
|
-
expert_read: "Decompose before you judge
|
|
11803
|
+
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.",
|
|
11804
|
+
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
11805
|
deepdive: [
|
|
11782
11806
|
"Always show the waterfall: +expansion \u2212contraction \u2212churn.",
|
|
11783
11807
|
"GRR is the floor. NRR adds expansion on top.",
|
|
@@ -11805,14 +11829,14 @@ var init_metric_definitions = __esm({
|
|
|
11805
11829
|
kind: "saas",
|
|
11806
11830
|
label: "Gross Revenue Retention",
|
|
11807
11831
|
group: "Retention",
|
|
11808
|
-
tagline: "How leaky is the bucket before expansion
|
|
11832
|
+
tagline: "How leaky is the bucket before expansion can hide it?",
|
|
11809
11833
|
how_computed: "GRR = ((startingArr \u2212 Churned \u2212 Contraction) / startingArr) \xD7 100. Expansion is excluded on purpose.",
|
|
11810
11834
|
formula_lines: [
|
|
11811
11835
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
11812
11836
|
"GRR = (starting \u2212 churned \u2212 contraction) / starting \xD7 100"
|
|
11813
11837
|
],
|
|
11814
|
-
meaning: "
|
|
11815
|
-
expert_read: "GRR
|
|
11838
|
+
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.",
|
|
11839
|
+
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
11840
|
deepdive: [
|
|
11817
11841
|
"GRR never includes Expansion. That is the point.",
|
|
11818
11842
|
"Owners: product and CS for churn. Packaging for contraction."
|
|
@@ -11835,14 +11859,14 @@ var init_metric_definitions = __esm({
|
|
|
11835
11859
|
kind: "saas",
|
|
11836
11860
|
label: "Pipeline Coverage",
|
|
11837
11861
|
group: "Pipeline",
|
|
11838
|
-
tagline: "
|
|
11862
|
+
tagline: "Does next quarter already have enough real pipeline?",
|
|
11839
11863
|
how_computed: "Open pipeline amount divided by trailing-90-day closed-won amount.",
|
|
11840
11864
|
formula_lines: [
|
|
11841
11865
|
"Coverage = openPipeline / trailing_90d_won",
|
|
11842
11866
|
"required \u2248 1 / win_rate (discount for time left)"
|
|
11843
11867
|
],
|
|
11844
|
-
meaning:
|
|
11845
|
-
expert_read: "
|
|
11868
|
+
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.`,
|
|
11869
|
+
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
11870
|
deepdive: [
|
|
11847
11871
|
"Always pair with Win Rate and Freshness.",
|
|
11848
11872
|
"Weighted Pipeline is the credibility-adjusted companion."
|
|
@@ -11927,14 +11951,14 @@ var init_metric_definitions = __esm({
|
|
|
11927
11951
|
kind: "saas",
|
|
11928
11952
|
label: "Pipeline Velocity",
|
|
11929
11953
|
group: "Pipeline",
|
|
11930
|
-
tagline: "
|
|
11954
|
+
tagline: "Four levers, one throughput number \u2014 which lever moved?",
|
|
11931
11955
|
how_computed: "(openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays. Requires 3 or more dated closed-won deals. Unit: $/day.",
|
|
11932
11956
|
formula_lines: [
|
|
11933
11957
|
"Velocity = (openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays",
|
|
11934
11958
|
"four levers: #opps \xB7 deal size \xB7 win rate \xB7 cycle days"
|
|
11935
11959
|
],
|
|
11936
|
-
meaning: "
|
|
11937
|
-
expert_read:
|
|
11960
|
+
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.",
|
|
11961
|
+
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
11962
|
deepdive: [
|
|
11939
11963
|
"Needs 3 or more dated wins. Otherwise this metric is unavailable.",
|
|
11940
11964
|
"Pairs with Flow Rate (cycle) and Win Rate (conversion)."
|
|
@@ -11956,11 +11980,11 @@ var init_metric_definitions = __esm({
|
|
|
11956
11980
|
kind: "saas",
|
|
11957
11981
|
label: "Win Rate",
|
|
11958
11982
|
group: "Sales Efficiency",
|
|
11959
|
-
tagline: "Of
|
|
11983
|
+
tagline: "Of the deals we decide, how often do we win?",
|
|
11960
11984
|
how_computed: "closed-won / (won + lost) \xD7 100.",
|
|
11961
11985
|
formula_lines: ["Win Rate = won / (won + lost) \xD7 100"],
|
|
11962
|
-
meaning: "
|
|
11963
|
-
expert_read: "A rising win rate on falling opportunity volume
|
|
11986
|
+
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).",
|
|
11987
|
+
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
11988
|
deepdive: [
|
|
11965
11989
|
"Required coverage is about 1 / win rate.",
|
|
11966
11990
|
"Cut by segment or source before company-wide coaching."
|
|
@@ -12300,10 +12324,10 @@ var init_guide_slides = __esm({
|
|
|
12300
12324
|
TALK = {
|
|
12301
12325
|
id: "talk",
|
|
12302
12326
|
label: "Talk to NTRP",
|
|
12303
|
-
tagline: "
|
|
12327
|
+
tagline: "Start with the question you care about \u2014 we'll mirror it back before we compute.",
|
|
12304
12328
|
visual: {
|
|
12305
12329
|
kind: "funnel",
|
|
12306
|
-
caption: "
|
|
12330
|
+
caption: "When a dim \u23CE hint shows, bare Enter submits it. No hint means Enter does nothing.",
|
|
12307
12331
|
funnel: [
|
|
12308
12332
|
{ label: "Type a question", widthPct: 100 },
|
|
12309
12333
|
{ label: "\u23CE yes (scope)", widthPct: 78 },
|
|
@@ -12312,25 +12336,23 @@ var init_guide_slides = __esm({
|
|
|
12312
12336
|
]
|
|
12313
12337
|
},
|
|
12314
12338
|
lines: [
|
|
12315
|
-
|
|
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.",
|
|
12339
|
+
`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.`,
|
|
12318
12340
|
"",
|
|
12319
|
-
"
|
|
12341
|
+
"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.",
|
|
12320
12342
|
"",
|
|
12321
|
-
"Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it."
|
|
12343
|
+
"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
12344
|
],
|
|
12323
12345
|
deepdive: [
|
|
12324
12346
|
"Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
|
|
12325
|
-
'
|
|
12326
|
-
'After compute, the prompt
|
|
12327
|
-
"
|
|
12347
|
+
'"use demo data" loads a fitted sample book. Company profile is optional. In scripts, /demo --no-profile.',
|
|
12348
|
+
'After compute, the prompt returns to \u203A. Brief is the default depth; type "go deep" for the long read.',
|
|
12349
|
+
"/home shows phase status. /help lists conversation shortcuts \u2014 not every command."
|
|
12328
12350
|
]
|
|
12329
12351
|
};
|
|
12330
12352
|
ASK = {
|
|
12331
12353
|
id: "ask",
|
|
12332
12354
|
label: "After the numbers",
|
|
12333
|
-
tagline: '
|
|
12355
|
+
tagline: 'Two kinds of questions: glossary (free) versus "our" numbers (needs data \u2014 and often a key).',
|
|
12334
12356
|
visual: {
|
|
12335
12357
|
kind: "split",
|
|
12336
12358
|
caption: "Possessives such as our, my, and the team's skip the glossary. They go to compute or scope.",
|
|
@@ -12340,25 +12362,23 @@ var init_guide_slides = __esm({
|
|
|
12340
12362
|
]
|
|
12341
12363
|
},
|
|
12342
12364
|
lines: [
|
|
12343
|
-
|
|
12365
|
+
`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.`,
|
|
12344
12366
|
"",
|
|
12345
|
-
'"what is our ARR?"
|
|
12367
|
+
'"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.',
|
|
12346
12368
|
"",
|
|
12347
|
-
"
|
|
12348
|
-
"",
|
|
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."
|
|
12369
|
+
"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
12370
|
],
|
|
12351
12371
|
deepdive: [
|
|
12352
|
-
"
|
|
12353
|
-
"
|
|
12354
|
-
"
|
|
12372
|
+
"/connect ollama for a keyless local model. /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
|
|
12373
|
+
"/model refresh re-discovers models. A retired model self-heals on the first 404.",
|
|
12374
|
+
"Skipping this tour on first-run does not complete it \u2014 a home \u2691 chip can bring you back after the first analysis.",
|
|
12355
12375
|
"Tab completes /deepdive <metric>. Finding cards and playbook triggers also link here."
|
|
12356
12376
|
]
|
|
12357
12377
|
};
|
|
12358
12378
|
HANDOFF = {
|
|
12359
12379
|
id: "handoff",
|
|
12360
12380
|
label: "Ship work to your AI",
|
|
12361
|
-
tagline: "Teach the inbox once
|
|
12381
|
+
tagline: "Teach the inbox once \u2014 then your desktop AI can pick up what we write.",
|
|
12362
12382
|
visual: {
|
|
12363
12383
|
kind: "layer_stack",
|
|
12364
12384
|
caption: "You and your AI find and open the file. NTRP only writes.",
|
|
@@ -12370,28 +12390,26 @@ var init_guide_slides = __esm({
|
|
|
12370
12390
|
]
|
|
12371
12391
|
},
|
|
12372
12392
|
lines: [
|
|
12373
|
-
'
|
|
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.",
|
|
12393
|
+
'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
12394
|
"",
|
|
12377
|
-
'After that, each /handoff prints "Handoff ready"
|
|
12395
|
+
'"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
12396
|
"",
|
|
12379
|
-
"
|
|
12397
|
+
"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
12398
|
],
|
|
12381
12399
|
deepdive: [
|
|
12382
12400
|
"Inbox copies: latest-handoff.md, latest-pickup.md, SKILL.md, INDEX.md. Dated files also live under ~/.ntrp/exports/.",
|
|
12383
|
-
"
|
|
12384
|
-
"Audience-framed Metric definitions append to decks and reports
|
|
12385
|
-
"
|
|
12401
|
+
"/exports lists writes. /inbox show prints the folder and latest pointer. /handoff --print shows the prompt body in the terminal.",
|
|
12402
|
+
"Audience-framed Metric definitions append to decks and reports \u2014 your AI gets the same glossary you walked.",
|
|
12403
|
+
"/end closes a session and writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
|
|
12386
12404
|
]
|
|
12387
12405
|
};
|
|
12388
12406
|
LOOP = {
|
|
12389
12407
|
id: "loop",
|
|
12390
12408
|
label: "Stay in the loop",
|
|
12391
|
-
tagline: "
|
|
12409
|
+
tagline: "Listen \u2192 plan \u2192 review \u2192 remember. We learn your business over time.",
|
|
12392
12410
|
visual: {
|
|
12393
12411
|
kind: "layer_stack",
|
|
12394
|
-
caption: "Stethoscope, not hospital
|
|
12412
|
+
caption: "Stethoscope, not hospital \u2014 we observe and recommend; you decide the surgery.",
|
|
12395
12413
|
layers: [
|
|
12396
12414
|
{ label: "Listen (/diagnose, /metrics)" },
|
|
12397
12415
|
{ label: 'Plan ("how should we fix this?" / /strategy)', highlight: true },
|
|
@@ -12400,18 +12418,16 @@ var init_guide_slides = __esm({
|
|
|
12400
12418
|
]
|
|
12401
12419
|
},
|
|
12402
12420
|
lines: [
|
|
12403
|
-
'
|
|
12421
|
+
'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
12422
|
"",
|
|
12405
|
-
"When a vital
|
|
12423
|
+
"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
12424
|
"",
|
|
12407
|
-
"
|
|
12408
|
-
"",
|
|
12409
|
-
"Commands: /sessions show, /session <id> pickup, /end (transcript plus brief), /home, /progress, /help."
|
|
12425
|
+
"Handy later: /sessions show, /session <id> pickup, /end (transcript + brief), /home, /progress, /help."
|
|
12410
12426
|
],
|
|
12411
12427
|
deepdive: [
|
|
12412
|
-
'
|
|
12428
|
+
'"build me a game plan" before analysis queues the strategist and resumes after compute.',
|
|
12413
12429
|
"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
|
-
"
|
|
12430
|
+
"/scratch factory-resets data and config. progress.json and install.json survive unless you pass --include-progress.",
|
|
12415
12431
|
"Credits in /progress accrue in interactive ntrp only."
|
|
12416
12432
|
]
|
|
12417
12433
|
};
|
|
@@ -12428,6 +12444,11 @@ var init_guide_slides = __esm({
|
|
|
12428
12444
|
var metric_tour_exports = {};
|
|
12429
12445
|
__export(metric_tour_exports, {
|
|
12430
12446
|
METRICS_TOUR_MILESTONE_ID: () => METRICS_TOUR_MILESTONE_ID,
|
|
12447
|
+
TOUR_CLOSE_LINES: () => TOUR_CLOSE_LINES,
|
|
12448
|
+
TOUR_CLOSE_TITLE: () => TOUR_CLOSE_TITLE,
|
|
12449
|
+
TOUR_INTRO_LINES: () => TOUR_INTRO_LINES,
|
|
12450
|
+
TOUR_INTRO_TITLE: () => TOUR_INTRO_TITLE,
|
|
12451
|
+
TOUR_OFFER_BLURB: () => TOUR_OFFER_BLURB,
|
|
12431
12452
|
describeTourDeck: () => describeTourDeck,
|
|
12432
12453
|
firstRunTourDefaultChoice: () => firstRunTourDefaultChoice,
|
|
12433
12454
|
getDeepdiveNudge: () => getDeepdiveNudge,
|
|
@@ -12481,7 +12502,7 @@ function getDeepdiveNudge(ctx) {
|
|
|
12481
12502
|
if (hasCompletedMetricsTour() || hasSeenDeepdiveHomeNudge()) return null;
|
|
12482
12503
|
if (!hasAnalysisForDeepdiveNudge(ctx)) return null;
|
|
12483
12504
|
return {
|
|
12484
|
-
text: "Tour \u2014
|
|
12505
|
+
text: "Tour \u2014 how we think about the numbers, and how to use NTRP",
|
|
12485
12506
|
command: "/deepdive"
|
|
12486
12507
|
};
|
|
12487
12508
|
}
|
|
@@ -12567,19 +12588,13 @@ function paintIntro(opts) {
|
|
|
12567
12588
|
index: opts.index,
|
|
12568
12589
|
total: opts.total,
|
|
12569
12590
|
motion: opts.motion,
|
|
12570
|
-
titleOverride:
|
|
12591
|
+
titleOverride: TOUR_INTRO_TITLE,
|
|
12571
12592
|
visualOverride: GATING_LAYER_VISUAL,
|
|
12572
12593
|
skipFormula: true,
|
|
12573
12594
|
extraLines: [
|
|
12574
|
-
|
|
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.",
|
|
12577
|
-
"",
|
|
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.",
|
|
12595
|
+
...TOUR_INTRO_LINES,
|
|
12579
12596
|
"",
|
|
12580
|
-
"
|
|
12581
|
-
"",
|
|
12582
|
-
chalk10.dim("No AI key needed for this tour. Press Enter to advance; type /deepdive (or d) on any slide for more.")
|
|
12597
|
+
chalk10.dim("No AI key needed for this tour. Enter advances; /deepdive (or d) opens more on any slide.")
|
|
12583
12598
|
],
|
|
12584
12599
|
footer: `${progressDots(opts.index, opts.total)} \u23CE next \xB7 q skip`
|
|
12585
12600
|
});
|
|
@@ -12590,19 +12605,19 @@ function paintClose(opts) {
|
|
|
12590
12605
|
index: opts.index,
|
|
12591
12606
|
total: opts.total,
|
|
12592
12607
|
motion: opts.motion,
|
|
12593
|
-
titleOverride:
|
|
12608
|
+
titleOverride: TOUR_CLOSE_TITLE,
|
|
12594
12609
|
skipFormula: true,
|
|
12595
12610
|
extraLines: [
|
|
12596
|
-
|
|
12597
|
-
` ${paint("accent", "/diagnose")} \u2014 five vital signs
|
|
12611
|
+
TOUR_CLOSE_LINES[0],
|
|
12612
|
+
` ${paint("accent", "/diagnose")} \u2014 five vital signs with dollars attached`,
|
|
12598
12613
|
` ${paint("accent", "/metrics")} \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)`,
|
|
12599
12614
|
` ${paint("accent", "/deepdive")} \u2014 replay this tour \xB7 ${paint("accent", "/deepdive guide")} how-to only`,
|
|
12600
12615
|
` ${paint("accent", "/handoff")} \u2014 ship a file; teach the inbox once (/inbox skill)`,
|
|
12601
12616
|
` ${paint("accent", "/strategy")} \u2014 measurable plan \xB7 ${paint("accent", "/help")} shortcuts`,
|
|
12602
12617
|
"",
|
|
12603
|
-
|
|
12618
|
+
TOUR_CLOSE_LINES[7],
|
|
12604
12619
|
"",
|
|
12605
|
-
|
|
12620
|
+
TOUR_CLOSE_LINES[9]
|
|
12606
12621
|
],
|
|
12607
12622
|
footer: `${progressDots(opts.index, opts.total)} \u23CE done \xB7 q quit`
|
|
12608
12623
|
});
|
|
@@ -12628,7 +12643,7 @@ function guideExtraLines(slide, deepdive) {
|
|
|
12628
12643
|
];
|
|
12629
12644
|
if (deepdive && slide.deepdive.length > 0) {
|
|
12630
12645
|
lines.push("");
|
|
12631
|
-
lines.push(sectionHeading("
|
|
12646
|
+
lines.push(sectionHeading("How to read it"));
|
|
12632
12647
|
for (const bullet of slide.deepdive) {
|
|
12633
12648
|
lines.push(`\xB7 ${bullet}`);
|
|
12634
12649
|
}
|
|
@@ -12763,10 +12778,8 @@ function firstRunTourDefaultChoice() {
|
|
|
12763
12778
|
async function offerFirstRunTour(ctx) {
|
|
12764
12779
|
if (hasCompletedMetricsTour()) return false;
|
|
12765
12780
|
console.log();
|
|
12766
|
-
console.log(" " + bold("
|
|
12767
|
-
console.log(
|
|
12768
|
-
" " + chalk10.dim("SaaS numbers, five vitals, then how to talk to NTRP and ship work to your AI.")
|
|
12769
|
-
);
|
|
12781
|
+
console.log(" " + bold("A short tour") + chalk10.dim(" \u2014 ~3 minutes, no AI key required"));
|
|
12782
|
+
console.log(" " + chalk10.dim(TOUR_OFFER_BLURB));
|
|
12770
12783
|
const tourDefault = firstRunTourDefaultChoice();
|
|
12771
12784
|
if (tourDefault === "skip") {
|
|
12772
12785
|
console.log(" " + chalk10.dim("You have taken this tour before."));
|
|
@@ -12805,7 +12818,7 @@ async function offerFirstRunTour(ctx) {
|
|
|
12805
12818
|
await runMetricTour(ctx, { live: false });
|
|
12806
12819
|
return true;
|
|
12807
12820
|
}
|
|
12808
|
-
var TOUR_COMPLETED_KEY, TOUR_SKIPPED_KEY, HOME_NUDGE_SEEN_KEY, METRICS_TOUR_MILESTONE_ID;
|
|
12821
|
+
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
12822
|
var init_metric_tour = __esm({
|
|
12810
12823
|
"src/conversation/metric-tour.ts"() {
|
|
12811
12824
|
"use strict";
|
|
@@ -12823,6 +12836,30 @@ var init_metric_tour = __esm({
|
|
|
12823
12836
|
TOUR_SKIPPED_KEY = "metrics-tour-skipped";
|
|
12824
12837
|
HOME_NUDGE_SEEN_KEY = "metrics-tour-home-nudge-seen";
|
|
12825
12838
|
METRICS_TOUR_MILESTONE_ID = "metrics_tour";
|
|
12839
|
+
TOUR_INTRO_TITLE = "How we listen to a pipeline";
|
|
12840
|
+
TOUR_CLOSE_TITLE = "Curious? Start listening";
|
|
12841
|
+
TOUR_INTRO_LINES = [
|
|
12842
|
+
"We think about GTM health through two lenses \u2014 then we'll show how to talk to NTRP.",
|
|
12843
|
+
"",
|
|
12844
|
+
"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.",
|
|
12845
|
+
"",
|
|
12846
|
+
"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.",
|
|
12847
|
+
"",
|
|
12848
|
+
"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."
|
|
12849
|
+
];
|
|
12850
|
+
TOUR_CLOSE_LINES = [
|
|
12851
|
+
"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:",
|
|
12852
|
+
"/diagnose \u2014 five vital signs with dollars attached",
|
|
12853
|
+
"/metrics \u2014 SaaS scorecard (ARR, NRR, coverage\u2026)",
|
|
12854
|
+
"/deepdive \u2014 replay this tour \xB7 /deepdive guide how-to only",
|
|
12855
|
+
"/handoff \u2014 ship a file; teach the inbox once (/inbox skill)",
|
|
12856
|
+
"/strategy \u2014 measurable plan \xB7 /help shortcuts",
|
|
12857
|
+
"",
|
|
12858
|
+
"Fourteen more SaaS metrics sit behind /deepdive list \u2014 including unit economics that unlock when spend data lands.",
|
|
12859
|
+
"",
|
|
12860
|
+
"We're a stethoscope, not a hospital: we listen hard, translate scores into dollars, and point at plays \u2014 you decide the surgery."
|
|
12861
|
+
];
|
|
12862
|
+
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
12863
|
}
|
|
12827
12864
|
});
|
|
12828
12865
|
|
|
@@ -15472,7 +15509,6 @@ __export(phase_exports, {
|
|
|
15472
15509
|
buildConversationPrompt: () => buildConversationPrompt,
|
|
15473
15510
|
consumeOrientEmptyEnterCoach: () => consumeOrientEmptyEnterCoach,
|
|
15474
15511
|
formatPhaseLabel: () => formatPhaseLabel,
|
|
15475
|
-
getConversationPhaseBlock: () => getConversationPhaseBlock,
|
|
15476
15512
|
resolveConversationPhase: () => resolveConversationPhase,
|
|
15477
15513
|
sessionHasData: () => sessionHasData
|
|
15478
15514
|
});
|
|
@@ -15512,6 +15548,8 @@ function formatPhaseLabel(phase) {
|
|
|
15512
15548
|
switch (phase) {
|
|
15513
15549
|
case "orient":
|
|
15514
15550
|
return "setup";
|
|
15551
|
+
case "awaiting_data":
|
|
15552
|
+
return "data loading";
|
|
15515
15553
|
case "explore":
|
|
15516
15554
|
return "ready to ask";
|
|
15517
15555
|
case "think":
|
|
@@ -15520,15 +15558,24 @@ function formatPhaseLabel(phase) {
|
|
|
15520
15558
|
return phase.replace(/_/g, " ");
|
|
15521
15559
|
}
|
|
15522
15560
|
}
|
|
15561
|
+
function accentPromptHead(phase, sessionName) {
|
|
15562
|
+
const scope = sessionName ? ` ${sessionName}` : "";
|
|
15563
|
+
if (phase === "orient" || phase === "awaiting_data" || phase === "explore") {
|
|
15564
|
+
if (phase === "explore" && sessionName) {
|
|
15565
|
+
return paint("accent", `\u203A`) + chalk11.dim(`${scope} `);
|
|
15566
|
+
}
|
|
15567
|
+
return paint("accent", `\u203A `);
|
|
15568
|
+
}
|
|
15569
|
+
if (phase === "compute") {
|
|
15570
|
+
return paint("accent", `\u2026 `);
|
|
15571
|
+
}
|
|
15572
|
+
const word = PROMPT_LABELS[phase].replace(" \u203A", "");
|
|
15573
|
+
return paint("accent", `${word}${scope} \u203A `);
|
|
15574
|
+
}
|
|
15523
15575
|
function buildConversationPrompt(ctx) {
|
|
15524
15576
|
const phase = resolveConversationPhase(ctx);
|
|
15525
|
-
const
|
|
15526
|
-
const scope = ctx.sessionName ? ` ${ctx.sessionName}` : "";
|
|
15577
|
+
const sessionName = ctx.sessionName ?? "";
|
|
15527
15578
|
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
15579
|
if (phase === "explore") {
|
|
15533
15580
|
const mode = defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2));
|
|
15534
15581
|
const modeTag = mode === "brief" ? "brief" : "deep";
|
|
@@ -15536,34 +15583,15 @@ function buildConversationPrompt(ctx) {
|
|
|
15536
15583
|
const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 strategy after /connect") : "";
|
|
15537
15584
|
const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk11.dim(" \xB7 think after /connect") : "";
|
|
15538
15585
|
const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
15539
|
-
return
|
|
15586
|
+
return accentPromptHead(phase, sessionName) + chalk11.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
|
|
15540
15587
|
}
|
|
15541
15588
|
if (phase === "think") {
|
|
15542
15589
|
const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
|
|
15543
15590
|
const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
15544
|
-
return
|
|
15591
|
+
return accentPromptHead(phase, sessionName) + chalk11.dim(stack) + enterHint2 + " ";
|
|
15545
15592
|
}
|
|
15546
15593
|
const enterHint = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
|
|
15547
|
-
return
|
|
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");
|
|
15594
|
+
return accentPromptHead(phase, sessionName) + enterHint;
|
|
15567
15595
|
}
|
|
15568
15596
|
var PROMPT_LABELS;
|
|
15569
15597
|
var init_phase = __esm({
|
|
@@ -15579,9 +15607,9 @@ var init_phase = __esm({
|
|
|
15579
15607
|
PROMPT_LABELS = {
|
|
15580
15608
|
orient: "\u203A",
|
|
15581
15609
|
scope: "scope \u203A",
|
|
15582
|
-
awaiting_data: "
|
|
15610
|
+
awaiting_data: "\u203A",
|
|
15583
15611
|
compute: "\u2026",
|
|
15584
|
-
explore: "
|
|
15612
|
+
explore: "\u203A",
|
|
15585
15613
|
think: "think \u203A",
|
|
15586
15614
|
strategize: "strategy \u203A",
|
|
15587
15615
|
deliver: "ship \u203A"
|
|
@@ -20715,7 +20743,7 @@ function buildFindingsFindingHint(prefs) {
|
|
|
20715
20743
|
const voice2 = prefs ?? loadVoicePrefs();
|
|
20716
20744
|
const register = voice2.personality === "robotic" ? "STE-100 sentences. No contractions, slang, or filler." : voice2.personality === "composed" ? "Composed consultant sentences. Light contractions OK." : voice2.personality === "casual" ? "Casual consultant sentences. Warm, still short." : "Loose consultant sentences. Personality in the wrap, not the math.";
|
|
20717
20745
|
const roast = voice2.roast === "light" ? "Light roast: what is working, then what to improve." : voice2.roast === "medium" ? "Medium roast: name the miss. No padding." : voice2.roast === "dark" ? "Dark roast: surgical. Verdict + dollar in one breath." : "Heavy roast: maximum roast after the number, never instead of it.";
|
|
20718
|
-
return `Pyramid-shaped, 2-3 sentences: (1) HEADLINE \u2014 verdict + dollar figure in one short sentence (\u226420 words); (2)
|
|
20746
|
+
return `Pyramid-shaped, 2-3 sentences: (1) HEADLINE \u2014 verdict + dollar figure in one short sentence (\u226420 words); (2) DRIVERS \u2014 the one or two distinct, non-overlapping numbers that prove it; (3) SO-WHAT \u2014 the consequence or the action. ${register} ${roast} Keep dollar figures and play names. Do not invent numbers. An executive should be able to repeat sentence 1 from memory.`;
|
|
20719
20747
|
}
|
|
20720
20748
|
function buildFindingsSchemaBlock(prefs) {
|
|
20721
20749
|
const finding = buildFindingsFindingHint(prefs);
|
|
@@ -21107,8 +21135,11 @@ function buildInvestigationTools() {
|
|
|
21107
21135
|
if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
|
|
21108
21136
|
return tools;
|
|
21109
21137
|
}
|
|
21110
|
-
function buildFreshNlTools() {
|
|
21111
|
-
const
|
|
21138
|
+
function buildFreshNlTools(opts = {}) {
|
|
21139
|
+
const conversationTools = opts.analysisReady ? CONVERSATION_TOOLS.filter(
|
|
21140
|
+
(t) => t.name !== "propose_scope" && t.name !== "confirm_scope" && t.name !== "run_compute"
|
|
21141
|
+
) : CONVERSATION_TOOLS;
|
|
21142
|
+
const tools = [...AGENTIC_TOOLS, ...conversationTools];
|
|
21112
21143
|
if (isWebRetrievalEnabled()) tools.push(WEB_SEARCH_TOOL);
|
|
21113
21144
|
return tools;
|
|
21114
21145
|
}
|
|
@@ -21908,10 +21939,10 @@ function wrapForTarget(target, analysisBlock, conversationBlock, openQuestions,
|
|
|
21908
21939
|
const contextLabel = handoffInstructionPrefix(ctx.analysis.primary);
|
|
21909
21940
|
const forWhom = audiencePhrase(ctx.scope?.audience);
|
|
21910
21941
|
const instructions = {
|
|
21911
|
-
deck: `produce an executive review deck outline for ${company}, framed for ${forWhom}.
|
|
21942
|
+
deck: `produce an executive review deck outline for ${company}, framed for ${forWhom}. Pyramid structure: (1) HEADLINE \u2014 the governing number and verdict in one line; (2) DRIVERS \u2014 2\u20133 distinct, non-overlapping risks or causes with dollar impact (not a full scorecard); (3) SO-WHAT \u2014 recommended plays; (4) ASKS / decisions the room must make. Optionally one pipeline-health slide after the headline if it supports the call. Use the Metric definitions appendix so every slide can briefly remind the room what the number means for this audience.`,
|
|
21912
21943
|
asana: `produce an Asana project plan with sections and tasks tied to findings for ${forWhom}. Prioritize by dollar impact. Reference metric definitions when a task owner needs to know what "good" looks like.`,
|
|
21913
21944
|
clay: `produce a Clay table specification to operationalize the highest-impact finding for ${forWhom}.`,
|
|
21914
|
-
plan: `produce a prioritized action plan for ${forWhom} with problem, play, first 3 steps, owner, and leading indicator
|
|
21945
|
+
plan: `produce a prioritized action plan for ${forWhom}, answer-first: open with the governing call (highest-dollar problem), then items each with problem, play, first 3 steps, owner, and leading indicator. Ground indicators in the Metric definitions appendix. End with the sharpest asks / decisions.`
|
|
21915
21946
|
};
|
|
21916
21947
|
const parts = [
|
|
21917
21948
|
`# NTRP handoff \u2192 ${target}`,
|
|
@@ -22811,13 +22842,14 @@ Cite numbers from here; call tools when you need a new cut or to pressure-test a
|
|
|
22811
22842
|
|
|
22812
22843
|
` : "";
|
|
22813
22844
|
const conversationSection = opts.conversationBlock ? `
|
|
22814
|
-
CONVERSATION STATE:
|
|
22815
22845
|
${opts.conversationBlock}
|
|
22816
22846
|
|
|
22817
22847
|
` : "";
|
|
22818
22848
|
const scratchSection = buildScratchBlock(opts.thinkState);
|
|
22819
22849
|
const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
|
|
22850
|
+
- Pre-explore funnel gates are handled by the CLI; you speak in the think channel after analysis.
|
|
22820
22851
|
- You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
|
|
22852
|
+
- Probing turns: lead with the sharpest question that unlocks the next insight. Concluding turns: use pyramid (headline \u2192 drivers \u2192 so-what) \u2014 answer first, then structure.
|
|
22821
22853
|
- Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
|
|
22822
22854
|
- Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
|
|
22823
22855
|
- Ground claims about THIS pipeline in tool results. Label speculation explicitly ("hypothesis:", "speculation:").
|
|
@@ -22828,7 +22860,7 @@ ${opts.conversationBlock}
|
|
|
22828
22860
|
- You have continuity via prior think-channel messages. Never repeat an angle already covered unless asked.`;
|
|
22829
22861
|
const jobSection = `YOUR JOB (THINK CHANNEL \u2014 always deep):
|
|
22830
22862
|
- Decide whether you need tools, a direct answer, or both. Do not re-call a tool whose result you already have.
|
|
22831
|
-
-
|
|
22863
|
+
- Probing: lead with the question that matters most. Concluding: lead with the answer (headline), then drivers and so-what.
|
|
22832
22864
|
- When you use numbers, include dollar values where available and lead with financial impact.
|
|
22833
22865
|
- Descriptive exploration stays in this channel; plan-of-attack questions hand off via draft_strategy.
|
|
22834
22866
|
- After a successful draft_strategy tool result: reply with at most 1\u20132 sentences introducing the handoff. Do not write the plan.`;
|
|
@@ -22850,7 +22882,7 @@ ${buildCommandCatalogBlock()}
|
|
|
22850
22882
|
COMMAND SUGGESTION RULES:
|
|
22851
22883
|
- Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
|
|
22852
22884
|
- Never claim a command was run. Never invent flags.
|
|
22853
|
-
- Type \`done\` or \`cancel\` leaves the think channel back to
|
|
22885
|
+
- Type \`done\` or \`cancel\` leaves the think channel back to \u203A.`;
|
|
22854
22886
|
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
22887
|
|
|
22856
22888
|
${companyContextSection2()}${operatorSection2()}${socraticCraft}
|
|
@@ -22866,6 +22898,9 @@ ${EXECUTION_BIAS_BLOCK}
|
|
|
22866
22898
|
GTM ENGINEERING (how recommendations become systems):
|
|
22867
22899
|
${GTM_ENGINEERING_BLOCK}
|
|
22868
22900
|
|
|
22901
|
+
IDEA GATE (when recommending a play or before draft_strategy \u2014 silent; probing/imagination turns skip this until you conclude):
|
|
22902
|
+
${HEILMEIER_GATE}
|
|
22903
|
+
|
|
22869
22904
|
OUTPUT DOCTRINE (how answers are structured for recall):
|
|
22870
22905
|
${PYRAMID_OUTPUT_BLOCK}
|
|
22871
22906
|
|
|
@@ -22900,7 +22935,7 @@ ${SAFETY_BLOCK}`;
|
|
|
22900
22935
|
const dynamic = [
|
|
22901
22936
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
22902
22937
|
...dynamicSections,
|
|
22903
|
-
buildRuntimeBlock()
|
|
22938
|
+
buildRuntimeBlock(opts.runtimeFacts ?? {})
|
|
22904
22939
|
].join("\n\n");
|
|
22905
22940
|
return { stable, dynamic };
|
|
22906
22941
|
}
|
|
@@ -22912,6 +22947,170 @@ var init_think_prompt = __esm({
|
|
|
22912
22947
|
}
|
|
22913
22948
|
});
|
|
22914
22949
|
|
|
22950
|
+
// src/conversation/ghost-hints.ts
|
|
22951
|
+
function ghostExamplesForPhase(phase, limit = 2) {
|
|
22952
|
+
const hints = PHASE_GHOST_HINTS[phase] ?? [];
|
|
22953
|
+
return hints.slice(0, limit).map((h) => h.replace(/^try\s+/i, ""));
|
|
22954
|
+
}
|
|
22955
|
+
var PHASE_GHOST_HINTS;
|
|
22956
|
+
var init_ghost_hints = __esm({
|
|
22957
|
+
"src/conversation/ghost-hints.ts"() {
|
|
22958
|
+
"use strict";
|
|
22959
|
+
PHASE_GHOST_HINTS = {
|
|
22960
|
+
orient: [
|
|
22961
|
+
'try "pipeline health"',
|
|
22962
|
+
"try /deepdive",
|
|
22963
|
+
"try /deepdive guide",
|
|
22964
|
+
'try "is our retention real for the board?"',
|
|
22965
|
+
'try "what is the most expensive problem to solve?"',
|
|
22966
|
+
'try "board deck on Q3"'
|
|
22967
|
+
],
|
|
22968
|
+
awaiting_data: [
|
|
22969
|
+
'try "use demo data"',
|
|
22970
|
+
"try a CSV path",
|
|
22971
|
+
"try /ingest",
|
|
22972
|
+
'try "go ahead"'
|
|
22973
|
+
],
|
|
22974
|
+
explore: [
|
|
22975
|
+
'try "what is the most expensive problem?"',
|
|
22976
|
+
'try "how should we fix this?"',
|
|
22977
|
+
'try "which segment is weakest?"',
|
|
22978
|
+
'try "what is ARR?"',
|
|
22979
|
+
"try /deepdive freshness",
|
|
22980
|
+
'try "ship a board deck"'
|
|
22981
|
+
]
|
|
22982
|
+
};
|
|
22983
|
+
}
|
|
22984
|
+
});
|
|
22985
|
+
|
|
22986
|
+
// src/conversation/situation.ts
|
|
22987
|
+
function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
22988
|
+
const phase = resolveConversationPhase(ctx);
|
|
22989
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
22990
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
22991
|
+
const analysisReady = isAnalysisReady(ctx);
|
|
22992
|
+
const action = resolveRecommendedAction(ctx);
|
|
22993
|
+
const examples = ghostExamplesForPhase(
|
|
22994
|
+
phase === "think" || phase === "strategize" || phase === "deliver" ? "explore" : phase,
|
|
22995
|
+
2
|
|
22996
|
+
);
|
|
22997
|
+
const lines = [
|
|
22998
|
+
"WHERE YOU ARE IN NTRP:",
|
|
22999
|
+
FUNNEL_SPINE,
|
|
23000
|
+
`Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
|
|
23001
|
+
];
|
|
23002
|
+
if (channel === "think") {
|
|
23003
|
+
lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
|
|
23004
|
+
lines.push("Response mode: deep (tools on)");
|
|
23005
|
+
} else if (channel === "strategist") {
|
|
23006
|
+
lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
|
|
23007
|
+
} else {
|
|
23008
|
+
lines.push(
|
|
23009
|
+
responseMode === "brief" ? "Response mode: brief (tools off; cite COMPLETED SESSION ANALYSIS; ~40\u201390 words)" : "Response mode: deep (tools on; new cuts and drill-downs)"
|
|
23010
|
+
);
|
|
23011
|
+
}
|
|
23012
|
+
if (ctx.scope) {
|
|
23013
|
+
const bits = [`Scope: ${ctx.scope.intent_summary}`, `lens=${ctx.scope.primary_lens}`];
|
|
23014
|
+
if (ctx.scope.audience) bits.push(`audience=${ctx.scope.audience}`);
|
|
23015
|
+
if (ctx.scope.time_horizon) bits.push(`horizon=${ctx.scope.time_horizon}`);
|
|
23016
|
+
lines.push(bits.join(" \xB7 "));
|
|
23017
|
+
if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
|
|
23018
|
+
}
|
|
23019
|
+
if (ctx.dataset?.label) {
|
|
23020
|
+
lines.push(`Dataset: ${ctx.dataset.label} \xB7 stage=${ctx.stage}`);
|
|
23021
|
+
} else if (sessionHasData(ctx)) {
|
|
23022
|
+
lines.push(`Dataset: loaded \xB7 stage=${ctx.stage}`);
|
|
23023
|
+
} else {
|
|
23024
|
+
lines.push(`Dataset: empty \xB7 stage=${ctx.stage}`);
|
|
23025
|
+
}
|
|
23026
|
+
if (ctx.gapAudit) {
|
|
23027
|
+
lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
|
|
23028
|
+
if (ctx.gapAudit.missing.length > 0) {
|
|
23029
|
+
lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
|
|
23030
|
+
}
|
|
23031
|
+
}
|
|
23032
|
+
if (analysisReady) {
|
|
23033
|
+
lines.push(
|
|
23034
|
+
"Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
|
|
23035
|
+
);
|
|
23036
|
+
lines.push(
|
|
23037
|
+
"Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /deepdive <metric>, /thinkwithme, /playbook"
|
|
23038
|
+
);
|
|
23039
|
+
if (channel === "explore" && responseMode === "deep") {
|
|
23040
|
+
lines.push(
|
|
23041
|
+
"Tools: diagnostics + draft_strategy + draft_handoff (+ audit_data_gaps for re-check). Scope/compute orchestration is off."
|
|
23042
|
+
);
|
|
23043
|
+
}
|
|
23044
|
+
} else if (phase === "awaiting_data") {
|
|
23045
|
+
lines.push("Already done: scope confirmed; analysis not ready");
|
|
23046
|
+
lines.push(
|
|
23047
|
+
'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'
|
|
23048
|
+
);
|
|
23049
|
+
} else if (phase === "scope") {
|
|
23050
|
+
lines.push("Already done: intent proposed; waiting on confirm");
|
|
23051
|
+
lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
|
|
23052
|
+
} else if (phase === "orient") {
|
|
23053
|
+
lines.push("Already done: nothing locked yet");
|
|
23054
|
+
lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
|
|
23055
|
+
} else if (phase === "think") {
|
|
23056
|
+
lines.push("Already done: analysis complete; think channel open");
|
|
23057
|
+
lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
|
|
23058
|
+
}
|
|
23059
|
+
lines.push(
|
|
23060
|
+
`Armed user next step: ${action ? `${action.hint} (\u23CE submits "${action.submit}")` : "none"}`
|
|
23061
|
+
);
|
|
23062
|
+
lines.push(`Engine connected: ${canUseReplAi(ctx) ? "yes" : "no"}`);
|
|
23063
|
+
if (examples.length > 0) {
|
|
23064
|
+
lines.push(`User may try: ${examples.join(" \xB7 ")}`);
|
|
23065
|
+
}
|
|
23066
|
+
lines.push(
|
|
23067
|
+
"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"
|
|
23068
|
+
);
|
|
23069
|
+
lines.push(
|
|
23070
|
+
"Pre-explore funnel gates are handled by the CLI; you speak in explore/think/strategist unless tools explicitly allow a restart"
|
|
23071
|
+
);
|
|
23072
|
+
return lines.join("\n");
|
|
23073
|
+
}
|
|
23074
|
+
function buildSituationalRuntimeFacts(ctx, opts = {}) {
|
|
23075
|
+
const phase = resolveConversationPhase(ctx);
|
|
23076
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
23077
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
23078
|
+
return {
|
|
23079
|
+
phase,
|
|
23080
|
+
mode: responseMode,
|
|
23081
|
+
lens: ctx.scope?.primary_lens,
|
|
23082
|
+
stage: ctx.stage,
|
|
23083
|
+
can_compute: ctx.gapAudit?.can_compute === void 0 ? void 0 : String(ctx.gapAudit.can_compute),
|
|
23084
|
+
engine: canUseReplAi(ctx) ? "connected" : "none"
|
|
23085
|
+
};
|
|
23086
|
+
}
|
|
23087
|
+
function getConversationPhaseBlock(ctx, opts = {}) {
|
|
23088
|
+
return buildSituationalAwarenessBlock(ctx, opts);
|
|
23089
|
+
}
|
|
23090
|
+
var FUNNEL_SPINE, PHASE_MEANING;
|
|
23091
|
+
var init_situation = __esm({
|
|
23092
|
+
"src/conversation/situation.ts"() {
|
|
23093
|
+
"use strict";
|
|
23094
|
+
init_context2();
|
|
23095
|
+
init_explore_mode();
|
|
23096
|
+
init_repl_api();
|
|
23097
|
+
init_ghost_hints();
|
|
23098
|
+
init_recommended_action();
|
|
23099
|
+
init_phase();
|
|
23100
|
+
FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
|
|
23101
|
+
PHASE_MEANING = {
|
|
23102
|
+
orient: "setup \u2014 open chat; user has not locked a scope yet",
|
|
23103
|
+
scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
|
|
23104
|
+
awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
|
|
23105
|
+
compute: "compute in progress \u2014 wait",
|
|
23106
|
+
explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
|
|
23107
|
+
think: "socratic think channel \u2014 return to \u203A via done/cancel",
|
|
23108
|
+
strategize: "strategist flow \u2014 objective confirm or plan drafting",
|
|
23109
|
+
deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
|
|
23110
|
+
};
|
|
23111
|
+
}
|
|
23112
|
+
});
|
|
23113
|
+
|
|
22915
23114
|
// src/ai/agentic-loop.ts
|
|
22916
23115
|
function findingsJsonSchemaNudge() {
|
|
22917
23116
|
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 +23216,11 @@ The user already received the full report in the terminal. Answer follow-ups by
|
|
|
23017
23216
|
|
|
23018
23217
|
` : "";
|
|
23019
23218
|
const conversationSection = conversationBlock ? `
|
|
23020
|
-
CONVERSATION STATE:
|
|
23021
23219
|
${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
23220
|
|
|
23024
23221
|
` : "";
|
|
23025
23222
|
const conversationRules = `HOW A GREAT ANALYST CARRIES A CONVERSATION (read carefully \u2014 this is what separates you from a search box):
|
|
23223
|
+
- 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
23224
|
- 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
23225
|
- 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
23226
|
- 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 +23229,9 @@ Do not run compute until audit_data_gaps reports can_compute. If the user needs
|
|
|
23031
23229
|
- 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
23230
|
const briefJob = `YOUR JOB (BRIEF MODE \u2014 default after analysis is complete):
|
|
23033
23231
|
- 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.
|
|
23232
|
+
- Lead with the single most expensive or most actionable implication from the completed session analysis \u2014 not a scorecard restatement.
|
|
23233
|
+
- Do not re-list all five vital signs unless the user asked for that inventory.
|
|
23234
|
+
- Prefer naming one playbook play or one next cut over a generic "want me to dig deeper?" close.
|
|
23034
23235
|
- 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
23236
|
- 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
23237
|
- Name a playbook play only when the user asks what to do or which action to take.
|
|
@@ -23100,12 +23301,18 @@ ${commandStyleRule}`;
|
|
|
23100
23301
|
- Respond in plain text (not JSON). You do NOT need to emit the findings schema \u2014 only emit structured findings if the user explicitly asks for them.
|
|
23101
23302
|
- Include specific numbers from tool results, never guess.
|
|
23102
23303
|
- When you have enough information, answer directly. Don't call tools you don't need.`;
|
|
23103
|
-
const executionBiasSection = responseMode === "brief" ?
|
|
23304
|
+
const executionBiasSection = responseMode === "brief" ? `OUTPUT DOCTRINE (brief \u2014 one headline + \u22642 drivers + one so-what):
|
|
23305
|
+
${PYRAMID_OUTPUT_BLOCK}
|
|
23306
|
+
|
|
23307
|
+
` : `EXECUTION BIAS (how you work):
|
|
23104
23308
|
${EXECUTION_BIAS_BLOCK}
|
|
23105
23309
|
|
|
23106
23310
|
GTM ENGINEERING (how your recommendations become systems):
|
|
23107
23311
|
${GTM_ENGINEERING_BLOCK}
|
|
23108
23312
|
|
|
23313
|
+
IDEA GATE (when recommending action \u2014 silent):
|
|
23314
|
+
${HEILMEIER_GATE}
|
|
23315
|
+
|
|
23109
23316
|
OUTPUT DOCTRINE (how answers are structured for recall):
|
|
23110
23317
|
${PYRAMID_OUTPUT_BLOCK}
|
|
23111
23318
|
|
|
@@ -23150,7 +23357,7 @@ ${briefCommandSection}` : "";
|
|
|
23150
23357
|
const dynamic = [
|
|
23151
23358
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
23152
23359
|
...dynamicSections,
|
|
23153
|
-
buildRuntimeBlock()
|
|
23360
|
+
buildRuntimeBlock(promptOptions.runtimeFacts ?? {})
|
|
23154
23361
|
].join("\n\n");
|
|
23155
23362
|
return { stable, dynamic };
|
|
23156
23363
|
}
|
|
@@ -23182,15 +23389,27 @@ async function* agenticFindings(computeResult, divergences, options) {
|
|
|
23182
23389
|
analysisBlock: options.analysisBlock,
|
|
23183
23390
|
conversationBlock: options.conversationBlock,
|
|
23184
23391
|
sessionArtifact: options.sessionArtifact,
|
|
23185
|
-
thinkState: options.ctx.thinkState
|
|
23392
|
+
thinkState: options.ctx.thinkState,
|
|
23393
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
23394
|
+
channel: "think",
|
|
23395
|
+
responseMode: "deep"
|
|
23396
|
+
})
|
|
23186
23397
|
}) : mode === "fresh" ? buildFreshNlSystemPrompt(
|
|
23187
23398
|
options.sessionContext,
|
|
23188
23399
|
options.memoryBlock,
|
|
23189
23400
|
options.analysisBlock,
|
|
23190
23401
|
options.conversationBlock,
|
|
23191
|
-
{
|
|
23402
|
+
{
|
|
23403
|
+
responseMode,
|
|
23404
|
+
sessionArtifact: options.sessionArtifact,
|
|
23405
|
+
experiment,
|
|
23406
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
23407
|
+
responseMode
|
|
23408
|
+
})
|
|
23409
|
+
}
|
|
23192
23410
|
) : buildSystemPrompt();
|
|
23193
|
-
const
|
|
23411
|
+
const analysisReady = isAnalysisReady(options.ctx);
|
|
23412
|
+
const tools = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools({ analysisReady }) : buildInvestigationTools() : [];
|
|
23194
23413
|
const toolCtx = { computeResult, divergences };
|
|
23195
23414
|
if (options.includeMetrics) {
|
|
23196
23415
|
try {
|
|
@@ -23359,6 +23578,8 @@ var init_agentic_loop = __esm({
|
|
|
23359
23578
|
init_prompt_parts();
|
|
23360
23579
|
init_think_prompt();
|
|
23361
23580
|
init_prompt();
|
|
23581
|
+
init_context2();
|
|
23582
|
+
init_situation();
|
|
23362
23583
|
MAX_ITERATIONS = 10;
|
|
23363
23584
|
INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
|
|
23364
23585
|
BRIEF_MAX_TOKENS = 768;
|
|
@@ -25753,6 +25974,11 @@ function generateMarkdownReport(data) {
|
|
|
25753
25974
|
lines.push("");
|
|
25754
25975
|
lines.push(`*Generated: ${data.generatedAt}*`);
|
|
25755
25976
|
lines.push("");
|
|
25977
|
+
const theCall = formatTheCallSection(data.findings);
|
|
25978
|
+
if (theCall) {
|
|
25979
|
+
lines.push(theCall.trimEnd());
|
|
25980
|
+
lines.push("");
|
|
25981
|
+
}
|
|
25756
25982
|
lines.push("## Overall Health");
|
|
25757
25983
|
lines.push("");
|
|
25758
25984
|
lines.push(`${statusEmoji(data.health.overall_status)} **Score: ${formatScore(data.health.overall_score)} / 100** (${data.health.overall_status})`);
|
|
@@ -26186,6 +26412,11 @@ function exportToNotes(data) {
|
|
|
26186
26412
|
const body = [];
|
|
26187
26413
|
body.push("# GTM Health Diagnosis");
|
|
26188
26414
|
body.push("");
|
|
26415
|
+
const theCall = formatTheCallSection(findings);
|
|
26416
|
+
if (theCall) {
|
|
26417
|
+
body.push(theCall.trimEnd());
|
|
26418
|
+
body.push("");
|
|
26419
|
+
}
|
|
26189
26420
|
body.push(`**Overall Score:** ${aggregate.overall_score}/100 (${aggregate.overall_status})`);
|
|
26190
26421
|
body.push(`**Gating Vital Sign:** ${VITAL_SIGN_LABELS[aggregate.gating_vital_sign]}`);
|
|
26191
26422
|
body.push(`**Date:** ${now2.toISOString().split("T")[0]} ${timeStr.replace(/(\d{2})(\d{2})/, "$1:$2")}`);
|
|
@@ -28249,10 +28480,13 @@ ${companyContextSection4()}${operatorSection4()}HOW YOU THINK (the strategist me
|
|
|
28249
28480
|
8. RESPECT CAPACITY. Total effort must fit the team that actually exists. A brilliant plan the team cannot staff is a bad plan.
|
|
28250
28481
|
|
|
28251
28482
|
ALTITUDE CONTRACT (the plan must work at every altitude a client reads it at):
|
|
28252
|
-
- 30,000 FT: summary_30k is
|
|
28483
|
+
- 30,000 FT: summary_30k is answer-first \u2014 open with the governing thought and what leadership should expect by when, then the situation and complication that make it true. A board member reads only this; it must survive being forwarded unedited.
|
|
28253
28484
|
- 10,000 FT: each workstream's title + problem line is a delegation unit \u2014 a one-liner an owner could receive in Slack and know what they own, why it's theirs, and what number they move.
|
|
28254
28485
|
- GROUND LEVEL: actions are Monday-morning prescriptive. You have built this before \u2014 read the linked play's full detail with get_play_detail and prescribe its known-good sequence adapted to THIS company's numbers and constraints. The first action of every workstream must be startable within 48 hours with no new tooling.
|
|
28255
28486
|
|
|
28487
|
+
OUTPUT DOCTRINE (narrative fields \u2014 summary_30k and workstream problem lines \u2014 follow pyramid recall):
|
|
28488
|
+
${PYRAMID_OUTPUT_BLOCK}
|
|
28489
|
+
|
|
28256
28490
|
${buildUserVisibleProseBlock()}
|
|
28257
28491
|
|
|
28258
28492
|
GTM ENGINEERING (plans install systems, not heroics):
|
|
@@ -28335,6 +28569,8 @@ function buildStressTestMessage() {
|
|
|
28335
28569
|
6. EXPECTATION HONESTY: are target ranges defensible from the baseline and effort, or heroic? Widen ranges or lower confidence rather than promising what the data does not support. Where the local play track record shows a play has hit or missed here before, weight confidence accordingly.
|
|
28336
28570
|
7. CONTINGENCY QUALITY: is each trigger observable on a specific date, and is each fallback a real pre-decision (alternate play, descope, escalate) rather than "monitor closely"?
|
|
28337
28571
|
8. ALTITUDE CHECK: does summary_30k survive being forwarded to a board member unedited? Is each workstream title + problem a self-contained delegation one-liner? Is every first action startable within 48 hours?
|
|
28572
|
+
9. HEILMEIER GATE (silent \u2014 never name this to the user). Run every workstream through this filter. Failures: cut the workstream, demote it to assumptions, or rewrite until it passes \u2014 then emit final JSON.
|
|
28573
|
+
${HEILMEIER_GATE}
|
|
28338
28574
|
|
|
28339
28575
|
Then respond with the FINAL revised plan as strict JSON in the same schema \u2014 no markdown fences, no prose. Fold what you learned into constraints, assumptions, risks, and confidence. This version is the one that gets saved and reviewed against, so make every number one you are willing to be checked on.`;
|
|
28340
28576
|
}
|
|
@@ -32396,10 +32632,10 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
32396
32632
|
setAgentContext(ctx);
|
|
32397
32633
|
try {
|
|
32398
32634
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
32399
|
-
const
|
|
32635
|
+
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
32636
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { responseMode });
|
|
32400
32637
|
const bundle = await loadSessionAnalysisBundle();
|
|
32401
32638
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
32402
|
-
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
32403
32639
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
32404
32640
|
mode: "fresh",
|
|
32405
32641
|
userQuestion: input,
|
|
@@ -32497,6 +32733,7 @@ var init_nl = __esm({
|
|
|
32497
32733
|
"use strict";
|
|
32498
32734
|
init_spinner();
|
|
32499
32735
|
init_context2();
|
|
32736
|
+
init_situation();
|
|
32500
32737
|
init_phase();
|
|
32501
32738
|
init_agent_context();
|
|
32502
32739
|
init_orchestrator();
|
|
@@ -32562,7 +32799,7 @@ async function* streamAsk(question, ctx) {
|
|
|
32562
32799
|
sessionContext: ctx.resumedSessionSummary,
|
|
32563
32800
|
includeMetrics: true,
|
|
32564
32801
|
analysisBlock: buildAnalysisBlock(ctx),
|
|
32565
|
-
conversationBlock: getConversationPhaseBlock(ctx),
|
|
32802
|
+
conversationBlock: getConversationPhaseBlock(ctx, { responseMode }),
|
|
32566
32803
|
sessionArtifact,
|
|
32567
32804
|
responseMode,
|
|
32568
32805
|
priorMessages: ctx.conversation,
|
|
@@ -32607,7 +32844,7 @@ var init_ask = __esm({
|
|
|
32607
32844
|
init_divergence();
|
|
32608
32845
|
init_repl_api();
|
|
32609
32846
|
init_context2();
|
|
32610
|
-
|
|
32847
|
+
init_situation();
|
|
32611
32848
|
init_session_analysis();
|
|
32612
32849
|
init_smoke_protocol();
|
|
32613
32850
|
}
|
|
@@ -32994,11 +33231,14 @@ function buildRecapSystemPrompt(companyBlock) {
|
|
|
32994
33231
|
"",
|
|
32995
33232
|
companyBlock ? `## Company Context
|
|
32996
33233
|
${companyBlock}` : "",
|
|
33234
|
+
"",
|
|
33235
|
+
"OUTPUT DOCTRINE (how the recap is structured for recall):",
|
|
33236
|
+
PYRAMID_OUTPUT_BLOCK,
|
|
32997
33237
|
"",
|
|
32998
33238
|
"Produce a concise session recap in markdown, pyramid-shaped:",
|
|
32999
|
-
"1. **The Story** \u2014
|
|
33000
|
-
"2. **Key Findings** \u2014 3-5 bullets
|
|
33001
|
-
"3. **Next Steps** \u2014 2-3 actions, each with an owner-shaped verb and, where a play applies, the play named.",
|
|
33239
|
+
"1. **The Story** \u2014 answer-first: open with the resolution (what leadership should take away), then the situation and complication that make it true. One or two sentences. This is the line the client repeats tomorrow.",
|
|
33240
|
+
"2. **Key Findings** \u2014 3-5 bullets as drivers: each headline-first with its number and dollar value; distinct and non-overlapping; no bullet without a figure unless none exists.",
|
|
33241
|
+
"3. **Next Steps** \u2014 2-3 so-what actions, each with an owner-shaped verb and, where a play applies, the play named.",
|
|
33002
33242
|
"4. **Open Loops** \u2014 anything left unresolved or promised for next time (omit the section if none).",
|
|
33003
33243
|
"",
|
|
33004
33244
|
"Keep the total output under 300 words.",
|
|
@@ -33800,7 +34040,7 @@ async function runThinkTurn(input, ctx) {
|
|
|
33800
34040
|
setAgentContext(ctx);
|
|
33801
34041
|
try {
|
|
33802
34042
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
33803
|
-
const conversationBlock = getConversationPhaseBlock(ctx);
|
|
34043
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { channel: "think", responseMode: "deep" });
|
|
33804
34044
|
const bundle = await loadSessionAnalysisBundle();
|
|
33805
34045
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
33806
34046
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
@@ -33887,7 +34127,7 @@ var init_think = __esm({
|
|
|
33887
34127
|
"use strict";
|
|
33888
34128
|
init_spinner();
|
|
33889
34129
|
init_context2();
|
|
33890
|
-
|
|
34130
|
+
init_situation();
|
|
33891
34131
|
init_agent_context();
|
|
33892
34132
|
init_agentic_loop();
|
|
33893
34133
|
init_thread();
|
|
@@ -33951,7 +34191,7 @@ function printChannelIntro(seed) {
|
|
|
33951
34191
|
console.log(
|
|
33952
34192
|
" " + chalk67.dim(
|
|
33953
34193
|
"Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
|
|
33954
|
-
) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to
|
|
34194
|
+
) + chalk67.cyan("done") + chalk67.dim(" or ") + chalk67.cyan("cancel") + chalk67.dim(" to return to \u203A.")
|
|
33955
34195
|
);
|
|
33956
34196
|
if (seed) {
|
|
33957
34197
|
console.log(" " + chalk67.dim("Seed: ") + seed);
|
|
@@ -34037,7 +34277,7 @@ function clearThinkFlow(ctx, reason) {
|
|
|
34037
34277
|
console.log();
|
|
34038
34278
|
console.log(
|
|
34039
34279
|
" " + chalk67.dim(
|
|
34040
|
-
reason === "done" ? "Think channel closed. Back to
|
|
34280
|
+
reason === "done" ? "Think channel closed. Back to \u203A." : "Think session cancelled. Continue exploration."
|
|
34041
34281
|
)
|
|
34042
34282
|
);
|
|
34043
34283
|
console.log();
|
|
@@ -36457,7 +36697,7 @@ handler: ../commands/thinkwithme.ts
|
|
|
36457
36697
|
|
|
36458
36698
|
Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
|
|
36459
36699
|
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
|
|
36700
|
+
Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
|
|
36461
36701
|
When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
|
|
36462
36702
|
},
|
|
36463
36703
|
{
|
|
@@ -36860,7 +37100,7 @@ function formatCatalogLine(meta) {
|
|
|
36860
37100
|
const note = DESTRUCTIVE_COMMAND_NOTES[meta.name] ? ` (${DESTRUCTIVE_COMMAND_NOTES[meta.name]})` : "";
|
|
36861
37101
|
return `- /${meta.name}${args} \u2014 ${meta.description}${note}`;
|
|
36862
37102
|
}
|
|
36863
|
-
var ANALYST_FILE_NAME, ANALYST_FILE_MAX_CHARS, EXECUTION_BIAS_BLOCK, ANALYST_INSTINCT_BLOCK, SAFETY_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, GTM_ENGINEERING_BLOCK, PYRAMID_OUTPUT_BLOCK;
|
|
37103
|
+
var ANALYST_FILE_NAME, ANALYST_FILE_MAX_CHARS, EXECUTION_BIAS_BLOCK, ANALYST_INSTINCT_BLOCK, SAFETY_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, GTM_ENGINEERING_BLOCK, HEILMEIER_GATE, PYRAMID_OUTPUT_BLOCK;
|
|
36864
37104
|
var init_prompt_parts = __esm({
|
|
36865
37105
|
"src/ai/prompt-parts.ts"() {
|
|
36866
37106
|
"use strict";
|
|
@@ -36939,12 +37179,23 @@ Instrument trust: every metric here carries confidence and reliability_gate fiel
|
|
|
36939
37179
|
- EVERY FIX GETS A MECHANISM. One-time cleanups decay in a quarter. Pair each cleanup with the mechanism that keeps it fixed: a routing rule, an SLA with a report behind it, an enrichment waterfall, a signal-triggered task, an alert in the channel reps already work in.
|
|
36940
37180
|
- INSTRUMENT WHAT YOU CHANGE. A system you can't measure is a system you can't defend at the next QBR. Name the metric each mechanism should move and where it will be read.
|
|
36941
37181
|
Restraint: you diagnose and prescribe the system; you do not build it here. Name the mechanism class, not a vendor shopping list.`;
|
|
37182
|
+
HEILMEIER_GATE = `Before you recommend an action, play, or workstream \u2014 run this gate. Failures are silent: kill, demote to an assumption, or ask one sharper question. Never say Heilmeier, DARPA, or catechism to the user.
|
|
37183
|
+
- END STATE. Name the precise outcome in one line. Vague goals die here.
|
|
37184
|
+
- STATUS QUO. How is it done today, and what is the hard limit of that approach?
|
|
37185
|
+
- NEWNESS. What is new in this approach, and why might it work when the status quo fails? If nothing is new, kill it as a restatement.
|
|
37186
|
+
- STAKE. Who cares? If it works, what difference does it make \u2014 dollars recovered or a decision unlocked? Name the cost of doing nothing. No stake \u2192 do not recommend.
|
|
37187
|
+
- RISKS AND PAYOFFS. State the main downside and the upside in the same breath.
|
|
37188
|
+
- EFFORT. Rough cost in team hours or capacity. If it cannot fit the team that exists, cut or re-sequence \u2014 do not soft-pedal.
|
|
37189
|
+
- TIME. Bound by sales-cycle and metric-update physics. Dates faster than evidence can appear are invalid.
|
|
37190
|
+
- EXAMS. Name a mid-term check and a final exam: dated, measurable, with an instrument that exists. No exams \u2192 demote to assumption or drop.
|
|
37191
|
+
- KILL RULE. If newness, stake, or exams fail: do not recommend. Demote, drop, or ask one sharper question. Surviving ideas then get pyramid packaging.`;
|
|
36942
37192
|
PYRAMID_OUTPUT_BLOCK = `Structure everything the way a client remembers it \u2014 pyramid, answer first:
|
|
36943
|
-
- HEADLINE FIRST. Open with the verdict and the number in one sentence (\
|
|
36944
|
-
- THEN THE DRIVERS. Support the headline with 2-3 distinct, non-overlapping
|
|
37193
|
+
- HEADLINE FIRST. Open with the verdict and the number in one sentence (\u226420 words): what is true and what it costs. Never open with methodology or context.
|
|
37194
|
+
- THEN THE DRIVERS. Support the headline with 2-3 drivers that are distinct, non-overlapping, and together explain the headline \u2014 each with its own number. If two points share a cause, merge them.
|
|
36945
37195
|
- THEN THE SO-WHAT. Close with what it means for the decision at hand: the action, the owner-shaped next step, or the sharpest next cut.
|
|
36946
37196
|
- THE RECALL TEST. A busy executive should be able to repeat your headline and one number to their CEO an hour later. If they couldn't, tighten it.
|
|
36947
|
-
- ALTITUDE CONTROL. Answer at the altitude asked: a high-level question gets the 30,000-ft story (one narrative sentence, three numbers max) with an offer to descend; a "how do we fix it" / plan-of-attack question prefers draft_strategy (or, if answering one play inline: one play + mechanism + what to verify \u2014 observe and recommend, never invent a multi-week Phase 1/2/3 program)
|
|
37197
|
+
- ALTITUDE CONTROL. Answer at the altitude asked: a high-level question gets the 30,000-ft story (one narrative sentence, three numbers max) with an offer to descend; a "how do we fix it" / plan-of-attack question prefers draft_strategy (or, if answering one play inline: one play + mechanism + what to verify \u2014 observe and recommend, never invent a multi-week Phase 1/2/3 program).
|
|
37198
|
+
- BRIEF MODE. When brevity is required: one headline + \u22642 drivers + one so-what. Still answer-first; never open with a scorecard.`;
|
|
36948
37199
|
}
|
|
36949
37200
|
});
|
|
36950
37201
|
|
|
@@ -36953,7 +37204,7 @@ function buildMetricsPlaybookBlock() {
|
|
|
36953
37204
|
const plays = getMetricsPlays();
|
|
36954
37205
|
return plays.map((p) => `- "${p.name}" (id: ${p.id}) \u2014 when ${p.trigger_condition}: ${p.why}`).join("\n");
|
|
36955
37206
|
}
|
|
36956
|
-
function
|
|
37207
|
+
function buildMetricsFindingsSystemPrompt() {
|
|
36957
37208
|
const profile = loadProfile();
|
|
36958
37209
|
const profileBlock = buildCompanyProfileBlock();
|
|
36959
37210
|
const motionLabel = motionBenchmarkLabel(profile?.sales_motion);
|
|
@@ -37032,7 +37283,7 @@ async function generateMetricsFindings(input, ctx) {
|
|
|
37032
37283
|
const { response, meta } = await completeWithFailover(
|
|
37033
37284
|
{
|
|
37034
37285
|
surface: "metrics_findings",
|
|
37035
|
-
system:
|
|
37286
|
+
system: buildMetricsFindingsSystemPrompt(),
|
|
37036
37287
|
messages: [{ role: "user", content: userMessage }],
|
|
37037
37288
|
max_tokens: 4096
|
|
37038
37289
|
},
|
|
@@ -39468,7 +39719,7 @@ function printHelp() {
|
|
|
39468
39719
|
console.log(" " + chalk88.dim("After analysis, type questions in English."));
|
|
39469
39720
|
console.log(" " + chalk88.dim("Type ") + paint("accent", '"how should we fix this?"') + chalk88.dim(" to make a strategy."));
|
|
39470
39721
|
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", "
|
|
39722
|
+
console.log(" " + chalk88.dim("The ") + paint("accent", "\u203A") + chalk88.dim(" prompt shows brief or deep after analysis. Brief is the default."));
|
|
39472
39723
|
console.log();
|
|
39473
39724
|
console.log(" " + sectionHeading("Shortcuts"));
|
|
39474
39725
|
const shortcuts = [
|
|
@@ -39527,6 +39778,7 @@ var init_repl = __esm({
|
|
|
39527
39778
|
init_prompt_parts();
|
|
39528
39779
|
init_store();
|
|
39529
39780
|
init_phase();
|
|
39781
|
+
init_ghost_hints();
|
|
39530
39782
|
init_recommended_action();
|
|
39531
39783
|
init_inline_suggestion();
|
|
39532
39784
|
init_loop_guard2();
|
|
@@ -39587,23 +39839,7 @@ var init_repl = __esm({
|
|
|
39587
39839
|
"May your pipeline stay hydrated.",
|
|
39588
39840
|
"Don't let the zombie deals bite."
|
|
39589
39841
|
];
|
|
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
|
-
};
|
|
39842
|
+
GHOST_HINTS = PHASE_GHOST_HINTS;
|
|
39607
39843
|
ghostHintTurn = 0;
|
|
39608
39844
|
activeGhostHint = null;
|
|
39609
39845
|
suggestionPainted = false;
|