@sonnechasser/ntrp 1.4.3 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +629 -239
- package/dist/mcp/server.js +325 -98
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -5742,8 +5742,22 @@ var init_pseudonymize = __esm({
|
|
|
5742
5742
|
});
|
|
5743
5743
|
|
|
5744
5744
|
// src/config/profile.ts
|
|
5745
|
+
var profile_exports = {};
|
|
5746
|
+
__export(profile_exports, {
|
|
5747
|
+
isProfileConfigured: () => isProfileConfigured,
|
|
5748
|
+
loadProfile: () => loadProfile,
|
|
5749
|
+
profileExists: () => profileExists,
|
|
5750
|
+
profilePath: () => profilePath,
|
|
5751
|
+
saveProfile: () => saveProfile,
|
|
5752
|
+
updateProfile: () => updateProfile
|
|
5753
|
+
});
|
|
5745
5754
|
import { readFileSync as readFileSync11, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
|
|
5746
5755
|
import { join as join12 } from "path";
|
|
5756
|
+
function ensureDir5() {
|
|
5757
|
+
if (!existsSync12(NTRP_DIR3)) {
|
|
5758
|
+
mkdirSync9(NTRP_DIR3, { recursive: true });
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5747
5761
|
function profilePath() {
|
|
5748
5762
|
return PROFILE_PATH;
|
|
5749
5763
|
}
|
|
@@ -5764,6 +5778,35 @@ function loadProfile() {
|
|
|
5764
5778
|
return null;
|
|
5765
5779
|
}
|
|
5766
5780
|
}
|
|
5781
|
+
function saveProfile(profile) {
|
|
5782
|
+
ensureDir5();
|
|
5783
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
5784
|
+
const toWrite = {
|
|
5785
|
+
...profile,
|
|
5786
|
+
schema_version: 1,
|
|
5787
|
+
created_at: profile.created_at || now2,
|
|
5788
|
+
updated_at: now2
|
|
5789
|
+
};
|
|
5790
|
+
writeFileSync10(PROFILE_PATH, JSON.stringify(toWrite, null, 2) + "\n");
|
|
5791
|
+
}
|
|
5792
|
+
function updateProfile(patch) {
|
|
5793
|
+
const existing = loadProfile();
|
|
5794
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
5795
|
+
const merged = {
|
|
5796
|
+
schema_version: 1,
|
|
5797
|
+
company_name: "",
|
|
5798
|
+
industry: "",
|
|
5799
|
+
product_description: "",
|
|
5800
|
+
target_customer: "",
|
|
5801
|
+
sales_motion: "mid_market",
|
|
5802
|
+
created_at: now2,
|
|
5803
|
+
updated_at: now2,
|
|
5804
|
+
...existing ?? {},
|
|
5805
|
+
...patch
|
|
5806
|
+
};
|
|
5807
|
+
saveProfile(merged);
|
|
5808
|
+
return merged;
|
|
5809
|
+
}
|
|
5767
5810
|
var NTRP_DIR3, PROFILE_PATH;
|
|
5768
5811
|
var init_profile = __esm({
|
|
5769
5812
|
"src/config/profile.ts"() {
|
|
@@ -8598,6 +8641,15 @@ var init_context2 = __esm({
|
|
|
8598
8641
|
});
|
|
8599
8642
|
|
|
8600
8643
|
// src/pipeline/segments.ts
|
|
8644
|
+
function resolveSegmentByName(query, segments) {
|
|
8645
|
+
const lower = query.toLowerCase();
|
|
8646
|
+
const exact = segments.find((s) => s.segment.name.toLowerCase() === lower);
|
|
8647
|
+
if (exact) return { type: "exact", segment: exact };
|
|
8648
|
+
const matches = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
|
|
8649
|
+
if (matches.length === 1) return { type: "exact", segment: matches[0] };
|
|
8650
|
+
if (matches.length > 1) return { type: "ambiguous", matches };
|
|
8651
|
+
return { type: "none" };
|
|
8652
|
+
}
|
|
8601
8653
|
function resolveSegmentScopeFromSnapshot(segment, snapshot) {
|
|
8602
8654
|
const orgIds = [];
|
|
8603
8655
|
const peopleIds = [];
|
|
@@ -8945,8 +8997,9 @@ function resolveThresholds(salesMotion, computedBaselines) {
|
|
|
8945
8997
|
return resolved;
|
|
8946
8998
|
}
|
|
8947
8999
|
async function getResolvedThresholds() {
|
|
9000
|
+
const { loadProfile: loadProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
8948
9001
|
const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_store(), store_exports));
|
|
8949
|
-
const motion = getConfigValue2("sales-motion");
|
|
9002
|
+
const motion = loadProfile2()?.sales_motion ?? getConfigValue2("sales-motion");
|
|
8950
9003
|
return resolveThresholds(motion ?? null, {});
|
|
8951
9004
|
}
|
|
8952
9005
|
var init_resolve = __esm({
|
|
@@ -10274,7 +10327,7 @@ handler: ../commands/thinkwithme.ts
|
|
|
10274
10327
|
|
|
10275
10328
|
Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
|
|
10276
10329
|
Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
|
|
10277
|
-
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.
|
|
10278
10331
|
When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
|
|
10279
10332
|
},
|
|
10280
10333
|
{
|
|
@@ -10462,6 +10515,7 @@ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
|
|
|
10462
10515
|
\`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
|
|
10463
10516
|
\`llm-primary\` (default provider), \`llm-tier\`, \`llm-auto-failover\`,
|
|
10464
10517
|
\`voice-personality\`, \`voice-roast\`,
|
|
10518
|
+
\`sales-motion\`, \`rep_hourly_cost\`, \`hours_per_activity\`,
|
|
10465
10519
|
\`default-format\`, \`export-dir\`, \`ai-inbox-dir\` (or type \`/inbox set\`).
|
|
10466
10520
|
|
|
10467
10521
|
Setting a provider key opens a hidden prompt and auto-discovers that provider's models.
|
|
@@ -11240,8 +11294,11 @@ function buildInvestigationTools() {
|
|
|
11240
11294
|
if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
|
|
11241
11295
|
return tools2;
|
|
11242
11296
|
}
|
|
11243
|
-
function buildFreshNlTools() {
|
|
11244
|
-
const
|
|
11297
|
+
function buildFreshNlTools(opts = {}) {
|
|
11298
|
+
const conversationTools = opts.analysisReady ? CONVERSATION_TOOLS.filter(
|
|
11299
|
+
(t) => t.name !== "propose_scope" && t.name !== "confirm_scope" && t.name !== "run_compute"
|
|
11300
|
+
) : CONVERSATION_TOOLS;
|
|
11301
|
+
const tools2 = [...AGENTIC_TOOLS, ...conversationTools];
|
|
11245
11302
|
if (isWebRetrievalEnabled()) tools2.push(WEB_SEARCH_TOOL);
|
|
11246
11303
|
return tools2;
|
|
11247
11304
|
}
|
|
@@ -13817,7 +13874,6 @@ __export(phase_exports, {
|
|
|
13817
13874
|
buildConversationPrompt: () => buildConversationPrompt,
|
|
13818
13875
|
consumeOrientEmptyEnterCoach: () => consumeOrientEmptyEnterCoach,
|
|
13819
13876
|
formatPhaseLabel: () => formatPhaseLabel,
|
|
13820
|
-
getConversationPhaseBlock: () => getConversationPhaseBlock,
|
|
13821
13877
|
resolveConversationPhase: () => resolveConversationPhase,
|
|
13822
13878
|
sessionHasData: () => sessionHasData
|
|
13823
13879
|
});
|
|
@@ -13857,6 +13913,8 @@ function formatPhaseLabel(phase) {
|
|
|
13857
13913
|
switch (phase) {
|
|
13858
13914
|
case "orient":
|
|
13859
13915
|
return "setup";
|
|
13916
|
+
case "awaiting_data":
|
|
13917
|
+
return "data loading";
|
|
13860
13918
|
case "explore":
|
|
13861
13919
|
return "ready to ask";
|
|
13862
13920
|
case "think":
|
|
@@ -13865,15 +13923,24 @@ function formatPhaseLabel(phase) {
|
|
|
13865
13923
|
return phase.replace(/_/g, " ");
|
|
13866
13924
|
}
|
|
13867
13925
|
}
|
|
13926
|
+
function accentPromptHead(phase, sessionName) {
|
|
13927
|
+
const scope = sessionName ? ` ${sessionName}` : "";
|
|
13928
|
+
if (phase === "orient" || phase === "awaiting_data" || phase === "explore") {
|
|
13929
|
+
if (phase === "explore" && sessionName) {
|
|
13930
|
+
return paint("accent", `\u203A`) + chalk8.dim(`${scope} `);
|
|
13931
|
+
}
|
|
13932
|
+
return paint("accent", `\u203A `);
|
|
13933
|
+
}
|
|
13934
|
+
if (phase === "compute") {
|
|
13935
|
+
return paint("accent", `\u2026 `);
|
|
13936
|
+
}
|
|
13937
|
+
const word = PROMPT_LABELS[phase].replace(" \u203A", "");
|
|
13938
|
+
return paint("accent", `${word}${scope} \u203A `);
|
|
13939
|
+
}
|
|
13868
13940
|
function buildConversationPrompt(ctx) {
|
|
13869
13941
|
const phase = resolveConversationPhase(ctx);
|
|
13870
|
-
const
|
|
13871
|
-
const scope = ctx.sessionName ? ` ${ctx.sessionName}` : "";
|
|
13942
|
+
const sessionName = ctx.sessionName ?? "";
|
|
13872
13943
|
const action = resolveRecommendedAction(ctx);
|
|
13873
|
-
if (phase === "orient") {
|
|
13874
|
-
const enterHint2 = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
|
|
13875
|
-
return paint("accent", `${label} `) + enterHint2;
|
|
13876
|
-
}
|
|
13877
13944
|
if (phase === "explore") {
|
|
13878
13945
|
const mode = defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2));
|
|
13879
13946
|
const modeTag = mode === "brief" ? "brief" : "deep";
|
|
@@ -13881,34 +13948,15 @@ function buildConversationPrompt(ctx) {
|
|
|
13881
13948
|
const strategyWait = ctx.strategistState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 strategy after /connect") : "";
|
|
13882
13949
|
const thinkWait = ctx.thinkState?.step === "awaiting_connect" ? chalk8.dim(" \xB7 think after /connect") : "";
|
|
13883
13950
|
const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
13884
|
-
return
|
|
13951
|
+
return accentPromptHead(phase, sessionName) + chalk8.dim(`${modeTag} \xB7 ${stack}`) + strategyWait + thinkWait + enterHint2 + " ";
|
|
13885
13952
|
}
|
|
13886
13953
|
if (phase === "think") {
|
|
13887
13954
|
const stack = canUseReplAi(ctx) ? formatActiveStackShort(ctx) : "no engine";
|
|
13888
13955
|
const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
|
|
13889
|
-
return
|
|
13956
|
+
return accentPromptHead(phase, sessionName) + chalk8.dim(stack) + enterHint2 + " ";
|
|
13890
13957
|
}
|
|
13891
13958
|
const enterHint = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
|
|
13892
|
-
return
|
|
13893
|
-
}
|
|
13894
|
-
function getConversationPhaseBlock(ctx) {
|
|
13895
|
-
const phase = resolveConversationPhase(ctx);
|
|
13896
|
-
const lines = [`Conversation phase: ${formatPhaseLabel(phase)}`];
|
|
13897
|
-
if (ctx.scope) {
|
|
13898
|
-
lines.push(`Intent: ${ctx.scope.intent_summary}`);
|
|
13899
|
-
lines.push(`Primary lens: ${ctx.scope.primary_lens}`);
|
|
13900
|
-
if (ctx.scope.audience) lines.push(`Audience: ${ctx.scope.audience}`);
|
|
13901
|
-
if (ctx.scope.time_horizon) lines.push(`Time horizon: ${ctx.scope.time_horizon}`);
|
|
13902
|
-
if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
|
|
13903
|
-
}
|
|
13904
|
-
if (ctx.dataset?.label) lines.push(`Dataset: ${ctx.dataset.label}`);
|
|
13905
|
-
if (ctx.gapAudit) {
|
|
13906
|
-
lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
|
|
13907
|
-
if (ctx.gapAudit.missing.length > 0) {
|
|
13908
|
-
lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
|
|
13909
|
-
}
|
|
13910
|
-
}
|
|
13911
|
-
return lines.join("\n");
|
|
13959
|
+
return accentPromptHead(phase, sessionName) + enterHint;
|
|
13912
13960
|
}
|
|
13913
13961
|
var PROMPT_LABELS;
|
|
13914
13962
|
var init_phase = __esm({
|
|
@@ -13924,9 +13972,9 @@ var init_phase = __esm({
|
|
|
13924
13972
|
PROMPT_LABELS = {
|
|
13925
13973
|
orient: "\u203A",
|
|
13926
13974
|
scope: "scope \u203A",
|
|
13927
|
-
awaiting_data: "
|
|
13975
|
+
awaiting_data: "\u203A",
|
|
13928
13976
|
compute: "\u2026",
|
|
13929
|
-
explore: "
|
|
13977
|
+
explore: "\u203A",
|
|
13930
13978
|
think: "think \u203A",
|
|
13931
13979
|
strategize: "strategy \u203A",
|
|
13932
13980
|
deliver: "ship \u203A"
|
|
@@ -15999,15 +16047,15 @@ var init_metric_definitions = __esm({
|
|
|
15999
16047
|
kind: "vital",
|
|
16000
16048
|
label: "Freshness",
|
|
16001
16049
|
group: "Vital Signs",
|
|
16002
|
-
tagline: "
|
|
16050
|
+
tagline: "Is the CRM still talking about live work \u2014 or a museum of old deals?",
|
|
16003
16051
|
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.",
|
|
16004
16052
|
formula_lines: [
|
|
16005
16053
|
"freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
|
|
16006
16054
|
"people/orgs fresh if activity within 90d",
|
|
16007
16055
|
"opps fresh if activity within 30d AND not past-due"
|
|
16008
16056
|
],
|
|
16009
|
-
meaning:
|
|
16010
|
-
expert_read:
|
|
16057
|
+
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."',
|
|
16058
|
+
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.`,
|
|
16011
16059
|
deepdive: [
|
|
16012
16060
|
"Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
|
|
16013
16061
|
'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
|
|
@@ -16037,15 +16085,15 @@ var init_metric_definitions = __esm({
|
|
|
16037
16085
|
kind: "vital",
|
|
16038
16086
|
label: "Flow Rate",
|
|
16039
16087
|
group: "Vital Signs",
|
|
16040
|
-
tagline: "
|
|
16088
|
+
tagline: "Where does deal motion slow \u2014 and what's stuck behind that bottleneck?",
|
|
16041
16089
|
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.",
|
|
16042
16090
|
formula_lines: [
|
|
16043
16091
|
"base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
|
|
16044
16092
|
"score = base \u2212 stuckSharePenalty (\u226420)",
|
|
16045
16093
|
"stuck = no update > stuck_days OR past-due close"
|
|
16046
16094
|
],
|
|
16047
|
-
meaning:
|
|
16048
|
-
expert_read: "
|
|
16095
|
+
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."',
|
|
16096
|
+
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.",
|
|
16049
16097
|
deepdive: [
|
|
16050
16098
|
"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.",
|
|
16051
16099
|
'Dollar translation: sum of amount on stuck deals \u2192 "stuck in pipeline".',
|
|
@@ -16077,15 +16125,15 @@ var init_metric_definitions = __esm({
|
|
|
16077
16125
|
kind: "vital",
|
|
16078
16126
|
label: "Drop Rate",
|
|
16079
16127
|
group: "Vital Signs",
|
|
16080
|
-
tagline: "Where
|
|
16128
|
+
tagline: "Where does paid demand fall between systems before anyone works it?",
|
|
16081
16129
|
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.",
|
|
16082
16130
|
formula_lines: [
|
|
16083
16131
|
"score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
|
|
16084
16132
|
"cross-system = marketing people also in sales CRM",
|
|
16085
16133
|
"abandoned = open opps with no activity in 30d"
|
|
16086
16134
|
],
|
|
16087
|
-
meaning:
|
|
16088
|
-
expert_read:
|
|
16135
|
+
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."',
|
|
16136
|
+
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.',
|
|
16089
16137
|
deepdive: [
|
|
16090
16138
|
"Status bands: green 80 or more, yellow 60 or more, red below 60.",
|
|
16091
16139
|
'Dollar translation: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
|
|
@@ -16116,15 +16164,15 @@ var init_metric_definitions = __esm({
|
|
|
16116
16164
|
kind: "vital",
|
|
16117
16165
|
label: "Signal:Noise",
|
|
16118
16166
|
group: "Vital Signs",
|
|
16119
|
-
tagline: "How much
|
|
16167
|
+
tagline: "How much of the team's effort still aims at deals that can close?",
|
|
16120
16168
|
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.",
|
|
16121
16169
|
formula_lines: [
|
|
16122
16170
|
"score = (signalCount / activityCount) \xD7 100",
|
|
16123
16171
|
"signal = linked to open opp / pipeline person / pipeline org",
|
|
16124
16172
|
"lookback = trailing 90 days"
|
|
16125
16173
|
],
|
|
16126
|
-
meaning:
|
|
16127
|
-
expert_read: "
|
|
16174
|
+
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.`,
|
|
16175
|
+
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.",
|
|
16128
16176
|
deepdive: [
|
|
16129
16177
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
16130
16178
|
"Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost).",
|
|
@@ -16153,15 +16201,15 @@ var init_metric_definitions = __esm({
|
|
|
16153
16201
|
kind: "vital",
|
|
16154
16202
|
label: "Thread Depth",
|
|
16155
16203
|
group: "Vital Signs",
|
|
16156
|
-
tagline: "How fragile is
|
|
16204
|
+
tagline: "How fragile is revenue if one champion changes jobs?",
|
|
16157
16205
|
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.",
|
|
16158
16206
|
formula_lines: [
|
|
16159
16207
|
"score = % open deals with \u22652 active people (90d)",
|
|
16160
16208
|
"people counted via opp contacts + same-org activity",
|
|
16161
16209
|
"threshold configurable (default 2)"
|
|
16162
16210
|
],
|
|
16163
|
-
meaning: "
|
|
16164
|
-
expert_read: "Weight by deal size
|
|
16211
|
+
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.",
|
|
16212
|
+
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.",
|
|
16165
16213
|
deepdive: [
|
|
16166
16214
|
"Status bands: green 65 or more, yellow 40 or more, red below 40.",
|
|
16167
16215
|
'Dollar translation: sum of amount on single-threaded deals \u2192 "single-threaded".',
|
|
@@ -16193,14 +16241,14 @@ var init_metric_definitions = __esm({
|
|
|
16193
16241
|
kind: "saas",
|
|
16194
16242
|
label: "ARR",
|
|
16195
16243
|
group: "Revenue",
|
|
16196
|
-
tagline: "How big is the
|
|
16244
|
+
tagline: "How big is the engine \u2014 and what's the mix that got it there?",
|
|
16197
16245
|
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.",
|
|
16198
16246
|
formula_lines: [
|
|
16199
16247
|
"ARR \u2248 \u03A3 amount on closed-won opportunities",
|
|
16200
16248
|
"New + Expansion = growth \xB7 Churned + Contraction = leakage"
|
|
16201
16249
|
],
|
|
16202
|
-
meaning: "
|
|
16203
|
-
expert_read: "Always decompose growth into new versus expansion
|
|
16250
|
+
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.",
|
|
16251
|
+
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.",
|
|
16204
16252
|
deepdive: [
|
|
16205
16253
|
"Companion metrics: New ARR, Expansion ARR, Churned ARR, Contraction ARR.",
|
|
16206
16254
|
"Estimation method may be ledger, pipeline_inferred, or snapshot. Read confidence and reliability_gate.",
|
|
@@ -16353,15 +16401,15 @@ var init_metric_definitions = __esm({
|
|
|
16353
16401
|
kind: "saas",
|
|
16354
16402
|
label: "Net Revenue Retention",
|
|
16355
16403
|
group: "Retention",
|
|
16356
|
-
tagline: "Would
|
|
16404
|
+
tagline: "Would the base still grow if new logo sales paused?",
|
|
16357
16405
|
how_computed: "startingArr = ARR + Churned + Contraction \u2212 Expansion. NRR = ((starting \u2212 Churned \u2212 Contraction + Expansion) / starting) \xD7 100.",
|
|
16358
16406
|
formula_lines: [
|
|
16359
16407
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
16360
16408
|
"NRR = (starting \u2212 churned \u2212 contraction + expansion) / starting \xD7 100",
|
|
16361
16409
|
"NRR = 100% + expansion% \u2212 contraction% \u2212 churn%"
|
|
16362
16410
|
],
|
|
16363
|
-
meaning: "
|
|
16364
|
-
expert_read: "Decompose before you judge
|
|
16411
|
+
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.",
|
|
16412
|
+
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.",
|
|
16365
16413
|
deepdive: [
|
|
16366
16414
|
"Always show the waterfall: +expansion \u2212contraction \u2212churn.",
|
|
16367
16415
|
"GRR is the floor. NRR adds expansion on top.",
|
|
@@ -16389,14 +16437,14 @@ var init_metric_definitions = __esm({
|
|
|
16389
16437
|
kind: "saas",
|
|
16390
16438
|
label: "Gross Revenue Retention",
|
|
16391
16439
|
group: "Retention",
|
|
16392
|
-
tagline: "How leaky is the bucket before expansion
|
|
16440
|
+
tagline: "How leaky is the bucket before expansion can hide it?",
|
|
16393
16441
|
how_computed: "GRR = ((startingArr \u2212 Churned \u2212 Contraction) / startingArr) \xD7 100. Expansion is excluded on purpose.",
|
|
16394
16442
|
formula_lines: [
|
|
16395
16443
|
"starting = ARR + churned + contraction \u2212 expansion",
|
|
16396
16444
|
"GRR = (starting \u2212 churned \u2212 contraction) / starting \xD7 100"
|
|
16397
16445
|
],
|
|
16398
|
-
meaning: "
|
|
16399
|
-
expert_read: "GRR
|
|
16446
|
+
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.",
|
|
16447
|
+
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.",
|
|
16400
16448
|
deepdive: [
|
|
16401
16449
|
"GRR never includes Expansion. That is the point.",
|
|
16402
16450
|
"Owners: product and CS for churn. Packaging for contraction."
|
|
@@ -16419,14 +16467,14 @@ var init_metric_definitions = __esm({
|
|
|
16419
16467
|
kind: "saas",
|
|
16420
16468
|
label: "Pipeline Coverage",
|
|
16421
16469
|
group: "Pipeline",
|
|
16422
|
-
tagline: "
|
|
16470
|
+
tagline: "Does next quarter already have enough real pipeline?",
|
|
16423
16471
|
how_computed: "Open pipeline amount divided by trailing-90-day closed-won amount.",
|
|
16424
16472
|
formula_lines: [
|
|
16425
16473
|
"Coverage = openPipeline / trailing_90d_won",
|
|
16426
16474
|
"required \u2248 1 / win_rate (discount for time left)"
|
|
16427
16475
|
],
|
|
16428
|
-
meaning:
|
|
16429
|
-
expert_read: "
|
|
16476
|
+
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.`,
|
|
16477
|
+
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.",
|
|
16430
16478
|
deepdive: [
|
|
16431
16479
|
"Always pair with Win Rate and Freshness.",
|
|
16432
16480
|
"Weighted Pipeline is the credibility-adjusted companion."
|
|
@@ -16511,14 +16559,14 @@ var init_metric_definitions = __esm({
|
|
|
16511
16559
|
kind: "saas",
|
|
16512
16560
|
label: "Pipeline Velocity",
|
|
16513
16561
|
group: "Pipeline",
|
|
16514
|
-
tagline: "
|
|
16562
|
+
tagline: "Four levers, one throughput number \u2014 which lever moved?",
|
|
16515
16563
|
how_computed: "(openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays. Requires 3 or more dated closed-won deals. Unit: $/day.",
|
|
16516
16564
|
formula_lines: [
|
|
16517
16565
|
"Velocity = (openOpps \xD7 avgDeal \xD7 winRate) / avgCycleDays",
|
|
16518
16566
|
"four levers: #opps \xB7 deal size \xB7 win rate \xB7 cycle days"
|
|
16519
16567
|
],
|
|
16520
|
-
meaning: "
|
|
16521
|
-
expert_read:
|
|
16568
|
+
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.",
|
|
16569
|
+
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.',
|
|
16522
16570
|
deepdive: [
|
|
16523
16571
|
"Needs 3 or more dated wins. Otherwise this metric is unavailable.",
|
|
16524
16572
|
"Pairs with Flow Rate (cycle) and Win Rate (conversion)."
|
|
@@ -16540,11 +16588,11 @@ var init_metric_definitions = __esm({
|
|
|
16540
16588
|
kind: "saas",
|
|
16541
16589
|
label: "Win Rate",
|
|
16542
16590
|
group: "Sales Efficiency",
|
|
16543
|
-
tagline: "Of
|
|
16591
|
+
tagline: "Of the deals we decide, how often do we win?",
|
|
16544
16592
|
how_computed: "closed-won / (won + lost) \xD7 100.",
|
|
16545
16593
|
formula_lines: ["Win Rate = won / (won + lost) \xD7 100"],
|
|
16546
|
-
meaning: "
|
|
16547
|
-
expert_read: "A rising win rate on falling opportunity volume
|
|
16594
|
+
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).",
|
|
16595
|
+
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.",
|
|
16548
16596
|
deepdive: [
|
|
16549
16597
|
"Required coverage is about 1 / win rate.",
|
|
16550
16598
|
"Cut by segment or source before company-wide coaching."
|
|
@@ -18759,6 +18807,170 @@ var init_strategist_flow = __esm({
|
|
|
18759
18807
|
}
|
|
18760
18808
|
});
|
|
18761
18809
|
|
|
18810
|
+
// src/conversation/ghost-hints.ts
|
|
18811
|
+
function ghostExamplesForPhase(phase, limit = 2) {
|
|
18812
|
+
const hints = PHASE_GHOST_HINTS[phase] ?? [];
|
|
18813
|
+
return hints.slice(0, limit).map((h) => h.replace(/^try\s+/i, ""));
|
|
18814
|
+
}
|
|
18815
|
+
var PHASE_GHOST_HINTS;
|
|
18816
|
+
var init_ghost_hints = __esm({
|
|
18817
|
+
"src/conversation/ghost-hints.ts"() {
|
|
18818
|
+
"use strict";
|
|
18819
|
+
PHASE_GHOST_HINTS = {
|
|
18820
|
+
orient: [
|
|
18821
|
+
'try "pipeline health"',
|
|
18822
|
+
"try /deepdive",
|
|
18823
|
+
"try /deepdive guide",
|
|
18824
|
+
'try "is our retention real for the board?"',
|
|
18825
|
+
'try "what is the most expensive problem to solve?"',
|
|
18826
|
+
'try "board deck on Q3"'
|
|
18827
|
+
],
|
|
18828
|
+
awaiting_data: [
|
|
18829
|
+
'try "use demo data"',
|
|
18830
|
+
"try a CSV path",
|
|
18831
|
+
"try /ingest",
|
|
18832
|
+
'try "go ahead"'
|
|
18833
|
+
],
|
|
18834
|
+
explore: [
|
|
18835
|
+
'try "what is the most expensive problem?"',
|
|
18836
|
+
'try "how should we fix this?"',
|
|
18837
|
+
'try "which segment is weakest?"',
|
|
18838
|
+
'try "what is ARR?"',
|
|
18839
|
+
"try /deepdive freshness",
|
|
18840
|
+
'try "ship a board deck"'
|
|
18841
|
+
]
|
|
18842
|
+
};
|
|
18843
|
+
}
|
|
18844
|
+
});
|
|
18845
|
+
|
|
18846
|
+
// src/conversation/situation.ts
|
|
18847
|
+
function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
18848
|
+
const phase = resolveConversationPhase(ctx);
|
|
18849
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
18850
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
18851
|
+
const analysisReady = isAnalysisReady(ctx);
|
|
18852
|
+
const action = resolveRecommendedAction(ctx);
|
|
18853
|
+
const examples = ghostExamplesForPhase(
|
|
18854
|
+
phase === "think" || phase === "strategize" || phase === "deliver" ? "explore" : phase,
|
|
18855
|
+
2
|
|
18856
|
+
);
|
|
18857
|
+
const lines = [
|
|
18858
|
+
"WHERE YOU ARE IN NTRP:",
|
|
18859
|
+
FUNNEL_SPINE,
|
|
18860
|
+
`Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
|
|
18861
|
+
];
|
|
18862
|
+
if (channel === "think") {
|
|
18863
|
+
lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
|
|
18864
|
+
lines.push("Response mode: deep (tools on)");
|
|
18865
|
+
} else if (channel === "strategist") {
|
|
18866
|
+
lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
|
|
18867
|
+
} else {
|
|
18868
|
+
lines.push(
|
|
18869
|
+
responseMode === "brief" ? "Response mode: brief (tools off; cite COMPLETED SESSION ANALYSIS; ~40\u201390 words)" : "Response mode: deep (tools on; new cuts and drill-downs)"
|
|
18870
|
+
);
|
|
18871
|
+
}
|
|
18872
|
+
if (ctx.scope) {
|
|
18873
|
+
const bits = [`Scope: ${ctx.scope.intent_summary}`, `lens=${ctx.scope.primary_lens}`];
|
|
18874
|
+
if (ctx.scope.audience) bits.push(`audience=${ctx.scope.audience}`);
|
|
18875
|
+
if (ctx.scope.time_horizon) bits.push(`horizon=${ctx.scope.time_horizon}`);
|
|
18876
|
+
lines.push(bits.join(" \xB7 "));
|
|
18877
|
+
if (ctx.scope.confirmed_at) lines.push(`Scope confirmed: ${ctx.scope.confirmed_at}`);
|
|
18878
|
+
}
|
|
18879
|
+
if (ctx.dataset?.label) {
|
|
18880
|
+
lines.push(`Dataset: ${ctx.dataset.label} \xB7 stage=${ctx.stage}`);
|
|
18881
|
+
} else if (sessionHasData(ctx)) {
|
|
18882
|
+
lines.push(`Dataset: loaded \xB7 stage=${ctx.stage}`);
|
|
18883
|
+
} else {
|
|
18884
|
+
lines.push(`Dataset: empty \xB7 stage=${ctx.stage}`);
|
|
18885
|
+
}
|
|
18886
|
+
if (ctx.gapAudit) {
|
|
18887
|
+
lines.push(`Can compute: ${ctx.gapAudit.can_compute}`);
|
|
18888
|
+
if (ctx.gapAudit.missing.length > 0) {
|
|
18889
|
+
lines.push(`Data gaps: ${ctx.gapAudit.missing.map((m) => m.label).join(", ")}`);
|
|
18890
|
+
}
|
|
18891
|
+
}
|
|
18892
|
+
if (analysisReady) {
|
|
18893
|
+
lines.push(
|
|
18894
|
+
"Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
|
|
18895
|
+
);
|
|
18896
|
+
lines.push(
|
|
18897
|
+
"Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /deepdive <metric>, /thinkwithme, /playbook"
|
|
18898
|
+
);
|
|
18899
|
+
if (channel === "explore" && responseMode === "deep") {
|
|
18900
|
+
lines.push(
|
|
18901
|
+
"Tools: diagnostics + draft_strategy + draft_handoff (+ audit_data_gaps for re-check). Scope/compute orchestration is off."
|
|
18902
|
+
);
|
|
18903
|
+
}
|
|
18904
|
+
} else if (phase === "awaiting_data") {
|
|
18905
|
+
lines.push("Already done: scope confirmed; analysis not ready");
|
|
18906
|
+
lines.push(
|
|
18907
|
+
'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'
|
|
18908
|
+
);
|
|
18909
|
+
} else if (phase === "scope") {
|
|
18910
|
+
lines.push("Already done: intent proposed; waiting on confirm");
|
|
18911
|
+
lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
|
|
18912
|
+
} else if (phase === "orient") {
|
|
18913
|
+
lines.push("Already done: nothing locked yet");
|
|
18914
|
+
lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
|
|
18915
|
+
} else if (phase === "think") {
|
|
18916
|
+
lines.push("Already done: analysis complete; think channel open");
|
|
18917
|
+
lines.push("Available now: socratic exploration; draft_strategy / draft_handoff when ready to graduate");
|
|
18918
|
+
}
|
|
18919
|
+
lines.push(
|
|
18920
|
+
`Armed user next step: ${action ? `${action.hint} (\u23CE submits "${action.submit}")` : "none"}`
|
|
18921
|
+
);
|
|
18922
|
+
lines.push(`Engine connected: ${canUseReplAi(ctx) ? "yes" : "no"}`);
|
|
18923
|
+
if (examples.length > 0) {
|
|
18924
|
+
lines.push(`User may try: ${examples.join(" \xB7 ")}`);
|
|
18925
|
+
}
|
|
18926
|
+
lines.push(
|
|
18927
|
+
"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"
|
|
18928
|
+
);
|
|
18929
|
+
lines.push(
|
|
18930
|
+
"Pre-explore funnel gates are handled by the CLI; you speak in explore/think/strategist unless tools explicitly allow a restart"
|
|
18931
|
+
);
|
|
18932
|
+
return lines.join("\n");
|
|
18933
|
+
}
|
|
18934
|
+
function buildSituationalRuntimeFacts(ctx, opts = {}) {
|
|
18935
|
+
const phase = resolveConversationPhase(ctx);
|
|
18936
|
+
const channel = opts.channel ?? (phase === "think" ? "think" : "explore");
|
|
18937
|
+
const responseMode = opts.responseMode ?? (channel === "think" ? "deep" : defaultExploreResponseMode(ctx, Math.floor(ctx.messages.length / 2)));
|
|
18938
|
+
return {
|
|
18939
|
+
phase,
|
|
18940
|
+
mode: responseMode,
|
|
18941
|
+
lens: ctx.scope?.primary_lens,
|
|
18942
|
+
stage: ctx.stage,
|
|
18943
|
+
can_compute: ctx.gapAudit?.can_compute === void 0 ? void 0 : String(ctx.gapAudit.can_compute),
|
|
18944
|
+
engine: canUseReplAi(ctx) ? "connected" : "none"
|
|
18945
|
+
};
|
|
18946
|
+
}
|
|
18947
|
+
function getConversationPhaseBlock(ctx, opts = {}) {
|
|
18948
|
+
return buildSituationalAwarenessBlock(ctx, opts);
|
|
18949
|
+
}
|
|
18950
|
+
var FUNNEL_SPINE, PHASE_MEANING;
|
|
18951
|
+
var init_situation = __esm({
|
|
18952
|
+
"src/conversation/situation.ts"() {
|
|
18953
|
+
"use strict";
|
|
18954
|
+
init_context2();
|
|
18955
|
+
init_explore_mode();
|
|
18956
|
+
init_repl_api();
|
|
18957
|
+
init_ghost_hints();
|
|
18958
|
+
init_recommended_action();
|
|
18959
|
+
init_phase();
|
|
18960
|
+
FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
|
|
18961
|
+
PHASE_MEANING = {
|
|
18962
|
+
orient: "setup \u2014 open chat; user has not locked a scope yet",
|
|
18963
|
+
scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
|
|
18964
|
+
awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
|
|
18965
|
+
compute: "compute in progress \u2014 wait",
|
|
18966
|
+
explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
|
|
18967
|
+
think: "socratic think channel \u2014 return to \u203A via done/cancel",
|
|
18968
|
+
strategize: "strategist flow \u2014 objective confirm or plan drafting",
|
|
18969
|
+
deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
|
|
18970
|
+
};
|
|
18971
|
+
}
|
|
18972
|
+
});
|
|
18973
|
+
|
|
18762
18974
|
// src/services/think.ts
|
|
18763
18975
|
var think_exports = {};
|
|
18764
18976
|
__export(think_exports, {
|
|
@@ -18798,7 +19010,7 @@ async function runThinkTurn(input, ctx) {
|
|
|
18798
19010
|
setAgentContext(ctx);
|
|
18799
19011
|
try {
|
|
18800
19012
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
18801
|
-
const conversationBlock = getConversationPhaseBlock(ctx);
|
|
19013
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { channel: "think", responseMode: "deep" });
|
|
18802
19014
|
const bundle = await loadSessionAnalysisBundle();
|
|
18803
19015
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
18804
19016
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
@@ -18885,7 +19097,7 @@ var init_think = __esm({
|
|
|
18885
19097
|
"use strict";
|
|
18886
19098
|
init_spinner();
|
|
18887
19099
|
init_context2();
|
|
18888
|
-
|
|
19100
|
+
init_situation();
|
|
18889
19101
|
init_agent_context();
|
|
18890
19102
|
init_agentic_loop();
|
|
18891
19103
|
init_thread();
|
|
@@ -18949,7 +19161,7 @@ function printChannelIntro(seed) {
|
|
|
18949
19161
|
console.log(
|
|
18950
19162
|
" " + chalk19.dim(
|
|
18951
19163
|
"Socratic channel \u2014 explore, challenge assumptions, pull evidence. Type "
|
|
18952
|
-
) + chalk19.cyan("done") + chalk19.dim(" or ") + chalk19.cyan("cancel") + chalk19.dim(" to return to
|
|
19164
|
+
) + chalk19.cyan("done") + chalk19.dim(" or ") + chalk19.cyan("cancel") + chalk19.dim(" to return to \u203A.")
|
|
18953
19165
|
);
|
|
18954
19166
|
if (seed) {
|
|
18955
19167
|
console.log(" " + chalk19.dim("Seed: ") + seed);
|
|
@@ -19035,7 +19247,7 @@ function clearThinkFlow(ctx, reason) {
|
|
|
19035
19247
|
console.log();
|
|
19036
19248
|
console.log(
|
|
19037
19249
|
" " + chalk19.dim(
|
|
19038
|
-
reason === "done" ? "Think channel closed. Back to
|
|
19250
|
+
reason === "done" ? "Think channel closed. Back to \u203A." : "Think session cancelled. Continue exploration."
|
|
19039
19251
|
)
|
|
19040
19252
|
);
|
|
19041
19253
|
console.log();
|
|
@@ -20117,10 +20329,10 @@ async function runNaturalLanguage(input, ctx) {
|
|
|
20117
20329
|
setAgentContext(ctx);
|
|
20118
20330
|
try {
|
|
20119
20331
|
const analysisBlock = buildAnalysisBlock(ctx);
|
|
20120
|
-
const
|
|
20332
|
+
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
20333
|
+
const conversationBlock = getConversationPhaseBlock(ctx, { responseMode });
|
|
20121
20334
|
const bundle = await loadSessionAnalysisBundle();
|
|
20122
20335
|
const sessionArtifact = hasAnyAnalysis(bundle) ? buildExploreContextBlock(bundle, ctx) : "";
|
|
20123
|
-
const responseMode = resolveExploreResponseMode(input, ctx, ctx.conversation.length);
|
|
20124
20336
|
for await (const event of agenticFindings(snapshot, ctx.snapshot.divergences, {
|
|
20125
20337
|
mode: "fresh",
|
|
20126
20338
|
userQuestion: input,
|
|
@@ -20218,6 +20430,7 @@ var init_nl = __esm({
|
|
|
20218
20430
|
"use strict";
|
|
20219
20431
|
init_spinner();
|
|
20220
20432
|
init_context2();
|
|
20433
|
+
init_situation();
|
|
20221
20434
|
init_phase();
|
|
20222
20435
|
init_agent_context();
|
|
20223
20436
|
init_orchestrator();
|
|
@@ -23291,7 +23504,7 @@ var init_scenario_fit = __esm({
|
|
|
23291
23504
|
import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, existsSync as existsSync22, mkdirSync as mkdirSync13, unlinkSync as unlinkSync3 } from "fs";
|
|
23292
23505
|
import { homedir as homedir7 } from "os";
|
|
23293
23506
|
import { join as join24 } from "path";
|
|
23294
|
-
function
|
|
23507
|
+
function ensureDir6() {
|
|
23295
23508
|
if (!existsSync22(NTRP_DIR4)) {
|
|
23296
23509
|
mkdirSync13(NTRP_DIR4, { recursive: true });
|
|
23297
23510
|
}
|
|
@@ -23308,7 +23521,7 @@ function loadCachedTaxonomy(profile) {
|
|
|
23308
23521
|
}
|
|
23309
23522
|
}
|
|
23310
23523
|
function saveCachedTaxonomy(taxonomy) {
|
|
23311
|
-
|
|
23524
|
+
ensureDir6();
|
|
23312
23525
|
writeFileSync15(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
23313
23526
|
}
|
|
23314
23527
|
var NTRP_DIR4, TAXONOMY_PATH;
|
|
@@ -25445,12 +25658,12 @@ Cite numbers from here; call tools when you need a new cut or to pressure-test a
|
|
|
25445
25658
|
|
|
25446
25659
|
` : "";
|
|
25447
25660
|
const conversationSection = opts.conversationBlock ? `
|
|
25448
|
-
CONVERSATION STATE:
|
|
25449
25661
|
${opts.conversationBlock}
|
|
25450
25662
|
|
|
25451
25663
|
` : "";
|
|
25452
25664
|
const scratchSection = buildScratchBlock(opts.thinkState);
|
|
25453
25665
|
const socraticCraft = `HOW YOU THINK WITH THE USER (socratic partner \u2014 not a search box, not a strategist):
|
|
25666
|
+
- Pre-explore funnel gates are handled by the CLI; you speak in the think channel after analysis.
|
|
25454
25667
|
- You are a thinking partner. Prefer sharp questions that unlock the user's idea over dumping answers.
|
|
25455
25668
|
- Protect imagination: give "what if" space before the smell-test \u2014 do not kill creativity in the first sentence.
|
|
25456
25669
|
- Steelman the user's position before you attack it; then deliver one hard pushback grounded in evidence when possible.
|
|
@@ -25484,7 +25697,7 @@ ${buildCommandCatalogBlock()}
|
|
|
25484
25697
|
COMMAND SUGGESTION RULES:
|
|
25485
25698
|
- Suggest at most ONE command when it adds capability beyond your answer (e.g. \`/strategy\`, \`/handoff\`).
|
|
25486
25699
|
- Never claim a command was run. Never invent flags.
|
|
25487
|
-
- Type \`done\` or \`cancel\` leaves the think channel back to
|
|
25700
|
+
- Type \`done\` or \`cancel\` leaves the think channel back to \u203A.`;
|
|
25488
25701
|
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.
|
|
25489
25702
|
|
|
25490
25703
|
${companyContextSection3()}${operatorSection3()}${socraticCraft}
|
|
@@ -25534,7 +25747,7 @@ ${SAFETY_BLOCK}`;
|
|
|
25534
25747
|
const dynamic = [
|
|
25535
25748
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
25536
25749
|
...dynamicSections,
|
|
25537
|
-
buildRuntimeBlock()
|
|
25750
|
+
buildRuntimeBlock(opts.runtimeFacts ?? {})
|
|
25538
25751
|
].join("\n\n");
|
|
25539
25752
|
return { stable, dynamic };
|
|
25540
25753
|
}
|
|
@@ -25651,12 +25864,11 @@ The user already received the full report in the terminal. Answer follow-ups by
|
|
|
25651
25864
|
|
|
25652
25865
|
` : "";
|
|
25653
25866
|
const conversationSection = conversationBlock ? `
|
|
25654
|
-
CONVERSATION STATE:
|
|
25655
25867
|
${conversationBlock}
|
|
25656
|
-
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.
|
|
25657
25868
|
|
|
25658
25869
|
` : "";
|
|
25659
25870
|
const conversationRules = `HOW A GREAT ANALYST CARRIES A CONVERSATION (read carefully \u2014 this is what separates you from a search box):
|
|
25871
|
+
- 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.
|
|
25660
25872
|
- 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.
|
|
25661
25873
|
- 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.
|
|
25662
25874
|
- 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.
|
|
@@ -25665,6 +25877,9 @@ Do not run compute until audit_data_gaps reports can_compute. If the user needs
|
|
|
25665
25877
|
- 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.`;
|
|
25666
25878
|
const briefJob = `YOUR JOB (BRIEF MODE \u2014 default after analysis is complete):
|
|
25667
25879
|
- 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.
|
|
25880
|
+
- Lead with the single most expensive or most actionable implication from the completed session analysis \u2014 not a scorecard restatement.
|
|
25881
|
+
- Do not re-list all five vital signs unless the user asked for that inventory.
|
|
25882
|
+
- Prefer naming one playbook play or one next cut over a generic "want me to dig deeper?" close.
|
|
25668
25883
|
- 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.
|
|
25669
25884
|
- 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.
|
|
25670
25885
|
- Name a playbook play only when the user asks what to do or which action to take.
|
|
@@ -25784,7 +25999,7 @@ ${briefCommandSection}` : "";
|
|
|
25784
25999
|
const dynamic = [
|
|
25785
26000
|
"SESSION STATE (current \u2014 changes as the session progresses):",
|
|
25786
26001
|
...dynamicSections,
|
|
25787
|
-
buildRuntimeBlock()
|
|
26002
|
+
buildRuntimeBlock(promptOptions.runtimeFacts ?? {})
|
|
25788
26003
|
].join("\n\n");
|
|
25789
26004
|
return { stable, dynamic };
|
|
25790
26005
|
}
|
|
@@ -25816,15 +26031,27 @@ async function* agenticFindings(computeResult, divergences, options) {
|
|
|
25816
26031
|
analysisBlock: options.analysisBlock,
|
|
25817
26032
|
conversationBlock: options.conversationBlock,
|
|
25818
26033
|
sessionArtifact: options.sessionArtifact,
|
|
25819
|
-
thinkState: options.ctx.thinkState
|
|
26034
|
+
thinkState: options.ctx.thinkState,
|
|
26035
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
26036
|
+
channel: "think",
|
|
26037
|
+
responseMode: "deep"
|
|
26038
|
+
})
|
|
25820
26039
|
}) : mode === "fresh" ? buildFreshNlSystemPrompt(
|
|
25821
26040
|
options.sessionContext,
|
|
25822
26041
|
options.memoryBlock,
|
|
25823
26042
|
options.analysisBlock,
|
|
25824
26043
|
options.conversationBlock,
|
|
25825
|
-
{
|
|
26044
|
+
{
|
|
26045
|
+
responseMode,
|
|
26046
|
+
sessionArtifact: options.sessionArtifact,
|
|
26047
|
+
experiment,
|
|
26048
|
+
runtimeFacts: buildSituationalRuntimeFacts(options.ctx, {
|
|
26049
|
+
responseMode
|
|
26050
|
+
})
|
|
26051
|
+
}
|
|
25826
26052
|
) : buildSystemPrompt2();
|
|
25827
|
-
const
|
|
26053
|
+
const analysisReady = isAnalysisReady(options.ctx);
|
|
26054
|
+
const tools2 = useTools ? mode === "think" ? buildThinkTools() : mode === "fresh" ? buildFreshNlTools({ analysisReady }) : buildInvestigationTools() : [];
|
|
25828
26055
|
const toolCtx = { computeResult, divergences };
|
|
25829
26056
|
if (options.includeMetrics) {
|
|
25830
26057
|
try {
|
|
@@ -25993,6 +26220,8 @@ var init_agentic_loop = __esm({
|
|
|
25993
26220
|
init_prompt_parts();
|
|
25994
26221
|
init_think_prompt();
|
|
25995
26222
|
init_prompt();
|
|
26223
|
+
init_context2();
|
|
26224
|
+
init_situation();
|
|
25996
26225
|
MAX_ITERATIONS = 10;
|
|
25997
26226
|
INVESTIGATION_EVIDENCE_NUDGE_AFTER = 5;
|
|
25998
26227
|
BRIEF_MAX_TOKENS = 768;
|
|
@@ -26096,22 +26325,19 @@ async function loadReportData(segmentName) {
|
|
|
26096
26325
|
let { health, segments, findings, entityCounts } = diagnosis;
|
|
26097
26326
|
let scopedSegment = null;
|
|
26098
26327
|
if (segmentName) {
|
|
26099
|
-
const
|
|
26100
|
-
|
|
26101
|
-
|
|
26102
|
-
|
|
26103
|
-
|
|
26104
|
-
else if (subs.length > 1) {
|
|
26105
|
-
throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
|
|
26106
|
-
matches: subs.map((s) => s.segment.name)
|
|
26107
|
-
});
|
|
26108
|
-
}
|
|
26328
|
+
const resolved = resolveSegmentByName(segmentName, segments);
|
|
26329
|
+
if (resolved.type === "ambiguous") {
|
|
26330
|
+
throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
|
|
26331
|
+
matches: resolved.matches.map((s) => s.segment.name)
|
|
26332
|
+
});
|
|
26109
26333
|
}
|
|
26110
|
-
if (
|
|
26334
|
+
if (resolved.type === "none") {
|
|
26111
26335
|
throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
|
|
26112
26336
|
available_segments: segments.map((s) => s.segment.name)
|
|
26113
26337
|
});
|
|
26114
26338
|
}
|
|
26339
|
+
const match = resolved.segment;
|
|
26340
|
+
const lower = segmentName.toLowerCase();
|
|
26115
26341
|
scopedSegment = match;
|
|
26116
26342
|
health = match.result;
|
|
26117
26343
|
segments = [match];
|
|
@@ -26137,6 +26363,7 @@ var init_report = __esm({
|
|
|
26137
26363
|
init_errors2();
|
|
26138
26364
|
init_types2();
|
|
26139
26365
|
init_session_analysis();
|
|
26366
|
+
init_segments();
|
|
26140
26367
|
}
|
|
26141
26368
|
});
|
|
26142
26369
|
|
|
@@ -26187,7 +26414,7 @@ async function* streamAsk(question, ctx) {
|
|
|
26187
26414
|
sessionContext: ctx.resumedSessionSummary,
|
|
26188
26415
|
includeMetrics: true,
|
|
26189
26416
|
analysisBlock: buildAnalysisBlock(ctx),
|
|
26190
|
-
conversationBlock: getConversationPhaseBlock(ctx),
|
|
26417
|
+
conversationBlock: getConversationPhaseBlock(ctx, { responseMode }),
|
|
26191
26418
|
sessionArtifact,
|
|
26192
26419
|
responseMode,
|
|
26193
26420
|
priorMessages: ctx.conversation,
|
|
@@ -26232,7 +26459,7 @@ var init_ask = __esm({
|
|
|
26232
26459
|
init_divergence();
|
|
26233
26460
|
init_repl_api();
|
|
26234
26461
|
init_context2();
|
|
26235
|
-
|
|
26462
|
+
init_situation();
|
|
26236
26463
|
init_session_analysis();
|
|
26237
26464
|
init_smoke_protocol();
|
|
26238
26465
|
}
|