@sonnechasser/ntrp 2.2.0 → 2.2.2
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 +21741 -22121
- package/dist/mcp/server.js +301 -597
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -1088,7 +1088,7 @@ import chalk3 from "chalk";
|
|
|
1088
1088
|
function printDeepdiveHint(metricId, label) {
|
|
1089
1089
|
const name = label ?? metricId;
|
|
1090
1090
|
console.log(
|
|
1091
|
-
" " + chalk3.dim("How this number works: ") + paint("accent", `/
|
|
1091
|
+
" " + chalk3.dim("How this number works: ") + paint("accent", `/tour ${metricId}`) + chalk3.dim(` \u2014 ${name}`)
|
|
1092
1092
|
);
|
|
1093
1093
|
console.log();
|
|
1094
1094
|
}
|
|
@@ -12296,145 +12296,42 @@ var init_packs = __esm({
|
|
|
12296
12296
|
SILENT = "Never name methodology brands in customer-visible text. Apply the moves; keep labels silent.";
|
|
12297
12297
|
COUNSEL_PACKS = [
|
|
12298
12298
|
{
|
|
12299
|
-
id: "
|
|
12300
|
-
catalog_line: "-
|
|
12301
|
-
internal_name: "
|
|
12302
|
-
vital_links: [
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
"
|
|
12308
|
-
"
|
|
12309
|
-
"
|
|
12310
|
-
"No invented reorgs or named people \u2014 function owners only.",
|
|
12311
|
-
"Do not kill zombies first unless freshness/zombies are the gating constraint."
|
|
12312
|
-
],
|
|
12313
|
-
silent_brand_rule: SILENT,
|
|
12314
|
-
role_card_ids: ["vp_sales", "sales_manager", "ae", "sdr", "se", "sales_ops"],
|
|
12315
|
-
tree_weight: { plg: 0.5, smb_velocity: 1, mid_market: 1, enterprise: 1 },
|
|
12316
|
-
body: `THINK: Conversion physics \u2014 coverage vs win rate vs cycle vs noise. Structure (pod, hunter/farmer, overlay, SE pool) explains capacity, not the first fix. Inspection beats activity. Forecast fiction is a credibility problem before it is a revenue problem.
|
|
12317
|
-
ASK: Coverage, conversion, or cycle? Which stage clusters stuck $ ? Thread count on the largest deals? Which activities lack an opp? Where does manager commit diverge from stage evidence?
|
|
12318
|
-
CUT: stage-age, owner, segment, amount band, activity-to-opp link.
|
|
12319
|
-
DISAGREE: vs marketing \u2014 rejection reasons before "bad leads"; vs revops \u2014 keep fields that protect exit criteria, cut vanity; vs cs \u2014 oversell shows up in handoff packet; vs exec \u2014 sandbag vs stretch, use instruments.
|
|
12320
|
-
TREE: plg=assist/expand not a hunting machine. smb_velocity=SDR\u2192AE pod, speed. mid_market=balanced inspection. enterprise=SE pool, named accounts, multi-thread.
|
|
12321
|
-
CARDS: vp_sales for capacity/quota system; sales_manager for inspection; ae for deal strategy; sdr for create/accept; se only if enterprise/complex; sales_ops for territory/comp distortion.
|
|
12322
|
-
PLAYS: get_play_detail unstick-pipeline / multi-thread-deals / clean-dead-pipeline / retarget-effort / compress-sales-cycle only when that vital is the constraint.`
|
|
12323
|
-
},
|
|
12324
|
-
{
|
|
12325
|
-
id: "counsel_marketing",
|
|
12326
|
-
catalog_line: "- counsel_marketing \u2014 demand quality, ABM, PMM/lifecycle; content/brand/partner as support seats",
|
|
12327
|
-
internal_name: "Marketing counsel",
|
|
12328
|
-
vital_links: ["drop_rate", "signal_to_noise", "nrr"],
|
|
12329
|
-
board_cut: "Marketing's dollar contribution and the leak (creation vs handoff vs conversion narrative).",
|
|
12330
|
-
operator_cut: "SLA, scoring, journey gates, program kill rules, PMM adoption exams \u2014 function owners.",
|
|
12331
|
-
when_to_use: "Pipeline creation, handoff leak, magic number / CAC inputs, messaging stalls, named-account whitespace.",
|
|
12332
|
-
kill_rules: [
|
|
12333
|
-
"Do not buy more top-of-funnel when drop_rate is the constraint.",
|
|
12334
|
-
"Do not optimize MQL count against pipeline dollars.",
|
|
12335
|
-
"No ABM theater on smb_velocity without named-account process.",
|
|
12336
|
-
"Stop at accepted pipeline quality + narrative enablement \u2014 do not own close.",
|
|
12337
|
-
"If content/brand/partner is not the constraint, do not open those cards."
|
|
12338
|
-
],
|
|
12339
|
-
silent_brand_rule: SILENT,
|
|
12340
|
-
role_card_ids: [
|
|
12341
|
-
"cmo",
|
|
12342
|
-
"demand_gen",
|
|
12343
|
-
"growth",
|
|
12344
|
-
"abm",
|
|
12345
|
-
"pmm",
|
|
12346
|
-
"lifecycle",
|
|
12347
|
-
"mkt_ops",
|
|
12348
|
-
"content",
|
|
12349
|
-
"brand",
|
|
12350
|
-
"field_partner"
|
|
12299
|
+
id: "counsel_gtm",
|
|
12300
|
+
catalog_line: "- counsel_gtm \u2014 one GTM consultant: weigh tradeoffs, pick one constraint, one sequence, one call",
|
|
12301
|
+
internal_name: "GTM consultant",
|
|
12302
|
+
vital_links: [
|
|
12303
|
+
"freshness",
|
|
12304
|
+
"flow_rate",
|
|
12305
|
+
"drop_rate",
|
|
12306
|
+
"signal_to_noise",
|
|
12307
|
+
"thread_depth",
|
|
12308
|
+
"nrr",
|
|
12309
|
+
"grr"
|
|
12351
12310
|
],
|
|
12352
|
-
tree_weight: { plg: 1, smb_velocity: 0.85, mid_market: 1, enterprise: 1 },
|
|
12353
|
-
body: `THINK: Creation vs acceptance vs narrative. Sub-seats own different constraints \u2014 demand=volume/quality of accepted pipeline; ABM=named-list orchestration; PMM=win-rate/cycle via message; lifecycle=nurture/reactivation without colliding SDR; mkt ops=taxonomy/sync; content/brand/partner=support, not default.
|
|
12354
|
-
ASK: Is the leak before CRM, in routing, or at AE reject? Which channel's pipeline closes? Are MQLs a shared definition? Would fewer named accounts beat more spend?
|
|
12355
|
-
CUT: source, campaign, MQL\u2192SQL\u2192Opp, named-list vs rest, journey vs SDR sequence overlap.
|
|
12356
|
-
DISAGREE: vs sales \u2014 instrument acceptance reasons, not blame; vs revops \u2014 shared definitions, not last-touch religion; vs cs \u2014 frequency caps on customers; vs exec \u2014 brand spend needs a GTM exam or defer.
|
|
12357
|
-
TREE: plg=growth/lifecycle over classical demand. smb_velocity=inbound+outbound shared; no fake ABM. mid_market=demand-led, ABM opportunistic. enterprise=ABM+PMM first.
|
|
12358
|
-
CARDS: demand_gen if creation/quality; abm if named list; pmm if propose/decide stalls; lifecycle if nurture/collision; mkt_ops if scoring/sync; growth if PLG activation; content/brand/field_partner only when that sub-seat is the constraint.
|
|
12359
|
-
PLAYS: get_play_detail demand-quality-over-volume when accepts are weak; abm-orchestration when named-list process exists; rebalance-pipeline-mix / improve-magic-number for mix/efficiency. Handoff dollars usually belong with revops (fix-handoff-gap / harden-routing-sla) \u2014 marketing consumes the SLA.`
|
|
12360
|
-
},
|
|
12361
|
-
{
|
|
12362
|
-
id: "counsel_revops",
|
|
12363
|
-
catalog_line: "- counsel_revops \u2014 GTM systems, routing/SLA/hygiene; forecast/capacity/territory governance",
|
|
12364
|
-
internal_name: "RevOps counsel",
|
|
12365
|
-
vital_links: ["drop_rate", "freshness", "signal_to_noise"],
|
|
12366
|
-
board_cut: "Whether the revenue machine's instruments can be trusted; cost of leak from process/system breaks.",
|
|
12367
|
-
operator_cut: "Routing SLA, stage validation, sync observability, hygiene, GTM eng backlog \u2014 RevOps / mkt ops / GTM eng.",
|
|
12368
|
-
when_to_use: "Broken instruments, routing, sync, definitions, hygiene, automation debt; forecast ritual design; capacity/territory ops.",
|
|
12369
|
-
kill_rules: [
|
|
12370
|
-
"No governance theater when routing/SLA is broken.",
|
|
12371
|
-
"Do not automate bad process.",
|
|
12372
|
-
"No boil-the-ocean warehouse while drop_rate burns.",
|
|
12373
|
-
"Do not take sides on credit wars \u2014 impose shared definitions.",
|
|
12374
|
-
"Prefer system/process enforcement over headcount."
|
|
12375
|
-
],
|
|
12376
|
-
silent_brand_rule: SILENT,
|
|
12377
|
-
role_card_ids: ["vp_revops", "revops_specialist", "gtm_engineer", "mkt_ops", "sales_ops"],
|
|
12378
|
-
tree_weight: { plg: 0.6, smb_velocity: 0.75, mid_market: 1, enterprise: 1 },
|
|
12379
|
-
body: `THINK: 65% systems \u2014 missing vs late vs wrong vs unused data. Foundation identity before activation automation. 35% governance \u2014 forecast ritual (timely commits, inspection cadence), capacity model math, territory/comp ops that distort behavior. Dashboards without SLA/enforcement are theater.
|
|
12380
|
-
ASK: Behavior or instrument? Which timer/queue/sync break maps to the dollar? Do we have observability before another flow? Is forecast wrong because inputs are dirty or because the ritual is missing? Does territory/comp explain attainment better than skill talk?
|
|
12381
|
-
CUT: source\u2192owner path, unassigned age, stage-exit compliance, sync error class, identity match, commit vs inspection evidence.
|
|
12382
|
-
DISAGREE: vs sales \u2014 keep fields that protect exit criteria; vs marketing \u2014 time-box safe launches; vs cs \u2014 identity + handoff packet before dual-tool debates; vs exec \u2014 platform spend tied to constraint dollar and a 30-day hold exam.
|
|
12383
|
-
TREE: plg=product\u2194CRM identity. smb_velocity=lean routing/SLA. mid_market=full OS. enterprise=matrixed + GTM eng.
|
|
12384
|
-
CARDS: revops_specialist for day-to-day CRM/SLA; gtm_engineer for integrations/identity; mkt_ops for MAP/sync; sales_ops for territory/comp; vp_revops for OS tradeoffs and freeze decisions.
|
|
12385
|
-
PLAYS: get_play_detail harden-routing-sla when timers/observability are the leak; fix-handoff-gap when the path/source audit is missing; forecast-ritual-hygiene only after instrument trust; clean-dead-pipeline / retarget-effort when hygiene/automation feeds noise.`
|
|
12386
|
-
},
|
|
12387
|
-
{
|
|
12388
|
-
id: "counsel_exec",
|
|
12389
|
-
catalog_line: "- counsel_exec \u2014 merge seats into one constraint, one sequence, board-ready call",
|
|
12390
|
-
internal_name: "Executive counsel",
|
|
12391
|
-
vital_links: ["freshness", "flow_rate", "drop_rate", "signal_to_noise", "thread_depth", "nrr", "grr"],
|
|
12392
12311
|
board_cut: "The call, the dollar, the one bet, the exam date.",
|
|
12393
12312
|
operator_cut: "Sequenced workstreams with function owners and contingencies.",
|
|
12394
|
-
when_to_use: "
|
|
12313
|
+
when_to_use: "Any GTM advice \u2014 light NL or full plan. Always before plan ship; cross-functional objectives; CS vs new-logo tension.",
|
|
12395
12314
|
kill_rules: [
|
|
12396
|
-
"
|
|
12315
|
+
"Merge, do not concatenate \u2014 one governing constraint and one sequence.",
|
|
12397
12316
|
"No reorg/headcount as first move.",
|
|
12398
12317
|
"No parallel initiatives that starve the bottleneck.",
|
|
12399
12318
|
"Open with verdict + dollars, not methodology.",
|
|
12400
|
-
"Do not
|
|
12319
|
+
"Do not drop retention when NRR/GRR dollars matter alongside new-logo.",
|
|
12320
|
+
"Do not print named-role opinions or pack ids as speakers."
|
|
12401
12321
|
],
|
|
12402
12322
|
silent_brand_rule: SILENT,
|
|
12403
|
-
role_card_ids: [
|
|
12323
|
+
role_card_ids: [],
|
|
12404
12324
|
tree_weight: { plg: 1, smb_velocity: 1, mid_market: 1, enterprise: 1 },
|
|
12405
|
-
body: `THINK: Merge, do not concatenate. One governing constraint. Drop non-constraint theater. Board altitude = verdict + dollar + exam; operator altitude = sequenced owners
|
|
12406
|
-
|
|
12325
|
+
body: `THINK: Merge, do not concatenate. One governing constraint. Drop non-constraint theater. Board altitude = verdict + dollar + exam; operator altitude = sequenced Monday owners (who does the work \u2014 not who is talking).
|
|
12326
|
+
TENSIONS (weigh inside one recommendation \u2014 never as competing seats):
|
|
12327
|
+
- CS vs new-logo: when NRR/GRR dollars and new-logo/freshness dollars both matter, keep both in one sequence under one governing constraint. Do not drop retention.
|
|
12328
|
+
- Quality vs volume: accepted pipeline quality and conversion beat MQL/activity theater when drop_rate or signal_to_noise is the constraint.
|
|
12329
|
+
- Systems vs theater: instrument trust (routing/SLA/hygiene) before narrative bets or dashboards without enforcement.
|
|
12330
|
+
ASK: What unlocks the others? What dies in 90 days if we are wrong? Which ask is polish?
|
|
12407
12331
|
CUT: layer order of vitals; $ at risk by function; in-flight work that collides.
|
|
12408
|
-
DISAGREE
|
|
12332
|
+
DISAGREE (fold in \u2014 do not stage speakers): reject sales-only or marketing-only tunnels when instruments point elsewhere; hold systems work when instruments are untrusted; hold CS in the plan when GRR/NRR is the money; reject more top-of-funnel when drop_rate is the leak.
|
|
12409
12333
|
TREE: plg=CS/growth weight. smb_velocity=sales machine + routing. mid_market=full GTM. enterprise=thread/ABM/SE + NRR.
|
|
12410
|
-
|
|
12411
|
-
PLAYS: do not pick plays here \u2014 sequence the constraint owner's play and demote the rest to contingency.`
|
|
12412
|
-
},
|
|
12413
|
-
{
|
|
12414
|
-
id: "counsel_cs",
|
|
12415
|
-
catalog_line: "- counsel_cs \u2014 NRR/GRR, renewals, TTV/onboarding, sales\u2192CS handoff",
|
|
12416
|
-
internal_name: "Customer Success counsel",
|
|
12417
|
-
vital_links: ["freshness", "nrr", "grr", "contraction_arr"],
|
|
12418
|
-
board_cut: "NRR/GRR dollars at risk and the retention/expansion bet.",
|
|
12419
|
-
operator_cut: "Risk flags, renewal cadence, handoff packet fields, touch model by segment \u2014 CS / renewals / onboarding.",
|
|
12420
|
-
when_to_use: "NRR/GRR/churn/expansion; post-sale handoff; renewals; after sales wins that create CS landmines.",
|
|
12421
|
-
kill_rules: [
|
|
12422
|
-
"Do not push expansion into unhealthy accounts.",
|
|
12423
|
-
"No more QBRs when onboarding/TTV is the constraint.",
|
|
12424
|
-
"Do not treat renewals as pure billing.",
|
|
12425
|
-
"Do not ignore sales handoff quality as root cause.",
|
|
12426
|
-
"No CS headcount before segmentation/touch-model math."
|
|
12427
|
-
],
|
|
12428
|
-
silent_brand_rule: SILENT,
|
|
12429
|
-
role_card_ids: ["vp_cs", "csm", "renewals", "onboarding"],
|
|
12430
|
-
tree_weight: { plg: 1, smb_velocity: 0.7, mid_market: 1, enterprise: 1 },
|
|
12431
|
-
body: `THINK: GRR/NRR dollars. Churn vs failed expansion vs both. TTV/onboarding leak beats QBR theater. Champion loss vs product vs oversell. Expansion only after health restore when churn risk is high. Sales\u2192CS packet is often the real drop after Closed-Won.
|
|
12432
|
-
ASK: Leading indicator 60\u201390 days before churn? Was the sale clean (ICP/scope/handoff)? Onboarding incomplete or value never landed? Expansion owner CS vs AM vs AE?
|
|
12433
|
-
CUT: tenure, segment, product usage, renewal date aging, handoff-packet completeness, champion map.
|
|
12434
|
-
DISAGREE: vs sales \u2014 oversell and late CS involvement; vs marketing \u2014 customer email fatigue; vs revops \u2014 one SoR for renewal dates/risk; vs exec \u2014 growth plans must carry GRR exams.
|
|
12435
|
-
TREE: plg=CS+lifecycle+growth share the constraint. smb_velocity=often folded into CSM/AE; still name GRR. mid_market=full CS OS. enterprise=strategic accounts, renewals specialist.
|
|
12436
|
-
CARDS: csm for health/save; renewals for commercial deadline; vp_cs for touch model and where expansion sits; onboarding for readiness/TTV (capture required when events are missing).
|
|
12437
|
-
PLAYS: get_play_detail reduce-logo-churn / fix-renewal-process / renewal-early-warning / accelerate-expansion / sales-cs-handoff-packet when that instrument is the constraint.`
|
|
12334
|
+
PLAYS: sequence the constraint owner's play and demote the rest to contingency. Monday owners stay function-shaped ("RevOps starts the path audit") \u2014 never nameplates for who is talking.`
|
|
12438
12335
|
}
|
|
12439
12336
|
];
|
|
12440
12337
|
}
|
|
@@ -13092,21 +12989,21 @@ var init_metric_definitions = __esm({
|
|
|
13092
12989
|
kind: "vital",
|
|
13093
12990
|
label: "Freshness",
|
|
13094
12991
|
group: "Vital Signs",
|
|
13095
|
-
tagline: "Is the CRM
|
|
13096
|
-
how_computed: "
|
|
12992
|
+
tagline: "Is the CRM talking about live deals \u2014 or a museum of old ones?",
|
|
12993
|
+
how_computed: "We look at how recently people, companies, and deals got a touch. Deals need a fresher pulse than people or companies, and past-due deals count as stale. The formula lines on this card show the exact mix and windows.",
|
|
13097
12994
|
formula_lines: [
|
|
13098
12995
|
"freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
|
|
13099
12996
|
"people/orgs fresh if activity within 90d",
|
|
13100
12997
|
"opps fresh if activity within 30d AND not past-due"
|
|
13101
12998
|
],
|
|
13102
|
-
meaning:
|
|
13103
|
-
expert_read:
|
|
12999
|
+
meaning: `Freshness asks whether the pipeline number is still true. Quiet deals pad the forecast. Like milk dates on a shelf \u2014 past the date, don't trust the carton. Stale open dollars show as "pipeline at risk."`,
|
|
13000
|
+
expert_read: "Cut by owner and stage first \u2014 reds usually clump, they don't sprinkle. A sudden cliff is often a broken sync or a departed rep, not slow decay. New bulk imports can look stale when they're just new.",
|
|
13104
13001
|
deepdive: [
|
|
13105
|
-
"
|
|
13106
|
-
'
|
|
13107
|
-
"
|
|
13108
|
-
"
|
|
13109
|
-
"
|
|
13002
|
+
"Green 80+, yellow 60+, red below 60. Motion presets can change the windows.",
|
|
13003
|
+
'Dollars: sum of amount on stale open deals \u2192 "pipeline at risk".',
|
|
13004
|
+
"Read this first. If Freshness is red, later vitals are harder to trust.",
|
|
13005
|
+
"Weak score \u2192 play Clean Dead Pipeline.",
|
|
13006
|
+
"Useful moves: stale-deal alert, weekly hygiene scrub, enrichment on quiet records."
|
|
13110
13007
|
],
|
|
13111
13008
|
visual: {
|
|
13112
13009
|
kind: "bars",
|
|
@@ -13130,21 +13027,21 @@ var init_metric_definitions = __esm({
|
|
|
13130
13027
|
kind: "vital",
|
|
13131
13028
|
label: "Flow Rate",
|
|
13132
13029
|
group: "Vital Signs",
|
|
13133
|
-
tagline: "Where
|
|
13134
|
-
how_computed: "
|
|
13030
|
+
tagline: "Where do deals stop moving \u2014 and what's piled up behind that stage?",
|
|
13031
|
+
how_computed: "We start from how old open deals are versus a max window, then shave points for the share that look stuck. A deal is stuck when it goes quiet too long or its close date is already past. The formula lines show the base, the penalty, and the stuck rules.",
|
|
13135
13032
|
formula_lines: [
|
|
13136
13033
|
"base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
|
|
13137
13034
|
"score = base \u2212 stuckSharePenalty (\u226420)",
|
|
13138
13035
|
"stuck = no update > stuck_days OR past-due close"
|
|
13139
13036
|
],
|
|
13140
|
-
meaning: '
|
|
13141
|
-
expert_read: "
|
|
13037
|
+
meaning: 'Flow Rate asks whether deals are still advancing or sitting still. Stuck deals pad next quarter on paper. Like traffic behind a closed lane \u2014 the jam is one stage, not the whole highway. Dollars on stuck deals show as "stuck in pipeline."',
|
|
13038
|
+
expert_read: "Find the stage where advancement collapses first \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.",
|
|
13142
13039
|
deepdive: [
|
|
13143
|
-
"Status uses average open age. Green
|
|
13144
|
-
'
|
|
13145
|
-
"
|
|
13146
|
-
"
|
|
13147
|
-
"
|
|
13040
|
+
"Status uses average open age. Green \u226445 days, yellow \u226490, else red. Defaults: max_days 120, stuck_days 60.",
|
|
13041
|
+
'Dollars: sum of amount on stuck deals \u2192 "stuck in pipeline".',
|
|
13042
|
+
"Read with Drop Rate \u2014 stuck deals and handoff leaks often show up together.",
|
|
13043
|
+
"Weak score \u2192 play Unstick the Pipeline.",
|
|
13044
|
+
"Useful moves: stage-age report, past-due close cleanup, progression plans on stuck deals, forecast hygiene on optimistic close dates."
|
|
13148
13045
|
],
|
|
13149
13046
|
visual: {
|
|
13150
13047
|
kind: "funnel",
|
|
@@ -13170,21 +13067,21 @@ var init_metric_definitions = __esm({
|
|
|
13170
13067
|
kind: "vital",
|
|
13171
13068
|
label: "Drop Rate",
|
|
13172
13069
|
group: "Vital Signs",
|
|
13173
|
-
tagline: "Where does paid demand fall between systems before
|
|
13174
|
-
how_computed: "
|
|
13070
|
+
tagline: "Where does paid demand fall between systems before a rep works it?",
|
|
13071
|
+
how_computed: "We blend two checks: do marketing people also show up in sales CRM, and are open deals still being worked (not abandoned). The formula lines show the weights and the 30-day abandon window.",
|
|
13175
13072
|
formula_lines: [
|
|
13176
13073
|
"score = crossSystemRetention\xD70.6 + oppRetention\xD70.4",
|
|
13177
13074
|
"cross-system = marketing people also in sales CRM",
|
|
13178
13075
|
"abandoned = open opps with no activity in 30d"
|
|
13179
13076
|
],
|
|
13180
|
-
meaning: '
|
|
13181
|
-
expert_read:
|
|
13077
|
+
meaning: 'Drop Rate prices the handoff leak \u2014 money you already spent on leads that never reach a working rep. Like buying groceries and leaving half the bags in the parking lot. The dollar line is "est. lost at handoff."',
|
|
13078
|
+
expert_read: "A useful first cut is by lead source \u2014 the leak usually concentrates in one or two, not everywhere. Routing rules, unassigned territories, dead queues, or a sync gap between marketing and CRM show up more often than lazy reps. The cheapest pipeline you can buy is often the leads you already paid for.",
|
|
13182
13079
|
deepdive: [
|
|
13183
|
-
"
|
|
13184
|
-
'
|
|
13185
|
-
"
|
|
13186
|
-
"
|
|
13187
|
-
"
|
|
13080
|
+
"Green 80+, yellow 60+, red below 60.",
|
|
13081
|
+
'Dollars: dropped \xD7 conversion \xD7 avg deal (fallback: drop% \xD7 open pipeline) \u2192 "est. lost at handoff".',
|
|
13082
|
+
"Read with Flow Rate \u2014 handoff leaks and stuck deals often show up together.",
|
|
13083
|
+
"Weak score \u2192 play Fix the Handoff Gap.",
|
|
13084
|
+
"Useful moves: source-level handoff audit, routing and sync repair, time-to-first-touch SLA, weekly marketing-only-leads report."
|
|
13188
13085
|
],
|
|
13189
13086
|
visual: {
|
|
13190
13087
|
kind: "funnel",
|
|
@@ -13209,21 +13106,21 @@ var init_metric_definitions = __esm({
|
|
|
13209
13106
|
kind: "vital",
|
|
13210
13107
|
label: "Signal:Noise",
|
|
13211
13108
|
group: "Vital Signs",
|
|
13212
|
-
tagline: "How much
|
|
13213
|
-
how_computed: "
|
|
13109
|
+
tagline: "How much effort still aims at accounts that can close \u2014 versus dead ends?",
|
|
13110
|
+
how_computed: "We look at the last 90 days of activity and ask what share is signal \u2014 linked to an open deal, a pipeline person, or a pipeline company. The formula lines show the ratio and what counts as signal.",
|
|
13214
13111
|
formula_lines: [
|
|
13215
13112
|
"score = (signalCount / activityCount) \xD7 100",
|
|
13216
13113
|
"signal = linked to open opp / pipeline person / pipeline org",
|
|
13217
13114
|
"lookback = trailing 90 days"
|
|
13218
13115
|
],
|
|
13219
|
-
meaning:
|
|
13116
|
+
meaning: 'Signal:Noise asks whether activity still points at deals that can close. Noise hours are capacity spent on dead ends. Like a radio stuck between stations \u2014 lots of static, little signal. Those hours show as "misdirected effort."',
|
|
13220
13117
|
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.",
|
|
13221
13118
|
deepdive: [
|
|
13222
|
-
"
|
|
13223
|
-
|
|
13224
|
-
"
|
|
13225
|
-
"
|
|
13226
|
-
"
|
|
13119
|
+
"Green 65+, yellow 40+, red below 40.",
|
|
13120
|
+
'Dollar defaults: 0.25 hours/activity \xD7 $75/hr (config: hours_per_activity, rep_hourly_cost) \u2192 "misdirected effort".',
|
|
13121
|
+
"Read Freshness, Flow Rate, and Drop Rate before you trust this score.",
|
|
13122
|
+
"Weak score \u2192 play Retarget Misdirected Effort.",
|
|
13123
|
+
"Useful moves: refresh account lists, signal-based targeting, stop logging against closed or unlinked records, coverage-model redesign."
|
|
13227
13124
|
],
|
|
13228
13125
|
visual: {
|
|
13229
13126
|
kind: "split",
|
|
@@ -13246,21 +13143,21 @@ var init_metric_definitions = __esm({
|
|
|
13246
13143
|
kind: "vital",
|
|
13247
13144
|
label: "Thread Depth",
|
|
13248
13145
|
group: "Vital Signs",
|
|
13249
|
-
tagline: "How fragile is
|
|
13250
|
-
how_computed: "
|
|
13146
|
+
tagline: "How fragile is a deal if one champion changes jobs?",
|
|
13147
|
+
how_computed: "We ask what share of open deals have at least two people active in the last 90 days. Contacts on the deal and activity at the same company both count. The formula lines show the threshold.",
|
|
13251
13148
|
formula_lines: [
|
|
13252
13149
|
"score = % open deals with \u22652 active people (90d)",
|
|
13253
13150
|
"people counted via opp contacts + same-org activity",
|
|
13254
13151
|
"threshold configurable (default 2)"
|
|
13255
13152
|
],
|
|
13256
|
-
meaning:
|
|
13257
|
-
expert_read: "Weight by deal size when you triage
|
|
13153
|
+
meaning: 'Thread Depth asks how many people keep each deal warm. One contact is a single point of failure \u2014 if they leave, the deal often dies with them. Like a bridge with one cable. Dollars on thin deals show as "single-threaded."',
|
|
13154
|
+
expert_read: "Weight by deal size when you triage \u2014 one thin mega-deal can outweigh ten small ones. Late-cycle single-threading tends to matter more than early. In enterprise motions, thin threads often lead to slipped quarters: the champion leaves and nobody else is warm enough to carry the deal.",
|
|
13258
13155
|
deepdive: [
|
|
13259
|
-
"
|
|
13260
|
-
'
|
|
13261
|
-
"
|
|
13262
|
-
"
|
|
13263
|
-
"
|
|
13156
|
+
"Green 65+, yellow 40+, red below 40.",
|
|
13157
|
+
'Dollars: sum of amount on single-threaded deals \u2192 "single-threaded".',
|
|
13158
|
+
"Read this last, after Freshness, Flow Rate, Drop Rate, and Signal:Noise.",
|
|
13159
|
+
"Weak score \u2192 play Multi-Thread Your Deals.",
|
|
13160
|
+
"Useful moves: buying-committee map, warm internal referral first, CRM contact roles, mid-stage single-thread alerts, champion job-change signals."
|
|
13264
13161
|
],
|
|
13265
13162
|
visual: {
|
|
13266
13163
|
kind: "bars",
|
|
@@ -14181,139 +14078,38 @@ var init_constraint_signals = __esm({
|
|
|
14181
14078
|
});
|
|
14182
14079
|
|
|
14183
14080
|
// src/data/gtm-counsel/seating.ts
|
|
14184
|
-
function defaultSeatOrder(tree) {
|
|
14185
|
-
const tieBreak = TREE_TIE_BREAKS[tree];
|
|
14186
|
-
return COUNSEL_PACKS.filter(
|
|
14187
|
-
(pack) => pack.id !== "counsel_exec"
|
|
14188
|
-
).slice().sort((a, b) => {
|
|
14189
|
-
const weightDelta = b.tree_weight[tree] - a.tree_weight[tree];
|
|
14190
|
-
if (weightDelta !== 0) return weightDelta;
|
|
14191
|
-
return tieBreak.indexOf(a.id) - tieBreak.indexOf(b.id);
|
|
14192
|
-
}).map((pack) => pack.id);
|
|
14193
|
-
}
|
|
14194
|
-
function baselineSeatWeight(packId, tree) {
|
|
14195
|
-
const pack = COUNSEL_PACKS.find((candidate) => candidate.id === packId);
|
|
14196
|
-
const weight = pack?.tree_weight[tree] ?? 0.5;
|
|
14197
|
-
if (weight >= 0.8) return "full";
|
|
14198
|
-
if (weight >= 0.5) return "secondary";
|
|
14199
|
-
return "risk_note";
|
|
14200
|
-
}
|
|
14201
14081
|
function inferConstraintOwnerPacks(objective, playIds = [], gatingVital) {
|
|
14202
14082
|
return resolveLegacyConstraint(objective, playIds, gatingVital).locked_owners;
|
|
14203
14083
|
}
|
|
14204
14084
|
function inferConstraintOwnerPack(objective, playIds = [], gatingVital) {
|
|
14205
14085
|
return inferConstraintOwnerPacks(objective, playIds, gatingVital)[0] ?? null;
|
|
14206
14086
|
}
|
|
14207
|
-
function inferRoundtableBias(
|
|
14208
|
-
|
|
14209
|
-
const notes = [];
|
|
14210
|
-
const demoteToRiskNote = [];
|
|
14211
|
-
const preferFull = [];
|
|
14212
|
-
const strongMarketing = /marketing (lens|view|perspective)|from marketing|as cmo\b/.test(text);
|
|
14213
|
-
const strongRevops = /revops (lens|view)|rev ops (lens|view)|systems view|from revops/.test(text);
|
|
14214
|
-
const strongCs = /cs (lens|view)|success (lens|view)|retention view|nrr view/.test(text);
|
|
14215
|
-
const strongSales = /sales (lens|view|perspective)|from sales|ae view|quota view/.test(text);
|
|
14216
|
-
const weakMarketing = /\b(cmo|demand gen|demand-gen|mqls?\b|abm\b|pmm\b)\b/.test(text);
|
|
14217
|
-
const weakRevops = /\b(revops|rev ops|routing sla|gtm engineer)\b/.test(text);
|
|
14218
|
-
const weakCs = /\b(customer success|renewals manager|csm\b)\b/.test(text);
|
|
14219
|
-
const weakSales = /\b(vp sales|sales manager|quota attain)\b/.test(text);
|
|
14220
|
-
const notCro = /not cro|ignore cro|operator framing|not ceo.centric|skip exec politics/.test(text);
|
|
14221
|
-
if (strongMarketing || weakMarketing) {
|
|
14222
|
-
preferFull.push("counsel_marketing", "counsel_exec");
|
|
14223
|
-
notes.push(strongMarketing ? "Bias: marketing-weighted roundtable" : "Bias: marketing mentioned \u2014 keep marketing full");
|
|
14224
|
-
}
|
|
14225
|
-
if (strongRevops || weakRevops) {
|
|
14226
|
-
preferFull.push("counsel_revops", "counsel_exec");
|
|
14227
|
-
notes.push(strongRevops ? "Bias: RevOps-weighted roundtable" : "Bias: RevOps mentioned \u2014 keep RevOps full");
|
|
14228
|
-
}
|
|
14229
|
-
if (strongCs || weakCs) {
|
|
14230
|
-
preferFull.push("counsel_cs", "counsel_exec");
|
|
14231
|
-
notes.push(strongCs ? "Bias: CS-weighted roundtable" : "Bias: CS mentioned \u2014 keep CS full");
|
|
14232
|
-
}
|
|
14233
|
-
if (strongSales || weakSales) {
|
|
14234
|
-
preferFull.push("counsel_sales", "counsel_exec");
|
|
14235
|
-
notes.push(strongSales ? "Bias: sales-weighted roundtable" : "Bias: sales mentioned \u2014 keep sales full");
|
|
14236
|
-
}
|
|
14237
|
-
if (notCro) {
|
|
14238
|
-
notes.push("Bias: operator framing for exec seat (not CRO-centric voice)");
|
|
14239
|
-
}
|
|
14240
|
-
const strong = [
|
|
14241
|
-
strongMarketing ? "counsel_marketing" : null,
|
|
14242
|
-
strongRevops ? "counsel_revops" : null,
|
|
14243
|
-
strongCs ? "counsel_cs" : null,
|
|
14244
|
-
strongSales ? "counsel_sales" : null
|
|
14245
|
-
].filter((p) => p != null);
|
|
14246
|
-
if (strong.length === 1) {
|
|
14247
|
-
for (const p of ALL_FUNCTION) {
|
|
14248
|
-
if (p !== strong[0]) demoteToRiskNote.push(p);
|
|
14249
|
-
}
|
|
14250
|
-
}
|
|
14251
|
-
return { demoteToRiskNote, preferFull, notes };
|
|
14087
|
+
function inferRoundtableBias(_objective, _profile) {
|
|
14088
|
+
return { demoteToRiskNote: [], preferFull: [], notes: [] };
|
|
14252
14089
|
}
|
|
14253
14090
|
function buildRoundtableSeating(opts) {
|
|
14254
|
-
const
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14091
|
+
const constraint = opts.resolvedConstraint ?? resolveLegacyConstraint(
|
|
14092
|
+
opts.objective,
|
|
14093
|
+
opts.playIds ?? [],
|
|
14094
|
+
opts.gatingVital ?? null
|
|
14095
|
+
);
|
|
14096
|
+
const bias_notes = [...constraint.notes ?? []];
|
|
14097
|
+
if (constraint.locked_owners?.length) {
|
|
14098
|
+
bias_notes.push(
|
|
14099
|
+
`Constraint owners (internal, not speakers): ${constraint.locked_owners.join(", ")}`
|
|
14100
|
+
);
|
|
14101
|
+
}
|
|
14102
|
+
return {
|
|
14103
|
+
tree: opts.tree ?? "mid_market",
|
|
14104
|
+
seats: [],
|
|
14105
|
+
bias_notes,
|
|
14106
|
+
constraint
|
|
14267
14107
|
};
|
|
14268
|
-
|
|
14269
|
-
health: opts.health ?? {
|
|
14270
|
-
gating_vital: gatingVital && ["freshness", "flow_rate", "drop_rate", "signal_to_noise", "thread_depth"].includes(
|
|
14271
|
-
gatingVital
|
|
14272
|
-
) ? gatingVital : null
|
|
14273
|
-
},
|
|
14274
|
-
objective: objectiveContext
|
|
14275
|
-
});
|
|
14276
|
-
const constraintOwners = constraint.locked_owners;
|
|
14277
|
-
const ownerSet = new Set(constraintOwners);
|
|
14278
|
-
const demote = new Set(bias.demoteToRiskNote);
|
|
14279
|
-
const prefer = new Set(bias.preferFull);
|
|
14280
|
-
const seats = [{ pack_id: "counsel_exec", weight: "full" }];
|
|
14281
|
-
for (const packId of defaultSeatOrder(tree)) {
|
|
14282
|
-
let weight = baselineSeatWeight(packId, tree);
|
|
14283
|
-
if (demote.has(packId) && !prefer.has(packId)) weight = "risk_note";
|
|
14284
|
-
if (prefer.has(packId)) weight = "full";
|
|
14285
|
-
if (ownerSet.has(packId)) weight = "full";
|
|
14286
|
-
seats.push({ pack_id: packId, weight });
|
|
14287
|
-
}
|
|
14288
|
-
for (const owner of constraintOwners) {
|
|
14289
|
-
if (!seats.some((s) => s.pack_id === owner)) {
|
|
14290
|
-
seats.push({ pack_id: owner, weight: "full" });
|
|
14291
|
-
}
|
|
14292
|
-
}
|
|
14293
|
-
const bias_notes = [...bias.notes, ...constraint.notes];
|
|
14294
|
-
if (constraintOwners.length) {
|
|
14295
|
-
bias_notes.push(`Constraint-owning pack locked full: ${constraintOwners.join(", ")}`);
|
|
14296
|
-
}
|
|
14297
|
-
return { tree, seats, bias_notes, constraint };
|
|
14298
|
-
}
|
|
14299
|
-
var ALL_FUNCTION, TREE_TIE_BREAKS;
|
|
14108
|
+
}
|
|
14300
14109
|
var init_seating = __esm({
|
|
14301
14110
|
"src/data/gtm-counsel/seating.ts"() {
|
|
14302
14111
|
"use strict";
|
|
14303
|
-
init_packs();
|
|
14304
14112
|
init_constraint_signals();
|
|
14305
|
-
ALL_FUNCTION = [
|
|
14306
|
-
"counsel_sales",
|
|
14307
|
-
"counsel_marketing",
|
|
14308
|
-
"counsel_revops",
|
|
14309
|
-
"counsel_cs"
|
|
14310
|
-
];
|
|
14311
|
-
TREE_TIE_BREAKS = {
|
|
14312
|
-
plg: ["counsel_cs", "counsel_marketing", "counsel_revops", "counsel_sales"],
|
|
14313
|
-
smb_velocity: ["counsel_sales", "counsel_revops", "counsel_marketing", "counsel_cs"],
|
|
14314
|
-
mid_market: ["counsel_sales", "counsel_marketing", "counsel_revops", "counsel_cs"],
|
|
14315
|
-
enterprise: ["counsel_sales", "counsel_marketing", "counsel_revops", "counsel_cs"]
|
|
14316
|
-
};
|
|
14317
14113
|
}
|
|
14318
14114
|
});
|
|
14319
14115
|
|
|
@@ -14351,7 +14147,7 @@ __export(gtm_counsel_exports, {
|
|
|
14351
14147
|
});
|
|
14352
14148
|
function buildCounselCatalogBlock() {
|
|
14353
14149
|
const lines = COUNSEL_PACKS.map((p) => p.catalog_line).join("\n");
|
|
14354
|
-
return `GTM
|
|
14150
|
+
return `GTM consultant (one voice). Call get_counsel_detail with counsel_gtm for the pack body. Do not open role cards. Never invent reorgs; never name methodology brands to the user; never print pack ids or function seats as speakers.
|
|
14355
14151
|
${lines}`;
|
|
14356
14152
|
}
|
|
14357
14153
|
function buildOrgTreeContextLine(profile) {
|
|
@@ -14377,7 +14173,7 @@ function formatCounselDetail(pack, tree) {
|
|
|
14377
14173
|
tree_weight: pack.tree_weight[tree],
|
|
14378
14174
|
org_tree: tree,
|
|
14379
14175
|
body: pack.body,
|
|
14380
|
-
notice: "Apply moves in body/board_cut/operator_cut. Never name internal methodology brands to the user.
|
|
14176
|
+
notice: "Apply moves in body/board_cut/operator_cut. Never name internal methodology brands to the user. Speak as one GTM consultant \u2014 never as competing seats or named-role opinions."
|
|
14381
14177
|
};
|
|
14382
14178
|
}
|
|
14383
14179
|
function formatRoleCardDetail(card, tree) {
|
|
@@ -15026,21 +14822,33 @@ Hours saved, weekly activity trend, session counts, AI token usage, and the full
|
|
|
15026
14822
|
Type reset to confirm. This clears hours and milestones. The install identity stays.`
|
|
15027
14823
|
},
|
|
15028
14824
|
{
|
|
15029
|
-
name: "
|
|
14825
|
+
name: "tour",
|
|
15030
14826
|
raw: `---
|
|
15031
|
-
name:
|
|
14827
|
+
name: tour
|
|
15032
14828
|
description: Show slides for numbers and how to use NTRP
|
|
15033
14829
|
section: Navigation
|
|
15034
14830
|
args: [<metric>|guide|list|tour]
|
|
15035
|
-
handler: ../commands/
|
|
14831
|
+
handler: ../commands/tour.ts
|
|
15036
14832
|
---
|
|
15037
14833
|
|
|
15038
14834
|
CLI slide deck: SaaS refresher, five vital signs (definition, formula, visual, dollar translation), then a how-to section (talk, ask, ship to your AI, loop).
|
|
15039
|
-
Bare \`/
|
|
15040
|
-
Type \`/
|
|
15041
|
-
Type \`/
|
|
14835
|
+
Bare \`/tour\` runs the full onboarding tour. Type \`/tour guide\` to jump to how to use NTRP (includes teaching the inbox once).
|
|
14836
|
+
Type \`/tour handoff\` for the ship-to-AI slide. Type \`/tour <metric>\` to jump to one metric card.
|
|
14837
|
+
Type \`/tour list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
|
|
15042
14838
|
During the tour: Enter advances, \`b\` / \`back\` goes to the previous slide, \`q\` quits. After the tour, the same \`b\` / \`back\` word steps back one confirm gate in the REPL (scope, data, strategy) \u2014 it does not unload data or undo compute.
|
|
15043
14839
|
Live values overlay when an analysis exists.`
|
|
14840
|
+
},
|
|
14841
|
+
{
|
|
14842
|
+
name: "deepdive",
|
|
14843
|
+
raw: `---
|
|
14844
|
+
name: deepdive
|
|
14845
|
+
description: Retired \u2014 the slideshow is /tour
|
|
14846
|
+
section: Hidden
|
|
14847
|
+
hidden: true
|
|
14848
|
+
handler: ../commands/deepdive.ts
|
|
14849
|
+
---
|
|
14850
|
+
|
|
14851
|
+
Former name for the numbers slideshow. Prints a one-line pointer to \`/tour\`. Does not open the deck.`
|
|
15044
14852
|
},
|
|
15045
14853
|
{
|
|
15046
14854
|
name: "thinkwithme",
|
|
@@ -16275,39 +16083,19 @@ var init_tool_schemas = __esm({
|
|
|
16275
16083
|
},
|
|
16276
16084
|
{
|
|
16277
16085
|
name: "get_counsel_detail",
|
|
16278
|
-
description: "Read
|
|
16086
|
+
description: "Read the GTM consultant pack (counsel_gtm): when to use, kill rules, vital links, and instruction body. Use before recommending cross-functional work or during strategist plan check. One voice \u2014 never seat competing functions. Never name methodology brands to the user; no invented reorgs.",
|
|
16279
16087
|
parameters: {
|
|
16280
16088
|
type: "object",
|
|
16281
16089
|
properties: {
|
|
16282
16090
|
counsel_id: {
|
|
16283
16091
|
type: "string",
|
|
16284
16092
|
maxLength: 40,
|
|
16285
|
-
description: "Exact id:
|
|
16093
|
+
description: "Exact id: counsel_gtm."
|
|
16286
16094
|
}
|
|
16287
16095
|
},
|
|
16288
16096
|
required: ["counsel_id"]
|
|
16289
16097
|
}
|
|
16290
16098
|
},
|
|
16291
|
-
{
|
|
16292
|
-
name: "get_role_card",
|
|
16293
|
-
description: "Read a GTM role expertise card (e.g. ae, sdr, vp_revops, csm): owns/does-not-own, metrics, consult questions, failure modes, and tensions. Call after get_counsel_detail when you need how a specific seat thinks. Annotates presence for the active org tree.",
|
|
16294
|
-
parameters: {
|
|
16295
|
-
type: "object",
|
|
16296
|
-
properties: {
|
|
16297
|
-
role_id: {
|
|
16298
|
-
type: "string",
|
|
16299
|
-
maxLength: 40,
|
|
16300
|
-
description: "Exact role id listed on the pack you pulled via get_counsel_detail, e.g. 'ae', 'demand_gen', 'gtm_engineer'."
|
|
16301
|
-
},
|
|
16302
|
-
org_tree: {
|
|
16303
|
-
type: "string",
|
|
16304
|
-
enum: ["plg", "smb_velocity", "mid_market", "enterprise"],
|
|
16305
|
-
description: "Optional org tree override; defaults to profile sales_motion / mid_market."
|
|
16306
|
-
}
|
|
16307
|
-
},
|
|
16308
|
-
required: ["role_id"]
|
|
16309
|
-
}
|
|
16310
|
-
},
|
|
16311
16099
|
{
|
|
16312
16100
|
name: "get_session_brief",
|
|
16313
16101
|
description: "Read the 1-page context brief of a PRIOR session by id or 4-char suffix: status, dataset, scope, computed scores with dollar values, headline metrics, deliverables, and conversation log. Use when the user references earlier work \u2014 'last week we found\u2026', 'compare with the previous analysis', 'what did session 9297 conclude?'. Read-only.",
|
|
@@ -18859,17 +18647,9 @@ function renderRuminationLog(job) {
|
|
|
18859
18647
|
);
|
|
18860
18648
|
if (job.library_path) lines.push(`Strategy: ${job.library_path}`);
|
|
18861
18649
|
if (job.handoff_path) lines.push(`Handoff: ${job.handoff_path}`);
|
|
18862
|
-
if (job.roundtable) {
|
|
18863
|
-
lines.push("");
|
|
18864
|
-
lines.push("## Counsel roundtable");
|
|
18650
|
+
if (job.roundtable?.digest) {
|
|
18865
18651
|
lines.push("");
|
|
18866
18652
|
lines.push(job.roundtable.digest);
|
|
18867
|
-
if (job.roundtable.issues.length) {
|
|
18868
|
-
lines.push("");
|
|
18869
|
-
lines.push(
|
|
18870
|
-
`Validation: ${job.roundtable.issues.map((entry) => entry.code).join(", ")}`
|
|
18871
|
-
);
|
|
18872
|
-
}
|
|
18873
18653
|
}
|
|
18874
18654
|
lines.push("");
|
|
18875
18655
|
lines.push("## Rounds");
|
|
@@ -18998,7 +18778,7 @@ function consumeOrientEmptyEnterCoach(ctx) {
|
|
|
18998
18778
|
if (!hasValidLicense()) {
|
|
18999
18779
|
return "Type /activate to paste a key. Type /checkout if you need to sign up.";
|
|
19000
18780
|
}
|
|
19001
|
-
return "Type a question, type use demo data, or type /
|
|
18781
|
+
return "Type a question, type use demo data, or type /tour. Enter alone does not start a step here.";
|
|
19002
18782
|
}
|
|
19003
18783
|
function formatPhaseLabel(phase) {
|
|
19004
18784
|
switch (phase) {
|
|
@@ -19918,7 +19698,7 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
|
|
|
19918
19698
|
if (cached2 && cached2.length > 0) {
|
|
19919
19699
|
const ghosts = asksToGhostHints(cached2);
|
|
19920
19700
|
const gating = ctx.snapshot.computeResult?.aggregate.gating_vital_sign;
|
|
19921
|
-
if (gating) ghosts.push(`try /
|
|
19701
|
+
if (gating) ghosts.push(`try /tour ${gating}`);
|
|
19922
19702
|
ghosts.push('try "how should we fix this?"');
|
|
19923
19703
|
return dedupeGhosts(ghosts);
|
|
19924
19704
|
}
|
|
@@ -19929,7 +19709,7 @@ function buildExploreTeachingGhosts(ctx, staticExplore) {
|
|
|
19929
19709
|
});
|
|
19930
19710
|
return dedupeGhosts([
|
|
19931
19711
|
...asksToGhostHints(asks),
|
|
19932
|
-
`try /
|
|
19712
|
+
`try /tour ${health.gating_vital_sign}`,
|
|
19933
19713
|
'try "how should we fix this?"'
|
|
19934
19714
|
]);
|
|
19935
19715
|
}
|
|
@@ -20535,7 +20315,7 @@ function buildDefinitionsAppendix(bundle, opts = {}) {
|
|
|
20535
20315
|
lines.push(`## Metric definitions (for the ${audienceLabel(audience)})`);
|
|
20536
20316
|
lines.push("");
|
|
20537
20317
|
lines.push(
|
|
20538
|
-
audience === "ops" ? "Formula-first brief for operators who execute the plan. Full slides: `/
|
|
20318
|
+
audience === "ops" ? "Formula-first brief for operators who execute the plan. Full slides: `/tour <metric>`." : "Meaning-first brief for the room. Full slides: `/tour <metric>`."
|
|
20539
20319
|
);
|
|
20540
20320
|
lines.push("");
|
|
20541
20321
|
for (const c of candidates) {
|
|
@@ -20622,8 +20402,8 @@ function generateMarkdownReport(data) {
|
|
|
20622
20402
|
lines.push("");
|
|
20623
20403
|
for (const entity of vs.entity_details.slice(0, 5)) {
|
|
20624
20404
|
const name = entity.name ?? entity.id ?? "Unknown";
|
|
20625
|
-
const
|
|
20626
|
-
lines.push(`- **${name}**: ${
|
|
20405
|
+
const issue = entity.issue ?? "";
|
|
20406
|
+
lines.push(`- **${name}**: ${issue}`);
|
|
20627
20407
|
}
|
|
20628
20408
|
if (vs.entity_details.length > 5) {
|
|
20629
20409
|
lines.push(`- *...and ${vs.entity_details.length - 5} more*`);
|
|
@@ -21636,56 +21416,36 @@ Respond with the full plan as strict JSON matching this schema \u2014 no markdow
|
|
|
21636
21416
|
${STRATEGIST_PLAN_SCHEMA_BLOCK}`;
|
|
21637
21417
|
}
|
|
21638
21418
|
function buildRoundtableMessage(input) {
|
|
21639
|
-
const bias = input.biasNotes.length > 0 ? `
|
|
21419
|
+
const bias = input.biasNotes.length > 0 ? `Weighing notes:
|
|
21640
21420
|
- ${input.biasNotes.join("\n- ")}
|
|
21641
21421
|
|
|
21642
21422
|
` : "";
|
|
21643
|
-
const
|
|
21644
|
-
|
|
21645
|
-
` : "";
|
|
21646
|
-
const constraint = input.constraint ? `CONSTRAINT CHANNELS:
|
|
21423
|
+
const constraint = input.constraint ? `CONSTRAINT CHANNELS (weigh inside one recommendation \u2014 do not print as speakers):
|
|
21647
21424
|
- Health gate: ${input.constraint.health_gate ? String(input.constraint.health_gate.id) : "not verified"} \u2192 ${input.constraint.health_gate_owners.join(", ") || "no mapped owner"}
|
|
21648
21425
|
- Objective constraint: ${input.constraint.objective_constraint ? String(input.constraint.objective_constraint.id) : "not verified"} \u2192 ${input.constraint.objective_owners.join(", ") || "no mapped owner"}
|
|
21649
|
-
- If
|
|
21426
|
+
- If both matter (e.g. freshness/new-logo dollars and red NRR/GRR), keep both in one sequence under one governing constraint. Do not drop retention.
|
|
21650
21427
|
|
|
21651
21428
|
` : "";
|
|
21652
|
-
return `STAGE R \u2014
|
|
21429
|
+
return `STAGE R \u2014 PLAN CHECK. Org tree: ${input.tree}. Before stress-testing, run one GTM consultant check on the draft. Speak as NTRP \u2014 one voice. No seats, no CRO/CMO/CS/sales/marketing/RevOps/Exec nameplates.
|
|
21653
21430
|
|
|
21654
|
-
${
|
|
21655
|
-
FUNCTION SEATS (weight full = deep challenge; secondary/risk_note = short risk note only):
|
|
21656
|
-
${input.seatsBlock}
|
|
21657
|
-
|
|
21658
|
-
Call get_counsel_detail only for full seats. risk_note and secondary seats write a short risk without a full pack pull. Cap get_role_card to one id per challenged workstream owner \u2014 pick from that pack's role_card_ids. Call get_play_detail if a play_id is thin. Do not invent reorgs or named people. Never name methodology brands in customer-facing fields.
|
|
21431
|
+
${constraint}${bias}Call get_counsel_detail for counsel_gtm only. Do not call get_role_card. Call get_play_detail if a play_id is thin. Do not invent reorgs or named people. Never name methodology brands in customer-facing fields. Monday owners on actions stay function-shaped ("RevOps starts the path audit") \u2014 that is who does the work, not who is talking.
|
|
21659
21432
|
|
|
21660
21433
|
DRAFT PLAN JSON:
|
|
21661
21434
|
${input.draftPlanJson}
|
|
21662
21435
|
|
|
21663
21436
|
Respond with STRICT JSON only (no prose, no fences):
|
|
21664
21437
|
{
|
|
21665
|
-
"
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
"challenge": "\u226440 words \u2014 what the draft gets wrong or misses; empty only if support + risk or exec pre-mortem",
|
|
21671
|
-
"missing_exam": "metric + date + instrument (or empty string)",
|
|
21672
|
-
"cross_functional_risk": "\u226430 words",
|
|
21673
|
-
"role_cards_used": ["ae"]
|
|
21674
|
-
}
|
|
21675
|
-
],
|
|
21676
|
-
"exec": {
|
|
21677
|
-
"governing_constraint": "one constraint with dollar label",
|
|
21678
|
-
"sequence": ["play or workstream order"],
|
|
21679
|
-
"drop": ["non-constraint theater to kill"],
|
|
21680
|
-
"merge_verdict": "\u226450 words",
|
|
21681
|
-
"pre_mortem": "if all seats support: the one most likely death cause; else optional"
|
|
21682
|
-
}
|
|
21438
|
+
"governing_constraint": "one constraint with dollar label",
|
|
21439
|
+
"sequence": ["first move", "second move", "third move"],
|
|
21440
|
+
"drop": "non-constraint theater to kill",
|
|
21441
|
+
"verdict": "\u226450 words \u2014 one recommendation that already weighed the tradeoffs",
|
|
21442
|
+
"pre_mortem": "required \u2014 the one most likely death cause if this plan ships as-is"
|
|
21683
21443
|
}
|
|
21684
21444
|
|
|
21685
|
-
Rules:
|
|
21445
|
+
Rules: one consultant, one sequence, one governing constraint. Fold CS vs new-logo, quality vs volume, and systems vs theater into the recommendation \u2014 never as competing opinions. pre_mortem is always required. Never emit seats[], exec wrappers, pack ids as speakers, or CRO/CMO/CS says headers.`;
|
|
21686
21446
|
}
|
|
21687
21447
|
function buildStressTestMessage(roundtableDigest) {
|
|
21688
|
-
const roundtableBlock = roundtableDigest ? `0.
|
|
21448
|
+
const roundtableBlock = roundtableDigest ? `0. PLAN CHECK DIGEST (mandatory): address every challenge \u2014 accept and revise, or reject with an instrument reason. Add missing_exam items or explicitly defer with why. Reflect Drop in the final plan. If the check was all-agree, Pre-mortem is the death cause check 1 must defend \u2014 do not invent a fake challenge after the fact.
|
|
21689
21449
|
${roundtableDigest}
|
|
21690
21450
|
|
|
21691
21451
|
` : "";
|
|
@@ -22252,181 +22012,169 @@ function str2(value) {
|
|
|
22252
22012
|
return typeof value === "string" ? value.trim() : "";
|
|
22253
22013
|
}
|
|
22254
22014
|
function strArray2(value) {
|
|
22015
|
+
if (typeof value === "string") {
|
|
22016
|
+
const t = value.trim();
|
|
22017
|
+
return t ? [t] : [];
|
|
22018
|
+
}
|
|
22255
22019
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
22256
22020
|
}
|
|
22257
22021
|
function words(value) {
|
|
22258
22022
|
return value.trim() ? value.trim().split(/\s+/).length : 0;
|
|
22259
22023
|
}
|
|
22260
|
-
function
|
|
22024
|
+
function pushIssue(issues, code, message, severity = "blocking") {
|
|
22261
22025
|
issues.push({ code, severity, message });
|
|
22262
22026
|
}
|
|
22263
|
-
function
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
}
|
|
22268
|
-
const obj = raw;
|
|
22269
|
-
const pack = str2(obj.pack);
|
|
22270
|
-
if (pack === "counsel_exec") {
|
|
22271
|
-
issue(issues, "exec_in_seats", "counsel_exec must appear only in the top-level exec object.");
|
|
22272
|
-
return null;
|
|
22273
|
-
}
|
|
22274
|
-
if (!FUNCTION_PACKS.has(pack)) {
|
|
22275
|
-
issue(issues, "unknown_pack", `Seat ${index + 1} has unknown pack '${pack || "(empty)"}'.`);
|
|
22276
|
-
return null;
|
|
22277
|
-
}
|
|
22278
|
-
const stance = str2(obj.stance);
|
|
22279
|
-
if (!STANCES.has(stance)) {
|
|
22280
|
-
issue(issues, "invalid_stance", `${pack} has invalid stance '${stance || "(empty)"}'.`);
|
|
22281
|
-
}
|
|
22282
|
-
if (typeof obj.constraint_agree !== "boolean") {
|
|
22283
|
-
issue(issues, "constraint_agree", `${pack} must set constraint_agree to true or false.`);
|
|
22284
|
-
}
|
|
22285
|
-
const challenge = str2(obj.challenge);
|
|
22286
|
-
const risk = str2(obj.cross_functional_risk);
|
|
22287
|
-
const missingExam = str2(obj.missing_exam);
|
|
22288
|
-
if (words(challenge) > 40) {
|
|
22289
|
-
issue(issues, "challenge_length", `${pack} challenge exceeds 40 words.`);
|
|
22290
|
-
}
|
|
22291
|
-
if (words(risk) > 30) {
|
|
22292
|
-
issue(issues, "risk_length", `${pack} cross_functional_risk exceeds 30 words.`);
|
|
22293
|
-
}
|
|
22294
|
-
const roleCards = strArray2(obj.role_cards_used);
|
|
22295
|
-
const allowed = new Set(getCounselPackById(pack)?.role_card_ids ?? []);
|
|
22296
|
-
const validRoles = [];
|
|
22297
|
-
for (const roleId of roleCards) {
|
|
22298
|
-
if (!getRoleCardById(roleId) || !allowed.has(roleId)) {
|
|
22299
|
-
issue(
|
|
22027
|
+
function scanBannedSpeakers(text, issues, field) {
|
|
22028
|
+
for (const pattern of BANNED_SPEAKER_PATTERNS) {
|
|
22029
|
+
if (pattern.test(text)) {
|
|
22030
|
+
pushIssue(
|
|
22300
22031
|
issues,
|
|
22301
|
-
"
|
|
22302
|
-
`${
|
|
22032
|
+
"banned_speaker",
|
|
22033
|
+
`${field} must not name competing seats or role speakers.`
|
|
22303
22034
|
);
|
|
22304
|
-
|
|
22035
|
+
return;
|
|
22305
22036
|
}
|
|
22306
|
-
validRoles.push(roleId);
|
|
22307
22037
|
}
|
|
22308
|
-
return {
|
|
22309
|
-
pack,
|
|
22310
|
-
stance: STANCES.has(stance) ? stance : "abstain",
|
|
22311
|
-
constraint_agree: obj.constraint_agree === true,
|
|
22312
|
-
challenge,
|
|
22313
|
-
missing_exam: missingExam,
|
|
22314
|
-
cross_functional_risk: risk,
|
|
22315
|
-
role_cards_used: validRoles
|
|
22316
|
-
};
|
|
22317
22038
|
}
|
|
22318
|
-
function
|
|
22039
|
+
function parseConsultantCheck(raw, issues) {
|
|
22319
22040
|
if (!raw || typeof raw !== "object") {
|
|
22320
|
-
|
|
22041
|
+
pushIssue(issues, "consultant_shape", "Consultant check output is not a JSON object.");
|
|
22321
22042
|
return null;
|
|
22322
22043
|
}
|
|
22323
22044
|
const obj = raw;
|
|
22045
|
+
if ("seats" in obj) {
|
|
22046
|
+
pushIssue(issues, "seats_forbidden", "Consultant check must not include a seats array.");
|
|
22047
|
+
}
|
|
22048
|
+
if (obj.exec && typeof obj.exec === "object") {
|
|
22049
|
+
pushIssue(
|
|
22050
|
+
issues,
|
|
22051
|
+
"exec_wrapper_forbidden",
|
|
22052
|
+
"Consultant check must be a flat object \u2014 not seats[] + exec."
|
|
22053
|
+
);
|
|
22054
|
+
}
|
|
22324
22055
|
const governing = str2(obj.governing_constraint);
|
|
22325
|
-
const
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22056
|
+
const sequence = strArray2(obj.sequence);
|
|
22057
|
+
const dropRaw = obj.drop;
|
|
22058
|
+
const drop = Array.isArray(dropRaw) ? strArray2(dropRaw).join("; ") : str2(dropRaw);
|
|
22059
|
+
const verdict = str2(obj.verdict) || str2(obj.merge_verdict);
|
|
22060
|
+
const preMortem = str2(obj.pre_mortem) || str2(obj.preMortem);
|
|
22061
|
+
if (!governing) pushIssue(issues, "governing_constraint", "governing_constraint is required.");
|
|
22062
|
+
if (!verdict) pushIssue(issues, "verdict", "verdict is required.");
|
|
22063
|
+
if (words(verdict) > 50) pushIssue(issues, "verdict_length", "verdict exceeds 50 words.");
|
|
22064
|
+
if (!preMortem) {
|
|
22065
|
+
pushIssue(
|
|
22066
|
+
issues,
|
|
22067
|
+
"missing_pre_mortem",
|
|
22068
|
+
"pre_mortem is required (all-agree checks still need a death cause)."
|
|
22069
|
+
);
|
|
22070
|
+
}
|
|
22071
|
+
for (const [field, text] of [
|
|
22072
|
+
["governing_constraint", governing],
|
|
22073
|
+
["verdict", verdict],
|
|
22074
|
+
["drop", drop],
|
|
22075
|
+
["pre_mortem", preMortem],
|
|
22076
|
+
["sequence", sequence.join(" ")]
|
|
22077
|
+
]) {
|
|
22078
|
+
if (text) scanBannedSpeakers(text, issues, field);
|
|
22330
22079
|
}
|
|
22331
22080
|
return {
|
|
22332
22081
|
governing_constraint: governing,
|
|
22333
|
-
sequence
|
|
22334
|
-
drop
|
|
22335
|
-
|
|
22336
|
-
pre_mortem:
|
|
22082
|
+
sequence,
|
|
22083
|
+
drop,
|
|
22084
|
+
verdict,
|
|
22085
|
+
pre_mortem: preMortem
|
|
22337
22086
|
};
|
|
22338
22087
|
}
|
|
22339
|
-
function validateRoundtableResult(raw, options) {
|
|
22088
|
+
function validateRoundtableResult(raw, options = {}) {
|
|
22340
22089
|
const issues = [];
|
|
22341
22090
|
if (!raw || typeof raw !== "object") {
|
|
22342
22091
|
return {
|
|
22343
22092
|
result: null,
|
|
22344
|
-
issues: [
|
|
22093
|
+
issues: [
|
|
22094
|
+
{
|
|
22095
|
+
code: "consultant_shape",
|
|
22096
|
+
severity: "blocking",
|
|
22097
|
+
message: "Consultant check output is not a JSON object."
|
|
22098
|
+
}
|
|
22099
|
+
],
|
|
22345
22100
|
blocking: true
|
|
22346
22101
|
};
|
|
22347
22102
|
}
|
|
22348
|
-
const
|
|
22349
|
-
if (!Array.isArray(obj.seats)) {
|
|
22350
|
-
issue(issues, "missing_seats", "Roundtable requires a seats array.");
|
|
22351
|
-
}
|
|
22352
|
-
const seats = (Array.isArray(obj.seats) ? obj.seats : []).map((seat, index) => parseSeat(seat, issues, index)).filter((seat) => seat != null);
|
|
22353
|
-
const exec = parseExec(obj.exec, issues);
|
|
22354
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22355
|
-
for (const seat of seats) {
|
|
22356
|
-
if (seen.has(seat.pack)) {
|
|
22357
|
-
issue(issues, "duplicate_seat", `Roundtable contains duplicate seat ${seat.pack}.`);
|
|
22358
|
-
}
|
|
22359
|
-
seen.add(seat.pack);
|
|
22360
|
-
}
|
|
22361
|
-
const expected = options.seating.seats.filter((seat) => seat.pack_id !== "counsel_exec").map((seat) => seat.pack_id);
|
|
22362
|
-
for (const pack of expected) {
|
|
22363
|
-
if (!seen.has(pack)) issue(issues, "missing_seat", `Roundtable omitted seated pack ${pack}.`);
|
|
22364
|
-
}
|
|
22365
|
-
for (const pack of seen) {
|
|
22366
|
-
if (!expected.includes(pack)) issue(issues, "unexpected_seat", `Roundtable emitted unseated pack ${pack}.`);
|
|
22367
|
-
}
|
|
22103
|
+
const parsed = parseConsultantCheck(raw, issues);
|
|
22368
22104
|
const pulls = new Set(options.counselPulls ?? []);
|
|
22369
22105
|
if (options.requireCounselPulls !== false) {
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
}
|
|
22377
|
-
if (exec) {
|
|
22378
|
-
const allSupport = seats.length > 0 && seats.every((seat) => seat.stance === "support");
|
|
22379
|
-
if (allSupport && !exec.pre_mortem) {
|
|
22380
|
-
issue(issues, "missing_pre_mortem", "All-support roundtables require exec.pre_mortem.");
|
|
22106
|
+
if (!pulls.has("counsel_gtm")) {
|
|
22107
|
+
pushIssue(
|
|
22108
|
+
issues,
|
|
22109
|
+
"missing_counsel_pull",
|
|
22110
|
+
"Consultant check must call get_counsel_detail for counsel_gtm."
|
|
22111
|
+
);
|
|
22381
22112
|
}
|
|
22382
|
-
for (const
|
|
22383
|
-
if (
|
|
22384
|
-
|
|
22113
|
+
for (const pull of pulls) {
|
|
22114
|
+
if (pull !== "counsel_gtm") {
|
|
22115
|
+
pushIssue(
|
|
22385
22116
|
issues,
|
|
22386
|
-
"
|
|
22387
|
-
|
|
22117
|
+
"forbidden_counsel_pull",
|
|
22118
|
+
`Only counsel_gtm may be pulled (saw '${pull}').`
|
|
22388
22119
|
);
|
|
22389
22120
|
}
|
|
22390
|
-
if (seat.stance === "abstain" && !seat.cross_functional_risk) {
|
|
22391
|
-
issue(issues, "abstain_without_risk", `${seat.pack} abstained without a risk note.`);
|
|
22392
|
-
}
|
|
22393
22121
|
}
|
|
22394
22122
|
}
|
|
22395
|
-
|
|
22396
|
-
const
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22123
|
+
if (options.requireRetentionInSequence && parsed) {
|
|
22124
|
+
const blob = `${parsed.governing_constraint} ${parsed.sequence.join(" ")} ${parsed.verdict} ${parsed.drop}`.toLowerCase();
|
|
22125
|
+
const hasRetention = /nrr|grr|retention|renewal|churn|expansion/.test(blob);
|
|
22126
|
+
const hasNewLogo = /freshness|new[- ]?logo|acquisition|inbound|outbound/.test(blob);
|
|
22127
|
+
if (hasNewLogo && !hasRetention) {
|
|
22128
|
+
pushIssue(
|
|
22129
|
+
issues,
|
|
22130
|
+
"retention_dropped",
|
|
22131
|
+
"Dual-dollar check: new-logo/freshness present but retention/NRR/GRR absent from sequence.",
|
|
22132
|
+
"advisory"
|
|
22133
|
+
);
|
|
22400
22134
|
}
|
|
22401
22135
|
}
|
|
22402
22136
|
const blocking = issues.some((entry) => entry.severity === "blocking");
|
|
22403
22137
|
return {
|
|
22404
|
-
result:
|
|
22138
|
+
result: blocking ? null : parsed,
|
|
22405
22139
|
issues,
|
|
22406
22140
|
blocking
|
|
22407
22141
|
};
|
|
22408
22142
|
}
|
|
22409
|
-
function parseAndValidateRoundtable(text, options) {
|
|
22143
|
+
function parseAndValidateRoundtable(text, options = {}) {
|
|
22410
22144
|
return validateRoundtableResult(parseJsonObjectFromText(text), options);
|
|
22411
22145
|
}
|
|
22412
22146
|
function formatRoundtableDigestFromResult(result) {
|
|
22413
|
-
const lines =
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
if (result.
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
if (result.
|
|
22147
|
+
const lines = [];
|
|
22148
|
+
if (result.governing_constraint) {
|
|
22149
|
+
lines.push(`Governing constraint: ${result.governing_constraint}`);
|
|
22150
|
+
}
|
|
22151
|
+
if (result.sequence.length) {
|
|
22152
|
+
lines.push(`Sequence: ${result.sequence.join(" then ")}`);
|
|
22153
|
+
}
|
|
22154
|
+
if (result.drop) {
|
|
22155
|
+
lines.push(`Drop: ${result.drop}`);
|
|
22156
|
+
}
|
|
22157
|
+
if (result.verdict) {
|
|
22158
|
+
lines.push(`Verdict: ${result.verdict}`);
|
|
22159
|
+
}
|
|
22160
|
+
if (result.pre_mortem) {
|
|
22161
|
+
lines.push(`Pre-mortem: ${result.pre_mortem}`);
|
|
22162
|
+
}
|
|
22421
22163
|
return lines.join("\n");
|
|
22422
22164
|
}
|
|
22423
|
-
function buildRoundtableRetryMessage(validation,
|
|
22424
|
-
const required = seating.seats.filter((seat) => seat.pack_id !== "counsel_exec").map((seat) => seat.pack_id).join(", ");
|
|
22165
|
+
function buildRoundtableRetryMessage(validation, _seating) {
|
|
22425
22166
|
const failures = validation.issues.filter((entry) => entry.severity === "blocking").map((entry) => `${entry.code}: ${entry.message}`).join("\n- ");
|
|
22426
|
-
return `
|
|
22427
|
-
- ${failures || "
|
|
22428
|
-
|
|
22429
|
-
|
|
22167
|
+
return `Plan check JSON did not validate.
|
|
22168
|
+
- ${failures || "consultant_shape: unusable JSON"}
|
|
22169
|
+
Respond with ONLY corrected flat JSON (no seats, no exec wrapper):
|
|
22170
|
+
{
|
|
22171
|
+
"governing_constraint": "...",
|
|
22172
|
+
"sequence": ["a", "b", "c"],
|
|
22173
|
+
"drop": "...",
|
|
22174
|
+
"verdict": "...",
|
|
22175
|
+
"pre_mortem": "..."
|
|
22176
|
+
}
|
|
22177
|
+
Call get_counsel_detail for counsel_gtm only. pre_mortem is required. Never print CRO/CMO/CS/sales/marketing/RevOps/Exec as speakers.`;
|
|
22430
22178
|
}
|
|
22431
22179
|
function normalizedPlanText(plan) {
|
|
22432
22180
|
return JSON.stringify(plan).toLowerCase();
|
|
@@ -22438,69 +22186,55 @@ function checkRoundtableReflection(input) {
|
|
|
22438
22186
|
const issues = [];
|
|
22439
22187
|
const draft = normalizedPlanText(input.draftPlan);
|
|
22440
22188
|
const final = normalizedPlanText(input.finalPlan);
|
|
22441
|
-
|
|
22442
|
-
const
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
"roundtable_drop_ignored",
|
|
22447
|
-
`Final plan still contains exec drop '${dropped}'.`,
|
|
22448
|
-
"advisory"
|
|
22449
|
-
);
|
|
22450
|
-
}
|
|
22451
|
-
}
|
|
22452
|
-
for (const seat of input.roundtable.seats) {
|
|
22453
|
-
if (seat.missing_exam) {
|
|
22454
|
-
const tokens = significantTokens(seat.missing_exam);
|
|
22455
|
-
if (tokens.length > 0 && !tokens.some((token) => final.includes(token))) {
|
|
22456
|
-
issue(
|
|
22189
|
+
if (input.roundtable.drop) {
|
|
22190
|
+
for (const dropped of input.roundtable.drop.split(/;|\n/).map((part) => part.trim()).filter(Boolean)) {
|
|
22191
|
+
const needle = dropped.toLowerCase();
|
|
22192
|
+
if (needle.length >= 4 && draft.includes(needle) && final.includes(needle)) {
|
|
22193
|
+
pushIssue(
|
|
22457
22194
|
issues,
|
|
22458
|
-
"
|
|
22459
|
-
|
|
22195
|
+
"consultant_drop_ignored",
|
|
22196
|
+
`Final plan still contains drop '${dropped}'.`,
|
|
22460
22197
|
"advisory"
|
|
22461
22198
|
);
|
|
22462
22199
|
}
|
|
22463
22200
|
}
|
|
22464
|
-
if (seat.stance === "challenge" && seat.challenge && draft === final) {
|
|
22465
|
-
issue(
|
|
22466
|
-
issues,
|
|
22467
|
-
"roundtable_unaddressed",
|
|
22468
|
-
`${seat.pack} challenged the draft but the final plan is unchanged.`,
|
|
22469
|
-
"advisory"
|
|
22470
|
-
);
|
|
22471
|
-
}
|
|
22472
22201
|
}
|
|
22473
|
-
|
|
22474
|
-
if (allSupport && input.roundtable.exec.pre_mortem) {
|
|
22202
|
+
if (input.roundtable.pre_mortem) {
|
|
22475
22203
|
const defensiveText = JSON.stringify({
|
|
22476
22204
|
risks: input.finalPlan.risks,
|
|
22477
22205
|
assumptions: input.finalPlan.assumptions,
|
|
22478
22206
|
contingencies: input.finalPlan.workstreams.map((ws) => ws.contingency)
|
|
22479
22207
|
}).toLowerCase();
|
|
22480
|
-
const tokens = significantTokens(input.roundtable.
|
|
22208
|
+
const tokens = significantTokens(input.roundtable.pre_mortem);
|
|
22481
22209
|
if (tokens.length > 0 && !tokens.some((token) => defensiveText.includes(token))) {
|
|
22482
|
-
|
|
22210
|
+
pushIssue(
|
|
22483
22211
|
issues,
|
|
22484
|
-
"
|
|
22485
|
-
"Final risks and contingencies do not reflect
|
|
22212
|
+
"consultant_premortem_missing",
|
|
22213
|
+
"Final risks and contingencies do not reflect pre_mortem.",
|
|
22486
22214
|
"advisory"
|
|
22487
22215
|
);
|
|
22488
22216
|
}
|
|
22489
22217
|
}
|
|
22490
22218
|
return { issues, blocking: false };
|
|
22491
22219
|
}
|
|
22492
|
-
var
|
|
22220
|
+
var BANNED_SPEAKER_PATTERNS;
|
|
22493
22221
|
var init_roundtable_validate = __esm({
|
|
22494
22222
|
"src/ai/roundtable-validate.ts"() {
|
|
22495
22223
|
"use strict";
|
|
22496
|
-
init_gtm_counsel();
|
|
22497
22224
|
init_strategist_validate();
|
|
22498
|
-
|
|
22499
|
-
|
|
22500
|
-
|
|
22501
|
-
|
|
22502
|
-
|
|
22503
|
-
|
|
22225
|
+
BANNED_SPEAKER_PATTERNS = [
|
|
22226
|
+
/\bCRO\s+says\b/i,
|
|
22227
|
+
/\bCMO\s+says\b/i,
|
|
22228
|
+
/\bCS\s+says\b/i,
|
|
22229
|
+
/\bsales\s+says\b/i,
|
|
22230
|
+
/\bfrom a marketing lens\b/i,
|
|
22231
|
+
/\bfrom a sales lens\b/i,
|
|
22232
|
+
/\bcounsel_sales\b/,
|
|
22233
|
+
/\bcounsel_marketing\b/,
|
|
22234
|
+
/\bcounsel_revops\b/,
|
|
22235
|
+
/\bcounsel_cs\b/,
|
|
22236
|
+
/\bcounsel_exec\b/
|
|
22237
|
+
];
|
|
22504
22238
|
}
|
|
22505
22239
|
});
|
|
22506
22240
|
|
|
@@ -23077,9 +22811,7 @@ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`
|
|
|
23077
22811
|
const observeRoundtableTool = (name, args) => {
|
|
23078
22812
|
if (name !== "get_counsel_detail") return;
|
|
23079
22813
|
const counselId = typeof args.counsel_id === "string" ? args.counsel_id : "";
|
|
23080
|
-
if (
|
|
23081
|
-
counselId
|
|
23082
|
-
)) {
|
|
22814
|
+
if (counselId === "counsel_gtm") {
|
|
23083
22815
|
counselPulls.add(counselId);
|
|
23084
22816
|
}
|
|
23085
22817
|
};
|
|
@@ -23099,7 +22831,7 @@ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`
|
|
|
23099
22831
|
const roundtableText = yield* runStage(
|
|
23100
22832
|
"strategist",
|
|
23101
22833
|
ROUNDTABLE_MAX_ROUNDS,
|
|
23102
|
-
"Tool budget reached for
|
|
22834
|
+
"Tool budget reached for the plan check. Respond with the plan-check JSON now \u2014 strict JSON only.",
|
|
23103
22835
|
true,
|
|
23104
22836
|
observeRoundtableTool
|
|
23105
22837
|
);
|
|
@@ -23117,7 +22849,7 @@ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`
|
|
|
23117
22849
|
const retryText = yield* runStage(
|
|
23118
22850
|
"strategist",
|
|
23119
22851
|
2,
|
|
23120
|
-
"Respond with ONLY corrected
|
|
22852
|
+
"Respond with ONLY corrected plan-check JSON now. No prose.",
|
|
23121
22853
|
true,
|
|
23122
22854
|
observeRoundtableTool
|
|
23123
22855
|
);
|
|
@@ -23132,13 +22864,13 @@ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`
|
|
|
23132
22864
|
const roundtableIssues = [...roundtableValidation.issues];
|
|
23133
22865
|
let roundtableDigest = roundtableResult ? formatRoundtableDigestFromResult(roundtableResult) : "";
|
|
23134
22866
|
if (!roundtableResult) {
|
|
23135
|
-
roundtableDigest = "
|
|
22867
|
+
roundtableDigest = "Plan check didn't land. Stress-testing still runs.";
|
|
23136
22868
|
yield {
|
|
23137
22869
|
type: "notice",
|
|
23138
|
-
text:
|
|
22870
|
+
text: "Plan check didn't land. Stress-testing still runs."
|
|
23139
22871
|
};
|
|
23140
22872
|
} else {
|
|
23141
|
-
yield { type: "notice", text: "
|
|
22873
|
+
yield { type: "notice", text: "Recommendation locked. Stress-testing next." };
|
|
23142
22874
|
}
|
|
23143
22875
|
yield { type: "stage", stage: "stress", label: STAGE_LABELS.stress };
|
|
23144
22876
|
messages.push({ role: "user", content: buildStressTestMessage(roundtableDigest) });
|
|
@@ -23176,8 +22908,8 @@ Respond with ONLY the corrected plan JSON object in the required schema (title,
|
|
|
23176
22908
|
text: "Strategist JSON failed validation \u2014 emitting grounded fallback plan from live vitals."
|
|
23177
22909
|
};
|
|
23178
22910
|
}
|
|
23179
|
-
for (const
|
|
23180
|
-
yield { type: "notice", text:
|
|
22911
|
+
for (const issue of validated.issues) {
|
|
22912
|
+
yield { type: "notice", text: issue };
|
|
23181
22913
|
}
|
|
23182
22914
|
if (roundtableResult && candidatePlan) {
|
|
23183
22915
|
const reflection = checkRoundtableReflection({
|
|
@@ -23187,10 +22919,6 @@ Respond with ONLY the corrected plan JSON object in the required schema (title,
|
|
|
23187
22919
|
});
|
|
23188
22920
|
for (const reflectionIssue of reflection.issues) {
|
|
23189
22921
|
roundtableIssues.push(reflectionIssue);
|
|
23190
|
-
yield {
|
|
23191
|
-
type: "notice",
|
|
23192
|
-
text: `Counsel reflection (${reflectionIssue.code}): ${reflectionIssue.message}`
|
|
23193
|
-
};
|
|
23194
22922
|
}
|
|
23195
22923
|
}
|
|
23196
22924
|
yield {
|
|
@@ -23299,7 +23027,7 @@ var init_strategist2 = __esm({
|
|
|
23299
23027
|
STAGE_LABELS = {
|
|
23300
23028
|
ground: "Grounding \u2014 reading health, metrics, segments, history",
|
|
23301
23029
|
backcast: "Sequencing \u2014 backcasting from objective",
|
|
23302
|
-
roundtable: "
|
|
23030
|
+
roundtable: "Checking the plan",
|
|
23303
23031
|
stress: "Stress-testing \u2014 capacity, measurability, timeline"
|
|
23304
23032
|
};
|
|
23305
23033
|
}
|
|
@@ -23331,6 +23059,10 @@ function recordScoreStreak(job, score) {
|
|
|
23331
23059
|
}
|
|
23332
23060
|
job.last_critic_score = score;
|
|
23333
23061
|
}
|
|
23062
|
+
function formatBlockingGapForUser(gap) {
|
|
23063
|
+
if (gap.code === "roundtable_invalid") return "Plan check didn't land.";
|
|
23064
|
+
return `Plan gap (${gap.code}): ${gap.note}`;
|
|
23065
|
+
}
|
|
23334
23066
|
async function* strategistCraftSession(options) {
|
|
23335
23067
|
assertReplAi(options.ctx);
|
|
23336
23068
|
const todayIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -23373,8 +23105,8 @@ async function* strategistCraftSession(options) {
|
|
|
23373
23105
|
const gap = {
|
|
23374
23106
|
code: "roundtable_invalid",
|
|
23375
23107
|
severity: "blocking",
|
|
23376
|
-
note: !job.roundtable ? "No validated
|
|
23377
|
-
fix: "Re-run
|
|
23108
|
+
note: !job.roundtable ? "No validated plan check is attached to this plan." : job.roundtable.counsel_fingerprint !== currentFingerprint ? "The latest plan check is stale for the current plan." : "The latest plan check failed structural validation.",
|
|
23109
|
+
fix: "Re-run the plan check until it lands a single consultant recommendation with a pre-mortem."
|
|
23378
23110
|
};
|
|
23379
23111
|
scored.gaps.push(gap);
|
|
23380
23112
|
scored.blocking.push(gap);
|
|
@@ -23452,7 +23184,7 @@ async function* strategistCraftSession(options) {
|
|
|
23452
23184
|
};
|
|
23453
23185
|
const rubric = scorePlan(locked);
|
|
23454
23186
|
job.rubric_gaps = rubric.gaps;
|
|
23455
|
-
yield { ...event, plan: locked, issues: [...event.issues, ...rubric.blocking.map(
|
|
23187
|
+
yield { ...event, plan: locked, issues: [...event.issues, ...rubric.blocking.map(formatBlockingGapForUser)] };
|
|
23456
23188
|
} else if (event.type === "done") {
|
|
23457
23189
|
lastMeta = event.usage ?? lastMeta;
|
|
23458
23190
|
job.cumulative_input_tokens += event.usage?.input_tokens ?? 0;
|
|
@@ -23522,7 +23254,7 @@ async function* strategistCraftSession(options) {
|
|
|
23522
23254
|
planJson: JSON.stringify(plan),
|
|
23523
23255
|
rubricGaps: [
|
|
23524
23256
|
rubric.blocking.map((g) => `${g.code}: ${g.note}`).join("\n") || "(none)",
|
|
23525
|
-
lastRoundtableDigest ? `
|
|
23257
|
+
lastRoundtableDigest ? `PLAN CHECK DIGEST (address or reject with instruments):
|
|
23526
23258
|
${lastRoundtableDigest}` : ""
|
|
23527
23259
|
].filter(Boolean).join("\n\n"),
|
|
23528
23260
|
healthSnapshot
|
|
@@ -23634,7 +23366,7 @@ ${lastRoundtableDigest}` : ""
|
|
|
23634
23366
|
if (nextFp !== lastCounselFp) {
|
|
23635
23367
|
yield {
|
|
23636
23368
|
type: "notice",
|
|
23637
|
-
text: "Plan
|
|
23369
|
+
text: "Plan shifted. Checking the plan again."
|
|
23638
23370
|
};
|
|
23639
23371
|
try {
|
|
23640
23372
|
const profile = loadProfile();
|
|
@@ -23712,7 +23444,7 @@ ${lastRoundtableDigest}` : ""
|
|
|
23712
23444
|
requireCounselPulls: false
|
|
23713
23445
|
});
|
|
23714
23446
|
const result = validation.blocking ? null : validation.result;
|
|
23715
|
-
lastRoundtableDigest = result ? formatRoundtableDigestFromResult(result) : "
|
|
23447
|
+
lastRoundtableDigest = result ? formatRoundtableDigestFromResult(result) : "Plan check didn't land.";
|
|
23716
23448
|
const record = {
|
|
23717
23449
|
seating,
|
|
23718
23450
|
result,
|
|
@@ -23728,7 +23460,7 @@ ${lastRoundtableDigest}` : ""
|
|
|
23728
23460
|
lastCounselFp = "";
|
|
23729
23461
|
yield {
|
|
23730
23462
|
type: "notice",
|
|
23731
|
-
text:
|
|
23463
|
+
text: "Plan check didn't land."
|
|
23732
23464
|
};
|
|
23733
23465
|
} else {
|
|
23734
23466
|
lastCounselFp = nextFp;
|
|
@@ -23755,7 +23487,7 @@ ${lastRoundtableDigest}` : ""
|
|
|
23755
23487
|
}
|
|
23756
23488
|
yield {
|
|
23757
23489
|
type: "notice",
|
|
23758
|
-
text:
|
|
23490
|
+
text: "Plan check didn't land."
|
|
23759
23491
|
};
|
|
23760
23492
|
}
|
|
23761
23493
|
}
|
|
@@ -23776,7 +23508,7 @@ ${lastRoundtableDigest}` : ""
|
|
|
23776
23508
|
yield {
|
|
23777
23509
|
type: "plan",
|
|
23778
23510
|
plan: job.plan,
|
|
23779
|
-
issues: rubric.blocking.map(
|
|
23511
|
+
issues: rubric.blocking.map(formatBlockingGapForUser),
|
|
23780
23512
|
measurable_targets: measurable.measurable_targets,
|
|
23781
23513
|
total_targets: measurable.total_targets,
|
|
23782
23514
|
baseline_batch_id: options.baselineBatchId ?? null
|
|
@@ -23873,12 +23605,6 @@ function renderCraftHandoffMarkdown(opts) {
|
|
|
23873
23605
|
lines.push(formatWorkstreamMarkdown(ws).trimEnd());
|
|
23874
23606
|
lines.push("");
|
|
23875
23607
|
}
|
|
23876
|
-
if (opts.roundtableDigest) {
|
|
23877
|
-
lines.push("## Counsel roundtable");
|
|
23878
|
-
lines.push("");
|
|
23879
|
-
lines.push(opts.roundtableDigest);
|
|
23880
|
-
lines.push("");
|
|
23881
|
-
}
|
|
23882
23608
|
const risks = filterOperatorLines(plan.risks);
|
|
23883
23609
|
if (risks.length > 0) {
|
|
23884
23610
|
lines.push("## Risks");
|
|
@@ -24922,8 +24648,8 @@ var init_ghost_hints = __esm({
|
|
|
24922
24648
|
PHASE_GHOST_HINTS = {
|
|
24923
24649
|
orient: [
|
|
24924
24650
|
'try "pipeline health"',
|
|
24925
|
-
"try /
|
|
24926
|
-
"try /
|
|
24651
|
+
"try /tour",
|
|
24652
|
+
"try /tour guide",
|
|
24927
24653
|
'try "is our retention real for the board?"',
|
|
24928
24654
|
'try "what is the most expensive problem to solve?"',
|
|
24929
24655
|
'try "board deck on Q3"'
|
|
@@ -24940,7 +24666,7 @@ var init_ghost_hints = __esm({
|
|
|
24940
24666
|
'try "how should we fix this?"',
|
|
24941
24667
|
'try "which segment is weakest?"',
|
|
24942
24668
|
'try "what is ARR?"',
|
|
24943
|
-
"try /
|
|
24669
|
+
"try /tour freshness",
|
|
24944
24670
|
'try "ship a board deck"'
|
|
24945
24671
|
]
|
|
24946
24672
|
};
|
|
@@ -24999,7 +24725,7 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
|
|
|
24999
24725
|
"Already done: scope confirmed; compute ran; do not re-propose scope or re-run compute unless the user asks to restart"
|
|
25000
24726
|
);
|
|
25001
24727
|
lines.push(
|
|
25002
|
-
"Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /
|
|
24728
|
+
"Available now: answer from the artifact; suggest at most one of /strategy, /handoff, /tour <metric>, /thinkwithme, /playbook"
|
|
25003
24729
|
);
|
|
25004
24730
|
if (channel === "explore" && responseMode === "deep") {
|
|
25005
24731
|
lines.push(
|
|
@@ -25662,7 +25388,7 @@ function tryKeylessDefinitionAnswer(ctx, input) {
|
|
|
25662
25388
|
}
|
|
25663
25389
|
console.log();
|
|
25664
25390
|
console.log(
|
|
25665
|
-
" " + chalk23.dim("More: ") + paint("accent", `/
|
|
25391
|
+
" " + chalk23.dim("More: ") + paint("accent", `/tour ${explainer.id}`) + chalk23.dim(" \xB7 full tour: ") + paint("accent", "/tour")
|
|
25666
25392
|
);
|
|
25667
25393
|
console.log();
|
|
25668
25394
|
recordMessage(ctx, "user", input);
|
|
@@ -25739,7 +25465,7 @@ async function handleExploreWithoutKey(ctx, input) {
|
|
|
25739
25465
|
console.log();
|
|
25740
25466
|
console.log(" " + chalk24.yellow("No key is connected. Q&A stays off until you type ") + paint("accent", "/connect") + chalk24.yellow("."));
|
|
25741
25467
|
console.log(" " + chalk24.dim("These commands work without a key:"));
|
|
25742
|
-
console.log(" " + paint("accent", "/
|
|
25468
|
+
console.log(" " + paint("accent", "/tour") + chalk24.dim(" slides for numbers and how to use NTRP"));
|
|
25743
25469
|
console.log(" " + paint("accent", "/playbook") + chalk24.dim(" recommended plays from your computed vitals"));
|
|
25744
25470
|
console.log(" " + chalk24.cyan('"how should we fix this?"') + chalk24.dim(" a simple plan. No AI."));
|
|
25745
25471
|
console.log(" " + paint("accent", "/handoff") + chalk24.dim(" write this analysis for another tool"));
|
|
@@ -25747,7 +25473,7 @@ async function handleExploreWithoutKey(ctx, input) {
|
|
|
25747
25473
|
recordMessage(
|
|
25748
25474
|
ctx,
|
|
25749
25475
|
"agent",
|
|
25750
|
-
"No key is connected. Offered paths without a key (/
|
|
25476
|
+
"No key is connected. Offered paths without a key (/tour, /playbook, simple plan, /handoff)."
|
|
25751
25477
|
);
|
|
25752
25478
|
}
|
|
25753
25479
|
var NO_KEY_NUDGES;
|
|
@@ -26579,7 +26305,7 @@ function printFindingInline2(finding) {
|
|
|
26579
26305
|
if (play) console.log(" " + chalk25.dim("\u2192 " + play.play_name + " \u2014 " + play.rationale));
|
|
26580
26306
|
if (finding.recommended_focus) {
|
|
26581
26307
|
console.log(
|
|
26582
|
-
" " + chalk25.dim("How this works: ") + paint("accent", `/
|
|
26308
|
+
" " + chalk25.dim("How this works: ") + paint("accent", `/tour ${finding.recommended_focus}`)
|
|
26583
26309
|
);
|
|
26584
26310
|
}
|
|
26585
26311
|
}
|
|
@@ -31203,8 +30929,8 @@ var init_diagnose = __esm({
|
|
|
31203
30929
|
function summarizeEntityDetails(details = []) {
|
|
31204
30930
|
const summary = {};
|
|
31205
30931
|
for (const detail of details) {
|
|
31206
|
-
const
|
|
31207
|
-
summary[
|
|
30932
|
+
const issue = typeof detail.issue === "string" ? detail.issue : "unclassified";
|
|
30933
|
+
summary[issue] = (summary[issue] ?? 0) + 1;
|
|
31208
30934
|
}
|
|
31209
30935
|
return summary;
|
|
31210
30936
|
}
|
|
@@ -31684,8 +31410,8 @@ async function handleGetVitalSignDetail(input, ctx) {
|
|
|
31684
31410
|
if (!vital) return { error: `Vital sign '${vitalSign}' not found` };
|
|
31685
31411
|
const entitySummary = {};
|
|
31686
31412
|
for (const detail of vital.entity_details) {
|
|
31687
|
-
const
|
|
31688
|
-
const key =
|
|
31413
|
+
const issue = detail.issue;
|
|
31414
|
+
const key = issue ?? "unclassified";
|
|
31689
31415
|
entitySummary[key] = (entitySummary[key] ?? 0) + 1;
|
|
31690
31416
|
}
|
|
31691
31417
|
return {
|
|
@@ -32000,27 +31726,6 @@ async function handleGetCounselDetail(input) {
|
|
|
32000
31726
|
}
|
|
32001
31727
|
return formatCounselDetail2(pack, getActiveOrgTree2());
|
|
32002
31728
|
}
|
|
32003
|
-
async function handleGetRoleCard(input) {
|
|
32004
|
-
const roleId = typeof input.role_id === "string" ? input.role_id.trim() : "";
|
|
32005
|
-
const {
|
|
32006
|
-
getRoleCardById: getRoleCardById2,
|
|
32007
|
-
listRoleCardIds: listRoleCardIds2,
|
|
32008
|
-
formatRoleCardDetail: formatRoleCardDetail2,
|
|
32009
|
-
getActiveOrgTree: getActiveOrgTree2,
|
|
32010
|
-
listOrgTreeIds: listOrgTreeIds2
|
|
32011
|
-
} = await Promise.resolve().then(() => (init_gtm_counsel(), gtm_counsel_exports));
|
|
32012
|
-
const card = roleId ? getRoleCardById2(roleId) : void 0;
|
|
32013
|
-
if (!card) {
|
|
32014
|
-
return {
|
|
32015
|
-
error: `Unknown role id '${roleId}'.`,
|
|
32016
|
-
valid_role_ids: listRoleCardIds2()
|
|
32017
|
-
};
|
|
32018
|
-
}
|
|
32019
|
-
const treeArg = typeof input.org_tree === "string" ? input.org_tree.trim() : "";
|
|
32020
|
-
const trees = listOrgTreeIds2();
|
|
32021
|
-
const tree = trees.includes(treeArg) ? treeArg : getActiveOrgTree2();
|
|
32022
|
-
return formatRoleCardDetail2(card, tree);
|
|
32023
|
-
}
|
|
32024
31729
|
async function handleGetRevenueMetrics(_input, ctx) {
|
|
32025
31730
|
if (!ctx.metrics || ctx.metrics.length === 0) {
|
|
32026
31731
|
const { computeFullMetrics: computeFullMetrics2 } = await Promise.resolve().then(() => (init_compute(), compute_exports));
|
|
@@ -32317,7 +32022,6 @@ var init_tool_handlers = __esm({
|
|
|
32317
32022
|
get_play_detail: (input, _) => handleGetPlayDetail(input),
|
|
32318
32023
|
get_framework_detail: (input, _) => handleGetFrameworkDetail(input),
|
|
32319
32024
|
get_counsel_detail: (input, _) => handleGetCounselDetail(input),
|
|
32320
|
-
get_role_card: (input, _) => handleGetRoleCard(input),
|
|
32321
32025
|
get_session_brief: (input, _) => handleGetSessionBrief(input),
|
|
32322
32026
|
get_revenue_metrics: handleGetRevenueMetrics,
|
|
32323
32027
|
get_revenue_metrics_timeseries: (input, _) => handleGetRevenueMetricsTimeseries(input),
|