@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/mcp/server.js
CHANGED
|
@@ -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
|
|
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 (\
|
|
10814
|
-
- THEN THE DRIVERS. Support the headline with 2-3 distinct, non-overlapping
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
13967
|
+
return accentPromptHead(phase, sessionName) + chalk8.dim(stack) + enterHint2 + " ";
|
|
13944
13968
|
}
|
|
13945
13969
|
const enterHint = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
|
|
13946
|
-
return
|
|
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: "
|
|
13986
|
+
awaiting_data: "\u203A",
|
|
13982
13987
|
compute: "\u2026",
|
|
13983
|
-
explore: "
|
|
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
|
|
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:
|
|
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: "
|
|
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:
|
|
16064
|
-
expert_read:
|
|
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: "
|
|
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:
|
|
16102
|
-
expert_read: "
|
|
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
|
|
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:
|
|
16142
|
-
expert_read:
|
|
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
|
|
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:
|
|
16181
|
-
expert_read: "
|
|
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
|
|
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: "
|
|
16218
|
-
expert_read: "Weight by deal size
|
|
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
|
|
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: "
|
|
16257
|
-
expert_read: "Always decompose growth into new versus expansion
|
|
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
|
|
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: "
|
|
16418
|
-
expert_read: "Decompose before you judge
|
|
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
|
|
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: "
|
|
16453
|
-
expert_read: "GRR
|
|
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: "
|
|
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:
|
|
16483
|
-
expert_read: "
|
|
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: "
|
|
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: "
|
|
16575
|
-
expert_read:
|
|
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
|
|
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: "
|
|
16601
|
-
expert_read: "A rising win rate on falling opportunity volume
|
|
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}.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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" ?
|
|
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
|
-
{
|
|
25880
|
-
|
|
25881
|
-
|
|
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
|
-
|
|
26488
|
+
init_situation();
|
|
26288
26489
|
init_session_analysis();
|
|
26289
26490
|
init_smoke_protocol();
|
|
26290
26491
|
}
|