@sonnechasser/ntrp 1.5.8 → 1.8.0
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 +2617 -111
- package/dist/mcp/server.js +9131 -6621
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -7530,6 +7530,171 @@ var init_untrusted = __esm({
|
|
|
7530
7530
|
}
|
|
7531
7531
|
});
|
|
7532
7532
|
|
|
7533
|
+
// src/data/gtm-counsel/play-routing.ts
|
|
7534
|
+
function getPlayRouting(playId) {
|
|
7535
|
+
return PLAY_ROUTING[playId];
|
|
7536
|
+
}
|
|
7537
|
+
function primaryOwnerForPlay(playId) {
|
|
7538
|
+
return getPlayRouting(playId)?.primary_owner ?? null;
|
|
7539
|
+
}
|
|
7540
|
+
function counselIdsForPlay(playId) {
|
|
7541
|
+
const ids = getPlayRouting(playId)?.counsel_ids;
|
|
7542
|
+
return ids ? [...ids] : [];
|
|
7543
|
+
}
|
|
7544
|
+
function graphForPlay(playId) {
|
|
7545
|
+
return getPlayRouting(playId)?.graph;
|
|
7546
|
+
}
|
|
7547
|
+
function playRoutingIntegrityIssues(validPlayIds3) {
|
|
7548
|
+
const issues = [];
|
|
7549
|
+
for (const [playId, routing] of Object.entries(PLAY_ROUTING)) {
|
|
7550
|
+
if (validPlayIds3 && !validPlayIds3.has(playId)) {
|
|
7551
|
+
issues.push(`${playId}: routing has no built-in play`);
|
|
7552
|
+
}
|
|
7553
|
+
if (routing.counsel_ids[0] !== routing.primary_owner) {
|
|
7554
|
+
issues.push(`${playId}: primary_owner must be counsel_ids[0]`);
|
|
7555
|
+
}
|
|
7556
|
+
if (new Set(routing.counsel_ids).size !== routing.counsel_ids.length) {
|
|
7557
|
+
issues.push(`${playId}: duplicate counsel_ids`);
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
return issues;
|
|
7561
|
+
}
|
|
7562
|
+
var PLAY_ROUTING, PLAY_COUNSEL_HOOKS;
|
|
7563
|
+
var init_play_routing = __esm({
|
|
7564
|
+
"src/data/gtm-counsel/play-routing.ts"() {
|
|
7565
|
+
"use strict";
|
|
7566
|
+
PLAY_ROUTING = {
|
|
7567
|
+
"multi-thread-deals": {
|
|
7568
|
+
primary_owner: "counsel_sales",
|
|
7569
|
+
counsel_ids: ["counsel_sales", "counsel_exec"],
|
|
7570
|
+
constraint_signals: ["thread_depth"]
|
|
7571
|
+
},
|
|
7572
|
+
"clean-dead-pipeline": {
|
|
7573
|
+
primary_owner: "counsel_sales",
|
|
7574
|
+
counsel_ids: ["counsel_sales", "counsel_revops"],
|
|
7575
|
+
constraint_signals: ["freshness"]
|
|
7576
|
+
},
|
|
7577
|
+
"fix-handoff-gap": {
|
|
7578
|
+
primary_owner: "counsel_revops",
|
|
7579
|
+
counsel_ids: ["counsel_revops", "counsel_marketing", "counsel_sales"],
|
|
7580
|
+
constraint_signals: ["drop_rate"],
|
|
7581
|
+
graph: {
|
|
7582
|
+
exclusive_group: "handoff_repair",
|
|
7583
|
+
next_if: [
|
|
7584
|
+
{
|
|
7585
|
+
play_id: "harden-routing-sla",
|
|
7586
|
+
when: "The source-to-owner path is known but SLA timers, queues, or acceptance monitoring remain broken."
|
|
7587
|
+
}
|
|
7588
|
+
]
|
|
7589
|
+
}
|
|
7590
|
+
},
|
|
7591
|
+
"retarget-effort": {
|
|
7592
|
+
primary_owner: "counsel_sales",
|
|
7593
|
+
counsel_ids: ["counsel_sales", "counsel_marketing", "counsel_revops"],
|
|
7594
|
+
constraint_signals: ["signal_to_noise"]
|
|
7595
|
+
},
|
|
7596
|
+
"unstick-pipeline": {
|
|
7597
|
+
primary_owner: "counsel_sales",
|
|
7598
|
+
counsel_ids: ["counsel_sales", "counsel_exec"],
|
|
7599
|
+
constraint_signals: ["flow_rate"]
|
|
7600
|
+
},
|
|
7601
|
+
"reduce-logo-churn": {
|
|
7602
|
+
primary_owner: "counsel_cs",
|
|
7603
|
+
counsel_ids: ["counsel_cs", "counsel_exec"],
|
|
7604
|
+
constraint_signals: ["grr"]
|
|
7605
|
+
},
|
|
7606
|
+
"accelerate-expansion": {
|
|
7607
|
+
primary_owner: "counsel_cs",
|
|
7608
|
+
counsel_ids: ["counsel_cs", "counsel_sales"],
|
|
7609
|
+
constraint_signals: ["nrr"]
|
|
7610
|
+
},
|
|
7611
|
+
"fix-renewal-process": {
|
|
7612
|
+
primary_owner: "counsel_cs",
|
|
7613
|
+
counsel_ids: ["counsel_cs", "counsel_revops"],
|
|
7614
|
+
constraint_signals: ["contraction_arr"],
|
|
7615
|
+
graph: {
|
|
7616
|
+
next_if: [
|
|
7617
|
+
{
|
|
7618
|
+
play_id: "renewal-early-warning",
|
|
7619
|
+
when: "Leading risk indicators are absent, unmonitored, or first appear inside 30 days."
|
|
7620
|
+
}
|
|
7621
|
+
]
|
|
7622
|
+
}
|
|
7623
|
+
},
|
|
7624
|
+
"rebalance-pipeline-mix": {
|
|
7625
|
+
primary_owner: "counsel_marketing",
|
|
7626
|
+
counsel_ids: ["counsel_marketing", "counsel_sales", "counsel_exec"],
|
|
7627
|
+
constraint_signals: ["pipeline_coverage"]
|
|
7628
|
+
},
|
|
7629
|
+
"compress-sales-cycle": {
|
|
7630
|
+
primary_owner: "counsel_sales",
|
|
7631
|
+
counsel_ids: ["counsel_sales", "counsel_marketing"],
|
|
7632
|
+
constraint_signals: ["avg_sales_cycle", "flow_rate"]
|
|
7633
|
+
},
|
|
7634
|
+
"improve-magic-number": {
|
|
7635
|
+
primary_owner: "counsel_marketing",
|
|
7636
|
+
counsel_ids: ["counsel_marketing", "counsel_exec", "counsel_revops"],
|
|
7637
|
+
constraint_signals: ["magic_number"]
|
|
7638
|
+
},
|
|
7639
|
+
"harden-routing-sla": {
|
|
7640
|
+
primary_owner: "counsel_revops",
|
|
7641
|
+
counsel_ids: ["counsel_revops", "counsel_marketing", "counsel_sales"],
|
|
7642
|
+
constraint_signals: ["drop_rate"],
|
|
7643
|
+
graph: {
|
|
7644
|
+
prerequisites: [
|
|
7645
|
+
{
|
|
7646
|
+
play_id: "fix-handoff-gap",
|
|
7647
|
+
reason: "Map the source-to-owner path before installing SLA timers and observability."
|
|
7648
|
+
}
|
|
7649
|
+
],
|
|
7650
|
+
exclusive_group: "handoff_repair"
|
|
7651
|
+
}
|
|
7652
|
+
},
|
|
7653
|
+
"demand-quality-over-volume": {
|
|
7654
|
+
primary_owner: "counsel_marketing",
|
|
7655
|
+
counsel_ids: ["counsel_marketing", "counsel_revops"],
|
|
7656
|
+
constraint_signals: ["mql_to_opp", "cost_per_pipeline", "magic_number"],
|
|
7657
|
+
graph: {
|
|
7658
|
+
blocked_by: [
|
|
7659
|
+
{
|
|
7660
|
+
signal: "drop_rate",
|
|
7661
|
+
status: "red",
|
|
7662
|
+
reason: "Do not increase demand spend until the routing path and acceptance SLA are trustworthy."
|
|
7663
|
+
}
|
|
7664
|
+
]
|
|
7665
|
+
}
|
|
7666
|
+
},
|
|
7667
|
+
"sales-cs-handoff-packet": {
|
|
7668
|
+
primary_owner: "counsel_cs",
|
|
7669
|
+
counsel_ids: ["counsel_cs", "counsel_sales", "counsel_revops"],
|
|
7670
|
+
constraint_signals: ["grr", "packet_field_completeness"]
|
|
7671
|
+
},
|
|
7672
|
+
"renewal-early-warning": {
|
|
7673
|
+
primary_owner: "counsel_cs",
|
|
7674
|
+
counsel_ids: ["counsel_cs", "counsel_revops"],
|
|
7675
|
+
constraint_signals: ["grr", "early_warning_lead_time"]
|
|
7676
|
+
},
|
|
7677
|
+
"abm-orchestration": {
|
|
7678
|
+
primary_owner: "counsel_marketing",
|
|
7679
|
+
counsel_ids: ["counsel_marketing", "counsel_sales"],
|
|
7680
|
+
constraint_signals: ["pipeline_coverage", "named_list_pipeline", "named_list_win_rate"],
|
|
7681
|
+
graph: {
|
|
7682
|
+
tree_exclude: ["smb_velocity"],
|
|
7683
|
+
required_evidence: ["named_account_process"]
|
|
7684
|
+
}
|
|
7685
|
+
},
|
|
7686
|
+
"forecast-ritual-hygiene": {
|
|
7687
|
+
primary_owner: "counsel_revops",
|
|
7688
|
+
counsel_ids: ["counsel_revops", "counsel_sales", "counsel_exec"],
|
|
7689
|
+
constraint_signals: ["freshness", "flow_rate", "weighted_pipeline", "forecast_commit_history"]
|
|
7690
|
+
}
|
|
7691
|
+
};
|
|
7692
|
+
PLAY_COUNSEL_HOOKS = Object.fromEntries(
|
|
7693
|
+
Object.entries(PLAY_ROUTING).map(([id, routing]) => [id, [...routing.counsel_ids]])
|
|
7694
|
+
);
|
|
7695
|
+
}
|
|
7696
|
+
});
|
|
7697
|
+
|
|
7533
7698
|
// src/data/playbook.ts
|
|
7534
7699
|
var playbook_exports = {};
|
|
7535
7700
|
__export(playbook_exports, {
|
|
@@ -7583,6 +7748,8 @@ function addCustomPlay(input) {
|
|
|
7583
7748
|
steps: input.steps,
|
|
7584
7749
|
tools_that_help: input.tools_that_help ?? [],
|
|
7585
7750
|
expected_outcome: input.expected_outcome ?? "Improvement in the targeted vital sign",
|
|
7751
|
+
exam: input.exam,
|
|
7752
|
+
routing: input.routing,
|
|
7586
7753
|
source: "learned"
|
|
7587
7754
|
};
|
|
7588
7755
|
try {
|
|
@@ -7620,9 +7787,16 @@ function withKnownRecommendedPlays(finding) {
|
|
|
7620
7787
|
if (next === finding.recommended_plays) return finding;
|
|
7621
7788
|
return { ...finding, recommended_plays: next };
|
|
7622
7789
|
}
|
|
7623
|
-
function matchTriggeredPlays(vitals, layers) {
|
|
7790
|
+
function matchTriggeredPlays(vitals, layers, options = {}) {
|
|
7624
7791
|
const bySign = new Map(vitals.map((v) => [v.vital_sign, v]));
|
|
7625
7792
|
const out = [];
|
|
7793
|
+
const selectedIds = /* @__PURE__ */ new Set();
|
|
7794
|
+
const selectedGroups = /* @__PURE__ */ new Set();
|
|
7795
|
+
const evidence = new Set(options.evidence ?? []);
|
|
7796
|
+
const statuses = {
|
|
7797
|
+
...Object.fromEntries(vitals.map((v) => [v.vital_sign, v.status])),
|
|
7798
|
+
...options.signalStatuses ?? {}
|
|
7799
|
+
};
|
|
7626
7800
|
for (const layer of layers) {
|
|
7627
7801
|
for (const sign of layer.signs) {
|
|
7628
7802
|
const vital = bySign.get(sign);
|
|
@@ -7631,7 +7805,26 @@ function matchTriggeredPlays(vitals, layers) {
|
|
|
7631
7805
|
if (!fires) continue;
|
|
7632
7806
|
for (const play of getAllPlays()) {
|
|
7633
7807
|
if (play.trigger_vital_sign === sign) {
|
|
7808
|
+
const graph = (getPlayRouting(play.id) ?? play.routing)?.graph;
|
|
7809
|
+
if (options.tree && graph?.tree_exclude?.includes(options.tree) && !(graph.required_evidence ?? []).every((item) => evidence.has(item))) {
|
|
7810
|
+
continue;
|
|
7811
|
+
}
|
|
7812
|
+
if (graph?.blocked_by?.some(
|
|
7813
|
+
(block) => statuses[String(block.signal)] === block.status
|
|
7814
|
+
)) {
|
|
7815
|
+
continue;
|
|
7816
|
+
}
|
|
7817
|
+
if (graph?.exclusive_group && selectedGroups.has(graph.exclusive_group)) {
|
|
7818
|
+
continue;
|
|
7819
|
+
}
|
|
7820
|
+
if (graph?.prerequisites?.some(
|
|
7821
|
+
(requirement) => !selectedIds.has(requirement.play_id)
|
|
7822
|
+
)) {
|
|
7823
|
+
continue;
|
|
7824
|
+
}
|
|
7634
7825
|
out.push({ play, vital, layer: layer.layer });
|
|
7826
|
+
selectedIds.add(play.id);
|
|
7827
|
+
if (graph?.exclusive_group) selectedGroups.add(graph.exclusive_group);
|
|
7635
7828
|
}
|
|
7636
7829
|
}
|
|
7637
7830
|
}
|
|
@@ -7643,6 +7836,7 @@ var init_playbook = __esm({
|
|
|
7643
7836
|
"src/data/playbook.ts"() {
|
|
7644
7837
|
"use strict";
|
|
7645
7838
|
init_store();
|
|
7839
|
+
init_play_routing();
|
|
7646
7840
|
PLAYBOOK = [
|
|
7647
7841
|
{
|
|
7648
7842
|
id: "multi-thread-deals",
|
|
@@ -7658,7 +7852,13 @@ var init_playbook = __esm({
|
|
|
7658
7852
|
"Set an alert when a mid-stage or later deal has one active contact."
|
|
7659
7853
|
],
|
|
7660
7854
|
tools_that_help: ["Buying-committee enrichment (waterfall)", "Job-change signal tracking", "CRM contact roles", "Single-thread alerts"],
|
|
7661
|
-
expected_outcome: "Thread Depth
|
|
7855
|
+
expected_outcome: "Thread Depth reaches 2+ on late-stage deals; single-threaded deal count drops 50% or more within 2 weeks (instrument: thread_depth).",
|
|
7856
|
+
exam: {
|
|
7857
|
+
instrument_ids: ["thread_depth"],
|
|
7858
|
+
target_guidance: "Thread depth \u22652 on late-stage deals; single-threaded count \u221250% or more.",
|
|
7859
|
+
check_window_days: 14,
|
|
7860
|
+
measurement_mode: "native"
|
|
7861
|
+
}
|
|
7662
7862
|
},
|
|
7663
7863
|
{
|
|
7664
7864
|
id: "clean-dead-pipeline",
|
|
@@ -7674,7 +7874,13 @@ var init_playbook = __esm({
|
|
|
7674
7874
|
"Set a stale-deal alert at N quiet days. Calibrate N to this motion's cycle."
|
|
7675
7875
|
],
|
|
7676
7876
|
tools_that_help: ["CRM bulk update", "Signal-based reactivation triggers", "Enrichment refresh (waterfall)", "Pipeline hygiene cadence"],
|
|
7677
|
-
expected_outcome: "Freshness
|
|
7877
|
+
expected_outcome: "Freshness rises 20 points or more and stale pipeline dollars fall 40\u201360% within 30 days (instrument: freshness).",
|
|
7878
|
+
exam: {
|
|
7879
|
+
instrument_ids: ["freshness"],
|
|
7880
|
+
target_guidance: "Freshness +20 points; stale pipeline dollars \u221240\u201360%.",
|
|
7881
|
+
check_window_days: 30,
|
|
7882
|
+
measurement_mode: "native"
|
|
7883
|
+
}
|
|
7678
7884
|
},
|
|
7679
7885
|
{
|
|
7680
7886
|
id: "fix-handoff-gap",
|
|
@@ -7685,12 +7891,19 @@ var init_playbook = __esm({
|
|
|
7685
7891
|
steps: [
|
|
7686
7892
|
"Audit the leak by source. Find which lead sources never reach the CRM or a rep queue.",
|
|
7687
7893
|
"Trace the routing path: assignment rules, territory, inactive-rep queues, and the marketing to CRM sync.",
|
|
7688
|
-
"
|
|
7689
|
-
"
|
|
7690
|
-
"Set a weekly
|
|
7894
|
+
"Name the failure class per source: never arrived, late, wrong owner, or unused in queue.",
|
|
7895
|
+
"Repair the broken path: reassign orphaned queues, dedupe/enrich routing fields, fix the sync gap.",
|
|
7896
|
+
"Set a weekly source\u2192owner path report. Alert when any source handoff rate drops.",
|
|
7897
|
+
"If timers, unassigned age, or observability are the leak, switch to Harden Routing & Acceptance SLA (harden-routing-sla). Do not treat this play as the SLA install."
|
|
7691
7898
|
],
|
|
7692
|
-
tools_that_help: ["Lead routing audit", "Enrichment waterfall (routing fields)", "
|
|
7693
|
-
expected_outcome: "Drop Rate
|
|
7899
|
+
tools_that_help: ["Lead routing audit", "Enrichment waterfall (routing fields)", "Source\u2192owner path report", "Handoff-degradation alerts"],
|
|
7900
|
+
expected_outcome: "Drop Rate +15 or more (instrument: drop_rate). Marketing-only lead share \u221260% or more (instrument: source\u2192owner path count). Time-to-first-touch is not this play's exam \u2014 that lives on harden-routing-sla.",
|
|
7901
|
+
exam: {
|
|
7902
|
+
instrument_ids: ["drop_rate"],
|
|
7903
|
+
target_guidance: "Drop Rate +15 points; marketing-only lead share \u221260% or more.",
|
|
7904
|
+
check_window_days: 30,
|
|
7905
|
+
measurement_mode: "native"
|
|
7906
|
+
}
|
|
7694
7907
|
},
|
|
7695
7908
|
{
|
|
7696
7909
|
id: "retarget-effort",
|
|
@@ -7706,7 +7919,13 @@ var init_playbook = __esm({
|
|
|
7706
7919
|
"Automate or delete noise work such as logging, list building, and manual research."
|
|
7707
7920
|
],
|
|
7708
7921
|
tools_that_help: ["Activity reports by rep", "ICP/propensity scoring", "Signal routing to rep channels", "Enrichment automation"],
|
|
7709
|
-
expected_outcome: "Signal-to-Noise
|
|
7922
|
+
expected_outcome: "Signal-to-Noise reaches 70% or more and misdirected effort dollars fall 30\u201350% within 30 days (instrument: signal_to_noise).",
|
|
7923
|
+
exam: {
|
|
7924
|
+
instrument_ids: ["signal_to_noise"],
|
|
7925
|
+
target_guidance: "Signal-to-Noise \u226570%; misdirected effort dollars \u221230\u201350%.",
|
|
7926
|
+
check_window_days: 30,
|
|
7927
|
+
measurement_mode: "native"
|
|
7928
|
+
}
|
|
7710
7929
|
},
|
|
7711
7930
|
{
|
|
7712
7931
|
id: "unstick-pipeline",
|
|
@@ -7722,7 +7941,13 @@ var init_playbook = __esm({
|
|
|
7722
7941
|
"Set an aging alert at the motion-calibrated threshold. Escalate past 2 times median stage duration."
|
|
7723
7942
|
],
|
|
7724
7943
|
tools_that_help: ["Deal inspection reports", "Stage exit criteria", "Aging alerts", "Manager escalation workflow"],
|
|
7725
|
-
expected_outcome: "Flow Rate
|
|
7944
|
+
expected_outcome: "Flow Rate improves 15 points or more and stuck deal count falls 40% or more within 2 weeks (instrument: flow_rate).",
|
|
7945
|
+
exam: {
|
|
7946
|
+
instrument_ids: ["flow_rate"],
|
|
7947
|
+
target_guidance: "Flow Rate +15 points; stuck deal count \u221240% or more.",
|
|
7948
|
+
check_window_days: 14,
|
|
7949
|
+
measurement_mode: "native"
|
|
7950
|
+
}
|
|
7726
7951
|
},
|
|
7727
7952
|
{
|
|
7728
7953
|
id: "reduce-logo-churn",
|
|
@@ -7739,7 +7964,13 @@ var init_playbook = __esm({
|
|
|
7739
7964
|
"Set early-warning triggers 90 days before renewal."
|
|
7740
7965
|
],
|
|
7741
7966
|
tools_that_help: ["CS platform", "Renewal calendar", "NPS/CSAT surveys"],
|
|
7742
|
-
expected_outcome: "GRR
|
|
7967
|
+
expected_outcome: "GRR closes 25\u201350% of the gap to the motion benchmark within 2 quarters (instrument: grr).",
|
|
7968
|
+
exam: {
|
|
7969
|
+
instrument_ids: ["grr"],
|
|
7970
|
+
target_guidance: "Close 25\u201350% of the GRR benchmark gap.",
|
|
7971
|
+
check_window_days: 180,
|
|
7972
|
+
measurement_mode: "native"
|
|
7973
|
+
}
|
|
7743
7974
|
},
|
|
7744
7975
|
{
|
|
7745
7976
|
id: "accelerate-expansion",
|
|
@@ -7756,7 +7987,13 @@ var init_playbook = __esm({
|
|
|
7756
7987
|
"Track expansion pipeline apart from new business."
|
|
7757
7988
|
],
|
|
7758
7989
|
tools_that_help: ["Account plans", "Usage analytics", "Expansion playbooks"],
|
|
7759
|
-
expected_outcome: "Expansion ARR grows
|
|
7990
|
+
expected_outcome: "Expansion ARR grows 15\u201325% quarter over quarter without GRR decline (instrument: nrr).",
|
|
7991
|
+
exam: {
|
|
7992
|
+
instrument_ids: ["nrr"],
|
|
7993
|
+
target_guidance: "Expansion ARR +15\u201325% quarter over quarter; GRR does not decline.",
|
|
7994
|
+
check_window_days: 90,
|
|
7995
|
+
measurement_mode: "native"
|
|
7996
|
+
}
|
|
7760
7997
|
},
|
|
7761
7998
|
{
|
|
7762
7999
|
id: "fix-renewal-process",
|
|
@@ -7768,12 +8005,20 @@ var init_playbook = __esm({
|
|
|
7768
8005
|
steps: [
|
|
7769
8006
|
"List all contraction events. Categorize the root cause.",
|
|
7770
8007
|
"Set a standard renewal timeline: 120, 90, 60, and 30-day checkpoints.",
|
|
8008
|
+
"Define leading risk indicators (champion change, usage drop, open severity tickets) and flag them 60\u201390 days before renewal.",
|
|
7771
8009
|
"Engage the economic buyer before the renewal date.",
|
|
7772
8010
|
"Make an ROI recap deck template for every renewal.",
|
|
7773
|
-
"Escalate contractions above 20% to leadership review."
|
|
8011
|
+
"Escalate contractions above 20% to leadership review.",
|
|
8012
|
+
"Pair with Renewal Early Warning (renewal-early-warning) when leading indicators are missing or unmonitored."
|
|
7774
8013
|
],
|
|
7775
8014
|
tools_that_help: ["Renewal workflow", "QBR templates", "Value realization reports"],
|
|
7776
|
-
expected_outcome: "Contraction ARR drops
|
|
8015
|
+
expected_outcome: "Contraction ARR drops 30\u201350% within 2 quarters (instrument: contraction_arr).",
|
|
8016
|
+
exam: {
|
|
8017
|
+
instrument_ids: ["contraction_arr"],
|
|
8018
|
+
target_guidance: "Contraction ARR \u221230\u201350%.",
|
|
8019
|
+
check_window_days: 180,
|
|
8020
|
+
measurement_mode: "native"
|
|
8021
|
+
}
|
|
7777
8022
|
},
|
|
7778
8023
|
{
|
|
7779
8024
|
id: "rebalance-pipeline-mix",
|
|
@@ -7790,7 +8035,13 @@ var init_playbook = __esm({
|
|
|
7790
8035
|
"Review discounting and stage inflation that hide a thin pipeline."
|
|
7791
8036
|
],
|
|
7792
8037
|
tools_that_help: ["Pipeline analytics", "Marketing attribution", "Capacity planning"],
|
|
7793
|
-
expected_outcome: "Pipeline coverage
|
|
8038
|
+
expected_outcome: "Pipeline coverage closes 25\u201350% of the benchmark gap within 90 days (instrument: pipeline_coverage).",
|
|
8039
|
+
exam: {
|
|
8040
|
+
instrument_ids: ["pipeline_coverage"],
|
|
8041
|
+
target_guidance: "Close 25\u201350% of the pipeline-coverage benchmark gap.",
|
|
8042
|
+
check_window_days: 90,
|
|
8043
|
+
measurement_mode: "native"
|
|
8044
|
+
}
|
|
7794
8045
|
},
|
|
7795
8046
|
{
|
|
7796
8047
|
id: "compress-sales-cycle",
|
|
@@ -7807,7 +8058,13 @@ var init_playbook = __esm({
|
|
|
7807
8058
|
"Remove low-probability aged deals to free rep capacity."
|
|
7808
8059
|
],
|
|
7809
8060
|
tools_that_help: ["Stage duration reports", "MAP templates", "Deal coaching"],
|
|
7810
|
-
expected_outcome: "Median cycle
|
|
8061
|
+
expected_outcome: "Median sales cycle drops 15\u201325% within one quarter (instrument: avg_sales_cycle).",
|
|
8062
|
+
exam: {
|
|
8063
|
+
instrument_ids: ["avg_sales_cycle"],
|
|
8064
|
+
target_guidance: "Median sales cycle \u221215\u201325%.",
|
|
8065
|
+
check_window_days: 90,
|
|
8066
|
+
measurement_mode: "native"
|
|
8067
|
+
}
|
|
7811
8068
|
},
|
|
7812
8069
|
{
|
|
7813
8070
|
id: "improve-magic-number",
|
|
@@ -7824,7 +8081,150 @@ var init_playbook = __esm({
|
|
|
7824
8081
|
"Review rep ramp time and quota attainment curves."
|
|
7825
8082
|
],
|
|
7826
8083
|
tools_that_help: ["Finance model", "Channel ROI dashboard", "CAC by source"],
|
|
7827
|
-
expected_outcome: "Magic number
|
|
8084
|
+
expected_outcome: "Magic number closes 25\u201350% of the motion-benchmark gap within 2 quarters (instrument: magic_number).",
|
|
8085
|
+
exam: {
|
|
8086
|
+
instrument_ids: ["magic_number"],
|
|
8087
|
+
target_guidance: "Close 25\u201350% of the magic-number benchmark gap.",
|
|
8088
|
+
check_window_days: 180,
|
|
8089
|
+
measurement_mode: "native"
|
|
8090
|
+
}
|
|
8091
|
+
},
|
|
8092
|
+
{
|
|
8093
|
+
id: "harden-routing-sla",
|
|
8094
|
+
name: "Harden Routing & Acceptance SLA",
|
|
8095
|
+
trigger_vital_sign: "drop_rate",
|
|
8096
|
+
trigger_condition: "Handoff path exists but SLA timers, unassigned queues, or acceptance monitoring are missing or breached. Use fix-handoff-gap first when the source\u2192owner path itself is unknown.",
|
|
8097
|
+
why: "Handoff leaks that survive a path audit are usually SLA and observability failures: unassigned queues, dead routers, silent sync errors, and no timer on first touch. More demand spend cannot fix a broken pipe.",
|
|
8098
|
+
steps: [
|
|
8099
|
+
"Map lead\u2192owner path with timers: create, sync, assign, first touch, accept/reject.",
|
|
8100
|
+
"Instrument unassigned age and SLA breach alerts with a named RevOps owner.",
|
|
8101
|
+
"Fix routing rules and inactive-rep queues before launching new campaigns.",
|
|
8102
|
+
"Publish acceptance reasons AEs must use; review weekly with Demand and SDR leads.",
|
|
8103
|
+
"Prove the fix held for 30 days with monitoring \u2014 do not declare victory after a one-week cleanup."
|
|
8104
|
+
],
|
|
8105
|
+
tools_that_help: ["CRM assignment logs", "SLA dashboards", "Sync error queues", "Enrichment for routing fields"],
|
|
8106
|
+
expected_outcome: "Median unassigned age inside the published SLA within 30 days (instrument: assignment logs). Drop Rate +10 or more after a 30-day hold (instrument: drop_rate). \u226580% of rejects carry an acceptance reason.",
|
|
8107
|
+
exam: {
|
|
8108
|
+
instrument_ids: ["drop_rate"],
|
|
8109
|
+
target_guidance: "Drop Rate +10 points; median unassigned age inside SLA; rejection reasons \u226580%.",
|
|
8110
|
+
check_window_days: 30,
|
|
8111
|
+
measurement_mode: "native"
|
|
8112
|
+
}
|
|
8113
|
+
},
|
|
8114
|
+
{
|
|
8115
|
+
id: "demand-quality-over-volume",
|
|
8116
|
+
name: "Demand Quality over Volume",
|
|
8117
|
+
trigger_metric: "magic_number",
|
|
8118
|
+
trigger_lens: "revenue_metrics",
|
|
8119
|
+
trigger_condition: "MQL\u2192SQL or SQL\u2192Opp conversion is weak, or accepted-pipeline $ lags MQL volume. Not for routing/SLA leaks \u2014 use fix-handoff-gap / harden-routing-sla when drop_rate is the gating vital.",
|
|
8120
|
+
why: "Optimizing for MQL count burns budget and trust. Dollars come from accepted pipeline that closes \u2014 not form fills Sales will not work.",
|
|
8121
|
+
steps: [
|
|
8122
|
+
"Kill rule: if drop_rate is red, do not increase media spend until the handoff path and acceptance SLA are trustworthy.",
|
|
8123
|
+
"Cut or pause channels whose pipeline does not close; keep channels with proven opp\u2192won.",
|
|
8124
|
+
"Tighten scoring and ICP gates with RevOps; publish disqual rules to SDR/AE.",
|
|
8125
|
+
"Set exams on MQL\u2192SQL\u2192Opp $ and cost per pipeline $, not MQL count.",
|
|
8126
|
+
"Align Demand, SDR, and AE on one definition of accepted handoff."
|
|
8127
|
+
],
|
|
8128
|
+
tools_that_help: ["Attribution with agreed rules", "Scoring models", "Channel ROI", "Rejection reason reports"],
|
|
8129
|
+
expected_outcome: "MQL\u2192Opp conversion +20% or more, or cost per accepted pipeline $ \u221220% or more, within one quarter (instruments: mql_to_opp, cost_per_pipeline). MQL count is not the exam.",
|
|
8130
|
+
exam: {
|
|
8131
|
+
instrument_ids: ["mql_to_opp", "cost_per_pipeline"],
|
|
8132
|
+
target_guidance: "MQL\u2192Opp +20% or cost per accepted pipeline dollar \u221220%.",
|
|
8133
|
+
check_window_days: 90,
|
|
8134
|
+
measurement_mode: "external"
|
|
8135
|
+
}
|
|
8136
|
+
},
|
|
8137
|
+
{
|
|
8138
|
+
id: "sales-cs-handoff-packet",
|
|
8139
|
+
name: "Sales\u2192CS Handoff Packet",
|
|
8140
|
+
trigger_lens: "revenue_metrics",
|
|
8141
|
+
trigger_metric: "grr",
|
|
8142
|
+
trigger_condition: "Churn or rocky onboarding traces to incomplete sales handoffs, oversell, or missing implementation readiness",
|
|
8143
|
+
why: "Won deals that arrive without scope, success criteria, champion map, or implementation readiness become churn and contraction. The leak is after the booking, not before.",
|
|
8144
|
+
steps: [
|
|
8145
|
+
"Define a minimum handoff packet: ICP fit, sold scope, success criteria, champion/EB contacts, known risks, close notes.",
|
|
8146
|
+
"Block or flag Closed-Won without packet fields (observe + recommend field capture if instruments are thin).",
|
|
8147
|
+
"Involve CS early on complex or high-ACV deals before signature.",
|
|
8148
|
+
"Audit recent churn/contraction for missing handoff evidence; feed findings to Sales managers.",
|
|
8149
|
+
"Review packet completion weekly in Sales + CS ops until compliance holds."
|
|
8150
|
+
],
|
|
8151
|
+
tools_that_help: ["CRM required fields", "CS handoff checklist", "Onboarding readiness score"],
|
|
8152
|
+
expected_outcome: "Handoff packet completion \u226590% of Closed-Won within 30 days (instrument: packet field completeness). Early-tenure logo churn attributed to oversell/missing context \u221230% or more within two quarters (instrument: grr / tenure-cut churn). Observe + recommend field capture if packet fields do not exist yet.",
|
|
8153
|
+
exam: {
|
|
8154
|
+
instrument_ids: ["packet_field_completeness", "grr"],
|
|
8155
|
+
target_guidance: "Packet completion \u226590%; early-tenure handoff-attributed churn \u221230% or more.",
|
|
8156
|
+
check_window_days: 180,
|
|
8157
|
+
measurement_mode: "capture_required"
|
|
8158
|
+
}
|
|
8159
|
+
},
|
|
8160
|
+
{
|
|
8161
|
+
id: "renewal-early-warning",
|
|
8162
|
+
name: "Renewal Early Warning",
|
|
8163
|
+
trigger_metric: "grr",
|
|
8164
|
+
trigger_lens: "revenue_metrics",
|
|
8165
|
+
trigger_condition: "Renewals scramble late, GRR soft, or risk flags appear only inside 30 days",
|
|
8166
|
+
why: "Last-week renewal heroics are a process failure. Leading indicators 60\u201390 days out beat discount addiction at the deadline.",
|
|
8167
|
+
steps: [
|
|
8168
|
+
"Define 3\u20135 leading risk indicators with owners (usage drop, champion change, open P1s, unpaid invoices).",
|
|
8169
|
+
"Surface risk on the renewal book at 90 and 60 days \u2014 not only at 30.",
|
|
8170
|
+
"Tie each red flag to a save play with a dated exam.",
|
|
8171
|
+
"Align Renewals, CSM, and RevOps on one system of record for renewal dates and risk.",
|
|
8172
|
+
"Review early-warning hit rate after each cohort; calibrate indicators that do not predict churn."
|
|
8173
|
+
],
|
|
8174
|
+
tools_that_help: ["Renewal calendar", "Health scores validated to churn", "CS risk workflow"],
|
|
8175
|
+
expected_outcome: "Share of the renewal book with a risk flag \u226560 days out reaches \u226580% (instrument: early_warning_lead_time). Emergency discounts on unflagged renewals \u221240% or more within two cohorts (instrument: contraction_arr / discount on late saves).",
|
|
8176
|
+
exam: {
|
|
8177
|
+
instrument_ids: ["early_warning_lead_time", "contraction_arr"],
|
|
8178
|
+
target_guidance: "Risk flags \u226560 days out on \u226580% of the book; emergency discounts \u221240%.",
|
|
8179
|
+
check_window_days: 120,
|
|
8180
|
+
measurement_mode: "capture_required"
|
|
8181
|
+
}
|
|
8182
|
+
},
|
|
8183
|
+
{
|
|
8184
|
+
id: "abm-orchestration",
|
|
8185
|
+
name: "ABM Orchestration on Named Accounts",
|
|
8186
|
+
trigger_lens: "revenue_metrics",
|
|
8187
|
+
trigger_metric: "pipeline_coverage",
|
|
8188
|
+
trigger_condition: "Enterprise or named-account motion with weak on-list pipeline. Not a default play for smb_velocity.",
|
|
8189
|
+
why: "ABM fails when it is spray with logos. It works when Marketing, SDR, and AE share one named list, plays, and account-level exams.",
|
|
8190
|
+
steps: [
|
|
8191
|
+
"Kill rule: do not run this play on smb_velocity without an agreed named-account list and AE commitment.",
|
|
8192
|
+
"Agree the named list and tiers with Sales; align to territories.",
|
|
8193
|
+
"Orchestrate plays across marketing + SDR + AE with clear owners per account.",
|
|
8194
|
+
"Measure pipeline and wins on the named list \u2014 not vanity engagement alone.",
|
|
8195
|
+
"Exit silent accounts on a schedule; do not fund forever."
|
|
8196
|
+
],
|
|
8197
|
+
tools_that_help: ["ABM platform", "Account plans", "Intent + engagement with RevOps definitions"],
|
|
8198
|
+
expected_outcome: "On-list pipeline $ +25% or more and on-list win rate +5 points or more within two quarters (instruments: named_list_pipeline, named_list_win_rate). Off-list activity share does not rise (instrument: signal_to_noise on named vs rest).",
|
|
8199
|
+
exam: {
|
|
8200
|
+
instrument_ids: ["named_list_pipeline", "named_list_win_rate", "signal_to_noise"],
|
|
8201
|
+
target_guidance: "On-list pipeline +25% or more; on-list win rate +5 points; no off-list noise increase.",
|
|
8202
|
+
check_window_days: 180,
|
|
8203
|
+
measurement_mode: "external"
|
|
8204
|
+
}
|
|
8205
|
+
},
|
|
8206
|
+
{
|
|
8207
|
+
id: "forecast-ritual-hygiene",
|
|
8208
|
+
name: "Restore Forecast Ritual Hygiene",
|
|
8209
|
+
trigger_metric: "weighted_pipeline",
|
|
8210
|
+
trigger_lens: "revenue_metrics",
|
|
8211
|
+
trigger_condition: "Forecast credibility is weak: stale or stuck pipeline, past-due closes, or commit changes are not captured",
|
|
8212
|
+
why: "A forecast ritual cannot repair dirty pipeline, but clean instruments without a commit cadence still produce surprise. Separate input trust from judgment drift, then inspect both.",
|
|
8213
|
+
steps: [
|
|
8214
|
+
"Confirm Freshness and Flow Rate are usable before changing forecast cadence.",
|
|
8215
|
+
"Cut past-due close dollars and stage-age exceptions by manager and segment.",
|
|
8216
|
+
"Snapshot best case / commit weekly with written evidence for material changes.",
|
|
8217
|
+
"Compare commit movement with weighted pipeline and actual closes; observe + recommend commit-history capture when it does not exist.",
|
|
8218
|
+
"Hold a 30-day ritual exam before changing methodology, tooling, or headcount."
|
|
8219
|
+
],
|
|
8220
|
+
tools_that_help: ["Weighted pipeline report", "Past-due close audit", "Weekly commit snapshot", "Stage-age inspection"],
|
|
8221
|
+
expected_outcome: "Past-due close dollars fall 30\u201350% and weekly commit changes carry evidence within 30 days (instruments: freshness, flow_rate, weighted_pipeline; commit history requires capture).",
|
|
8222
|
+
exam: {
|
|
8223
|
+
instrument_ids: ["freshness", "flow_rate", "weighted_pipeline", "forecast_commit_history"],
|
|
8224
|
+
target_guidance: "Past-due close dollars \u221230\u201350%; 100% of material commit changes carry evidence.",
|
|
8225
|
+
check_window_days: 30,
|
|
8226
|
+
measurement_mode: "capture_required"
|
|
8227
|
+
}
|
|
7828
8228
|
}
|
|
7829
8229
|
];
|
|
7830
8230
|
PLAYS_FILE = "plays.jsonl";
|
|
@@ -10090,10 +10490,10 @@ function hr(width, ch = "\u2500") {
|
|
|
10090
10490
|
return ch.repeat(Math.max(0, width));
|
|
10091
10491
|
}
|
|
10092
10492
|
function wrapWords(text, maxW) {
|
|
10093
|
-
const
|
|
10493
|
+
const words2 = text.split(/\s+/).filter(Boolean);
|
|
10094
10494
|
const lines = [];
|
|
10095
10495
|
let cur = "";
|
|
10096
|
-
for (let word of
|
|
10496
|
+
for (let word of words2) {
|
|
10097
10497
|
if (visibleWidth(word) > maxW) {
|
|
10098
10498
|
if (cur) {
|
|
10099
10499
|
lines.push(cur);
|
|
@@ -15883,6 +16283,8 @@ function loadRuminationJob(id) {
|
|
|
15883
16283
|
if (!parsed || typeof parsed !== "object" || parsed.id !== id) return null;
|
|
15884
16284
|
if (parsed.best_plan === void 0) parsed.best_plan = parsed.plan ?? null;
|
|
15885
16285
|
if (parsed.best_score === void 0) parsed.best_score = parsed.last_critic?.score ?? null;
|
|
16286
|
+
if (parsed.roundtable === void 0) parsed.roundtable = null;
|
|
16287
|
+
if (parsed.roundtable_history === void 0) parsed.roundtable_history = [];
|
|
15886
16288
|
return parsed;
|
|
15887
16289
|
} catch {
|
|
15888
16290
|
return null;
|
|
@@ -15914,7 +16316,9 @@ function createRuminationJob(opts) {
|
|
|
15914
16316
|
created_at: now2,
|
|
15915
16317
|
updated_at: now2,
|
|
15916
16318
|
no_improve_streak: 0,
|
|
15917
|
-
last_critic_score: null
|
|
16319
|
+
last_critic_score: null,
|
|
16320
|
+
roundtable: null,
|
|
16321
|
+
roundtable_history: []
|
|
15918
16322
|
};
|
|
15919
16323
|
}
|
|
15920
16324
|
function renderRuminationLog(job) {
|
|
@@ -15928,6 +16332,18 @@ function renderRuminationLog(job) {
|
|
|
15928
16332
|
);
|
|
15929
16333
|
if (job.library_path) lines.push(`Strategy: ${job.library_path}`);
|
|
15930
16334
|
if (job.handoff_path) lines.push(`Handoff: ${job.handoff_path}`);
|
|
16335
|
+
if (job.roundtable) {
|
|
16336
|
+
lines.push("");
|
|
16337
|
+
lines.push("## Counsel roundtable");
|
|
16338
|
+
lines.push("");
|
|
16339
|
+
lines.push(job.roundtable.digest);
|
|
16340
|
+
if (job.roundtable.issues.length) {
|
|
16341
|
+
lines.push("");
|
|
16342
|
+
lines.push(
|
|
16343
|
+
`Validation: ${job.roundtable.issues.map((entry) => entry.code).join(", ")}`
|
|
16344
|
+
);
|
|
16345
|
+
}
|
|
16346
|
+
}
|
|
15931
16347
|
lines.push("");
|
|
15932
16348
|
lines.push("## Rounds");
|
|
15933
16349
|
lines.push("");
|
|
@@ -16779,6 +17195,1256 @@ var init_companion = __esm({
|
|
|
16779
17195
|
}
|
|
16780
17196
|
});
|
|
16781
17197
|
|
|
17198
|
+
// src/data/gtm-counsel/packs.ts
|
|
17199
|
+
function listCounselPackIds() {
|
|
17200
|
+
return COUNSEL_PACKS.map((p) => p.id);
|
|
17201
|
+
}
|
|
17202
|
+
function getCounselPackById(id) {
|
|
17203
|
+
return COUNSEL_PACKS.find((p) => p.id === id);
|
|
17204
|
+
}
|
|
17205
|
+
var SILENT, COUNSEL_PACKS;
|
|
17206
|
+
var init_packs = __esm({
|
|
17207
|
+
"src/data/gtm-counsel/packs.ts"() {
|
|
17208
|
+
"use strict";
|
|
17209
|
+
SILENT = "Never name methodology brands in customer-visible text. Apply the moves; keep labels silent.";
|
|
17210
|
+
COUNSEL_PACKS = [
|
|
17211
|
+
{
|
|
17212
|
+
id: "counsel_sales",
|
|
17213
|
+
catalog_line: "- counsel_sales \u2014 pipeline conversion, capacity, deal strategy, sales-team structure",
|
|
17214
|
+
internal_name: "Sales counsel",
|
|
17215
|
+
vital_links: ["flow_rate", "thread_depth", "freshness", "signal_to_noise"],
|
|
17216
|
+
board_cut: "Sales constraint in dollars (stuck, single-threaded, stale, misdirected effort) and the one sales-process bet.",
|
|
17217
|
+
operator_cut: "Manager inspection cadence, stage exit criteria, SDR acceptance SLA, multi-thread targets \u2014 function owners.",
|
|
17218
|
+
when_to_use: "flow_rate / thread_depth / opp freshness / activity noise; attainment, cycle, coverage, forecast-from-the-line.",
|
|
17219
|
+
kill_rules: [
|
|
17220
|
+
"No headcount before capacity math + constraint vital.",
|
|
17221
|
+
"No more activity when signal_to_noise is red.",
|
|
17222
|
+
"Do not protect zombie pipeline for coverage optics.",
|
|
17223
|
+
"No invented reorgs or named people \u2014 function owners only.",
|
|
17224
|
+
"Do not kill zombies first unless freshness/zombies are the gating constraint."
|
|
17225
|
+
],
|
|
17226
|
+
silent_brand_rule: SILENT,
|
|
17227
|
+
role_card_ids: ["vp_sales", "sales_manager", "ae", "sdr", "se", "sales_ops"],
|
|
17228
|
+
tree_weight: { plg: 0.5, smb_velocity: 1, mid_market: 1, enterprise: 1 },
|
|
17229
|
+
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.
|
|
17230
|
+
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?
|
|
17231
|
+
CUT: stage-age, owner, segment, amount band, activity-to-opp link.
|
|
17232
|
+
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.
|
|
17233
|
+
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.
|
|
17234
|
+
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.
|
|
17235
|
+
PLAYS: get_play_detail unstick-pipeline / multi-thread-deals / clean-dead-pipeline / retarget-effort / compress-sales-cycle only when that vital is the constraint.`
|
|
17236
|
+
},
|
|
17237
|
+
{
|
|
17238
|
+
id: "counsel_marketing",
|
|
17239
|
+
catalog_line: "- counsel_marketing \u2014 demand quality, ABM, PMM/lifecycle; content/brand/partner as support seats",
|
|
17240
|
+
internal_name: "Marketing counsel",
|
|
17241
|
+
vital_links: ["drop_rate", "signal_to_noise", "nrr"],
|
|
17242
|
+
board_cut: "Marketing's dollar contribution and the leak (creation vs handoff vs conversion narrative).",
|
|
17243
|
+
operator_cut: "SLA, scoring, journey gates, program kill rules, PMM adoption exams \u2014 function owners.",
|
|
17244
|
+
when_to_use: "Pipeline creation, handoff leak, magic number / CAC inputs, messaging stalls, named-account whitespace.",
|
|
17245
|
+
kill_rules: [
|
|
17246
|
+
"Do not buy more top-of-funnel when drop_rate is the constraint.",
|
|
17247
|
+
"Do not optimize MQL count against pipeline dollars.",
|
|
17248
|
+
"No ABM theater on smb_velocity without named-account process.",
|
|
17249
|
+
"Stop at accepted pipeline quality + narrative enablement \u2014 do not own close.",
|
|
17250
|
+
"If content/brand/partner is not the constraint, do not open those cards."
|
|
17251
|
+
],
|
|
17252
|
+
silent_brand_rule: SILENT,
|
|
17253
|
+
role_card_ids: [
|
|
17254
|
+
"cmo",
|
|
17255
|
+
"demand_gen",
|
|
17256
|
+
"growth",
|
|
17257
|
+
"abm",
|
|
17258
|
+
"pmm",
|
|
17259
|
+
"lifecycle",
|
|
17260
|
+
"mkt_ops",
|
|
17261
|
+
"content",
|
|
17262
|
+
"brand",
|
|
17263
|
+
"field_partner"
|
|
17264
|
+
],
|
|
17265
|
+
tree_weight: { plg: 1, smb_velocity: 0.85, mid_market: 1, enterprise: 1 },
|
|
17266
|
+
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.
|
|
17267
|
+
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?
|
|
17268
|
+
CUT: source, campaign, MQL\u2192SQL\u2192Opp, named-list vs rest, journey vs SDR sequence overlap.
|
|
17269
|
+
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.
|
|
17270
|
+
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.
|
|
17271
|
+
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.
|
|
17272
|
+
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.`
|
|
17273
|
+
},
|
|
17274
|
+
{
|
|
17275
|
+
id: "counsel_revops",
|
|
17276
|
+
catalog_line: "- counsel_revops \u2014 GTM systems, routing/SLA/hygiene; forecast/capacity/territory governance",
|
|
17277
|
+
internal_name: "RevOps counsel",
|
|
17278
|
+
vital_links: ["drop_rate", "freshness", "signal_to_noise"],
|
|
17279
|
+
board_cut: "Whether the revenue machine's instruments can be trusted; cost of leak from process/system breaks.",
|
|
17280
|
+
operator_cut: "Routing SLA, stage validation, sync observability, hygiene, GTM eng backlog \u2014 RevOps / mkt ops / GTM eng.",
|
|
17281
|
+
when_to_use: "Broken instruments, routing, sync, definitions, hygiene, automation debt; forecast ritual design; capacity/territory ops.",
|
|
17282
|
+
kill_rules: [
|
|
17283
|
+
"No governance theater when routing/SLA is broken.",
|
|
17284
|
+
"Do not automate bad process.",
|
|
17285
|
+
"No boil-the-ocean warehouse while drop_rate burns.",
|
|
17286
|
+
"Do not take sides on credit wars \u2014 impose shared definitions.",
|
|
17287
|
+
"Prefer system/process enforcement over headcount."
|
|
17288
|
+
],
|
|
17289
|
+
silent_brand_rule: SILENT,
|
|
17290
|
+
role_card_ids: ["vp_revops", "revops_specialist", "gtm_engineer", "mkt_ops", "sales_ops"],
|
|
17291
|
+
tree_weight: { plg: 0.6, smb_velocity: 0.75, mid_market: 1, enterprise: 1 },
|
|
17292
|
+
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.
|
|
17293
|
+
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?
|
|
17294
|
+
CUT: source\u2192owner path, unassigned age, stage-exit compliance, sync error class, identity match, commit vs inspection evidence.
|
|
17295
|
+
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.
|
|
17296
|
+
TREE: plg=product\u2194CRM identity. smb_velocity=lean routing/SLA. mid_market=full OS. enterprise=matrixed + GTM eng.
|
|
17297
|
+
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.
|
|
17298
|
+
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.`
|
|
17299
|
+
},
|
|
17300
|
+
{
|
|
17301
|
+
id: "counsel_exec",
|
|
17302
|
+
catalog_line: "- counsel_exec \u2014 merge seats into one constraint, one sequence, board-ready call",
|
|
17303
|
+
internal_name: "Executive counsel",
|
|
17304
|
+
vital_links: ["freshness", "flow_rate", "drop_rate", "signal_to_noise", "thread_depth", "nrr", "grr"],
|
|
17305
|
+
board_cut: "The call, the dollar, the one bet, the exam date.",
|
|
17306
|
+
operator_cut: "Sequenced workstreams with function owners and contingencies.",
|
|
17307
|
+
when_to_use: "Always in strategist roundtable before plan ship; cross-functional objectives; seat conflicts.",
|
|
17308
|
+
kill_rules: [
|
|
17309
|
+
"Do not average seats into mush \u2014 pick a constraint and sequence.",
|
|
17310
|
+
"No reorg/headcount as first move.",
|
|
17311
|
+
"No parallel initiatives that starve the bottleneck.",
|
|
17312
|
+
"Open with verdict + dollars, not methodology.",
|
|
17313
|
+
"Do not ignore CS when NRR is the dollar engine."
|
|
17314
|
+
],
|
|
17315
|
+
silent_brand_rule: SILENT,
|
|
17316
|
+
role_card_ids: ["cro", "cmo", "vp_sales", "vp_revops", "vp_cs"],
|
|
17317
|
+
tree_weight: { plg: 1, smb_velocity: 1, mid_market: 1, enterprise: 1 },
|
|
17318
|
+
body: `THINK: Merge, do not concatenate. One governing constraint. Drop non-constraint theater. Board altitude = verdict + dollar + exam; operator altitude = sequenced owners. CS is the dollar engine when NRR/GRR dwarfs new-logo at risk (typical PLG and installed-base heavy enterprise).
|
|
17319
|
+
ASK: What unlocks the others? What dies in 90 days if we are wrong? Which seat's ask is polish?
|
|
17320
|
+
CUT: layer order of vitals; $ at risk by function; in-flight work that collides.
|
|
17321
|
+
DISAGREE: Force other packs to drop non-constraint work. Reject sales-only or marketing-only tunnels when instruments point elsewhere. Hold RevOps to instrument trust before narrative bets. Hold CS in the plan when GRR is the money.
|
|
17322
|
+
TREE: plg=CS/growth weight. smb_velocity=sales machine + routing. mid_market=full GTM. enterprise=thread/ABM/SE + NRR.
|
|
17323
|
+
CARDS: cro for sequencing investment; cmo when demand/brand tradeoff; vp_sales / vp_revops / vp_cs when that function must own the Monday bet. Do not clone this pack into the CRO card.
|
|
17324
|
+
PLAYS: do not pick plays here \u2014 sequence the constraint owner's play and demote the rest to contingency.`
|
|
17325
|
+
},
|
|
17326
|
+
{
|
|
17327
|
+
id: "counsel_cs",
|
|
17328
|
+
catalog_line: "- counsel_cs \u2014 NRR/GRR, renewals, TTV/onboarding, sales\u2192CS handoff",
|
|
17329
|
+
internal_name: "Customer Success counsel",
|
|
17330
|
+
vital_links: ["freshness", "nrr", "grr", "contraction_arr"],
|
|
17331
|
+
board_cut: "NRR/GRR dollars at risk and the retention/expansion bet.",
|
|
17332
|
+
operator_cut: "Risk flags, renewal cadence, handoff packet fields, touch model by segment \u2014 CS / renewals / onboarding.",
|
|
17333
|
+
when_to_use: "NRR/GRR/churn/expansion; post-sale handoff; renewals; after sales wins that create CS landmines.",
|
|
17334
|
+
kill_rules: [
|
|
17335
|
+
"Do not push expansion into unhealthy accounts.",
|
|
17336
|
+
"No more QBRs when onboarding/TTV is the constraint.",
|
|
17337
|
+
"Do not treat renewals as pure billing.",
|
|
17338
|
+
"Do not ignore sales handoff quality as root cause.",
|
|
17339
|
+
"No CS headcount before segmentation/touch-model math."
|
|
17340
|
+
],
|
|
17341
|
+
silent_brand_rule: SILENT,
|
|
17342
|
+
role_card_ids: ["vp_cs", "csm", "renewals", "onboarding"],
|
|
17343
|
+
tree_weight: { plg: 1, smb_velocity: 0.7, mid_market: 1, enterprise: 1 },
|
|
17344
|
+
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.
|
|
17345
|
+
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?
|
|
17346
|
+
CUT: tenure, segment, product usage, renewal date aging, handoff-packet completeness, champion map.
|
|
17347
|
+
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.
|
|
17348
|
+
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.
|
|
17349
|
+
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).
|
|
17350
|
+
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.`
|
|
17351
|
+
}
|
|
17352
|
+
];
|
|
17353
|
+
}
|
|
17354
|
+
});
|
|
17355
|
+
|
|
17356
|
+
// src/data/gtm-counsel/resolve-tree.ts
|
|
17357
|
+
function isOrgTreeId(v) {
|
|
17358
|
+
return typeof v === "string" && TREES.includes(v);
|
|
17359
|
+
}
|
|
17360
|
+
function resolveOrgTree(profile) {
|
|
17361
|
+
if (profile && isOrgTreeId(profile.sales_motion)) return profile.sales_motion;
|
|
17362
|
+
const fromConfig = getConfigValue("sales-motion");
|
|
17363
|
+
if (isOrgTreeId(fromConfig)) return fromConfig;
|
|
17364
|
+
return "mid_market";
|
|
17365
|
+
}
|
|
17366
|
+
function listOrgTreeIds() {
|
|
17367
|
+
return [...TREES];
|
|
17368
|
+
}
|
|
17369
|
+
var TREES;
|
|
17370
|
+
var init_resolve_tree = __esm({
|
|
17371
|
+
"src/data/gtm-counsel/resolve-tree.ts"() {
|
|
17372
|
+
"use strict";
|
|
17373
|
+
init_store();
|
|
17374
|
+
TREES = ["plg", "smb_velocity", "mid_market", "enterprise"];
|
|
17375
|
+
}
|
|
17376
|
+
});
|
|
17377
|
+
|
|
17378
|
+
// src/data/gtm-counsel/role-cards.ts
|
|
17379
|
+
function presence(plg, smb, mid, ent) {
|
|
17380
|
+
return { plg, smb_velocity: smb, mid_market: mid, enterprise: ent };
|
|
17381
|
+
}
|
|
17382
|
+
function listRoleCardIds() {
|
|
17383
|
+
return ROLE_CARDS.map((c) => c.id);
|
|
17384
|
+
}
|
|
17385
|
+
function getRoleCardById(id) {
|
|
17386
|
+
return ROLE_CARDS.find((c) => c.id === id);
|
|
17387
|
+
}
|
|
17388
|
+
var ROLE_CARDS;
|
|
17389
|
+
var init_role_cards = __esm({
|
|
17390
|
+
"src/data/gtm-counsel/role-cards.ts"() {
|
|
17391
|
+
"use strict";
|
|
17392
|
+
ROLE_CARDS = [
|
|
17393
|
+
{
|
|
17394
|
+
id: "cro",
|
|
17395
|
+
title: "Chief Revenue Officer",
|
|
17396
|
+
function: "exec",
|
|
17397
|
+
presence: presence("thin", "present", "core", "core"),
|
|
17398
|
+
owns: [
|
|
17399
|
+
"Revenue-system investment sequencing across Sales/CS/RevOps",
|
|
17400
|
+
"Forecast integrity to CEO/board",
|
|
17401
|
+
"Capacity vs quota realism"
|
|
17402
|
+
],
|
|
17403
|
+
does_not_own: ["Brand craft", "CRM admin", "Product roadmap", "Counsel-exec merge rules (use the pack)"],
|
|
17404
|
+
success_metrics: ["bookings", "nrr", "grr", "pipeline_coverage", "forecast_accuracy"],
|
|
17405
|
+
typical_decisions: ["Which constraint to fund", "Volume vs conversion vs retention this quarter"],
|
|
17406
|
+
data_they_trust: ["Vital dollars", "Cohort retention", "Pipeline by source/segment"],
|
|
17407
|
+
consult_questions: [
|
|
17408
|
+
"Which vital/stage $ is gating, from the health snapshot?",
|
|
17409
|
+
"If one function is funded, which unlocks the others?",
|
|
17410
|
+
"Where does commit diverge from stage evidence?"
|
|
17411
|
+
],
|
|
17412
|
+
failure_modes: ["Sales-only reflex", "Parallel bets starving the constraint"],
|
|
17413
|
+
tensions: ["cmo credit", "vp_sales sandbag", "vp_cs new-logo vs NRR", "vp_revops platform spend"],
|
|
17414
|
+
play_hooks: ["rebalance-pipeline-mix", "improve-magic-number", "reduce-logo-churn", "accelerate-expansion"],
|
|
17415
|
+
signal_links: ["nrr", "grr", "pipeline_coverage"],
|
|
17416
|
+
stress_notes: "Do not clone counsel_exec merge rules into this card."
|
|
17417
|
+
},
|
|
17418
|
+
{
|
|
17419
|
+
id: "cmo",
|
|
17420
|
+
title: "Chief Marketing Officer",
|
|
17421
|
+
function: "exec",
|
|
17422
|
+
presence: presence("present", "thin", "core", "core"),
|
|
17423
|
+
owns: ["Demand-system design", "Marketing contribution to accepted pipeline", "Program portfolio mix"],
|
|
17424
|
+
does_not_own: ["Close process", "CRM stage hygiene", "SQL without shared SLA"],
|
|
17425
|
+
success_metrics: ["pipeline_created", "mql_to_sql", "drop_rate", "magic_number"],
|
|
17426
|
+
typical_decisions: ["Volume vs quality", "ABM vs broad", "Kill/scale a channel"],
|
|
17427
|
+
data_they_trust: ["Agreed attribution", "SQL rejection reasons", "Win/loss themes"],
|
|
17428
|
+
consult_questions: [
|
|
17429
|
+
"Creation vs acceptance \u2014 which instrument is red?",
|
|
17430
|
+
"Which source/channel's pipeline actually closes?",
|
|
17431
|
+
"Would fewer named accounts beat more spend?"
|
|
17432
|
+
],
|
|
17433
|
+
failure_modes: ["MQL vanity", "More spend while drop_rate is red"],
|
|
17434
|
+
tensions: ["vp_sales quality/volume", "vp_revops attribution", "cro brand vs bookings"],
|
|
17435
|
+
play_hooks: ["demand-quality-over-volume", "abm-orchestration", "improve-magic-number", "rebalance-pipeline-mix"],
|
|
17436
|
+
signal_links: ["drop_rate", "magic_number", "mql_to_opp"],
|
|
17437
|
+
stress_notes: "If drop_rate is red, do not prescribe more spend."
|
|
17438
|
+
},
|
|
17439
|
+
{
|
|
17440
|
+
id: "vp_sales",
|
|
17441
|
+
title: "VP / Head of Sales",
|
|
17442
|
+
function: "sales",
|
|
17443
|
+
presence: presence("thin", "core", "core", "core"),
|
|
17444
|
+
owns: ["Quota/capacity system", "SDR\u2194AE design", "Forecast from the line"],
|
|
17445
|
+
does_not_own: ["MAP strategy", "CRM architecture", "Deal-level inspection (manager)", "Close execution (AE)"],
|
|
17446
|
+
success_metrics: ["attainment", "win_rate", "avg_sales_cycle", "flow_rate", "pipeline_coverage"],
|
|
17447
|
+
typical_decisions: ["Hire SDR vs AE vs SE", "Capacity vs win-rate bet"],
|
|
17448
|
+
data_they_trust: ["Capacity model", "Stage conversion", "Ramp curves"],
|
|
17449
|
+
consult_questions: [
|
|
17450
|
+
"Is the miss coverage, conversion, or cycle \u2014 which vital?",
|
|
17451
|
+
"What capacity math says hire vs improve win rate?",
|
|
17452
|
+
"Where is rollup forecast fiction?"
|
|
17453
|
+
],
|
|
17454
|
+
failure_modes: ["Headcount before math", "Activity mandate when signal_to_noise is red"],
|
|
17455
|
+
tensions: ["cmo leads", "vp_revops process friction", "vp_cs oversell"],
|
|
17456
|
+
play_hooks: ["unstick-pipeline", "compress-sales-cycle", "rebalance-pipeline-mix"],
|
|
17457
|
+
signal_links: ["flow_rate", "avg_sales_cycle", "pipeline_coverage", "win_rate"],
|
|
17458
|
+
stress_notes: "No headcount without capacity math and a constraint vital."
|
|
17459
|
+
},
|
|
17460
|
+
{
|
|
17461
|
+
id: "sales_manager",
|
|
17462
|
+
title: "Sales Manager / Director",
|
|
17463
|
+
function: "sales",
|
|
17464
|
+
presence: presence("thin", "core", "core", "core"),
|
|
17465
|
+
owns: ["Pod inspection cadence", "Team forecast integrity", "Coaching vs PIP"],
|
|
17466
|
+
does_not_own: ["Company capacity model", "Marketing mix"],
|
|
17467
|
+
success_metrics: ["team_attainment", "forecast_accuracy", "flow_rate", "thread_depth", "freshness"],
|
|
17468
|
+
typical_decisions: ["Which deals get air cover", "Death-watch vs coach"],
|
|
17469
|
+
data_they_trust: ["Next-step/date evidence", "Thread counts", "Call/inspection artifacts"],
|
|
17470
|
+
consult_questions: [
|
|
17471
|
+
"Which owners concentrate stuck/stale $ ?",
|
|
17472
|
+
"What % of opps lack next step, date, or second thread?",
|
|
17473
|
+
"Is the miss skill, ICP, or process in this pod?"
|
|
17474
|
+
],
|
|
17475
|
+
failure_modes: ["Cheerleading forecast", "Protecting zombies for coverage"],
|
|
17476
|
+
tensions: ["vp_sales commit pressure", "ae sandbag", "revops_specialist required fields"],
|
|
17477
|
+
play_hooks: ["unstick-pipeline", "clean-dead-pipeline", "multi-thread-deals"],
|
|
17478
|
+
signal_links: ["flow_rate", "thread_depth", "freshness"],
|
|
17479
|
+
stress_notes: "Coaching needs an inspection cadence exam, not vibes."
|
|
17480
|
+
},
|
|
17481
|
+
{
|
|
17482
|
+
id: "ae",
|
|
17483
|
+
title: "Account Executive",
|
|
17484
|
+
function: "sales",
|
|
17485
|
+
presence: presence("present", "core", "core", "core"),
|
|
17486
|
+
owns: ["Opp progression to close", "Deal multi-thread and commercial integrity"],
|
|
17487
|
+
does_not_own: ["Territory design", "Inbound SLA", "Post-sale delivery"],
|
|
17488
|
+
success_metrics: ["quota", "win_rate", "flow_rate", "thread_depth", "freshness"],
|
|
17489
|
+
typical_decisions: ["Advance/stall/kill", "Bring SE/exec/CS", "Discount vs term"],
|
|
17490
|
+
data_they_trust: ["Stage exit evidence", "Stakeholder map", "Mutual close dates"],
|
|
17491
|
+
consult_questions: [
|
|
17492
|
+
"Stuck on process, product, politics, or single thread \u2014 which vital?",
|
|
17493
|
+
"Which stage-exit criterion is unmet on the largest stuck $ ?",
|
|
17494
|
+
"Assist vs hunt for this account (plg)?"
|
|
17495
|
+
],
|
|
17496
|
+
failure_modes: ["Happy ears", "Zombies for coverage", "Oversell CS cannot deliver"],
|
|
17497
|
+
tensions: ["sdr acceptance", "se custom POV", "csm handoff"],
|
|
17498
|
+
play_hooks: ["unstick-pipeline", "multi-thread-deals", "clean-dead-pipeline", "sales-cs-handoff-packet"],
|
|
17499
|
+
signal_links: ["flow_rate", "thread_depth", "freshness", "win_rate"],
|
|
17500
|
+
stress_notes: "Name unmet exit criteria and thread count; no rapport-only advice."
|
|
17501
|
+
},
|
|
17502
|
+
{
|
|
17503
|
+
id: "sdr",
|
|
17504
|
+
title: "SDR / BDR",
|
|
17505
|
+
function: "sales",
|
|
17506
|
+
presence: presence("thin", "core", "core", "present"),
|
|
17507
|
+
owns: ["Pipeline creation", "First-pass ICP/DQ", "Handoff quality into AE"],
|
|
17508
|
+
does_not_own: ["Close", "Campaign strategy", "Routing architecture"],
|
|
17509
|
+
success_metrics: ["sql_accepted", "opp_created", "signal_to_noise", "drop_rate"],
|
|
17510
|
+
typical_decisions: ["Persist vs DQ", "Escalate bad lists"],
|
|
17511
|
+
data_they_trust: ["ICP/DQ rules", "AE rejection reasons", "Speed-to-lead timers"],
|
|
17512
|
+
consult_questions: [
|
|
17513
|
+
"Volume low or AE acceptance low \u2014 which instrument?",
|
|
17514
|
+
"Where does inbound SLA break (create\u2192touch)?",
|
|
17515
|
+
"Are meetings becoming opps or activity theater?"
|
|
17516
|
+
],
|
|
17517
|
+
failure_modes: ["Meeting spam", "Dials when acceptance is the constraint"],
|
|
17518
|
+
tensions: ["ae quality bar", "demand_gen list quality", "revops_specialist routing delay"],
|
|
17519
|
+
play_hooks: ["harden-routing-sla", "demand-quality-over-volume", "retarget-effort"],
|
|
17520
|
+
signal_links: ["drop_rate", "signal_to_noise", "mql_to_opp"],
|
|
17521
|
+
stress_notes: "Do not prescribe more dials when acceptance rate is the constraint."
|
|
17522
|
+
},
|
|
17523
|
+
{
|
|
17524
|
+
id: "se",
|
|
17525
|
+
title: "Sales Engineer / SC",
|
|
17526
|
+
function: "sales",
|
|
17527
|
+
presence: presence("absent", "thin", "present", "core"),
|
|
17528
|
+
owns: ["Technical validation", "POV/demo standards", "Scope honesty"],
|
|
17529
|
+
does_not_own: ["Commercial close", "Lead gen", "Implementation delivery"],
|
|
17530
|
+
success_metrics: ["win_rate_se_touched", "avg_sales_cycle", "flow_rate"],
|
|
17531
|
+
typical_decisions: ["Standard demo vs custom POV", "SE queue go/no-go"],
|
|
17532
|
+
data_they_trust: ["Written success criteria", "Security/architecture checklist"],
|
|
17533
|
+
consult_questions: [
|
|
17534
|
+
"Technical vs commercial stuck \u2014 which?",
|
|
17535
|
+
"Is SE queue time the cycle constraint?",
|
|
17536
|
+
"Are we customizing a bad-fit deal?"
|
|
17537
|
+
],
|
|
17538
|
+
failure_modes: ["Unsupportable custom", "Late SE on enterprise cycle"],
|
|
17539
|
+
tensions: ["ae one-off custom", "csm overscope", "pmm narrative vs technical truth"],
|
|
17540
|
+
play_hooks: ["compress-sales-cycle", "unstick-pipeline", "multi-thread-deals"],
|
|
17541
|
+
signal_links: ["thread_depth", "flow_rate", "avg_sales_cycle"],
|
|
17542
|
+
stress_notes: "Thin on smb_velocity; do not prescribe an SE pool there."
|
|
17543
|
+
},
|
|
17544
|
+
{
|
|
17545
|
+
id: "sales_ops",
|
|
17546
|
+
title: "Sales Operations",
|
|
17547
|
+
function: "sales",
|
|
17548
|
+
presence: presence("thin", "present", "present", "core"),
|
|
17549
|
+
owns: ["Territory/overlay/quota admin", "Comp mechanics", "Sales reporting packs"],
|
|
17550
|
+
does_not_own: ["Coaching", "MAP creative", "CRM platform choice"],
|
|
17551
|
+
success_metrics: ["territory_balance", "crediting_accuracy", "signal_to_noise"],
|
|
17552
|
+
typical_decisions: ["Territory exception", "Crediting dispute"],
|
|
17553
|
+
data_they_trust: ["Ownership history", "Comp statements"],
|
|
17554
|
+
consult_questions: [
|
|
17555
|
+
"Does territory/comp explain the miss better than skill?",
|
|
17556
|
+
"Which reports disagree with vital instruments?",
|
|
17557
|
+
"Do SPIFs spike unlinked activity?"
|
|
17558
|
+
],
|
|
17559
|
+
failure_modes: ["Spreadsheet vs CRM truth", "SPIFs that raise signal_to_noise red"],
|
|
17560
|
+
tensions: ["vp_sales exceptions", "revops_specialist CRM ownership"],
|
|
17561
|
+
play_hooks: ["rebalance-pipeline-mix", "retarget-effort"],
|
|
17562
|
+
signal_links: ["pipeline_coverage", "signal_to_noise"],
|
|
17563
|
+
stress_notes: "Fix definitions/ownership before new dashboards."
|
|
17564
|
+
},
|
|
17565
|
+
{
|
|
17566
|
+
id: "demand_gen",
|
|
17567
|
+
title: "Demand Generation Lead",
|
|
17568
|
+
function: "marketing",
|
|
17569
|
+
presence: presence("thin", "core", "core", "present"),
|
|
17570
|
+
owns: ["Accepted-pipeline creation from programs", "Channel mix experiments"],
|
|
17571
|
+
does_not_own: ["Named-account orchestration (abm)", "Positioning (pmm)", "Routing architecture"],
|
|
17572
|
+
success_metrics: ["pipeline_created", "cost_per_pipeline", "mql_to_opp", "magic_number"],
|
|
17573
|
+
typical_decisions: ["Kill/scale a channel", "Tighten scoring vs buy volume"],
|
|
17574
|
+
data_they_trust: ["Opp\u2192won by source", "AE rejection codes"],
|
|
17575
|
+
consult_questions: [
|
|
17576
|
+
"Which channel's pipeline closes \u2014 cut by source?",
|
|
17577
|
+
"Are we optimizing MQL count against $ ?",
|
|
17578
|
+
"Is drop_rate a reject-quality problem or a routing problem?"
|
|
17579
|
+
],
|
|
17580
|
+
failure_modes: ["MQL vanity", "Spend increase while drop_rate red"],
|
|
17581
|
+
tensions: ["abm budget", "sdr quality", "mkt_ops launch capacity"],
|
|
17582
|
+
play_hooks: ["demand-quality-over-volume", "improve-magic-number", "rebalance-pipeline-mix"],
|
|
17583
|
+
signal_links: ["mql_to_opp", "cost_per_pipeline", "magic_number", "pipeline_coverage"],
|
|
17584
|
+
stress_notes: "If the leak is routing/SLA, hand to revops \u2014 do not buy more leads."
|
|
17585
|
+
},
|
|
17586
|
+
{
|
|
17587
|
+
id: "abm",
|
|
17588
|
+
title: "ABM Lead",
|
|
17589
|
+
function: "marketing",
|
|
17590
|
+
presence: presence("absent", "thin", "present", "core"),
|
|
17591
|
+
owns: ["Named-list tiering with Sales", "Orchestrated plays on that list"],
|
|
17592
|
+
does_not_own: ["Broad demand portfolio", "Deal close"],
|
|
17593
|
+
success_metrics: ["named_list_pipeline", "named_list_win_rate", "thread_depth"],
|
|
17594
|
+
typical_decisions: ["Tier entry/exit", "Stop funding a silent account"],
|
|
17595
|
+
data_they_trust: ["List=territory agreement", "Opps on named accounts"],
|
|
17596
|
+
consult_questions: [
|
|
17597
|
+
"Do AE territories match the named list?",
|
|
17598
|
+
"Pipeline/wins on-list vs engagement vanity?",
|
|
17599
|
+
"Is this smb_velocity without a named-account process?"
|
|
17600
|
+
],
|
|
17601
|
+
failure_modes: ["Logo spray", "List\u2260territory"],
|
|
17602
|
+
tensions: ["demand_gen credit", "ae commitment", "sdr random outbound"],
|
|
17603
|
+
play_hooks: ["abm-orchestration", "multi-thread-deals", "retarget-effort"],
|
|
17604
|
+
signal_links: ["named_list_pipeline", "named_list_win_rate", "thread_depth", "signal_to_noise"],
|
|
17605
|
+
stress_notes: "Kill this play on smb_velocity without an agreed named list."
|
|
17606
|
+
},
|
|
17607
|
+
{
|
|
17608
|
+
id: "pmm",
|
|
17609
|
+
title: "Product Marketing",
|
|
17610
|
+
function: "marketing",
|
|
17611
|
+
presence: presence("core", "present", "core", "core"),
|
|
17612
|
+
owns: ["Positioning/narrative/competitive proof", "Win/loss synthesis"],
|
|
17613
|
+
does_not_own: ["Media buying", "Quota"],
|
|
17614
|
+
success_metrics: ["win_rate", "avg_sales_cycle", "enablement_adoption"],
|
|
17615
|
+
typical_decisions: ["Segment message vs one narrative", "Loss = product vs narrative vs ICP"],
|
|
17616
|
+
data_they_trust: ["Structured win/loss", "Whether sellers use the current narrative"],
|
|
17617
|
+
consult_questions: [
|
|
17618
|
+
"Are deals stalling at propose/decide for message or process?",
|
|
17619
|
+
"Do SDRs/AEs use the current narrative \u2014 adoption exam?",
|
|
17620
|
+
"Is packaging attracting bad ICP (later churn)?"
|
|
17621
|
+
],
|
|
17622
|
+
failure_modes: ["Asset counts without adoption", "Monthly message churn"],
|
|
17623
|
+
tensions: ["demand_gen offers", "ae one-pager pressure", "se technical truth"],
|
|
17624
|
+
play_hooks: ["compress-sales-cycle", "unstick-pipeline", "improve-magic-number"],
|
|
17625
|
+
signal_links: ["win_rate", "avg_sales_cycle"],
|
|
17626
|
+
stress_notes: "Exams are win-rate/cycle and asset adoption \u2014 not deck volume."
|
|
17627
|
+
},
|
|
17628
|
+
{
|
|
17629
|
+
id: "lifecycle",
|
|
17630
|
+
title: "Lifecycle / Email Marketing",
|
|
17631
|
+
function: "marketing",
|
|
17632
|
+
presence: presence("core", "present", "core", "present"),
|
|
17633
|
+
owns: ["Stage-gated journeys", "Suppression and frequency caps"],
|
|
17634
|
+
does_not_own: ["Human SDR sequences", "CSM relationships"],
|
|
17635
|
+
success_metrics: ["journey_conversion", "signal_to_noise", "unsubscribe_rate"],
|
|
17636
|
+
typical_decisions: ["Build/kill a journey", "Who owns the human touch"],
|
|
17637
|
+
data_they_trust: ["Stage gates on clean CRM stages", "Holdouts"],
|
|
17638
|
+
consult_questions: [
|
|
17639
|
+
"Are journeys gated on trustworthy stages (freshness/drop_rate)?",
|
|
17640
|
+
"Where do lifecycle and SDR double-touch the same person?",
|
|
17641
|
+
"Creation gap or retention gap?"
|
|
17642
|
+
],
|
|
17643
|
+
failure_modes: ["Spray that burns domain/trust", "Collision with SDR"],
|
|
17644
|
+
tensions: ["sdr human touch", "csm customer fatigue", "demand_gen calendar"],
|
|
17645
|
+
play_hooks: ["retarget-effort", "accelerate-expansion"],
|
|
17646
|
+
signal_links: ["signal_to_noise", "nrr"],
|
|
17647
|
+
stress_notes: "Kill more emails when deliverability or SDR collision is the constraint."
|
|
17648
|
+
},
|
|
17649
|
+
{
|
|
17650
|
+
id: "growth",
|
|
17651
|
+
title: "Growth Lead",
|
|
17652
|
+
function: "marketing",
|
|
17653
|
+
presence: presence("core", "present", "thin", "thin"),
|
|
17654
|
+
owns: ["Activation\u2192paid loops", "Product-event experiments tied to revenue ids"],
|
|
17655
|
+
does_not_own: ["Enterprise outbound", "Classical demand portfolio"],
|
|
17656
|
+
success_metrics: ["activation_rate", "paid_conversion", "nrr"],
|
|
17657
|
+
typical_decisions: ["Which funnel step to experiment", "Sales-assist threshold"],
|
|
17658
|
+
data_they_trust: ["Product events joined to CRM identity", "Cohort retention by path"],
|
|
17659
|
+
consult_questions: [
|
|
17660
|
+
"Acquisition, activation, or monetization \u2014 which step $ ?",
|
|
17661
|
+
"Is product\u2192CRM identity dropping (drop_rate/freshness)?",
|
|
17662
|
+
"When should a human AE enter?"
|
|
17663
|
+
],
|
|
17664
|
+
failure_modes: ["Local click metrics that hurt NRR", "Shadow analytics vs RevOps defs"],
|
|
17665
|
+
tensions: ["demand_gen paid ownership", "gtm_engineer event taxonomy", "csm support load"],
|
|
17666
|
+
play_hooks: ["accelerate-expansion", "fix-handoff-gap"],
|
|
17667
|
+
signal_links: ["nrr", "drop_rate", "freshness"],
|
|
17668
|
+
stress_notes: "Core on plg; do not run a growth-loop plan as default on enterprise."
|
|
17669
|
+
},
|
|
17670
|
+
{
|
|
17671
|
+
id: "mkt_ops",
|
|
17672
|
+
title: "Marketing Operations",
|
|
17673
|
+
function: "marketing",
|
|
17674
|
+
presence: presence("present", "present", "core", "core"),
|
|
17675
|
+
owns: ["MAP execution/sync", "UTM/campaign taxonomy", "Scoring implementation"],
|
|
17676
|
+
does_not_own: ["Which programs to run", "Sales stage definitions"],
|
|
17677
|
+
success_metrics: ["sync_error_rate", "drop_rate", "freshness", "time_to_launch"],
|
|
17678
|
+
typical_decisions: ["Block a corrupt launch", "Safe scoring change"],
|
|
17679
|
+
data_they_trust: ["Sync logs", "Error queues", "Field dictionary"],
|
|
17680
|
+
consult_questions: [
|
|
17681
|
+
"Leak in MAP, sync, routing, or human SLA \u2014 which log?",
|
|
17682
|
+
"What taxonomy/scoring debt blocks the instrument?",
|
|
17683
|
+
"Missing vs late vs wrong vs unused campaign data?"
|
|
17684
|
+
],
|
|
17685
|
+
failure_modes: ["Silent sync failures", "Scoring nobody trusts"],
|
|
17686
|
+
tensions: ["demand_gen speed", "revops_specialist lifecycle ownership", "gtm_engineer who automates"],
|
|
17687
|
+
play_hooks: ["fix-handoff-gap", "harden-routing-sla"],
|
|
17688
|
+
signal_links: ["drop_rate", "freshness"],
|
|
17689
|
+
stress_notes: "Observable routing/sync fixes before more programs."
|
|
17690
|
+
},
|
|
17691
|
+
{
|
|
17692
|
+
id: "content",
|
|
17693
|
+
title: "Content Marketing",
|
|
17694
|
+
function: "marketing",
|
|
17695
|
+
presence: presence("present", "present", "present", "present"),
|
|
17696
|
+
owns: ["Education/SEO/sales-enablement assets that feed demand or PMM"],
|
|
17697
|
+
does_not_own: ["Channel spend", "Named-account orchestration", "Positioning system"],
|
|
17698
|
+
success_metrics: ["assisted_pipeline", "enablement_adoption"],
|
|
17699
|
+
typical_decisions: ["What to publish vs kill", "Sales asset vs SEO asset"],
|
|
17700
|
+
data_they_trust: ["Which assets appear in closed-won paths"],
|
|
17701
|
+
consult_questions: [
|
|
17702
|
+
"Is content the constraint, or is demand/PMM starving for a specific asset?",
|
|
17703
|
+
"Which assets show up in won deals vs vanity traffic?",
|
|
17704
|
+
"Are sellers using the assets (adoption)?"
|
|
17705
|
+
],
|
|
17706
|
+
failure_modes: ["Publishing volume without pipeline exams"],
|
|
17707
|
+
tensions: ["demand_gen calendar", "pmm narrative control"],
|
|
17708
|
+
play_hooks: ["improve-magic-number", "demand-quality-over-volume"],
|
|
17709
|
+
signal_links: ["magic_number", "pipeline_coverage"],
|
|
17710
|
+
stress_notes: "Thin card \u2014 open only when an asset gap is the constraint."
|
|
17711
|
+
},
|
|
17712
|
+
{
|
|
17713
|
+
id: "brand",
|
|
17714
|
+
title: "Brand Marketing",
|
|
17715
|
+
function: "marketing",
|
|
17716
|
+
presence: presence("thin", "thin", "present", "present"),
|
|
17717
|
+
owns: ["Category/brand system"],
|
|
17718
|
+
does_not_own: ["Pipeline SLA", "MQL definitions"],
|
|
17719
|
+
success_metrics: ["brand_search", "pipeline_influenced"],
|
|
17720
|
+
typical_decisions: ["Brand program vs always-on demand"],
|
|
17721
|
+
data_they_trust: ["Influenced pipeline with an agreed exam"],
|
|
17722
|
+
consult_questions: [
|
|
17723
|
+
"What GTM exam makes this brand bet measurable?",
|
|
17724
|
+
"Is this polish on a non-constraint?",
|
|
17725
|
+
"Enterprise category need vs smb_velocity waste?"
|
|
17726
|
+
],
|
|
17727
|
+
failure_modes: ["Brand programs with no pipeline/NRR exam"],
|
|
17728
|
+
tensions: ["cro near-term bookings", "demand_gen budget"],
|
|
17729
|
+
play_hooks: ["improve-magic-number"],
|
|
17730
|
+
signal_links: ["magic_number", "pipeline_coverage"],
|
|
17731
|
+
stress_notes: "Thin card \u2014 defer without a dated pipeline or NRR exam."
|
|
17732
|
+
},
|
|
17733
|
+
{
|
|
17734
|
+
id: "field_partner",
|
|
17735
|
+
title: "Field / Partner Marketing",
|
|
17736
|
+
function: "marketing",
|
|
17737
|
+
presence: presence("thin", "thin", "present", "core"),
|
|
17738
|
+
owns: ["Events and partner co-marketing sourced pipeline"],
|
|
17739
|
+
does_not_own: ["Partner contract/comp", "AE close"],
|
|
17740
|
+
success_metrics: ["partner_sourced_pipeline", "event_pipeline"],
|
|
17741
|
+
typical_decisions: ["Which event/partner to fund"],
|
|
17742
|
+
data_they_trust: ["Sourced/influenced opps with source hygiene"],
|
|
17743
|
+
consult_questions: [
|
|
17744
|
+
"Is partner_motion real in custom_context, or are we inventing a channel?",
|
|
17745
|
+
"Event/partner pipeline that closes vs badge scans?",
|
|
17746
|
+
"Does source hygiene let RevOps trust the cut?"
|
|
17747
|
+
],
|
|
17748
|
+
failure_modes: ["Badge-scan vanity", "Invented partner motion"],
|
|
17749
|
+
tensions: ["ae time", "demand_gen budget"],
|
|
17750
|
+
play_hooks: ["rebalance-pipeline-mix", "fix-handoff-gap"],
|
|
17751
|
+
signal_links: ["pipeline_coverage", "drop_rate"],
|
|
17752
|
+
stress_notes: "Thin card \u2014 skip unless partner/field is in profile context or the tree is enterprise."
|
|
17753
|
+
},
|
|
17754
|
+
{
|
|
17755
|
+
id: "vp_revops",
|
|
17756
|
+
title: "VP / Head of RevOps",
|
|
17757
|
+
function: "revops",
|
|
17758
|
+
presence: presence("thin", "thin", "core", "core"),
|
|
17759
|
+
owns: ["GTM operating system", "Definition dictionary", "Systems vs governance mix", "Change-freeze calls"],
|
|
17760
|
+
does_not_own: ["Quota politics", "Campaign creative", "Closing"],
|
|
17761
|
+
success_metrics: ["drop_rate", "freshness", "routing_sla", "forecast_hygiene"],
|
|
17762
|
+
typical_decisions: ["Automate vs policy vs train", "Freeze CRM mid-quarter"],
|
|
17763
|
+
data_they_trust: ["Routing/stage logs", "Exception queues"],
|
|
17764
|
+
consult_questions: [
|
|
17765
|
+
"Behavior vs instrument \u2014 which log proves it?",
|
|
17766
|
+
"Is this a 65% systems leak or a 35% forecast/capacity ritual gap?",
|
|
17767
|
+
"What 30-day hold exam proves the fix stuck?"
|
|
17768
|
+
],
|
|
17769
|
+
failure_modes: ["Dashboard theater", "Automating broken process"],
|
|
17770
|
+
tensions: ["vp_sales friction", "cmo scoring", "gtm_engineer roadmap"],
|
|
17771
|
+
play_hooks: ["harden-routing-sla", "fix-handoff-gap", "clean-dead-pipeline", "forecast-ritual-hygiene"],
|
|
17772
|
+
signal_links: ["drop_rate", "freshness", "flow_rate", "weighted_pipeline", "forecast_commit_history"],
|
|
17773
|
+
stress_notes: "Systems-shaped constraints get systems fixes, not a steering committee."
|
|
17774
|
+
},
|
|
17775
|
+
{
|
|
17776
|
+
id: "revops_specialist",
|
|
17777
|
+
title: "RevOps Specialist / Manager",
|
|
17778
|
+
function: "revops",
|
|
17779
|
+
presence: presence("thin", "present", "core", "core"),
|
|
17780
|
+
owns: ["CRM process day-to-day", "Assignment rules/SLA timers", "Hygiene/dupes"],
|
|
17781
|
+
does_not_own: ["GTM strategy", "Unbounded reports"],
|
|
17782
|
+
success_metrics: ["routing_sla", "unassigned_age", "duplicate_rate", "drop_rate", "freshness"],
|
|
17783
|
+
typical_decisions: ["Patch vs proper fix", "Exception vs enforce"],
|
|
17784
|
+
data_they_trust: ["Assignment logs", "Flow errors", "Before/after samples"],
|
|
17785
|
+
consult_questions: [
|
|
17786
|
+
"Missing, late, wrong, or unused data \u2014 which class?",
|
|
17787
|
+
"Which queue/timer maps to the drop_rate $ ?",
|
|
17788
|
+
"What monitor proves 30-day hold?"
|
|
17789
|
+
],
|
|
17790
|
+
failure_modes: ["One-off workflows", "No monitoring after 'fixed'"],
|
|
17791
|
+
tensions: ["ae required-field friction", "mkt_ops sync ownership", "gtm_engineer code vs declarative"],
|
|
17792
|
+
play_hooks: ["harden-routing-sla", "fix-handoff-gap", "clean-dead-pipeline"],
|
|
17793
|
+
signal_links: ["drop_rate", "freshness"],
|
|
17794
|
+
stress_notes: "Training vs policy vs enforcement \u2014 pick one; require a monitor."
|
|
17795
|
+
},
|
|
17796
|
+
{
|
|
17797
|
+
id: "gtm_engineer",
|
|
17798
|
+
title: "GTM Engineer / Automation",
|
|
17799
|
+
function: "revops",
|
|
17800
|
+
presence: presence("present", "thin", "present", "core"),
|
|
17801
|
+
owns: ["Integrations and event pipelines", "Identity resolution", "Observability/retries"],
|
|
17802
|
+
does_not_own: ["Stage/SLA definitions", "Campaign strategy"],
|
|
17803
|
+
success_metrics: ["freshness", "drop_rate", "identity_match_rate", "error_budget"],
|
|
17804
|
+
typical_decisions: ["Build vs buy", "Batch vs stream", "Backfill vs detect-first"],
|
|
17805
|
+
data_they_trust: ["Dead-letter queues", "Schema diffs", "Latency of lead-to-route"],
|
|
17806
|
+
consult_questions: [
|
|
17807
|
+
"Never arrived, late, or wrong \u2014 which failure class?",
|
|
17808
|
+
"Is identity resolution the hidden drop_rate?",
|
|
17809
|
+
"Observability before another automation?"
|
|
17810
|
+
],
|
|
17811
|
+
failure_modes: ["Fragile zaps", "Activating automation on broken identity"],
|
|
17812
|
+
tensions: ["revops_specialist declarative vs code", "growth event taxonomy"],
|
|
17813
|
+
play_hooks: ["harden-routing-sla", "fix-handoff-gap"],
|
|
17814
|
+
signal_links: ["freshness", "drop_rate"],
|
|
17815
|
+
stress_notes: "Foundation identity before activation automation."
|
|
17816
|
+
},
|
|
17817
|
+
{
|
|
17818
|
+
id: "vp_cs",
|
|
17819
|
+
title: "VP / Head of Customer Success",
|
|
17820
|
+
function: "cs",
|
|
17821
|
+
presence: presence("core", "present", "core", "core"),
|
|
17822
|
+
owns: ["Retention/expansion operating system", "Touch model by segment", "Where expansion sits"],
|
|
17823
|
+
does_not_own: ["Net-new logos", "Day-to-day save execution (csm)", "Renewal commercial desk (renewals)"],
|
|
17824
|
+
success_metrics: ["grr", "nrr", "logo_churn", "ttv"],
|
|
17825
|
+
typical_decisions: ["Tech-touch vs high-touch", "CS vs AM vs AE for expansion"],
|
|
17826
|
+
data_they_trust: ["Health scores calibrated to churn", "Renewal book aging"],
|
|
17827
|
+
consult_questions: [
|
|
17828
|
+
"NRR limited by churn, failed expansion, or both?",
|
|
17829
|
+
"Is TTV/onboarding the leak (not QBR cadence)?",
|
|
17830
|
+
"Did Sales hand off oversold deals \u2014 packet completeness?"
|
|
17831
|
+
],
|
|
17832
|
+
failure_modes: ["QBR theater", "Expansion into unhealthy accounts"],
|
|
17833
|
+
tensions: ["vp_sales oversell", "product blockers", "lifecycle fatigue"],
|
|
17834
|
+
play_hooks: ["reduce-logo-churn", "accelerate-expansion", "sales-cs-handoff-packet", "fix-renewal-process", "renewal-early-warning"],
|
|
17835
|
+
signal_links: ["grr", "nrr", "ttv", "contraction_arr"],
|
|
17836
|
+
stress_notes: "If onboarding/TTV is the leak, do not start with more QBRs."
|
|
17837
|
+
},
|
|
17838
|
+
{
|
|
17839
|
+
id: "csm",
|
|
17840
|
+
title: "Customer Success Manager",
|
|
17841
|
+
function: "cs",
|
|
17842
|
+
presence: presence("core", "core", "core", "core"),
|
|
17843
|
+
owns: ["Account health and save plans", "Adoption/value evidence"],
|
|
17844
|
+
does_not_own: ["Net-new logos", "Renewal desk commercial close", "Health-score model build"],
|
|
17845
|
+
success_metrics: ["grr", "logo_churn", "ttv", "nrr"],
|
|
17846
|
+
typical_decisions: ["Save vs churn", "Expand now vs restore health first"],
|
|
17847
|
+
data_they_trust: ["Usage+outcomes", "Champion map", "Handoff packet"],
|
|
17848
|
+
consult_questions: [
|
|
17849
|
+
"Product, value, politics, or champion loss \u2014 which leading indicator failed?",
|
|
17850
|
+
"Handoff packet complete on recent churns?",
|
|
17851
|
+
"Expansion now or after health restore?"
|
|
17852
|
+
],
|
|
17853
|
+
failure_modes: ["Friendly without commercial clarity", "Expanding unhealthy accounts"],
|
|
17854
|
+
tensions: ["ae oversell", "renewals ownership at deadline", "lifecycle email"],
|
|
17855
|
+
play_hooks: ["reduce-logo-churn", "accelerate-expansion", "sales-cs-handoff-packet"],
|
|
17856
|
+
signal_links: ["grr", "nrr", "ttv"],
|
|
17857
|
+
stress_notes: "Risk must translate to GRR/NRR $ and an exam date."
|
|
17858
|
+
},
|
|
17859
|
+
{
|
|
17860
|
+
id: "renewals",
|
|
17861
|
+
title: "Renewals Manager",
|
|
17862
|
+
function: "cs",
|
|
17863
|
+
presence: presence("present", "present", "core", "core"),
|
|
17864
|
+
owns: ["Renewal forecast and commercial deadline", "Save plays at the boundary"],
|
|
17865
|
+
does_not_own: ["Day-to-day adoption", "Net-new sales"],
|
|
17866
|
+
success_metrics: ["grr", "renewal_rate", "contraction_arr", "early_warning_lead_time"],
|
|
17867
|
+
typical_decisions: ["When to open commercial talk", "Discount vs term vs walk"],
|
|
17868
|
+
data_they_trust: ["Contract dates SoR", "CSM risk flags \u226560 days out"],
|
|
17869
|
+
consult_questions: [
|
|
17870
|
+
"What share of the book is unflagged inside 60 days?",
|
|
17871
|
+
"Commercial, value, or champion-change risk?",
|
|
17872
|
+
"Are AE promises creating renewal landmines?"
|
|
17873
|
+
],
|
|
17874
|
+
failure_modes: ["Last-week scramble", "Discount addiction"],
|
|
17875
|
+
tensions: ["csm relationship", "ae original promises", "sales_ops discount policy"],
|
|
17876
|
+
play_hooks: ["renewal-early-warning", "fix-renewal-process", "reduce-logo-churn"],
|
|
17877
|
+
signal_links: ["grr", "contraction_arr", "early_warning_lead_time"],
|
|
17878
|
+
stress_notes: "If value never landed (TTV), fix onboarding before renewal training."
|
|
17879
|
+
},
|
|
17880
|
+
{
|
|
17881
|
+
id: "onboarding",
|
|
17882
|
+
title: "Onboarding / Implementation Lead",
|
|
17883
|
+
function: "cs",
|
|
17884
|
+
presence: presence("core", "present", "present", "core"),
|
|
17885
|
+
owns: ["Closed-Won readiness", "Time-to-first-value path", "Implementation risk escalation"],
|
|
17886
|
+
does_not_own: ["Product roadmap", "Renewal commercial close", "Net-new deal close"],
|
|
17887
|
+
success_metrics: ["ttv", "onboarding_completion", "packet_field_completeness", "early_tenure_grr"],
|
|
17888
|
+
typical_decisions: ["Ready to start vs return for missing scope", "Standard path vs risk escalation"],
|
|
17889
|
+
data_they_trust: ["Handoff packet", "Implementation milestones", "Product usage or first-value evidence"],
|
|
17890
|
+
consult_questions: [
|
|
17891
|
+
"Which readiness field is missing on delayed starts?",
|
|
17892
|
+
"Where does signed\u2192kickoff\u2192first value stall by segment?",
|
|
17893
|
+
"Is product usage measurable, or must we observe + recommend event capture?"
|
|
17894
|
+
],
|
|
17895
|
+
failure_modes: ["Starting without scope or owner", "Calling kickoff completion 'value'"],
|
|
17896
|
+
tensions: ["ae speed vs readiness", "csm relationship ownership", "product blocker escalation"],
|
|
17897
|
+
play_hooks: ["sales-cs-handoff-packet", "reduce-logo-churn"],
|
|
17898
|
+
signal_links: ["ttv", "packet_field_completeness", "grr"],
|
|
17899
|
+
stress_notes: "If TTV events do not exist, recommend capture; never invent onboarding performance."
|
|
17900
|
+
}
|
|
17901
|
+
];
|
|
17902
|
+
}
|
|
17903
|
+
});
|
|
17904
|
+
|
|
17905
|
+
// src/data/gtm-counsel/constraint-signals.ts
|
|
17906
|
+
function uniq(ids) {
|
|
17907
|
+
return [...new Set(ids)];
|
|
17908
|
+
}
|
|
17909
|
+
function isVitalSignal(value) {
|
|
17910
|
+
return VITAL_SIGNAL_IDS.includes(value);
|
|
17911
|
+
}
|
|
17912
|
+
function isKnownConstraintSignal(value) {
|
|
17913
|
+
return isVitalSignal(value) || Boolean(getMetricExplainer(value)) || ROUTED_SIGNALS.has(value) || Boolean(METRIC_SIGNAL_OWNERS[value]);
|
|
17914
|
+
}
|
|
17915
|
+
function signalKind(id) {
|
|
17916
|
+
if (isVitalSignal(id)) return "vital";
|
|
17917
|
+
if (getMetricExplainer(id) || ROUTED_SIGNALS.has(id) || METRIC_SIGNAL_OWNERS[id]) return "metric";
|
|
17918
|
+
return "stage";
|
|
17919
|
+
}
|
|
17920
|
+
function staleOwners(blob) {
|
|
17921
|
+
const staleDeal = /stale (opp|deal|pipeline)|zombie|dead pipeline|quiet opp/.test(blob);
|
|
17922
|
+
const dataDecay = /sync|enrich|contact decay|data quality|duplicate|hygiene of (people|contact|record)/.test(blob);
|
|
17923
|
+
if (staleDeal && !dataDecay) return ["counsel_sales"];
|
|
17924
|
+
if (dataDecay && !staleDeal) return ["counsel_revops"];
|
|
17925
|
+
return ["counsel_revops", "counsel_sales"];
|
|
17926
|
+
}
|
|
17927
|
+
function noiseOwners(blob) {
|
|
17928
|
+
const nurture = /nurture|campaign|lifecycle|email sequence|drip/.test(blob);
|
|
17929
|
+
const automation = /automat|zap|workflow noise|bad flow|enrich spam/.test(blob);
|
|
17930
|
+
const repActivity = /rep activity|dials?|calls logged|activity theater|unlinked activ/.test(blob);
|
|
17931
|
+
if (automation && !nurture && !repActivity) return ["counsel_revops"];
|
|
17932
|
+
if (nurture && !repActivity) return ["counsel_marketing"];
|
|
17933
|
+
if (repActivity && !nurture) return ["counsel_sales"];
|
|
17934
|
+
if (nurture && repActivity) return ["counsel_sales", "counsel_marketing"];
|
|
17935
|
+
return ["counsel_sales", "counsel_marketing"];
|
|
17936
|
+
}
|
|
17937
|
+
function dropOwners(blob) {
|
|
17938
|
+
const acceptanceQuality = /reject|acceptance|accepted pipeline|mql.?to.?(sql|opp)|sql.?to.?opp|bad fit|lead quality|scoring/.test(
|
|
17939
|
+
blob
|
|
17940
|
+
);
|
|
17941
|
+
return acceptanceQuality ? ["counsel_revops", "counsel_marketing"] : ["counsel_revops"];
|
|
17942
|
+
}
|
|
17943
|
+
function ownersForConstraintSignal(signal, blob = "") {
|
|
17944
|
+
if (signal === "freshness") return staleOwners(blob);
|
|
17945
|
+
if (signal === "signal_to_noise") return noiseOwners(blob);
|
|
17946
|
+
if (signal === "drop_rate") return dropOwners(blob);
|
|
17947
|
+
if (signal === "flow_rate" || signal === "thread_depth") return ["counsel_sales"];
|
|
17948
|
+
return [...METRIC_SIGNAL_OWNERS[signal] ?? []];
|
|
17949
|
+
}
|
|
17950
|
+
function phraseOwners(blob) {
|
|
17951
|
+
if (/\b(churn|nrr|grr|renewal|retention|customer success|onboarding|ttv|expansion arr)\b/.test(blob)) {
|
|
17952
|
+
return ["counsel_cs"];
|
|
17953
|
+
}
|
|
17954
|
+
if (/\b(rout(e|ing)|time-to-lead|assignment (rule|queue)|unassigned|sync (gap|fail)|enrichment coverage)\b/.test(
|
|
17955
|
+
blob
|
|
17956
|
+
)) {
|
|
17957
|
+
return ["counsel_revops"];
|
|
17958
|
+
}
|
|
17959
|
+
if (/\b(mql|sql accept|demand gen|demand-gen|abm|named.account|pipeline created|created pipeline|marketing.sourced|marketing.created)\b/.test(
|
|
17960
|
+
blob
|
|
17961
|
+
)) {
|
|
17962
|
+
return ["counsel_marketing"];
|
|
17963
|
+
}
|
|
17964
|
+
if (/\b(quota|win rate|sales cycle|deal inspect|single.thread|forecast (commit|sandbag)|sdr\b|ae\b)\b/.test(
|
|
17965
|
+
blob
|
|
17966
|
+
)) {
|
|
17967
|
+
return ["counsel_sales"];
|
|
17968
|
+
}
|
|
17969
|
+
return [];
|
|
17970
|
+
}
|
|
17971
|
+
function normalizeSignal(value) {
|
|
17972
|
+
if (typeof value !== "string") return null;
|
|
17973
|
+
const raw = value.trim().toLowerCase();
|
|
17974
|
+
if (!raw) return null;
|
|
17975
|
+
const resolved = resolveMetricId(raw) ?? raw.replace(/[-\s]+/g, "_");
|
|
17976
|
+
return isKnownConstraintSignal(resolved) ? resolved : null;
|
|
17977
|
+
}
|
|
17978
|
+
function digestObjectField(digest, field) {
|
|
17979
|
+
if (!digest || typeof digest !== "object") return null;
|
|
17980
|
+
const value = digest[field];
|
|
17981
|
+
return value && typeof value === "object" ? value : null;
|
|
17982
|
+
}
|
|
17983
|
+
function signalFromDigest(digest, field) {
|
|
17984
|
+
const value = digestObjectField(digest, field);
|
|
17985
|
+
if (!value) return null;
|
|
17986
|
+
const id = normalizeSignal(value.signal ?? value.id);
|
|
17987
|
+
if (!id) return null;
|
|
17988
|
+
return {
|
|
17989
|
+
id,
|
|
17990
|
+
kind: signalKind(id),
|
|
17991
|
+
dollar_value: typeof value.dollar_value === "number" ? value.dollar_value : null,
|
|
17992
|
+
cause: typeof value.cause === "string" ? value.cause.trim() : void 0,
|
|
17993
|
+
evidence: typeof value.evidence === "string" ? value.evidence.trim() : void 0
|
|
17994
|
+
};
|
|
17995
|
+
}
|
|
17996
|
+
function healthGateSignal(ctx, blob) {
|
|
17997
|
+
const structured = signalFromDigest(ctx.digest, "health_gate");
|
|
17998
|
+
if (structured && isVitalSignal(String(structured.id))) {
|
|
17999
|
+
const reading2 = ctx.vital_readings?.find((v) => v.vital_sign === structured.id);
|
|
18000
|
+
return {
|
|
18001
|
+
...structured,
|
|
18002
|
+
dollar_value: reading2?.dollar_value ?? structured.dollar_value ?? null
|
|
18003
|
+
};
|
|
18004
|
+
}
|
|
18005
|
+
if (ctx.digest && typeof ctx.digest === "object") {
|
|
18006
|
+
const d = ctx.digest;
|
|
18007
|
+
const legacy = normalizeSignal(d.gating_vital);
|
|
18008
|
+
if (legacy && isVitalSignal(legacy)) {
|
|
18009
|
+
const reading2 = ctx.vital_readings?.find((v) => v.vital_sign === legacy);
|
|
18010
|
+
return {
|
|
18011
|
+
id: legacy,
|
|
18012
|
+
kind: "vital",
|
|
18013
|
+
dollar_value: reading2?.dollar_value ?? null,
|
|
18014
|
+
evidence: "Stage A gating vital"
|
|
18015
|
+
};
|
|
18016
|
+
}
|
|
18017
|
+
const lines = Array.isArray(d.worst_problems_ordered) ? d.worst_problems_ordered.filter((x) => typeof x === "string") : [];
|
|
18018
|
+
for (const line of lines) {
|
|
18019
|
+
const lower = line.toLowerCase();
|
|
18020
|
+
const vital = VITAL_SIGNAL_IDS.find(
|
|
18021
|
+
(id) => lower.includes(id) || lower.includes(id.replace(/_/g, " "))
|
|
18022
|
+
);
|
|
18023
|
+
if (vital) {
|
|
18024
|
+
const reading2 = ctx.vital_readings?.find((v) => v.vital_sign === vital);
|
|
18025
|
+
return {
|
|
18026
|
+
id: vital,
|
|
18027
|
+
kind: "vital",
|
|
18028
|
+
dollar_value: reading2?.dollar_value ?? null,
|
|
18029
|
+
evidence: line
|
|
18030
|
+
};
|
|
18031
|
+
}
|
|
18032
|
+
}
|
|
18033
|
+
}
|
|
18034
|
+
if (!ctx.gating_vital) return null;
|
|
18035
|
+
const reading = ctx.vital_readings?.find((v) => v.vital_sign === ctx.gating_vital);
|
|
18036
|
+
return {
|
|
18037
|
+
id: ctx.gating_vital,
|
|
18038
|
+
kind: "vital",
|
|
18039
|
+
dollar_value: reading?.dollar_value ?? null,
|
|
18040
|
+
evidence: blob ? `Health snapshot gate for ${blob.slice(0, 80)}` : "Health snapshot gate"
|
|
18041
|
+
};
|
|
18042
|
+
}
|
|
18043
|
+
function metricMention(blob) {
|
|
18044
|
+
const definitions = listMetricExplainers().filter((m) => m.kind === "saas").flatMap((m) => [m.id, m.label, ...m.aliases ?? []].map((term) => ({ id: m.id, term }))).sort((a, b) => b.term.length - a.term.length);
|
|
18045
|
+
for (const { id, term } of definitions) {
|
|
18046
|
+
const escaped = term.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18047
|
+
if (new RegExp(`(^|\\b)${escaped}(\\b|$)`, "i").test(blob)) return id;
|
|
18048
|
+
}
|
|
18049
|
+
return null;
|
|
18050
|
+
}
|
|
18051
|
+
function objectiveSignal(ctx, health) {
|
|
18052
|
+
const structured = signalFromDigest(health.digest, "objective_constraint");
|
|
18053
|
+
if (structured) {
|
|
18054
|
+
const reading = health.metric_readings?.find((m) => m.metric === structured.id);
|
|
18055
|
+
if (!reading || !["green", "neutral"].includes(reading.status) || structured.kind === "vital") {
|
|
18056
|
+
return {
|
|
18057
|
+
...structured,
|
|
18058
|
+
evidence: structured.evidence || "Stage A objective constraint"
|
|
18059
|
+
};
|
|
18060
|
+
}
|
|
18061
|
+
}
|
|
18062
|
+
for (const playId of ctx.play_ids ?? []) {
|
|
18063
|
+
const signal = getPlayRouting(playId)?.constraint_signals[0];
|
|
18064
|
+
if (signal) {
|
|
18065
|
+
return {
|
|
18066
|
+
id: signal,
|
|
18067
|
+
kind: signalKind(String(signal)),
|
|
18068
|
+
evidence: `Draft play: ${playId}`
|
|
18069
|
+
};
|
|
18070
|
+
}
|
|
18071
|
+
}
|
|
18072
|
+
const blob = [ctx.objective, ...ctx.operator_constraints ?? []].join(" ").toLowerCase();
|
|
18073
|
+
const mentionedMetric = metricMention(blob);
|
|
18074
|
+
const metric = mentionedMetric === "pipeline_coverage" && /\b(stale|zombie|inflated|fake|false) (deal|opp|pipeline|coverage)|\b(inflating|faking) coverage\b/.test(
|
|
18075
|
+
blob
|
|
18076
|
+
) ? null : mentionedMetric;
|
|
18077
|
+
if (metric) {
|
|
18078
|
+
const reading = health.metric_readings?.find((m) => m.metric === metric);
|
|
18079
|
+
return {
|
|
18080
|
+
id: metric,
|
|
18081
|
+
kind: "metric",
|
|
18082
|
+
evidence: reading ? `${reading.formatted ?? reading.value ?? "available"} (${reading.status})` : "Objective metric (not yet computed)"
|
|
18083
|
+
};
|
|
18084
|
+
}
|
|
18085
|
+
const vital = VITAL_SIGNAL_IDS.find(
|
|
18086
|
+
(id) => blob.includes(id) || blob.includes(id.replace(/_/g, " "))
|
|
18087
|
+
);
|
|
18088
|
+
if (vital) return { id: vital, kind: "vital", evidence: "Objective text" };
|
|
18089
|
+
return null;
|
|
18090
|
+
}
|
|
18091
|
+
function resolveConstraint(input) {
|
|
18092
|
+
const blob = [
|
|
18093
|
+
input.objective.objective,
|
|
18094
|
+
...input.objective.operator_constraints ?? [],
|
|
18095
|
+
...input.objective.play_ids ?? []
|
|
18096
|
+
].join(" ").toLowerCase();
|
|
18097
|
+
const healthGate = healthGateSignal(input.health, blob);
|
|
18098
|
+
const objective = objectiveSignal(input.objective, input.health);
|
|
18099
|
+
const redMetrics = (input.health.metric_readings ?? []).filter(
|
|
18100
|
+
(metric) => metric.status === "red" && metric.value != null
|
|
18101
|
+
);
|
|
18102
|
+
const redMetricOwners = redMetrics.flatMap(
|
|
18103
|
+
(metric) => ownersForConstraintSignal(metric.metric, blob)
|
|
18104
|
+
);
|
|
18105
|
+
const healthOwners = uniq([
|
|
18106
|
+
...healthGate ? ownersForConstraintSignal(String(healthGate.id), blob) : [],
|
|
18107
|
+
...redMetricOwners
|
|
18108
|
+
]);
|
|
18109
|
+
const playOwners = (input.objective.play_ids ?? []).map(primaryOwnerForPlay).filter((id) => id != null);
|
|
18110
|
+
const signalOwners = objective ? ownersForConstraintSignal(String(objective.id), blob) : [];
|
|
18111
|
+
const objectiveOwners = uniq([
|
|
18112
|
+
...playOwners,
|
|
18113
|
+
...signalOwners,
|
|
18114
|
+
...phraseOwners(blob)
|
|
18115
|
+
]);
|
|
18116
|
+
const lockedOwners = uniq([...healthOwners, ...objectiveOwners]);
|
|
18117
|
+
const notes = [];
|
|
18118
|
+
if (healthGate) {
|
|
18119
|
+
notes.push(
|
|
18120
|
+
`Health gate: ${String(healthGate.id)} \u2192 ${healthOwners.join(", ") || "no mapped owner"}`
|
|
18121
|
+
);
|
|
18122
|
+
}
|
|
18123
|
+
if (redMetrics.length > 0) {
|
|
18124
|
+
notes.push(
|
|
18125
|
+
`Verified red metrics: ${redMetrics.map((metric) => metric.metric).join(", ")} \u2192 ${uniq(redMetricOwners).join(", ") || "no mapped owner"}`
|
|
18126
|
+
);
|
|
18127
|
+
}
|
|
18128
|
+
if (objective) {
|
|
18129
|
+
notes.push(
|
|
18130
|
+
`Objective constraint: ${String(objective.id)} \u2192 ${objectiveOwners.join(", ") || "no mapped owner"}`
|
|
18131
|
+
);
|
|
18132
|
+
}
|
|
18133
|
+
if (healthOwners.length > 0 && objectiveOwners.length > 0 && !objectiveOwners.some((id) => healthOwners.includes(id))) {
|
|
18134
|
+
notes.push(
|
|
18135
|
+
`Constraint conflict: preserve ${healthOwners.join(", ")} for health and ${objectiveOwners.join(", ")} for the objective; exec must sequence them.`
|
|
18136
|
+
);
|
|
18137
|
+
}
|
|
18138
|
+
return {
|
|
18139
|
+
health_gate: healthGate,
|
|
18140
|
+
objective_constraint: objective,
|
|
18141
|
+
health_gate_owners: healthOwners,
|
|
18142
|
+
objective_owners: objectiveOwners,
|
|
18143
|
+
locked_owners: lockedOwners,
|
|
18144
|
+
primary_owner: healthOwners[0] ?? objectiveOwners[0] ?? null,
|
|
18145
|
+
notes
|
|
18146
|
+
};
|
|
18147
|
+
}
|
|
18148
|
+
function resolveLegacyConstraint(objective, playIds = [], gatingVital) {
|
|
18149
|
+
return resolveConstraint({
|
|
18150
|
+
health: {
|
|
18151
|
+
gating_vital: isVitalSignal(gatingVital ?? "") ? gatingVital : null
|
|
18152
|
+
},
|
|
18153
|
+
objective: { objective, play_ids: playIds }
|
|
18154
|
+
});
|
|
18155
|
+
}
|
|
18156
|
+
function extractGatingVitalFromDigest(digest, fallbackVital) {
|
|
18157
|
+
const signal = healthGateSignal(
|
|
18158
|
+
{
|
|
18159
|
+
digest,
|
|
18160
|
+
gating_vital: isVitalSignal(fallbackVital ?? "") ? fallbackVital : null
|
|
18161
|
+
},
|
|
18162
|
+
""
|
|
18163
|
+
);
|
|
18164
|
+
return signal && isVitalSignal(String(signal.id)) ? signal.id : null;
|
|
18165
|
+
}
|
|
18166
|
+
var VITAL_SIGNAL_IDS, METRIC_SIGNAL_OWNERS, ROUTED_SIGNALS;
|
|
18167
|
+
var init_constraint_signals = __esm({
|
|
18168
|
+
"src/data/gtm-counsel/constraint-signals.ts"() {
|
|
18169
|
+
"use strict";
|
|
18170
|
+
init_metric_definitions();
|
|
18171
|
+
init_play_routing();
|
|
18172
|
+
VITAL_SIGNAL_IDS = [
|
|
18173
|
+
"freshness",
|
|
18174
|
+
"flow_rate",
|
|
18175
|
+
"drop_rate",
|
|
18176
|
+
"signal_to_noise",
|
|
18177
|
+
"thread_depth"
|
|
18178
|
+
];
|
|
18179
|
+
METRIC_SIGNAL_OWNERS = {
|
|
18180
|
+
nrr: ["counsel_cs"],
|
|
18181
|
+
grr: ["counsel_cs"],
|
|
18182
|
+
contraction_arr: ["counsel_cs"],
|
|
18183
|
+
logo_churn: ["counsel_cs"],
|
|
18184
|
+
early_warning_lead_time: ["counsel_cs"],
|
|
18185
|
+
packet_field_completeness: ["counsel_cs"],
|
|
18186
|
+
ttv: ["counsel_cs"],
|
|
18187
|
+
magic_number: ["counsel_marketing"],
|
|
18188
|
+
mql_to_opp: ["counsel_marketing"],
|
|
18189
|
+
cost_per_pipeline: ["counsel_marketing"],
|
|
18190
|
+
named_list_pipeline: ["counsel_marketing"],
|
|
18191
|
+
named_list_win_rate: ["counsel_marketing"],
|
|
18192
|
+
pipeline_coverage: ["counsel_marketing", "counsel_sales"],
|
|
18193
|
+
avg_sales_cycle: ["counsel_sales"],
|
|
18194
|
+
win_rate: ["counsel_sales"],
|
|
18195
|
+
weighted_pipeline: ["counsel_revops", "counsel_sales"],
|
|
18196
|
+
forecast_commit_history: ["counsel_revops", "counsel_sales"]
|
|
18197
|
+
};
|
|
18198
|
+
ROUTED_SIGNALS = new Set(
|
|
18199
|
+
Object.values(PLAY_ROUTING).flatMap(
|
|
18200
|
+
(routing) => routing.constraint_signals.map((signal) => String(signal))
|
|
18201
|
+
)
|
|
18202
|
+
);
|
|
18203
|
+
}
|
|
18204
|
+
});
|
|
18205
|
+
|
|
18206
|
+
// src/data/gtm-counsel/seating.ts
|
|
18207
|
+
function defaultSeatOrder(tree) {
|
|
18208
|
+
const tieBreak = TREE_TIE_BREAKS[tree];
|
|
18209
|
+
return COUNSEL_PACKS.filter(
|
|
18210
|
+
(pack) => pack.id !== "counsel_exec"
|
|
18211
|
+
).slice().sort((a, b) => {
|
|
18212
|
+
const weightDelta = b.tree_weight[tree] - a.tree_weight[tree];
|
|
18213
|
+
if (weightDelta !== 0) return weightDelta;
|
|
18214
|
+
return tieBreak.indexOf(a.id) - tieBreak.indexOf(b.id);
|
|
18215
|
+
}).map((pack) => pack.id);
|
|
18216
|
+
}
|
|
18217
|
+
function baselineSeatWeight(packId, tree) {
|
|
18218
|
+
const pack = COUNSEL_PACKS.find((candidate) => candidate.id === packId);
|
|
18219
|
+
const weight = pack?.tree_weight[tree] ?? 0.5;
|
|
18220
|
+
if (weight >= 0.8) return "full";
|
|
18221
|
+
if (weight >= 0.5) return "secondary";
|
|
18222
|
+
return "risk_note";
|
|
18223
|
+
}
|
|
18224
|
+
function inferConstraintOwnerPacks(objective, playIds = [], gatingVital) {
|
|
18225
|
+
return resolveLegacyConstraint(objective, playIds, gatingVital).locked_owners;
|
|
18226
|
+
}
|
|
18227
|
+
function inferConstraintOwnerPack(objective, playIds = [], gatingVital) {
|
|
18228
|
+
return inferConstraintOwnerPacks(objective, playIds, gatingVital)[0] ?? null;
|
|
18229
|
+
}
|
|
18230
|
+
function inferRoundtableBias(objective, profile) {
|
|
18231
|
+
const text = [objective, profile?.user_scope, profile?.custom_context].filter(Boolean).join(" ").toLowerCase();
|
|
18232
|
+
const notes = [];
|
|
18233
|
+
const demoteToRiskNote = [];
|
|
18234
|
+
const preferFull = [];
|
|
18235
|
+
const strongMarketing = /marketing (lens|view|perspective)|from marketing|as cmo\b/.test(text);
|
|
18236
|
+
const strongRevops = /revops (lens|view)|rev ops (lens|view)|systems view|from revops/.test(text);
|
|
18237
|
+
const strongCs = /cs (lens|view)|success (lens|view)|retention view|nrr view/.test(text);
|
|
18238
|
+
const strongSales = /sales (lens|view|perspective)|from sales|ae view|quota view/.test(text);
|
|
18239
|
+
const weakMarketing = /\b(cmo|demand gen|demand-gen|mqls?\b|abm\b|pmm\b)\b/.test(text);
|
|
18240
|
+
const weakRevops = /\b(revops|rev ops|routing sla|gtm engineer)\b/.test(text);
|
|
18241
|
+
const weakCs = /\b(customer success|renewals manager|csm\b)\b/.test(text);
|
|
18242
|
+
const weakSales = /\b(vp sales|sales manager|quota attain)\b/.test(text);
|
|
18243
|
+
const notCro = /not cro|ignore cro|operator framing|not ceo.centric|skip exec politics/.test(text);
|
|
18244
|
+
if (strongMarketing || weakMarketing) {
|
|
18245
|
+
preferFull.push("counsel_marketing", "counsel_exec");
|
|
18246
|
+
notes.push(strongMarketing ? "Bias: marketing-weighted roundtable" : "Bias: marketing mentioned \u2014 keep marketing full");
|
|
18247
|
+
}
|
|
18248
|
+
if (strongRevops || weakRevops) {
|
|
18249
|
+
preferFull.push("counsel_revops", "counsel_exec");
|
|
18250
|
+
notes.push(strongRevops ? "Bias: RevOps-weighted roundtable" : "Bias: RevOps mentioned \u2014 keep RevOps full");
|
|
18251
|
+
}
|
|
18252
|
+
if (strongCs || weakCs) {
|
|
18253
|
+
preferFull.push("counsel_cs", "counsel_exec");
|
|
18254
|
+
notes.push(strongCs ? "Bias: CS-weighted roundtable" : "Bias: CS mentioned \u2014 keep CS full");
|
|
18255
|
+
}
|
|
18256
|
+
if (strongSales || weakSales) {
|
|
18257
|
+
preferFull.push("counsel_sales", "counsel_exec");
|
|
18258
|
+
notes.push(strongSales ? "Bias: sales-weighted roundtable" : "Bias: sales mentioned \u2014 keep sales full");
|
|
18259
|
+
}
|
|
18260
|
+
if (notCro) {
|
|
18261
|
+
notes.push("Bias: operator framing for exec seat (not CRO-centric voice)");
|
|
18262
|
+
}
|
|
18263
|
+
const strong = [
|
|
18264
|
+
strongMarketing ? "counsel_marketing" : null,
|
|
18265
|
+
strongRevops ? "counsel_revops" : null,
|
|
18266
|
+
strongCs ? "counsel_cs" : null,
|
|
18267
|
+
strongSales ? "counsel_sales" : null
|
|
18268
|
+
].filter((p) => p != null);
|
|
18269
|
+
if (strong.length === 1) {
|
|
18270
|
+
for (const p of ALL_FUNCTION) {
|
|
18271
|
+
if (p !== strong[0]) demoteToRiskNote.push(p);
|
|
18272
|
+
}
|
|
18273
|
+
}
|
|
18274
|
+
return { demoteToRiskNote, preferFull, notes };
|
|
18275
|
+
}
|
|
18276
|
+
function buildRoundtableSeating(opts) {
|
|
18277
|
+
const {
|
|
18278
|
+
tree,
|
|
18279
|
+
objective,
|
|
18280
|
+
profile = null,
|
|
18281
|
+
playIds = [],
|
|
18282
|
+
gatingVital = null,
|
|
18283
|
+
operatorConstraints = []
|
|
18284
|
+
} = opts;
|
|
18285
|
+
const bias = inferRoundtableBias(objective, profile);
|
|
18286
|
+
const objectiveContext = {
|
|
18287
|
+
objective,
|
|
18288
|
+
play_ids: playIds,
|
|
18289
|
+
operator_constraints: operatorConstraints
|
|
18290
|
+
};
|
|
18291
|
+
const constraint = opts.resolvedConstraint ?? resolveConstraint({
|
|
18292
|
+
health: opts.health ?? {
|
|
18293
|
+
gating_vital: gatingVital && ["freshness", "flow_rate", "drop_rate", "signal_to_noise", "thread_depth"].includes(
|
|
18294
|
+
gatingVital
|
|
18295
|
+
) ? gatingVital : null
|
|
18296
|
+
},
|
|
18297
|
+
objective: objectiveContext
|
|
18298
|
+
});
|
|
18299
|
+
const constraintOwners = constraint.locked_owners;
|
|
18300
|
+
const ownerSet = new Set(constraintOwners);
|
|
18301
|
+
const demote = new Set(bias.demoteToRiskNote);
|
|
18302
|
+
const prefer = new Set(bias.preferFull);
|
|
18303
|
+
const seats = [{ pack_id: "counsel_exec", weight: "full" }];
|
|
18304
|
+
for (const packId of defaultSeatOrder(tree)) {
|
|
18305
|
+
let weight = baselineSeatWeight(packId, tree);
|
|
18306
|
+
if (demote.has(packId) && !prefer.has(packId)) weight = "risk_note";
|
|
18307
|
+
if (prefer.has(packId)) weight = "full";
|
|
18308
|
+
if (ownerSet.has(packId)) weight = "full";
|
|
18309
|
+
seats.push({ pack_id: packId, weight });
|
|
18310
|
+
}
|
|
18311
|
+
for (const owner of constraintOwners) {
|
|
18312
|
+
if (!seats.some((s) => s.pack_id === owner)) {
|
|
18313
|
+
seats.push({ pack_id: owner, weight: "full" });
|
|
18314
|
+
}
|
|
18315
|
+
}
|
|
18316
|
+
const bias_notes = [...bias.notes, ...constraint.notes];
|
|
18317
|
+
if (constraintOwners.length) {
|
|
18318
|
+
bias_notes.push(`Constraint-owning pack locked full: ${constraintOwners.join(", ")}`);
|
|
18319
|
+
}
|
|
18320
|
+
return { tree, seats, bias_notes, constraint };
|
|
18321
|
+
}
|
|
18322
|
+
var ALL_FUNCTION, TREE_TIE_BREAKS;
|
|
18323
|
+
var init_seating = __esm({
|
|
18324
|
+
"src/data/gtm-counsel/seating.ts"() {
|
|
18325
|
+
"use strict";
|
|
18326
|
+
init_packs();
|
|
18327
|
+
init_constraint_signals();
|
|
18328
|
+
ALL_FUNCTION = [
|
|
18329
|
+
"counsel_sales",
|
|
18330
|
+
"counsel_marketing",
|
|
18331
|
+
"counsel_revops",
|
|
18332
|
+
"counsel_cs"
|
|
18333
|
+
];
|
|
18334
|
+
TREE_TIE_BREAKS = {
|
|
18335
|
+
plg: ["counsel_cs", "counsel_marketing", "counsel_revops", "counsel_sales"],
|
|
18336
|
+
smb_velocity: ["counsel_sales", "counsel_revops", "counsel_marketing", "counsel_cs"],
|
|
18337
|
+
mid_market: ["counsel_sales", "counsel_marketing", "counsel_revops", "counsel_cs"],
|
|
18338
|
+
enterprise: ["counsel_sales", "counsel_marketing", "counsel_revops", "counsel_cs"]
|
|
18339
|
+
};
|
|
18340
|
+
}
|
|
18341
|
+
});
|
|
18342
|
+
|
|
18343
|
+
// src/data/gtm-counsel/index.ts
|
|
18344
|
+
var gtm_counsel_exports = {};
|
|
18345
|
+
__export(gtm_counsel_exports, {
|
|
18346
|
+
COUNSEL_PACKS: () => COUNSEL_PACKS,
|
|
18347
|
+
PLAY_COUNSEL_HOOKS: () => PLAY_COUNSEL_HOOKS,
|
|
18348
|
+
PLAY_ROUTING: () => PLAY_ROUTING,
|
|
18349
|
+
ROLE_CARDS: () => ROLE_CARDS,
|
|
18350
|
+
buildCounselCatalogBlock: () => buildCounselCatalogBlock,
|
|
18351
|
+
buildOrgTreeContextLine: () => buildOrgTreeContextLine,
|
|
18352
|
+
buildRoundtableSeating: () => buildRoundtableSeating,
|
|
18353
|
+
counselIdsForPlay: () => counselIdsForPlay,
|
|
18354
|
+
extractGatingVitalFromDigest: () => extractGatingVitalFromDigest,
|
|
18355
|
+
formatCounselDetail: () => formatCounselDetail,
|
|
18356
|
+
formatRoleCardDetail: () => formatRoleCardDetail,
|
|
18357
|
+
getActiveOrgTree: () => getActiveOrgTree,
|
|
18358
|
+
getCounselPackById: () => getCounselPackById,
|
|
18359
|
+
getPlayRouting: () => getPlayRouting,
|
|
18360
|
+
getRoleCardById: () => getRoleCardById,
|
|
18361
|
+
graphForPlay: () => graphForPlay,
|
|
18362
|
+
inferConstraintOwnerPack: () => inferConstraintOwnerPack,
|
|
18363
|
+
inferConstraintOwnerPacks: () => inferConstraintOwnerPacks,
|
|
18364
|
+
inferRoundtableBias: () => inferRoundtableBias,
|
|
18365
|
+
isKnownConstraintSignal: () => isKnownConstraintSignal,
|
|
18366
|
+
listCounselPackIds: () => listCounselPackIds,
|
|
18367
|
+
listOrgTreeIds: () => listOrgTreeIds,
|
|
18368
|
+
listRoleCardIds: () => listRoleCardIds,
|
|
18369
|
+
ownersForConstraintSignal: () => ownersForConstraintSignal,
|
|
18370
|
+
playRoutingIntegrityIssues: () => playRoutingIntegrityIssues,
|
|
18371
|
+
primaryOwnerForPlay: () => primaryOwnerForPlay,
|
|
18372
|
+
resolveConstraint: () => resolveConstraint,
|
|
18373
|
+
resolveOrgTree: () => resolveOrgTree
|
|
18374
|
+
});
|
|
18375
|
+
function buildCounselCatalogBlock() {
|
|
18376
|
+
const lines = COUNSEL_PACKS.map((p) => p.catalog_line).join("\n");
|
|
18377
|
+
return `GTM counsel packs (function perspectives). Call get_counsel_detail for a pack body. Open get_role_card only for ids listed on the pack you pulled \u2014 do not enumerate every role. Never invent reorgs; never name methodology brands to the user.
|
|
18378
|
+
${lines}`;
|
|
18379
|
+
}
|
|
18380
|
+
function buildOrgTreeContextLine(profile) {
|
|
18381
|
+
const p = profile === void 0 ? loadProfile() : profile;
|
|
18382
|
+
const tree = resolveOrgTree(p);
|
|
18383
|
+
return `- Org tree: ${tree}`;
|
|
18384
|
+
}
|
|
18385
|
+
function getActiveOrgTree(profile) {
|
|
18386
|
+
const p = profile === void 0 ? loadProfile() : profile;
|
|
18387
|
+
return resolveOrgTree(p);
|
|
18388
|
+
}
|
|
18389
|
+
function formatCounselDetail(pack, tree) {
|
|
18390
|
+
return {
|
|
18391
|
+
counsel_id: pack.id,
|
|
18392
|
+
catalog_line: pack.catalog_line,
|
|
18393
|
+
vital_links: pack.vital_links,
|
|
18394
|
+
board_cut: pack.board_cut,
|
|
18395
|
+
operator_cut: pack.operator_cut,
|
|
18396
|
+
when_to_use: pack.when_to_use,
|
|
18397
|
+
kill_rules: pack.kill_rules,
|
|
18398
|
+
silent_brand_rule: pack.silent_brand_rule,
|
|
18399
|
+
role_card_ids: pack.role_card_ids,
|
|
18400
|
+
tree_weight: pack.tree_weight[tree],
|
|
18401
|
+
org_tree: tree,
|
|
18402
|
+
body: pack.body,
|
|
18403
|
+
notice: "Apply moves in body/board_cut/operator_cut. Never name internal methodology brands to the user. Open get_role_card for listed role_card_ids when challenging owners."
|
|
18404
|
+
};
|
|
18405
|
+
}
|
|
18406
|
+
function formatRoleCardDetail(card, tree) {
|
|
18407
|
+
return {
|
|
18408
|
+
role_id: card.id,
|
|
18409
|
+
title: card.title,
|
|
18410
|
+
function: card.function,
|
|
18411
|
+
org_tree: tree,
|
|
18412
|
+
presence: card.presence[tree],
|
|
18413
|
+
owns: card.owns,
|
|
18414
|
+
does_not_own: card.does_not_own,
|
|
18415
|
+
success_metrics: card.success_metrics,
|
|
18416
|
+
typical_decisions: card.typical_decisions,
|
|
18417
|
+
data_they_trust: card.data_they_trust,
|
|
18418
|
+
consult_questions: card.consult_questions,
|
|
18419
|
+
failure_modes: card.failure_modes,
|
|
18420
|
+
tensions: card.tensions,
|
|
18421
|
+
play_hooks: card.play_hooks,
|
|
18422
|
+
signal_links: card.signal_links,
|
|
18423
|
+
// Transitional response field for clients built before the split.
|
|
18424
|
+
vital_play_hooks: [...card.play_hooks, ...card.signal_links],
|
|
18425
|
+
stress_notes: card.stress_notes,
|
|
18426
|
+
notice: presenceNotice(card.presence[tree]) + " Function-shaped owners only \u2014 do not invent headcount or named people."
|
|
18427
|
+
};
|
|
18428
|
+
}
|
|
18429
|
+
function presenceNotice(p) {
|
|
18430
|
+
if (p === "absent") return "This role is typically absent on this org tree \u2014 treat advice as thin. ";
|
|
18431
|
+
if (p === "thin") return "This role is thin on this org tree \u2014 keep recommendations light. ";
|
|
18432
|
+
if (p === "present") return "This role is present but not always core on this org tree. ";
|
|
18433
|
+
return "This role is core on this org tree. ";
|
|
18434
|
+
}
|
|
18435
|
+
var init_gtm_counsel = __esm({
|
|
18436
|
+
"src/data/gtm-counsel/index.ts"() {
|
|
18437
|
+
"use strict";
|
|
18438
|
+
init_profile();
|
|
18439
|
+
init_packs();
|
|
18440
|
+
init_resolve_tree();
|
|
18441
|
+
init_role_cards();
|
|
18442
|
+
init_seating();
|
|
18443
|
+
init_play_routing();
|
|
18444
|
+
init_constraint_signals();
|
|
18445
|
+
}
|
|
18446
|
+
});
|
|
18447
|
+
|
|
16782
18448
|
// src/memory/play-outcomes.ts
|
|
16783
18449
|
var play_outcomes_exports = {};
|
|
16784
18450
|
__export(play_outcomes_exports, {
|
|
@@ -19540,10 +21206,10 @@ async function insertDirect(dataset) {
|
|
|
19540
21206
|
health: null
|
|
19541
21207
|
};
|
|
19542
21208
|
}
|
|
19543
|
-
function hashString(
|
|
21209
|
+
function hashString(str3) {
|
|
19544
21210
|
let hash = 0;
|
|
19545
|
-
for (let i = 0; i <
|
|
19546
|
-
const char =
|
|
21211
|
+
for (let i = 0; i < str3.length; i++) {
|
|
21212
|
+
const char = str3.charCodeAt(i);
|
|
19547
21213
|
hash = (hash << 5) - hash + char | 0;
|
|
19548
21214
|
}
|
|
19549
21215
|
return Math.abs(hash);
|
|
@@ -21017,7 +22683,7 @@ var init_tool_schemas = __esm({
|
|
|
21017
22683
|
},
|
|
21018
22684
|
{
|
|
21019
22685
|
name: "get_play_detail",
|
|
21020
|
-
description: "Read the full definition of a playbook play by id: trigger condition, why it works, step-by-step actions,
|
|
22686
|
+
description: "Read the full definition of a playbook play by id: trigger condition, why it works, step-by-step actions, counsel owner/contributors, prerequisites/exclusions, and structured exam. The system prompt lists only the play catalog \u2014 call this before recommending a play when the user needs the how, or when drafting workstream actions from a play.",
|
|
21021
22687
|
parameters: {
|
|
21022
22688
|
type: "object",
|
|
21023
22689
|
properties: {
|
|
@@ -21045,6 +22711,41 @@ var init_tool_schemas = __esm({
|
|
|
21045
22711
|
required: ["framework_id"]
|
|
21046
22712
|
}
|
|
21047
22713
|
},
|
|
22714
|
+
{
|
|
22715
|
+
name: "get_counsel_detail",
|
|
22716
|
+
description: "Read a GTM function counsel pack (sales, marketing, revops, exec, cs): when to use, kill rules, vital links, role cards to open, and instruction body. Use before recommending cross-functional work or during strategist roundtable. Never name methodology brands to the user; no invented reorgs.",
|
|
22717
|
+
parameters: {
|
|
22718
|
+
type: "object",
|
|
22719
|
+
properties: {
|
|
22720
|
+
counsel_id: {
|
|
22721
|
+
type: "string",
|
|
22722
|
+
maxLength: 40,
|
|
22723
|
+
description: "Exact id: counsel_sales, counsel_marketing, counsel_revops, counsel_exec, or counsel_cs."
|
|
22724
|
+
}
|
|
22725
|
+
},
|
|
22726
|
+
required: ["counsel_id"]
|
|
22727
|
+
}
|
|
22728
|
+
},
|
|
22729
|
+
{
|
|
22730
|
+
name: "get_role_card",
|
|
22731
|
+
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.",
|
|
22732
|
+
parameters: {
|
|
22733
|
+
type: "object",
|
|
22734
|
+
properties: {
|
|
22735
|
+
role_id: {
|
|
22736
|
+
type: "string",
|
|
22737
|
+
maxLength: 40,
|
|
22738
|
+
description: "Exact role id listed on the pack you pulled via get_counsel_detail, e.g. 'ae', 'demand_gen', 'gtm_engineer'."
|
|
22739
|
+
},
|
|
22740
|
+
org_tree: {
|
|
22741
|
+
type: "string",
|
|
22742
|
+
enum: ["plg", "smb_velocity", "mid_market", "enterprise"],
|
|
22743
|
+
description: "Optional org tree override; defaults to profile sales_motion / mid_market."
|
|
22744
|
+
}
|
|
22745
|
+
},
|
|
22746
|
+
required: ["role_id"]
|
|
22747
|
+
}
|
|
22748
|
+
},
|
|
21048
22749
|
{
|
|
21049
22750
|
name: "get_session_brief",
|
|
21050
22751
|
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.",
|
|
@@ -21171,7 +22872,7 @@ Optional packs via get_framework_detail: meddpicc, challenger, jtbd, porter, mck
|
|
|
21171
22872
|
function frameworkIdsForPlay(playId) {
|
|
21172
22873
|
return PLAY_FRAMEWORK_HOOKS[playId] ?? [];
|
|
21173
22874
|
}
|
|
21174
|
-
var PLAY_FRAMEWORK_HOOKS,
|
|
22875
|
+
var PLAY_FRAMEWORK_HOOKS, SILENT2, FRAMEWORK_PACKS;
|
|
21175
22876
|
var init_frameworks = __esm({
|
|
21176
22877
|
"src/data/frameworks.ts"() {
|
|
21177
22878
|
"use strict";
|
|
@@ -21180,9 +22881,15 @@ var init_frameworks = __esm({
|
|
|
21180
22881
|
"unstick-pipeline": ["bottleneck", "okr_measurability", "owner_shape"],
|
|
21181
22882
|
"fix-handoff-gap": ["bottleneck", "owner_shape", "aar"],
|
|
21182
22883
|
"retarget-effort": ["bottleneck", "gtm_engineering"],
|
|
21183
|
-
"multi-thread-deals": ["owner_shape", "heilmeier"]
|
|
22884
|
+
"multi-thread-deals": ["owner_shape", "heilmeier"],
|
|
22885
|
+
"harden-routing-sla": ["bottleneck", "gtm_engineering", "owner_shape", "aar"],
|
|
22886
|
+
"demand-quality-over-volume": ["bottleneck", "heilmeier", "okr_measurability"],
|
|
22887
|
+
"sales-cs-handoff-packet": ["owner_shape", "aar", "okr_measurability"],
|
|
22888
|
+
"renewal-early-warning": ["aar", "owner_shape", "okr_measurability"],
|
|
22889
|
+
"abm-orchestration": ["bottleneck", "heilmeier", "owner_shape"],
|
|
22890
|
+
"forecast-ritual-hygiene": ["bottleneck", "aar", "owner_shape", "okr_measurability"]
|
|
21184
22891
|
};
|
|
21185
|
-
|
|
22892
|
+
SILENT2 = "Never name this framework (or DARPA, McKinsey, Goldratt, etc.) in customer-visible text. Apply the moves; keep the brand silent.";
|
|
21186
22893
|
FRAMEWORK_PACKS = [
|
|
21187
22894
|
{
|
|
21188
22895
|
id: "bottleneck",
|
|
@@ -21197,7 +22904,7 @@ var init_frameworks = __esm({
|
|
|
21197
22904
|
"If the recommendation does not move the constraint, demote or drop it.",
|
|
21198
22905
|
"Do not prescribe parallel fixes that starve the constraint of capacity."
|
|
21199
22906
|
],
|
|
21200
|
-
silent_brand_rule:
|
|
22907
|
+
silent_brand_rule: SILENT2,
|
|
21201
22908
|
body: `BOTTLENECK (constraint first):
|
|
21202
22909
|
- Respect layer gating: freshness trust \u2192 flow/drop movement \u2192 signal efficiency \u2192 thread resilience.
|
|
21203
22910
|
- Name the single constraint vital or pipeline stage with its score and dollar label.
|
|
@@ -21214,7 +22921,7 @@ var init_frameworks = __esm({
|
|
|
21214
22921
|
operator_cut: "Headline \u226420 words \u2192 2\u20133 non-overlapping drivers \u2192 owner-shaped so-what.",
|
|
21215
22922
|
when_to_use: "Every executive-facing answer, finding, recap Story, summary_30k.",
|
|
21216
22923
|
kill_rules: ["Never open with methodology or a full scorecard.", "Merge overlapping drivers."],
|
|
21217
|
-
silent_brand_rule:
|
|
22924
|
+
silent_brand_rule: SILENT2,
|
|
21218
22925
|
body: `PACKAGING (answer-first):
|
|
21219
22926
|
- Governing thought first (verdict + dollars).
|
|
21220
22927
|
- Then the situation and complication that make the call true (support, not opener).
|
|
@@ -21231,7 +22938,7 @@ var init_frameworks = __esm({
|
|
|
21231
22938
|
operator_cut: "Belief \u2192 evidence from vitals/metrics \u2192 keep, kill, or re-sequence the play.",
|
|
21232
22939
|
when_to_use: "Strategy review, session distill calibrations, win/miss logging.",
|
|
21233
22940
|
kill_rules: ["Do not log vanity narrative without a measured instrument.", "Calibrations supersede; do not pile contradictions."],
|
|
21234
|
-
silent_brand_rule:
|
|
22941
|
+
silent_brand_rule: SILENT2,
|
|
21235
22942
|
body: `AFTER-ACTION:
|
|
21236
22943
|
- BELIEF: what we expected to move (metric + range + check date).
|
|
21237
22944
|
- EVIDENCE: what the instruments actually showed (baseline \u2192 reading).
|
|
@@ -21248,7 +22955,7 @@ var init_frameworks = __esm({
|
|
|
21248
22955
|
operator_cut: "RevOps / AE lead / CS lead / Marketing ops \u2014 R does, A decides; avoid named individuals in plans.",
|
|
21249
22956
|
when_to_use: "Workstream titles, handoff plans, play prescriptions.",
|
|
21250
22957
|
kill_rules: ["No orphan actions without an accountable function.", "Do not invent headcount or reorgs."],
|
|
21251
|
-
silent_brand_rule:
|
|
22958
|
+
silent_brand_rule: SILENT2,
|
|
21252
22959
|
body: `OWNER-SHAPE:
|
|
21253
22960
|
- Name the function (RevOps, sales manager, CS lead, marketing ops) \u2014 not a person's name from the CRM.
|
|
21254
22961
|
- Accountable decides; Responsible executes. Consulted optional; Inform via existing channels.
|
|
@@ -21264,7 +22971,7 @@ var init_frameworks = __esm({
|
|
|
21264
22971
|
operator_cut: "Baseline, target range, check date, measured_by instrument \u2014 or demote to assumption.",
|
|
21265
22972
|
when_to_use: "Strategist outcomes, Heilmeier exams, milestones.",
|
|
21266
22973
|
kill_rules: ["No 'improve' without a number.", "No exam faster than sales-cycle physics."],
|
|
21267
|
-
silent_brand_rule:
|
|
22974
|
+
silent_brand_rule: SILENT2,
|
|
21268
22975
|
body: `EXAMS (objective + key results):
|
|
21269
22976
|
- Objective = precise end state in one line.
|
|
21270
22977
|
- Key results = mid-term check + final exam: metric, baseline, target RANGE, check date, instrument.
|
|
@@ -21280,7 +22987,7 @@ var init_frameworks = __esm({
|
|
|
21280
22987
|
operator_cut: "Kill restatements; demote unmeasurable ideas.",
|
|
21281
22988
|
when_to_use: "Deep recommend, think before draft_strategy, strategist Stage C.",
|
|
21282
22989
|
kill_rules: ["Fail newness, stake, or exams \u2192 do not recommend."],
|
|
21283
|
-
silent_brand_rule:
|
|
22990
|
+
silent_brand_rule: SILENT2,
|
|
21284
22991
|
body: `IDEA GATE: end state, status quo + limit, newness, stake + cost of inaction, risks/payoffs, effort, time physics, mid+final exams. Kill on newness/stake/exams failure.`
|
|
21285
22992
|
},
|
|
21286
22993
|
{
|
|
@@ -21293,7 +23000,7 @@ var init_frameworks = __esm({
|
|
|
21293
23000
|
operator_cut: "Same shape for findings and deep answers.",
|
|
21294
23001
|
when_to_use: "All packaged analyst prose.",
|
|
21295
23002
|
kill_rules: ["Methodology-first openers die."],
|
|
21296
|
-
silent_brand_rule:
|
|
23003
|
+
silent_brand_rule: SILENT2,
|
|
21297
23004
|
body: `PYRAMID: HEADLINE FIRST \u2192 DRIVERS (non-overlapping) \u2192 SO-WHAT. Recall test. Altitude control.`
|
|
21298
23005
|
},
|
|
21299
23006
|
{
|
|
@@ -21306,7 +23013,7 @@ var init_frameworks = __esm({
|
|
|
21306
23013
|
operator_cut: "Pair every cleanup with a mechanism and an instrument.",
|
|
21307
23014
|
when_to_use: "When recommending action on tool-capable surfaces.",
|
|
21308
23015
|
kill_rules: ["Skipping the rung below will not hold."],
|
|
21309
|
-
silent_brand_rule:
|
|
23016
|
+
silent_brand_rule: SILENT2,
|
|
21310
23017
|
body: `GTM SYSTEMS: three rungs (foundation \u2192 modeling \u2192 activation); signals over lists; CRM is cheapest pipeline; every fix gets a mechanism; instrument what you change.`
|
|
21311
23018
|
},
|
|
21312
23019
|
{
|
|
@@ -21319,7 +23026,7 @@ var init_frameworks = __esm({
|
|
|
21319
23026
|
operator_cut: "Map Metrics, Economic buyer, Decision criteria/process, Paper process, Identify pain, Champion, Competition \u2014 as observation, not CRM surgery.",
|
|
21320
23027
|
when_to_use: "Thread-depth or stuck late-stage deals when the user asks how to qualify.",
|
|
21321
23028
|
kill_rules: ["Do not rewrite the CRM; observe and recommend contacts/process gaps."],
|
|
21322
|
-
silent_brand_rule:
|
|
23029
|
+
silent_brand_rule: SILENT2,
|
|
21323
23030
|
body: `DEAL QUALIFICATION LENS: use when single-threaded or stuck late-stage deals need a checklist. Prefer introducing a second contact and clarifying decision process over more outbound volume.`
|
|
21324
23031
|
},
|
|
21325
23032
|
{
|
|
@@ -21332,7 +23039,7 @@ var init_frameworks = __esm({
|
|
|
21332
23039
|
operator_cut: "In think: teach with evidence, tailor to this pipeline, take control of next step \u2014 still stethoscope.",
|
|
21333
23040
|
when_to_use: "Think channel when the user is soft on a weak story.",
|
|
21334
23041
|
kill_rules: ["Do not turn into a pitch script or CRM sequence builder."],
|
|
21335
|
-
silent_brand_rule:
|
|
23042
|
+
silent_brand_rule: SILENT2,
|
|
21336
23043
|
body: `TEACH-TAILOR-TAKE: reframe with verified numbers; tailor to segment/motion; land one owner-shaped next step.`
|
|
21337
23044
|
},
|
|
21338
23045
|
{
|
|
@@ -21345,7 +23052,7 @@ var init_frameworks = __esm({
|
|
|
21345
23052
|
operator_cut: "When explaining ARR/NRR/vitals: what decision does this number unlock?",
|
|
21346
23053
|
when_to_use: "Metric definitions / board deck framing.",
|
|
21347
23054
|
kill_rules: ["Ornamental metrics without a decision job die."],
|
|
21348
|
-
silent_brand_rule:
|
|
23055
|
+
silent_brand_rule: SILENT2,
|
|
21349
23056
|
body: `JOB OF THE METRIC: for each number, name the decision it serves. If none, cut it from the board package.`
|
|
21350
23057
|
},
|
|
21351
23058
|
{
|
|
@@ -21358,7 +23065,7 @@ var init_frameworks = __esm({
|
|
|
21358
23065
|
operator_cut: "Do not let industry essays displace vital-sign evidence.",
|
|
21359
23066
|
when_to_use: "Only when the user explicitly asks for competitive structure.",
|
|
21360
23067
|
kill_rules: ["Never use as a substitute for gating vital diagnosis."],
|
|
21361
|
-
silent_brand_rule:
|
|
23068
|
+
silent_brand_rule: SILENT2,
|
|
21362
23069
|
body: `INDUSTRY STRUCTURE: optional context. Prefer vital signs and dollars for recommendations.`
|
|
21363
23070
|
},
|
|
21364
23071
|
{
|
|
@@ -21371,7 +23078,7 @@ var init_frameworks = __esm({
|
|
|
21371
23078
|
operator_cut: "Refuse surgery; observe handoff/ownership symptoms via vitals.",
|
|
21372
23079
|
when_to_use: "Only if asked; redirect to owner-shape + bottleneck.",
|
|
21373
23080
|
kill_rules: ["No reorg prescriptions."],
|
|
21374
|
-
silent_brand_rule:
|
|
23081
|
+
silent_brand_rule: SILENT2,
|
|
21375
23082
|
body: `ORG ALIGNMENT: out of scope for CRM surgery. Point to function-shaped owners and the constraint vital instead.`
|
|
21376
23083
|
},
|
|
21377
23084
|
{
|
|
@@ -21384,7 +23091,7 @@ var init_frameworks = __esm({
|
|
|
21384
23091
|
operator_cut: "Already covered by Stage A + bottleneck \u2014 do not duplicate jargon.",
|
|
21385
23092
|
when_to_use: "Rarely; prefer existing grounding.",
|
|
21386
23093
|
kill_rules: ["Do not invent a parallel loop vocabulary for the user."],
|
|
21387
|
-
silent_brand_rule:
|
|
23094
|
+
silent_brand_rule: SILENT2,
|
|
21388
23095
|
body: `OBSERVE-ORIENT: prefer NTRP layer gating and Stage A hypothesis-first over a separate combat loop.`
|
|
21389
23096
|
}
|
|
21390
23097
|
];
|
|
@@ -21549,8 +23256,8 @@ async function handleGetVitalSignDetail(input, ctx) {
|
|
|
21549
23256
|
if (!vital) return { error: `Vital sign '${vitalSign}' not found` };
|
|
21550
23257
|
const entitySummary = {};
|
|
21551
23258
|
for (const detail of vital.entity_details) {
|
|
21552
|
-
const
|
|
21553
|
-
const key =
|
|
23259
|
+
const issue2 = detail.issue;
|
|
23260
|
+
const key = issue2 ?? "unclassified";
|
|
21554
23261
|
entitySummary[key] = (entitySummary[key] ?? 0) + 1;
|
|
21555
23262
|
}
|
|
21556
23263
|
return {
|
|
@@ -21791,10 +23498,30 @@ async function handleGetPlayDetail(input) {
|
|
|
21791
23498
|
steps: play.steps,
|
|
21792
23499
|
tools_that_help: play.tools_that_help,
|
|
21793
23500
|
expected_outcome: play.expected_outcome,
|
|
23501
|
+
exam: play.exam ?? null,
|
|
21794
23502
|
source: play.source ?? "seed",
|
|
21795
23503
|
local_track_record: trackRecord,
|
|
21796
23504
|
framework_ids: frameworkIdsForPlay2(play.id)
|
|
21797
23505
|
};
|
|
23506
|
+
try {
|
|
23507
|
+
const {
|
|
23508
|
+
counselIdsForPlay: counselIdsForPlay2,
|
|
23509
|
+
getPlayRouting: getPlayRouting2,
|
|
23510
|
+
ownersForConstraintSignal: ownersForConstraintSignal2
|
|
23511
|
+
} = await Promise.resolve().then(() => (init_gtm_counsel(), gtm_counsel_exports));
|
|
23512
|
+
const routing = getPlayRouting2(play.id) ?? play.routing;
|
|
23513
|
+
const fallbackSignal = play.trigger_vital_sign ?? play.trigger_metric ?? "";
|
|
23514
|
+
const fallbackOwner = fallbackSignal ? ownersForConstraintSignal2(fallbackSignal, play.trigger_condition)[0] ?? null : null;
|
|
23515
|
+
detail.primary_owner = routing?.primary_owner ?? fallbackOwner;
|
|
23516
|
+
detail.counsel_ids = routing?.counsel_ids ?? (fallbackOwner ? [fallbackOwner] : counselIdsForPlay2(play.id));
|
|
23517
|
+
detail.constraint_signals = routing?.constraint_signals ?? [play.trigger_vital_sign, play.trigger_metric].filter(Boolean);
|
|
23518
|
+
detail.graph = routing?.graph ?? null;
|
|
23519
|
+
} catch {
|
|
23520
|
+
detail.primary_owner = null;
|
|
23521
|
+
detail.counsel_ids = [];
|
|
23522
|
+
detail.constraint_signals = [];
|
|
23523
|
+
detail.graph = null;
|
|
23524
|
+
}
|
|
21798
23525
|
if (play.source === "learned") {
|
|
21799
23526
|
detail.security_notice = UNTRUSTED_CONTENT_NOTICE;
|
|
21800
23527
|
detail.why = wrapUntrustedContent(String(play.why ?? ""));
|
|
@@ -21828,6 +23555,44 @@ async function handleGetFrameworkDetail(input) {
|
|
|
21828
23555
|
notice: "Apply the moves in body/board_cut/operator_cut. Never name internal brand labels to the user."
|
|
21829
23556
|
};
|
|
21830
23557
|
}
|
|
23558
|
+
async function handleGetCounselDetail(input) {
|
|
23559
|
+
const counselId = typeof input.counsel_id === "string" ? input.counsel_id.trim() : "";
|
|
23560
|
+
const {
|
|
23561
|
+
getCounselPackById: getCounselPackById2,
|
|
23562
|
+
listCounselPackIds: listCounselPackIds2,
|
|
23563
|
+
formatCounselDetail: formatCounselDetail2,
|
|
23564
|
+
getActiveOrgTree: getActiveOrgTree2
|
|
23565
|
+
} = await Promise.resolve().then(() => (init_gtm_counsel(), gtm_counsel_exports));
|
|
23566
|
+
const pack = counselId ? getCounselPackById2(counselId) : void 0;
|
|
23567
|
+
if (!pack) {
|
|
23568
|
+
return {
|
|
23569
|
+
error: `Unknown counsel id '${counselId}'.`,
|
|
23570
|
+
valid_counsel_ids: listCounselPackIds2()
|
|
23571
|
+
};
|
|
23572
|
+
}
|
|
23573
|
+
return formatCounselDetail2(pack, getActiveOrgTree2());
|
|
23574
|
+
}
|
|
23575
|
+
async function handleGetRoleCard(input) {
|
|
23576
|
+
const roleId = typeof input.role_id === "string" ? input.role_id.trim() : "";
|
|
23577
|
+
const {
|
|
23578
|
+
getRoleCardById: getRoleCardById2,
|
|
23579
|
+
listRoleCardIds: listRoleCardIds2,
|
|
23580
|
+
formatRoleCardDetail: formatRoleCardDetail2,
|
|
23581
|
+
getActiveOrgTree: getActiveOrgTree2,
|
|
23582
|
+
listOrgTreeIds: listOrgTreeIds2
|
|
23583
|
+
} = await Promise.resolve().then(() => (init_gtm_counsel(), gtm_counsel_exports));
|
|
23584
|
+
const card = roleId ? getRoleCardById2(roleId) : void 0;
|
|
23585
|
+
if (!card) {
|
|
23586
|
+
return {
|
|
23587
|
+
error: `Unknown role id '${roleId}'.`,
|
|
23588
|
+
valid_role_ids: listRoleCardIds2()
|
|
23589
|
+
};
|
|
23590
|
+
}
|
|
23591
|
+
const treeArg = typeof input.org_tree === "string" ? input.org_tree.trim() : "";
|
|
23592
|
+
const trees = listOrgTreeIds2();
|
|
23593
|
+
const tree = trees.includes(treeArg) ? treeArg : getActiveOrgTree2();
|
|
23594
|
+
return formatRoleCardDetail2(card, tree);
|
|
23595
|
+
}
|
|
21831
23596
|
async function handleGetRevenueMetrics(_input, ctx) {
|
|
21832
23597
|
if (!ctx.metrics || ctx.metrics.length === 0) {
|
|
21833
23598
|
const { computeFullMetrics: computeFullMetrics2 } = await Promise.resolve().then(() => (init_compute(), compute_exports));
|
|
@@ -22123,6 +23888,8 @@ var init_tool_handlers = __esm({
|
|
|
22123
23888
|
query_entity_counts: (input, _) => handleQueryEntityCounts(input),
|
|
22124
23889
|
get_play_detail: (input, _) => handleGetPlayDetail(input),
|
|
22125
23890
|
get_framework_detail: (input, _) => handleGetFrameworkDetail(input),
|
|
23891
|
+
get_counsel_detail: (input, _) => handleGetCounselDetail(input),
|
|
23892
|
+
get_role_card: (input, _) => handleGetRoleCard(input),
|
|
22126
23893
|
get_session_brief: (input, _) => handleGetSessionBrief(input),
|
|
22127
23894
|
get_revenue_metrics: handleGetRevenueMetrics,
|
|
22128
23895
|
get_revenue_metrics_timeseries: (input, _) => handleGetRevenueMetricsTimeseries(input),
|
|
@@ -22422,6 +24189,8 @@ ${buildPlaybookBlock()}
|
|
|
22422
24189
|
|
|
22423
24190
|
${buildFrameworkCatalogBlock()}
|
|
22424
24191
|
|
|
24192
|
+
${buildCounselCatalogBlock()}
|
|
24193
|
+
|
|
22425
24194
|
MEASURABILITY CONTRACT (non-negotiable \u2014 this is what separates you from a slide deck):
|
|
22426
24195
|
- Treat the objective as the end state; treat expected outcomes and leading indicators as key results (mid-term and final exams).
|
|
22427
24196
|
- Every expected outcome and leading indicator needs: a metric, the current baseline copied from your verified grounding work, a target RANGE, a check date, and the named instrument that will measure it.
|
|
@@ -22433,7 +24202,7 @@ MEASURABILITY CONTRACT (non-negotiable \u2014 this is what separates you from a
|
|
|
22433
24202
|
SAFETY & EVIDENCE (non-negotiable):
|
|
22434
24203
|
${SAFETY_BLOCK}
|
|
22435
24204
|
|
|
22436
|
-
You will work in
|
|
24205
|
+
You will work in staged passes (ground \u2192 backcast \u2192 roundtable \u2192 stress). Follow the stage instructions in each message. Use tools deliberately \u2014 each call should answer a specific question you need for the plan.`;
|
|
22437
24206
|
}
|
|
22438
24207
|
function buildGroundingMessage(input) {
|
|
22439
24208
|
const sections = [];
|
|
@@ -22443,6 +24212,7 @@ function buildGroundingMessage(input) {
|
|
|
22443
24212
|
Establish verified reality before any planning. Work hypothesis-first, like an engagement manager on day one: form your top candidate explanations for what stands between today and the objective, then use tools to confirm or kill each one \u2014 don't boil the ocean.
|
|
22444
24213
|
1. Current state: which vital signs / metrics are worst, what are the exact scores and dollar values, which segments concentrate the problem?
|
|
22445
24214
|
2. CONSTRAINT: name the single gating vital (layer order) or stage where deals die \u2014 with score and dollar label. This is the bottleneck the plan must move first.
|
|
24215
|
+
Keep the five-vital health gate separate from the user's objective constraint. They may disagree; report both with evidence instead of silently replacing one.
|
|
22446
24216
|
3. What is already in flight (active strategies below, if any), what has worked before (wins), and what the local play track record says.
|
|
22447
24217
|
4. What are the binding constraints: data gaps that limit measurability, sales-cycle length that bounds verification speed, capacity signals?
|
|
22448
24218
|
Rank the problems you verify by dollars at stake \xD7 confidence in the read \xD7 speed to impact \u2014 that ranking becomes the spine of the plan. Non-constraint problems wait.
|
|
@@ -22465,6 +24235,18 @@ ${input.constraintsNote}`);
|
|
|
22465
24235
|
}
|
|
22466
24236
|
sections.push(`When you have verified what you need (aim for focused tool use, not exhaustive), respond with a REALITY DIGEST as strict JSON \u2014 no markdown fences, no prose before or after:
|
|
22467
24237
|
{
|
|
24238
|
+
"health_gate": {
|
|
24239
|
+
"signal": "one of freshness | flow_rate | drop_rate | signal_to_noise | thread_depth",
|
|
24240
|
+
"dollar_value": 0,
|
|
24241
|
+
"cause": "verified cause, not a recommendation",
|
|
24242
|
+
"evidence": "exact score / dollar / entity evidence"
|
|
24243
|
+
},
|
|
24244
|
+
"objective_constraint": {
|
|
24245
|
+
"signal": "verified vital, SaaS metric, or stage tied to the objective",
|
|
24246
|
+
"dollar_value": 0,
|
|
24247
|
+
"cause": "why this blocks the stated objective",
|
|
24248
|
+
"evidence": "exact metric / stage / tool evidence"
|
|
24249
|
+
},
|
|
22468
24250
|
"current_state": ["One line per verified fact you will build on, each with its exact number"],
|
|
22469
24251
|
"worst_problems_ordered": ["Problem + number + dollar value, in layer-dependency order"],
|
|
22470
24252
|
"constraints": ["Binding constraints you verified or were given"],
|
|
@@ -22479,15 +24261,70 @@ function buildBackcastMessage(objective) {
|
|
|
22479
24261
|
|
|
22480
24262
|
Reason in reverse: what must be true immediately before the objective holds? What must be true before that? Chain back to today, then forward-order the chain into 2-5 workstreams. For each: sequence rationale (what it unblocks), linked plays, owner-ready actions, effort hours, dated milestones with verification thresholds, tangible deliverables, an expected outcome RANGE anchored to a baseline from your reality digest, leading indicators that move earlier than the outcome, and a pre-decided contingency.
|
|
22481
24263
|
|
|
24264
|
+
Do not treat this as sales-physics-only. If the digest implicates marketing creation or handoff, split quality/acceptance from routing leak before prescribing demand spend. If instruments look untrustworthy, sequence RevOps trust before sales theater. If NRR/GRR/churn is the dollar, include CS. Still one constraint \u2014 do not parallelize.
|
|
24265
|
+
|
|
22482
24266
|
You may make a small number of additional tool calls to verify a specific baseline you are missing \u2014 but do not re-investigate broadly.
|
|
22483
24267
|
|
|
22484
24268
|
Respond with the full plan as strict JSON matching this schema \u2014 no markdown fences, no prose before or after:
|
|
22485
24269
|
${STRATEGIST_PLAN_SCHEMA_BLOCK}`;
|
|
22486
24270
|
}
|
|
22487
|
-
function
|
|
24271
|
+
function buildRoundtableMessage(input) {
|
|
24272
|
+
const bias = input.biasNotes.length > 0 ? `Bias notes:
|
|
24273
|
+
- ${input.biasNotes.join("\n- ")}
|
|
24274
|
+
|
|
24275
|
+
` : "";
|
|
24276
|
+
const owner = input.constraintOwner ? `Constraint-owning pack (must stay full seat): ${input.constraintOwner}
|
|
24277
|
+
|
|
24278
|
+
` : "";
|
|
24279
|
+
const constraint = input.constraint ? `CONSTRAINT CHANNELS:
|
|
24280
|
+
- Health gate: ${input.constraint.health_gate ? String(input.constraint.health_gate.id) : "not verified"} \u2192 ${input.constraint.health_gate_owners.join(", ") || "no mapped owner"}
|
|
24281
|
+
- Objective constraint: ${input.constraint.objective_constraint ? String(input.constraint.objective_constraint.id) : "not verified"} \u2192 ${input.constraint.objective_owners.join(", ") || "no mapped owner"}
|
|
24282
|
+
- If these differ, preserve both seats and have exec sequence the objective behind the health gate unless evidence supports another order.
|
|
24283
|
+
|
|
24284
|
+
` : "";
|
|
24285
|
+
return `STAGE R \u2014 ROUNDTABLE. Org tree: ${input.tree}. Before stress-testing, seat GTM counsel packs and challenge the draft.
|
|
24286
|
+
|
|
24287
|
+
${owner}${constraint}${bias}EXEC MERGE SEAT: counsel_exec (full; call get_counsel_detail; output only in top-level exec).
|
|
24288
|
+
FUNCTION SEATS (weight full = deep challenge; secondary/risk_note = short risk note only):
|
|
24289
|
+
${input.seatsBlock}
|
|
24290
|
+
|
|
24291
|
+
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.
|
|
24292
|
+
|
|
24293
|
+
DRAFT PLAN JSON:
|
|
24294
|
+
${input.draftPlanJson}
|
|
24295
|
+
|
|
24296
|
+
Respond with STRICT JSON only (no prose, no fences):
|
|
24297
|
+
{
|
|
24298
|
+
"seats": [
|
|
24299
|
+
{
|
|
24300
|
+
"pack": "counsel_sales",
|
|
24301
|
+
"stance": "support" | "challenge" | "abstain",
|
|
24302
|
+
"constraint_agree": true,
|
|
24303
|
+
"challenge": "\u226440 words \u2014 what the draft gets wrong or misses; empty only if support + risk or exec pre-mortem",
|
|
24304
|
+
"missing_exam": "metric + date + instrument (or empty string)",
|
|
24305
|
+
"cross_functional_risk": "\u226430 words",
|
|
24306
|
+
"role_cards_used": ["ae"]
|
|
24307
|
+
}
|
|
24308
|
+
],
|
|
24309
|
+
"exec": {
|
|
24310
|
+
"governing_constraint": "one constraint with dollar label",
|
|
24311
|
+
"sequence": ["play or workstream order"],
|
|
24312
|
+
"drop": ["non-constraint theater to kill"],
|
|
24313
|
+
"merge_verdict": "\u226450 words",
|
|
24314
|
+
"pre_mortem": "if all seats support: the one most likely death cause; else optional"
|
|
24315
|
+
}
|
|
24316
|
+
}
|
|
24317
|
+
|
|
24318
|
+
Rules: do not invent a fake challenge. Empty challenge is allowed only with stance support AND a non-empty cross_functional_risk or a non-empty exec.pre_mortem. If every seat supports, exec MUST write a pre-mortem death cause. Constraint-owning pack may not abstain without a risk note.`;
|
|
24319
|
+
}
|
|
24320
|
+
function buildStressTestMessage(roundtableDigest) {
|
|
24321
|
+
const roundtableBlock = roundtableDigest ? `0. ROUNDTABLE 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 exec.drop in the final plan. If every seat supported, exec pre-mortem is the death cause check 1 must defend \u2014 do not invent a fake challenge after the fact.
|
|
24322
|
+
${roundtableDigest}
|
|
24323
|
+
|
|
24324
|
+
` : "";
|
|
22488
24325
|
return `STAGE C \u2014 STRESS TEST. Now attack your own draft the way a skeptical COO would. Audit it against these checks and revise:
|
|
22489
24326
|
|
|
22490
|
-
1. PRE-MORTEM: it is the first check date and the plan has visibly failed \u2014 write the one most likely cause of death, then make sure the plan already defends against it (a constraint, a contingency trigger, or a re-sequence). If it doesn't, fix the plan, not the story.
|
|
24327
|
+
${roundtableBlock}1. PRE-MORTEM: it is the first check date and the plan has visibly failed \u2014 write the one most likely cause of death, then make sure the plan already defends against it (a constraint, a contingency trigger, or a re-sequence). If it doesn't, fix the plan, not the story.
|
|
22491
24328
|
2. CAPACITY MATH: sum the effort_hours. Does it fit the team implied by the company context and constraints? If overcommitted, cut or re-sequence \u2014 do not shrink the estimates to make it fit.
|
|
22492
24329
|
3. MEASURABILITY: for every expected_outcome and leading indicator \u2014 is the baseline a real number from your reality digest? Does measured_by name an instrument that exists given the data gaps? Anything unmeasurable gets excluded from targets and recorded in assumptions.
|
|
22493
24330
|
4. TIMELINE SANITY: can each check_date actually show evidence by then, given the sales cycle and how the metric updates? Fix dates that are faster than physics.
|
|
@@ -22554,6 +24391,7 @@ var init_strategist_prompt = __esm({
|
|
|
22554
24391
|
"use strict";
|
|
22555
24392
|
init_prompt_parts();
|
|
22556
24393
|
init_frameworks();
|
|
24394
|
+
init_gtm_counsel();
|
|
22557
24395
|
init_prompt();
|
|
22558
24396
|
STRATEGIST_PLAN_SCHEMA_BLOCK = `{
|
|
22559
24397
|
"title": "Short plan name, e.g. 'Q4 Pipeline Recovery'",
|
|
@@ -22954,7 +24792,7 @@ function validateStrategistPlan(raw, opts) {
|
|
|
22954
24792
|
}
|
|
22955
24793
|
function buildGroundedFallbackPlan(input) {
|
|
22956
24794
|
const today = parseIsoDate(input.todayIso) ?? /* @__PURE__ */ new Date();
|
|
22957
|
-
const triggered = matchTriggeredPlays(input.vitals, LAYERS);
|
|
24795
|
+
const triggered = matchTriggeredPlays(input.vitals, LAYERS, { tree: input.tree });
|
|
22958
24796
|
const issues = [
|
|
22959
24797
|
"LLM plan JSON invalid \u2014 using grounded fallback from triggered plays and live vitals"
|
|
22960
24798
|
];
|
|
@@ -23112,6 +24950,263 @@ var init_strategist_validate = __esm({
|
|
|
23112
24950
|
}
|
|
23113
24951
|
});
|
|
23114
24952
|
|
|
24953
|
+
// src/ai/roundtable-validate.ts
|
|
24954
|
+
function str2(value) {
|
|
24955
|
+
return typeof value === "string" ? value.trim() : "";
|
|
24956
|
+
}
|
|
24957
|
+
function strArray2(value) {
|
|
24958
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
24959
|
+
}
|
|
24960
|
+
function words(value) {
|
|
24961
|
+
return value.trim() ? value.trim().split(/\s+/).length : 0;
|
|
24962
|
+
}
|
|
24963
|
+
function issue(issues, code, message, severity = "blocking") {
|
|
24964
|
+
issues.push({ code, severity, message });
|
|
24965
|
+
}
|
|
24966
|
+
function parseSeat(raw, issues, index) {
|
|
24967
|
+
if (!raw || typeof raw !== "object") {
|
|
24968
|
+
issue(issues, "seat_shape", `Seat ${index + 1} is not an object.`);
|
|
24969
|
+
return null;
|
|
24970
|
+
}
|
|
24971
|
+
const obj = raw;
|
|
24972
|
+
const pack = str2(obj.pack);
|
|
24973
|
+
if (pack === "counsel_exec") {
|
|
24974
|
+
issue(issues, "exec_in_seats", "counsel_exec must appear only in the top-level exec object.");
|
|
24975
|
+
return null;
|
|
24976
|
+
}
|
|
24977
|
+
if (!FUNCTION_PACKS.has(pack)) {
|
|
24978
|
+
issue(issues, "unknown_pack", `Seat ${index + 1} has unknown pack '${pack || "(empty)"}'.`);
|
|
24979
|
+
return null;
|
|
24980
|
+
}
|
|
24981
|
+
const stance = str2(obj.stance);
|
|
24982
|
+
if (!STANCES.has(stance)) {
|
|
24983
|
+
issue(issues, "invalid_stance", `${pack} has invalid stance '${stance || "(empty)"}'.`);
|
|
24984
|
+
}
|
|
24985
|
+
if (typeof obj.constraint_agree !== "boolean") {
|
|
24986
|
+
issue(issues, "constraint_agree", `${pack} must set constraint_agree to true or false.`);
|
|
24987
|
+
}
|
|
24988
|
+
const challenge = str2(obj.challenge);
|
|
24989
|
+
const risk = str2(obj.cross_functional_risk);
|
|
24990
|
+
const missingExam = str2(obj.missing_exam);
|
|
24991
|
+
if (words(challenge) > 40) {
|
|
24992
|
+
issue(issues, "challenge_length", `${pack} challenge exceeds 40 words.`);
|
|
24993
|
+
}
|
|
24994
|
+
if (words(risk) > 30) {
|
|
24995
|
+
issue(issues, "risk_length", `${pack} cross_functional_risk exceeds 30 words.`);
|
|
24996
|
+
}
|
|
24997
|
+
const roleCards = strArray2(obj.role_cards_used);
|
|
24998
|
+
const allowed = new Set(getCounselPackById(pack)?.role_card_ids ?? []);
|
|
24999
|
+
const validRoles = [];
|
|
25000
|
+
for (const roleId of roleCards) {
|
|
25001
|
+
if (!getRoleCardById(roleId) || !allowed.has(roleId)) {
|
|
25002
|
+
issue(
|
|
25003
|
+
issues,
|
|
25004
|
+
"role_not_in_pack",
|
|
25005
|
+
`${pack} used role card '${roleId}' that is not listed on that pack.`
|
|
25006
|
+
);
|
|
25007
|
+
continue;
|
|
25008
|
+
}
|
|
25009
|
+
validRoles.push(roleId);
|
|
25010
|
+
}
|
|
25011
|
+
return {
|
|
25012
|
+
pack,
|
|
25013
|
+
stance: STANCES.has(stance) ? stance : "abstain",
|
|
25014
|
+
constraint_agree: obj.constraint_agree === true,
|
|
25015
|
+
challenge,
|
|
25016
|
+
missing_exam: missingExam,
|
|
25017
|
+
cross_functional_risk: risk,
|
|
25018
|
+
role_cards_used: validRoles
|
|
25019
|
+
};
|
|
25020
|
+
}
|
|
25021
|
+
function parseExec(raw, issues) {
|
|
25022
|
+
if (!raw || typeof raw !== "object") {
|
|
25023
|
+
issue(issues, "missing_exec", "Roundtable requires a top-level exec object.");
|
|
25024
|
+
return null;
|
|
25025
|
+
}
|
|
25026
|
+
const obj = raw;
|
|
25027
|
+
const governing = str2(obj.governing_constraint);
|
|
25028
|
+
const verdict = str2(obj.merge_verdict);
|
|
25029
|
+
if (!governing) issue(issues, "exec_constraint", "exec.governing_constraint is required.");
|
|
25030
|
+
if (!verdict) issue(issues, "exec_verdict", "exec.merge_verdict is required.");
|
|
25031
|
+
if (words(verdict) > 50) {
|
|
25032
|
+
issue(issues, "exec_verdict_length", "exec.merge_verdict exceeds 50 words.");
|
|
25033
|
+
}
|
|
25034
|
+
return {
|
|
25035
|
+
governing_constraint: governing,
|
|
25036
|
+
sequence: strArray2(obj.sequence),
|
|
25037
|
+
drop: strArray2(obj.drop),
|
|
25038
|
+
merge_verdict: verdict,
|
|
25039
|
+
pre_mortem: str2(obj.pre_mortem)
|
|
25040
|
+
};
|
|
25041
|
+
}
|
|
25042
|
+
function validateRoundtableResult(raw, options) {
|
|
25043
|
+
const issues = [];
|
|
25044
|
+
if (!raw || typeof raw !== "object") {
|
|
25045
|
+
return {
|
|
25046
|
+
result: null,
|
|
25047
|
+
issues: [{ code: "roundtable_shape", severity: "blocking", message: "Roundtable output is not a JSON object." }],
|
|
25048
|
+
blocking: true
|
|
25049
|
+
};
|
|
25050
|
+
}
|
|
25051
|
+
const obj = raw;
|
|
25052
|
+
if (!Array.isArray(obj.seats)) {
|
|
25053
|
+
issue(issues, "missing_seats", "Roundtable requires a seats array.");
|
|
25054
|
+
}
|
|
25055
|
+
const seats = (Array.isArray(obj.seats) ? obj.seats : []).map((seat, index) => parseSeat(seat, issues, index)).filter((seat) => seat != null);
|
|
25056
|
+
const exec = parseExec(obj.exec, issues);
|
|
25057
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25058
|
+
for (const seat of seats) {
|
|
25059
|
+
if (seen.has(seat.pack)) {
|
|
25060
|
+
issue(issues, "duplicate_seat", `Roundtable contains duplicate seat ${seat.pack}.`);
|
|
25061
|
+
}
|
|
25062
|
+
seen.add(seat.pack);
|
|
25063
|
+
}
|
|
25064
|
+
const expected = options.seating.seats.filter((seat) => seat.pack_id !== "counsel_exec").map((seat) => seat.pack_id);
|
|
25065
|
+
for (const pack of expected) {
|
|
25066
|
+
if (!seen.has(pack)) issue(issues, "missing_seat", `Roundtable omitted seated pack ${pack}.`);
|
|
25067
|
+
}
|
|
25068
|
+
for (const pack of seen) {
|
|
25069
|
+
if (!expected.includes(pack)) issue(issues, "unexpected_seat", `Roundtable emitted unseated pack ${pack}.`);
|
|
25070
|
+
}
|
|
25071
|
+
const pulls = new Set(options.counselPulls ?? []);
|
|
25072
|
+
if (options.requireCounselPulls !== false) {
|
|
25073
|
+
const fullPacks = options.seating.seats.filter((seat) => seat.weight === "full").map((seat) => seat.pack_id);
|
|
25074
|
+
for (const pack of fullPacks) {
|
|
25075
|
+
if (!pulls.has(pack)) {
|
|
25076
|
+
issue(issues, "missing_counsel_pull", `Full seat ${pack} did not call get_counsel_detail.`);
|
|
25077
|
+
}
|
|
25078
|
+
}
|
|
25079
|
+
}
|
|
25080
|
+
if (exec) {
|
|
25081
|
+
const allSupport = seats.length > 0 && seats.every((seat) => seat.stance === "support");
|
|
25082
|
+
if (allSupport && !exec.pre_mortem) {
|
|
25083
|
+
issue(issues, "missing_pre_mortem", "All-support roundtables require exec.pre_mortem.");
|
|
25084
|
+
}
|
|
25085
|
+
for (const seat of seats) {
|
|
25086
|
+
if (!seat.challenge && !(seat.stance === "support" && (Boolean(seat.cross_functional_risk) || Boolean(exec.pre_mortem)))) {
|
|
25087
|
+
issue(
|
|
25088
|
+
issues,
|
|
25089
|
+
"empty_challenge",
|
|
25090
|
+
`${seat.pack} may leave challenge empty only with support plus risk or exec pre-mortem.`
|
|
25091
|
+
);
|
|
25092
|
+
}
|
|
25093
|
+
if (seat.stance === "abstain" && !seat.cross_functional_risk) {
|
|
25094
|
+
issue(issues, "abstain_without_risk", `${seat.pack} abstained without a risk note.`);
|
|
25095
|
+
}
|
|
25096
|
+
}
|
|
25097
|
+
}
|
|
25098
|
+
for (const owner of options.constraintOwners) {
|
|
25099
|
+
const seat = seats.find((candidate) => candidate.pack === owner);
|
|
25100
|
+
if (!seat) continue;
|
|
25101
|
+
if (seat.stance === "abstain" && !seat.cross_functional_risk) {
|
|
25102
|
+
issue(issues, "owner_abstain", `Constraint owner ${owner} abstained without a risk note.`);
|
|
25103
|
+
}
|
|
25104
|
+
}
|
|
25105
|
+
const blocking = issues.some((entry) => entry.severity === "blocking");
|
|
25106
|
+
return {
|
|
25107
|
+
result: exec ? { seats, exec } : null,
|
|
25108
|
+
issues,
|
|
25109
|
+
blocking
|
|
25110
|
+
};
|
|
25111
|
+
}
|
|
25112
|
+
function parseAndValidateRoundtable(text, options) {
|
|
25113
|
+
return validateRoundtableResult(parseJsonObjectFromText(text), options);
|
|
25114
|
+
}
|
|
25115
|
+
function formatRoundtableDigestFromResult(result) {
|
|
25116
|
+
const lines = result.seats.map(
|
|
25117
|
+
(seat) => `- ${seat.pack} [${seat.stance}]: ${seat.challenge || "(no challenge)"}${seat.missing_exam ? ` | missing exam: ${seat.missing_exam}` : ""}${seat.cross_functional_risk ? ` | risk: ${seat.cross_functional_risk}` : ""}`
|
|
25118
|
+
);
|
|
25119
|
+
lines.push(`EXEC governing: ${result.exec.governing_constraint || "(none)"}`);
|
|
25120
|
+
if (result.exec.sequence.length) lines.push(`EXEC sequence: ${result.exec.sequence.join(" \u2192 ")}`);
|
|
25121
|
+
if (result.exec.drop.length) lines.push(`EXEC drop: ${result.exec.drop.join("; ")}`);
|
|
25122
|
+
if (result.exec.merge_verdict) lines.push(`EXEC verdict: ${result.exec.merge_verdict}`);
|
|
25123
|
+
if (result.exec.pre_mortem) lines.push(`EXEC pre-mortem: ${result.exec.pre_mortem}`);
|
|
25124
|
+
return lines.join("\n");
|
|
25125
|
+
}
|
|
25126
|
+
function buildRoundtableRetryMessage(validation, seating) {
|
|
25127
|
+
const required = seating.seats.filter((seat) => seat.pack_id !== "counsel_exec").map((seat) => seat.pack_id).join(", ");
|
|
25128
|
+
const failures = validation.issues.filter((entry) => entry.severity === "blocking").map((entry) => `${entry.code}: ${entry.message}`).join("\n- ");
|
|
25129
|
+
return `Roundtable JSON did not validate.
|
|
25130
|
+
- ${failures || "roundtable_shape: unusable JSON"}
|
|
25131
|
+
Required function seats: ${required}.
|
|
25132
|
+
Respond with ONLY corrected roundtable JSON. counsel_exec belongs only in top-level exec. All-support requires exec.pre_mortem.`;
|
|
25133
|
+
}
|
|
25134
|
+
function normalizedPlanText(plan) {
|
|
25135
|
+
return JSON.stringify(plan).toLowerCase();
|
|
25136
|
+
}
|
|
25137
|
+
function significantTokens(value) {
|
|
25138
|
+
return value.toLowerCase().split(/[^a-z0-9_]+/).filter((token) => token.length >= 4).filter((token) => !["that", "with", "from", "this", "when", "must", "risk"].includes(token));
|
|
25139
|
+
}
|
|
25140
|
+
function checkRoundtableReflection(input) {
|
|
25141
|
+
const issues = [];
|
|
25142
|
+
const draft = normalizedPlanText(input.draftPlan);
|
|
25143
|
+
const final = normalizedPlanText(input.finalPlan);
|
|
25144
|
+
for (const dropped of input.roundtable.exec.drop) {
|
|
25145
|
+
const needle = dropped.toLowerCase();
|
|
25146
|
+
if (needle.length >= 4 && draft.includes(needle) && final.includes(needle)) {
|
|
25147
|
+
issue(
|
|
25148
|
+
issues,
|
|
25149
|
+
"roundtable_drop_ignored",
|
|
25150
|
+
`Final plan still contains exec drop '${dropped}'.`,
|
|
25151
|
+
"advisory"
|
|
25152
|
+
);
|
|
25153
|
+
}
|
|
25154
|
+
}
|
|
25155
|
+
for (const seat of input.roundtable.seats) {
|
|
25156
|
+
if (seat.missing_exam) {
|
|
25157
|
+
const tokens = significantTokens(seat.missing_exam);
|
|
25158
|
+
if (tokens.length > 0 && !tokens.some((token) => final.includes(token))) {
|
|
25159
|
+
issue(
|
|
25160
|
+
issues,
|
|
25161
|
+
"roundtable_exam_missing",
|
|
25162
|
+
`${seat.pack} missing exam is not visible in the final plan.`,
|
|
25163
|
+
"advisory"
|
|
25164
|
+
);
|
|
25165
|
+
}
|
|
25166
|
+
}
|
|
25167
|
+
if (seat.stance === "challenge" && seat.challenge && draft === final) {
|
|
25168
|
+
issue(
|
|
25169
|
+
issues,
|
|
25170
|
+
"roundtable_unaddressed",
|
|
25171
|
+
`${seat.pack} challenged the draft but the final plan is unchanged.`,
|
|
25172
|
+
"advisory"
|
|
25173
|
+
);
|
|
25174
|
+
}
|
|
25175
|
+
}
|
|
25176
|
+
const allSupport = input.roundtable.seats.length > 0 && input.roundtable.seats.every((seat) => seat.stance === "support");
|
|
25177
|
+
if (allSupport && input.roundtable.exec.pre_mortem) {
|
|
25178
|
+
const defensiveText = JSON.stringify({
|
|
25179
|
+
risks: input.finalPlan.risks,
|
|
25180
|
+
assumptions: input.finalPlan.assumptions,
|
|
25181
|
+
contingencies: input.finalPlan.workstreams.map((ws) => ws.contingency)
|
|
25182
|
+
}).toLowerCase();
|
|
25183
|
+
const tokens = significantTokens(input.roundtable.exec.pre_mortem);
|
|
25184
|
+
if (tokens.length > 0 && !tokens.some((token) => defensiveText.includes(token))) {
|
|
25185
|
+
issue(
|
|
25186
|
+
issues,
|
|
25187
|
+
"roundtable_premortem_missing",
|
|
25188
|
+
"Final risks and contingencies do not reflect exec.pre_mortem.",
|
|
25189
|
+
"advisory"
|
|
25190
|
+
);
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
return { issues, blocking: false };
|
|
25194
|
+
}
|
|
25195
|
+
var FUNCTION_PACKS, STANCES;
|
|
25196
|
+
var init_roundtable_validate = __esm({
|
|
25197
|
+
"src/ai/roundtable-validate.ts"() {
|
|
25198
|
+
"use strict";
|
|
25199
|
+
init_gtm_counsel();
|
|
25200
|
+
init_strategist_validate();
|
|
25201
|
+
FUNCTION_PACKS = new Set(
|
|
25202
|
+
listCounselPackIds().filter(
|
|
25203
|
+
(id) => id !== "counsel_exec"
|
|
25204
|
+
)
|
|
25205
|
+
);
|
|
25206
|
+
STANCES = /* @__PURE__ */ new Set(["support", "challenge", "abstain"]);
|
|
25207
|
+
}
|
|
25208
|
+
});
|
|
25209
|
+
|
|
23115
25210
|
// src/ai/strategist-rubric.ts
|
|
23116
25211
|
function numbersMatch2(a, b) {
|
|
23117
25212
|
if (a === b) return true;
|
|
@@ -23278,6 +25373,65 @@ function scoreConsultantPlan(plan, opts) {
|
|
|
23278
25373
|
fix: "Copy baselines from the health snapshot exactly."
|
|
23279
25374
|
});
|
|
23280
25375
|
}
|
|
25376
|
+
const playPositions = /* @__PURE__ */ new Map();
|
|
25377
|
+
const groups = /* @__PURE__ */ new Map();
|
|
25378
|
+
for (const [workstream, ws] of plan.workstreams.entries()) {
|
|
25379
|
+
for (const [position, playId] of ws.play_ids.entries()) {
|
|
25380
|
+
playPositions.set(playId, { workstream, position });
|
|
25381
|
+
const group = (getPlayRouting(playId) ?? getPlaybook().find((play) => play.id === playId)?.routing)?.graph?.exclusive_group;
|
|
25382
|
+
if (group) {
|
|
25383
|
+
const workstreams = groups.get(group) ?? /* @__PURE__ */ new Set();
|
|
25384
|
+
workstreams.add(workstream);
|
|
25385
|
+
groups.set(group, workstreams);
|
|
25386
|
+
}
|
|
25387
|
+
}
|
|
25388
|
+
}
|
|
25389
|
+
for (const [group, workstreams] of groups) {
|
|
25390
|
+
if (workstreams.size > 1) {
|
|
25391
|
+
gaps.push({
|
|
25392
|
+
code: "play_sequence",
|
|
25393
|
+
severity: "advisory",
|
|
25394
|
+
note: `Exclusive play group '${group}' is split across parallel workstreams.`,
|
|
25395
|
+
fix: "Keep the diagnostic play primary and express the later play as a gated next step."
|
|
25396
|
+
});
|
|
25397
|
+
}
|
|
25398
|
+
}
|
|
25399
|
+
for (const [playId, current] of playPositions) {
|
|
25400
|
+
const routing = getPlayRouting(playId) ?? getPlaybook().find((play) => play.id === playId)?.routing;
|
|
25401
|
+
for (const requirement of routing?.graph?.prerequisites ?? []) {
|
|
25402
|
+
const prior = playPositions.get(requirement.play_id);
|
|
25403
|
+
if (!prior || prior.workstream > current.workstream || prior.workstream === current.workstream && prior.position >= current.position) {
|
|
25404
|
+
gaps.push({
|
|
25405
|
+
code: "play_sequence",
|
|
25406
|
+
severity: "advisory",
|
|
25407
|
+
note: `${playId} appears before prerequisite ${requirement.play_id}.`,
|
|
25408
|
+
fix: requirement.reason
|
|
25409
|
+
});
|
|
25410
|
+
}
|
|
25411
|
+
}
|
|
25412
|
+
const planText = JSON.stringify(plan).toLowerCase();
|
|
25413
|
+
if (opts.orgTree && routing?.graph?.tree_exclude?.includes(opts.orgTree) && !(routing.graph.required_evidence?.includes("named_account_process") && /named[- ]account|named list/.test(planText))) {
|
|
25414
|
+
gaps.push({
|
|
25415
|
+
code: "play_sequence",
|
|
25416
|
+
severity: "advisory",
|
|
25417
|
+
note: `${playId} is excluded on the ${opts.orgTree} tree without its required evidence.`,
|
|
25418
|
+
fix: "Remove the play or state and verify the motion-specific evidence that makes it applicable."
|
|
25419
|
+
});
|
|
25420
|
+
}
|
|
25421
|
+
for (const block of routing?.graph?.blocked_by ?? []) {
|
|
25422
|
+
const vital = opts.snapshot.aggregate.vital_signs.find(
|
|
25423
|
+
(reading) => reading.vital_sign === block.signal
|
|
25424
|
+
);
|
|
25425
|
+
if (vital?.status === block.status) {
|
|
25426
|
+
gaps.push({
|
|
25427
|
+
code: "play_sequence",
|
|
25428
|
+
severity: "advisory",
|
|
25429
|
+
note: `${playId} is blocked while ${String(block.signal)} is ${block.status}.`,
|
|
25430
|
+
fix: block.reason
|
|
25431
|
+
});
|
|
25432
|
+
}
|
|
25433
|
+
}
|
|
25434
|
+
}
|
|
23281
25435
|
const blocking = gaps.filter((g) => g.severity === "blocking");
|
|
23282
25436
|
return { pass: blocking.length === 0, gaps, blocking };
|
|
23283
25437
|
}
|
|
@@ -23361,6 +25515,7 @@ var init_strategist_rubric = __esm({
|
|
|
23361
25515
|
"src/ai/strategist-rubric.ts"() {
|
|
23362
25516
|
"use strict";
|
|
23363
25517
|
init_playbook();
|
|
25518
|
+
init_play_routing();
|
|
23364
25519
|
init_strategist_validate();
|
|
23365
25520
|
init_formatters();
|
|
23366
25521
|
OWNER_TOKENS = [
|
|
@@ -23398,7 +25553,9 @@ var init_strategist_rubric = __esm({
|
|
|
23398
25553
|
"alternatives_killed",
|
|
23399
25554
|
"thin_evidence",
|
|
23400
25555
|
"invented_numbers",
|
|
23401
|
-
"objective_drift"
|
|
25556
|
+
"objective_drift",
|
|
25557
|
+
"roundtable_invalid",
|
|
25558
|
+
"play_sequence"
|
|
23402
25559
|
];
|
|
23403
25560
|
}
|
|
23404
25561
|
});
|
|
@@ -23489,7 +25646,7 @@ async function* strategistPlanSession(options) {
|
|
|
23489
25646
|
lastMeta = result.meta;
|
|
23490
25647
|
return result.response;
|
|
23491
25648
|
};
|
|
23492
|
-
async function* runStage(surface, maxRounds, budgetNudge, requireJson = false) {
|
|
25649
|
+
async function* runStage(surface, maxRounds, budgetNudge, requireJson = false, onToolCall) {
|
|
23493
25650
|
for (let round = 0; round < maxRounds; round++) {
|
|
23494
25651
|
const response = await callLlm(surface, true);
|
|
23495
25652
|
if (response.tool_calls.length === 0) {
|
|
@@ -23507,6 +25664,7 @@ async function* strategistPlanSession(options) {
|
|
|
23507
25664
|
yield { type: "thinking", text: response.text.trim().slice(0, 200) };
|
|
23508
25665
|
}
|
|
23509
25666
|
for (const tc of response.tool_calls) {
|
|
25667
|
+
onToolCall?.(tc.name, tc.arguments ?? {});
|
|
23510
25668
|
yield { type: "tool_call", name: tc.name };
|
|
23511
25669
|
const result = await executeToolCall(tc.name, tc.arguments ?? {}, toolCtx, {
|
|
23512
25670
|
allowedTools,
|
|
@@ -23533,7 +25691,8 @@ async function* strategistPlanSession(options) {
|
|
|
23533
25691
|
vitals: vitalsForFallback,
|
|
23534
25692
|
todayIso,
|
|
23535
25693
|
gatingVitalSign: options.computeResult.aggregate.gating_vital_sign,
|
|
23536
|
-
totalValueAtRisk: options.computeResult.aggregate.total_value_at_risk
|
|
25694
|
+
totalValueAtRisk: options.computeResult.aggregate.total_value_at_risk,
|
|
25695
|
+
tree: getActiveOrgTree(loadProfile())
|
|
23537
25696
|
});
|
|
23538
25697
|
}
|
|
23539
25698
|
yield { type: "stage", stage: "ground", label: STAGE_LABELS.ground };
|
|
@@ -23586,8 +25745,106 @@ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`
|
|
|
23586
25745
|
text: "Backcast plan validated \u2014 will use it if the stress-test revision fails validation."
|
|
23587
25746
|
};
|
|
23588
25747
|
}
|
|
25748
|
+
const profile = loadProfile();
|
|
25749
|
+
const tree = getActiveOrgTree(profile);
|
|
25750
|
+
const playIds = collectPlayIds(candidatePlan?.plan ?? null);
|
|
25751
|
+
const gatingVital = extractGatingVitalFromDigest(
|
|
25752
|
+
digest,
|
|
25753
|
+
options.computeResult.aggregate.gating_vital_sign
|
|
25754
|
+
);
|
|
25755
|
+
const resolvedConstraint = resolveConstraint({
|
|
25756
|
+
health: {
|
|
25757
|
+
gating_vital: gatingVital,
|
|
25758
|
+
vital_readings: vitalsForFallback,
|
|
25759
|
+
metric_readings: toolCtx.metrics,
|
|
25760
|
+
digest
|
|
25761
|
+
},
|
|
25762
|
+
objective: {
|
|
25763
|
+
objective: options.objective,
|
|
25764
|
+
play_ids: playIds,
|
|
25765
|
+
operator_constraints: options.constraintsNote ? [options.constraintsNote] : []
|
|
25766
|
+
}
|
|
25767
|
+
});
|
|
25768
|
+
const seating = buildRoundtableSeating({
|
|
25769
|
+
tree,
|
|
25770
|
+
objective: options.objective,
|
|
25771
|
+
profile,
|
|
25772
|
+
playIds,
|
|
25773
|
+
gatingVital,
|
|
25774
|
+
resolvedConstraint
|
|
25775
|
+
});
|
|
25776
|
+
const constraintOwner = resolvedConstraint.locked_owners.join(", ") || null;
|
|
25777
|
+
const seatsBlock = seating.seats.filter((s) => s.pack_id !== "counsel_exec").map((s) => `- ${s.pack_id} (${s.weight})`).join("\n");
|
|
25778
|
+
const draftPlanJson = candidatePlan ? JSON.stringify(candidatePlan.plan) : backcastText;
|
|
25779
|
+
const counselPulls = /* @__PURE__ */ new Set();
|
|
25780
|
+
const observeRoundtableTool = (name, args) => {
|
|
25781
|
+
if (name !== "get_counsel_detail") return;
|
|
25782
|
+
const counselId = typeof args.counsel_id === "string" ? args.counsel_id : "";
|
|
25783
|
+
if (["counsel_sales", "counsel_marketing", "counsel_revops", "counsel_exec", "counsel_cs"].includes(
|
|
25784
|
+
counselId
|
|
25785
|
+
)) {
|
|
25786
|
+
counselPulls.add(counselId);
|
|
25787
|
+
}
|
|
25788
|
+
};
|
|
25789
|
+
yield { type: "stage", stage: "roundtable", label: STAGE_LABELS.roundtable };
|
|
25790
|
+
messages.push({
|
|
25791
|
+
role: "user",
|
|
25792
|
+
content: buildRoundtableMessage({
|
|
25793
|
+
objective: options.objective,
|
|
25794
|
+
tree,
|
|
25795
|
+
seatsBlock,
|
|
25796
|
+
biasNotes: seating.bias_notes,
|
|
25797
|
+
draftPlanJson,
|
|
25798
|
+
constraintOwner,
|
|
25799
|
+
constraint: resolvedConstraint
|
|
25800
|
+
})
|
|
25801
|
+
});
|
|
25802
|
+
const roundtableText = yield* runStage(
|
|
25803
|
+
"strategist",
|
|
25804
|
+
ROUNDTABLE_MAX_ROUNDS,
|
|
25805
|
+
"Tool budget reached for roundtable. Respond with the roundtable JSON now \u2014 strict JSON only.",
|
|
25806
|
+
true,
|
|
25807
|
+
observeRoundtableTool
|
|
25808
|
+
);
|
|
25809
|
+
let roundtableValidation = parseAndValidateRoundtable(roundtableText, {
|
|
25810
|
+
seating,
|
|
25811
|
+
constraintOwners: resolvedConstraint.locked_owners,
|
|
25812
|
+
counselPulls,
|
|
25813
|
+
requireCounselPulls: true
|
|
25814
|
+
});
|
|
25815
|
+
if (roundtableValidation.blocking) {
|
|
25816
|
+
messages.push({
|
|
25817
|
+
role: "user",
|
|
25818
|
+
content: buildRoundtableRetryMessage(roundtableValidation, seating)
|
|
25819
|
+
});
|
|
25820
|
+
const retryText = yield* runStage(
|
|
25821
|
+
"strategist",
|
|
25822
|
+
2,
|
|
25823
|
+
"Respond with ONLY corrected roundtable JSON now. No prose.",
|
|
25824
|
+
true,
|
|
25825
|
+
observeRoundtableTool
|
|
25826
|
+
);
|
|
25827
|
+
roundtableValidation = parseAndValidateRoundtable(retryText, {
|
|
25828
|
+
seating,
|
|
25829
|
+
constraintOwners: resolvedConstraint.locked_owners,
|
|
25830
|
+
counselPulls,
|
|
25831
|
+
requireCounselPulls: true
|
|
25832
|
+
});
|
|
25833
|
+
}
|
|
25834
|
+
const roundtableResult = !roundtableValidation.blocking ? roundtableValidation.result : null;
|
|
25835
|
+
const roundtableIssues = [...roundtableValidation.issues];
|
|
25836
|
+
let roundtableDigest = roundtableResult ? formatRoundtableDigestFromResult(roundtableResult) : "";
|
|
25837
|
+
if (!roundtableResult) {
|
|
25838
|
+
roundtableDigest = "(Roundtable JSON failed structural validation \u2014 Stage C must run a pre-mortem, preserve both constraint channels, and avoid rubber-stamping the draft.)";
|
|
25839
|
+
yield {
|
|
25840
|
+
type: "notice",
|
|
25841
|
+
text: `Roundtable validation failed: ${roundtableValidation.issues.filter((entry) => entry.severity === "blocking").map((entry) => entry.code).join(", ") || "unknown shape"}.`
|
|
25842
|
+
};
|
|
25843
|
+
} else {
|
|
25844
|
+
yield { type: "notice", text: "Roundtable counsel captured \u2014 feeding into stress test." };
|
|
25845
|
+
}
|
|
23589
25846
|
yield { type: "stage", stage: "stress", label: STAGE_LABELS.stress };
|
|
23590
|
-
messages.push({ role: "user", content: buildStressTestMessage() });
|
|
25847
|
+
messages.push({ role: "user", content: buildStressTestMessage(roundtableDigest) });
|
|
23591
25848
|
const finalText = yield* runStage(
|
|
23592
25849
|
"strategist_stress",
|
|
23593
25850
|
STRESS_MAX_ROUNDS,
|
|
@@ -23622,14 +25879,38 @@ Respond with ONLY the corrected plan JSON object in the required schema (title,
|
|
|
23622
25879
|
text: "Strategist JSON failed validation \u2014 emitting grounded fallback plan from live vitals."
|
|
23623
25880
|
};
|
|
23624
25881
|
}
|
|
23625
|
-
for (const
|
|
23626
|
-
yield { type: "notice", text:
|
|
25882
|
+
for (const issue2 of validated.issues) {
|
|
25883
|
+
yield { type: "notice", text: issue2 };
|
|
25884
|
+
}
|
|
25885
|
+
if (roundtableResult && candidatePlan) {
|
|
25886
|
+
const reflection = checkRoundtableReflection({
|
|
25887
|
+
roundtable: roundtableResult,
|
|
25888
|
+
draftPlan: candidatePlan.plan,
|
|
25889
|
+
finalPlan: validated.plan
|
|
25890
|
+
});
|
|
25891
|
+
for (const reflectionIssue of reflection.issues) {
|
|
25892
|
+
roundtableIssues.push(reflectionIssue);
|
|
25893
|
+
yield {
|
|
25894
|
+
type: "notice",
|
|
25895
|
+
text: `Counsel reflection (${reflectionIssue.code}): ${reflectionIssue.message}`
|
|
25896
|
+
};
|
|
25897
|
+
}
|
|
23627
25898
|
}
|
|
25899
|
+
yield {
|
|
25900
|
+
type: "roundtable",
|
|
25901
|
+
result: roundtableResult,
|
|
25902
|
+
digest: roundtableDigest,
|
|
25903
|
+
issues: roundtableIssues,
|
|
25904
|
+
seating,
|
|
25905
|
+
counsel_pulls: [...counselPulls],
|
|
25906
|
+
counsel_fingerprint: planCounselFingerprint(validated.plan)
|
|
25907
|
+
};
|
|
23628
25908
|
const rubric = scoreConsultantPlan(validated.plan, {
|
|
23629
25909
|
snapshot: options.computeResult,
|
|
23630
25910
|
constraintsNote: options.constraintsNote,
|
|
23631
25911
|
todayIso,
|
|
23632
|
-
evidenceText
|
|
25912
|
+
evidenceText,
|
|
25913
|
+
orgTree: tree
|
|
23633
25914
|
});
|
|
23634
25915
|
for (const gap of rubric.blocking) {
|
|
23635
25916
|
yield { type: "notice", text: `Plan gap (${gap.code}): ${gap.note}` };
|
|
@@ -23655,6 +25936,29 @@ function validatePlanText(text, evidenceText, todayIso) {
|
|
|
23655
25936
|
if (!raw) return null;
|
|
23656
25937
|
return validateStrategistPlan(raw, { evidenceText, todayIso });
|
|
23657
25938
|
}
|
|
25939
|
+
function collectPlayIds(plan) {
|
|
25940
|
+
if (!plan?.workstreams) return [];
|
|
25941
|
+
const ids = /* @__PURE__ */ new Set();
|
|
25942
|
+
for (const ws of plan.workstreams) {
|
|
25943
|
+
for (const id of ws.play_ids ?? []) {
|
|
25944
|
+
if (typeof id === "string" && id.trim()) ids.add(id.trim());
|
|
25945
|
+
}
|
|
25946
|
+
}
|
|
25947
|
+
return [...ids];
|
|
25948
|
+
}
|
|
25949
|
+
function planCounselFingerprint(plan) {
|
|
25950
|
+
if (!plan) return "";
|
|
25951
|
+
const plays = collectPlayIds(plan).sort().join(",");
|
|
25952
|
+
const constraint = (plan.constraints ?? []).join("|");
|
|
25953
|
+
const hypo = plan.hypothesis ?? "";
|
|
25954
|
+
const sequence = (plan.workstreams ?? []).slice().sort((a, b) => a.order - b.order).map(
|
|
25955
|
+
(ws) => `${ws.order}:${ws.title}:${ws.problem}:${ws.play_ids.join(",")}:${ws.actions[0] ?? ""}:${ws.expected_outcome.metric}:${ws.expected_outcome.target_range}:${ws.expected_outcome.check_date}`
|
|
25956
|
+
).join("\u2192");
|
|
25957
|
+
return `${plays}::${constraint}::${hypo}::${sequence}::${plan.summary_30k ?? ""}`.slice(
|
|
25958
|
+
0,
|
|
25959
|
+
2e3
|
|
25960
|
+
);
|
|
25961
|
+
}
|
|
23658
25962
|
function describePlanValidationFailure(text, evidenceText, todayIso) {
|
|
23659
25963
|
const raw = parseJsonObjectFromText(text);
|
|
23660
25964
|
if (!raw) {
|
|
@@ -23669,7 +25973,7 @@ function describePlanValidationFailure(text, evidenceText, todayIso) {
|
|
|
23669
25973
|
}
|
|
23670
25974
|
return "unknown validation failure";
|
|
23671
25975
|
}
|
|
23672
|
-
var GROUND_MAX_ROUNDS, BACKCAST_MAX_ROUNDS, STRESS_MAX_ROUNDS, STAGE_MAX_TOKENS, PLAN_JSON_MAX_TOKENS, STAGE_LABELS;
|
|
25976
|
+
var GROUND_MAX_ROUNDS, BACKCAST_MAX_ROUNDS, ROUNDTABLE_MAX_ROUNDS, STRESS_MAX_ROUNDS, STAGE_MAX_TOKENS, PLAN_JSON_MAX_TOKENS, STAGE_LABELS;
|
|
23673
25977
|
var init_strategist2 = __esm({
|
|
23674
25978
|
"src/ai/strategist.ts"() {
|
|
23675
25979
|
"use strict";
|
|
@@ -23685,16 +25989,20 @@ var init_strategist2 = __esm({
|
|
|
23685
25989
|
init_thread();
|
|
23686
25990
|
init_strategist_prompt();
|
|
23687
25991
|
init_strategist_validate();
|
|
25992
|
+
init_roundtable_validate();
|
|
23688
25993
|
init_strategist_rubric();
|
|
23689
|
-
|
|
25994
|
+
init_profile();
|
|
25995
|
+
init_gtm_counsel();
|
|
23690
25996
|
GROUND_MAX_ROUNDS = 6;
|
|
23691
25997
|
BACKCAST_MAX_ROUNDS = 4;
|
|
25998
|
+
ROUNDTABLE_MAX_ROUNDS = 3;
|
|
23692
25999
|
STRESS_MAX_ROUNDS = 2;
|
|
23693
26000
|
STAGE_MAX_TOKENS = 4096;
|
|
23694
26001
|
PLAN_JSON_MAX_TOKENS = 8192;
|
|
23695
26002
|
STAGE_LABELS = {
|
|
23696
26003
|
ground: "Grounding \u2014 reading health, metrics, segments, history",
|
|
23697
26004
|
backcast: "Sequencing \u2014 backcasting from objective",
|
|
26005
|
+
roundtable: "Roundtable \u2014 sales, marketing, RevOps, CS, exec counsel",
|
|
23698
26006
|
stress: "Stress-testing \u2014 capacity, measurability, timeline"
|
|
23699
26007
|
};
|
|
23700
26008
|
}
|
|
@@ -23754,13 +26062,43 @@ async function* strategistCraftSession(options) {
|
|
|
23754
26062
|
const armedObjective = job.objective;
|
|
23755
26063
|
let lastMeta = { provider_used: "unknown", model_used: "unknown" };
|
|
23756
26064
|
let measurable = { measurable_targets: 0, total_targets: 0 };
|
|
23757
|
-
const scorePlan = (plan) =>
|
|
23758
|
-
|
|
23759
|
-
|
|
23760
|
-
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
26065
|
+
const scorePlan = (plan) => {
|
|
26066
|
+
const scored = scoreConsultantPlan(plan, {
|
|
26067
|
+
snapshot: options.computeResult,
|
|
26068
|
+
constraintsNote: options.constraintsNote ?? job.constraints_note,
|
|
26069
|
+
armedObjective,
|
|
26070
|
+
todayIso,
|
|
26071
|
+
evidenceText,
|
|
26072
|
+
orgTree: getActiveOrgTree(loadProfile())
|
|
26073
|
+
});
|
|
26074
|
+
const currentFingerprint = planCounselFingerprint(plan);
|
|
26075
|
+
if (!job.roundtable || job.roundtable.counsel_fingerprint !== currentFingerprint || job.roundtable.issues.some((entry) => entry.severity === "blocking")) {
|
|
26076
|
+
const gap = {
|
|
26077
|
+
code: "roundtable_invalid",
|
|
26078
|
+
severity: "blocking",
|
|
26079
|
+
note: !job.roundtable ? "No validated counsel roundtable is attached to this plan." : job.roundtable.counsel_fingerprint !== currentFingerprint ? "The latest counsel roundtable is stale for the current plan." : "The latest counsel roundtable failed structural validation.",
|
|
26080
|
+
fix: "Re-run Stage R with every seated pack, required counsel pulls, and a valid exec merge."
|
|
26081
|
+
};
|
|
26082
|
+
scored.gaps.push(gap);
|
|
26083
|
+
scored.blocking.push(gap);
|
|
26084
|
+
scored.pass = false;
|
|
26085
|
+
}
|
|
26086
|
+
return scored;
|
|
26087
|
+
};
|
|
26088
|
+
const rememberRoundtable = (event) => {
|
|
26089
|
+
const record = {
|
|
26090
|
+
seating: event.seating,
|
|
26091
|
+
result: event.result,
|
|
26092
|
+
digest: event.digest,
|
|
26093
|
+
counsel_pulls: event.counsel_pulls,
|
|
26094
|
+
counsel_fingerprint: event.counsel_fingerprint,
|
|
26095
|
+
issues: event.issues,
|
|
26096
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
26097
|
+
};
|
|
26098
|
+
job.roundtable = record;
|
|
26099
|
+
job.roundtable_history.push(record);
|
|
26100
|
+
return record;
|
|
26101
|
+
};
|
|
23764
26102
|
const rememberBest = (plan, score) => {
|
|
23765
26103
|
if (score == null) {
|
|
23766
26104
|
if (!job.best_plan) job.best_plan = plan;
|
|
@@ -23804,6 +26142,9 @@ async function* strategistCraftSession(options) {
|
|
|
23804
26142
|
if (event.type === "notice" && fallbackNotice(event.text)) {
|
|
23805
26143
|
job.from_fallback = true;
|
|
23806
26144
|
}
|
|
26145
|
+
if (event.type === "roundtable") {
|
|
26146
|
+
rememberRoundtable(event);
|
|
26147
|
+
}
|
|
23807
26148
|
if (event.type === "plan") {
|
|
23808
26149
|
const locked = lockPlanObjective(event.plan, armedObjective);
|
|
23809
26150
|
job.plan = locked;
|
|
@@ -23859,6 +26200,8 @@ async function* strategistCraftSession(options) {
|
|
|
23859
26200
|
};
|
|
23860
26201
|
let stop;
|
|
23861
26202
|
const startRound = (job.iterations.at(-1)?.round ?? 0) + 1;
|
|
26203
|
+
let lastRoundtableDigest = job.roundtable?.digest ?? null;
|
|
26204
|
+
let lastCounselFp = job.roundtable?.counsel_fingerprint ?? "";
|
|
23862
26205
|
for (let round = startRound; round <= maxRounds; round++) {
|
|
23863
26206
|
if (options.interrupted?.()) {
|
|
23864
26207
|
stop = "interrupt";
|
|
@@ -23880,7 +26223,11 @@ async function* strategistCraftSession(options) {
|
|
|
23880
26223
|
buildCriticMessage({
|
|
23881
26224
|
objective: armedObjective,
|
|
23882
26225
|
planJson: JSON.stringify(plan),
|
|
23883
|
-
rubricGaps:
|
|
26226
|
+
rubricGaps: [
|
|
26227
|
+
rubric.blocking.map((g) => `${g.code}: ${g.note}`).join("\n") || "(none)",
|
|
26228
|
+
lastRoundtableDigest ? `ROUNDTABLE DIGEST (address or reject with instruments):
|
|
26229
|
+
${lastRoundtableDigest}` : ""
|
|
26230
|
+
].filter(Boolean).join("\n\n"),
|
|
23884
26231
|
healthSnapshot
|
|
23885
26232
|
}),
|
|
23886
26233
|
CRITIC_MAX_TOKENS
|
|
@@ -23938,6 +26285,9 @@ async function* strategistCraftSession(options) {
|
|
|
23938
26285
|
for await (const event of runIteration0({ ...options, objective: armedObjective })) {
|
|
23939
26286
|
if (event.type === "notice" && fallbackNotice(event.text)) {
|
|
23940
26287
|
job.from_fallback = true;
|
|
26288
|
+
} else if (event.type === "roundtable") {
|
|
26289
|
+
rememberRoundtable(event);
|
|
26290
|
+
yield event;
|
|
23941
26291
|
} else if (event.type === "plan") {
|
|
23942
26292
|
job.plan = lockPlanObjective(event.plan, armedObjective);
|
|
23943
26293
|
rememberBest(job.plan, null);
|
|
@@ -23983,6 +26333,135 @@ async function* strategistCraftSession(options) {
|
|
|
23983
26333
|
};
|
|
23984
26334
|
const nextRubric = scorePlan(job.plan);
|
|
23985
26335
|
job.rubric_gaps = nextRubric.gaps;
|
|
26336
|
+
const nextFp = planCounselFingerprint(job.plan);
|
|
26337
|
+
if (nextFp !== lastCounselFp) {
|
|
26338
|
+
yield {
|
|
26339
|
+
type: "notice",
|
|
26340
|
+
text: "Plan plays/constraints shifted \u2014 re-running counsel roundtable pass."
|
|
26341
|
+
};
|
|
26342
|
+
try {
|
|
26343
|
+
const profile = loadProfile();
|
|
26344
|
+
const tree = getActiveOrgTree(profile);
|
|
26345
|
+
const playIds = (job.plan.workstreams ?? []).flatMap((w) => w.play_ids ?? []);
|
|
26346
|
+
const refreshedConstraint = resolveConstraint({
|
|
26347
|
+
health: {
|
|
26348
|
+
gating_vital: options.computeResult.aggregate.gating_vital_sign,
|
|
26349
|
+
vital_readings: options.computeResult.aggregate.vital_signs.map((v) => ({
|
|
26350
|
+
vital_sign: v.vital_sign,
|
|
26351
|
+
score: v.score,
|
|
26352
|
+
status: v.status,
|
|
26353
|
+
dollar_value: v.dollar_value
|
|
26354
|
+
}))
|
|
26355
|
+
},
|
|
26356
|
+
objective: {
|
|
26357
|
+
objective: armedObjective,
|
|
26358
|
+
play_ids: playIds,
|
|
26359
|
+
operator_constraints: options.constraintsNote ? [options.constraintsNote] : []
|
|
26360
|
+
}
|
|
26361
|
+
});
|
|
26362
|
+
const priorConstraint = job.roundtable?.seating.constraint;
|
|
26363
|
+
const lockedOwners = priorConstraint ? [.../* @__PURE__ */ new Set([
|
|
26364
|
+
...priorConstraint.health_gate_owners,
|
|
26365
|
+
...refreshedConstraint.objective_owners
|
|
26366
|
+
])] : refreshedConstraint.locked_owners;
|
|
26367
|
+
const resolvedConstraint = priorConstraint ? {
|
|
26368
|
+
...refreshedConstraint,
|
|
26369
|
+
health_gate: priorConstraint.health_gate,
|
|
26370
|
+
health_gate_owners: priorConstraint.health_gate_owners,
|
|
26371
|
+
locked_owners: lockedOwners,
|
|
26372
|
+
primary_owner: priorConstraint.health_gate_owners[0] ?? refreshedConstraint.primary_owner,
|
|
26373
|
+
notes: [
|
|
26374
|
+
.../* @__PURE__ */ new Set([
|
|
26375
|
+
...priorConstraint.notes.filter(
|
|
26376
|
+
(note) => note.startsWith("Health gate:") || note.startsWith("Verified red metrics:")
|
|
26377
|
+
),
|
|
26378
|
+
...refreshedConstraint.notes.filter(
|
|
26379
|
+
(note) => !note.startsWith("Health gate:") && !note.startsWith("Verified red metrics:")
|
|
26380
|
+
)
|
|
26381
|
+
])
|
|
26382
|
+
]
|
|
26383
|
+
} : refreshedConstraint;
|
|
26384
|
+
const seating = buildRoundtableSeating({
|
|
26385
|
+
tree,
|
|
26386
|
+
objective: armedObjective,
|
|
26387
|
+
profile,
|
|
26388
|
+
playIds,
|
|
26389
|
+
gatingVital: options.computeResult.aggregate.gating_vital_sign,
|
|
26390
|
+
resolvedConstraint
|
|
26391
|
+
});
|
|
26392
|
+
const seatsBlock = seating.seats.filter((s) => s.pack_id !== "counsel_exec").map((s) => `- ${s.pack_id} (${s.weight})`).join("\n");
|
|
26393
|
+
const rtRaw = await callText(
|
|
26394
|
+
"strategist",
|
|
26395
|
+
buildRoundtableMessage({
|
|
26396
|
+
objective: armedObjective,
|
|
26397
|
+
tree,
|
|
26398
|
+
seatsBlock,
|
|
26399
|
+
biasNotes: [
|
|
26400
|
+
...seating.bias_notes,
|
|
26401
|
+
"Craft re-entry: no tools this pass \u2014 use counsel catalog from the system prompt."
|
|
26402
|
+
],
|
|
26403
|
+
draftPlanJson: JSON.stringify(job.plan),
|
|
26404
|
+
constraintOwner: resolvedConstraint.locked_owners.join(", ") || null,
|
|
26405
|
+
constraint: resolvedConstraint
|
|
26406
|
+
}),
|
|
26407
|
+
REVISE_MAX_TOKENS
|
|
26408
|
+
);
|
|
26409
|
+
job.cumulative_input_tokens += rtRaw.input;
|
|
26410
|
+
job.cumulative_output_tokens += rtRaw.output;
|
|
26411
|
+
const validation = parseAndValidateRoundtable(rtRaw.text, {
|
|
26412
|
+
seating,
|
|
26413
|
+
constraintOwners: resolvedConstraint.locked_owners,
|
|
26414
|
+
counselPulls: [],
|
|
26415
|
+
requireCounselPulls: false
|
|
26416
|
+
});
|
|
26417
|
+
const result = validation.blocking ? null : validation.result;
|
|
26418
|
+
lastRoundtableDigest = result ? formatRoundtableDigestFromResult(result) : "(Craft counsel re-entry failed structural validation; critic must not ship until Stage R is valid.)";
|
|
26419
|
+
const record = {
|
|
26420
|
+
seating,
|
|
26421
|
+
result,
|
|
26422
|
+
digest: lastRoundtableDigest,
|
|
26423
|
+
counsel_pulls: [],
|
|
26424
|
+
counsel_fingerprint: nextFp,
|
|
26425
|
+
issues: validation.issues,
|
|
26426
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
26427
|
+
};
|
|
26428
|
+
job.roundtable = record;
|
|
26429
|
+
job.roundtable_history.push(record);
|
|
26430
|
+
if (validation.blocking) {
|
|
26431
|
+
lastCounselFp = "";
|
|
26432
|
+
yield {
|
|
26433
|
+
type: "notice",
|
|
26434
|
+
text: `Craft roundtable invalid: ${validation.issues.filter((entry) => entry.severity === "blocking").map((entry) => entry.code).join(", ")}.`
|
|
26435
|
+
};
|
|
26436
|
+
} else {
|
|
26437
|
+
lastCounselFp = nextFp;
|
|
26438
|
+
}
|
|
26439
|
+
} catch (err) {
|
|
26440
|
+
lastCounselFp = "";
|
|
26441
|
+
if (job.roundtable) {
|
|
26442
|
+
const failed = {
|
|
26443
|
+
...job.roundtable,
|
|
26444
|
+
result: null,
|
|
26445
|
+
counsel_fingerprint: "",
|
|
26446
|
+
issues: [
|
|
26447
|
+
...job.roundtable.issues,
|
|
26448
|
+
{
|
|
26449
|
+
code: "roundtable_reentry_error",
|
|
26450
|
+
severity: "blocking",
|
|
26451
|
+
message: String(err.message ?? err)
|
|
26452
|
+
}
|
|
26453
|
+
],
|
|
26454
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
26455
|
+
};
|
|
26456
|
+
job.roundtable = failed;
|
|
26457
|
+
job.roundtable_history.push(failed);
|
|
26458
|
+
}
|
|
26459
|
+
yield {
|
|
26460
|
+
type: "notice",
|
|
26461
|
+
text: `Craft roundtable re-entry failed: ${String(err.message ?? err)}.`
|
|
26462
|
+
};
|
|
26463
|
+
}
|
|
26464
|
+
}
|
|
23986
26465
|
} else {
|
|
23987
26466
|
yield { type: "notice", text: "Revise JSON invalid \u2014 keeping prior plan." };
|
|
23988
26467
|
}
|
|
@@ -24041,8 +26520,11 @@ var init_strategist_craft = __esm({
|
|
|
24041
26520
|
init_context2();
|
|
24042
26521
|
init_strategist2();
|
|
24043
26522
|
init_strategist_validate();
|
|
26523
|
+
init_roundtable_validate();
|
|
24044
26524
|
init_strategist_rubric();
|
|
24045
26525
|
init_strategist_prompt();
|
|
26526
|
+
init_profile();
|
|
26527
|
+
init_gtm_counsel();
|
|
24046
26528
|
init_store3();
|
|
24047
26529
|
CRITIC_MAX_TOKENS = 2048;
|
|
24048
26530
|
REVISE_MAX_TOKENS = 8192;
|
|
@@ -24096,6 +26578,12 @@ function renderCraftHandoffMarkdown(opts) {
|
|
|
24096
26578
|
);
|
|
24097
26579
|
lines.push("");
|
|
24098
26580
|
}
|
|
26581
|
+
if (opts.roundtableDigest) {
|
|
26582
|
+
lines.push("## Counsel roundtable");
|
|
26583
|
+
lines.push("");
|
|
26584
|
+
lines.push(opts.roundtableDigest);
|
|
26585
|
+
lines.push("");
|
|
26586
|
+
}
|
|
24099
26587
|
if (plan.risks.length > 0) {
|
|
24100
26588
|
lines.push("## Risks");
|
|
24101
26589
|
lines.push("");
|
|
@@ -24135,7 +26623,8 @@ function writeCraftPlanHandoff(opts) {
|
|
|
24135
26623
|
constraintLine: opts.constraintLine,
|
|
24136
26624
|
killedAlternative: opts.killedAlternative,
|
|
24137
26625
|
outOfScope: opts.outOfScope,
|
|
24138
|
-
slug: opts.slug
|
|
26626
|
+
slug: opts.slug,
|
|
26627
|
+
roundtableDigest: opts.roundtableDigest
|
|
24139
26628
|
});
|
|
24140
26629
|
const path = resolveArchivePath("prompt:plan", `handoff-plan-${exportStamp()}.md`);
|
|
24141
26630
|
writeRedactedText(path, markdown);
|
|
@@ -24308,7 +26797,8 @@ async function executeStrategistJob(req) {
|
|
|
24308
26797
|
sessionId: req.ctx.sessionId,
|
|
24309
26798
|
constraintLine,
|
|
24310
26799
|
killedAlternative,
|
|
24311
|
-
slug: result.slug
|
|
26800
|
+
slug: result.slug,
|
|
26801
|
+
roundtableDigest: job?.roundtable?.digest
|
|
24312
26802
|
});
|
|
24313
26803
|
result.handoff_path = written.path;
|
|
24314
26804
|
result.inbox_path = written.inboxPath;
|
|
@@ -24964,6 +27454,7 @@ async function printKeylessSkeletonPlan(ctx, objective) {
|
|
|
24964
27454
|
if (snapshot) {
|
|
24965
27455
|
const { matchTriggeredPlays: matchTriggeredPlays2 } = await Promise.resolve().then(() => (init_playbook(), playbook_exports));
|
|
24966
27456
|
const { LAYERS: LAYERS2 } = await Promise.resolve().then(() => (init_health_score(), health_score_exports));
|
|
27457
|
+
const { getActiveOrgTree: getActiveOrgTree2 } = await Promise.resolve().then(() => (init_gtm_counsel(), gtm_counsel_exports));
|
|
24967
27458
|
const triggered = matchTriggeredPlays2(
|
|
24968
27459
|
snapshot.aggregate.vital_signs.map((v) => ({
|
|
24969
27460
|
vital_sign: v.vital_sign,
|
|
@@ -24972,7 +27463,8 @@ async function printKeylessSkeletonPlan(ctx, objective) {
|
|
|
24972
27463
|
dollar_value: v.dollar_value,
|
|
24973
27464
|
dollar_label: v.dollar_label
|
|
24974
27465
|
})),
|
|
24975
|
-
LAYERS2
|
|
27466
|
+
LAYERS2,
|
|
27467
|
+
{ tree: getActiveOrgTree2() }
|
|
24976
27468
|
);
|
|
24977
27469
|
if (triggered.length > 0) {
|
|
24978
27470
|
console.log(" " + chalk18.bold("Simple plan") + chalk18.dim(" \u2014 from computed vital signs. No AI."));
|
|
@@ -25380,6 +27872,8 @@ ${buildPlaybookBlock()}
|
|
|
25380
27872
|
|
|
25381
27873
|
${buildFrameworkCatalogBlock()}
|
|
25382
27874
|
|
|
27875
|
+
${buildCounselCatalogBlock()}
|
|
27876
|
+
|
|
25383
27877
|
${commandSection}
|
|
25384
27878
|
${formattingSection}
|
|
25385
27879
|
OUTPUT RULES:
|
|
@@ -25409,6 +27903,7 @@ var init_think_prompt = __esm({
|
|
|
25409
27903
|
"use strict";
|
|
25410
27904
|
init_prompt_parts();
|
|
25411
27905
|
init_frameworks();
|
|
27906
|
+
init_gtm_counsel();
|
|
25412
27907
|
init_prompt();
|
|
25413
27908
|
}
|
|
25414
27909
|
});
|
|
@@ -25638,6 +28133,8 @@ ${buildPlaybookBlock()}
|
|
|
25638
28133
|
|
|
25639
28134
|
${buildFrameworkCatalogBlock()}
|
|
25640
28135
|
|
|
28136
|
+
${buildCounselCatalogBlock()}
|
|
28137
|
+
|
|
25641
28138
|
${commandSection}`;
|
|
25642
28139
|
const stable = `You are a world-class GTM operating partner \u2014 the kind of analyst a CEO keeps on speed dial. You are exceptionally well-read, rigorous, and commercially sharp, and you have tools to query a local database of this company's CRM and pipeline data. The user is having an ongoing, free-form conversation with you about their go-to-market health and SaaS metrics.
|
|
25643
28140
|
|
|
@@ -25887,6 +28384,7 @@ var init_agentic_loop = __esm({
|
|
|
25887
28384
|
init_untrusted();
|
|
25888
28385
|
init_prompt_parts();
|
|
25889
28386
|
init_frameworks();
|
|
28387
|
+
init_gtm_counsel();
|
|
25890
28388
|
init_think_prompt();
|
|
25891
28389
|
init_prompt();
|
|
25892
28390
|
init_context2();
|
|
@@ -28774,7 +31272,7 @@ function stripFences2(text) {
|
|
|
28774
31272
|
return trimmed;
|
|
28775
31273
|
}
|
|
28776
31274
|
function validateTaxonomy(raw, profile) {
|
|
28777
|
-
const
|
|
31275
|
+
const strArray3 = (key, min = 3) => {
|
|
28778
31276
|
const v = raw[key];
|
|
28779
31277
|
if (!Array.isArray(v)) throw new Error(`Missing or invalid array: ${key}`);
|
|
28780
31278
|
const items = v.filter((x) => typeof x === "string" && x.trim().length > 0);
|
|
@@ -28804,14 +31302,14 @@ function validateTaxonomy(raw, profile) {
|
|
|
28804
31302
|
mid_market: pickTitles("mid_market"),
|
|
28805
31303
|
smb: pickTitles("smb")
|
|
28806
31304
|
};
|
|
28807
|
-
const rep_titles =
|
|
28808
|
-
const industries =
|
|
28809
|
-
const company_name_prefixes =
|
|
28810
|
-
const company_name_suffixes =
|
|
28811
|
-
const deal_verbs =
|
|
28812
|
-
const deal_modifiers =
|
|
28813
|
-
const topics =
|
|
28814
|
-
const challenges =
|
|
31305
|
+
const rep_titles = strArray3("rep_titles");
|
|
31306
|
+
const industries = strArray3("industries");
|
|
31307
|
+
const company_name_prefixes = strArray3("company_name_prefixes", 5);
|
|
31308
|
+
const company_name_suffixes = strArray3("company_name_suffixes", 3);
|
|
31309
|
+
const deal_verbs = strArray3("deal_verbs");
|
|
31310
|
+
const deal_modifiers = strArray3("deal_modifiers");
|
|
31311
|
+
const topics = strArray3("topics");
|
|
31312
|
+
const challenges = strArray3("challenges");
|
|
28815
31313
|
const rwRaw = raw["region_weights"];
|
|
28816
31314
|
if (!rwRaw || typeof rwRaw !== "object") throw new Error("Missing region_weights");
|
|
28817
31315
|
const rw = rwRaw;
|
|
@@ -29621,8 +32119,8 @@ var init_metrics_report = __esm({
|
|
|
29621
32119
|
function summarizeEntityDetails(details = []) {
|
|
29622
32120
|
const summary = {};
|
|
29623
32121
|
for (const detail of details) {
|
|
29624
|
-
const
|
|
29625
|
-
summary[
|
|
32122
|
+
const issue2 = typeof detail.issue === "string" ? detail.issue : "unclassified";
|
|
32123
|
+
summary[issue2] = (summary[issue2] ?? 0) + 1;
|
|
29626
32124
|
}
|
|
29627
32125
|
return summary;
|
|
29628
32126
|
}
|
|
@@ -29927,31 +32425,31 @@ function stripFences3(text) {
|
|
|
29927
32425
|
}
|
|
29928
32426
|
function validateDraft(raw) {
|
|
29929
32427
|
const out = {};
|
|
29930
|
-
const
|
|
32428
|
+
const str3 = (k) => {
|
|
29931
32429
|
const v = raw[k];
|
|
29932
32430
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
|
|
29933
32431
|
};
|
|
29934
|
-
const companyName =
|
|
32432
|
+
const companyName = str3("company_name");
|
|
29935
32433
|
if (companyName) out.company_name = companyName;
|
|
29936
|
-
const industry =
|
|
32434
|
+
const industry = str3("industry");
|
|
29937
32435
|
if (industry) out.industry = industry;
|
|
29938
|
-
const productDescription =
|
|
32436
|
+
const productDescription = str3("product_description");
|
|
29939
32437
|
if (productDescription) out.product_description = productDescription;
|
|
29940
|
-
const targetCustomer =
|
|
32438
|
+
const targetCustomer = str3("target_customer");
|
|
29941
32439
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
29942
|
-
const motion =
|
|
32440
|
+
const motion = str3("sales_motion")?.toLowerCase();
|
|
29943
32441
|
if (motion && ALLOWED_MOTIONS.has(motion)) {
|
|
29944
32442
|
out.sales_motion = motion;
|
|
29945
32443
|
}
|
|
29946
|
-
const dealSize =
|
|
32444
|
+
const dealSize = str3("average_deal_size");
|
|
29947
32445
|
if (dealSize) out.average_deal_size = dealSize;
|
|
29948
32446
|
const cycleDays = raw["sales_cycle_days"];
|
|
29949
32447
|
if (typeof cycleDays === "number" && Number.isFinite(cycleDays) && cycleDays > 0) {
|
|
29950
32448
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
29951
32449
|
}
|
|
29952
|
-
const crm =
|
|
32450
|
+
const crm = str3("primary_crm")?.toLowerCase();
|
|
29953
32451
|
if (crm && ALLOWED_CRMS.has(crm)) out.primary_crm = crm;
|
|
29954
|
-
const engagement =
|
|
32452
|
+
const engagement = str3("engagement_tool")?.toLowerCase();
|
|
29955
32453
|
if (engagement && ALLOWED_ENGAGEMENT.has(engagement)) out.engagement_tool = engagement;
|
|
29956
32454
|
return out;
|
|
29957
32455
|
}
|
|
@@ -30373,31 +32871,31 @@ Emit the refined profile now as STRICT JSON.`;
|
|
|
30373
32871
|
}
|
|
30374
32872
|
function validateRefined(raw) {
|
|
30375
32873
|
const out = {};
|
|
30376
|
-
const
|
|
32874
|
+
const str3 = (k) => {
|
|
30377
32875
|
const v = raw[k];
|
|
30378
32876
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
|
|
30379
32877
|
};
|
|
30380
|
-
const industry =
|
|
32878
|
+
const industry = str3("industry");
|
|
30381
32879
|
if (industry) out.industry = industry;
|
|
30382
|
-
const productDescription =
|
|
32880
|
+
const productDescription = str3("product_description");
|
|
30383
32881
|
if (productDescription) out.product_description = productDescription;
|
|
30384
|
-
const targetCustomer =
|
|
32882
|
+
const targetCustomer = str3("target_customer");
|
|
30385
32883
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
30386
|
-
const motion =
|
|
32884
|
+
const motion = str3("sales_motion")?.toLowerCase();
|
|
30387
32885
|
if (motion && ALLOWED_MOTIONS2.has(motion)) {
|
|
30388
32886
|
out.sales_motion = motion;
|
|
30389
32887
|
}
|
|
30390
|
-
const dealSize =
|
|
32888
|
+
const dealSize = str3("average_deal_size");
|
|
30391
32889
|
if (dealSize) out.average_deal_size = dealSize;
|
|
30392
32890
|
const cycleDays = raw["sales_cycle_days"];
|
|
30393
32891
|
if (typeof cycleDays === "number" && Number.isFinite(cycleDays) && cycleDays > 0) {
|
|
30394
32892
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
30395
32893
|
}
|
|
30396
|
-
const crm =
|
|
32894
|
+
const crm = str3("primary_crm")?.toLowerCase();
|
|
30397
32895
|
if (crm && ALLOWED_CRMS2.has(crm)) out.primary_crm = crm;
|
|
30398
|
-
const engagement =
|
|
32896
|
+
const engagement = str3("engagement_tool")?.toLowerCase();
|
|
30399
32897
|
if (engagement && ALLOWED_ENGAGEMENT2.has(engagement)) out.engagement_tool = engagement;
|
|
30400
|
-
const userScope =
|
|
32898
|
+
const userScope = str3("user_scope");
|
|
30401
32899
|
if (userScope) out.user_scope = userScope;
|
|
30402
32900
|
return out;
|
|
30403
32901
|
}
|
|
@@ -32209,8 +34707,8 @@ function generateMarkdownReport(data) {
|
|
|
32209
34707
|
lines.push("");
|
|
32210
34708
|
for (const entity of vs.entity_details.slice(0, 5)) {
|
|
32211
34709
|
const name = entity.name ?? entity.id ?? "Unknown";
|
|
32212
|
-
const
|
|
32213
|
-
lines.push(`- **${name}**: ${
|
|
34710
|
+
const issue2 = entity.issue ?? "";
|
|
34711
|
+
lines.push(`- **${name}**: ${issue2}`);
|
|
32214
34712
|
}
|
|
32215
34713
|
if (vs.entity_details.length > 5) {
|
|
32216
34714
|
lines.push(`- *...and ${vs.entity_details.length - 5} more*`);
|
|
@@ -36282,33 +38780,33 @@ Apply the feedback now as STRICT JSON.`;
|
|
|
36282
38780
|
}
|
|
36283
38781
|
function validatePatch(raw) {
|
|
36284
38782
|
const out = {};
|
|
36285
|
-
const
|
|
38783
|
+
const str3 = (k) => {
|
|
36286
38784
|
const v = raw[k];
|
|
36287
38785
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
|
|
36288
38786
|
};
|
|
36289
|
-
const industry =
|
|
38787
|
+
const industry = str3("industry");
|
|
36290
38788
|
if (industry) out.industry = industry;
|
|
36291
|
-
const productDescription =
|
|
38789
|
+
const productDescription = str3("product_description");
|
|
36292
38790
|
if (productDescription) out.product_description = productDescription;
|
|
36293
|
-
const targetCustomer =
|
|
38791
|
+
const targetCustomer = str3("target_customer");
|
|
36294
38792
|
if (targetCustomer) out.target_customer = targetCustomer;
|
|
36295
|
-
const motion =
|
|
38793
|
+
const motion = str3("sales_motion")?.toLowerCase();
|
|
36296
38794
|
if (motion && ALLOWED_MOTIONS3.has(motion)) {
|
|
36297
38795
|
out.sales_motion = motion;
|
|
36298
38796
|
}
|
|
36299
|
-
const dealSize =
|
|
38797
|
+
const dealSize = str3("average_deal_size");
|
|
36300
38798
|
if (dealSize) out.average_deal_size = dealSize;
|
|
36301
38799
|
const cycleDays = raw["sales_cycle_days"];
|
|
36302
38800
|
if (typeof cycleDays === "number" && Number.isFinite(cycleDays) && cycleDays > 0) {
|
|
36303
38801
|
out.sales_cycle_days = Math.round(cycleDays);
|
|
36304
38802
|
}
|
|
36305
|
-
const crm =
|
|
38803
|
+
const crm = str3("primary_crm")?.toLowerCase();
|
|
36306
38804
|
if (crm && ALLOWED_CRMS3.has(crm)) out.primary_crm = crm;
|
|
36307
|
-
const engagement =
|
|
38805
|
+
const engagement = str3("engagement_tool")?.toLowerCase();
|
|
36308
38806
|
if (engagement && ALLOWED_ENGAGEMENT3.has(engagement)) out.engagement_tool = engagement;
|
|
36309
|
-
const userScope =
|
|
38807
|
+
const userScope = str3("user_scope");
|
|
36310
38808
|
if (userScope) out.user_scope = userScope;
|
|
36311
|
-
const customContext =
|
|
38809
|
+
const customContext = str3("custom_context");
|
|
36312
38810
|
if (customContext) out.custom_context = customContext;
|
|
36313
38811
|
return out;
|
|
36314
38812
|
}
|
|
@@ -40278,12 +42776,13 @@ import { existsSync as existsSync37, readFileSync as readFileSync24 } from "fs";
|
|
|
40278
42776
|
import { join as join38 } from "path";
|
|
40279
42777
|
function buildCompanyProfileBlock() {
|
|
40280
42778
|
const p = loadProfile();
|
|
40281
|
-
if (!p) return
|
|
42779
|
+
if (!p) return buildOrgTreeContextLine(null);
|
|
40282
42780
|
const lines = [];
|
|
40283
42781
|
lines.push(`- Industry: ${sanitizeExternalText(p.industry)}`);
|
|
40284
42782
|
lines.push(`- Product: ${sanitizeExternalText(p.product_description)}`);
|
|
40285
42783
|
lines.push(`- Target customer: ${sanitizeExternalText(p.target_customer)}`);
|
|
40286
42784
|
lines.push(`- Sales motion: ${sanitizeExternalText(p.sales_motion)}`);
|
|
42785
|
+
lines.push(buildOrgTreeContextLine(p));
|
|
40287
42786
|
if (p.average_deal_size) lines.push(`- Avg deal size: ${sanitizeExternalText(String(p.average_deal_size))}`);
|
|
40288
42787
|
if (p.sales_cycle_days !== void 0) lines.push(`- Typical sales cycle: ~${p.sales_cycle_days} days`);
|
|
40289
42788
|
if (p.primary_crm) lines.push(`- Primary CRM: ${sanitizeExternalText(p.primary_crm)}`);
|
|
@@ -40377,6 +42876,7 @@ var init_prompt_parts = __esm({
|
|
|
40377
42876
|
"use strict";
|
|
40378
42877
|
init_profile();
|
|
40379
42878
|
init_store();
|
|
42879
|
+
init_gtm_counsel();
|
|
40380
42880
|
init_playbook();
|
|
40381
42881
|
init_play_outcomes();
|
|
40382
42882
|
init_registry2();
|
|
@@ -40425,9 +42925,15 @@ Restraint matters: NTRP is a stethoscope, not a surgeon. Observe, connect, and r
|
|
|
40425
42925
|
Expert read: weight by deal size \u2014 one single-threaded mega-deal outweighs ten small ones. Single-threading late in the cycle is far more dangerous than early. In enterprise motions, thread depth is a leading indicator of slipped quarters: champions change jobs, and there's no second door in.`;
|
|
40426
42926
|
PLAYBOOK_BLOCK = `- "Multi-Thread Your Deals" (id: multi-thread-deals) \u2014 when thread_depth is low
|
|
40427
42927
|
- "Clean Dead Pipeline" (id: clean-dead-pipeline) \u2014 when freshness is low
|
|
40428
|
-
- "Fix the Handoff Gap" (id: fix-handoff-gap) \u2014 when drop_rate is high
|
|
42928
|
+
- "Fix the Handoff Gap" (id: fix-handoff-gap) \u2014 when drop_rate is high and the source\u2192owner path is unknown
|
|
40429
42929
|
- "Retarget Misdirected Effort" (id: retarget-effort) \u2014 when signal_to_noise is low
|
|
40430
|
-
- "Unstick the Pipeline" (id: unstick-pipeline) \u2014 when flow_rate is low
|
|
42930
|
+
- "Unstick the Pipeline" (id: unstick-pipeline) \u2014 when flow_rate is low
|
|
42931
|
+
- "Harden Routing & Acceptance SLA" (id: harden-routing-sla) \u2014 when the handoff path exists but SLA/observability is the leak
|
|
42932
|
+
- "Demand Quality over Volume" (id: demand-quality-over-volume) \u2014 when MQL\u2192SQL\u2192Opp quality is weak (not a drop_rate routing play)
|
|
42933
|
+
- "Sales\u2192CS Handoff Packet" (id: sales-cs-handoff-packet) \u2014 when post-sale handoffs create churn risk
|
|
42934
|
+
- "Renewal Early Warning" (id: renewal-early-warning) \u2014 when renewals scramble late
|
|
42935
|
+
- "ABM Orchestration on Named Accounts" (id: abm-orchestration) \u2014 enterprise/named-account list pipeline; kill on smb_velocity without a named list
|
|
42936
|
+
- "Restore Forecast Ritual Hygiene" (id: forecast-ritual-hygiene) \u2014 after instrument trust, when commit evidence and past-due closes undermine forecast credibility`;
|
|
40431
42937
|
DESTRUCTIVE_COMMAND_NOTES = {
|
|
40432
42938
|
reset: "destructive \u2014 wipes all data, requires --force"
|
|
40433
42939
|
};
|