@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.
@@ -10327,7 +10327,7 @@ handler: ../commands/thinkwithme.ts
10327
10327
 
10328
10328
  Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
10329
10329
  Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
10330
- Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to ask \u203A.
10330
+ Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
10331
10331
  When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
10332
10332
  },
10333
10333
  {
@@ -10730,7 +10730,7 @@ function formatCatalogLine(meta) {
10730
10730
  const note = DESTRUCTIVE_COMMAND_NOTES[meta.name] ? ` (${DESTRUCTIVE_COMMAND_NOTES[meta.name]})` : "";
10731
10731
  return `- /${meta.name}${args} \u2014 ${meta.description}${note}`;
10732
10732
  }
10733
- 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;
10733
+ 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;
10734
10734
  var init_prompt_parts = __esm({
10735
10735
  "src/ai/prompt-parts.ts"() {
10736
10736
  "use strict";
@@ -10809,12 +10809,23 @@ Instrument trust: every metric here carries confidence and reliability_gate fiel
10809
10809
  - 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.
10810
10810
  - 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.
10811
10811
  Restraint: you diagnose and prescribe the system; you do not build it here. Name the mechanism class, not a vendor shopping list.`;
10812
+ 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.
10813
+ - END STATE. Name the precise outcome in one line. Vague goals die here.
10814
+ - STATUS QUO. How is it done today, and what is the hard limit of that approach?
10815
+ - 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.
10816
+ - 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.
10817
+ - RISKS AND PAYOFFS. State the main downside and the upside in the same breath.
10818
+ - 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.
10819
+ - TIME. Bound by sales-cycle and metric-update physics. Dates faster than evidence can appear are invalid.
10820
+ - 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.
10821
+ - KILL RULE. If newness, stake, or exams fail: do not recommend. Demote, drop, or ask one sharper question. Surviving ideas then get pyramid packaging.`;
10812
10822
  PYRAMID_OUTPUT_BLOCK = `Structure everything the way a client remembers it \u2014 pyramid, answer first:
10813
- - HEADLINE FIRST. Open with the verdict and the number in one sentence (\u226415 words where possible): what is true and what it costs. Never open with methodology or context.
10814
- - THEN THE DRIVERS. Support the headline with 2-3 distinct, non-overlapping drivers, each with its own number. If two points share a cause, merge them.
10823
+ - 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.
10824
+ - 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.
10815
10825
  - 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.
10816
10826
  - 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.
10817
- - 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).`;
10827
+ - 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).
10828
+ - BRIEF MODE. When brevity is required: one headline + \u22642 drivers + one so-what. Still answer-first; never open with a scorecard.`;
10818
10829
  }
10819
10830
  });
10820
10831
 
@@ -10902,7 +10913,7 @@ function buildFindingsFindingHint(prefs) {
10902
10913
  const voice2 = prefs ?? loadVoicePrefs();
10903
10914
  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.";
10904
10915
  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.";
10905
- return `Pyramid-shaped, 2-3 sentences: (1) HEADLINE \u2014 verdict + dollar figure in one short sentence (\u226420 words); (2) EVIDENCE \u2014 the one or two 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.`;
10916
+ 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.`;
10906
10917
  }
10907
10918
  function buildFindingsSchemaBlock(prefs) {
10908
10919
  const finding = buildFindingsFindingHint(prefs);
@@ -11294,8 +11305,11 @@ function buildInvestigationTools() {
11294
11305
  if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
11295
11306
  return tools2;
11296
11307
  }
11297
- function buildFreshNlTools() {
11298
- const tools2 = [...AGENTIC_TOOLS, ...CONVERSATION_TOOLS];
11308
+ function buildFreshNlTools(opts = {}) {
11309
+ const conversationTools = opts.analysisReady ? CONVERSATION_TOOLS.filter(
11310
+ (t) => t.name !== "propose_scope" && t.name !== "confirm_scope" && t.name !== "run_compute"
11311
+ ) : CONVERSATION_TOOLS;
11312
+ const tools2 = [...AGENTIC_TOOLS, ...conversationTools];
11299
11313
  if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
11300
11314
  return tools2;
11301
11315
  }
@@ -13871,7 +13885,6 @@ __export(phase_exports, {
13871
13885
  buildConversationPrompt: () => buildConversationPrompt,
13872
13886
  consumeOrientEmptyEnterCoach: () => consumeOrientEmptyEnterCoach,
13873
13887
  formatPhaseLabel: () => formatPhaseLabel,
13874
- getConversationPhaseBlock: () => getConversationPhaseBlock,
13875
13888
  resolveConversationPhase: () => resolveConversationPhase,
13876
13889
  sessionHasData: () => sessionHasData
13877
13890
  });
@@ -13911,6 +13924,8 @@ function formatPhaseLabel(phase) {
13911
13924
  switch (phase) {
13912
13925
  case "orient":
13913
13926
  return "setup";
13927
+ case "awaiting_data":
13928
+ return "data loading";
13914
13929
  case "explore":
13915
13930
  return "ready to ask";
13916
13931
  case "think":
@@ -13919,15 +13934,24 @@ function formatPhaseLabel(phase) {
13919
13934
  return phase.replace(/_/g, " ");
13920
13935
  }
13921
13936
  }
13937
+ function accentPromptHead(phase, sessionName) {
13938
+ const scope = sessionName ? ` ${sessionName}` : "";
13939
+ if (phase === "orient" || phase === "awaiting_data" || phase === "explore") {
13940
+ if (phase === "explore" && sessionName) {
13941
+ return paint("accent", `\u203A`) + chalk8.dim(`${scope} `);
13942
+ }
13943
+ return paint("accent", `\u203A `);
13944
+ }
13945
+ if (phase === "compute") {
13946
+ return paint("accent", `\u2026 `);
13947
+ }
13948
+ const word = PROMPT_LABELS[phase].replace(" \u203A", "");
13949
+ return paint("accent", `${word}${scope} \u203A `);
13950
+ }
13922
13951
  function buildConversationPrompt(ctx) {
13923
13952
  const phase = resolveConversationPhase(ctx);
13924
- const label = PROMPT_LABELS[phase];
13925
- const scope = ctx.sessionName ? ` ${ctx.sessionName}` : "";
13953
+ const sessionName = ctx.sessionName ?? "";
13926
13954
  const action = resolveRecommendedAction(ctx);
13927
- if (phase === "orient") {
13928
- const enterHint2 = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
13929
- return paint("accent", `${label} `) + enterHint2;
13930
- }
13931
13955
  if (phase === "explore") {
13932
13956
  const mode = defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2));
13933
13957
  const modeTag = mode === "brief" ? "brief" : "deep";
@@ -13935,34 +13959,15 @@ function buildConversationPrompt(ctx) {
13935
13959
  const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 strategy after /connect") : "";
13936
13960
  const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 think after /connect") : "";
13937
13961
  const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
13938
- return paint("accent", `ask${scope} \u203A `) + chalk8.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
13962
+ return accentPromptHead(phase, sessionName) + chalk8.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
13939
13963
  }
13940
13964
  if (phase === "think") {
13941
13965
  const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
13942
13966
  const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
13943
- return paint("accent", `think${scope} \u203A `) + chalk8.dim(stack) + enterHint2 + " ";
13967
+ return accentPromptHead(phase, sessionName) + chalk8.dim(stack) + enterHint2 + " ";
13944
13968
  }
13945
13969
  const enterHint = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
13946
- return paint("accent", `${label.replace(" \u203A", "")}${scope} \u203A `) + enterHint;
13947
- }
13948
- function getConversationPhaseBlock(ctx) {
13949
- const phase = resolveConversationPhase(ctx);
13950
- const lines = [`Conversation phase: ${formatPhaseLabel(phase)}`];
13951
- if (ctx.scope) {
13952
- lines.push(`Intent: ${ctx.scope.intent_summary}`);
13953
- lines.push(`Primary lens: ${ctx.scope.primary_lens}`);
13954
- if (ctx.scope.audience) lines.push(`Audience: ${ctx.scope.audience}`);
13955
- if (ctx.scope.time_horizon) lines.push(`Time horizon: ${ctx.scope.time_horizon}`);
13956
- if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
13957
- }
13958
- if (ctx.dataset?.label) lines.push(`Dataset: ${ctx.dataset.label}`);
13959
- if (ctx.gapAudit) {
13960
- lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
13961
- if (ctx.gapAudit.missing.length > 0) {
13962
- lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
13963
- }
13964
- }
13965
- return lines.join("\n");
13970
+ return accentPromptHead(phase, sessionName) + enterHint;
13966
13971
  }
13967
13972
  var PROMPT_LABELS;
13968
13973
  var init_phase = __esm({
@@ -13978,9 +13983,9 @@ var init_phase = __esm({
13978
13983
  PROMPT_LABELS = {
13979
13984
  orient: "\u203A",
13980
13985
  scope: "scope \u203A",
13981
- awaiting_data: "data \u203A",
13986
+ awaiting_data: "\u203A",
13982
13987
  compute: "\u2026",
13983
- explore: "ask \u203A",
13988
+ explore: "\u203A",
13984
13989
  think: "think \u203A",
13985
13990
  strategize: "strategy \u203A",
13986
13991
  deliver: "ship \u203A"
@@ -14308,7 +14313,7 @@ function buildMetricsPlaybookBlock() {
14308
14313
  const plays = getMetricsPlays();
14309
14314
  return plays.map((p) => `- "${p.name}" (id: ${p.id}) \u2014 when ${p.trigger_condition}: ${p.why}`).join("\n");
14310
14315
  }
14311
- function buildSystemPrompt() {
14316
+ function buildMetricsFindingsSystemPrompt() {
14312
14317
  const profile = loadProfile();
14313
14318
  const profileBlock = buildCompanyProfileBlock();
14314
14319
  const motionLabel = motionBenchmarkLabel(profile?.sales_motion);
@@ -14387,7 +14392,7 @@ async function generateMetricsFindings(input, ctx) {
14387
14392
  const { response, meta } = await completeWithFailover(
14388
14393
  {
14389
14394
  surface: "metrics_findings",
14390
- system: buildSystemPrompt(),
14395
+ system: buildMetricsFindingsSystemPrompt(),
14391
14396
  messages: [{ role: "user", content: userMessage }],
14392
14397
  max_tokens: 4096
14393
14398
  },
@@ -16053,15 +16058,15 @@ var init_metric_definitions = __esm({
16053
16058
  kind: "vital",
16054
16059
  label: "Freshness",
16055
16060
  group: "Vital Signs",
16056
- tagline: "Does the CRM report which records are still active?",
16061
+ tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
16057
16062
  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.",
16058
16063
  formula_lines: [
16059
16064
  "freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
16060
16065
  "people/orgs fresh if activity within 90d",
16061
16066
  "opps fresh if activity within 30d AND not past-due"
16062
16067
  ],
16063
- 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.",
16064
- 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.",
16068
+ 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."',
16069
+ 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.`,
16065
16070
  deepdive: [
16066
16071
  "Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
16067
16072
  'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
@@ -16091,15 +16096,15 @@ var init_metric_definitions = __esm({
16091
16096
  kind: "vital",
16092
16097
  label: "Flow Rate",
16093
16098
  group: "Vital Signs",
16094
- tagline: "How fast do deals move, and where do they stop?",
16099
+ tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
16095
16100
  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.",
16096
16101
  formula_lines: [
16097
16102
  "base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
16098
16103
  "score = base \u2212 stuckSharePenalty (\u226420)",
16099
16104
  "stuck = no update > stuck_days OR past-due close"
16100
16105
  ],
16101
- meaning: "Board question: is next quarter slipping because deals are stuck? Dollar value equals the amount stuck in pipeline.",
16102
- 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.",
16106
+ 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."',
16107
+ 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.",
16103
16108
  deepdive: [
16104
16109
  "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.",
16105
16110
  'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
@@ -16131,15 +16136,15 @@ var init_metric_definitions = __esm({
16131
16136
  kind: "vital",
16132
16137
  label: "Drop Rate",
16133
16138
  group: "Vital Signs",
16134
- tagline: "Where do leads drop between systems?",
16139
+ tagline: "Where does paid demand fall between systems before anyone works it?",
16135
16140
  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.",
16136
16141
  formula_lines: [
16137
16142
  "score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
16138
16143
  "cross-system = marketing people also in sales CRM",
16139
16144
  "abandoned = open opps with no activity in 30d"
16140
16145
  ],
16141
- 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.",
16142
- 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.",
16146
+ 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."',
16147
+ 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.',
16143
16148
  deepdive: [
16144
16149
  "Status bands: green 80 or more, yellow 60 or more, red below 60.",
16145
16150
  'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
@@ -16170,15 +16175,15 @@ var init_metric_definitions = __esm({
16170
16175
  kind: "vital",
16171
16176
  label: "Signal:Noise",
16172
16177
  group: "Vital Signs",
16173
- tagline: "How much activity is aimed at deals that can still close?",
16178
+ tagline: "How much of the team's effort still aims at deals that can close?",
16174
16179
  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.",
16175
16180
  formula_lines: [
16176
16181
  "score = (signalCount / activityCount) \xD7 100",
16177
16182
  "signal = linked to open opp / pipeline person / pipeline org",
16178
16183
  "lookback = trailing 90 days"
16179
16184
  ],
16180
- meaning: "Board question: are we burning capacity on dead accounts? Dollar value = noiseCount \xD7 hours_per_activity \xD7 rep_hourly_cost \u2014 misdirected effort.",
16181
- 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.",
16185
+ 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.`,
16186
+ 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.",
16182
16187
  deepdive: [
16183
16188
  "Status bands: green 65 or more, yellow 40 or more, red below 40.",
16184
16189
  "Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
@@ -16207,15 +16212,15 @@ var init_metric_definitions = __esm({
16207
16212
  kind: "vital",
16208
16213
  label: "Thread Depth",
16209
16214
  group: "Vital Signs",
16210
- tagline: "How fragile is the pipeline if one champion stops?",
16215
+ tagline: "How fragile is revenue if one champion changes jobs?",
16211
16216
  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.",
16212
16217
  formula_lines: [
16213
16218
  "score = % open deals with \u22652 active people (90d)",
16214
16219
  "people counted via opp contacts + same-org activity",
16215
16220
  "threshold configurable (default 2)"
16216
16221
  ],
16217
- meaning: "Board question: how much revenue is lost if one contact changes jobs? Dollar value equals the sum of amount on single-threaded deals.",
16218
- 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.",
16222
+ 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.",
16223
+ 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.",
16219
16224
  deepdive: [
16220
16225
  "Status bands: green 65 or more, yellow 40 or more, red below 40.",
16221
16226
  'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
@@ -16247,14 +16252,14 @@ var init_metric_definitions = __esm({
16247
16252
  kind: "saas",
16248
16253
  label: "ARR",
16249
16254
  group: "Revenue",
16250
- tagline: "How big is the revenue engine, and from where?",
16255
+ tagline: "How big is the engine \u2014 and what's the mix that got it there?",
16251
16256
  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.",
16252
16257
  formula_lines: [
16253
16258
  "ARR \u2248 \u03A3 amount on closed-won opportunities",
16254
16259
  "New + Expansion = growth \xB7 Churned + Contraction = leakage"
16255
16260
  ],
16256
- meaning: "Board question: how fast are we growing, and from where? Always decompose growth into new versus expansion. The mix is the story.",
16257
- 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.",
16261
+ 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.",
16262
+ 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.",
16258
16263
  deepdive: [
16259
16264
  "Companion metrics: New ARR, Expansion ARR, Churned ARR, Contraction ARR.",
16260
16265
  "Estimation method may be ledger, pipeline_inferred, or snapshot. Read confidence and reliability_gate.",
@@ -16407,15 +16412,15 @@ var init_metric_definitions = __esm({
16407
16412
  kind: "saas",
16408
16413
  label: "Net Revenue Retention",
16409
16414
  group: "Retention",
16410
- tagline: "Would this business grow if sales stopped selling?",
16415
+ tagline: "Would the base still grow if new logo sales paused?",
16411
16416
  how_computed: "startingArr = ARR + Churned + Contraction \u2212 Expansion. NRR = ((starting \u2212 Churned \u2212 Contraction + Expansion) / starting) \xD7 100.",
16412
16417
  formula_lines: [
16413
16418
  "starting = ARR + churned + contraction \u2212 expansion",
16414
16419
  "NRR = (starting \u2212 churned \u2212 contraction + expansion) / starting \xD7 100",
16415
16420
  "NRR = 100% + expansion% \u2212 contraction% \u2212 churn%"
16416
16421
  ],
16417
- meaning: "Board question: would this business grow if sales stopped selling? A value above 100% means growth from existing customers.",
16418
- 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.",
16422
+ 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.",
16423
+ 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.",
16419
16424
  deepdive: [
16420
16425
  "Always show the waterfall: +expansion \u2212contraction \u2212churn.",
16421
16426
  "GRR is the floor. NRR adds expansion on top.",
@@ -16443,14 +16448,14 @@ var init_metric_definitions = __esm({
16443
16448
  kind: "saas",
16444
16449
  label: "Gross Revenue Retention",
16445
16450
  group: "Retention",
16446
- tagline: "How leaky is the bucket before expansion hides it?",
16451
+ tagline: "How leaky is the bucket before expansion can hide it?",
16447
16452
  how_computed: "GRR = ((startingArr \u2212 Churned \u2212 Contraction) / startingArr) \xD7 100. Expansion is excluded on purpose.",
16448
16453
  formula_lines: [
16449
16454
  "starting = ARR + churned + contraction \u2212 expansion",
16450
16455
  "GRR = (starting \u2212 churned \u2212 contraction) / starting \xD7 100"
16451
16456
  ],
16452
- meaning: "Board question: how leaky is the bucket before expansion hides it? Prior: above 90% is healthy. Above 95% is strong for enterprise.",
16453
- expert_read: "GRR is the honesty metric. Expansion can make NRR look fine while GRR erodes. Always read both.",
16457
+ 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.",
16458
+ 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.",
16454
16459
  deepdive: [
16455
16460
  "GRR never includes Expansion. That is the point.",
16456
16461
  "Owners: product and CS for churn. Packaging for contraction."
@@ -16473,14 +16478,14 @@ var init_metric_definitions = __esm({
16473
16478
  kind: "saas",
16474
16479
  label: "Pipeline Coverage",
16475
16480
  group: "Pipeline",
16476
- tagline: "Is next quarter already at risk?",
16481
+ tagline: "Does next quarter already have enough real pipeline?",
16477
16482
  how_computed: "Open pipeline amount divided by trailing-90-day closed-won amount.",
16478
16483
  formula_lines: [
16479
16484
  "Coverage = openPipeline / trailing_90d_won",
16480
16485
  "required \u2248 1 / win_rate (discount for time left)"
16481
16486
  ],
16482
- meaning: "Board question: is next quarter already at risk? Priors scale with cycle length: about 3x for velocity/SMB, 4 to 5x for enterprise.",
16483
- 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.",
16487
+ 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.`,
16488
+ 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.",
16484
16489
  deepdive: [
16485
16490
  "Always pair with Win Rate and Freshness.",
16486
16491
  "Weighted Pipeline is the credibility-adjusted companion."
@@ -16565,14 +16570,14 @@ var init_metric_definitions = __esm({
16565
16570
  kind: "saas",
16566
16571
  label: "Pipeline Velocity",
16567
16572
  group: "Pipeline",
16568
- tagline: "Revenue throughput per day. Four levers, one number.",
16573
+ tagline: "Four levers, one throughput number \u2014 which lever moved?",
16569
16574
  how_computed: "(openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays. Requires 3 or more dated closed-won deals. Unit: $/day.",
16570
16575
  formula_lines: [
16571
16576
  "Velocity = (openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays",
16572
16577
  "four levers: #opps \xB7 deal size \xB7 win rate \xB7 cycle days"
16573
16578
  ],
16574
- meaning: "Board question: which lever moved when throughput changed? This is the most decision-ready pipeline metric.",
16575
- expert_read: "When velocity changes, name which lever moved. A win-rate rise on falling opportunity volume is qualification tightening, not improvement.",
16579
+ 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.",
16580
+ 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.',
16576
16581
  deepdive: [
16577
16582
  "Needs 3 or more dated wins. Otherwise this metric is unavailable.",
16578
16583
  "Pairs with Flow Rate (cycle) and Win Rate (conversion)."
@@ -16594,11 +16599,11 @@ var init_metric_definitions = __esm({
16594
16599
  kind: "saas",
16595
16600
  label: "Win Rate",
16596
16601
  group: "Sales Efficiency",
16597
- tagline: "Of decided deals, how often do we win?",
16602
+ tagline: "Of the deals we decide, how often do we win?",
16598
16603
  how_computed: "closed-won / (won + lost) \xD7 100.",
16599
16604
  formula_lines: ["Win Rate = won / (won + lost) \xD7 100"],
16600
- meaning: "Board question: are we converting the pipeline we create? Priors: 25\u201335% SMB, 18\u201325% mid-market, 12\u201318% enterprise on qualified opportunities.",
16601
- expert_read: "A rising win rate on falling opportunity volume is qualification tightening, not improvement. Check the denominator.",
16605
+ 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).",
16606
+ 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.",
16602
16607
  deepdive: [
16603
16608
  "Required coverage is about 1 / win rate.",
16604
16609
  "Cut by segment or source before company-wide coaching."
@@ -17007,10 +17012,10 @@ function wrapForTarget(target, analysisBlock, conversationBlock, openQuestions,
17007
17012
  const contextLabel = handoffInstructionPrefix(ctx.analysis.primary);
17008
17013
  const forWhom = audiencePhrase(ctx.scope?.audience);
17009
17014
  const instructions = {
17010
- deck: `produce an executive review deck outline for ${company}, framed for ${forWhom}. Structure: (1) Headline number; (2) Pipeline health; (3) Top 3 risks with dollar impact; (4) Recommended plays; (5) Asks / decisions. Use the Metric definitions appendix so every slide can briefly remind the room what the number means for this audience.`,
17015
+ 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.`,
17011
17016
  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.`,
17012
17017
  clay: `produce a Clay table specification to operationalize the highest-impact finding for ${forWhom}.`,
17013
- plan: `produce a prioritized action plan for ${forWhom} with problem, play, first 3 steps, owner, and leading indicator per item. Ground indicators in the Metric definitions appendix.`
17018
+ 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.`
17014
17019
  };
17015
17020
  const parts = [
17016
17021
  `# NTRP handoff \u2192 ${target}`,
@@ -17405,10 +17410,13 @@ ${companyContextSection2()}${operatorSection2()}HOW YOU THINK (the strategist me
17405
17410
  8. RESPECT CAPACITY. Total effort must fit the team that actually exists. A brilliant plan the team cannot staff is a bad plan.
17406
17411
 
17407
17412
  ALTITUDE CONTRACT (the plan must work at every altitude a client reads it at):
17408
- - 30,000 FT: summary_30k is a situation-complication-resolution narrative \u2014 where the business stands, what gates what, and what leadership should expect by when. A board member reads only this; it must survive being forwarded unedited.
17413
+ - 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.
17409
17414
  - 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.
17410
17415
  - 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.
17411
17416
 
17417
+ OUTPUT DOCTRINE (narrative fields \u2014 summary_30k and workstream problem lines \u2014 follow pyramid recall):
17418
+ ${PYRAMID_OUTPUT_BLOCK}
17419
+
17412
17420
  ${buildUserVisibleProseBlock()}
17413
17421
 
17414
17422
  GTM ENGINEERING (plans install systems, not heroics):
@@ -17491,6 +17499,8 @@ function buildStressTestMessage() {
17491
17499
  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.
17492
17500
  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"?
17493
17501
  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?
17502
+ 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.
17503
+ ${HEILMEIER_GATE}
17494
17504
 
17495
17505
  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.`;
17496
17506
  }
@@ -18813,6 +18823,170 @@ var init_strategist_flow = __esm({
18813
18823
  }
18814
18824
  });
18815
18825
 
18826
+ // src/conversation/ghost-hints.ts
18827
+ function ghostExamplesForPhase(phase, limit = 2) {
18828
+ const hints = PHASE_GHOST_HINTS[phase] ?? [];
18829
+ return hints.slice(0, limit).map((h) => h.replace(/^try\s+/i, ""));
18830
+ }
18831
+ var PHASE_GHOST_HINTS;
18832
+ var init_ghost_hints = __esm({
18833
+ "src/conversation/ghost-hints.ts"() {
18834
+ "use strict";
18835
+ PHASE_GHOST_HINTS = {
18836
+ orient: [
18837
+ 'try "pipeline health"',
18838
+ "try /deepdive",
18839
+ "try /deepdive guide",
18840
+ 'try "is our retention real for the board?"',
18841
+ 'try "what is the most expensive problem to solve?"',
18842
+ 'try "board deck on Q3"'
18843
+ ],
18844
+ awaiting_data: [
18845
+ 'try "use demo data"',
18846
+ "try a CSV path",
18847
+ "try /ingest",
18848
+ 'try "go ahead"'
18849
+ ],
18850
+ explore: [
18851
+ 'try "what is the most expensive problem?"',
18852
+ 'try "how should we fix this?"',
18853
+ 'try "which segment is weakest?"',
18854
+ 'try "what is ARR?"',
18855
+ "try /deepdive freshness",
18856
+ 'try "ship a board deck"'
18857
+ ]
18858
+ };
18859
+ }
18860
+ });
18861
+
18862
+ // src/conversation/situation.ts
18863
+ function buildSituationalAwarenessBlock(ctx, opts = {}) {
18864
+ const phase = resolveConversationPhase(ctx);
18865
+ const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
18866
+ const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
18867
+ const analysisReady = isAnalysisReady(ctx);
18868
+ const action = resolveRecommendedAction(ctx);
18869
+ const examples = ghostExamplesForPhase(
18870
+ phase === "think" || phase === "strategize" || phase === "deliver" ? "explore" : phase,
18871
+ 2
18872
+ );
18873
+ const lines = [
18874
+ "WHERE YOU ARE IN NTRP:",
18875
+ FUNNEL_SPINE,
18876
+ `Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
18877
+ ];
18878
+ if (channel === "think") {
18879
+ lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
18880
+ lines.push("Response mode: deep (tools on)");
18881
+ } else if (channel === "strategist") {
18882
+ lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
18883
+ } else {
18884
+ lines.push(
18885
+ responseMode === "brief" ? "Response mode: brief (tools off; cite COMPLETED SESSION ANALYSIS; ~40\u201390 words)" : "Response mode: deep (tools on; new cuts and drill-downs)"
18886
+ );
18887
+ }
18888
+ if (ctx.scope) {
18889
+ const bits = [`Scope: ${ctx.scope.intent_summary}`, `lens=${ctx.scope.primary_lens}`];
18890
+ if (ctx.scope.audience) bits.push(`audience=${ctx.scope.audience}`);
18891
+ if (ctx.scope.time_horizon) bits.push(`horizon=${ctx.scope.time_horizon}`);
18892
+ lines.push(bits.join(" \xB7 "));
18893
+ if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
18894
+ }
18895
+ if (ctx.dataset?.label) {
18896
+ lines.push(`Dataset: ${ctx.dataset.label} \xB7 stage=${ctx.stage}`);
18897
+ } else if (sessionHasData(ctx)) {
18898
+ lines.push(`Dataset: loaded \xB7 stage=${ctx.stage}`);
18899
+ } else {
18900
+ lines.push(`Dataset: empty \xB7 stage=${ctx.stage}`);
18901
+ }
18902
+ if (ctx.gapAudit) {
18903
+ lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
18904
+ if (ctx.gapAudit.missing.length > 0) {
18905
+ lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
18906
+ }
18907
+ }
18908
+ if (analysisReady) {
18909
+ lines.push(
18910
+ "Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
18911
+ );
18912
+ lines.push(
18913
+ "Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /deepdive <metric>, /thinkwithme, /playbook"
18914
+ );
18915
+ if (channel === "explore" && responseMode === "deep") {
18916
+ lines.push(
18917
+ "Tools: diagnostics + draft_strategy + draft_handoff (+ audit_data_gaps for re-check). Scope/compute orchestration is off."
18918
+ );
18919
+ }
18920
+ } else if (phase === "awaiting_data") {
18921
+ lines.push("Already done: scope confirmed; analysis not ready");
18922
+ lines.push(
18923
+ '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'
18924
+ );
18925
+ } else if (phase === "scope") {
18926
+ lines.push("Already done: intent proposed; waiting on confirm");
18927
+ lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
18928
+ } else if (phase === "orient") {
18929
+ lines.push("Already done: nothing locked yet");
18930
+ lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
18931
+ } else if (phase === "think") {
18932
+ lines.push("Already done: analysis complete; think channel open");
18933
+ lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
18934
+ }
18935
+ lines.push(
18936
+ `Armed user next step: ${action ? `${action.hint} (\u23CE submits "${action.submit}")` : "none"}`
18937
+ );
18938
+ lines.push(`Engine connected: ${canUseReplAi(ctx) ? "yes" : "no"}`);
18939
+ if (examples.length > 0) {
18940
+ lines.push(`User may try: ${examples.join(" \xB7 ")}`);
18941
+ }
18942
+ lines.push(
18943
+ "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"
18944
+ );
18945
+ lines.push(
18946
+ "Pre-explore funnel gates are handled by the CLI; you speak in explore/think/strategist unless tools explicitly allow a restart"
18947
+ );
18948
+ return lines.join("\n");
18949
+ }
18950
+ function buildSituationalRuntimeFacts(ctx, opts = {}) {
18951
+ const phase = resolveConversationPhase(ctx);
18952
+ const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
18953
+ const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
18954
+ return {
18955
+ phase,
18956
+ mode: responseMode,
18957
+ lens: ctx.scope?.primary_lens,
18958
+ stage: ctx.stage,
18959
+ can_compute: ctx.gapAudit?.can_compute === void 0 ? void 0 : String(ctx.gapAudit.can_compute),
18960
+ engine: canUseReplAi(ctx) ? "connected" : "none"
18961
+ };
18962
+ }
18963
+ function getConversationPhaseBlock(ctx, opts = {}) {
18964
+ return buildSituationalAwarenessBlock(ctx, opts);
18965
+ }
18966
+ var FUNNEL_SPINE, PHASE_MEANING;
18967
+ var init_situation = __esm({
18968
+ "src/conversation/situation.ts"() {
18969
+ "use strict";
18970
+ init_context2();
18971
+ init_explore_mode();
18972
+ init_repl_api();
18973
+ init_ghost_hints();
18974
+ init_recommended_action();
18975
+ init_phase();
18976
+ FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
18977
+ PHASE_MEANING = {
18978
+ orient: "setup \u2014 open chat; user has not locked a scope yet",
18979
+ scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
18980
+ awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
18981
+ compute: "compute in progress \u2014 wait",
18982
+ explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
18983
+ think: "socratic think channel \u2014 return to \u203A via done/cancel",
18984
+ strategize: "strategist flow \u2014 objective confirm or plan drafting",
18985
+ deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
18986
+ };
18987
+ }
18988
+ });
18989
+
18816
18990
  // src/services/think.ts
18817
18991
  var think_exports = {};
18818
18992
  __export(think_exports, {
@@ -18852,7 +19026,7 @@ async function runThinkTurn(input, ctx) {
18852
19026
  setAgentContext(ctx);
18853
19027
  try {
18854
19028
  const analysisBlock = buildAnalysisBlock(ctx);
18855
- const conversationBlock = getConversationPhaseBlock(ctx);
19029
+ const conversationBlock = getConversationPhaseBlock(ctx, { channel: "think", responseMode: "deep" });
18856
19030
  const bundle = await loadSessionAnalysisBundle();
18857
19031
  const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
18858
19032
  for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
@@ -18939,7 +19113,7 @@ var init_think = __esm({
18939
19113
  "use strict";
18940
19114
  init_spinner();
18941
19115
  init_context2();
18942
- init_phase();
19116
+ init_situation();
18943
19117
  init_agent_context();
18944
19118
  init_agentic_loop();
18945
19119
  init_thread();
@@ -19003,7 +19177,7 @@ function printChannelIntro(seed) {
19003
19177
  console.log(
19004
19178
  " " + chalk19.dim(
19005
19179
  "Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
19006
- ) + chalk19.cyan("done") + chalk19.dim(" or ") + chalk19.cyan("cancel") + chalk19.dim(" to return to ask \u203A.")
19180
+ ) + chalk19.cyan("done") + chalk19.dim(" or ") + chalk19.cyan("cancel") + chalk19.dim(" to return to \u203A.")
19007
19181
  );
19008
19182
  if (seed) {
19009
19183
  console.log(" " + chalk19.dim("Seed: ") + seed);
@@ -19089,7 +19263,7 @@ function clearThinkFlow(ctx, reason) {
19089
19263
  console.log();
19090
19264
  console.log(
19091
19265
  " " + chalk19.dim(
19092
- reason === "done" ? "Think channel closed. Back to ask \u203A." : "Think session cancelled. Continue exploration."
19266
+ reason === "done" ? "Think channel closed. Back to \u203A." : "Think session cancelled. Continue exploration."
19093
19267
  )
19094
19268
  );
19095
19269
  console.log();
@@ -20171,10 +20345,10 @@ async function runNaturalLanguage(input, ctx) {
20171
20345
  setAgentContext(ctx);
20172
20346
  try {
20173
20347
  const analysisBlock = buildAnalysisBlock(ctx);
20174
- const conversationBlock = getConversationPhaseBlock(ctx);
20348
+ const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
20349
+ const conversationBlock = getConversationPhaseBlock(ctx, { responseMode });
20175
20350
  const bundle = await loadSessionAnalysisBundle();
20176
20351
  const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
20177
- const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
20178
20352
  for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
20179
20353
  mode: "fresh",
20180
20354
  userQuestion: input,
@@ -20272,6 +20446,7 @@ var init_nl = __esm({
20272
20446
  "use strict";
20273
20447
  init_spinner();
20274
20448
  init_context2();
20449
+ init_situation();
20275
20450
  init_phase();
20276
20451
  init_agent_context();
20277
20452
  init_orchestrator();
@@ -25499,13 +25674,14 @@ Cite numbers from here; call tools when you need a new cut or to pressure-test a
25499
25674
 
25500
25675
  ` : "";
25501
25676
  const conversationSection = opts.conversationBlock ? `
25502
- CONVERSATION STATE:
25503
25677
  ${opts.conversationBlock}
25504
25678
 
25505
25679
  ` : "";
25506
25680
  const scratchSection = buildScratchBlock(opts.thinkState);
25507
25681
  const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
25682
+ - Pre-explore funnel gates are handled by the CLI; you speak in the think channel after analysis.
25508
25683
  - You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
25684
+ - 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.
25509
25685
  - Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
25510
25686
  - Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
25511
25687
  - Ground claims about THIS pipeline in tool results. Label speculation explicitly ("hypothesis:", "speculation:").
@@ -25516,7 +25692,7 @@ ${opts.conversationBlock}
25516
25692
  - You have continuity via prior think-channel messages. Never repeat an angle already covered unless asked.`;
25517
25693
  const jobSection = `YOUR JOB (THINK CHANNEL \u2014 always deep):
25518
25694
  - Decide whether you need tools, a direct answer, or both. Do not re-call a tool whose result you already have.
25519
- - Lead with the answer or the question that matters most, then structure.
25695
+ - Probing: lead with the question that matters most. Concluding: lead with the answer (headline), then drivers and so-what.
25520
25696
  - When you use numbers, include dollar values where available and lead with financial impact.
25521
25697
  - Descriptive exploration stays in this channel; plan-of-attack questions hand off via draft_strategy.
25522
25698
  - After a successful draft_strategy tool result: reply with at most 1\u20132 sentences introducing the handoff. Do not write the plan.`;
@@ -25538,7 +25714,7 @@ ${buildCommandCatalogBlock()}
25538
25714
  COMMAND SUGGESTION RULES:
25539
25715
  - Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
25540
25716
  - Never claim a command was run. Never invent flags.
25541
- - Type \`done\` or \`cancel\` leaves the think channel back to ask \u203A.`;
25717
+ - Type \`done\` or \`cancel\` leaves the think channel back to \u203A.`;
25542
25718
  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.
25543
25719
 
25544
25720
  ${companyContextSection3()}${operatorSection3()}${socraticCraft}
@@ -25554,6 +25730,9 @@ ${EXECUTION_BIAS_BLOCK}
25554
25730
  GTM ENGINEERING (how recommendations become systems):
25555
25731
  ${GTM_ENGINEERING_BLOCK}
25556
25732
 
25733
+ IDEA GATE (when recommending a play or before draft_strategy \u2014 silent; probing/imagination turns skip this until you conclude):
25734
+ ${HEILMEIER_GATE}
25735
+
25557
25736
  OUTPUT DOCTRINE (how answers are structured for recall):
25558
25737
  ${PYRAMID_OUTPUT_BLOCK}
25559
25738
 
@@ -25588,7 +25767,7 @@ ${SAFETY_BLOCK}`;
25588
25767
  const dynamic = [
25589
25768
  "SESSION STATE (current \u2014 changes as the session progresses):",
25590
25769
  ...dynamicSections,
25591
- buildRuntimeBlock()
25770
+ buildRuntimeBlock(opts.runtimeFacts ?? {})
25592
25771
  ].join("\n\n");
25593
25772
  return { stable, dynamic };
25594
25773
  }
@@ -25620,7 +25799,7 @@ function operatorSection4() {
25620
25799
 
25621
25800
  `;
25622
25801
  }
25623
- function buildSystemPrompt2() {
25802
+ function buildSystemPrompt() {
25624
25803
  const stable = `You are an expert GTM health investigator. You have tools to query a local database of CRM and pipeline data. Your job is to investigate the health scores you've been given and discover the specific root causes behind any problems.
25625
25804
 
25626
25805
  ${companyContextSection4()}${operatorSection4()}INVESTIGATION APPROACH:
@@ -25705,12 +25884,11 @@ The user already received the full report in the terminal. Answer follow-ups by
25705
25884
 
25706
25885
  ` : "";
25707
25886
  const conversationSection = conversationBlock ? `
25708
- CONVERSATION STATE:
25709
25887
  ${conversationBlock}
25710
- 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.
25711
25888
 
25712
25889
  ` : "";
25713
25890
  const conversationRules = `HOW A GREAT ANALYST CARRIES A CONVERSATION (read carefully \u2014 this is what separates you from a search box):
25891
+ - 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.
25714
25892
  - 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.
25715
25893
  - 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.
25716
25894
  - 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.
@@ -25719,6 +25897,9 @@ Do not run compute until audit_data_gaps reports can_compute. If the user needs
25719
25897
  - 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.`;
25720
25898
  const briefJob = `YOUR JOB (BRIEF MODE \u2014 default after analysis is complete):
25721
25899
  - 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.
25900
+ - Lead with the single most expensive or most actionable implication from the completed session analysis \u2014 not a scorecard restatement.
25901
+ - Do not re-list all five vital signs unless the user asked for that inventory.
25902
+ - Prefer naming one playbook play or one next cut over a generic "want me to dig deeper?" close.
25722
25903
  - 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.
25723
25904
  - 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.
25724
25905
  - Name a playbook play only when the user asks what to do or which action to take.
@@ -25788,12 +25969,18 @@ ${commandStyleRule}`;
25788
25969
  - 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.
25789
25970
  - Include specific numbers from tool results, never guess.
25790
25971
  - When you have enough information, answer directly. Don't call tools you don't need.`;
25791
- const executionBiasSection = responseMode === "brief" ? "" : `EXECUTION BIAS (how you work):
25972
+ const executionBiasSection = responseMode === "brief" ? `OUTPUT DOCTRINE (brief \u2014 one headline + \u22642 drivers + one so-what):
25973
+ ${PYRAMID_OUTPUT_BLOCK}
25974
+
25975
+ ` : `EXECUTION BIAS (how you work):
25792
25976
  ${EXECUTION_BIAS_BLOCK}
25793
25977
 
25794
25978
  GTM ENGINEERING (how your recommendations become systems):
25795
25979
  ${GTM_ENGINEERING_BLOCK}
25796
25980
 
25981
+ IDEA GATE (when recommending action \u2014 silent):
25982
+ ${HEILMEIER_GATE}
25983
+
25797
25984
  OUTPUT DOCTRINE (how answers are structured for recall):
25798
25985
  ${PYRAMID_OUTPUT_BLOCK}
25799
25986
 
@@ -25838,7 +26025,7 @@ ${briefCommandSection}` : "";
25838
26025
  const dynamic = [
25839
26026
  "SESSION STATE (current \u2014 changes as the session progresses):",
25840
26027
  ...dynamicSections,
25841
- buildRuntimeBlock()
26028
+ buildRuntimeBlock(promptOptions.runtimeFacts ?? {})
25842
26029
  ].join("\n\n");
25843
26030
  return { stable, dynamic };
25844
26031
  }
@@ -25870,15 +26057,27 @@ async function* agenticFindings(computeResult, divergences, options) {
25870
26057
  analysisBlock: options.analysisBlock,
25871
26058
  conversationBlock: options.conversationBlock,
25872
26059
  sessionArtifact: options.sessionArtifact,
25873
- thinkState: options.ctx.thinkState
26060
+ thinkState: options.ctx.thinkState,
26061
+ runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
26062
+ channel: "think",
26063
+ responseMode: "deep"
26064
+ })
25874
26065
  }) : mode === "fresh" ? buildFreshNlSystemPrompt(
25875
26066
  options.sessionContext,
25876
26067
  options.memoryBlock,
25877
26068
  options.analysisBlock,
25878
26069
  options.conversationBlock,
25879
- { responseMode, sessionArtifact: options.sessionArtifact, experiment }
25880
- ) : buildSystemPrompt2();
25881
- const tools2 = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools() : buildInvestigationTools() : [];
26070
+ {
26071
+ responseMode,
26072
+ sessionArtifact: options.sessionArtifact,
26073
+ experiment,
26074
+ runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
26075
+ responseMode
26076
+ })
26077
+ }
26078
+ ) : buildSystemPrompt();
26079
+ const analysisReady = isAnalysisReady(options.ctx);
26080
+ const tools2 = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools({ analysisReady }) : buildInvestigationTools() : [];
25882
26081
  const toolCtx = { computeResult, divergences };
25883
26082
  if (options.includeMetrics) {
25884
26083
  try {
@@ -26047,6 +26246,8 @@ var init_agentic_loop = __esm({
26047
26246
  init_prompt_parts();
26048
26247
  init_think_prompt();
26049
26248
  init_prompt();
26249
+ init_context2();
26250
+ init_situation();
26050
26251
  MAX_ITERATIONS = 10;
26051
26252
  INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
26052
26253
  BRIEF_MAX_TOKENS = 768;
@@ -26239,7 +26440,7 @@ async function* streamAsk(question, ctx) {
26239
26440
  sessionContext: ctx.resumedSessionSummary,
26240
26441
  includeMetrics: true,
26241
26442
  analysisBlock: buildAnalysisBlock(ctx),
26242
- conversationBlock: getConversationPhaseBlock(ctx),
26443
+ conversationBlock: getConversationPhaseBlock(ctx, { responseMode }),
26243
26444
  sessionArtifact,
26244
26445
  responseMode,
26245
26446
  priorMessages: ctx.conversation,
@@ -26284,7 +26485,7 @@ var init_ask = __esm({
26284
26485
  init_divergence();
26285
26486
  init_repl_api();
26286
26487
  init_context2();
26287
- init_phase();
26488
+ init_situation();
26288
26489
  init_session_analysis();
26289
26490
  init_smoke_protocol();
26290
26491
  }