@sonnechasser/ntrp 0.2.3 → 0.3.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.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ process.noDeprecation = true;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
4
  var __esm = (fn, res) => function __init() {
4
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -18,9 +19,45 @@ var init_context = __esm({
18
19
  }
19
20
  });
20
21
 
22
+ // src/output/formatters.ts
23
+ var init_formatters = __esm({
24
+ "src/output/formatters.ts"() {
25
+ "use strict";
26
+ }
27
+ });
28
+
29
+ // src/services/terminal-capture.ts
30
+ var init_terminal_capture = __esm({
31
+ "src/services/terminal-capture.ts"() {
32
+ "use strict";
33
+ }
34
+ });
35
+
36
+ // src/services/context-doc.ts
37
+ import { writeFileSync } from "fs";
38
+ var init_context_doc = __esm({
39
+ "src/services/context-doc.ts"() {
40
+ "use strict";
41
+ init_context2();
42
+ init_formatters();
43
+ init_terminal_capture();
44
+ }
45
+ });
46
+
47
+ // src/services/transcript.ts
48
+ import { existsSync, readFileSync, writeFileSync as writeFileSync2, rmSync } from "fs";
49
+ import { join } from "path";
50
+ var init_transcript = __esm({
51
+ "src/services/transcript.ts"() {
52
+ "use strict";
53
+ init_context2();
54
+ init_terminal_capture();
55
+ }
56
+ });
57
+
21
58
  // src/cli/context.ts
22
- import { basename, join, resolve, sep } from "path";
23
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, statSync, rmSync } from "fs";
59
+ import { basename, join as join2, resolve, sep } from "path";
60
+ import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync2, readdirSync, statSync, rmSync as rmSync2 } from "fs";
24
61
  import { homedir } from "os";
25
62
  import { randomUUID } from "crypto";
26
63
  function isAnalysisReady(ctx) {
@@ -32,24 +69,28 @@ function isAnalysisReady(ctx) {
32
69
  function defaultSessionAnalysis(primary = "gtm_health") {
33
70
  return { primary, completed: [] };
34
71
  }
72
+ var STALE_SESSION_MS;
35
73
  var init_context2 = __esm({
36
74
  "src/cli/context.ts"() {
37
75
  "use strict";
38
76
  init_thread_compat();
39
77
  init_context();
78
+ init_context_doc();
79
+ init_transcript();
80
+ STALE_SESSION_MS = 14 * 24 * 60 * 60 * 1e3;
40
81
  }
41
82
  });
42
83
 
43
84
  // src/config/store.ts
44
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
85
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
45
86
  import { homedir as homedir2 } from "os";
46
- import { join as join2, resolve as resolve2 } from "path";
87
+ import { join as join3, resolve as resolve2 } from "path";
47
88
  function ntrpHome() {
48
89
  return NTRP_DIR;
49
90
  }
50
91
  function getMemoryDir() {
51
- const dir = join2(NTRP_DIR, "memory");
52
- if (!existsSync2(dir)) {
92
+ const dir = join3(NTRP_DIR, "memory");
93
+ if (!existsSync3(dir)) {
53
94
  mkdirSync2(dir, { recursive: true });
54
95
  }
55
96
  return dir;
@@ -58,43 +99,36 @@ var NTRP_DIR, CONFIG_PATH;
58
99
  var init_store = __esm({
59
100
  "src/config/store.ts"() {
60
101
  "use strict";
61
- NTRP_DIR = process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join2(homedir2(), ".ntrp");
62
- CONFIG_PATH = join2(NTRP_DIR, "config.json");
102
+ NTRP_DIR = process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join3(homedir2(), ".ntrp");
103
+ CONFIG_PATH = join3(NTRP_DIR, "config.json");
63
104
  }
64
105
  });
65
106
 
66
107
  // src/config/profile.ts
67
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
68
- import { join as join3 } from "path";
108
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
109
+ import { join as join4 } from "path";
69
110
  var NTRP_DIR2, PROFILE_PATH;
70
111
  var init_profile = __esm({
71
112
  "src/config/profile.ts"() {
72
113
  "use strict";
73
114
  init_store();
74
115
  NTRP_DIR2 = ntrpHome();
75
- PROFILE_PATH = join3(NTRP_DIR2, "profile.json");
116
+ PROFILE_PATH = join4(NTRP_DIR2, "profile.json");
76
117
  }
77
118
  });
78
119
 
79
120
  // src/db/connection.ts
80
- import { dirname, join as join4, resolve as resolve3 } from "path";
121
+ import { dirname, join as join5, resolve as resolve3 } from "path";
81
122
  var NTRP_DIR3, DEFAULT_DB_PATH, DB_PATH_PINNED;
82
123
  var init_connection = __esm({
83
124
  "src/db/connection.ts"() {
84
125
  "use strict";
85
- NTRP_DIR3 = process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join4(process.env.HOME ?? "", ".ntrp");
86
- DEFAULT_DB_PATH = process.env.NTRP_DB_PATH ? resolve3(process.env.NTRP_DB_PATH) : join4(NTRP_DIR3, "ntrp.duckdb");
126
+ NTRP_DIR3 = process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join5(process.env.HOME ?? "", ".ntrp");
127
+ DEFAULT_DB_PATH = process.env.NTRP_DB_PATH ? resolve3(process.env.NTRP_DB_PATH) : join5(NTRP_DIR3, "ntrp.duckdb");
87
128
  DB_PATH_PINNED = !!process.env.NTRP_DB_PATH;
88
129
  }
89
130
  });
90
131
 
91
- // src/output/formatters.ts
92
- var init_formatters = __esm({
93
- "src/output/formatters.ts"() {
94
- "use strict";
95
- }
96
- });
97
-
98
132
  // src/db/queries.ts
99
133
  var init_queries = __esm({
100
134
  "src/db/queries.ts"() {
@@ -449,6 +483,14 @@ var init_insights = __esm({
449
483
  }
450
484
  });
451
485
 
486
+ // src/ai/explore-mode.ts
487
+ var init_explore_mode = __esm({
488
+ "src/ai/explore-mode.ts"() {
489
+ "use strict";
490
+ init_context2();
491
+ }
492
+ });
493
+
452
494
  // src/ai/llm/models-cache.ts
453
495
  var CACHE_TTL_MS;
454
496
  var init_models_cache = __esm({
@@ -579,6 +621,8 @@ var init_phase = __esm({
579
621
  "src/conversation/phase.ts"() {
580
622
  "use strict";
581
623
  init_context2();
624
+ init_repl_api();
625
+ init_explore_mode();
582
626
  init_session_state();
583
627
  init_theme();
584
628
  }
@@ -598,8 +642,8 @@ var init_gap_audit = __esm({
598
642
  });
599
643
 
600
644
  // src/strategies/library.ts
601
- import { writeFileSync as writeFileSync4 } from "fs";
602
- import { join as join5 } from "path";
645
+ import { writeFileSync as writeFileSync6 } from "fs";
646
+ import { join as join6 } from "path";
603
647
  import { stringify as stringifyYaml } from "yaml";
604
648
  var init_library = __esm({
605
649
  "src/strategies/library.ts"() {
@@ -641,10 +685,17 @@ var init_strategist = __esm({
641
685
  }
642
686
  });
643
687
 
688
+ // src/ai/llm/types.ts
689
+ var init_types2 = __esm({
690
+ "src/ai/llm/types.ts"() {
691
+ "use strict";
692
+ }
693
+ });
694
+
644
695
  // src/config/install.ts
645
696
  import { randomUUID as randomUUID2 } from "crypto";
646
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
647
- import { join as join6 } from "path";
697
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync7 } from "fs";
698
+ import { join as join7 } from "path";
648
699
  var init_install = __esm({
649
700
  "src/config/install.ts"() {
650
701
  "use strict";
@@ -653,8 +704,8 @@ var init_install = __esm({
653
704
  });
654
705
 
655
706
  // src/config/progress-migrate.ts
656
- import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync6 } from "fs";
657
- import { join as join7 } from "path";
707
+ import { existsSync as existsSync6, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync8 } from "fs";
708
+ import { join as join8 } from "path";
658
709
  var init_progress_migrate = __esm({
659
710
  "src/config/progress-migrate.ts"() {
660
711
  "use strict";
@@ -670,8 +721,8 @@ var init_usage_backfill = __esm({
670
721
  });
671
722
 
672
723
  // src/config/progress.ts
673
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "fs";
674
- import { join as join8 } from "path";
724
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
725
+ import { join as join9 } from "path";
675
726
  var init_progress = __esm({
676
727
  "src/config/progress.ts"() {
677
728
  "use strict";
@@ -690,13 +741,6 @@ var init_usage_stats = __esm({
690
741
  }
691
742
  });
692
743
 
693
- // src/ai/llm/types.ts
694
- var init_types2 = __esm({
695
- "src/ai/llm/types.ts"() {
696
- "use strict";
697
- }
698
- });
699
-
700
744
  // src/ai/llm/errors.ts
701
745
  var init_errors2 = __esm({
702
746
  "src/ai/llm/errors.ts"() {
@@ -711,6 +755,7 @@ var init_anthropic = __esm({
711
755
  "src/ai/llm/adapters/anthropic.ts"() {
712
756
  "use strict";
713
757
  init_errors2();
758
+ init_types2();
714
759
  }
715
760
  });
716
761
 
@@ -720,6 +765,7 @@ var init_openai_compat = __esm({
720
765
  "src/ai/llm/adapters/openai-compat.ts"() {
721
766
  "use strict";
722
767
  init_errors2();
768
+ init_types2();
723
769
  }
724
770
  });
725
771
 
@@ -805,12 +851,26 @@ var init_tool_schemas = __esm({
805
851
 
806
852
  // src/ai/privacy.ts
807
853
  import { homedir as homedir3 } from "os";
808
- import { join as join9 } from "path";
854
+ import { join as join10 } from "path";
809
855
  var AUDIT_DIR;
810
856
  var init_privacy = __esm({
811
857
  "src/ai/privacy.ts"() {
812
858
  "use strict";
813
- AUDIT_DIR = join9(homedir3(), ".ntrp", "audit");
859
+ AUDIT_DIR = join10(homedir3(), ".ntrp", "audit");
860
+ }
861
+ });
862
+
863
+ // src/ai/untrusted.ts
864
+ var UNTRUSTED_MARKER_NAME, UNTRUSTED_MARKER_END_NAME, MARKER_SPOOF_PATTERN;
865
+ var init_untrusted = __esm({
866
+ "src/ai/untrusted.ts"() {
867
+ "use strict";
868
+ UNTRUSTED_MARKER_NAME = "EXTERNAL_UNTRUSTED_CONTENT";
869
+ UNTRUSTED_MARKER_END_NAME = "END_EXTERNAL_UNTRUSTED_CONTENT";
870
+ MARKER_SPOOF_PATTERN = new RegExp(
871
+ `<{2,}\\s*/?\\s*(?:${UNTRUSTED_MARKER_NAME}|${UNTRUSTED_MARKER_END_NAME})[^>]*>{2,}`,
872
+ "gi"
873
+ );
814
874
  }
815
875
  });
816
876
 
@@ -821,20 +881,40 @@ var init_tool_handlers = __esm({
821
881
  init_connection();
822
882
  init_privacy();
823
883
  init_web_search();
884
+ init_untrusted();
885
+ }
886
+ });
887
+
888
+ // src/ai/loop-guard.ts
889
+ var init_loop_guard = __esm({
890
+ "src/ai/loop-guard.ts"() {
891
+ "use strict";
892
+ }
893
+ });
894
+
895
+ // src/ai/thread.ts
896
+ var PRUNED_TOOL_RESULT;
897
+ var init_thread = __esm({
898
+ "src/ai/thread.ts"() {
899
+ "use strict";
900
+ PRUNED_TOOL_RESULT = JSON.stringify({
901
+ pruned: true,
902
+ note: "Old tool result cleared to free context \u2014 call the tool again if you still need it."
903
+ });
824
904
  }
825
905
  });
826
906
 
827
907
  // src/data/playbook.ts
828
- import { existsSync as existsSync7, readFileSync as readFileSync7, appendFileSync } from "fs";
829
- import { join as join10 } from "path";
908
+ import { existsSync as existsSync8, readFileSync as readFileSync8, appendFileSync } from "fs";
909
+ import { join as join11 } from "path";
830
910
  function playsPath() {
831
- return join10(getMemoryDir(), PLAYS_FILE);
911
+ return join11(getMemoryDir(), PLAYS_FILE);
832
912
  }
833
913
  function getCustomPlays() {
834
914
  const path = playsPath();
835
- if (!existsSync7(path)) return [];
915
+ if (!existsSync8(path)) return [];
836
916
  const out = [];
837
- for (const line of readFileSync7(path, "utf-8").split("\n")) {
917
+ for (const line of readFileSync8(path, "utf-8").split("\n")) {
838
918
  const trimmed = line.trim();
839
919
  if (!trimmed) continue;
840
920
  try {
@@ -880,80 +960,80 @@ var init_playbook = __esm({
880
960
  name: "Multi-Thread Your Deals",
881
961
  trigger_vital_sign: "thread_depth",
882
962
  trigger_condition: "thread_depth score < 2 (most deals have \u22641 active contact)",
883
- why: "Single-threaded deals die when your one contact goes dark, changes roles, or loses budget authority. Every deal needs at least 2 active contacts to survive.",
963
+ why: "Single-threaded deals die when your one contact goes dark, changes roles, or loses budget authority. Every deal needs at least 2 active contacts to survive \u2014 and late-stage single-threading is a leading indicator of a slipped quarter.",
884
964
  steps: [
885
- "Pull the list of single-threaded deals from entity_details",
886
- "For each deal, identify the existing contact and their role",
887
- "Research the account org chart for a second entry point (champion, economic buyer, or technical evaluator)",
888
- "Send a warm intro or multi-thread via your existing contact",
889
- "Log the new contact as an activity linked to the opportunity"
965
+ "Pull single-threaded deals weighted by amount \u2014 one exposed mega-deal outranks ten small ones",
966
+ "Enrich the buying committee for each: map champion, economic buyer, and technical evaluator (enrichment waterfall or manual research)",
967
+ "Multi-thread through the existing contact first \u2014 a warm internal referral beats a cold second thread",
968
+ "Log every new contact with a role against the opportunity so thread depth is measured, not remembered",
969
+ "Install the mechanism: an alert when any deal past mid-stage has one active contact, and a job-change signal on champions so you hear about departures before the deal goes quiet"
890
970
  ],
891
- tools_that_help: ["LinkedIn Sales Navigator", "Org chart tools", "CRM contact roles"],
892
- expected_outcome: "Thread depth score rises above threshold; single-threaded deal count drops by 50%+ within 2 weeks"
971
+ tools_that_help: ["Buying-committee enrichment (waterfall)", "Job-change signal tracking", "CRM contact roles", "Single-thread alerts"],
972
+ expected_outcome: "Thread depth score rises above threshold; single-threaded deal count drops by 50%+ within 2 weeks; zero late-stage deals with one thread"
893
973
  },
894
974
  {
895
975
  id: "clean-dead-pipeline",
896
976
  name: "Clean Dead Pipeline",
897
977
  trigger_vital_sign: "freshness",
898
978
  trigger_condition: "freshness score < 60",
899
- why: "Stale accounts and zombie deals inflate your pipeline number but deliver zero revenue. They waste forecasting accuracy and hide the real health of your business.",
979
+ why: "Stale accounts and zombie deals inflate your pipeline number but deliver zero revenue. They corrupt the forecast, and they hide the real coverage math \u2014 you can't fix what the CRM is lying about. Clearing them is also the cheapest pipeline you'll ever source: those records are already paid for.",
900
980
  steps: [
901
- "Export the stale opportunities list from entity_details",
902
- "For each stale deal: contact the prospect within 48 hours or move to Closed Lost",
903
- "For stale contacts: send a re-engagement sequence or archive",
904
- "For stale organizations: verify they still match your ICP; archive if not",
905
- "Set a recurring pipeline hygiene review (weekly 15-min scrub)"
981
+ "Split the stale pool into saveable vs already-dead: contacted-recently-enough-to-revive vs fiction to clear",
982
+ "Saveable deals: contact within 48 hours with a specific reason to talk, or move to Closed Lost \u2014 inaction is the worst choice",
983
+ "Dead-but-paid-for records: route into a signal-triggered reactivation track (funding, hiring, job-change, site-visit triggers) instead of deleting them",
984
+ "Stale organizations: re-verify ICP fit before re-working; archive what no longer fits so reps stop fishing in dead water",
985
+ "Install the mechanism: a stale-deal alert at N quiet days (calibrated to this motion's cycle), a weekly 15-minute hygiene scrub, and enrichment refresh on records that go quiet"
906
986
  ],
907
- tools_that_help: ["CRM bulk update", "Email sequences", "Pipeline review meeting template"],
908
- expected_outcome: "Freshness score jumps 20+ points; pipeline accuracy improves; forecast confidence increases"
987
+ tools_that_help: ["CRM bulk update", "Signal-based reactivation triggers", "Enrichment refresh (waterfall)", "Pipeline hygiene cadence"],
988
+ expected_outcome: "Freshness score jumps 20+ points; forecast reflects reality; reactivation track produces meetings at a fraction of cold-acquisition cost"
909
989
  },
910
990
  {
911
991
  id: "fix-handoff-gap",
912
992
  name: "Fix the Handoff Gap",
913
993
  trigger_vital_sign: "drop_rate",
914
994
  trigger_condition: "drop_rate score indicates >30% of marketing leads not reaching sales",
915
- why: "Every lead that marketing generates but sales never sees is wasted budget and lost revenue. The marketing\u2192sales handoff is the #1 leak in most GTM motions.",
995
+ why: "Every lead that marketing generates but sales never sees is wasted budget and lost revenue. The marketing\u2192sales handoff is the #1 leak in most GTM motions \u2014 and it is almost always a systems failure (routing, sync, dead queues), not a people failure.",
916
996
  steps: [
917
- "Audit the marketing\u2192sales handoff: which leads exist only in marketing systems?",
918
- "Check lead routing rules \u2014 are MQLs being assigned to active reps?",
919
- "Verify SLA: how quickly are handed-off leads being touched by sales?",
920
- "Fix routing gaps: unassigned territories, inactive rep queues, missing automation",
921
- "Set up a weekly handoff report showing marketing-only leads"
997
+ "Audit the leak by source: which lead sources exist only in marketing systems and never reach the CRM or a rep queue? The leak usually concentrates in one or two sources",
998
+ "Trace the routing path end-to-end: assignment rules, territory coverage, inactive-rep queues, and the marketing\u2192CRM sync itself \u2014 find where records fall on the floor",
999
+ "Fix the pipes: repair routing gaps, reassign orphaned queues, and dedupe/enrich records so routing has the fields it needs to route",
1000
+ "Set the SLA and instrument it: time-to-first-touch on handed-off leads, with a report someone owns",
1001
+ "Install the mechanism: an automated weekly marketing-only-leads report and an alert when any source's handoff rate degrades \u2014 so the leak can't quietly reopen"
922
1002
  ],
923
- tools_that_help: ["Lead routing audit", "SLA dashboard", "Marketing\u2192Sales sync tool"],
924
- expected_outcome: "Drop rate improves 15+ points; marketing-only lead count drops by 60%+"
1003
+ tools_that_help: ["Lead routing audit", "Enrichment waterfall (routing fields)", "SLA dashboard", "Handoff-degradation alerts"],
1004
+ expected_outcome: "Drop rate improves 15+ points; marketing-only lead count drops by 60%+; time-to-first-touch inside SLA"
925
1005
  },
926
1006
  {
927
1007
  id: "retarget-effort",
928
1008
  name: "Retarget Misdirected Effort",
929
1009
  trigger_vital_sign: "signal_to_noise",
930
1010
  trigger_condition: "signal_to_noise score < 50% (majority of activities not linked to pipeline)",
931
- why: "When reps spend more than half their time on activities unconnected to open pipeline, they're burning hours that could be closing deals. Every noisy activity is time stolen from revenue.",
1011
+ why: "When reps spend more than half their time on activities unconnected to open pipeline, they're burning hours that could be closing deals. Persistent noise is a targeting-system problem \u2014 reps fish in the pond they can see because the account lists are stale \u2014 not a coaching problem.",
932
1012
  steps: [
933
- "Review the noisy activities from entity_details \u2014 what types dominate?",
934
- "Identify root cause: are reps prospecting dead accounts? Logging admin tasks? Working closed deals?",
935
- "Create a 'focus list' of pipeline-linked accounts for each rep",
936
- "Set activity targets: 80% of weekly activities should touch open pipeline",
937
- "Remove or automate low-value activities that generate noise"
1013
+ "Cut noisy activity by rep and account status: dead accounts, closed deals, unlinked admin \u2014 name what dominates",
1014
+ "Fix the pond, not the fishing: rebuild rep focus lists from ICP fit and live signals (intent, hiring, funding, usage) instead of memory",
1015
+ "Route signals to reps in the channel they already work in, so the next action is the scored account, not the familiar one",
1016
+ "Set the ratio target and instrument it: 80% of weekly activities touch open pipeline or scored accounts, on a per-rep report",
1017
+ "Automate or delete the noise-generating busywork (logging, list building, manual research) so the time actually moves to pipeline"
938
1018
  ],
939
- tools_that_help: ["Activity reports by rep", "Focus account lists", "CRM activity rules"],
940
- expected_outcome: "Signal-to-noise ratio improves to 70%+; rep productivity increases measurably"
1019
+ tools_that_help: ["Activity reports by rep", "ICP/propensity scoring", "Signal routing to rep channels", "Enrichment automation"],
1020
+ expected_outcome: "Signal-to-noise ratio improves to 70%+; rep hours shift measurably from dead accounts to scored pipeline"
941
1021
  },
942
1022
  {
943
1023
  id: "unstick-pipeline",
944
1024
  name: "Unstick the Pipeline",
945
1025
  trigger_vital_sign: "flow_rate",
946
1026
  trigger_condition: "flow_rate score < 50 (high average deal age or many stuck deals)",
947
- why: "Stuck deals block revenue and demoralize reps. A deal that hasn't moved in 14+ days is either dead or needs intervention \u2014 either way, inaction is the worst choice.",
1027
+ why: "Stuck deals block revenue and demoralize reps. A deal that hasn't moved in 14+ days (calibrate to this motion's cycle) is either dead or needs intervention \u2014 and stuck deals with past-due close dates are a forecast-credibility problem before they're a revenue problem.",
948
1028
  steps: [
949
- "Pull stuck deals sorted by amount (highest value first)",
950
- "For each stuck deal: identify the blocker (no next step? waiting on prospect? internal approval?)",
951
- "Create a specific next action with a deadline for each stuck deal",
952
- "Escalate deals stuck >30 days to manager review",
953
- "Consider past-due deals: update close dates or move to Closed Lost"
1029
+ "Pull stuck deals sorted by amount, and find the stage where they cluster \u2014 there is usually one stage where deals go to die",
1030
+ "For each stuck deal: name the blocker (no next step, waiting on prospect, internal approval, missing stakeholder) \u2014 'stuck' is a symptom, the blocker is the work",
1031
+ "Create a specific next action with a deadline for each; deals with no plausible next action get triaged to Closed Lost so the forecast tells the truth",
1032
+ "Fix the stage, not just the deals: add exit criteria and a required-next-step field to the stage where deals cluster",
1033
+ "Install the mechanism: an aging alert at the motion-calibrated threshold and automatic manager escalation past 2x median stage duration"
954
1034
  ],
955
- tools_that_help: ["Deal inspection reports", "Next-step templates", "Manager escalation workflow"],
956
- expected_outcome: "Flow rate score improves 15+ points; stuck deal count drops by 40%+ within 2 weeks"
1035
+ tools_that_help: ["Deal inspection reports", "Stage exit criteria", "Aging alerts", "Manager escalation workflow"],
1036
+ expected_outcome: "Flow rate score improves 15+ points; stuck deal count drops by 40%+ within 2 weeks; the die-stage conversion measurably improves"
957
1037
  },
958
1038
  {
959
1039
  id: "reduce-logo-churn",
@@ -1069,6 +1149,17 @@ var init_playbook = __esm({
1069
1149
  }
1070
1150
  });
1071
1151
 
1152
+ // src/memory/play-outcomes.ts
1153
+ import { existsSync as existsSync9, readFileSync as readFileSync9, appendFileSync as appendFileSync2 } from "fs";
1154
+ import { join as join12 } from "path";
1155
+ import { randomUUID as randomUUID3 } from "crypto";
1156
+ var init_play_outcomes = __esm({
1157
+ "src/memory/play-outcomes.ts"() {
1158
+ "use strict";
1159
+ init_store();
1160
+ }
1161
+ });
1162
+
1072
1163
  // src/workflows/registry.ts
1073
1164
  var init_registry = __esm({
1074
1165
  "src/workflows/registry.ts"() {
@@ -1077,11 +1168,15 @@ var init_registry = __esm({
1077
1168
  });
1078
1169
 
1079
1170
  // src/ai/prompt-parts.ts
1171
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1172
+ import { join as join13 } from "path";
1080
1173
  var init_prompt_parts = __esm({
1081
1174
  "src/ai/prompt-parts.ts"() {
1082
1175
  "use strict";
1083
1176
  init_profile();
1177
+ init_store();
1084
1178
  init_playbook();
1179
+ init_play_outcomes();
1085
1180
  init_registry();
1086
1181
  }
1087
1182
  });
@@ -1458,6 +1553,7 @@ var init_strategist_validate = __esm({
1458
1553
  var init_strategist2 = __esm({
1459
1554
  "src/ai/strategist.ts"() {
1460
1555
  "use strict";
1556
+ init_types2();
1461
1557
  init_failover();
1462
1558
  init_surfaces();
1463
1559
  init_llm_config();
@@ -1465,6 +1561,8 @@ var init_strategist2 = __esm({
1465
1561
  init_tool_schemas();
1466
1562
  init_web_search();
1467
1563
  init_tool_handlers();
1564
+ init_loop_guard();
1565
+ init_thread();
1468
1566
  init_strategist_prompt();
1469
1567
  init_strategist_validate();
1470
1568
  }